合并 agent-mvp 到 master #1
@@ -51,20 +51,3 @@ ALTER TABLE users
|
|||||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_keycloak_id ON users(keycloak_id);
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_keycloak_id ON users(keycloak_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
|
CREATE INDEX IF NOT EXISTS idx_users_role ON users(role);
|
||||||
CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active);
|
CREATE INDEX IF NOT EXISTS idx_users_is_active ON users(is_active);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS user_project_membership (
|
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
||||||
user_id UUID NOT NULL,
|
|
||||||
project_id UUID NOT NULL,
|
|
||||||
project_role VARCHAR(20) DEFAULT 'viewer' NOT NULL,
|
|
||||||
CONSTRAINT user_project_membership_role_check
|
|
||||||
CHECK (
|
|
||||||
project_role IN ('member', 'viewer')
|
|
||||||
),
|
|
||||||
CONSTRAINT user_project_membership_unique UNIQUE (user_id, project_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_user_project_membership_user_id
|
|
||||||
ON user_project_membership(user_id);
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_user_project_membership_project_id
|
|
||||||
ON user_project_membership(project_id);
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ CREATE TABLE IF NOT EXISTS projects (
|
|||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);
|
CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);
|
||||||
CREATE INDEX IF NOT EXISTS idx_projects_code ON projects(code);
|
|
||||||
|
|
||||||
DROP TRIGGER IF EXISTS update_projects_updated_at ON projects;
|
DROP TRIGGER IF EXISTS update_projects_updated_at ON projects;
|
||||||
CREATE TRIGGER update_projects_updated_at
|
CREATE TRIGGER update_projects_updated_at
|
||||||
@@ -33,6 +32,20 @@ CREATE TRIGGER update_projects_updated_at
|
|||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE FUNCTION update_updated_at_column();
|
EXECUTE FUNCTION update_updated_at_column();
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_project_membership (
|
||||||
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||||
|
project_role VARCHAR(20) DEFAULT 'viewer' NOT NULL,
|
||||||
|
CONSTRAINT user_project_membership_role_check
|
||||||
|
CHECK (project_role IN ('member', 'viewer')),
|
||||||
|
CONSTRAINT user_project_membership_unique UNIQUE (user_id, project_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
-- The unique (user_id, project_id) index already supports user-side lookups.
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_project_membership_project_id
|
||||||
|
ON user_project_membership(project_id);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS project_databases (
|
CREATE TABLE IF NOT EXISTS project_databases (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||||
|
|||||||
@@ -1,6 +1,35 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def test_metadata_schema_creates_membership_after_referenced_tables():
|
||||||
|
auth_sql = Path("resources/sql/004_metadata_auth_management.sql").read_text(
|
||||||
|
encoding="utf-8"
|
||||||
|
)
|
||||||
|
project_sql = Path(
|
||||||
|
"resources/sql/005_metadata_project_configuration.sql"
|
||||||
|
).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "CREATE TABLE IF NOT EXISTS user_project_membership" not in auth_sql
|
||||||
|
assert project_sql.index("CREATE TABLE IF NOT EXISTS projects") < project_sql.index(
|
||||||
|
"CREATE TABLE IF NOT EXISTS user_project_membership"
|
||||||
|
)
|
||||||
|
assert "user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE" in project_sql
|
||||||
|
assert (
|
||||||
|
"project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE"
|
||||||
|
in project_sql
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_metadata_schema_avoids_indexes_duplicated_by_unique_constraints():
|
||||||
|
project_sql = Path(
|
||||||
|
"resources/sql/005_metadata_project_configuration.sql"
|
||||||
|
).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
assert "idx_projects_code" not in project_sql
|
||||||
|
assert "idx_user_project_membership_user_id" not in project_sql
|
||||||
|
assert "idx_user_project_membership_project_id" in project_sql
|
||||||
|
|
||||||
|
|
||||||
def test_rbac_migration_normalizes_legacy_roles():
|
def test_rbac_migration_normalizes_legacy_roles():
|
||||||
sql = Path("resources/sql/006_metadata_rbac_roles.sql").read_text(
|
sql = Path("resources/sql/006_metadata_rbac_roles.sql").read_text(
|
||||||
encoding="utf-8"
|
encoding="utf-8"
|
||||||
|
|||||||
Reference in New Issue
Block a user