- panel/: standalone Express + SQLite + JWT admin (server.js, db.js, seed.js, vanilla-JS admin UI under public/). CRUD for articles with cover image uploads. README documents API, run instructions, and integration steps. - src/data/reports.ts: appended bootstrapReports() that fetches from VITE_PANEL_API (defaults to http://localhost:3001) and mutates the in-memory reports array. Static array kept as fallback when panel is offline. - src/main.tsx: awaits bootstrapReports() before initial render so all existing consumers see panel data without per-file changes. |
||
|---|---|---|
| .. | ||
| public | ||
| .env.example | ||
| .gitignore | ||
| README.md | ||
| db.js | ||
| package-lock.json | ||
| package.json | ||
| seed.js | ||
| server.js | ||
README.md
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-filedata.db)multerfor uploadsbcryptjs+jsonwebtokenfor auth- Vanilla JS admin UI (no build step)
First-time setup
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
{
"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, 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:
// 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.dbfile and theuploads/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_SECRETandADMIN_PASSWORDin production.
Reset admin password
sqlite3 data.db "DELETE FROM users WHERE username='admin';"
# edit .env with the new password, then:
npm run seed