feat(T01): auth + user management + RBAC + base frontend + i18n
- Backend: FastAPI, JWT auth (HS256), bcrypt, RBAC middleware - User CRUD (admin-only), soft-delete, pagination - Pydantic BaseSettings config, async SQLAlchemy 2.0 - 50 backend tests, 88% coverage - Frontend: Next.js 14 App Router, Tailwind, design tokens - Login page, auth context, API client with auto-refresh - i18n: next-intl, DE/EN (31 keys each) - 6 base UI components (Button, Input, Card, Table, Modal, Toast) - 12 frontend tests, npm build success
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
"""SQLAlchemy User model with UUID primary key."""
|
||||
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Enum, String, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class UserRole(str, enum.Enum):
|
||||
"""User roles for RBAC."""
|
||||
admin = "admin"
|
||||
verkaeufer = "verkaeufer"
|
||||
buchhaltung = "buchhaltung"
|
||||
|
||||
|
||||
class User(Base):
|
||||
"""User entity for authentication and authorization."""
|
||||
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
default=uuid.uuid4,
|
||||
)
|
||||
email: Mapped[str] = mapped_column(
|
||||
String(255), unique=True, nullable=False, index=True,
|
||||
)
|
||||
password_hash: Mapped[str] = mapped_column(
|
||||
String(255), nullable=False,
|
||||
)
|
||||
full_name: Mapped[str] = mapped_column(
|
||||
String(200), nullable=False,
|
||||
)
|
||||
role: Mapped[UserRole] = mapped_column(
|
||||
Enum(UserRole, name="user_role"),
|
||||
nullable=False,
|
||||
default=UserRole.verkaeufer,
|
||||
)
|
||||
language: Mapped[str] = mapped_column(
|
||||
String(5), nullable=False, default="de",
|
||||
)
|
||||
is_active: Mapped[bool] = mapped_column(
|
||||
Boolean, nullable=False, default=True,
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=func.now(),
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<User id={self.id} email={self.email} role={self.role}>"
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Serialize user for API responses (excludes password_hash)."""
|
||||
return {
|
||||
"id": str(self.id),
|
||||
"email": self.email,
|
||||
"full_name": self.full_name,
|
||||
"role": self.role.value if isinstance(self.role, UserRole) else self.role,
|
||||
"language": self.language,
|
||||
"is_active": self.is_active,
|
||||
"created_at": self.created_at.isoformat() if self.created_at else None,
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
Reference in New Issue
Block a user