60 lines
6.3 KiB
Markdown
60 lines
6.3 KiB
Markdown
# 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` (`ROLE_NAMES`), `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 role names; an empty/missing array defaults to `['teacher','support_staff','office_manager','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.
|