Skip to content

Commit fc9207a

Browse files
committed
fix: update migration
1 parent 2fea739 commit fc9207a

4 files changed

Lines changed: 263 additions & 22 deletions

File tree

alembic/env.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
)
3838
from src.modules.todo.infrastructure.models.todo_model import TodoModel # noqa: F401
3939
from src.modules.user.infrastructure.models.refresh_token_model import (
40-
RefreshTokenModel, # noqa: F401
40+
UserSessionModel, # noqa: F401
4141
)
4242
from src.modules.user.infrastructure.models.user_model import UserModel # noqa: F401
4343
from src.shared.database.model import Base
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
"""normalize user tables
2+
3+
Revision ID: e73c215d7221
4+
Revises: d9a7c3f2b6e1
5+
Create Date: 2026-06-22 11:45:27.978571
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
from sqlalchemy.dialects import postgresql
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = 'e73c215d7221'
16+
down_revision: Union[str, Sequence[str], None] = 'd9a7c3f2b6e1'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
"""Upgrade schema."""
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.create_table('user_addresses',
25+
sa.Column('id', sa.Uuid(), nullable=False),
26+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
27+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
28+
sa.Column('deleted_at', sa.DateTime(), nullable=True),
29+
sa.Column('user_id', sa.String(length=36), nullable=False),
30+
sa.Column('label', sa.String(length=100), nullable=False),
31+
sa.Column('line1', sa.String(length=255), nullable=False),
32+
sa.Column('line2', sa.String(length=255), nullable=True),
33+
sa.Column('line3', sa.String(length=255), nullable=True),
34+
sa.Column('city', sa.String(length=100), nullable=False),
35+
sa.Column('state', sa.String(length=100), nullable=True),
36+
sa.Column('postal_code', sa.String(length=20), nullable=False),
37+
sa.Column('country', sa.String(length=2), nullable=False),
38+
sa.Column('is_default', sa.Boolean(), nullable=False),
39+
sa.PrimaryKeyConstraint('id')
40+
)
41+
op.create_index('ix_user_addresses_country', 'user_addresses', ['country'], unique=False)
42+
op.create_index('ix_user_addresses_is_default', 'user_addresses', ['is_default'], unique=False)
43+
op.create_index('ix_user_addresses_label', 'user_addresses', ['label'], unique=False)
44+
op.create_index('ix_user_addresses_user_id', 'user_addresses', ['user_id'], unique=False)
45+
op.create_table('user_contacts',
46+
sa.Column('id', sa.Uuid(), nullable=False),
47+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
48+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
49+
sa.Column('deleted_at', sa.DateTime(), nullable=True),
50+
sa.Column('user_id', sa.String(length=36), nullable=False),
51+
sa.Column('contact_type', sa.String(length=50), nullable=False),
52+
sa.Column('value', sa.String(length=255), nullable=False),
53+
sa.Column('is_primary', sa.Boolean(), nullable=False),
54+
sa.Column('is_verified', sa.Boolean(), nullable=False),
55+
sa.PrimaryKeyConstraint('id')
56+
)
57+
op.create_index('ix_user_contacts_is_primary', 'user_contacts', ['is_primary'], unique=False)
58+
op.create_index('ix_user_contacts_type', 'user_contacts', ['contact_type'], unique=False)
59+
op.create_index('ix_user_contacts_user_id', 'user_contacts', ['user_id'], unique=False)
60+
op.create_table('user_profiles',
61+
sa.Column('id', sa.Uuid(), nullable=False),
62+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
63+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
64+
sa.Column('deleted_at', sa.DateTime(), nullable=True),
65+
sa.Column('user_id', sa.String(length=36), nullable=False),
66+
sa.Column('first_name', sa.String(length=100), nullable=True),
67+
sa.Column('last_name', sa.String(length=100), nullable=True),
68+
sa.Column('display_name', sa.String(length=255), nullable=True),
69+
sa.Column('avatar_url', sa.String(length=500), nullable=True),
70+
sa.Column('bio', sa.Text(), nullable=True),
71+
sa.Column('birth_date', sa.Date(), nullable=True),
72+
sa.PrimaryKeyConstraint('id'),
73+
sa.UniqueConstraint('user_id')
74+
)
75+
op.create_index('ix_user_profiles_user_id', 'user_profiles', ['user_id'], unique=True)
76+
op.create_table('user_security',
77+
sa.Column('id', sa.Uuid(), nullable=False),
78+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
79+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
80+
sa.Column('deleted_at', sa.DateTime(), nullable=True),
81+
sa.Column('user_id', sa.String(length=36), nullable=False),
82+
sa.Column('failed_login_attempts', sa.Integer(), nullable=False),
83+
sa.Column('locked_until', sa.DateTime(timezone=True), nullable=True),
84+
sa.Column('password_changed_at', sa.DateTime(timezone=True), nullable=True),
85+
sa.Column('two_factor_enabled', sa.Boolean(), nullable=False),
86+
sa.Column('two_factor_secret', sa.String(length=255), nullable=True),
87+
sa.Column('two_factor_backup_codes', sa.String(length=1000), nullable=True),
88+
sa.PrimaryKeyConstraint('id'),
89+
sa.UniqueConstraint('user_id')
90+
)
91+
op.create_index('ix_user_security_locked_until', 'user_security', ['locked_until'], unique=False)
92+
op.create_index('ix_user_security_two_factor_enabled', 'user_security', ['two_factor_enabled'], unique=False)
93+
op.create_index('ix_user_security_user_id', 'user_security', ['user_id'], unique=True)
94+
op.create_table('user_sessions',
95+
sa.Column('id', sa.Uuid(), nullable=False),
96+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
97+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
98+
sa.Column('deleted_at', sa.DateTime(), nullable=True),
99+
sa.Column('user_id', sa.String(length=36), nullable=False),
100+
sa.Column('refresh_token_hash', sa.String(length=255), nullable=False),
101+
sa.Column('expires_at', sa.DateTime(timezone=True), nullable=False),
102+
sa.Column('device_info', sa.String(length=500), nullable=True),
103+
sa.Column('ip_address', sa.String(length=45), nullable=True),
104+
sa.Column('user_agent', sa.String(length=1000), nullable=True),
105+
sa.Column('is_revoked', sa.Boolean(), nullable=False),
106+
sa.Column('revoked_at', sa.DateTime(timezone=True), nullable=True),
107+
sa.Column('revoked_reason', sa.String(length=255), nullable=True),
108+
sa.PrimaryKeyConstraint('id')
109+
)
110+
op.create_index('ix_user_sessions_device_info', 'user_sessions', ['device_info'], unique=False)
111+
op.create_index('ix_user_sessions_expires_at', 'user_sessions', ['expires_at'], unique=False)
112+
op.create_index('ix_user_sessions_is_revoked', 'user_sessions', ['is_revoked'], unique=False)
113+
op.create_index('ix_user_sessions_token_hash', 'user_sessions', ['refresh_token_hash'], unique=True)
114+
op.create_index('ix_user_sessions_user_id', 'user_sessions', ['user_id'], unique=False)
115+
op.create_table('user_settings',
116+
sa.Column('id', sa.Uuid(), nullable=False),
117+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
118+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
119+
sa.Column('deleted_at', sa.DateTime(), nullable=True),
120+
sa.Column('user_id', sa.String(length=36), nullable=False),
121+
sa.Column('preferences', postgresql.JSONB(astext_type=sa.Text()), nullable=False),
122+
sa.PrimaryKeyConstraint('id'),
123+
sa.UniqueConstraint('user_id')
124+
)
125+
op.create_index('ix_user_settings_preferences', 'user_settings', ['preferences'], unique=False, postgresql_using='gin')
126+
op.create_index('ix_user_settings_user_id', 'user_settings', ['user_id'], unique=True)
127+
op.create_table('user_verifications',
128+
sa.Column('id', sa.Uuid(), nullable=False),
129+
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
130+
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=False),
131+
sa.Column('deleted_at', sa.DateTime(), nullable=True),
132+
sa.Column('user_id', sa.String(length=36), nullable=False),
133+
sa.Column('channel', sa.String(length=50), nullable=False),
134+
sa.Column('is_verified', sa.Boolean(), nullable=False),
135+
sa.Column('verified_at', sa.DateTime(timezone=True), nullable=True),
136+
sa.Column('verification_token', sa.String(length=255), nullable=True),
137+
sa.Column('token_expires_at', sa.DateTime(timezone=True), nullable=True),
138+
sa.PrimaryKeyConstraint('id')
139+
)
140+
op.create_index('ix_user_verifications_channel', 'user_verifications', ['channel'], unique=False)
141+
op.create_index('ix_user_verifications_is_verified', 'user_verifications', ['is_verified'], unique=False)
142+
op.create_index('ix_user_verifications_token', 'user_verifications', ['verification_token'], unique=False)
143+
op.create_index('ix_user_verifications_user_id', 'user_verifications', ['user_id'], unique=False)
144+
op.drop_index(op.f('ix_refresh_tokens_token_hash'), table_name='refresh_tokens')
145+
op.drop_index(op.f('ix_refresh_tokens_user_id'), table_name='refresh_tokens')
146+
op.drop_table('refresh_tokens')
147+
op.create_unique_constraint(None, 'authorization_resources', ['key'])
148+
op.alter_column('permissions', 'resource_id',
149+
existing_type=sa.UUID(),
150+
nullable=False)
151+
op.create_unique_constraint(None, 'permissions', ['key'])
152+
op.drop_constraint(op.f('fk_permissions_resource_id_authorization_resources'), 'permissions', type_='foreignkey')
153+
op.drop_constraint(op.f('role_permissions_permission_id_fkey'), 'role_permissions', type_='foreignkey')
154+
op.drop_constraint(op.f('role_permissions_role_id_fkey'), 'role_permissions', type_='foreignkey')
155+
op.create_unique_constraint(None, 'roles', ['name'])
156+
op.drop_constraint(op.f('todos_user_id_fkey'), 'todos', type_='foreignkey')
157+
op.drop_constraint(op.f('user_has_roles_user_id_fkey'), 'user_has_roles', type_='foreignkey')
158+
op.drop_constraint(op.f('user_has_roles_role_id_fkey'), 'user_has_roles', type_='foreignkey')
159+
op.add_column('users', sa.Column('password_hash', sa.String(length=255), nullable=True))
160+
op.add_column('users', sa.Column('auth_provider', sa.String(length=50), nullable=False))
161+
op.add_column('users', sa.Column('external_id', sa.String(length=255), nullable=True))
162+
op.add_column('users', sa.Column('status', sa.String(length=50), nullable=False))
163+
op.alter_column('users', 'username',
164+
existing_type=sa.VARCHAR(length=255),
165+
type_=sa.String(length=100),
166+
existing_nullable=True)
167+
op.create_index('ix_users_auth_provider', 'users', ['auth_provider'], unique=False)
168+
op.create_index('ix_users_status', 'users', ['status'], unique=False)
169+
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
170+
op.drop_column('users', 'password')
171+
op.drop_column('users', 'fullname')
172+
op.drop_column('users', 'birthday')
173+
# ### end Alembic commands ###
174+
175+
176+
def downgrade() -> None:
177+
"""Downgrade schema."""
178+
# ### commands auto generated by Alembic - please adjust! ###
179+
op.add_column('users', sa.Column('birthday', sa.DATE(), autoincrement=False, nullable=True))
180+
op.add_column('users', sa.Column('fullname', sa.VARCHAR(length=255), autoincrement=False, nullable=True))
181+
op.add_column('users', sa.Column('password', sa.VARCHAR(length=255), autoincrement=False, nullable=False))
182+
op.drop_index(op.f('ix_users_username'), table_name='users')
183+
op.drop_index('ix_users_status', table_name='users')
184+
op.drop_index('ix_users_auth_provider', table_name='users')
185+
op.alter_column('users', 'username',
186+
existing_type=sa.String(length=100),
187+
type_=sa.VARCHAR(length=255),
188+
existing_nullable=True)
189+
op.drop_column('users', 'status')
190+
op.drop_column('users', 'external_id')
191+
op.drop_column('users', 'auth_provider')
192+
op.drop_column('users', 'password_hash')
193+
op.create_foreign_key(op.f('user_has_roles_role_id_fkey'), 'user_has_roles', 'roles', ['role_id'], ['id'])
194+
op.create_foreign_key(op.f('user_has_roles_user_id_fkey'), 'user_has_roles', 'users', ['user_id'], ['id'])
195+
op.create_foreign_key(op.f('todos_user_id_fkey'), 'todos', 'users', ['user_id'], ['id'])
196+
op.drop_constraint(None, 'roles', type_='unique')
197+
op.create_foreign_key(op.f('role_permissions_role_id_fkey'), 'role_permissions', 'roles', ['role_id'], ['id'])
198+
op.create_foreign_key(op.f('role_permissions_permission_id_fkey'), 'role_permissions', 'permissions', ['permission_id'], ['id'])
199+
op.create_foreign_key(op.f('fk_permissions_resource_id_authorization_resources'), 'permissions', 'authorization_resources', ['resource_id'], ['id'])
200+
op.drop_constraint(None, 'permissions', type_='unique')
201+
op.alter_column('permissions', 'resource_id',
202+
existing_type=sa.UUID(),
203+
nullable=True)
204+
op.drop_constraint(None, 'authorization_resources', type_='unique')
205+
op.create_table('refresh_tokens',
206+
sa.Column('id', sa.UUID(), autoincrement=False, nullable=False),
207+
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=False),
208+
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=False),
209+
sa.Column('deleted_at', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
210+
sa.Column('user_id', sa.UUID(), autoincrement=False, nullable=False),
211+
sa.Column('token_hash', sa.VARCHAR(length=255), autoincrement=False, nullable=False),
212+
sa.Column('expires_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=False),
213+
sa.Column('is_revoked', sa.BOOLEAN(), autoincrement=False, nullable=False),
214+
sa.ForeignKeyConstraint(['user_id'], ['users.id'], name=op.f('refresh_tokens_user_id_fkey')),
215+
sa.PrimaryKeyConstraint('id', name=op.f('refresh_tokens_pkey'))
216+
)
217+
op.create_index(op.f('ix_refresh_tokens_user_id'), 'refresh_tokens', ['user_id'], unique=False)
218+
op.create_index(op.f('ix_refresh_tokens_token_hash'), 'refresh_tokens', ['token_hash'], unique=True)
219+
op.drop_index('ix_user_verifications_user_id', table_name='user_verifications')
220+
op.drop_index('ix_user_verifications_token', table_name='user_verifications')
221+
op.drop_index('ix_user_verifications_is_verified', table_name='user_verifications')
222+
op.drop_index('ix_user_verifications_channel', table_name='user_verifications')
223+
op.drop_table('user_verifications')
224+
op.drop_index('ix_user_settings_user_id', table_name='user_settings')
225+
op.drop_index('ix_user_settings_preferences', table_name='user_settings', postgresql_using='gin')
226+
op.drop_table('user_settings')
227+
op.drop_index('ix_user_sessions_user_id', table_name='user_sessions')
228+
op.drop_index('ix_user_sessions_token_hash', table_name='user_sessions')
229+
op.drop_index('ix_user_sessions_is_revoked', table_name='user_sessions')
230+
op.drop_index('ix_user_sessions_expires_at', table_name='user_sessions')
231+
op.drop_index('ix_user_sessions_device_info', table_name='user_sessions')
232+
op.drop_table('user_sessions')
233+
op.drop_index('ix_user_security_user_id', table_name='user_security')
234+
op.drop_index('ix_user_security_two_factor_enabled', table_name='user_security')
235+
op.drop_index('ix_user_security_locked_until', table_name='user_security')
236+
op.drop_table('user_security')
237+
op.drop_index('ix_user_profiles_user_id', table_name='user_profiles')
238+
op.drop_table('user_profiles')
239+
op.drop_index('ix_user_contacts_user_id', table_name='user_contacts')
240+
op.drop_index('ix_user_contacts_type', table_name='user_contacts')
241+
op.drop_index('ix_user_contacts_is_primary', table_name='user_contacts')
242+
op.drop_table('user_contacts')
243+
op.drop_index('ix_user_addresses_user_id', table_name='user_addresses')
244+
op.drop_index('ix_user_addresses_label', table_name='user_addresses')
245+
op.drop_index('ix_user_addresses_is_default', table_name='user_addresses')
246+
op.drop_index('ix_user_addresses_country', table_name='user_addresses')
247+
op.drop_table('user_addresses')
248+
# ### end Alembic commands ###

src/core/seed/authorization.py

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
from dataclasses import dataclass
22
from typing import Protocol
33

4-
from src.core.authorization.permissions import (
4+
from src.modules.authorization import AuthorizationResource, Permission, Role
5+
from src.modules.authorization.domain.permissions import (
6+
DEFAULT_POLICIES,
57
DEFAULT_RESOURCES,
68
DEFAULT_ROLES,
7-
DEFAULT_POLICIES,
89
)
9-
from src.modules.authorization import AuthorizationResource, Permission, Role
1010

1111

1212
class AuthorizationSeedRepository(Protocol):
@@ -84,7 +84,9 @@ async def seed_authorization(
8484
if name in existing_roles:
8585
continue
8686

87-
role = await repository.create_role(Role.create(name=name, description=description))
87+
role = await repository.create_role(
88+
Role.create(name=name, description=description)
89+
)
8890
existing_roles[role.name] = role
8991
roles_created += 1
9092

@@ -138,24 +140,16 @@ async def seed_authorization(
138140

139141

140142
def _default_roles() -> dict[str, str]:
141-
return {
142-
role.name: role.description
143-
for role in DEFAULT_ROLES
144-
}
143+
return {role.name: role.description for role in DEFAULT_ROLES}
145144

146145

147146
def _default_permission_keys() -> list[str]:
148-
return [
149-
permission_key
150-
for _, _, permission_key in _permission_policies()
151-
]
147+
return [permission_key for _, _, permission_key in _permission_policies()]
152148

153149

154150
def _permission_policies() -> list[tuple[str, str, str]]:
155151
return [
156-
policy
157-
for policy in DEFAULT_POLICIES
158-
if policy[0] == "p" and policy[2] != "*"
152+
policy for policy in DEFAULT_POLICIES if policy[0] == "p" and policy[2] != "*"
159153
]
160154

161155

src/core/seed/user.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
from dataclasses import dataclass
22
from typing import Protocol
33

4-
from src.core.authorization.permissions import (
4+
from src.core.security.password import PasswordSerrvice
5+
from src.modules.authorization.domain.permissions import (
56
ADMIN_ROLE,
67
DEFAULT_USER_ROLE,
78
MANAGER_ROLE,
89
VIEWER_ROLE,
910
)
10-
from src.core.security.password import PasswordSerrvice
11-
from src.modules.user import User
11+
from src.modules.user.domain.entities.user import User
1212

1313

1414
class SeedUserRepository(Protocol):
@@ -50,9 +50,8 @@ def has_admin_credentials(self) -> bool:
5050

5151
@property
5252
def should_seed_development_users(self) -> bool:
53-
return (
54-
self.app_env.lower() == "development"
55-
and bool(self.development_users_password.strip())
53+
return self.app_env.lower() == "development" and bool(
54+
self.development_users_password.strip()
5655
)
5756

5857

0 commit comments

Comments
 (0)