import sqlite3 from typing import List from fastapi import APIRouter, HTTPException, status from app.database import Database from app.schemas import AdSlot, AdSlotCreate, AdSlotUpdate from app.temple_scope import TempleId, ensure_temple_exists router = APIRouter(prefix="/ad-slots", tags=["ad-slots"]) AD_SLOT_COLUMNS = "id, temple_id, name, code, description, sort_order, is_active" def row_to_ad_slot(row: sqlite3.Row) -> AdSlot: return AdSlot( id=row["id"], temple_id=row["temple_id"], name=row["name"], code=row["code"], description=row["description"], sort_order=row["sort_order"], is_active=bool(row["is_active"]), ) def raise_duplicate_code_error() -> None: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="广告位编码已存在") @router.get("", response_model=List[AdSlot]) def list_ad_slots(db: Database, temple_id: TempleId) -> List[AdSlot]: ensure_temple_exists(db, temple_id) rows = db.execute( f""" SELECT {AD_SLOT_COLUMNS} FROM ad_slots WHERE temple_id = ? ORDER BY sort_order, id """, (temple_id,), ).fetchall() return [row_to_ad_slot(row) for row in rows] @router.post("", response_model=AdSlot, status_code=status.HTTP_201_CREATED) def create_ad_slot(payload: AdSlotCreate, db: Database, temple_id: TempleId) -> AdSlot: ensure_temple_exists(db, temple_id) try: cursor = db.execute( """ INSERT INTO ad_slots (temple_id, name, code, description, title, image_url, sort_order, is_active) VALUES (?, ?, ?, ?, ?, ?, ?, ?) """, ( temple_id, payload.name, payload.code, payload.description, payload.name, "", payload.sort_order, int(payload.is_active), ), ) db.commit() except sqlite3.IntegrityError: raise_duplicate_code_error() return get_ad_slot(cursor.lastrowid, db, temple_id) @router.get("/{ad_slot_id}", response_model=AdSlot) def get_ad_slot(ad_slot_id: int, db: Database, temple_id: TempleId) -> AdSlot: ensure_temple_exists(db, temple_id) row = db.execute( f""" SELECT {AD_SLOT_COLUMNS} FROM ad_slots WHERE id = ? AND temple_id = ? """, (ad_slot_id, temple_id), ).fetchone() if row is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ad slot not found") return row_to_ad_slot(row) @router.patch("/{ad_slot_id}", response_model=AdSlot) def update_ad_slot(ad_slot_id: int, payload: AdSlotUpdate, db: Database, temple_id: TempleId) -> AdSlot: ad_slot = get_ad_slot(ad_slot_id, db, temple_id) updates = payload.model_dump(exclude_unset=True) if not updates: return ad_slot name = updates.get("name", ad_slot.name) try: db.execute( """ UPDATE ad_slots SET name = ?, code = ?, description = ?, title = ?, sort_order = ?, is_active = ? WHERE id = ? AND temple_id = ? """, ( name, updates.get("code", ad_slot.code), updates.get("description", ad_slot.description), name, updates.get("sort_order", ad_slot.sort_order), int(updates.get("is_active", ad_slot.is_active)), ad_slot_id, temple_id, ), ) db.commit() except sqlite3.IntegrityError: raise_duplicate_code_error() return get_ad_slot(ad_slot_id, db, temple_id) @router.delete("/{ad_slot_id}", status_code=status.HTTP_204_NO_CONTENT) def delete_ad_slot(ad_slot_id: int, db: Database, temple_id: TempleId) -> None: ensure_temple_exists(db, temple_id) used = db.execute( "SELECT 1 FROM ads WHERE slot_id = ? AND temple_id = ? LIMIT 1", (ad_slot_id, temple_id), ).fetchone() if used is not None: raise HTTPException(status_code=status.HTTP_409_CONFLICT, detail="广告位已绑定广告,不能删除") cursor = db.execute( "DELETE FROM ad_slots WHERE id = ? AND temple_id = ?", (ad_slot_id, temple_id), ) db.commit() if cursor.rowcount == 0: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Ad slot not found")