305 lines
9.4 KiB
SQL
305 lines
9.4 KiB
SQL
|
|
-- PostgreSQL schema for Biz Connect — the target of the SQLite→Postgres migration.
|
||
|
|
--
|
||
|
|
-- Every column is defined up front here (unlike the SQLite db.js, which layers columns via ALTER TABLE
|
||
|
|
-- and hit a real ordering bug). Type mapping from the SQLite schema:
|
||
|
|
-- SQLite INTEGER epoch-ms timestamp -> BIGINT (ms since epoch; JS Number-safe)
|
||
|
|
-- SQLite INTEGER 0/1 boolean flag -> SMALLINT (kept numeric so app code still reads 0/1)
|
||
|
|
-- SQLite INTEGER byte size / duration-> BIGINT (files can exceed INT range)
|
||
|
|
-- SQLite INTEGER small count (peak) -> INTEGER
|
||
|
|
-- SQLite AUTOINCREMENT rowid -> BIGINT GENERATED ALWAYS AS IDENTITY
|
||
|
|
-- TEXT -> TEXT
|
||
|
|
-- Foreign keys mirror the three the SQLite schema enforced (users→teams, sessions_auth→users,
|
||
|
|
-- machines→teams). The data-migration script inserts in dependency order so these hold.
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS teams (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
name TEXT NOT NULL,
|
||
|
|
created_at BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS users (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL REFERENCES teams(id),
|
||
|
|
email TEXT NOT NULL UNIQUE,
|
||
|
|
pw_hash TEXT NOT NULL,
|
||
|
|
pw_salt TEXT NOT NULL,
|
||
|
|
role TEXT NOT NULL DEFAULT 'technician',
|
||
|
|
mfa_secret TEXT,
|
||
|
|
mfa_enabled SMALLINT NOT NULL DEFAULT 0,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
name TEXT,
|
||
|
|
active SMALLINT NOT NULL DEFAULT 1,
|
||
|
|
avatar_url TEXT,
|
||
|
|
status TEXT NOT NULL DEFAULT 'active',
|
||
|
|
bizgaze_user_id TEXT,
|
||
|
|
last_seen BIGINT
|
||
|
|
);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_users_bizgaze_user_id ON users(bizgaze_user_id);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS sessions_auth (
|
||
|
|
token TEXT PRIMARY KEY,
|
||
|
|
user_id TEXT NOT NULL REFERENCES users(id),
|
||
|
|
mfa_passed SMALLINT NOT NULL DEFAULT 0,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
expires_at BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS machines (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL REFERENCES teams(id),
|
||
|
|
name TEXT NOT NULL,
|
||
|
|
enroll_token TEXT NOT NULL UNIQUE,
|
||
|
|
unattended SMALLINT NOT NULL DEFAULT 0,
|
||
|
|
last_seen BIGINT,
|
||
|
|
created_at BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS audit_log (
|
||
|
|
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
user_id TEXT,
|
||
|
|
user_email TEXT,
|
||
|
|
machine_id TEXT,
|
||
|
|
machine_name TEXT,
|
||
|
|
action TEXT NOT NULL,
|
||
|
|
detail TEXT,
|
||
|
|
at BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS sessions_log (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
agent_email TEXT,
|
||
|
|
agent_name TEXT,
|
||
|
|
ticket TEXT,
|
||
|
|
started_at BIGINT NOT NULL,
|
||
|
|
ended_at BIGINT,
|
||
|
|
recording TEXT,
|
||
|
|
transcript TEXT
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS refresh_tokens (
|
||
|
|
token_hash TEXT PRIMARY KEY,
|
||
|
|
user_id TEXT NOT NULL,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
expires_at BIGINT NOT NULL,
|
||
|
|
revoked SMALLINT NOT NULL DEFAULT 0
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS api_keys (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
name TEXT,
|
||
|
|
key_hash TEXT NOT NULL UNIQUE,
|
||
|
|
scopes TEXT NOT NULL DEFAULT '',
|
||
|
|
created_by TEXT,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
last_used_at BIGINT,
|
||
|
|
revoked SMALLINT NOT NULL DEFAULT 0
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS webhooks (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
url TEXT NOT NULL,
|
||
|
|
secret TEXT NOT NULL,
|
||
|
|
events TEXT NOT NULL DEFAULT '',
|
||
|
|
active SMALLINT NOT NULL DEFAULT 1,
|
||
|
|
created_by TEXT,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
last_status INTEGER,
|
||
|
|
last_error TEXT,
|
||
|
|
last_at BIGINT
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS messages (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
sender_id TEXT NOT NULL,
|
||
|
|
recipient_id TEXT NOT NULL,
|
||
|
|
body TEXT NOT NULL,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
read_at BIGINT,
|
||
|
|
reply_to TEXT,
|
||
|
|
attachment_id TEXT,
|
||
|
|
conversation_id TEXT,
|
||
|
|
mentions TEXT,
|
||
|
|
delivered_at BIGINT,
|
||
|
|
poll_id TEXT,
|
||
|
|
msg_type TEXT,
|
||
|
|
deleted SMALLINT NOT NULL DEFAULT 0,
|
||
|
|
edited_at BIGINT,
|
||
|
|
fwd_from TEXT
|
||
|
|
);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_messages_pair ON messages(team_id, sender_id, recipient_id, created_at);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_messages_unread ON messages(team_id, recipient_id, sender_id, read_at);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_messages_conv ON messages(conversation_id, created_at);
|
||
|
|
-- New: attachment lookups drove the /files auth scan (see static.js authAttachment). Index it so the
|
||
|
|
-- per-Range playback auth is a keyed lookup, not a table scan (the auth cache stays as a second line).
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_messages_attachment ON messages(attachment_id);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS message_reactions (
|
||
|
|
message_id TEXT NOT NULL,
|
||
|
|
user_id TEXT NOT NULL,
|
||
|
|
emoji TEXT NOT NULL,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
PRIMARY KEY (message_id, user_id, emoji)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS attachments (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
uploader_id TEXT NOT NULL,
|
||
|
|
name TEXT NOT NULL,
|
||
|
|
mime TEXT,
|
||
|
|
size BIGINT,
|
||
|
|
created_at BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS conversations (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
type TEXT NOT NULL DEFAULT 'group',
|
||
|
|
name TEXT,
|
||
|
|
created_by TEXT,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
avatar_id TEXT,
|
||
|
|
admin_only SMALLINT NOT NULL DEFAULT 0
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS conversation_members (
|
||
|
|
conversation_id TEXT NOT NULL,
|
||
|
|
user_id TEXT NOT NULL,
|
||
|
|
last_read_at BIGINT NOT NULL DEFAULT 0,
|
||
|
|
joined_at BIGINT NOT NULL,
|
||
|
|
admin SMALLINT NOT NULL DEFAULT 0,
|
||
|
|
PRIMARY KEY (conversation_id, user_id)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS call_history (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
room TEXT NOT NULL,
|
||
|
|
group_id TEXT,
|
||
|
|
kind TEXT,
|
||
|
|
title TEXT,
|
||
|
|
peak INTEGER NOT NULL DEFAULT 0,
|
||
|
|
participants TEXT,
|
||
|
|
uids TEXT,
|
||
|
|
started_at BIGINT NOT NULL,
|
||
|
|
ended_at BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_call_history_team ON call_history(team_id, ended_at);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS user_aliases (
|
||
|
|
old_id TEXT PRIMARY KEY,
|
||
|
|
user_id TEXT NOT NULL,
|
||
|
|
team_id TEXT,
|
||
|
|
created_at BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS polls (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
conversation_id TEXT NOT NULL,
|
||
|
|
message_id TEXT,
|
||
|
|
question TEXT NOT NULL,
|
||
|
|
options TEXT NOT NULL,
|
||
|
|
multi SMALLINT NOT NULL DEFAULT 0,
|
||
|
|
closed SMALLINT NOT NULL DEFAULT 0,
|
||
|
|
created_by TEXT NOT NULL,
|
||
|
|
created_at BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS poll_votes (
|
||
|
|
poll_id TEXT NOT NULL,
|
||
|
|
user_id TEXT NOT NULL,
|
||
|
|
option_idx INTEGER NOT NULL,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
PRIMARY KEY (poll_id, user_id, option_idx)
|
||
|
|
);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS scheduled_meetings (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
group_id TEXT,
|
||
|
|
room_code TEXT NOT NULL,
|
||
|
|
title TEXT NOT NULL,
|
||
|
|
description TEXT,
|
||
|
|
scheduled_at BIGINT NOT NULL,
|
||
|
|
created_by TEXT NOT NULL,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
ended_at BIGINT,
|
||
|
|
participants TEXT,
|
||
|
|
reminded SMALLINT NOT NULL DEFAULT 0,
|
||
|
|
cancelled SMALLINT NOT NULL DEFAULT 0,
|
||
|
|
duration_mins INTEGER,
|
||
|
|
recurrence TEXT,
|
||
|
|
guest_emails TEXT,
|
||
|
|
lobby SMALLINT
|
||
|
|
);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_sched_team ON scheduled_meetings(team_id, scheduled_at);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_sched_code ON scheduled_meetings(room_code);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS recordings (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
team_id TEXT NOT NULL,
|
||
|
|
room TEXT,
|
||
|
|
group_id TEXT,
|
||
|
|
meeting_id TEXT,
|
||
|
|
title TEXT,
|
||
|
|
kind TEXT NOT NULL,
|
||
|
|
file TEXT,
|
||
|
|
mime TEXT,
|
||
|
|
size BIGINT,
|
||
|
|
duration_ms BIGINT,
|
||
|
|
created_by TEXT,
|
||
|
|
created_by_name TEXT,
|
||
|
|
created_at BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_rec_team ON recordings(team_id, created_at);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_rec_room ON recordings(room);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS push_subscriptions (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
user_id TEXT NOT NULL,
|
||
|
|
endpoint TEXT NOT NULL UNIQUE,
|
||
|
|
p256dh TEXT NOT NULL,
|
||
|
|
auth TEXT NOT NULL,
|
||
|
|
created_at BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_push_user ON push_subscriptions(user_id);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS device_tokens (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
user_id TEXT NOT NULL,
|
||
|
|
tenant_id TEXT,
|
||
|
|
platform TEXT NOT NULL,
|
||
|
|
token TEXT NOT NULL UNIQUE,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
last_seen BIGINT
|
||
|
|
);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_devtok_user ON device_tokens(user_id);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS app_installs (
|
||
|
|
id TEXT PRIMARY KEY,
|
||
|
|
install_id TEXT NOT NULL UNIQUE,
|
||
|
|
user_id TEXT,
|
||
|
|
user_email TEXT,
|
||
|
|
tenant_id TEXT,
|
||
|
|
platform TEXT,
|
||
|
|
app_version TEXT,
|
||
|
|
os TEXT,
|
||
|
|
first_seen BIGINT NOT NULL,
|
||
|
|
last_seen BIGINT NOT NULL
|
||
|
|
);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_installs_tenant ON app_installs(tenant_id);
|
||
|
|
CREATE INDEX IF NOT EXISTS idx_installs_user ON app_installs(user_id);
|
||
|
|
|
||
|
|
CREATE TABLE IF NOT EXISTS favorites (
|
||
|
|
user_id TEXT NOT NULL,
|
||
|
|
target TEXT NOT NULL,
|
||
|
|
created_at BIGINT NOT NULL,
|
||
|
|
PRIMARY KEY (user_id, target)
|
||
|
|
);
|