77 lines
3.4 KiB
SQL
77 lines
3.4 KiB
SQL
-- ==========================================
|
|
-- Migration 016: Teams
|
|
-- ==========================================
|
|
-- Teams are the middle tier between system admin and individual users.
|
|
-- A team admin can manage members, create team-scoped presets,
|
|
-- and enforce provider policies — without system-wide access.
|
|
-- ==========================================
|
|
|
|
-- ── 1. Teams table ──────────────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS teams (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
name VARCHAR(200) NOT NULL UNIQUE,
|
|
description TEXT DEFAULT '',
|
|
created_by UUID NOT NULL REFERENCES users(id) ON DELETE RESTRICT,
|
|
is_active BOOLEAN DEFAULT true,
|
|
settings JSONB DEFAULT '{}'::jsonb, -- team-level policies
|
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_teams_active ON teams(is_active) WHERE is_active = true;
|
|
|
|
CREATE TRIGGER teams_updated_at BEFORE UPDATE ON teams
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at();
|
|
|
|
COMMENT ON TABLE teams IS 'Organizational teams with scoped administration';
|
|
COMMENT ON COLUMN teams.settings IS 'Team policies: {"require_private_providers": false}';
|
|
|
|
-- ── 2. Team Members table ───────────────────
|
|
|
|
CREATE TABLE IF NOT EXISTS team_members (
|
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
role VARCHAR(20) NOT NULL DEFAULT 'member'
|
|
CHECK (role IN ('admin', 'member')),
|
|
joined_at TIMESTAMPTZ DEFAULT NOW(),
|
|
UNIQUE(team_id, user_id)
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_team_members_team ON team_members(team_id);
|
|
CREATE INDEX IF NOT EXISTS idx_team_members_user ON team_members(user_id);
|
|
|
|
COMMENT ON TABLE team_members IS 'Team membership with per-team roles (admin or member)';
|
|
COMMENT ON COLUMN team_members.role IS 'admin = manages team; member = uses team resources';
|
|
|
|
-- ── 3. Wire model_presets.team_id FK ────────
|
|
-- Column already exists (migration 012), just add the FK constraint.
|
|
|
|
DO $$ BEGIN
|
|
ALTER TABLE model_presets
|
|
ADD CONSTRAINT fk_presets_team
|
|
FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE SET NULL;
|
|
EXCEPTION
|
|
WHEN duplicate_object THEN NULL;
|
|
END $$;
|
|
|
|
-- ── 4. Add team_id to channels ──────────────
|
|
-- Team channels are visible to all team members.
|
|
|
|
ALTER TABLE channels ADD COLUMN IF NOT EXISTS team_id UUID REFERENCES teams(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_channels_team ON channels(team_id) WHERE team_id IS NOT NULL;
|
|
|
|
-- ── 5. Private provider flag ────────────────
|
|
-- Marks a provider as local/self-hosted (data stays on-prem).
|
|
|
|
ALTER TABLE api_configs ADD COLUMN IF NOT EXISTS is_private BOOLEAN DEFAULT false;
|
|
|
|
COMMENT ON COLUMN api_configs.is_private IS 'Private/self-hosted provider — data does not leave network';
|
|
|
|
-- ── 6. Add team_id to notes ─────────────────
|
|
-- Team notes are shared within the team.
|
|
|
|
ALTER TABLE notes ADD COLUMN IF NOT EXISTS team_id UUID REFERENCES teams(id) ON DELETE SET NULL;
|
|
CREATE INDEX IF NOT EXISTS idx_notes_team ON notes(team_id) WHERE team_id IS NOT NULL;
|