diff --git a/backend/app/database.py b/backend/app/database.py index 3a686d9..28018cd 100644 --- a/backend/app/database.py +++ b/backend/app/database.py @@ -285,6 +285,73 @@ def _run_migrations(): END IF; END $$; """, + # Teams and multi-tenancy migration (009_teams.sql) + """ + -- Create teams table + CREATE TABLE IF NOT EXISTS teams ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name VARCHAR(255) NOT NULL, + slug VARCHAR(255) NOT NULL UNIQUE, + description TEXT, + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + created_by VARCHAR(255) NOT NULL, + settings JSONB DEFAULT '{}'::jsonb, + CONSTRAINT check_team_slug_format CHECK (slug ~ '^[a-z0-9][a-z0-9-]*[a-z0-9]$' OR slug ~ '^[a-z0-9]$') + ); + """, + """ + -- Create team_memberships table + CREATE TABLE IF NOT EXISTS team_memberships ( + 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', + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, + invited_by VARCHAR(255), + CONSTRAINT unique_team_membership UNIQUE (team_id, user_id), + CONSTRAINT check_team_role CHECK (role IN ('owner', 'admin', 'member')) + ); + """, + # Add team_id column to projects table + """ + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 FROM information_schema.columns + WHERE table_name = 'projects' AND column_name = 'team_id' + ) THEN + ALTER TABLE projects ADD COLUMN team_id UUID REFERENCES teams(id) ON DELETE SET NULL; + END IF; + END $$; + """, + # Create indexes for teams + """ + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_teams_slug') THEN + CREATE INDEX idx_teams_slug ON teams(slug); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_teams_created_by') THEN + CREATE INDEX idx_teams_created_by ON teams(created_by); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_teams_created_at') THEN + CREATE INDEX idx_teams_created_at ON teams(created_at); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_team_memberships_team_id') THEN + CREATE INDEX idx_team_memberships_team_id ON team_memberships(team_id); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_team_memberships_user_id') THEN + CREATE INDEX idx_team_memberships_user_id ON team_memberships(user_id); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_team_memberships_role') THEN + CREATE INDEX idx_team_memberships_role ON team_memberships(role); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_projects_team_id') THEN + CREATE INDEX idx_projects_team_id ON projects(team_id); + END IF; + END $$; + """, ] with engine.connect() as conn: