Files
pablan/backend/app/api/templates/catalog.py
T
ProfessorNovaandClaude Opus 5 97dbff309c Pablan, as it stands
Self-hosted knowledge management for SMEs: a split-screen Markdown editor
whose sections an LLM refines while you write, and RAG question answering
over the documents that result. FastAPI + Postgres/pgvector on the back,
SvelteKit on the front, everything OpenAI-compatible and self-hostable.

Squashed into a single commit; the development history stays local.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CA43ZJda8Rbp2hKXNy8f6b
2026-09-04 08:36:17 +02:00

80 lines
2.6 KiB
Python

"""The blueprints that ship with the product.
Nothing in the catalog is active. It is a shelf an admin takes from: adding a
blueprint copies it into an ordinary template row, which the customer then owns
and edits. The catalog never touches that row again.
"""
from typing import Annotated
from fastapi import Depends
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.templates.blueprints import parse_or_422, taken_config_ids
from app.api.templates.routing import editor_router
from app.api.templates.schemas import CatalogDetail, CatalogSummary, TemplateDetail
from app.api.templates.view import detail
from app.db import get_db
from app.errors import ApiError
from app.template_catalog import catalog_for_locale, get_catalog_entry
from app.template_import import upsert_template
router = editor_router()
@router.get("/catalog")
async def list_catalog(
db: Annotated[AsyncSession, Depends(get_db)],
) -> list[CatalogSummary]:
"""The blueprints shipped with Pablan, each marked with whether this
instance has already added it."""
added = await taken_config_ids(db)
return [
CatalogSummary(
id=entry.id,
name=entry.name,
description=entry.description,
sections=entry.sections,
added=entry.id in added,
)
for entry in catalog_for_locale()
]
@router.get("/catalog/{catalog_id}")
async def get_catalog_blueprint(catalog_id: str) -> CatalogDetail:
"""Read a blueprint before adding it — the whole point of "view" is that
an admin can see its structure before committing to it."""
entry = get_catalog_entry(catalog_id)
if entry is None:
raise ApiError(404, "Blueprint not found.", "not_found")
return CatalogDetail(
id=entry.id,
name=entry.name,
description=entry.description,
sections=entry.sections,
added=False,
yaml=entry.source,
)
@router.post("/catalog/{catalog_id}")
async def add_from_catalog(
catalog_id: str,
db: Annotated[AsyncSession, Depends(get_db)],
) -> TemplateDetail:
"""Copy a blueprint into this instance. The result is an ordinary
template row: editable, and never touched by the catalog again."""
entry = get_catalog_entry(catalog_id)
if entry is None:
raise ApiError(404, "Blueprint not found.", "not_found")
if entry.id in await taken_config_ids(db):
raise ApiError(
409,
"This template has already been added — edit or duplicate it instead.",
"already_added",
)
row, _created = await upsert_template(db, parse_or_422(entry.source))
await db.commit()
return detail(row)