Skip to content

Commit 0bd1c7f

Browse files
authored
Merge pull request #2879 from data-for-change/dev
Merging dev to master
2 parents 4f66607 + d9b5932 commit 0bd1c7f

22 files changed

Lines changed: 3061 additions & 352 deletions

alembic/versions/11ddb0cff075_adding_location_verification_role.py

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,18 @@
1414

1515
from alembic import op
1616
import sqlalchemy as sa
17-
from sqlalchemy import orm
18-
import datetime
1917

2018

2119
def upgrade():
22-
from anyway.models import Roles
23-
24-
bind = op.get_bind()
25-
session = orm.Session(bind=bind)
26-
27-
role_location_verification = Roles(
28-
name="location_verification",
29-
description="Allows user to change and verify newsflash location.",
30-
create_date=datetime.datetime.now(),
31-
)
32-
session.add(role_location_verification)
33-
session.commit()
20+
# Use raw SQL to avoid schema mismatch with future 'app' column addition
21+
op.execute("""
22+
INSERT INTO roles (name, description, create_date)
23+
VALUES ('location_verification', 'Allows user to change and verify newsflash location.', now())
24+
""")
3425

3526

3627
def downgrade():
37-
from anyway.models import Roles
38-
39-
bind = op.get_bind()
40-
session = orm.Session(bind=bind)
41-
42-
# Delete the role added in the upgrade
43-
session.query(Roles).filter(Roles.name == "location_verification").delete()
44-
45-
session.commit()
28+
# Use raw SQL to avoid schema mismatch with future 'app' column addition
29+
op.execute("""
30+
DELETE FROM roles WHERE name = 'location_verification'
31+
""")

