feat: apikey alias/renewal + markdown/patch authorship

- APIKey.alias (unique, required). Creating with an existing alias
  renews that key: same key string kept, validity reset to 15d,
  reactivated, name/roles updated (response has renewed=true).
- get_actor(): X-API-Key -> key alias, Bearer -> 'admin'.
- markdown & patch create/update record author / created_at /
  updated_at / last_modified_by from the actor.
- Idempotent run_migrations() (information_schema-guarded ALTERs +
  backfill) so existing tables/data gain the new columns on startup;
  create_all still covers fresh DBs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
h z
2026-05-16 22:51:40 +01:00
parent 9e2477df8c
commit bf4c0dbbbd
8 changed files with 164 additions and 8 deletions

View File

@@ -1,8 +1,9 @@
from flask import Blueprint, request, jsonify
from sqlalchemy import or_
from api import limiter
from api import require_auth, etag_response, verify_token, is_user_admin
from api import require_auth, etag_response, verify_token, is_user_admin, get_actor
from contexts.RequestContext import RequestContext
from datetime import datetime, UTC
from db import get_db
from db.models.Markdown import Markdown
from db.models.MarkdownSetting import MarkdownSetting
@@ -225,7 +226,13 @@ def create_markdown():
setting_id = data.get('setting_id', None)
if not title or not content:
return jsonify({"error": "missing required fields"}), 400
new_markdown = Markdown(title=title, content=content, path_id=path_id, shortcut=shortcut, setting_id=setting_id)
actor = get_actor()
now = datetime.now(UTC)
new_markdown = Markdown(
title=title, content=content, path_id=path_id, shortcut=shortcut,
setting_id=setting_id, author=actor, last_modified_by=actor,
created_at=now, updated_at=now,
)
with get_db() as session:
try:
if shortcut != "":
@@ -301,6 +308,8 @@ def update_markdown(markdown_id):
markdown.shortcut = data.get('shortcut')
if 'setting_id' in data:
markdown.setting_id = data.get('setting_id')
markdown.updated_at = datetime.now(UTC)
markdown.last_modified_by = get_actor()
session.commit()
markdown_updated.send(None, payload=markdown.to_dict())
return jsonify(markdown.to_dict()), 200