Skip to content

Commit 40b3b67

Browse files
committed
refactor: user domain entities and commands use integer ids
1 parent 48dce53 commit 40b3b67

8 files changed

Lines changed: 28 additions & 39 deletions

File tree

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
from uuid import UUID
2-
31
from src.modules.user.application.auth.logout_user.command import LogoutUserCommand
42

53

64
def validate_logout_user_command(command: LogoutUserCommand) -> None:
75
try:
8-
UUID(command.user_id)
6+
int(command.user_id)
97
except ValueError as exc:
10-
raise ValueError("User id must be a valid UUID") from exc
8+
raise ValueError("User id must be a valid integer") from exc
119

1210
if not command.access_token.strip():
1311
raise ValueError("Access token is required")

src/modules/user/application/auth/two_factor/command.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,44 @@
22

33
from pydantic import BaseModel
44
from typing import Literal
5-
from uuid import UUID
65

76

87
class SetupTOTPCommand(BaseModel):
98
"""Command to set up TOTP 2FA."""
10-
user_id: UUID
9+
user_id: int
1110

1211

1312
class VerifyTOTPSetupCommand(BaseModel):
1413
"""Command to verify and enable TOTP 2FA."""
15-
user_id: UUID
14+
user_id: int
1615
code: str
1716

1817

1918
class DisableTOTPCommand(BaseModel):
2019
"""Command to disable TOTP 2FA."""
21-
user_id: UUID
20+
user_id: int
2221
code: str
2322

2423

2524
class SendEmail2FACodeCommand(BaseModel):
2625
"""Command to send a 2FA code via email."""
27-
user_id: UUID
26+
user_id: int
2827

2928

3029
class VerifyEmail2FACodeCommand(BaseModel):
3130
"""Command to verify an email-based 2FA code."""
32-
user_id: UUID
31+
user_id: int
3332
code: str
3433

3534

3635
class RegenerateBackupCodesCommand(BaseModel):
3736
"""Command to regenerate backup codes."""
38-
user_id: UUID
37+
user_id: int
3938
verify_code: str
4039

4140

4241
class Verify2FACommand(BaseModel):
4342
"""Command to verify a 2FA code during login."""
44-
user_id: UUID
43+
user_id: int
4544
code: str
4645
method: Literal["totp", "email", "backup"] = "totp"
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
from uuid import UUID
2-
31
from pydantic import BaseModel
42

53

64
class DetailUserQuery(BaseModel):
7-
user_id: UUID
5+
user_id: int

src/modules/user/domain/entities/refresh_token.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,22 @@
22

33
from dataclasses import dataclass
44
from datetime import datetime
5-
from uuid import UUID, uuid4
65

76

8-
@dataclass
7+
@dataclass(kw_only=True)
98
class RefreshToken:
10-
id: UUID
11-
user_id: UUID
9+
id: int | None = None
10+
user_id: int
1211
token_hash: str
1312
expires_at: datetime
1413
is_revoked: bool = False
1514

