-
Notifications
You must be signed in to change notification settings - Fork 0
Data Models
All models use Mongoose with timestamps: true. Every CV section document carries a candidateId foreign key back to Candidate.
| Model | Key fields |
|---|---|
Candidate |
email, password (bcrypt hash, never returned by the API), firstName, lastName, gender, marital, birthday, address, phone, introduction ({vi,en}), socialMedia
|
generalInformation |
candidateId, positionDesired, career ({vi,en}), levelCurrent, levelDesired, salaryDesired, education, yearsOfExperience, workLocation, workForm, careerGoal ({vi,en}), personalSkills[], professionalSkills[], foreignLanguages[]
|
Experience |
candidateId, company, position, startDate, endDate, isCurrent, description ({vi,en}), skills[]
|
Education |
candidateId, school, major, startDate, endDate, isCurrent, description ({vi,en}) |
Project |
candidateId, name, position, description ({vi,en}), technology[], startDate, endDate, isWorking, images[], link
|
Certificate |
candidateId, name, organization, startDate, endDate, isNoExpiration, link, images[], description ({vi,en}) |
Award |
candidateId, name, organization, issueDate, link, images[], description ({vi,en}) |
Reference |
candidateId, fullName, phone, company, position
|
Reusable sub-schemas (models/part/index.ts): foreignLanguageSchema, professionalSkillsSchema, personalSkills, socialMediaSchema, localizedTextSchema.
Free-text fields that a candidate actually writes prose into are stored per-language:
{ vi: string, en: string }Localized: Candidate.introduction, description on Education/Experience/Award/Certificate/Project, generalInformation.career / careerGoal.
Not localized (stays a plain string) — proper nouns and short labels: school, company, major, position, positionDesired, levelCurrent, levelDesired, education (degree level), workLocation, workForm. (positionDesired in particular shares its Joi validator with Experience.position — a job title, not a description — which is why it was deliberately excluded from localization.)
-
Authenticated CRUD (
GET/POST/PUT /education, etc.) always returns/accepts the full{ vi, en }object — the owner edits both languages directly. -
Public-facing reads (
GET /api/me/:email,GET /download-pdf) resolve each localized field down to a single string based on?lang=vi|en(defaultvi), falling back to whichever language actually has content if the requested one is empty.
npm run migrate:localize-text (src/scripts/migrate-localize-text-fields.ts) wraps any remaining plain-string values into { vi: <existing value>, en: '' }. It's a MongoDB aggregation-pipeline update matching only documents where the field is currently a string, so it's idempotent — safe to run repeatedly, a no-op once everything is migrated. This must run against the database before deploying a version of the app whose schema expects the {vi,en} shape — old plain-string documents won't read/cast correctly under the new schema otherwise.
Every write (create/update/delete) on a CV section is scoped to req.user._id (the authenticated candidate), never a client-supplied id — see Security for the incident history behind why this is emphasized here.