Files
admin/app/routers/ad_slots.py
T
2026-07-27 16:40:25 +08:00

141 lines
4.4 KiB
Python

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")