This document provides detailed security measures tailored for applications using Node.js with Postgres or MySQL, including authentication, authorization, file uploads, Stripe integration, and more. It emphasizes the importance of rebuilding safety nets such as Postgres Row Level Security and implementing strict ownership checks to mitigate risks associated with direct database access. Additionally, it covers best practices for CORS, CSRF protection, error handling, and secrets management, ensuring a comprehensive approach to securing the stack. |
||
|---|---|---|
| .. | ||
| public | ||
| scripts | ||
| .env.example | ||
| .gitignore | ||
| .liaraignore | ||
| README.md | ||
| db.js | ||
| imports-data.json | ||
| insert-events.mjs | ||
| insert-factory.mjs | ||
| liara.json | ||
| package-lock.json | ||
| package.json | ||
| run-import.mjs | ||
| scraper.js | ||
| seed-content.js | ||
| seed.js | ||
| server.js | ||
| update-radar-images.mjs | ||
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