CREATE TABLE IF NOT EXISTS guilds ( id BIGINT PRIMARY KEY NOT NULL, name TEXT, owner_id BIGINT, volume INTEGER NOT NULL ); CREATE TABLE IF NOT EXISTS dice_track ( id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(), guild_id BIGINT NOT NULL, owner_id BIGINT NOT NULL, dice TEXT NOT NULL, user_id BIGINT, value INT, operator TEXT ); CREATE TABLE IF NOT EXISTS events ( id UUID PRIMARY KEY NOT NULL, guild_id BIGINT NOT NULL, author_id BIGINT NOT NULL, title TEXT NOT NULL, date_time TIMESTAMPTZ NOT NULL, description TEXT, rsvp BIGINT[] NOT NULL ); CREATE TABLE IF NOT EXISTS races ( id INTEGER GENERATED ALWAYS AS IDENTITY, name TEXT NOT NULL, size TEXT NOT NULL, source TEXT NOT NULL, data JSON NOT NULL ); CREATE TABLE IF NOT EXISTS classes ( id INTEGER GENERATED ALWAYS AS IDENTITY ); CREATE TABLE IF NOT EXISTS feats ( id INTEGER GENERATED ALWAYS AS IDENTITY ); CREATE TABLE IF NOT EXISTS options_features ( id INTEGER GENERATED ALWAYS AS IDENTITY ); CREATE TABLE IF NOT EXISTS backgrounds ( id INTEGER GENERATED ALWAYS AS IDENTITY ); CREATE TABLE IF NOT EXISTS items ( id INTEGER GENERATED ALWAYS AS IDENTITY ); CREATE TABLE IF NOT EXISTS spells ( id INTEGER GENERATED ALWAYS AS IDENTITY, name TEXT NOT NULL, school TEXT NOT NULL, level INTEGER NOT NULL, ritual BOOLEAN DEFAULT FALSE, concentration BOOLEAN DEFAULT FALSE, classes TEXT[] NOT NULL, damage_inflict TEXT[] NOT NULL, damage_resist TEXT[] NOT NULL, conditions TEXT[] NOT NULL, saving_throw TEXT[] NOT NULL, attack_type TEXT, data JSONB NOT NULL ); CREATE TABLE IF NOT EXISTS conditions ( id INTEGER GENERATED ALWAYS AS IDENTITY ); CREATE TABLE IF NOT EXISTS bestiary ( id INTEGER GENERATED ALWAYS AS IDENTITY ); -- ============================================================ -- Auth / Users -- ============================================================ -- Core local user accounts. password_hash is NULL for OAuth-only users. CREATE TABLE IF NOT EXISTS users ( id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(), username TEXT UNIQUE NOT NULL, password_hash TEXT, email TEXT UNIQUE, first_name TEXT, last_name TEXT, role TEXT NOT NULL DEFAULT 'user' CHECK (role IN ('admin', 'user')), status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('active', 'banned')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- External OAuth provider connections (Discord, etc.) CREATE TABLE IF NOT EXISTS user_connections ( user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, provider TEXT NOT NULL, provider_user_id TEXT NOT NULL, provider_username TEXT, provider_avatar TEXT, PRIMARY KEY (user_id, provider), UNIQUE (provider, provider_user_id) ); CREATE TABLE IF NOT EXISTS grid_maps ( id TEXT PRIMARY KEY NOT NULL, name TEXT NOT NULL, public_access TEXT NOT NULL DEFAULT 'private' CHECK (public_access IN ('private', 'public_view', 'public_edit')), owner_id UUID NOT NULL REFERENCES users(id), colors TEXT[] NOT NULL DEFAULT ARRAY[ '#6b7280', '#92400e', '#15803d', '#1d4ed8', '#7c3aed', '#dc2626', '#ca8a04', '#0f172a', '#f9fafb' ], created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); -- Per-map role assignments; owner is auto-inserted on map creation CREATE TABLE IF NOT EXISTS map_permissions ( map_id TEXT NOT NULL REFERENCES grid_maps(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, role TEXT NOT NULL CHECK (role IN ('owner', 'editor', 'viewer')), PRIMARY KEY (map_id, user_id) ); -- Maps a user has favorited; makes them appear in the user's map list modal -- even if they have no explicit map_permissions entry (e.g. public maps) CREATE TABLE IF NOT EXISTS map_favorites ( user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, map_id TEXT NOT NULL REFERENCES grid_maps(id) ON DELETE CASCADE, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (user_id, map_id) ); -- Pending/resolved requests from users wanting viewer or editor access CREATE TABLE IF NOT EXISTS map_access_requests ( id UUID PRIMARY KEY NOT NULL DEFAULT gen_random_uuid(), map_id TEXT NOT NULL REFERENCES grid_maps(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, requested_role TEXT NOT NULL CHECK (requested_role IN ('editor', 'viewer')), status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'denied')), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), UNIQUE (map_id, user_id) ); -- Composite primary key replaces the old UUID id column CREATE TABLE IF NOT EXISTS grid_cells ( map_id TEXT NOT NULL REFERENCES grid_maps(id) ON DELETE CASCADE, x INTEGER NOT NULL, y INTEGER NOT NULL, color TEXT NOT NULL DEFAULT '#808080', PRIMARY KEY (map_id, x, y) ); CREATE TABLE IF NOT EXISTS grid_tokens ( id TEXT PRIMARY KEY NOT NULL, map_id TEXT NOT NULL REFERENCES grid_maps(id) ON DELETE CASCADE, x INTEGER NOT NULL, y INTEGER NOT NULL, label TEXT NOT NULL, color TEXT NOT NULL DEFAULT '#4444FF' );