feat(api): add public RESTful API with API key auth and documentation page

This commit is contained in:
2026-07-07 09:22:11 +08:00
parent 14c226d082
commit 6a49fedfbb
9 changed files with 910 additions and 5 deletions

View File

@@ -305,13 +305,16 @@ def settings_page(
user: User = Depends(get_current_user_web),
db: Session = Depends(get_db),
):
from app.models import ApiKey
profiles = db.query(Profile).filter(Profile.user_id == user.id).all()
profile_cards = {}
for p in profiles:
profile_cards[p.id] = db.query(Postcard).filter(Postcard.profile_id == p.id).all()
api_keys = db.query(ApiKey).filter(ApiKey.user_id == user.id).order_by(ApiKey.created_at.desc()).all()
return templates.TemplateResponse("settings.html", {
"request": request, "user": user, "profiles": profiles,
"profile_cards": profile_cards, "active_tab": active_tab,
"api_keys": api_keys,
})
@@ -558,6 +561,13 @@ def postcard_create(
if not profile:
return _redirect("/profiles")
cn = card_number.strip()
if db.query(Postcard).filter(Postcard.card_number == cn).first():
return templates.TemplateResponse("postcard_form.html", {
"request": request, "user": user, "profile": profile, "postcard": None,
"error": _t("pc_list.duplicate", user.language),
})
def _parse_dt(s: str) -> datetime | None:
s = s.strip()
if not s:
@@ -638,6 +648,14 @@ def postcard_update(
if not pc:
return _redirect("/profiles")
cn = card_number.strip()
dup = db.query(Postcard).filter(Postcard.card_number == cn, Postcard.id != postcard_id).first()
if dup:
return templates.TemplateResponse("postcard_form.html", {
"request": request, "user": user, "profile": pc.profile, "postcard": pc,
"error": _t("pc_list.duplicate", user.language),
})
def _parse_dt(s: str) -> datetime | None:
s = s.strip()
if not s:
@@ -821,3 +839,54 @@ def public_showcase(request: Request, username: str, db: Session = Depends(get_d
"profile_groups": profile_groups, "sent_all": sent_all, "received_all": received_all,
"lang": user.language,
})
# ---------- API Keys ----------
@router.post("/settings/apikeys/create")
def apikey_create(
request: Request,
name: str = Form(""),
user: User = Depends(get_current_user_web),
db: Session = Depends(get_db),
):
from app.models import ApiKey
from secrets import token_hex
key = f"mv_{token_hex(24)}"
ak = ApiKey(key=key, name=name.strip(), user_id=user.id)
db.add(ak)
db.commit()
db.refresh(ak)
# Re-render settings with apikeys tab active and the new key shown
api_keys = db.query(ApiKey).filter(ApiKey.user_id == user.id).order_by(ApiKey.created_at.desc()).all()
return templates.TemplateResponse("settings.html", {
"request": request, "user": user, "active_tab": "apikeys",
"api_keys": api_keys, "new_key": ak.key, "success": "API Key created",
})
@router.post("/settings/apikeys/{key_id}/delete")
def apikey_delete(
key_id: int,
user: User = Depends(get_current_user_web),
db: Session = Depends(get_db),
):
from app.models import ApiKey
ak = db.query(ApiKey).filter(ApiKey.id == key_id, ApiKey.user_id == user.id).first()
if ak:
db.delete(ak)
db.commit()
return _redirect("/settings?tab=apikeys")
# ---------- API Docs ----------
@router.get("/api/docs", response_class=HTMLResponse)
def api_docs_page(
request: Request,
user: User = Depends(get_current_user_web),
db: Session = Depends(get_db),
):
return templates.TemplateResponse("api_docs.html", {
"request": request, "user": user,
})