Until now collectives only existed at registration; an account with a personal workspace had no path to start one. The creator becomes Owner and gets a session scoped to the new workspace.
60 lines
3.2 KiB
PL/PgSQL
60 lines
3.2 KiB
PL/PgSQL
-- 037: per-workspace chat identity display settings (pseudonymization).
|
|
--
|
|
-- Owner need, two parts:
|
|
-- * Hub-local users must render as @username (never @hub_user:server) on the
|
|
-- surfaces the HUB renders — that part is code-only (mxid-display.ts).
|
|
-- * Per-collective pseudonymization: participants in PUBLIC chats appear
|
|
-- under automatically-assigned random display names; PRIVATE chats show
|
|
-- real names by default; an admin toggle decides whether participants can
|
|
-- see the real→pseudonym mapping.
|
|
--
|
|
-- STORAGE DECISION (why a new table and not instance_settings / branding):
|
|
-- * instance_settings (028) is the INSTANCE's legal/content posture — a
|
|
-- SYSTEM table with no workspace_id and a CHECK constraint pinning the key
|
|
-- set to exactly its five policy keys. chat.* keys cannot live there, and
|
|
-- pseudonymization is a PER-COLLECTIVE choice, not a deployment posture.
|
|
-- * workspaces.branding (036) is the tenant's LOOK — reusing it for chat
|
|
-- policy would couple two unrelated concerns inside another module's column.
|
|
-- So nothing that exists fits; this table is the per-workspace key/value
|
|
-- analogue of 028, following its conventions: the key set and the value
|
|
-- literals are CHECK-constrained so an undecodable row cannot exist, and the
|
|
-- ONE place that interprets the strings is chat-display.ts (missing/unknown
|
|
-- rows decode to the documented default).
|
|
--
|
|
-- Keys and defaults (decoded in apps/api/src/matrix-sso/chat-display.ts):
|
|
-- chat.pseudonymizePublic default true public chat: random display names
|
|
-- chat.pseudonymizePrivate default false private chat: real names
|
|
-- chat.revealMapping default false participants see Pseudonym (real)
|
|
-- No seed rows: absence IS the default, which keeps the table empty for every
|
|
-- workspace that never touches the toggles (the `pending` semantics of 028).
|
|
|
|
BEGIN;
|
|
|
|
CREATE TABLE workspace_chat_settings (
|
|
workspace_id uuid NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE ON UPDATE RESTRICT,
|
|
key text NOT NULL,
|
|
value text NOT NULL,
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_by uuid REFERENCES users(id) ON DELETE SET NULL,
|
|
PRIMARY KEY (workspace_id, key),
|
|
-- Unknown keys are rejected outright so a typo cannot fork the surface.
|
|
CONSTRAINT workspace_chat_settings_key_allowed CHECK (key IN (
|
|
'chat.pseudonymizePublic', 'chat.pseudonymizePrivate', 'chat.revealMapping'
|
|
)),
|
|
-- Every key is boolean-valued; only the literals the service layer decodes.
|
|
CONSTRAINT workspace_chat_settings_bool_value CHECK (value IN ('true', 'false'))
|
|
);
|
|
|
|
CREATE TRIGGER trg_workspace_chat_settings_ws_immutable BEFORE UPDATE ON workspace_chat_settings
|
|
FOR EACH ROW EXECUTE FUNCTION reject_workspace_change();
|
|
|
|
ALTER TABLE workspace_chat_settings ENABLE ROW LEVEL SECURITY;
|
|
ALTER TABLE workspace_chat_settings FORCE ROW LEVEL SECURITY;
|
|
|
|
CREATE POLICY tenant_isolation ON workspace_chat_settings
|
|
USING (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid)
|
|
WITH CHECK (workspace_id = NULLIF(current_setting('app.workspace_id', true), '')::uuid);
|
|
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON workspace_chat_settings TO sp_app;
|
|
|
|
COMMIT;
|