6.3 KiB
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/communicationsbehind theauthenticatedmiddleware insrc/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, anddb.communication_eventsinside the service (no separatedb/apifile). - Models:
src/db/models/communication_events.ts; plus the existingsrc/db/models/messages.tsandsrc/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 querycategory; supportslimit/page.POST /api/communications/parent-messages->201the created parent-message DTO. Body isreq.body.data.GET /api/communications/events->200{ rows, count }. Optional querytype; supportslimit/page.POST /api/communications/events->201the created event DTO. Body isreq.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 /eventsadditionally requires manage access (assertCanManageCommunications): the user must hold one ofCOMMUNICATION_MANAGER_ROLE_NAMES(super admin, admin, platform owner, tenant director, campus manager) or have global access; otherwiseForbiddenError.- 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-messagesfilters by organization viagetOrganizationIdOrGlobal(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 alwaysguardians, pluscampusScope(currentUser, COMMUNICATION_TENANT_WIDE_ROLE_NAMES).GET /eventsfilters by organization viagetOrganizationIdOrGlobal(currentUser)plus the samecampusScope. Global access users see events across all organizations.campusScope(fromservices/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 profilecampusIdwhen one is present.- On create,
organizationIdandcampusIdare 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 ofbehavior,event,progress,general, defaulting togeneral). - Event input (
EventInput):title(required),date(required, stored toevent_date),type(required; must be one ofmeeting,drill,event,deadline),roles(optional array of product-role values; an empty/missing array defaults to['teacher','para','office','director']; invalid values throwValidationError). communication_eventsmodel:title(text),event_date(DATEONLY),event_type(ENUM of the four types),roles(JSONB, default[]),organizationId(not null), nullablecampusId,createdById(not null), nullableupdatedById,paranoidsoft deletes,belongsToassociations toorganizations,campuses,users(createdBy, updatedBy).- List pagination: both lists use
resolvePagination(limit, page).
Behavior / Notes
createParentMessageruns insidewithTransaction: creates amessagesrow (subject = category,body = messageText,channel = in_app,audience = guardians,sent_at = now,status = sent) and a matchingmessage_recipientsrow (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(aliasmessage_recipients_message, onlyrecipient_label) and orders bysent_at desc, thencreatedAt desc. - Event list orders by
event_date asc, thencreatedAt desc.createEventis a single create (no transaction). - Validation failures throw
ValidationError; access failures throwForbiddenError.
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.