from fastapi import APIRouter, Depends, Response from pydantic import BaseModel, ConfigDict, Field, field_validator from ..auth import require_admin from ..services.identity_review import confirm_identities, review_identities, resolve_identity, repair_identity from ..services.duplicate_accounts import repair_duplicates router = APIRouter(prefix="/admin/identities", tags=["admin"], dependencies=[Depends(require_admin)]) class Confirmation(BaseModel): model_config = ConfigDict(extra="forbid") revision: str = Field(pattern=r"^[a-f0-9]{64}$") user_ids: list[int] = Field(min_length=1, max_length=3000) @field_validator("user_ids") @classmethod def unique_positive_ids(cls, value): if any(user_id <= 0 for user_id in value) or len(set(value)) != len(value): raise ValueError("Choose unique positive user IDs") return value @router.get("") async def review(response: Response): response.headers["Cache-Control"] = "no-store" report, _, _ = await review_identities() return report @router.post("/confirm") async def confirm(payload: Confirmation, response: Response, admin: dict = Depends(require_admin)): response.headers["Cache-Control"] = "no-store" return await confirm_identities(payload.revision, payload.user_ids, admin) class Resolution(BaseModel): model_config = ConfigDict(extra="forbid") user_id: int = Field(gt=0, strict=True) jellyfin_user_id: str = Field(pattern=r"^[a-f0-9]{32}$") class ResolutionConfirmation(Resolution): revision: str = Field(pattern=r"^[a-f0-9]{64}$") @router.post("/resolve/check") async def check_resolution(payload: Resolution, response: Response): response.headers["Cache-Control"] = "no-store" return await resolve_identity(payload.user_id, payload.jellyfin_user_id) @router.post("/resolve/confirm") async def confirm_resolution(payload: ResolutionConfirmation, response: Response, admin: dict = Depends(require_admin)): response.headers["Cache-Control"] = "no-store" return await resolve_identity(payload.user_id, payload.jellyfin_user_id, payload.revision, admin) class RepairResolution(Resolution): create_seerr: bool = Field(default=False, strict=True) class RepairConfirmation(RepairResolution): revision: str = Field(pattern=r'^[a-f0-9]{64}$') @router.post('/repair/check') async def check_repair(payload: RepairResolution, response: Response): response.headers['Cache-Control'] = 'no-store' return await repair_identity(payload.user_id, payload.jellyfin_user_id, create_seerr=payload.create_seerr) @router.post('/repair/confirm') async def confirm_repair(payload: RepairConfirmation, response: Response, admin: dict = Depends(require_admin)): response.headers['Cache-Control'] = 'no-store' return await repair_identity(payload.user_id, payload.jellyfin_user_id, payload.revision, admin, payload.create_seerr) class DuplicateCheck(BaseModel): model_config = ConfigDict(extra='forbid') user_id: int = Field(gt=0, strict=True) keep_id: int | None = Field(default=None, gt=0, strict=True) class DuplicateConfirmation(DuplicateCheck): keep_id: int = Field(gt=0, strict=True) revision: str = Field(pattern=r'^[a-f0-9]{64}$') @router.post('/duplicates/check') async def check_duplicates(payload: DuplicateCheck, response: Response): response.headers['Cache-Control'] = 'no-store' return await repair_duplicates(payload.user_id, payload.keep_id) @router.post('/duplicates/confirm') async def confirm_duplicates(payload: DuplicateConfirmation, response: Response, admin: dict = Depends(require_admin)): response.headers['Cache-Control'] = 'no-store' return await repair_duplicates(payload.user_id, payload.keep_id, payload.revision, admin)