114 lines
3.9 KiB
Markdown
114 lines
3.9 KiB
Markdown
# Andishkade Foolad — Admin Panel
|
|
|
|
A standalone Node/Express + SQLite admin panel for managing articles published on the Andishkade Foolad site.
|
|
|
|
## Features
|
|
|
|
- Login (single admin, bcrypt-hashed password, JWT session)
|
|
- CRUD for articles with all fields used on the site:
|
|
title, category, type, author / role / initial, publish date (Jalali string),
|
|
pages, price, isFree, summary, body, cover image, tags, featured flag
|
|
- Cover image upload (multer, stored on disk under `uploads/`)
|
|
- Public read API (`GET /api/articles`) that the main React site can consume
|
|
|
|
## Stack
|
|
|
|
- Node 20+
|
|
- Express 4
|
|
- `better-sqlite3` (single-file `data.db`)
|
|
- `multer` for uploads
|
|
- `bcryptjs` + `jsonwebtoken` for auth
|
|
- Vanilla JS admin UI (no build step)
|
|
|
|
---
|
|
|
|
## First-time setup
|
|
|
|
```bash
|
|
cd panel
|
|
npm install
|
|
cp .env.example .env # then edit .env, especially JWT_SECRET and ADMIN_PASSWORD
|
|
npm run seed # creates the admin user from .env
|
|
npm start
|
|
```
|
|
|
|
Then open <http://localhost:3001>.
|
|
|
|
Login with the `ADMIN_USERNAME` / `ADMIN_PASSWORD` values from `.env`.
|
|
|
|
## API
|
|
|
|
All write endpoints require `Authorization: Bearer <token>`. The token is returned by `POST /api/auth/login`.
|
|
|
|
| Method | Path | Auth | Purpose |
|
|
|--------|-----------------------|------|-----------------------------------|
|
|
| POST | `/api/auth/login` | — | `{username, password}` → `{token, user}` |
|
|
| GET | `/api/auth/me` | yes | current user info |
|
|
| GET | `/api/articles` | — | list articles (newest first) |
|
|
| GET | `/api/articles/:id` | — | single article |
|
|
| POST | `/api/articles` | yes | create article |
|
|
| PUT | `/api/articles/:id` | yes | update article |
|
|
| DELETE | `/api/articles/:id` | yes | delete article |
|
|
| POST | `/api/uploads` | yes | multipart `file` → `{url}` |
|
|
| GET | `/api/health` | — | `{ok: true}` |
|
|
|
|
### Article shape
|
|
|
|
```jsonc
|
|
{
|
|
"id": "abc123…",
|
|
"title": "…",
|
|
"category": "بازار جهانی",
|
|
"type": "special",
|
|
"author": "دکتر علی محمدی",
|
|
"authorRole": "مدیر ارشد پژوهش",
|
|
"authorInitial": "م",
|
|
"publishDate": "بهمن ۱۴۰۳",
|
|
"pages": 84,
|
|
"price": 850000,
|
|
"isFree": false,
|
|
"summary": "…",
|
|
"body": "متن کامل …",
|
|
"coverImage": "/uploads/abc.jpg",
|
|
"tags": ["تجارت جهانی", "صادرات"],
|
|
"featured": true,
|
|
"createdAt": "2026-05-26T13:20:00.000Z",
|
|
"updatedAt": "2026-05-26T13:20:00.000Z"
|
|
}
|
|
```
|
|
|
|
This shape is intentionally compatible with the `Report` type in [`../src/data/reports.ts`](../src/data/reports.ts), so the main site can swap the hardcoded array for `fetch('/api/articles')` without touching its UI components.
|
|
|
|
## Wiring the main site to the panel
|
|
|
|
In the main Vite app, replace the hardcoded import:
|
|
|
|
```ts
|
|
// src/lib/articles.ts
|
|
const API = import.meta.env.VITE_PANEL_API || 'http://localhost:3001';
|
|
|
|
export async function fetchReports() {
|
|
const res = await fetch(`${API}/api/articles`);
|
|
return res.json();
|
|
}
|
|
```
|
|
|
|
Then in the consuming components (e.g. `LatestNewsBentoSection.tsx`), call `fetchReports()` inside a `useEffect` / React Query hook instead of the static `reports` array.
|
|
|
|
Set `VITE_PANEL_API` in the main app's `.env` to the panel's public URL when deploying.
|
|
|
|
## Deployment notes
|
|
|
|
- The panel listens on `PORT` (default 3001). It serves the admin UI at `/` and exposes `/api/*`.
|
|
- Persist the `data.db` file and the `uploads/` folder — these are your content. Both are gitignored.
|
|
- For HF Spaces, the panel is **not** the same image as the main site; deploy it as a separate Space (Docker SDK) or on a small VPS.
|
|
- Always change `JWT_SECRET` and `ADMIN_PASSWORD` in production.
|
|
|
|
## Reset admin password
|
|
|
|
```bash
|
|
sqlite3 data.db "DELETE FROM users WHERE username='admin';"
|
|
# edit .env with the new password, then:
|
|
npm run seed
|
|
```
|