Prompt file imported from omar-elhadi/marsai (
.github/prompts/plan-marsaiProductionUpgrade.prompt.md). Copyright stays with the author.
Plan: Full Production Upgrade — Marsai Film Festival
This transforms a beginner hobby project into a real-world production application. Approach: close security holes first → migrate to TypeScript → rebuild architecture on top → add quality layers. All 8 phases are independently verifiable.
Decisions recorded: Full TS migration at once (strict), React Context for auth state, in-process p-queue for emails, full FR/EN i18n, cloud-platform Dockerfiles (no compose in prod), argon2 hard cut (dev DB), shared Zod validators via monorepo package, runtime mock data removal (test-only fixtures allowed).
Phase 1 — Security Critical Fixes (independent, do first)
- Delete
server/src/middlewares/auth.js— consolidate everything intoauth.middleware.js - Fix magic link vulnerability in
auth.controller.js— afterverifyToken, setloginToken: null, tokenExpires: nullin the DB (currently the token is never cleared → reusable indefinitely) - Replace
bcryptwithargon2(argon2id variant) inauth.service.js+create-admin.jsusing a hard cut (no backward bcrypt compatibility path) - Add
helmetmiddleware inserver/src/index.js(X-Frame-Options, CSP, HSTS, etc.) - Add
hpp(HTTP Parameter Pollution) middleware - Fix body parser limits —
express.json({ limit: '5mb' })instead of unbounded default - Sanitize email templates in
mail.service.js— user-controlledmessageis injected directly into HTML; add entity encoding - Fix S3 ACL — change
public-read→private+ use presigned URLs for all video playback - Validate
parseInt(req.params.id)in all controllers — return 400 ifNaN - Create
server/src/utils/validateEnv.js— throw on startup ifDATABASE_URL,JWT_SECRET,FRONTEND_URLare missing
Phase 2 — TypeScript Migration (blocks Phases 3 + 4)
Monorepo baseline (required before client/server TS refactor):
- Add root
package.jsonworkspaces forclient,server, andpackages/* - Create
packages/validators/shared package (TypeScript + Zod schemas) - Add exports map and path aliases so client and server both import from
@marsai/validators
Backend:
- Add
server/tsconfig.json(strict, ESNext, moduleResolution node16) - Add
tsx,@types/node,@types/express,@types/cookie-parser,@types/cors; updatedev/build/startscripts - Create
server/src/types/express.d.ts— augmentRequestwithuser?: { id: number; role: Role } - Create
server/src/types/index.ts— shared interfaces and imports from@marsai/validators - Rename all
.js→.ts; add types to params, return values, Prisma results
Frontend:
- Add
client/tsconfig.json+client/tsconfig.node.json, renamevite.config.js→vite.config.ts - Create
client/src/types/index.ts—Film,User,Vote,AuthUser,ApiResponse<T>, etc. - Rename all
.jsx→.tsx,.js→.ts - Remove
prop-typesdependency entirely (replaced by TS interfaces)
Shared validators (monorepo pattern):
- Move auth/film/vote/user Zod schemas into
packages/validators/src/ - Import schemas in server routes/middlewares from
@marsai/validators - Reuse schemas and inferred types in client forms through
zodResolver
Phase 3 — Backend Architecture (after Phase 2, parallel with Phase 4)
3a — Error Handling
- Create
server/src/utils/AppError.ts— class withstatusCode,isOperational - Create
server/src/utils/catchAsync.ts— wraps async controllers to propagate errors - Create
server/src/middlewares/error.middleware.ts— global Express error handler differentiatingAppError, Prisma errors, Zod errors, and unknown errors - Refactor all controllers to use
catchAsync()— remove scatteredtry/catch
3b — Structured Logging
- Add
pino+pino-http; createserver/src/utils/logger.ts - Replace all
console.log/errorwithlogger.info/error/warn - Add
pino-httpmiddleware inindex.tsfor automatic request logging
3c — Prisma Transactions
- Wrap
requestModification,updateFilmStatus,submitFilminprisma.$transaction() - Use atomic
incrementfor vote stats updates to prevent race conditions - Enforce
VALID_TRANSITIONSon everyupdateFilmStatuscall in the service layer
3d — Schema Additions (new migration)
- Add
deletedAt DateTime?toFilm,User,Submitter(soft deletes) - Add
approvedBy Int?,approvedAt DateTime?,rejectedBy Int?,rejectedAt DateTime?toFilm - Add new model
FilmStatusHistory—filmId,fromStatus,toStatus,changedBy,changedAt,comment? - Filter all queries with
where: { deletedAt: null }by default
3e — In-Process Email Queue
- Add
p-queue; createserver/src/utils/emailQueue.ts— PQueue (concurrency 5, 3 retries) - Wrap all
mailService.*calls withemailQueue.add()— email failure no longer blocks HTTP response
Phase 4 — Frontend Architecture (after Phase 2, parallel with Phase 3)
4a — AuthContext (replaces all localStorage reads)
- Create
client/src/contexts/AuthContext.tsx— providesuser,login(),logout(),isAuthenticated,hasRole() - On app init: call
GET /auth/meto rehydrate from httpOnly cookie (no localStorage) - Create
client/src/hooks/useAuth.tsconsumer hook - Refactor
ProtectedRoute.tsxto useuseAuth()— remove alllocalStorage.getItem("marsai_user")
4b — Centralized API Client
- Create
client/src/services/api/apiClient.ts— Axios instance withwithCredentials: true, 401 interceptor (clear auth + redirect to login), error normalization - Create resource modules:
films.ts,auth.ts,votes.ts,users.ts,awards.ts— typed methods usingApiResponse<T> - Replace all inline
fetch()calls with apiClient methods
4c — Custom Hooks
useFilms.ts— paginated + filtered listuseFilm.ts— single film + status managementuseVote.ts— vote submission with optimistic updateuseGallery.ts— gallery with infinite scroll
4d — Error Boundaries
- Create
client/src/components/ErrorBoundary/ErrorBoundary.tsx+ErrorFallback/ErrorFallback.tsx - Wrap
<App>in global ErrorBoundary; add per-route boundaries inApp.tsx
4e — Form Handling
- Install
react-hook-form+@hookform/resolvers - Refactor
SubmissionForm.tsxandLoginAdmin.tsxwithuseForm + zodResolver - Replace local client validators with imports from
@marsai/validators(single source of truth) - Remove all inline
style={{}}fromLoginAdmin.tsxand elsewhere → pure Tailwind
4f — UI Consistency
- Create
client/src/components/ui/Skeleton/Skeleton.tsx— reusable loading placeholder - Add consistent loading + error states to all data-fetching pages
- Move
formatDate,formatDurationtoclient/src/utils/format.ts(deduplicate from 3+ files)
Phase 5 — i18n FR/EN (after Phase 4)
- Install
i18next,react-i18next,i18next-browser-languagedetector - Create
client/src/i18n.ts— detect browser language, fallbackfr - Create
client/locales/fr/+client/locales/en/—common.json,films.json,jury.json,admin.json,errors.json - Replace all hardcoded UI strings with
useTranslation()hook - Add
LanguageSwitcher.tsxinHeader— persists in localStorage, updates<html lang>
Phase 6 — Testing with Vitest (after Phase 2)
Backend:
- Add
vitest,supertest,@types/supertest; createserver/vitest.config.ts - Create
server/src/__tests__/setup.ts— Prisma mock usingvitest-mock-extended - Unit tests:
auth.service.test.ts(login, argon2, magic link invalidation),film.service.test.ts(transitions, soft delete),vote.service.test.ts(upsert, race guard) - Route integration tests with Supertest:
auth.routes.test.ts,film.routes.test.ts,vote.routes.test.ts
Frontend:
- Add
vitest,@testing-library/react,@testing-library/user-event,jsdom; createclient/vitest.config.ts - Create
client/src/__tests__/setup.ts— jest-dom matchers - Component tests:
ProtectedRoute.test.tsx,ErrorBoundary.test.tsx,SubmissionForm.test.tsx,LoginAdmin.test.tsx - Hook tests:
useAuth.test.ts,useFilms.test.ts
Mock policy (testing only):
- Keep minimal mocks only under
client/src/__tests__/__fixtures__/andserver/src/__tests__/__fixtures__/ - Runtime app code must not import from
client/src/data/mockData.js,movies.js, ornewsData.js
Phase 7 — Containerization + CI/CD (after Phase 2, parallel with others)
server/Dockerfile— multi-stage:builder(tsc) →production(node:alpine + dist)client/Dockerfile— multi-stage:builder(vite build) →production(nginx:alpine + SPA nginx.conf)docker-compose.yml— dev only: MySQL 8.4 + server (tsx watch) + client (vite dev)- Update both
.env.examplefiles to be fully exhaustive (S3, YouTube API, Mail, JWT, MySQL) .github/workflows/ci.yml— on PR:lint,test-server,test-client,build-server,build-clientjobs
Phase 8 — Quality & Polish (final pass)
- Accessibility: ARIA roles on Header nav,
VideoModal, Gallery cards, all form fields (labels, descriptions) - Code splitting:
React.lazy()+<Suspense>for Admin routes and Jury routes - SEO:
<meta>tags for Film detail pages + public Gallery - Server-side pagination: cursor-based on
GET /filmsandGET /gallerywithlimit/cursorparams - Delete runtime mock sources in
client/src/data/(mockData.js,movies.js,newsData.js) after replacing imports with real API calls - Update all docs to reflect TypeScript, Docker commands, new folder structure
- Add API-backed endpoints for News/Events/Gallery content and wire pages to real-time or near-real-time data flow
Relevant Files
Critical security fixes now:
server/src/middlewares/auth.js— deleteserver/src/middlewares/auth.middleware.js— consolidate into thisserver/src/controllers/auth.controller.js— magic link token invalidationserver/src/services/auth.service.js— argon2id swapserver/src/services/mail.service.js— HTML injection fixserver/src/services/s3.service.js— ACL fixserver/src/index.js— helmet, hpp, body limits
Architecture core:
server/src/services/film.service.js— transactions, soft delete, audit trailserver/src/services/vote.service.js— atomic stats, race condition guardclient/src/App.jsx— ErrorBoundary, AuthContext providerclient/src/components/ProtectedRoute.jsx— refactor to useAuth()client/src/pages/LoginAdmin.jsx— react-hook-form, remove inline stylesclient/src/pages/Submission/SubmissionForm.jsx— react-hook-form + zodserver/prisma/schema.prisma— soft delete + audit trail additions
New Files to Create
Server:
package.json(root workspaces integration)server/tsconfig.jsonserver/src/types/express.d.ts,server/src/types/index.tsserver/src/utils/AppError.ts,catchAsync.ts,logger.ts,validateEnv.ts,emailQueue.tsserver/src/middlewares/error.middleware.tsserver/vitest.config.tsserver/src/__tests__/setup.ts,auth.service.test.ts,film.service.test.ts,vote.service.test.tsserver/src/__tests__/routes/auth.routes.test.ts,film.routes.test.ts,vote.routes.test.tsserver/Dockerfile,server/.dockerignore
Client:
client/tsconfig.json,client/tsconfig.node.jsonclient/src/types/index.tsclient/src/contexts/AuthContext.tsxclient/src/hooks/useAuth.ts,useFilms.ts,useFilm.ts,useVote.ts,useGallery.tsclient/src/services/api/apiClient.ts,films.ts,auth.ts,votes.ts,users.ts,awards.tsclient/src/components/ErrorBoundary/ErrorBoundary.tsxclient/src/components/ui/Skeleton/Skeleton.tsx,ErrorFallback/ErrorFallback.tsxclient/src/components/common/LanguageSwitcher/LanguageSwitcher.tsxclient/src/utils/format.ts,client/src/i18n.tsclient/locales/fr/common.json,films.json,jury.json,admin.json,errors.jsonclient/locales/en/common.json,films.json,jury.json,admin.json,errors.jsonclient/vitest.config.ts,client/src/__tests__/setup.tsclient/src/__tests__/ProtectedRoute.test.tsx,ErrorBoundary.test.tsx,SubmissionForm.test.tsx,LoginAdmin.test.tsxclient/src/__tests__/hooks/useAuth.test.ts,useFilms.test.tsclient/src/__tests__/__fixtures__/(minimal test fixtures only)client/Dockerfile,client/.dockerignore,client/nginx.conf
Packages:
packages/validators/package.jsonpackages/validators/tsconfig.jsonpackages/validators/src/auth.validator.ts,film.validator.ts,user.validator.ts,vote.validator.ts,index.ts
Root:
-
package.json(workspaces + shared scripts) -
tsconfig.base.json -
docker-compose.yml -
.github/workflows/ci.yml
Files to Delete
server/src/middlewares/auth.js(duplicate auth middleware)client/src/data/mockData.js(replace with real API calls)client/src/data/movies.js(replace with real API calls)client/src/data/newsData.js(replace with real API calls)
Resolved Decisions
- Argon2 migration path: hard cut is accepted (project in dev mode with low migration cost)
- Shared validators strategy: monorepo shared package
packages/validatorsis required - Mock data policy: remove runtime mock data and keep only minimal test fixtures
Verification Checklist
-
npm run -ws build— root workspace build passes (client,server,packages/validators) -
cd server && npx tsc --noEmit— zero TS errors -
cd client && npx tsc --noEmit— zero TS errors -
cd packages/validators && npx tsc --noEmit— shared schemas/types compile cleanly -
cd server && npx vitest run --coverage— all tests pass, >80% coverage on services -
cd client && npx vitest run --coverage— all tests pass -
docker compose up— full stack boots, seed runs, login works - Magic link used twice → second use returns 401
- Argon2:
create:adminscript produces working logins after migration -
rg "@/data/mockData|@/data/movies|@/data/newsData" client/src --glob '!**/__tests__/**'returns no runtime matches - Lighthouse: Performance ≥ 90, Accessibility ≥ 95 on Homepage + Submission page
- OWASP ZAP quick scan on
http://localhost:5001— no high-severity findings - GitHub Actions CI passes green on a test PR
Scope Boundaries
In scope: TypeScript, argon2id, AuthContext, Axios client, custom hooks, ErrorBoundary, react-hook-form, Vitest, Dockerfiles, CI workflow, i18n FR/EN, Pino logging, soft deletes, audit trail, email queue, Helmet+HPP
Out of scope: Redis/BullMQ, full OpenAPI spec, monitoring/APM, database backups, YouTube integration changes, Suno AI integration, production cloud-specific deployment config