§ unswayed-backend
Profiles (Phase 3)
Phase 3 builds the two profile surfaces that sit on top of the auth identity (Phase 0) and the reference tables (Phase 2): the applicant profile and the employer profile, plus the public/shared profile viewer and the follow-graph reads those resources expose.
It's the first phase where the product gets a face: a user can fill in who they are, upload a résumé, and be looked at by others.
Why it sits here
- It needs Phase 1's platform layer: résumé/education/photo media flow through the single
MediaStoragePort(Cloudinary, log adapter in dev/test). - It needs Phase 2's master data: a profile stores
country/state/city/gender/ethnicityas ids; the resources resolve them to{id, name}against the seeded lookup tables. - Phase 4 (employer jobs) depends on it: a job posting belongs to an
Employer, and the network-jobs feed reads the follow graph these resources expose — so the employer must be a real, profile-complete entity first.
What landed
Five new Prisma models — ApplicantEducation, ApplicantResume, ApplicantFilterPreference, EmployerReview, UserFollower — plus Applicant.ucn/skills and the hidden Google-Calendar token columns on Employer. Two NestJS modules:
applicant-profile/— profile read/update (§29), education CRUD (§30), the résumé builder + Affinda + AI-enhance pipeline (§35/§26/§36), and sticky filter preferences (§38). See Applicant profile.employer-profile/— theEmployer/EmployerReviewmodels, self read/update + change-pic (§40), the guest short profile (§5), and the type-branching viewer (§14). See Employer profile & viewer.
The shared edge (the part both modules lean on)
A few pieces are deliberately shared, single-owner so the two modules can't drift:
- Resource mappers are pure functions (
toApplicantResource,toEmployerResource,toEmployerProfileResource,toApplicantProfileResource,toPopularCompanyResource, …). They map a Prisma row + pre-resolved auxiliary data (resolved{id,name}refs, the follow network, review aggregates) to the exact snake_case wire shape. They do no I/O — the services resolve everything async and hand the mapper a finished view-model. MasterDataLookupService(inmaster-data/) resolves the five reference ids to{id, name}objects in one parallel batch.FollowsService(inusers/) reads theUserFollowergraph: top-5 followers/followings (filtered to completed + active accounts), the total counts, and per-entryisFollowedresolved against the viewing user.- UCN generation lives in
UsersService(generateUcn) and runs at applicant-create — completing a gap deferred from Phase 0.
A note on the contract
Everything here is reconciled to the frozen endpoint_documentation.md (§5/§14/§26/§29/§30/§35/§36/§38/§40). The one judgement call — reference fields serialize as {id, name} objects on the self/viewer resources (and .name strings on the applicant viewer, matching legacy) — is recorded as an internal deviation in API-CONTRACT.md / ADR-0026. The gate: 995 unit tests @ ≥90% coverage + 124 e2e, and the build dashboard reports Phase 3 done with drift 0.
Subpages