From eafefcb1dac87e874553b9165eafa8e33f186e8a Mon Sep 17 00:00:00 2001 From: dimitar Date: Wed, 29 Jul 2026 19:08:14 +0200 Subject: [PATCH] docs: add implementation plan and gitignore for old build artifacts --- .gitignore | 2 +- adminImplem.md | 259 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 adminImplem.md diff --git a/.gitignore b/.gitignore index 28e89ab..b35bd8a 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,4 @@ yarn-error.log* next-env.d.ts # docker -certbot/ \ No newline at end of file +certbot/.next_old/ diff --git a/adminImplem.md b/adminImplem.md new file mode 100644 index 0000000..5aa5567 --- /dev/null +++ b/adminImplem.md @@ -0,0 +1,259 @@ +# SuperAdmin & Admin + Code Access System — Implementation Plan + +## Overview + +Implement SuperAdmin/admin role management and code-gated memorial creation as outlined in `admin.md`. + +- SuperAdmin: hardcoded username `super`, password `admin` +- SuperAdmin creates admin accounts +- Admins generate access codes +- Users must enter a valid code during onboarding to create memories + +--- + +## Phase 1 — Database & Dependencies + +### New Prisma Models (`prisma/schema.prisma`) + +```prisma +enum Role { + SUPER_ADMIN + ADMIN +} + +model AdminUser { + id String @id @default(cuid()) + username String @unique + passwordHash String + role Role @default(ADMIN) + createdAt DateTime @default(now()) + createdCodes Code[] +} + +model Code { + id String @id @default(cuid()) + code String @unique + createdById String + createdBy AdminUser @relation(fields: [createdById], references: [id]) + usedByUserId String? // User.clerkId + usedAt DateTime? + createdAt DateTime @default(now()) + expiresAt DateTime? + + @@index([code]) + @@index([usedByUserId]) +} +``` + +### New Dependencies + +- `bcryptjs` + `@types/bcryptjs` — hash admin passwords + +### Seed SuperAdmin + +Seed script or migration that creates the SuperAdmin `AdminUser` record with hashed password. However, the login itself checks hardcoded `super`/`admin` first (and also queries DB by role for token auth), so the seed is optional — used mainly for listing in the admin panel. + +### Migration + +`npx prisma migrate dev --name add_admin_and_code` + +--- + +## Phase 2 — Admin Auth + +Admin auth is separate from Clerk. Uses a signed HMAC cookie. + +### `src/lib/admin-session.ts` (new) + +Helpers: + +- `createAdminSession(username: string, role: Role): string` — sign a cookie value with HMAC-SHA256 using `ADMIN_SESSION_SECRET` env var +- `verifyAdminSession(token: string): { username: string; role: Role } | null` — verify and decode +- `getAdminSession(): { username: string; role: Role } | null` — read from `request.cookies` or `cookies()` +- Cookie name: `admin_session` + +Payload: `{ username, role, iat }` serialized + HMAC signature. + +### `src/app/api/admin/login/route.ts` (new) + +- POST: accept `{ username, password }` +- If `username === "super"` and `password === "admin"` → set session with role `SUPER_ADMIN` +- Else query `AdminUser` where `username === username`, compare with `bcrypt.compare` +- Return `{ success: true }` and set `admin_session` cookie (httpOnly, secure, sameSite=lax, path=/admin) +- On failure: `401` + +### `src/app/api/admin/logout/route.ts` (new) + +- POST: clear `admin_session` cookie + +### `src/app/api/admin/change-password/route.ts` (new) + +- POST: accept `{ currentPassword, newPassword }` +- Verify admin session, then verify current password against DB +- Hash new password, update `AdminUser` record + +### `src/app/admin/login/page.tsx` (new) + +- Macedonian UI: username/password form +- On submit → `POST /api/admin/login` +- On success → redirect to `/admin/dashboard` +- Show error on failure + +### `src/middleware.ts` (modify) + +- Add `/admin(.*)` and `/api/admin(.*)` to the Clerk exclude list +- Before Clerk middleware runs: if path starts with `/admin`, check admin session cookie + - No/invalid cookie → redirect to `/admin/login` + - Valid → allow +- If path starts with `/api/admin`, check admin session cookie + - No/invalid cookie → return `401` + +--- + +## Phase 3 — Admin Panel + +All UI in Macedonian. Layout with sidebar navigation. + +### `src/app/admin/layout.tsx` (new) + +- Checks admin session (server component) +- Redirects to `/admin/login` if not authenticated +- Provides sidebar with links: Dashboard, Users (SuperAdmin only), Codes +- Logout button + +### `src/app/admin/dashboard/page.tsx` (new) + +- Stats cards: + - Total AdminUsers count + - Total Codes generated + - Codes used vs unused +- Simple overview + +### `src/app/admin/users/page.tsx` (new) + +- Accessible only to `SUPER_ADMIN` +- Table of admin users (username, role, created at) +- Button to create new admin (modal/page with username + password fields) +- Button to delete admin (with confirmation) +- Inline password reset option + +### `src/app/admin/users/create/page.tsx` or modal (new) + +- Form: username, password (with confirmation) +- POST to `/api/admin/users/` + +### `src/app/admin/codes/page.tsx` (new) + +- "Generate Code" button → POST `/api/admin/codes` → returns new code string, displays it +- Table of all codes: code value, who created it, status (used/unused), used by, used at +- Admin can see their own codes. SuperAdmin sees all. +- Delete code button + +### `src/app/api/admin/users/route.ts` (new) + +- GET: list all `AdminUser` (SuperAdmin only) +- POST: create `AdminUser` with hashed password (SuperAdmin only) + +### `src/app/api/admin/users/[id]/route.ts` (new) + +- DELETE: remove `AdminUser` (SuperAdmin only) +- PUT: reset password (SuperAdmin only) + +### `src/app/api/admin/codes/route.ts` (new) + +- GET: list codes (Admins see their own, SuperAdmin sees all) +- POST: generate a new code (`crypto.randomBytes(6).toString('hex').toUpperCase()` → 12 chars), store with `createdById` from session + +### `src/app/api/admin/codes/[id]/route.ts` (new) + +- DELETE: remove unused code + +--- + +## Phase 4 — Code Gating + +### `src/app/api/validate-code/route.ts` (new) + +- Requires Clerk auth (`auth()` from `@clerk/nextjs/server`) +- POST: `{ code: string }` +- Look up `Code` where `code === code` and `usedByUserId === null` +- If not found → `{ valid: false, error: "Невалиден или веќе искористен код" }` +- If found → update `usedByUserId = userId`, `usedAt = now()` +- Return `{ valid: true }` + +### `src/app/onboarding/page.tsx` (modify) + +- Add `"Код"` as step 0 (before "Податоци") +- STEPS becomes: `["Код", "Податоци", "Датуми", "Фотографии", "Поддомен", "Шаблон"]` +- Step 0: single input for code + "Потврди" button + - Calls `POST /api/validate-code` + - On success → shows green checkmark, enables "Продолжи" + - On failure → shows error, blocks progression +- Once validated, user can proceed to step 1 +- `canProceed()` for step 0 returns true only if code validated successfully + +### `src/app/api/publish/route.ts` (modify) + +- After Clerk auth check, query: `prisma.code.findFirst({ where: { usedByUserId: userId } })` +- If no code found → return `403` with `"Потребен е валиден код за креирање спомен страница"` + +--- + +## Phase 5 — Environment & Build + +### New Env Variable + +``` +ADMIN_SESSION_SECRET= +``` + +Add to `.env.example` and `docker-compose` files. + +### Build & Verify + +```bash +npm install bcryptjs +npm install -D @types/bcryptjs +npx prisma migrate dev --name add_admin_and_code +npm run build +``` + +Verify: +- [ ] Admin login at `/admin/login` with `super`/`admin` +- [ ] Create admin user from admin panel +- [ ] Login as created admin +- [ ] Generate code from admin panel +- [ ] Sign up as new user via Clerk +- [ ] Enter code in onboarding step 0 +- [ ] Proceed through onboarding and publish +- [ ] Verify publish fails without valid code + +--- + +## File Change Summary + +### New Files +| Path | Purpose | +|------|---------| +| `src/lib/admin-session.ts` | HMAC cookie helpers | +| `src/app/admin/login/page.tsx` | Admin login form | +| `src/app/admin/layout.tsx` | Admin layout with sidebar | +| `src/app/admin/dashboard/page.tsx` | Dashboard stats | +| `src/app/admin/users/page.tsx` | Admin user management | +| `src/app/admin/codes/page.tsx` | Code generation & listing | +| `src/app/api/admin/login/route.ts` | Admin login API | +| `src/app/api/admin/logout/route.ts` | Admin logout API | +| `src/app/api/admin/change-password/route.ts` | Self-service password change | +| `src/app/api/admin/users/route.ts` | List/create admin users | +| `src/app/api/admin/users/[id]/route.ts` | Delete/reset-password admin user | +| `src/app/api/admin/codes/route.ts` | List/create codes | +| `src/app/api/admin/codes/[id]/route.ts` | Delete code | +| `src/app/api/validate-code/route.ts` | Public code validation | + +### Modified Files +| Path | Change | +|------|--------| +| `prisma/schema.prisma` | Add `AdminUser` and `Code` models | +| `src/middleware.ts` | Exclude `/admin/*` from Clerk, add admin session check | +| `src/app/onboarding/page.tsx` | Add code step (step 0) | +| `src/app/api/publish/route.ts` | Add code usage check before publish |