Files
yu-zhi-ran/platform/backend/app/api/calendar.py
T
2026-06-09 17:18:09 +08:00

240 lines
8.1 KiB
Python

from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from typing import List, Optional
from datetime import datetime, date
from calendar import monthrange
from ..database import get_db
from ..models import ContentCalendar, Topic
from ..schemas import (
ContentCalendarCreate, ContentCalendarUpdate, ContentCalendarResponse,
ContentCalendarBase
)
from .auth import get_current_user, org_filter
router = APIRouter(prefix="/api/calendar", tags=["calendar"])
PLATFORM_ICONS = {
"zhihu": "",
"wechat": "",
"xiaohongshu": ""
}
def enrich_entry(entry, db):
d = entry.to_dict()
d["platform_icon"] = PLATFORM_ICONS.get(entry.platform, "📝")
d["topic_status"] = None
if entry.topic_id:
topic = db.query(Topic).filter(Topic.id == entry.topic_id).first()
if topic:
d["topic_status"] = topic.status
return d
@router.get("", response_model=List[ContentCalendarResponse])
def get_calendar(
year: Optional[int] = Query(None),
month: Optional[int] = Query(None),
db: Session = Depends(get_db),
current_user=Depends(get_current_user)
):
today = date.today()
year = year or today.year
month = month or today.month
start = date(year, month, 1)
last_day = monthrange(year, month)[1]
end = date(year, month, last_day)
base = db.query(ContentCalendar).filter(
ContentCalendar.planned_date >= start,
ContentCalendar.planned_date <= end
)
of = org_filter(current_user, Topic)
if of is not True:
base = base.outerjoin(Topic, ContentCalendar.topic_id == Topic.id).filter(
(ContentCalendar.topic_id.is_(None)) | (Topic.org_id == current_user.org_id)
)
entries = base.order_by(ContentCalendar.planned_date).all()
return [enrich_entry(e, db) for e in entries]
@router.get("/entries", response_model=List[ContentCalendarResponse])
def list_entries(
start_date: Optional[date] = None,
end_date: Optional[date] = None,
status: Optional[str] = None,
platform: Optional[str] = None,
limit: int = Query(50, le=200),
db: Session = Depends(get_db),
current_user=Depends(get_current_user)
):
query = db.query(ContentCalendar)
of = org_filter(current_user, Topic)
if of is not True:
query = query.outerjoin(Topic, ContentCalendar.topic_id == Topic.id).filter(
(ContentCalendar.topic_id.is_(None)) | (Topic.org_id == current_user.org_id)
)
if start_date:
query = query.filter(ContentCalendar.planned_date >= start_date)
if end_date:
query = query.filter(ContentCalendar.planned_date <= end_date)
if status:
query = query.filter(ContentCalendar.status == status)
if platform:
query = query.filter(ContentCalendar.platform == platform)
return query.order_by(ContentCalendar.planned_date.desc()).limit(limit).all()
@router.post("/entries", response_model=ContentCalendarResponse)
def create_entry(
data: ContentCalendarCreate,
db: Session = Depends(get_db),
current_user=Depends(get_current_user)
):
if data.topic_id:
topic = db.query(Topic).filter(Topic.id == data.topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="选题不存在")
if current_user.role != "admin" and topic.org_id != current_user.org_id:
raise HTTPException(status_code=404, detail="选题不存在")
entry = ContentCalendar(**data.model_dump())
db.add(entry)
db.commit()
db.refresh(entry)
if data.topic_id and data.title:
if not topic.title or topic.title != data.title:
topic.title = data.title
db.commit()
return entry
@router.put("/entries/{entry_id}", response_model=ContentCalendarResponse)
def update_entry(
entry_id: int,
data: ContentCalendarUpdate,
db: Session = Depends(get_db),
current_user=Depends(get_current_user)
):
entry = db.query(ContentCalendar).filter(ContentCalendar.id == entry_id).first()
if not entry:
raise HTTPException(status_code=404, detail="日历条目不存在")
if entry.topic_id:
topic = db.query(Topic).filter(Topic.id == entry.topic_id).first()
if topic and current_user.role != "admin" and topic.org_id != current_user.org_id:
raise HTTPException(status_code=404, detail="日历条目不存在")
for k, v in data.model_dump(exclude_unset=True).items():
setattr(entry, k, v)
db.commit()
db.refresh(entry)
if entry.topic_id:
topic = db.query(Topic).filter(Topic.id == entry.topic_id).first()
if topic and entry.status == "published" and not entry.published_date:
entry.published_date = date.today()
if topic.status == "pending" or topic.status == "ready":
topic.status = "published"
topic.published_at = date.today()
topic.updated_at = datetime.now()
db.commit()
return entry
@router.delete("/entries/{entry_id}")
def delete_entry(
entry_id: int,
db: Session = Depends(get_db),
current_user=Depends(get_current_user)
):
entry = db.query(ContentCalendar).filter(ContentCalendar.id == entry_id).first()
if not entry:
raise HTTPException(status_code=404, detail="日历条目不存在")
if entry.topic_id:
topic = db.query(Topic).filter(Topic.id == entry.topic_id).first()
if topic and current_user.role != "admin" and topic.org_id != current_user.org_id:
raise HTTPException(status_code=404, detail="日历条目不存在")
db.delete(entry)
db.commit()
return {"ok": True}
@router.post("/entries/bind-topic")
def bind_topic_to_entry(
entry_id: int,
topic_id: str,
db: Session = Depends(get_db),
current_user=Depends(get_current_user)
):
entry = db.query(ContentCalendar).filter(ContentCalendar.id == entry_id).first()
if not entry:
raise HTTPException(status_code=404, detail="日历条目不存在")
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="选题不存在")
if current_user.role != "admin" and topic.org_id != current_user.org_id:
raise HTTPException(status_code=404, detail="选题不存在")
entry.topic_id = topic_id
entry.title = topic.title
if topic.field_name:
entry.field_id = topic.field_id
db.commit()
db.refresh(entry)
return entry
@router.get("/stats")
def calendar_stats(
year: int = Query(...),
month: int = Query(...),
db: Session = Depends(get_db),
current_user=Depends(get_current_user)
):
start = date(year, month, 1)
last_day = monthrange(year, month)[1]
end = date(year, month, last_day)
q = db.query(ContentCalendar).filter(
ContentCalendar.planned_date >= start,
ContentCalendar.planned_date <= end
)
of = org_filter(current_user, Topic)
if of is not True:
q = q.outerjoin(Topic, ContentCalendar.topic_id == Topic.id).filter(
(ContentCalendar.topic_id.is_(None)) | (Topic.org_id == current_user.org_id)
)
entries = q.all()
stats = {"total": len(entries), "planned": 0, "published": 0, "delayed": 0, "cancelled": 0}
for e in entries:
if e.status in stats:
stats[e.status] += 1
return stats
@router.post("/entries/from-topic")
def create_from_topic(
topic_id: str,
planned_date: date,
platform: str = Query(...),
db: Session = Depends(get_db),
current_user=Depends(get_current_user)
):
topic = db.query(Topic).filter(Topic.id == topic_id).first()
if not topic:
raise HTTPException(status_code=404, detail="选题不存在")
if current_user.role != "admin" and topic.org_id != current_user.org_id:
raise HTTPException(status_code=404, detail="选题不存在")
entry = ContentCalendar(
topic_id=topic_id,
field_id=topic.field_id,
title=topic.title,
planned_date=planned_date,
platform=platform,
created_by=current_user.username
)
db.add(entry)
db.commit()
db.refresh(entry)
return entry