# Campus Catalog Backend

## Purpose

This slice is the public, read-only surface for campus records: it returns the active campuses with
their branding tokens so the frontend can render them without shipping campus rows as runtime
constants. The database is the source of truth for campuses. Authenticated campus create/update/delete
lives in a separate slice (see "campuses CRUD" below); this slice does not write.

## Slice Files (by layer)

- Route: `src/routes/public_campuses.ts` (thin wiring; `GET /`).
- Controller: `src/api/controllers/public_campuses.controller.ts` (custom — not the CRUD factory).
- Service (BLL): `src/services/campus_catalog.ts`.
- Repository (DAL): the catalog service queries `db.campuses.findAll` directly (it does not go
  through `src/db/api/campuses.ts`; that repository serves the authenticated campuses CRUD slice).
- Model: `src/db/models/campuses.ts`.
- Shared used: none beyond `db/models`.

## API

The slice is mounted at `/api/public/campuses` in `src/index.ts`. Unlike the authenticated routes,
this mount is registered before the `jwt` guard and has no authentication middleware — it is a public
endpoint.

- `GET /api/public/campuses` -> `200` `{ rows, count }`. No request parameters are read (the
  controller ignores the request). Each row is the campus catalog DTO below; rows are ordered by
  `name` ascending. `count` is `rows.length`.

## Access Rules

Public. No JWT, no role check, no per-user gating. Only campuses with `active = true` are returned.

## Tenant Scope

None. The query filters solely on `active = true` and is not scoped to any organization or campus —
it returns active campuses across all organizations.

## Data Contract

Campus catalog DTO (`toCampusCatalogDto`, also reflected in the service's selected `attributes`):
`id`, `name`, `code`, `mascot`, `color`, `bgGradient`, `borderColor`, `textColor`, `bgLight`,
`description`, `isOnline`. Other campus columns (address, phone, email, organizationId, audit fields)
are intentionally not exposed by this endpoint.

Relevant `campuses` model fields: `id` (UUID PK), `name` (TEXT, not null), `code` (TEXT, not null),
`address` / `phone` / `email` (TEXT, nullable), `mascot`, `color`, `bgGradient`, `borderColor`,
`textColor`, `bgLight`, `description` (all TEXT, nullable), `isOnline` (BOOLEAN, not null, default
false), `active` (BOOLEAN, not null, default false), `importHash` (unique, nullable),
`organizationId` (UUID, nullable), audit fields `createdById` / `updatedById`, and
`createdAt` / `updatedAt` / `deletedAt`. The model is `paranoid` (soft delete) with
`freezeTableName`. Associations include `belongsTo` organization, createdBy, updatedBy, and `hasMany`
staff, classes, timetables, attendance_sessions, messages (all keyed
on `campusId`).

## Behavior / Notes

- The service selects only the catalog `attributes` so the SQL fetches just the columns the DTO needs.
- Because the filter is `{ active: true }` with no tenant scope, inactive campuses are never returned
  and the result is not paginated.

## Tests

None yet (no `campus_catalog` / `public_campuses` unit/e2e test in `src/`).

## Related

- Frontend: `frontend/docs/campus-catalog.md`.
- Related slices: campuses CRUD (authenticated `/api/campuses`, route `src/routes/campuses.ts`,
  repository `src/db/api/campuses.ts`) for create/update/delete of campus records.
