This commit is contained in:
2026-03-09 14:05:00 +08:00
commit 754e720ba7
105 changed files with 5890 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
"""
Signin Log query router.
All endpoints require JWT authentication and enforce resource ownership.
"""
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import select, func
from sqlalchemy.ext.asyncio import AsyncSession
from shared.models import get_db, Account, SigninLog, User
from shared.response import success_response
from api_service.app.dependencies import get_current_user
from api_service.app.schemas.signin_log import SigninLogResponse, PaginatedResponse
router = APIRouter(prefix="/api/v1/accounts", tags=["signin-logs"])
async def _verify_account_ownership(
account_id: str,
user: User,
db: AsyncSession,
) -> Account:
"""Verify that the account belongs to the current user."""
result = await db.execute(select(Account).where(Account.id == account_id))
account = result.scalar_one_or_none()
if account is None:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Account not found")
if account.user_id != user.id:
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Access denied")
return account
@router.get("/{account_id}/signin-logs")
async def get_signin_logs(
account_id: str,
page: int = Query(1, ge=1, description="Page number (starts from 1)"),
size: int = Query(20, ge=1, le=100, description="Page size (max 100)"),
status_filter: Optional[str] = Query(None, alias="status", description="Filter by status"),
user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
"""
Query signin logs for a specific account with pagination and status filtering.
Returns logs sorted by signed_at in descending order (newest first).
"""
# Verify account ownership
await _verify_account_ownership(account_id, user, db)
# Build base query
query = select(SigninLog).where(SigninLog.account_id == account_id)
# Apply status filter if provided
if status_filter:
query = query.where(SigninLog.status == status_filter)
# Get total count
count_query = select(func.count()).select_from(query.subquery())
total_result = await db.execute(count_query)
total = total_result.scalar()
# Apply ordering and pagination
query = query.order_by(SigninLog.signed_at.desc())
offset = (page - 1) * size
query = query.offset(offset).limit(size)
# Execute query
result = await db.execute(query)
logs = result.scalars().all()
# Calculate total pages
total_pages = (total + size - 1) // size if total > 0 else 0
# Build response
paginated = PaginatedResponse(
items=[SigninLogResponse.model_validate(log) for log in logs],
total=total,
page=page,
size=size,
total_pages=total_pages,
)
return success_response(paginated.model_dump(mode="json"), "Signin logs retrieved")