feat: add compatibility table endpoint and rendering for game libraries

This commit is contained in:
Patryk Gensch
2026-06-18 23:32:31 +02:00
parent 8875540186
commit 2c785dc87c
8 changed files with 575 additions and 4 deletions

View File

@@ -15,7 +15,7 @@ from fastapi.staticfiles import StaticFiles
from .. import __version__
from .db import configure, init_db
from .routes import diff, games, jobs, snapshots
from .routes import compat, diff, games, jobs, snapshots
_STATIC = Path(__file__).parent / "static"
@@ -29,6 +29,7 @@ def create_app(database_url: str | None = None) -> FastAPI:
app.include_router(snapshots.router)
app.include_router(diff.router)
app.include_router(jobs.router)
app.include_router(compat.router)
@app.get("/health", tags=["meta"])
def health() -> dict[str, str]:

49
ams/api/routes/compat.py Normal file
View File

@@ -0,0 +1,49 @@
"""Catalog-wide compatibility table, grouped by library name, as a downloadable file.
GET /compat?format=md -> Markdown table (attachment compat.md)
GET /compat?format=json -> structured JSON (attachment compat.json)
Builds `ams.compat` entries from every catalogued snapshot, labelling each by its game (or
'(bez gry)' when unlinked). Same engine library name across games clusters into surface variants,
so divergent copies get an asterisk — exactly what the docs table needs.
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, Query
from fastapi.responses import Response
from sqlalchemy import select
from sqlalchemy.orm import Session
from ...compat import Entry, compatibility_table, render_json, render_markdown
from ...snapshot import Snapshot as Surface
from .. import models
from ..db import get_db
router = APIRouter(tags=["compat"])
_NO_GAME = "(bez gry)"
@router.get("/compat")
def get_compat(
format: str = Query("md", pattern="^(md|json)$", description="md = Markdown, json = structured"),
db: Session = Depends(get_db),
) -> Response:
entries = [
Entry(game=snap.game.name if snap.game else _NO_GAME, snapshot=Surface(snap.data))
for snap in db.scalars(select(models.Snapshot).order_by(models.Snapshot.id))
]
table = compatibility_table(entries)
if format == "json":
return Response(
content=render_json(table),
media_type="application/json",
headers={"Content-Disposition": 'attachment; filename="compat.json"'},
)
return Response(
content=render_markdown(table),
media_type="text/markdown; charset=utf-8",
headers={"Content-Disposition": 'attachment; filename="compat.md"'},
)

View File

@@ -16,7 +16,11 @@
<aside id="sidebar" class="sidebar">
<div class="panel-title">
Gry / wersje
<button id="upload-toggle" class="mini-btn" title="Wgraj grę (ISO / ZIP / DLL)">+ wgraj</button>
<span class="panel-actions">
<a id="dl-compat" class="mini-btn" href="/compat?format=md" download="compat.md"
title="Pobierz tablicę kompatybilności (Markdown) — grupowaną po nazwie biblioteki">⬇ kompat</a>
<button id="upload-toggle" class="mini-btn" title="Wgraj grę (ISO / ZIP / DLL)">+ wgraj</button>
</span>
</div>
<form id="upload-form" class="upload" hidden>

View File

@@ -19,9 +19,10 @@ body { background: var(--bg); color: var(--fg); font: 13px/1.45 var(--mono); }
letter-spacing: 1px; position: sticky; top: 0; background: var(--panel); border-bottom: 1px solid var(--border); }
.panel-title { display: flex; align-items: center; justify-content: space-between; }
.panel-actions { display: flex; gap: 6px; }
.mini-btn { background: #16202c; border: 1px solid var(--border); color: var(--accent);
border-radius: 5px; padding: 2px 8px; cursor: pointer; font-family: var(--mono); font-size: 11px;
letter-spacing: 0; text-transform: none; }
letter-spacing: 0; text-transform: none; text-decoration: none; display: inline-block; }
.mini-btn:hover { border-color: var(--accent); }
.upload { display: flex; flex-direction: column; gap: 7px; padding: 10px 12px;