chore: stage changes before history rewrite
This commit is contained in:
@@ -7,26 +7,63 @@ from pydantic import BaseModel
|
||||
from sqlalchemy.orm import Session as DbSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import ApiKey, Profile, Postcard, User
|
||||
from app.models import (
|
||||
ApiKey, Profile, Postcard, User,
|
||||
PERM_PROFILES_READ, PERM_PROFILES_WRITE,
|
||||
PERM_POSTCARDS_READ, PERM_POSTCARDS_WRITE,
|
||||
PERM_IMAGES_UPLOAD,
|
||||
)
|
||||
from app.config import UPLOAD_DIR
|
||||
|
||||
router = APIRouter(prefix="/api/v1", tags=["api"])
|
||||
|
||||
|
||||
# ─── Auth Dependency ──────────────────────────────────────────────
|
||||
# ─── Auth Dependencies ────────────────────────────────────────────
|
||||
|
||||
def get_api_user(
|
||||
def _parse_permissions(raw: str) -> list[str]:
|
||||
"""Parse JSON permissions string from DB, return list of scope strings."""
|
||||
import json
|
||||
try:
|
||||
val = json.loads(raw)
|
||||
return val if isinstance(val, list) else []
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
return []
|
||||
|
||||
|
||||
def get_api_key(
|
||||
x_api_key: str = Header(..., alias="X-API-Key"),
|
||||
db: DbSession = Depends(get_db),
|
||||
) -> User:
|
||||
"""Validate X-API-Key header and return the associated user."""
|
||||
api_key = db.query(ApiKey).filter(ApiKey.key == x_api_key).first()
|
||||
if not api_key or not api_key.is_active:
|
||||
) -> ApiKey:
|
||||
"""Validate X-API-Key header and return the ApiKey object (with permissions)."""
|
||||
ak = db.query(ApiKey).filter(ApiKey.key == x_api_key).first()
|
||||
if not ak or not ak.is_active:
|
||||
raise HTTPException(status_code=401, detail="Invalid or inactive API key")
|
||||
# Update last_used_at
|
||||
api_key.last_used_at = datetime.now(timezone.utc)
|
||||
ak.last_used_at = datetime.now(timezone.utc)
|
||||
db.commit()
|
||||
return api_key.user
|
||||
return ak
|
||||
|
||||
|
||||
def get_api_user(ak: ApiKey = Depends(get_api_key)) -> User:
|
||||
"""Return the User associated with a valid API key (no permission check)."""
|
||||
return ak.user
|
||||
|
||||
|
||||
def require_scopes(*scopes: str):
|
||||
"""Return a dependency that checks the API key has ALL given scopes.
|
||||
|
||||
Keys with empty permissions list (legacy) are treated as full-access.
|
||||
"""
|
||||
def _dep(ak: ApiKey = Depends(get_api_key)) -> ApiKey:
|
||||
perms = _parse_permissions(ak.permissions)
|
||||
if perms:
|
||||
missing = [s for s in scopes if s not in perms]
|
||||
if missing:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=f"Missing required permissions: {', '.join(missing)}",
|
||||
)
|
||||
return ak
|
||||
return _dep
|
||||
|
||||
|
||||
def get_api_user_optional(
|
||||
@@ -36,7 +73,8 @@ def get_api_user_optional(
|
||||
"""Like get_api_user but returns None if no key provided (for public endpoints)."""
|
||||
if not x_api_key:
|
||||
return None
|
||||
return get_api_user(x_api_key, db)
|
||||
ak = get_api_key(x_api_key, db)
|
||||
return ak.user
|
||||
|
||||
|
||||
# ─── Pydantic Schemas ────────────────────────────────────────────
|
||||
@@ -102,12 +140,14 @@ class PostcardOut(BaseModel):
|
||||
|
||||
class ApiKeyCreate(BaseModel):
|
||||
name: str = ""
|
||||
permissions: list[str] = []
|
||||
|
||||
class ApiKeyOut(BaseModel):
|
||||
id: int
|
||||
key: str
|
||||
name: str
|
||||
is_active: bool
|
||||
permissions: list[str]
|
||||
created_at: datetime
|
||||
last_used_at: datetime | None
|
||||
|
||||
@@ -150,14 +190,22 @@ def _postcard_or_404(user: User, profile_id: int, postcard_id: int, db: DbSessio
|
||||
# Profiles
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
|
||||
@router.get("/profiles", response_model=list[ProfileOut])
|
||||
def list_profiles(user: User = Depends(get_api_user), db: DbSession = Depends(get_db)):
|
||||
return db.query(Profile).filter(Profile.user_id == user.id).order_by(Profile.id).all()
|
||||
def list_profiles(
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_PROFILES_READ)),
|
||||
):
|
||||
return db.query(Profile).filter(Profile.user_id == ak.user_id).order_by(Profile.id).all()
|
||||
|
||||
|
||||
@router.post("/profiles", response_model=ProfileOut, status_code=201)
|
||||
def create_profile(body: ProfileCreate, user: User = Depends(get_api_user), db: DbSession = Depends(get_db)):
|
||||
p = Profile(nickname=body.nickname.strip(), user_id=user.id)
|
||||
def create_profile(
|
||||
body: ProfileCreate,
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_PROFILES_WRITE)),
|
||||
):
|
||||
p = Profile(nickname=body.nickname.strip(), user_id=ak.user_id)
|
||||
db.add(p)
|
||||
db.commit()
|
||||
db.refresh(p)
|
||||
@@ -165,13 +213,22 @@ def create_profile(body: ProfileCreate, user: User = Depends(get_api_user), db:
|
||||
|
||||
|
||||
@router.get("/profiles/{profile_id}", response_model=ProfileOut)
|
||||
def get_profile(profile_id: int, user: User = Depends(get_api_user), db: DbSession = Depends(get_db)):
|
||||
return _profile_or_404(user, profile_id, db)
|
||||
def get_profile(
|
||||
profile_id: int,
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_PROFILES_READ)),
|
||||
):
|
||||
return _profile_or_404(ak.user, profile_id, db)
|
||||
|
||||
|
||||
@router.put("/profiles/{profile_id}", response_model=ProfileOut)
|
||||
def update_profile(profile_id: int, body: ProfileUpdate, user: User = Depends(get_api_user), db: DbSession = Depends(get_db)):
|
||||
p = _profile_or_404(user, profile_id, db)
|
||||
def update_profile(
|
||||
profile_id: int,
|
||||
body: ProfileUpdate,
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_PROFILES_WRITE)),
|
||||
):
|
||||
p = _profile_or_404(ak.user, profile_id, db)
|
||||
if body.nickname is not None:
|
||||
p.nickname = body.nickname.strip()
|
||||
db.commit()
|
||||
@@ -180,8 +237,12 @@ def update_profile(profile_id: int, body: ProfileUpdate, user: User = Depends(ge
|
||||
|
||||
|
||||
@router.delete("/profiles/{profile_id}")
|
||||
def delete_profile(profile_id: int, user: User = Depends(get_api_user), db: DbSession = Depends(get_db)):
|
||||
p = _profile_or_404(user, profile_id, db)
|
||||
def delete_profile(
|
||||
profile_id: int,
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_PROFILES_WRITE)),
|
||||
):
|
||||
p = _profile_or_404(ak.user, profile_id, db)
|
||||
db.delete(p)
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
@@ -195,10 +256,10 @@ def delete_profile(profile_id: int, user: User = Depends(get_api_user), db: DbSe
|
||||
def list_postcards(
|
||||
profile_id: int,
|
||||
status: str | None = Query(None),
|
||||
user: User = Depends(get_api_user),
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_POSTCARDS_READ)),
|
||||
):
|
||||
_profile_or_404(user, profile_id, db)
|
||||
_profile_or_404(ak.user, profile_id, db)
|
||||
q = db.query(Postcard).filter(Postcard.profile_id == profile_id)
|
||||
if status:
|
||||
q = q.filter(Postcard.status == status)
|
||||
@@ -209,10 +270,10 @@ def list_postcards(
|
||||
def create_postcard(
|
||||
profile_id: int,
|
||||
body: PostcardCreate,
|
||||
user: User = Depends(get_api_user),
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_POSTCARDS_WRITE)),
|
||||
):
|
||||
_profile_or_404(user, profile_id, db)
|
||||
_profile_or_404(ak.user, profile_id, db)
|
||||
cn = body.card_number.strip()
|
||||
if db.query(Postcard).filter(Postcard.card_number == cn).first():
|
||||
raise HTTPException(status_code=409, detail="Card number already exists")
|
||||
@@ -238,10 +299,10 @@ def create_postcard(
|
||||
def get_postcard(
|
||||
profile_id: int,
|
||||
postcard_id: int,
|
||||
user: User = Depends(get_api_user),
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_POSTCARDS_READ)),
|
||||
):
|
||||
return _postcard_or_404(user, profile_id, postcard_id, db)
|
||||
return _postcard_or_404(ak.user, profile_id, postcard_id, db)
|
||||
|
||||
|
||||
@router.put("/profiles/{profile_id}/postcards/{postcard_id}", response_model=PostcardOut)
|
||||
@@ -249,10 +310,10 @@ def update_postcard(
|
||||
profile_id: int,
|
||||
postcard_id: int,
|
||||
body: PostcardUpdate,
|
||||
user: User = Depends(get_api_user),
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_POSTCARDS_WRITE)),
|
||||
):
|
||||
pc = _postcard_or_404(user, profile_id, postcard_id, db)
|
||||
pc = _postcard_or_404(ak.user, profile_id, postcard_id, db)
|
||||
for field in ("status", "recipient_name", "sender_name", "notes"):
|
||||
val = getattr(body, field)
|
||||
if val is not None:
|
||||
@@ -278,10 +339,10 @@ def update_postcard(
|
||||
def delete_postcard(
|
||||
profile_id: int,
|
||||
postcard_id: int,
|
||||
user: User = Depends(get_api_user),
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_POSTCARDS_WRITE)),
|
||||
):
|
||||
pc = _postcard_or_404(user, profile_id, postcard_id, db)
|
||||
pc = _postcard_or_404(ak.user, profile_id, postcard_id, db)
|
||||
db.delete(pc)
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
@@ -297,10 +358,10 @@ async def upload_image(
|
||||
postcard_id: int,
|
||||
side: str = Query("front", pattern="^(front|back)$"),
|
||||
file: UploadFile = File(...),
|
||||
user: User = Depends(get_api_user),
|
||||
db: DbSession = Depends(get_db),
|
||||
ak: ApiKey = Depends(require_scopes(PERM_IMAGES_UPLOAD)),
|
||||
):
|
||||
pc = _postcard_or_404(user, profile_id, postcard_id, db)
|
||||
pc = _postcard_or_404(ak.user, profile_id, postcard_id, db)
|
||||
ext = Path(file.filename).suffix if file.filename else ".jpg"
|
||||
save_name = f"pc_{pc.id}_{side}{ext}"
|
||||
save_path = UPLOAD_DIR / save_name
|
||||
@@ -320,26 +381,65 @@ async def upload_image(
|
||||
# ═══════════════════════════════════════════════════════════════════
|
||||
|
||||
@router.get("/keys", response_model=list[ApiKeyOut])
|
||||
def list_api_keys(user: User = Depends(get_api_user), db: DbSession = Depends(get_db)):
|
||||
def list_api_keys(
|
||||
db: DbSession = Depends(get_db),
|
||||
user: User = Depends(get_api_user),
|
||||
):
|
||||
return db.query(ApiKey).filter(ApiKey.user_id == user.id).order_by(ApiKey.created_at.desc()).all()
|
||||
|
||||
|
||||
@router.post("/keys", response_model=ApiKeyOut, status_code=201)
|
||||
def create_api_key(body: ApiKeyCreate, user: User = Depends(get_api_user), db: DbSession = Depends(get_db)):
|
||||
def create_api_key(
|
||||
body: ApiKeyCreate,
|
||||
db: DbSession = Depends(get_db),
|
||||
user: User = Depends(get_api_user),
|
||||
):
|
||||
key = f"mv_{token_hex(24)}"
|
||||
ak = ApiKey(key=key, name=body.name.strip(), user_id=user.id)
|
||||
db.add(ak)
|
||||
import json
|
||||
perms = json.dumps(body.permissions) if body.permissions else "[]"
|
||||
new_ak = ApiKey(key=key, name=body.name.strip(), user_id=user.id, permissions=perms)
|
||||
db.add(new_ak)
|
||||
db.commit()
|
||||
db.refresh(new_ak)
|
||||
return new_ak
|
||||
|
||||
|
||||
class ApiKeyUpdate(BaseModel):
|
||||
name: str | None = None
|
||||
permissions: list[str] | None = None
|
||||
|
||||
|
||||
@router.put("/keys/{key_id}", response_model=ApiKeyOut)
|
||||
def update_api_key(
|
||||
key_id: int,
|
||||
body: ApiKeyUpdate,
|
||||
db: DbSession = Depends(get_db),
|
||||
user: User = Depends(get_api_user),
|
||||
):
|
||||
import json
|
||||
ak = db.query(ApiKey).filter(ApiKey.id == key_id, ApiKey.user_id == user.id).first()
|
||||
if not ak:
|
||||
raise HTTPException(status_code=404, detail="API key not found")
|
||||
if body.name is not None:
|
||||
ak.name = body.name.strip()
|
||||
if body.permissions is not None:
|
||||
valid = [p for p in body.permissions if p in ALL_PERMISSIONS]
|
||||
ak.permissions = json.dumps(valid) if valid else "[]"
|
||||
db.commit()
|
||||
db.refresh(ak)
|
||||
return ak
|
||||
|
||||
|
||||
@router.delete("/keys/{key_id}")
|
||||
def delete_api_key(key_id: int, user: User = Depends(get_api_user), db: DbSession = Depends(get_db)):
|
||||
ak = db.query(ApiKey).filter(ApiKey.id == key_id, ApiKey.user_id == user.id).first()
|
||||
if not ak:
|
||||
def delete_api_key(
|
||||
key_id: int,
|
||||
db: DbSession = Depends(get_db),
|
||||
user: User = Depends(get_api_user),
|
||||
):
|
||||
target = db.query(ApiKey).filter(ApiKey.id == key_id, ApiKey.user_id == user.id).first()
|
||||
if not target:
|
||||
raise HTTPException(status_code=404, detail="API key not found")
|
||||
db.delete(ak)
|
||||
db.delete(target)
|
||||
db.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@@ -67,6 +67,19 @@ def _strftime(dt: datetime, fmt: str) -> str:
|
||||
templates.env.filters["strftime"] = _strftime
|
||||
|
||||
|
||||
import json as _json
|
||||
|
||||
def _json_loads(raw: str) -> list:
|
||||
"""Parse JSON string for Jinja2 templates."""
|
||||
try:
|
||||
return _json.loads(raw) if raw else []
|
||||
except (ValueError, TypeError):
|
||||
return []
|
||||
|
||||
|
||||
templates.env.filters["json_loads"] = _json_loads
|
||||
|
||||
|
||||
def _translate_filter(key: str, lang: str = "zh") -> str:
|
||||
return _t(key, lang)
|
||||
|
||||
@@ -77,6 +90,21 @@ from app.i18n import get_languages as _get_languages
|
||||
templates.env.globals["available_languages"] = _get_languages()
|
||||
|
||||
|
||||
def _get_copyright() -> str:
|
||||
from app.models import SystemConfig as _SC
|
||||
from app.database import SessionLocal
|
||||
try:
|
||||
db = SessionLocal()
|
||||
cfg = db.query(_SC).filter(_SC.key == "copyright_text").first()
|
||||
db.close()
|
||||
return cfg.value if cfg else ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
templates.env.globals["copyright_text"] = _get_copyright()
|
||||
|
||||
|
||||
def _redirect(url: str) -> RedirectResponse:
|
||||
return RedirectResponse(url, status_code=303)
|
||||
|
||||
@@ -188,6 +216,32 @@ def _require_admin(user: User) -> bool:
|
||||
return user.is_admin
|
||||
|
||||
|
||||
def _admin_context(user, db, active_tab="invites", **extra):
|
||||
"""Build context for admin.html with invite codes and system config."""
|
||||
from app.models import SystemConfig
|
||||
codes = db.query(InviteCode).order_by(InviteCode.created_at.desc()).all()
|
||||
config = {r.key: r.value for r in db.query(SystemConfig).all()}
|
||||
ctx = {"request": extra.get("request"), "user": user, "codes": codes, "active_tab": active_tab, "config": config}
|
||||
ctx.update({k: v for k, v in extra.items() if k != "request"})
|
||||
return ctx
|
||||
|
||||
|
||||
def _admin_render(request, user, db, active_tab="invites", **extra):
|
||||
return templates.TemplateResponse("admin.html", _admin_context(user, db, active_tab, request=request, **extra))
|
||||
|
||||
|
||||
@router.get("/admin", response_class=HTMLResponse)
|
||||
def admin_page(
|
||||
request: Request,
|
||||
active_tab: str = Query("invites", alias="tab"),
|
||||
user: User = Depends(get_current_user_web),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
if not _require_admin(user):
|
||||
return _redirect("/dashboard")
|
||||
return _admin_render(request, user, db, active_tab)
|
||||
|
||||
|
||||
@router.get("/admin/invites", response_class=HTMLResponse)
|
||||
def admin_invites_page(
|
||||
request: Request,
|
||||
@@ -196,10 +250,27 @@ def admin_invites_page(
|
||||
):
|
||||
if not _require_admin(user):
|
||||
return _redirect("/dashboard")
|
||||
codes = db.query(InviteCode).order_by(InviteCode.created_at.desc()).all()
|
||||
return templates.TemplateResponse("admin_invites.html", {
|
||||
"request": request, "user": user, "codes": codes,
|
||||
})
|
||||
return _admin_render(request, user, db, "invites")
|
||||
|
||||
|
||||
@router.post("/admin/config")
|
||||
def admin_save_config(
|
||||
request: Request,
|
||||
copyright_text: str = Form(""),
|
||||
user: User = Depends(get_current_user_web),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
if not _require_admin(user):
|
||||
return _redirect("/dashboard")
|
||||
from app.models import SystemConfig
|
||||
cfg = db.query(SystemConfig).filter(SystemConfig.key == "copyright_text").first()
|
||||
if cfg:
|
||||
cfg.value = copyright_text.strip()
|
||||
else:
|
||||
cfg = SystemConfig(key="copyright_text", value=copyright_text.strip())
|
||||
db.add(cfg)
|
||||
db.commit()
|
||||
return _admin_render(request, user, db, "config", success="Saved")
|
||||
|
||||
|
||||
@router.post("/admin/invites/add")
|
||||
@@ -220,17 +291,9 @@ def admin_add_invite(
|
||||
else:
|
||||
code = code.strip()
|
||||
if len(code) != 8:
|
||||
codes = db.query(InviteCode).order_by(InviteCode.created_at.desc()).all()
|
||||
return templates.TemplateResponse("admin_invites.html", {
|
||||
"request": request, "user": user, "codes": codes,
|
||||
"error": "邀请码必须为8个字符",
|
||||
})
|
||||
return _admin_render(request, user, db, "invites", error="邀请码必须为8个字符")
|
||||
if db.query(InviteCode).filter(InviteCode.code == code).first():
|
||||
codes = db.query(InviteCode).order_by(InviteCode.created_at.desc()).all()
|
||||
return templates.TemplateResponse("admin_invites.html", {
|
||||
"request": request, "user": user, "codes": codes,
|
||||
"error": "邀请码已存在",
|
||||
})
|
||||
return _admin_render(request, user, db, "invites", error="邀请码已存在")
|
||||
# Parse limits
|
||||
m_uses = None
|
||||
exp_at = None
|
||||
@@ -244,11 +307,7 @@ def admin_add_invite(
|
||||
)
|
||||
db.add(new_code)
|
||||
db.commit()
|
||||
codes = db.query(InviteCode).order_by(InviteCode.created_at.desc()).all()
|
||||
return templates.TemplateResponse("admin_invites.html", {
|
||||
"request": request, "user": user, "codes": codes,
|
||||
"success": f"邀请码 {code} 已创建",
|
||||
})
|
||||
return _admin_render(request, user, db, "invites", success=f"邀请码 {code} 已创建")
|
||||
|
||||
|
||||
@router.post("/admin/invites/{code_id}/edit")
|
||||
@@ -276,11 +335,7 @@ def admin_edit_invite(
|
||||
code_obj.max_uses = None
|
||||
code_obj.expires_at = None
|
||||
db.commit()
|
||||
codes = db.query(InviteCode).order_by(InviteCode.created_at.desc()).all()
|
||||
return templates.TemplateResponse("admin_invites.html", {
|
||||
"request": request, "user": user, "codes": codes,
|
||||
"success": f"邀请码 {code_obj.code} 已更新",
|
||||
})
|
||||
return _admin_render(request, user, db, "invites", success=f"邀请码 {code_obj.code} 已更新")
|
||||
|
||||
|
||||
@router.post("/admin/invites/{code_id}/delete")
|
||||
@@ -296,7 +351,7 @@ def admin_delete_invite(
|
||||
if code_obj:
|
||||
db.delete(code_obj)
|
||||
db.commit()
|
||||
return _redirect("/admin/invites")
|
||||
return _redirect("/admin?tab=invites")
|
||||
|
||||
|
||||
# ---------- Settings ----------
|
||||
@@ -862,17 +917,21 @@ def public_showcase(request: Request, username: str, db: Session = Depends(get_d
|
||||
def apikey_create(
|
||||
request: Request,
|
||||
name: str = Form(""),
|
||||
permissions: list[str] = Form([]),
|
||||
user: User = Depends(get_current_user_web),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
from app.models import ApiKey
|
||||
from app.models import ApiKey, ALL_PERMISSIONS
|
||||
from secrets import token_hex
|
||||
import json
|
||||
key = f"mv_{token_hex(24)}"
|
||||
ak = ApiKey(key=key, name=name.strip(), user_id=user.id)
|
||||
# Filter to valid scopes only
|
||||
valid = [p for p in permissions if p in ALL_PERMISSIONS]
|
||||
perms_json = json.dumps(valid) if valid else "[]"
|
||||
ak = ApiKey(key=key, name=name.strip(), user_id=user.id, permissions=perms_json)
|
||||
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",
|
||||
@@ -894,6 +953,27 @@ def apikey_delete(
|
||||
return _redirect("/settings?tab=apikeys")
|
||||
|
||||
|
||||
@router.post("/settings/apikeys/{key_id}/edit")
|
||||
def apikey_edit(
|
||||
key_id: int,
|
||||
request: Request,
|
||||
name: str = Form(""),
|
||||
permissions: list[str] = Form([]),
|
||||
user: User = Depends(get_current_user_web),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
from app.models import ApiKey, ALL_PERMISSIONS
|
||||
import json
|
||||
ak = db.query(ApiKey).filter(ApiKey.id == key_id, ApiKey.user_id == user.id).first()
|
||||
if not ak:
|
||||
return _redirect("/settings?tab=apikeys")
|
||||
ak.name = name.strip()
|
||||
valid = [p for p in permissions if p in ALL_PERMISSIONS]
|
||||
ak.permissions = json.dumps(valid) if valid else "[]"
|
||||
db.commit()
|
||||
return _redirect("/settings?tab=apikeys")
|
||||
|
||||
|
||||
# ---------- API Docs ----------
|
||||
|
||||
@router.get("/api/docs", response_class=HTMLResponse)
|
||||
|
||||
Reference in New Issue
Block a user