Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesMessages CommentsMessages + Comments — Data Model

Messages + Comments — Data Model

Schemas: libs/db/src/lib/schema/messaging.ts and libs/db/src/lib/schema/comments.ts.

messages (unified DMs + workout-anchored)

ColumnTypeNotes
iduuid PK
organization_iduuid NOT NULLFK
sender_iduuid NOT NULLFK users(id)
sender_rolesender_role NOT NULLcoach | member | admin — snapshot at send (so a later role change doesn’t rewrite history)
recipient_iduuid NOT NULLFK
workout_assignment_iduuidFK CASCADE — NULL = DM, non-NULL = workout-anchored comment
contenttextNullable — message may be attachment-only
created_attimestamptz NOT NULL
read_attimestamptz
edited_attimestamptzReserved; no API path
deleted_attimestamptzSoft delete

Indexes:

  • messages_org_created_idx on (organization_id, created_at).
  • messages_recipient_created_idx, messages_sender_created_idx — conversation reads.
  • messages_recipient_read_at_idx on (recipient_id, read_at) — unread-count subquery in listConversations.
  • messages_workout_assignment_idx on (workout_assignment_id, created_at) — workout-anchored thread reads.

message_attachments

ColumnTypeNotes
iduuid PK
message_iduuid NOT NULLFK CASCADE
typeattachment_type NOT NULLimage | video
r2_keytext NOT NULL
sizeint NOT NULL
mime_typetext NOT NULL
thumbnail_r2_keytext
created_attimestamptz NOT NULL

Index: message_attachments_message_idx.

exercise_comments (separate from messages)

ColumnTypeNotes
iduuid PK
workout_movement_iduuid NOT NULLFK CASCADE
author_iduuid NOT NULLFK users(id)
bodytext NOT NULL
parent_comment_iduuidSelf-FK for threading
created_at / updated_at / deleted_attimestamptz

Multi-org isolation

  • messages.organization_id is mandatory + denormalized.
  • verifyCanMessage requires both participants share the active org.
  • exercise_comments inherits org scope through the workout_movement → workout → organization chain.

PII handling

  • Message bodies are user-generated content; plaintext in DB.
  • Attachments (image/video) are in R2 — presigned reads only.
  • “A deleted member’s PII must be redacted from messages” is a stated invariant in the README spec but NOT enforced today. Member account deletion leaves sender_id / recipient_id dangling (no ON DELETE clause for these). Future work needed for GDPR-style erasure.

Soft / hard delete

  • messages.deleted_at soft-delete.
  • workout_assignments CASCADE → messages with that assignment_id are HARD-deleted with the assignment. Distinct from DM behavior. (Worth noting for QA.)
  • exercise_comments.deleted_at soft-delete.