alembic/versions/41af3263a5a3_add_id_column_to_telegram_forwarded_.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,3 @@ def downgrade():
2929
op.drop_column('telegram_forwarded_messages', 'id')
3030
op.create_primary_key('message_id', 'telegram_forwarded_messages', ['message_id'])
3131
op.drop_column('telegram_forwarded_messages', 'timestamp')
32-
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
"""Add safety data user system
2+
3+
Revision ID: a1b2c3d4e5f6
4+
Revises: 41af3263a5a3
5+
Create Date: 2025-11-24 12:00:00.000000
6+
7+
"""
8+
import datetime
9+
from sqlalchemy import orm, text
10+
from sqlalchemy.engine.reflection import Inspector
11+
from alembic import op
12+
import sqlalchemy as sa
13+
from sqlalchemy import text
14+
15+
# revision identifiers, used by Alembic.
16+
revision = 'a1b2c3d4e5f6'
17+
down_revision = '41af3263a5a3'
18+
branch_labels = None
19+
depends_on = None
20+
existing_tables = ['users', 'roles', 'organization', 'users_to_roles', 'users_to_organizations']
21+
ANYWAY_APP_ID_STR = '0'
22+
SAFETY_DATA_APP_ID = 1
23+
24+
def upgrade():
25+
# Drop non-PK constraints and non-primary indexes on users (PostgreSQL)
26+
for table in existing_tables:
27+
op.execute(f"""
28+
DO $$
29+
DECLARE r RECORD;
30+
BEGIN
31+
-- Drop constraints except primary keys
32+
FOR r IN
33+
SELECT c.conname, n.nspname, t.relname
34+
FROM pg_constraint c
35+
JOIN pg_class t ON c.conrelid = t.oid
36+
JOIN pg_namespace n ON t.relnamespace = n.oid
37+
WHERE t.relname = '{table}' AND n.nspname = 'public' AND c.contype <> 'p'
38+
LOOP
39+
EXECUTE format('ALTER TABLE %I.%I DROP CONSTRAINT IF EXISTS %I CASCADE', r.nspname, r.relname, r.conname);
40+
END LOOP;
41+
42+
-- Drop indexes that are not primary indexes
43+
FOR r IN
44+
SELECT n.nspname, i.relname as indexname
45+
FROM pg_index idx
46+
JOIN pg_class i ON i.oid = idx.indexrelid
47+
JOIN pg_class t ON t.oid = idx.indrelid
48+
JOIN pg_namespace n ON i.relnamespace = n.oid
49+
WHERE t.relname = '{table}' AND n.nspname = 'public' AND NOT idx.indisprimary
50+
LOOP
51+
EXECUTE format('DROP INDEX IF EXISTS %I.%I', r.nspname, r.indexname);
52+
END LOOP;
53+
END$$;
54+
""")
55+
# Add app column to users table (0 = ANYWAY, 1 = SAFETY_DATA)
56+
op.add_column('users', sa.Column('app', sa.Integer(), nullable=False, server_default=ANYWAY_APP_ID_STR))
57+
op.alter_column('users', 'app', server_default=None)
58+
op.create_index(op.f('ix_users_email_app'), 'users', ['email', 'app'], unique=True)
59+
60+
# Add app column to roles table (0 = ANYWAY, 1 = SAFETY_DATA)
61+
op.add_column('roles', sa.Column('app', sa.Integer(), nullable=False, server_default=ANYWAY_APP_ID_STR))
62+
op.alter_column('roles', 'app', server_default=None)
63+
op.create_index(op.f('ix_roles_name_app'), 'roles', ['name', 'app'], unique=True)
64+
65+
# Add app column to organization table (0 = ANYWAY, 1 = SAFETY_DATA)
66+
op.add_column('organization', sa.Column('app', sa.Integer(), nullable=False, server_default=ANYWAY_APP_ID_STR))
67+
op.alter_column('organization', 'app', server_default=None)
68+
op.create_index(op.f('ix_organization_name_app'), 'organization', ['name', 'app'], unique=True)
69+
70+
# Add app column to users_to_roles table
71+
op.add_column('users_to_roles', sa.Column('app', sa.Integer(), nullable=False, server_default=ANYWAY_APP_ID_STR))
72+
op.alter_column('users_to_roles', 'app', server_default=None)
73+
# op.drop_constraint('users_to_roles_pkey', 'users_to_roles', type_='primary')
74+
op.create_index('ix_users_to_roles_user_role_app', 'users_to_roles', ['user_id', 'role_id', 'app'],
75+
unique=True)
76+
77+
# Add app column to users_to_organizations table
78+
op.add_column('users_to_organizations', sa.Column('app', sa.Integer(), nullable=False,
79+
server_default=ANYWAY_APP_ID_STR))
80+
op.alter_column('users_to_organizations', 'app', server_default=None)
81+
op.create_index('ix_users_to_organizations_user_org_app', 'users_to_organizations',
82+
['user_id', 'organization_id', 'app'], unique=True)
83+
84+
# Create grants table
85+
op.create_table('grants',
86+
sa.Column('id', sa.Integer(), primary_key=True, autoincrement=True, nullable=False),
87+
sa.Column('name', sa.String(length=100), nullable=False),
88+
sa.Column('app', sa.Integer(), nullable=False),
89+
sa.Column('description', sa.String(length=255), nullable=True),
90+
sa.Column('create_date', sa.DateTime(), server_default=text('now()'), nullable=False),
91+
)
92+
op.create_index(op.f('ix_grants_name_app'), 'grants', ['name', 'app'], unique=True)
93+
94+
# Create users_to_grants table
95+
op.create_table('users_to_grants',
96+
sa.Column('user_id', sa.BigInteger(), nullable=False),
97+
sa.Column('grant_id', sa.Integer(), nullable=False),
98+
sa.Column('app', sa.Integer(), nullable=False),
99+
sa.Column('create_date', sa.DateTime(), server_default=text('now()'), nullable=False),
100+
sa.ForeignKeyConstraint(['grant_id'], ['grants.id'], ),
101+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
102+
)
103+
op.create_index(op.f('ix_users_to_grants_user_id'), 'users_to_grants', ['user_id'], unique=False)
104+
op.create_index('ix_users_to_grants_user_grant_app', 'users_to_grants', ['user_id', 'grant_id', 'app'], unique=True)
105+
106+
# Insert default roles for safety_data (app = 1)
107+
op.execute(f"""INSERT INTO roles (name, description, app, create_date)
108+
VALUES ('anonymous', 'Anonymous user', {SAFETY_DATA_APP_ID}, now())""")
109+
op.execute(f"""INSERT INTO roles (name, description, app, create_date)
110+
VALUES ('authenticated', 'Basic authenticated user', {SAFETY_DATA_APP_ID}, now())""")
111+
op.execute(f"""INSERT INTO roles (name, description, app, create_date)
112+
VALUES ('admins', 'Safety-Data administrator', {SAFETY_DATA_APP_ID}, now())""")
113+
114+
add_builtin_safety_data_admin()
115+
116+
117+
def downgrade():
118+
# Remove default roles (app = 1 for safety_data)
119+
for table in existing_tables:
120+
op.execute(f"DELETE FROM {table} WHERE app = 1")
121+
122+
# Revert users_to_grants table
123+
op.drop_table('users_to_grants')
124+
op.drop_table('grants')
125+
126+
# users_to_organizations
127+
op.drop_index('ix_users_to_organizations_user_org_app', table_name='users_to_organizations')
128+
op.create_index('ix_users_to_organizations_user_org', 'users_to_organizations', ['user_id', 'organization_id'], unique=True)
129+
op.drop_column('users_to_organizations', 'app')
130+
131+
# Revert users_to_roles table
132+
op.drop_index('ix_users_to_roles_user_role_app', table_name='users_to_roles')
133+
op.create_index('ix_users_to_roles_user_role', 'users_to_roles', ['user_id', 'role_id'], unique=True)
134+
op.drop_column('users_to_roles', 'app')
135+
136+
# Revert organization table
137+
op.drop_index(op.f('ix_organization_name_app'), table_name='organization')
138+
op.create_index('ix_organization_name', 'organization', ['name'], unique=True)
139+
# op.create_unique_constraint('organization_name_key', 'organization', ['name']) # Implicitly created by unique index?
140+
op.drop_column('organization', 'app')
141+
142+
# Revert roles table
143+
op.drop_index(op.f('ix_roles_name_app'), table_name='roles')
144+
op.create_index('ix_roles_name', 'roles', ['name'], unique=True)
145+
# op.create_unique_constraint('roles_name_key', 'roles', ['name'])
146+
op.drop_column('roles', 'app')
147+
148+
# Revert users table
149+
op.drop_index(op.f('ix_users_email_app'), table_name='users')
150+
op.drop_column('users', 'app')
151+
152+
def add_builtin_safety_data_admin():
153+
from anyway.models import Roles, Users, users_to_roles
154+
155+
ADMIN_EMAIL = "anyway@anyway.co.il"
156+
bind = op.get_bind()
157+
session = orm.Session(bind=bind)
158+
159+
res = session.query(Users).with_entities(Users.email) \
160+
.filter(Users.email == ADMIN_EMAIL, Users.app == SAFETY_DATA_APP_ID).first()
161+
if res is None:
162+
user = Users(
163+
user_register_date=datetime.datetime.now(),
164+
user_last_login_date=datetime.datetime.now(),
165+
email=ADMIN_EMAIL,
166+
oauth_provider_user_name=ADMIN_EMAIL,
167+
is_active=True,
168+
oauth_provider="google",
169+
is_user_completed_registration=True,
170+
oauth_provider_user_id="unknown-manual-insert",
171+
app=SAFETY_DATA_APP_ID,
172+
)
173+
session.add(user)
174+
175+
user_id = (
176+
session.query(Users).with_entities(Users.id).filter(
177+
Users.email == ADMIN_EMAIL, Users.app == SAFETY_DATA_APP_ID
178+
).first()
179+
)
180+
181+
role_id = session.query(Roles).with_entities(Roles.id).filter(
182+
Roles.name == "admins", Roles.app == SAFETY_DATA_APP_ID
183+
).first()
184+
185+
insert_users_to_roles = users_to_roles.insert().values(
186+
user_id=user_id.id,
187+
role_id=role_id.id,
188+
app=SAFETY_DATA_APP_ID,
189+
create_date=datetime.datetime.now(),
190+
)
191+
session.execute(insert_users_to_roles)
192+
193+
session.commit()

