0f82f8a3a2
Add new application structure: - app/main.py - application entry point - app/plugins/ - plugin system with auth plugin: - api/ - REST API endpoints and schemas - authorization/ - auth policies, providers, hooks - domain/ - business logic (service, models, jwt, password) - injection/ - route injection and guards - ops/ - operational utilities - runtime/ - runtime configuration - security/ - middleware, CSRF, dependencies - storage/ - user repositories and models - app/static/ - static assets (scalar.js for API docs) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
98 lines
3.5 KiB
Python
98 lines
3.5 KiB
Python
from __future__ import annotations
|
|
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.exc import IntegrityError
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.plugins.auth.storage.contracts import User, UserCreate, UserRepositoryProtocol
|
|
from app.plugins.auth.storage.models import User as UserModel
|
|
|
|
|
|
def _to_user(model: UserModel) -> User:
|
|
return User(
|
|
id=model.id,
|
|
email=model.email,
|
|
password_hash=model.password_hash,
|
|
system_role=model.system_role,
|
|
oauth_provider=model.oauth_provider,
|
|
oauth_id=model.oauth_id,
|
|
needs_setup=model.needs_setup,
|
|
token_version=model.token_version,
|
|
created_time=model.created_time,
|
|
updated_time=model.updated_time,
|
|
)
|
|
|
|
|
|
class DbUserRepository(UserRepositoryProtocol):
|
|
def __init__(self, session: AsyncSession) -> None:
|
|
self._session = session
|
|
|
|
async def create_user(self, data: UserCreate) -> User:
|
|
model = UserModel(
|
|
id=data.id,
|
|
email=data.email,
|
|
password_hash=data.password_hash,
|
|
system_role=data.system_role,
|
|
oauth_provider=data.oauth_provider,
|
|
oauth_id=data.oauth_id,
|
|
needs_setup=data.needs_setup,
|
|
token_version=data.token_version,
|
|
)
|
|
self._session.add(model)
|
|
try:
|
|
await self._session.flush()
|
|
except IntegrityError as exc:
|
|
await self._session.rollback()
|
|
raise ValueError("User already exists") from exc
|
|
await self._session.refresh(model)
|
|
return _to_user(model)
|
|
|
|
async def get_user_by_id(self, user_id: str) -> User | None:
|
|
model = await self._session.get(UserModel, user_id)
|
|
return _to_user(model) if model else None
|
|
|
|
async def get_user_by_email(self, email: str) -> User | None:
|
|
result = await self._session.execute(select(UserModel).where(UserModel.email == email))
|
|
model = result.scalar_one_or_none()
|
|
return _to_user(model) if model else None
|
|
|
|
async def get_user_by_oauth(self, provider: str, oauth_id: str) -> User | None:
|
|
result = await self._session.execute(
|
|
select(UserModel).where(
|
|
UserModel.oauth_provider == provider,
|
|
UserModel.oauth_id == oauth_id,
|
|
)
|
|
)
|
|
model = result.scalar_one_or_none()
|
|
return _to_user(model) if model else None
|
|
|
|
async def update_user(self, data: User) -> User:
|
|
model = await self._session.get(UserModel, data.id)
|
|
if model is None:
|
|
raise LookupError(f"User {data.id} not found")
|
|
model.email = data.email
|
|
model.password_hash = data.password_hash
|
|
model.system_role = data.system_role
|
|
model.oauth_provider = data.oauth_provider
|
|
model.oauth_id = data.oauth_id
|
|
model.needs_setup = data.needs_setup
|
|
model.token_version = data.token_version
|
|
try:
|
|
await self._session.flush()
|
|
except IntegrityError as exc:
|
|
await self._session.rollback()
|
|
raise ValueError("User already exists") from exc
|
|
await self._session.refresh(model)
|
|
return _to_user(model)
|
|
|
|
async def count_users(self) -> int:
|
|
return await self._session.scalar(select(func.count()).select_from(UserModel)) or 0
|
|
|
|
async def count_admin_users(self) -> int:
|
|
return (
|
|
await self._session.scalar(
|
|
select(func.count()).select_from(UserModel).where(UserModel.system_role == "admin")
|
|
)
|
|
or 0
|
|
)
|