Back to Documentation
design-message.md
Markdown Document
Modified: 1/6/2026
Size: 4.8 KB
# AudivaMax Direct Messages (DB Design)
This document specifies the database schema and implementation details for direct messages between authenticated users, including messages addressed to the admin (“superadmin”) account.
## Scope
**In scope**
- Persisted 1:1 direct messages: user → user, user → admin, admin → user.
- Read/unread state per recipient.
**Out of scope (future)**
- Threads / conversations, attachments, reactions, reporting/moderation workflows.
- Soft-delete/archiving per participant.
## “Superadmin” definition
There is no separate `superadmin` role in the current schema. The admin/superadmin account is a normal `user` row with `role = 'admin'` (created/updated by `apps/api/src/scripts/create-admin.ts`).
## Table: `message`
Created by migration: `packages/db/src/migrations/Migration20260104090000.ts`.
### Columns
| Column | Type | Null | Default | Notes |
| -------------- | -------------- | ---: | ----------------- | ------------------------------------------------------------------------ |
| `id` | `uuid` | no | _(app-generated)_ | UUIDv7 generated in application code. |
| `sender_id` | `uuid` | no | | FK → `user.id`. Authenticated user who sent the message. |
| `recipient_id` | `uuid` | no | | FK → `user.id`. User who receives the message (including admin account). |
| `subject` | `varchar(255)` | yes | | Optional subject line. |
| `body` | `text` | no | | Message content. |
| `is_read` | `boolean` | no | `false` | Read state for the recipient. |
| `created_at` | `timestamptz` | no | _(app-generated)_ | Set by MikroORM `onCreate`. |
| `updated_at` | `timestamptz` | no | _(app-generated)_ | Set by MikroORM `onUpdate`. |
### Constraints
- Primary key: `message_pkey (id)`
- Foreign keys:
- `message_sender_id_foreign (sender_id) → user(id) ON UPDATE CASCADE`
- `message_recipient_id_foreign (recipient_id) → user(id) ON UPDATE CASCADE`
- Note: no explicit `ON DELETE` rule is set; deletions of users referenced by messages are expected to be restricted by default behavior.
### Canonical DDL (as applied by migration)
```sql
create table "message" (
"id" uuid not null,
"sender_id" uuid not null,
"recipient_id" uuid not null,
"subject" varchar(255) null,
"body" text not null,
"is_read" boolean not null default false,
"created_at" timestamptz not null,
"updated_at" timestamptz not null,
constraint "message_pkey" primary key ("id")
);
```
## ORM mapping
Entity: `packages/db/src/entities/Message.ts`.
- `sender`: `ManyToOne(User)` → `sender_id`
- `recipient`: `ManyToOne(User)` → `recipient_id`
- `subject`: nullable string
- `body`: `text`
- `isRead`: boolean default `false` → `is_read`
- `createdAt` / `updatedAt`: managed by MikroORM lifecycle hooks
## Query patterns (and recommended indexes)
The migration does **not** currently create indexes besides the primary key. For typical messaging access patterns, these indexes are recommended for a follow-up migration:
- Inbox (most common): `recipient_id` + sort by newest
- Recommended index: `(recipient_id, created_at desc)`
- Sent messages: `sender_id` + sort by newest
- Recommended index: `(sender_id, created_at desc)`
- Unread count / badge:
- Recommended partial index (Postgres): `(recipient_id) WHERE is_read = false`
If “conversation view” (messages between two users) is added later, consider a derived conversation key (e.g., `least(sender_id, recipient_id)` / `greatest(...)`) or a separate `conversation` table.
## Authorization rules (API-layer)
Database constraints intentionally do not encode application authorization. The API should enforce:
- Only authenticated users can send messages.
- `sender_id` must equal the authenticated user’s id (no impersonation).
- `recipient_id` must exist.
- Only the recipient can mark a message as read (`is_read = true`).
- Recommended validation:
- Disallow `sender_id = recipient_id` (self-message), unless explicitly desired.
- Enforce max sizes (e.g., `subject ≤ 255`, `body` size limit) at the API boundary.
## Operational notes
- Migrations are applied on API startup (`apps/api/src/index.ts` runs `orm.getMigrator().up()`).
- Because ids and timestamps are application-generated, writes should always go through the API/ORM (or replicate the same generation logic in administrative SQL tools).
This is a read-only view of design-message.md. Changes must be made in the source repository.