Apple requires user-generated-content apps to offer a way to report
objectionable content and block abusive users. The chat had neither, which is
the #1 rejection cause for messaging apps. Added both, server-enforced.
Server:
- schema: message_reports + user_blocks tables.
- repos: reports {add,listForTeam,setStatus}, blocks {add,remove,has,listFor},
users.adminsOf(); thread + threadByConversation now exclude blocked senders in
SQL (like message_hidden) so the LIMIT counts only visible rows (no pagination
stall).
- routes: POST /api/messages/report, /api/users/block|unblock, GET
/api/users/blocked, GET /api/reports + POST /api/reports/resolve (admin only).
- enforcement: a blocked sender's DM/group messages are persisted but not
delivered (no live push, no background notification) to anyone who blocked
them; blocked users can't ring you (/api/calls/dm/start + /api/calls/invite);
admins can delete reported content (delete route now allows role=admin).
Client (home.html, all platforms via the web UI — no rebuild):
- message menu gains Report (canned-reason picker) + Block/Unblock.
- profile menu: "Blocked users" manager (list + unblock) for everyone;
"Reported messages" review (delete / block / resolve) for admins.
- blocked DMs hidden from the sidebar; block list loaded on boot.
- reports route to the workspace's OWN admins (org-internal moderation).
Verified: db-smoke 22/22 + a new moderation suite 12/12 (report+admin-list,
non-admin 403, block hides post-block history but sender still sees sent,
blocked call 403, unblock restores history + calling). New flag/ban icons added.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Production runs DB_BACKEND=pg, but the message_hidden table (#18) and
pinned_at/pinned_by columns (#13) were only added to the SQLite migrations in
db.js — never to schema.pg.sql. So thread/conversations/pinned queries hit a
missing relation/column and 500'd, which surfaced as "chat history removed"
(no data was ever deleted — the reads just errored).
pg.js init() runs schema.pg.sql on every boot. Added the message_hidden table
and, because CREATE TABLE IF NOT EXISTS can't add columns to the existing
messages table, idempotent ALTER TABLE ... ADD COLUMN IF NOT EXISTS for
pinned_at/pinned_by. Restores all chat history and re-enables pin + delete-for-me.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
server/db/schema.pg.sql — the full Postgres DDL, every column defined up front (no
ALTER-ordering fragility). SQLite→PG type mapping documented in-file (epoch-ms
INTEGER→BIGINT, 0/1 flags→SMALLINT kept numeric so app code is unchanged, sizes→
BIGINT, audit rowid→GENERATED IDENTITY). Mirrors the three existing FKs and adds a
new idx_messages_attachment (the /files auth scan we cached earlier becomes a keyed
lookup).
Validated against a throwaway Postgres 16: loads with no errors, 24 tables + 44
indexes created. Unwired — nothing uses it yet; the SQLite path is untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>