Initial commit: clean project structure
- 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
This commit is contained in:
120
backend/app/api/push_configs.py
Normal file
120
backend/app/api/push_configs.py
Normal file
@@ -0,0 +1,120 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user