Skip to content

Commit 63702a5

Browse files
authored
Merge pull request #2897 from data-for-change/dev
Merge dev to master
2 parents 8b46aa6 + 19e75a7 commit 63702a5

File tree

2 files changed

+101
-1
lines changed

2 files changed

+101
-1
lines changed
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""Add app-aware composite foreign keys to user association tables
2+
3+
Revision ID: b7c8d9e0f1a2
4+
Revises: a1b2c3d4e5f6
5+
Create Date: 2026-01-02 12:00:00.000000
6+
7+
"""
8+
from alembic import op
9+
import sqlalchemy as sa
10+
11+
# revision identifiers, used by Alembic.
12+
revision = 'b7c8d9e0f1a2'
13+
down_revision = 'a1b2c3d4e5f6'
14+
branch_labels = None
15+
depends_on = None
16+
17+
18+
def upgrade():
19+
"""
20+
Add composite foreign key constraints to ensure app-level data integrity.
21+
This ensures that:
22+
- users_to_roles references only users and roles from the same app
23+
- users_to_organizations references only users and organizations from the same app
24+
- users_to_grants references only users and grants from the same app
25+
"""
26+
27+
# First, add unique constraints on (id, app) for the referenced tables
28+
# These are needed for composite foreign keys
29+
op.create_index('ix_users_id_app', 'users', ['id', 'app'], unique=True)
30+
op.create_index('ix_roles_id_app', 'roles', ['id', 'app'], unique=True)
31+
op.create_index('ix_organization_id_app', 'organization', ['id', 'app'], unique=True)
32+
op.create_index('ix_grants_id_app', 'grants', ['id', 'app'], unique=True)
33+
34+
# Add composite foreign key constraint to users_to_roles
35+
# This ensures user_id and role_id both exist and have matching app values
36+
op.create_foreign_key(
37+
'fk_users_to_roles_user_app',
38+
'users_to_roles', 'users',
39+
['user_id', 'app'], ['id', 'app'],
40+
ondelete='CASCADE'
41+
)
42+
43+
op.create_foreign_key(
44+
'fk_users_to_roles_role_app',
45+
'users_to_roles', 'roles',
46+
['role_id', 'app'], ['id', 'app'],
47+
ondelete='CASCADE'
48+
)
49+
50+
# Add composite foreign key constraint to users_to_organizations
51+
# This ensures user_id and organization_id both exist and have matching app values
52+
op.create_foreign_key(
53+
'fk_users_to_organizations_user_app',
54+
'users_to_organizations', 'users',
55+
['user_id', 'app'], ['id', 'app'],
56+
ondelete='CASCADE'
57+
)
58+
59+
op.create_foreign_key(
60+
'fk_users_to_organizations_org_app',
61+
'users_to_organizations', 'organization',
62+
['organization_id', 'app'], ['id', 'app'],
63+
ondelete='CASCADE'
64+
)
65+
66+
# Add composite foreign key constraint to users_to_grants
67+
# This ensures user_id and grant_id both exist and have matching app values
68+
op.create_foreign_key(
69+
'fk_users_to_grants_user_app',
70+
'users_to_grants', 'users',
71+
['user_id', 'app'], ['id', 'app'],
72+
ondelete='CASCADE'
73+
)
74+
75+
op.create_foreign_key(
76+
'fk_users_to_grants_grant_app',
77+
'users_to_grants', 'grants',
78+
['grant_id', 'app'], ['id', 'app'],
79+
ondelete='CASCADE'
80+
)
81+
82+
83+
def downgrade():
84+
"""
85+
Remove composite foreign key constraints and unique indexes.
86+
"""
87+
88+
# Drop foreign key constraints
89+
op.drop_constraint('fk_users_to_grants_grant_app', 'users_to_grants', type_='foreignkey')
90+
op.drop_constraint('fk_users_to_grants_user_app', 'users_to_grants', type_='foreignkey')
91+
op.drop_constraint('fk_users_to_organizations_org_app', 'users_to_organizations', type_='foreignkey')
92+
op.drop_constraint('fk_users_to_organizations_user_app', 'users_to_organizations', type_='foreignkey')
93+
op.drop_constraint('fk_users_to_roles_role_app', 'users_to_roles', type_='foreignkey')
94+
op.drop_constraint('fk_users_to_roles_user_app', 'users_to_roles', type_='foreignkey')
95+
96+
# Drop unique indexes
97+
op.drop_index('ix_grants_id_app', table_name='grants')
98+
op.drop_index('ix_organization_id_app', table_name='organization')
99+
op.drop_index('ix_roles_id_app', table_name='roles')
100+
op.drop_index('ix_users_id_app', table_name='users')

anyway/error_code_and_strings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class Errors:
4545
Errors.BR_ONLY_SUPPORT_GOOGLE: "Google is the only supported OAuth 2.0 provider.",
4646
Errors.BR_UNKNOWN_FIELD: "Bad Request (Unknown field {}).",
4747
Errors.BR_NAME_MISSING: "Bad Request (Name is missing from request json).",
48-
Errors.BR_NOT_EXIST: "Bad Request ({} doesn't exist in DB).",
48+
Errors.BR_NOT_EXIST: "Bad Request ({} doesn't exist in DB for requested app).",
4949
Errors.BR_USER_NOT_FOUND: "Bad Request (User(email:{}) not found).",
5050
Errors.BR_USER_ALREADY_IN: "Bad Request (User is already in {}).",
5151
Errors.BR_MISSING_PERMISSION: "Bad Request (User is missing permission to access this resource).",

0 commit comments

Comments
 (0)