40227-vm/backend/docs/communications.md

7.9 KiB

Communications Backend

Purpose

The communications area exposes two product-focused flows instead of generic CRUD:

  • Parent/guardian direct messages through direct_messages.
  • Internal alert events through communication_events.

Slice Files (by layer)

  • Route: src/routes/communications.ts (thin wiring; GET /events, POST /events). Mounted at /api/communications behind the authenticated middleware in src/index.ts.
  • Controller: src/api/controllers/communications.controller.ts (custom — 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.communication_events inside the service (no separate db/api file).
  • Models: src/db/models/communication_events.ts.
  • Shared used: services/shared/access.ts, shared/constants/communications.ts (event-type values and manager role list), shared/constants/roles.ts (ROLE_NAMES), shared/constants/pagination.ts (resolvePagination), shared/errors/forbidden.ts, shared/errors/validation.ts.

Direct messages:

  • Route: src/routes/direct_messages.ts, mounted at /api/direct_messages, authenticated and gated by READ_PARENT_COMM.
  • Controller: src/api/controllers/direct_messages.controller.ts.
  • Service: src/services/direct_messages.ts.
  • Model: src/db/models/direct_messages.ts.

API

All routes require JWT authentication.

  • 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.
  • PATCH /api/communications/events/:id -> 200 the updated event DTO. Body is req.body.data.
  • DELETE /api/communications/events/:id -> 204. Soft-deletes a wrongly-created alert without creating a user-facing notification.
  • POST /api/communications/events/:id/cancel -> 201 the cancellation notification DTO. Body is req.body.data with optional reason.
  • GET /api/direct_messages/contacts -> 200 { rows }, contacts available through a shared student.
  • GET /api/direct_messages/conversations -> 200 { rows }, one row per otherUserId + studentId.
  • GET /api/direct_messages/thread/:otherUserId?studentId=:studentId -> 200 the isolated thread for that staff/guardian/student context; marks incoming messages in that context as read.
  • POST /api/direct_messages/send -> 200 the created message. Body is req.body.data with recipientId, body, and studentId.

Event DTO fields: id, title, date (from event_date), type (from event_type), targetLevel, roles, organizationId, schoolId, campusId, classId, canceledEventId, createdById, updatedById, createdAt, updatedAt.

Direct-message contact/conversation rows include conversationKey, userId, name, role, studentId, and studentName. conversationKey is derived from userId + studentId; it is a client key, not a stored column.

Access Rules

  • All endpoints require an authenticated user. Tenant-scoped alerts require a tenant context; platform-scope alerts are allowed for global-access managers.
  • POST /events additionally requires MANAGE_INTERNAL_COMM. custom_permissions can grant it and custom_permissions_filter can remove it for non-global users.
  • PATCH /events/:id, DELETE /events/:id, and POST /events/:id/cancel are allowed for the original creator or a manager with MANAGE_INTERNAL_COMM in the alert's scope. Global admins can mutate tenant alerts from platform root, but they do not see other users' tenant alerts in the root list; they see them through tenant drill-down.
  • Alert create accepts exact targets: system, all, organization, school, and campus. Class-level create/targeting is intentionally not supported; class-scope users read campus-level alerts.
  • Global root managers can create system, all, and selected organization/school/campus alerts. Organization managers can target their own organization, schools, or campuses. School managers can target their own school or campuses. Campus managers can target their own campus only.
  • Direct messages require READ_PARENT_COMM. The granted audience is office_manager, teacher, and guardian.
  • Direct-message access is membership-based and student-context based:
    • Guardians can find the teacher and office manager connected to each linked student.
    • Teachers can find guardians for students in their class.
    • Office managers can find guardians for students on their campus.
    • Threads and sends are allowed only when the requested studentId matches one of those contact rows.
  • The frontend does not send organization, campus, creator, or updater fields. The backend fills them from the authenticated user.

Tenant Scope

  • Internal alerts are stored with an exact target. targetLevel = system with a null tenant chain is visible only in the platform/system scope. targetLevel = all with a null tenant chain is a platform-wide broadcast.
  • List visibility includes the current scope and descendant targets. Platform-root users see platform alerts plus tenant-target alerts they created. When they drill into a tenant, they see that tenant scope like a scoped viewer. Organization users see their organization alerts plus school/campus alerts inside that organization. School users see their school alerts plus campus alerts inside that school. Campus/class users see their campus alerts only.
  • Parent alerts do not automatically propagate down: an organization alert is not a campus alert unless the sender also targets that campus. Class-scope users read campus-level alerts because class content is campus-level.
  • Selecting multiple tenant audiences creates multiple communication_events rows with the same title/date/type and different exact target stamps.
  • Direct messages are not tenant-broadcast records. Contacts and threads are resolved through the current user's student links, class, or campus.

Data Contract

  • Event input (EventInput): title (required), date (required, stored to event_date), type (required; must be one of meeting, drill, event, deadline), targets (optional array of { level, id }; omitted targets default to the creator's exact scope), roles (legacy metadata; it is not used for visibility).
  • communication_events model: title (text), event_date (DATEONLY), event_type (ENUM of the four types), targetLevel (text: system, all, organization, school, campus), roles (JSONB, default []), nullable organizationId, nullable schoolId, nullable campusId, nullable classId, nullable canceledEventId, 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

  • Direct-message conversations are separated by student. The same guardian and teacher can have separate threads for two different students because reads/writes filter by sender/recipient pair + studentId.
  • Event list orders by event_date asc, then createdAt desc. createEvent creates one row per exact target. cancelEvent creates a new cancellation notification with the same target stamp and soft-deletes the original alert; deleteEvent only soft-deletes the original alert.
  • Validation failures throw ValidationError; access failures throw ForbiddenError.

Tests

  • src/services/direct_messages.test.ts covers contact discovery through linked students, guardian/staff contact visibility, student-separated conversations, ambiguous same-counterpart thread rejection, and persisted studentId context when sending.
  • Frontend: frontend/docs/communications-integration.md.
  • Related backend slice: direct messages (src/services/direct_messages.ts) backs the guardian/staff Messages UI.