§ unswayed-backend · Hiring pipeline (Phase 6)
Employer reviews & popular companies
Employer reviews & popular companies
The §11 surface is small but public-facing: two guest-readable endpoints and one applicant-only write, in their own module (src/employer-reviews/). The EmployerReview model itself dates from Phase 3 (the profile pages already showed aggregates); Phase 6 adds the write path and the public reads.
The three endpoints
GET /api/employer/popular— public. Employers ranked by review count, descending, gated to completed + active accounts.searchis a multi-term AND: each space-split term must match at least one of company name / username / email. Each item is aPopularCompanyResource— and mind the id semantics:idis the user id,employer_idis theemployers.id. The rating block carriesreviews_count,averageRating, and aratingCountshistogram grouped byoverall_ratings.GET /api/employer/reviews/{id}— public;{id}=employers.id, newest first, each review eager-loading its author (applicant + user).POST /api/applicant/add-review/{id}— authenticated, applicant-only. Returns201with "Review Added successfully." and the created review.overall_ratingsvalidates as numeric 1–5 but stores as an Int (a4.7truncates — legacy parity);questions/ratingsare free-shape JSON.
The quirks, frozen on purpose
{id}isemployers.id, not the user id. The endpoint doc says "Employer user ID", but the legacy controller writes the path id straight intoemployer_id. The code wins — tests pin it.- No dedupe, no eligibility gate. Any applicant can review any employer, repeatedly, without ever having applied. Kept for wire parity; a dedupe/proof-of-interaction gate is the flagged improvement.
is_anonymousis stored but not enforced. The resource still emits the author block. Reproduced as-is — hiding the author is a client behavior today.
The deactivated scope
Legacy's EmployerReview model carried a global query scope: a review is invisible when either the employer's user or the reviewing applicant's user is deactivated (status = 2). The rebuild makes that a reusable where-builder applied on every read in this module — the popular ranking, the aggregates, and the review list — and the same rule already governs the Phase 3 profile aggregates, which share the one computeReviewAggregates helper (set-based: one grouped query for count/average/histogram, zero/{} on an empty set, no divide-by-zero).
Testing it
test/employer-reviews.e2e-spec.ts drives all three routes publicly/authenticated against the real app: seeded multi-employer ranking + search, the histogram math, the employers.id FK pin, the applicant-only guard, the 4.7 → 4 truncation, the JSON round-trip, the permitted duplicate, the anonymous author block, and a deactivated user vanishing a review from every read.