1615
@classmethod
1716
def create(
18-
cls, user_id: UUID, token_hash: str, expires_at: datetime
17+
cls, user_id: int, token_hash: str, expires_at: datetime
1918
) -> RefreshToken:
2019
return cls(
21-
id=uuid4(),
20+
id=None,
2221
user_id=user_id,
2322
token_hash=token_hash,
2423
expires_at=expires_at,

src/modules/user/domain/entities/user.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
from dataclasses import dataclass, field
44
from datetime import date, datetime
55
from typing import Optional
6-
from uuid import UUID, uuid4
76

87

98
@dataclass
109
class UserProfile:
1110
"""User profile containing personal information."""
1211

13-
user_id: UUID
12+
user_id: int
1413
first_name: Optional[str] = None
1514
last_name: Optional[str] = None
1615
display_name: Optional[str] = None
@@ -25,7 +24,7 @@ class UserProfile:
2524
class UserSettings:
2625
"""User preferences and settings."""
2726

28-
user_id: UUID
27+
user_id: int
2928
preferences: dict = field(default_factory=dict)
3029
created_at: Optional[str] = None
3130
updated_at: Optional[str] = None
@@ -35,7 +34,7 @@ class UserSettings:
3534
class UserSecurity:
3635
"""User security configuration and state."""
3736

38-
user_id: UUID
37+
user_id: int
3938
failed_login_attempts: int = 0
4039
locked_until: Optional[datetime] = None
4140
password_changed_at: Optional[datetime] = None
@@ -46,11 +45,11 @@ class UserSecurity:
4645
updated_at: Optional[str] = None
4746

4847

49-
@dataclass
48+
@dataclass(kw_only=True)
5049
class User:
5150
"""Core user identity and authentication aggregate root."""
5251

53-
id: UUID
52+
id: int | None = None
5453
email: str
5554
password_hash: str
5655

@@ -77,7 +76,7 @@ def create(
7776
auth_provider: str = "local",
7877
) -> User:
7978
return cls(
80-
id=uuid4(),
79+
id=None,
8180
email=email,
8281
password_hash=password_hash,
8382
username=username,

src/modules/user/domain/repositories/refresh_token_repository.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
from abc import ABC, abstractmethod
2-
from uuid import UUID
32

43
from src.modules.user.domain.entities.refresh_token import RefreshToken
54

@@ -14,6 +13,6 @@ async def save(self, refresh_token: RefreshToken) -> RefreshToken:
1413
pass
1514

1615
@abstractmethod
17-
async def revoke_by_user_id(self, user_id: UUID) -> None:
16+
async def revoke_by_user_id(self, user_id: int) -> None:
1817
"""Revokes all refresh tokens for a user (e.g., on password change or logout)"""
1918
pass

src/modules/user/domain/repositories/user_repository.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from abc import ABC, abstractmethod
22
from typing import Optional
3-
from uuid import UUID
43

54
from src.modules.user.domain.entities.user import User, UserProfile, UserSettings, UserSecurity
65

@@ -11,11 +10,11 @@ async def get_by_email(self, email: str) -> Optional[User]:
1110
pass
1211

1312
@abstractmethod
14-
async def get_by_id(self, user_id: UUID) -> Optional[User]:
13+
async def get_by_id(self, user_id: int) -> Optional[User]:
1514
pass
1615

1716
@abstractmethod
18-
async def get_by_id_with_relations(self, user_id: UUID) -> Optional[User]:
17+
async def get_by_id_with_relations(self, user_id: int) -> Optional[User]:
1918
"""Get user with profile, settings, and security loaded."""
2019
pass
2120

tests/test_application_validation.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from uuid import uuid4
2-
31
import pytest
42

53
from src.modules.todo.application.create_todo.command import CreateTodoCommand
@@ -65,21 +63,21 @@ def test_register_validation_rejects_short_password():
6563

6664

6765
def test_logout_validation_rejects_invalid_user_id():
68-
with pytest.raises(ValueError, match="User id must be a valid UUID"):
66+
with pytest.raises(ValueError, match="User id must be a valid integer"):
6967
validate_logout_user_command(
70-
LogoutUserCommand(user_id="not-a-uuid", access_token="access-token")
68+
LogoutUserCommand(user_id="not-an-int", access_token="access-token")
7169
)
7270

7371

7472
def test_logout_validation_rejects_blank_access_token():
7573
with pytest.raises(ValueError, match="Access token is required"):
7674
validate_logout_user_command(
77-
LogoutUserCommand(user_id=str(uuid4()), access_token=" ")
75+
LogoutUserCommand(user_id="1", access_token=" ")
7876
)
7977

8078

8179
def test_query_validation_accepts_valid_queries():
82-
user_id = uuid4()
80+
user_id = 1
8381

8482
validate_get_todos_query(GetTodosQuery(user_id=user_id))
8583
validate_detail_user_query(DetailUserQuery(user_id=user_id))

0 commit comments

Comments
 (0)