8649a13118
The GS/IR migration cutover is complete, so the runbook tooling is dead weight. Removed: - services/migrators/ (gs_ingest, ir_ingest, tag_apply, ml_queue, verify, cleanup), tasks/migration.py, api/migrate.py (+ blueprint registration) - MigrationRun model; alembic 0027 drops the migration_run table - frontend LegacyMigrationCard + migration store (+ MaintenancePanel ref) - celery include + task route + celery_signals queue mapping for migration.* - the 1 GB MAX_CONTENT_LENGTH / MAX_FORM_MEMORY override (added solely for the ir_ingest upload) - migration-surface tests (test_api_migrate, test_migration_verify, test_ir_ingest, test_gs_ingest, test_tag_apply) Kept: the alembic schema-migration tests (test_migration_00XX — unrelated) and cleanup_service.py (the permanent artist-cascade/unlink home). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
75 lines
2.8 KiB
Python
75 lines
2.8 KiB
Python
"""Quart app factory."""
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from quart import Quart, request
|
|
|
|
from .api import all_blueprints
|
|
from .config import get_config
|
|
from .frontend import frontend_bp
|
|
from .services.credential_crypto import CredentialCrypto
|
|
|
|
# Browser-extension origins. The FabledCurator extension fetches from
|
|
# moz-extension://<uuid>/ on Firefox and chrome-extension://<uuid>/ on
|
|
# Chromium-based browsers. Operator-flagged 2026-05-26: extension's
|
|
# 'Test connection' returned `NetworkError` because the X-Extension-Key
|
|
# header on /api/credentials triggers a CORS preflight that our routes
|
|
# don't handle. Whitelisting only these two schemes (not opening CORS
|
|
# up generally) lets the extension talk to a plain-HTTP self-hosted FC
|
|
# without weakening the no-CORS posture for normal browser usage.
|
|
_EXTENSION_ORIGIN_SCHEMES = ("moz-extension://", "chrome-extension://")
|
|
|
|
_CREDENTIAL_KEY_PATH = Path("/images/secrets/credential_key.b64")
|
|
|
|
|
|
def create_app() -> Quart:
|
|
cfg = get_config()
|
|
logging.basicConfig(level=cfg.log_level)
|
|
|
|
# Bootstrap the credential encryption key file (mode 0600) before
|
|
# any request can land. FC-3b: file-based system-generated key.
|
|
CredentialCrypto(_CREDENTIAL_KEY_PATH)
|
|
|
|
app = Quart(__name__)
|
|
app.secret_key = cfg.secret_key
|
|
|
|
for bp in all_blueprints():
|
|
app.register_blueprint(bp)
|
|
# Registered last so /api/* routes win over the SPA catch-all.
|
|
app.register_blueprint(frontend_bp)
|
|
|
|
@app.before_request
|
|
async def _extension_cors_preflight():
|
|
# Short-circuit OPTIONS preflight from the browser extension with a
|
|
# 204 + CORS headers (the after_request hook below adds them).
|
|
# Without this, OPTIONS lands on routes that only declared POST/GET
|
|
# methods and 405s before the after_request gets a chance.
|
|
if request.method != "OPTIONS":
|
|
return None
|
|
origin = request.headers.get("Origin", "")
|
|
if any(origin.startswith(s) for s in _EXTENSION_ORIGIN_SCHEMES):
|
|
return "", 204
|
|
return None
|
|
|
|
@app.after_request
|
|
async def _extension_cors_headers(response):
|
|
origin = request.headers.get("Origin", "")
|
|
if any(origin.startswith(s) for s in _EXTENSION_ORIGIN_SCHEMES):
|
|
response.headers["Access-Control-Allow-Origin"] = origin
|
|
response.headers["Access-Control-Allow-Methods"] = (
|
|
"GET, POST, PATCH, DELETE, OPTIONS"
|
|
)
|
|
response.headers["Access-Control-Allow-Headers"] = (
|
|
"Content-Type, X-Extension-Key"
|
|
)
|
|
response.headers["Access-Control-Max-Age"] = "86400"
|
|
return response
|
|
|
|
@app.after_serving
|
|
async def _dispose_db_engine() -> None:
|
|
from .extensions import dispose_engine
|
|
await dispose_engine()
|
|
|
|
return app
|