- Backend: FastAPI + SQLAlchemy + Celery (Python 3.11+) - Frontend: Vue 3 + TypeScript + Pinia + Tailwind - Admin Frontend: separate Vue 3 app for management - Docker Compose: 9 services orchestration - Specs: design prototypes, memory system PRD, product roadmap Cleanup performed: - Removed temporary debug scripts from backend root - Removed deprecated admin_app.py (embedded UI) - Removed duplicate docs from admin-frontend - Updated .gitignore for Vite cache and egg-info
121 lines
3.6 KiB
Python
121 lines
3.6 KiB
Python
"""Push configuration APIs."""
|
|
|
|
from datetime import time
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
|
from pydantic import BaseModel
|
|
from sqlalchemy import select
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.deps import require_user
|
|
from app.db.database import get_db
|
|
from app.db.models import ChildProfile, PushConfig, User
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class PushConfigUpsert(BaseModel):
|
|
"""Upsert push config payload."""
|
|
|
|
child_profile_id: str
|
|
push_time: time | None = None
|
|
push_days: list[int] | None = None
|
|
enabled: bool | None = None
|
|
|
|
|
|
class PushConfigResponse(BaseModel):
|
|
"""Push config response."""
|
|
|
|
id: str
|
|
child_profile_id: str
|
|
push_time: time | None
|
|
push_days: list[int]
|
|
enabled: bool
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
class PushConfigListResponse(BaseModel):
|
|
"""Push config list response."""
|
|
|
|
configs: list[PushConfigResponse]
|
|
total: int
|
|
|
|
|
|
def _validate_push_days(push_days: list[int]) -> list[int]:
|
|
invalid = [day for day in push_days if day < 0 or day > 6]
|
|
if invalid:
|
|
raise HTTPException(status_code=400, detail="推送日期必须在 0-6 之间")
|
|
return list(dict.fromkeys(push_days))
|
|
|
|
|
|
@router.get("/push-configs", response_model=PushConfigListResponse)
|
|
async def list_push_configs(
|
|
user: User = Depends(require_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""List push configs for current user."""
|
|
result = await db.execute(
|
|
select(PushConfig).where(PushConfig.user_id == user.id)
|
|
)
|
|
configs = result.scalars().all()
|
|
return PushConfigListResponse(configs=configs, total=len(configs))
|
|
|
|
|
|
@router.put("/push-configs", response_model=PushConfigResponse)
|
|
async def upsert_push_config(
|
|
payload: PushConfigUpsert,
|
|
response: Response,
|
|
user: User = Depends(require_user),
|
|
db: AsyncSession = Depends(get_db),
|
|
):
|
|
"""Create or update push config for a child profile."""
|
|
result = await db.execute(
|
|
select(ChildProfile).where(
|
|
ChildProfile.id == payload.child_profile_id,
|
|
ChildProfile.user_id == user.id,
|
|
)
|
|
)
|
|
profile = result.scalar_one_or_none()
|
|
if not profile:
|
|
raise HTTPException(status_code=404, detail="孩子档案不存在")
|
|
|
|
result = await db.execute(
|
|
select(PushConfig).where(PushConfig.child_profile_id == payload.child_profile_id)
|
|
)
|
|
config = result.scalar_one_or_none()
|
|
|
|
if config is None:
|
|
if payload.push_time is None or payload.push_days is None:
|
|
raise HTTPException(status_code=400, detail="创建配置需要提供推送时间和日期")
|
|
push_days = _validate_push_days(payload.push_days)
|
|
config = PushConfig(
|
|
user_id=user.id,
|
|
child_profile_id=payload.child_profile_id,
|
|
push_time=payload.push_time,
|
|
push_days=push_days,
|
|
enabled=True if payload.enabled is None else payload.enabled,
|
|
)
|
|
db.add(config)
|
|
await db.commit()
|
|
await db.refresh(config)
|
|
response.status_code = status.HTTP_201_CREATED
|
|
return config
|
|
|
|
updates = payload.model_dump(exclude_unset=True)
|
|
if "push_days" in updates and updates["push_days"] is not None:
|
|
updates["push_days"] = _validate_push_days(updates["push_days"])
|
|
if "push_time" in updates and updates["push_time"] is None:
|
|
raise HTTPException(status_code=400, detail="推送时间不能为空")
|
|
|
|
for key, value in updates.items():
|
|
if key == "child_profile_id":
|
|
continue
|
|
if value is not None:
|
|
setattr(config, key, value)
|
|
|
|
await db.commit()
|
|
await db.refresh(config)
|
|
return config
|