From 832e4b27a888e23d3b816049c4b807cfb2e1392d Mon Sep 17 00:00:00 2001 From: Mondo Diaz Date: Wed, 28 Jan 2026 20:08:56 +0000 Subject: [PATCH] Add teams migration to runtime migrations Add teams table, team_memberships table, and team_id column to projects in the runtime migrations. This fixes startup failures on existing databases that don't have the teams schema from the migration file. --- backend/app/database.py | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/backend/app/database.py b/backend/app/database.py index 3a686d9..1e6a3c7 100644 --- a/backend/app/database.py +++ b/backend/app/database.py @@ -285,6 +285,60 @@ def _run_migrations(): END IF; END $$; """, + # Teams and multi-tenancy migration (009_teams.sql) + """ + 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 NOW(), + updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + created_by VARCHAR(255) NOT NULL, + settings JSONB DEFAULT '{}' + ); + """, + """ + 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(50) NOT NULL DEFAULT 'member', + created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), + invited_by VARCHAR(255), + CONSTRAINT team_memberships_unique UNIQUE (team_id, user_id), + CONSTRAINT team_memberships_role_check CHECK (role IN ('owner', 'admin', 'member')) + ); + """, + """ + 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; + CREATE INDEX IF NOT EXISTS idx_projects_team_id ON projects(team_id); + END IF; + END $$; + """, + """ + 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_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; + END $$; + """, ] with engine.connect() as conn: @@ -293,6 +347,7 @@ def _run_migrations(): conn.execute(text(migration)) conn.commit() except Exception as e: + conn.rollback() logger.warning(f"Migration failed (may already be applied): {e}")