feat(fc3b): extension_api_key endpoints + create_app bootstraps credential key

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-20 18:36:28 -04:00
parent 617aa5a90a
commit e77d976ff0
3 changed files with 103 additions and 1 deletions
+41 -1
View File
@@ -1,10 +1,12 @@
"""Settings API: import filters, system stats."""
import secrets
from quart import Blueprint, jsonify, request
from sqlalchemy import func, select
from ..extensions import get_session
from ..models import ImageRecord, ImportBatch, ImportSettings, ImportTask, Tag
from ..models import AppSetting, ImageRecord, ImportBatch, ImportSettings, ImportTask, Tag
settings_bp = Blueprint("settings", __name__, url_prefix="/api")
@@ -137,3 +139,41 @@ async def system_stats():
},
"active_batch": active_batch,
})
# --- FC-3b: extension API key (lives in app_setting) -----------------------
async def _get_or_seed_extension_api_key(session) -> str:
row = (await session.execute(
select(AppSetting).where(AppSetting.key == "extension_api_key")
)).scalar_one_or_none()
if row is None:
row = AppSetting(key="extension_api_key", value=secrets.token_urlsafe(32))
session.add(row)
await session.commit()
await session.refresh(row)
return row.value
@settings_bp.route("/settings/extension_api_key", methods=["GET"])
async def get_extension_api_key():
async with get_session() as session:
key = await _get_or_seed_extension_api_key(session)
return jsonify({"key": key})
@settings_bp.route("/settings/extension_api_key/rotate", methods=["POST"])
async def rotate_extension_api_key():
async with get_session() as session:
row = (await session.execute(
select(AppSetting).where(AppSetting.key == "extension_api_key")
)).scalar_one_or_none()
new_value = secrets.token_urlsafe(32)
if row is None:
row = AppSetting(key="extension_api_key", value=new_value)
session.add(row)
else:
row.value = new_value
await session.commit()
return jsonify({"key": new_value})