# Communications Backend ## Purpose The communications slice exposes product-focused endpoints for parent messages and internal alert events, instead of the generated CRUD routes. Parent messages reuse the existing `messages` and `message_recipients` tables; internal alerts own the `communication_events` table. The backend is the source of truth for these records. ## Slice Files (by layer) - Route: `src/routes/communications.ts` (thin wiring; `GET /parent-messages`, `POST /parent-messages`, `GET /events`, `POST /events`). Mounted at `/api/communications` behind the `authenticated` middleware in `src/index.ts`. - Controller: `src/api/controllers/communications.controller.ts` (custom — `listParentMessages`, `createParentMessage`, `listEvents`, `createEvent`). - Service (BLL): `src/services/communications.ts` (+ `src/services/communications.types.ts`). Contains validation, scope resolution, and DTO mappers. - Repository (DAL): queries run through `db.messages`, `db.message_recipients`, and `db.communication_events` inside the service (no separate `db/api` file). - Models: `src/db/models/communication_events.ts`; plus the existing `src/db/models/messages.ts` and `src/db/models/message_recipients.ts` (used by the parent-message flow). - Shared used: `db/with-transaction.ts`, `services/shared/access.ts`, `services/shared/validate.ts` (`nullableString`), `shared/object.ts` (`isRecord`), `shared/constants/communications.ts` (channels, audiences, statuses, recipient types, event-type values, parent-message categories, manager/tenant-wide role lists), `shared/constants/roles.ts` (`PRODUCT_ROLE_VALUES`), `shared/constants/pagination.ts` (`resolvePagination`), `shared/errors/forbidden.ts`, `shared/errors/validation.ts`. ## API All routes require JWT authentication. - `GET /api/communications/parent-messages` -> `200` `{ rows, count }`. Optional query `category`; supports `limit`/`page`. - `POST /api/communications/parent-messages` -> `201` the created parent-message DTO. Body is `req.body.data`. - `GET /api/communications/events` -> `200` `{ rows, count }`. Optional query `type`; supports `limit`/`page`. - `POST /api/communications/events` -> `201` the created event DTO. Body is `req.body.data`. Parent-message DTO fields: `id`, `text` (from `body`), `to` (first recipient `recipient_label`), `date` (ISO, from `sent_at` or `createdAt`), `category` (derived from `subject`), `sentAt`, `organizationId`, `campusId`, `createdById`, `updatedById`, `createdAt`, `updatedAt`. Event DTO fields: `id`, `title`, `date` (from `event_date`), `type` (from `event_type`), `roles`, `organizationId`, `campusId`, `createdById`, `updatedById`, `createdAt`, `updatedAt`. ## Access Rules - All endpoints require an authenticated tenant user (`assertAuthenticatedTenantUser`). - `POST /events` additionally requires manage access (`assertCanManageCommunications`): the user must hold one of `COMMUNICATION_MANAGER_ROLE_NAMES` (super admin, admin, platform owner, tenant director, campus manager) or have global access; otherwise `ForbiddenError`. - Listing and creating parent messages requires only an authenticated tenant user. - The frontend does not send organization, campus, creator, or updater fields. The backend fills them from the authenticated user. ## Tenant Scope - `GET /parent-messages` filters by organization via `getOrganizationIdOrGlobal(currentUser)`: global access users see messages across all organizations; regular users see only their own org. Global access users also see all users' messages; regular users see only their own (`createdById`). Audience is always `guardians`, plus `campusScope(currentUser, COMMUNICATION_TENANT_WIDE_ROLE_NAMES)`. - `GET /events` filters by organization via `getOrganizationIdOrGlobal(currentUser)` plus the same `campusScope`. Global access users see events across all organizations. - `campusScope` (from `services/shared/access.ts`): tenant-wide roles (`COMMUNICATION_TENANT_WIDE_ROLE_NAMES` — super admin, admin, platform owner, tenant director) or global access see all of the organization; other users are restricted to their profile `campusId` when one is present. - On create, `organizationId` and `campusId` are derived from the user (`getOrganizationIdOrGlobal`, `getCampusId`). ## Data Contract - Parent message input (`ParentMessageInput`): `recipientName` (required non-empty string), `messageText` (required non-empty string), `category` (optional; mapped to one of `behavior`, `event`, `progress`, `general`, defaulting to `general`). - Event input (`EventInput`): `title` (required), `date` (required, stored to `event_date`), `type` (required; must be one of `meeting`, `drill`, `event`, `deadline`), `roles` (optional array of product-role values; an empty/missing array defaults to `['teacher','para','office','director']`; invalid values throw `ValidationError`). - `communication_events` model: `title` (text), `event_date` (DATEONLY), `event_type` (ENUM of the four types), `roles` (JSONB, default `[]`), `organizationId` (not null), nullable `campusId`, `createdById` (not null), nullable `updatedById`, `paranoid` soft deletes, `belongsTo` associations to `organizations`, `campuses`, `users` (createdBy, updatedBy). - List pagination: both lists use `resolvePagination(limit, page)`. ## Behavior / Notes - `createParentMessage` runs inside `withTransaction`: creates a `messages` row (`subject = category`, `body = messageText`, `channel = in_app`, `audience = guardians`, `sent_at = now`, `status = sent`) and a matching `message_recipients` row (`recipient_type = guardian`, `recipient_label = recipientName`, `delivery_status = sent`, `delivered_at = now`), then re-reads the message with its recipient to build the DTO. - Parent-message list includes `message_recipients` (alias `message_recipients_message`, only `recipient_label`) and orders by `sent_at desc`, then `createdAt desc`. - Event list orders by `event_date asc`, then `createdAt desc`. `createEvent` is a single create (no transaction). - Validation failures throw `ValidationError`; access failures throw `ForbiddenError`. ## Tests None yet (no `*.test.ts` under `backend/src` references this slice). ## Related - Frontend: `frontend/docs/communications-integration.md`. - Related backend slice: content catalog (`backend/docs/content-catalog.md`) backs safety protocols and parent-message templates referenced by the communications UI.