CODEX // oaoisme wiki

§ unswayed-backend · Hiring pipeline (Phase 6)

Employer reviews & popular companies

updated 2026-06-09

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. search is a multi-term AND: each space-split term must match at least one of company name / username / email. Each item is a PopularCompanyResource — and mind the id semantics: id is the user id, employer_id is the employers.id. The rating block carries reviews_count, averageRating, and a ratingCounts histogram grouped by overall_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. Returns 201 with "Review Added successfully." and the created review. overall_ratings validates as numeric 1–5 but stores as an Int (a 4.7 truncates — legacy parity); questions/ratings are free-shape JSON.

The quirks, frozen on purpose

  1. {id} is employers.id, not the user id. The endpoint doc says "Employer user ID", but the legacy controller writes the path id straight into employer_id. The code wins — tests pin it.
  2. 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.
  3. is_anonymous is 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.