-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
src/
├── server.ts # Entry: Express setup, MongoDB connect, Redis init
├── alias.ts # module-alias: @ → src/ (dev) or dist/ (prod)
├── config/
│ ├── process.config.ts # Env var validation + export
│ ├── cors.config.ts # CORS: origin '*' (known gap — see Security)
│ ├── session.config.ts # Express session config
│ ├── joi.config.ts # Shared Joi schemas (email, password, phone, description, etc.)
│ ├── regex.config.ts # Password + phone regex
│ └── swagger.config.ts # OpenAPI spec (swagger-jsdoc), served at /api-docs
├── database/
│ └── mongo.db.ts # Singleton MongoDB connection manager
├── locales/ # i18n message dictionaries (vi.ts default, en.ts)
├── middlewares/
│ ├── verifyToken.middleware.ts # JWT extraction + blacklist check; attaches req.user._id;
│ │ also forces req.body.candidateId to the authenticated user
│ ├── language.middleware.ts # Resolves Accept-Language → req.lang / req.t(key)
│ ├── rateLimit.middleware.ts # Redis-backed rate limit; mem fallback
│ ├── errors.middleware.ts # Global error handler; AppError-aware
│ └── requestLogger.middleware.ts # Logs method, URL, status, duration via Winston
├── models/
│ ├── candidate.model.ts, generalInformation.model.ts, experience.model.ts,
│ │ education.model.ts, project.model.ts, certificate.model.ts,
│ │ award.model.ts, reference.modal.ts
│ └── part/index.ts # Reusable sub-schemas (skills, languages, socialMedia, localizedTextSchema)
├── routers/
│ ├── api/v1/ # All active routes
│ └── api/v2/ # Auth v2 — thin, incomplete duplicate of v1 auth; see below
├── auth/ # v1 auth controller/service — the real, tested implementation
├── api/v1/auth/ # Legacy/duplicate auth impl used by v2 routes — untested, has had bugs
├── candidate/ # Self-profile controller/service
├── candidate_profile/ # One controller+service+validate per CV section
│ └── BaseController.ts / BaseService.ts # Shared CRUD factory used by all 7 sections
├── candidate_me/ # Public profile aggregation + PDF export (candidateId-based, no auth)
├── services/
│ ├── index.ts # Core DB ops: baseFindDocument, baseCreateDocument, baseUpdateDocument, baseDeleteDocument
│ ├── redis.ts # Redis client singleton
│ └── createPDF.ts # Puppeteer PDF generation
├── scripts/
│ └── migrate-localize-text-fields.ts # One-off data migration (see Data Models)
├── utils/ # jwt, bcrypt, tokenBlacklist, querySafe, i18n, timeout, helper, ...
├── errors/ # AppError hierarchy
└── types/ # base.type.ts, candidate.type.ts, express.d.ts
- Request logger (Winston)
-
Language resolution (
Accept-Language→req.lang) - Session middleware
- CORS (
origin: '*') - Body parser (JSON + URL-encoded)
-
GET /health— exempt from rate limit - Rate limiter (Redis or in-memory)
- Static files (
public/) - API router
- Global error handler
Dev runs on port 3001, prod on port 3008.
-
POST /auth/register→ validate Joi → check duplicate email → bcrypt(password, 12) → createCandidatedoc -
POST /auth/login→ find by email → bcrypt compare → signaccessToken(TOKEN_SECRET, expiryTOKEN_EXP_IN) +refreshToken(TOKEN_REFRESH, expiryTOKEN_REFRESH_EXP_IN, default 7d) with payload{ _id: candidateId } - Every protected request →
verifyTokenmiddleware → extract Bearer token → verify signature → check blacklist → attachreq.user._idand forcereq.body.candidateIdto that same id, overwriting anything the client sent (this is the fix for the IDOR incident — see Security) -
POST /auth/refresh→ verify refresh token → blacklist old refresh token → issue new pair -
POST /auth/logout→ blacklist current token (Redis TTL = token remaining exp; in-memory fallback)
routers/api/v2/auth.route.ts (/api/v2/auth/register, /login) points at a separate, untested implementation in src/api/v1/auth/ (confusing path — it's the legacy code that /api/v2/ happens to route to), not the real src/auth/ used by /api/v1/auth/*. This duplication caused a real bug: a missing await on password hashing made POST /api/v2/auth/register fail 100% of the time until fixed 2026-08-21. Recommendation, not yet done: delete src/api/v1/auth/ and point v2 at the same src/auth/ implementation v1 uses — tracked as issue #77.
All 7 CV sections (education, experience, award, certificate, project, reference, generalInformation) go through candidate_profile/BaseController.ts + BaseService.ts + services/index.ts's base DB ops (baseFindDocument, baseCreateDocument, baseUpdateDocument, baseDeleteDocument). Ownership is enforced via req.user._id (see Security for the incident that made this actually true, as of 2026-08-21).