Skip to main content

Announcement Module

Location: backend/src/announcement/

A school-wide announcement board. Staff post notices that everyone in their school can read; the module also tracks per-user read receipts that power an unread badge and "read by" avatars on the frontend.

Files

FilePurpose
announcement.module.tsModule definition
announcement.controller.tsAPI endpoints
announcement.service.tsBusiness logic, read tracking, caching
transformer.tsVersioned response shapes
dto/create-announcement.dto.tstitle (required), body (optional)
dto/update-announcement.dto.tsPartial update

Data model (public schema)

TableColumns
announcementid, school_id, author_user_profile_id, title, body, created_at, updated_at
announcement_read(announcement_id, user_profile_id) PK, read_at

RLS: everyone in the school reads announcements; members may insert; author-or-admin may update/delete. Read receipts are visible to the school and writable only for oneself. The backend uses the service client and enforces authorization in code (RLS is the secondary defense).

Permissions

announcement is a resource in the permission catalog with create / read / update / delete. Defaults: admins all, teachers full control, members read-only. The catalog syncs to public.permission_catalog on boot, so the resource shows up in the role editor automatically.

Endpoints

All under AuthGuard + PermissionGuard. :id mutations additionally require the caller to be the author or a school admin (enforced in the service, not by the permission alone).

MethodRoutePermissionNotes
GET/announcementsannouncement:readSchool feed, newest first, with readers[] merged in
GET/announcements/unread-countannouncement:read{ count } of others' posts not yet read
POST/announcements/mark-readannouncement:readRecords a read receipt for every in-school announcement
GET/announcements/:idannouncement:read
POST/announcementsannouncement:createAuthor = caller
PATCH/announcements/:idannouncement:updateAuthor/admin only
DELETE/announcements/:idannouncement:deleteAuthor/admin only

Read tracking

A read is recorded when a user opens the board (mark-read bulk-upserts a receipt per announcement) - a board-level "seen", not per-message. getUnreadCount returns announcements posted by others that the caller hasn't read. The author is never counted as unread on, nor shown as a reader of, their own post.

Caching

Announcement content (title/body/author) is cached per school under announcements:content:<schoolId> (30-day TTL) and invalidated only on create/update/delete. Read receipts are fetched live on each list and merged into the cached content, so "read by" stays accurate without invalidating the content cache every time someone reads.