add user name and surname

This commit is contained in:
2026-03-10 16:14:00 +01:00
parent 9332e29e53
commit f6ed383b3a
5 changed files with 84 additions and 2 deletions

View File

@@ -0,0 +1,30 @@
"""add name and surname to users table
Revision ID: 818478c251dc
Revises: 004
Create Date: 2026-03-10 15:10:42.811947
"""
from __future__ import annotations
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '818478c251dc'
down_revision: Union[str, None] = '004'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.add_column('users', sa.Column('name', sa.String(length=100), nullable=True))
op.add_column('users', sa.Column('surname', sa.String(length=100), nullable=True))
def downgrade() -> None:
op.drop_column('users', 'surname')
op.drop_column('users', 'name')

View File

@@ -55,11 +55,23 @@ async def get_current_user(
raise credentials_exc raise credentials_exc
# Live tier lookup — subscription row is the authoritative source. # Live tier lookup — subscription row is the authoritative source.
from app.models import Subscription # noqa: PLC0415 from app.models import Subscription, User # noqa: PLC0415
result = await db.execute( result = await db.execute(
select(Subscription.tier).where(Subscription.user_id == user_id) select(Subscription.tier).where(Subscription.user_id == user_id)
) )
tier: str = result.scalar_one_or_none() or "free" tier: str = result.scalar_one_or_none() or "free"
return UserProfile(id=user_id, email=email, tier=tier) # type: ignore[arg-type] # Fetch name/surname from user row.
user_result = await db.execute(
select(User.name, User.surname).where(User.id == user_id)
)
user_row = user_result.one_or_none()
return UserProfile(
id=user_id,
email=email,
name=user_row.name if user_row else None,
surname=user_row.surname if user_row else None,
tier=tier,
) # type: ignore[arg-type]

View File

@@ -66,6 +66,8 @@ def _make_access_token(user_id: str, email: str, tier: str) -> tuple[str, int]:
class _RegisterRequest(BaseModel): class _RegisterRequest(BaseModel):
email: str email: str
password: str password: str
name: str | None = None
surname: str | None = None
class _LoginRequest(BaseModel): class _LoginRequest(BaseModel):
@@ -93,6 +95,8 @@ async def register(
user = User( user = User(
id=str(uuid.uuid4()), id=str(uuid.uuid4()),
email=body.email, email=body.email,
name=body.name,
surname=body.surname,
password_hash=_hash_password(body.password), password_hash=_hash_password(body.password),
tier="free", tier="free",
encryption_key=Fernet.generate_key().decode(), encryption_key=Fernet.generate_key().decode(),
@@ -193,7 +197,39 @@ async def refresh(
) )
class _UpdateProfileRequest(BaseModel):
name: str | None = None
surname: str | None = None
@router.get("/me", response_model=UserProfile) @router.get("/me", response_model=UserProfile)
async def me(current_user: UserProfile = Depends(get_current_user)) -> UserProfile: async def me(current_user: UserProfile = Depends(get_current_user)) -> UserProfile:
"""Return the profile for the authenticated user.""" """Return the profile for the authenticated user."""
return current_user return current_user
@router.put("/me", response_model=UserProfile)
async def update_profile(
body: _UpdateProfileRequest,
current_user: UserProfile = Depends(get_current_user),
db: AsyncSession = Depends(get_session),
) -> UserProfile:
"""Update the authenticated user's name and surname."""
result = await db.execute(select(User).where(User.id == current_user.id))
user = result.scalar_one()
if body.name is not None:
user.name = body.name
if body.surname is not None:
user.surname = body.surname
await db.commit()
await db.refresh(user)
return UserProfile(
id=user.id,
email=user.email,
name=user.name,
surname=user.surname,
tier=current_user.tier,
)

View File

@@ -75,6 +75,8 @@ class User(Base):
Uuid(as_uuid=False), primary_key=True, default=_uuid Uuid(as_uuid=False), primary_key=True, default=_uuid
) )
email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True) email: Mapped[str] = mapped_column(String(255), unique=True, nullable=False, index=True)
name: Mapped[str | None] = mapped_column(String(100), nullable=True)
surname: Mapped[str | None] = mapped_column(String(100), nullable=True)
password_hash: Mapped[str] = mapped_column(String(255), nullable=False) password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
tier: Mapped[str] = mapped_column(TierEnum, nullable=False, default="free") tier: Mapped[str] = mapped_column(TierEnum, nullable=False, default="free")
stripe_customer_id: Mapped[str | None] = mapped_column(String(255), nullable=True) stripe_customer_id: Mapped[str | None] = mapped_column(String(255), nullable=True)

View File

@@ -27,6 +27,8 @@ class AuthTokens(BaseModel):
class UserProfile(BaseModel): class UserProfile(BaseModel):
id: str id: str
email: str email: str
name: str | None = None
surname: str | None = None
tier: BillingTier tier: BillingTier