alembic/versions/bd67c88713b8_user_permissions_management.py

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,7 @@
77
"""
88

99
# revision identifiers, used by Alembic.
10-
import datetime
11-
12-
from sqlalchemy import orm, text
10+
from sqlalchemy import text
1311
from sqlalchemy.engine.reflection import Inspector
1412

1513

@@ -66,46 +64,33 @@ def upgrade():
6664
sa.PrimaryKeyConstraint("user_id", "role_id"),
6765
)
6866

69-
from anyway.models import Roles, Users, users_to_roles
70-
71-
bind = op.get_bind()
72-
session = orm.Session(bind=bind)
73-
74-
role_admins = Roles(
75-
name="admins",
76-
description="This is the default admin role.",
77-
create_date=datetime.datetime.now(),
78-
)
79-
session.add(role_admins)
80-
81-
res = session.query(Users).with_entities(Users.email).filter(Users.email == ADMIN_EMAIL).first()
82-
if res is None:
83-
user = Users(
84-
user_register_date=datetime.datetime.now(),
85-
user_last_login_date=datetime.datetime.now(),
86-
email=ADMIN_EMAIL,
87-
oauth_provider_user_name=ADMIN_EMAIL,
88-
is_active=True,
89-
oauth_provider="google",
90-
is_user_completed_registration=True,
91-
oauth_provider_user_id="unknown-manual-insert",
67+
# Use raw SQL to avoid schema mismatch with future 'app' column addition
68+
# Insert admin role
69+
op.execute("""
70+
INSERT INTO roles (name, description, create_date)
71+
VALUES ('admins', 'This is the default admin role.', now())
72+
""")
73+
74+
# Insert admin user if not exists
75+
op.execute(f"""
76+
INSERT INTO users (user_register_date, user_last_login_date, email, oauth_provider_user_name,
77+
is_active, oauth_provider, is_user_completed_registration, oauth_provider_user_id)
78+
SELECT now(), now(), '{ADMIN_EMAIL}', '{ADMIN_EMAIL}',
79+
true, 'google', true, 'unknown-manual-insert'
80+
WHERE NOT EXISTS (SELECT 1 FROM users WHERE email = '{ADMIN_EMAIL}')
81+
""")
82+
83+
# Insert user-role association
84+
op.execute(f"""
85+
INSERT INTO users_to_roles (user_id, role_id, create_date)
86+
SELECT u.id, r.id, now()
87+
FROM users u, roles r
88+
WHERE u.email = '{ADMIN_EMAIL}' AND r.name = 'admins'
89+
AND NOT EXISTS (
90+
SELECT 1 FROM users_to_roles utr
91+
WHERE utr.user_id = u.id AND utr.role_id = r.id
9292
)
93-
session.add(user)
94-
95-
user_id = (
96-
session.query(Users).with_entities(Users.id).filter(Users.email == ADMIN_EMAIL).first()
97-
)
98-
99-
role_id = session.query(Roles).with_entities(Roles.id).filter(Roles.name == "admins").first()
100-
101-
insert_users_to_roles = users_to_roles.insert().values(
102-
user_id=user_id.id,
103-
role_id=role_id.id,
104-
create_date=datetime.datetime.now(),
105-
)
106-
session.execute(insert_users_to_roles)
107-
108-
session.commit()
93+
""")
10994

11095

11196
def downgrade():

anyway/backend_constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class Roles2Names(Enum):
5555
"https://anyway-infographics-demo.web.app",
5656
"https://media.anyway.co.il",
5757
"https://dev.anyway.co.il",
58+
"https://safety-data.anyway.co.il",
5859
]
5960

6061
ANYWAY_CORS_SITE_LIST_DEV = ANYWAY_CORS_SITE_LIST_PROD + [
@@ -63,6 +64,8 @@ class Roles2Names(Enum):
6364
"https://localhost:3000",
6465
"http://127.0.0.1:3000",
6566
"https://127.0.0.1:3000",
67+
"http://127.0.0.1:5000",
68+
"http://localhost:5000",
6669
]
6770

6871
class ResolutionCategories(Enum):

0 commit comments

Comments
 (0)