akif/arclet-boards
Arclet Boards — private Kanban workspaces with AppID, DataK3, shared web and MCP actions.
A remix template · 1 remixes · Remix akif/arclet-boards
app/web/routes.py
"""HTTP adapter: validated action contracts and permissions live in application/."""
from pathlib import Path
from fastapi import APIRouter, Request
from fastapi.responses import FileResponse
from pydantic import BaseModel, ConfigDict, Field
from app.application.contracts import READS, WRITES
from app.domain.model import Invalid, Person
STATIC = Path(__file__).parent / "static"
class ActionBody(BaseModel):
model_config = ConfigDict(extra="forbid")
arguments: dict = Field(default_factory=dict)
def build_router(boards):
router = APIRouter()
def call(request, action, payload=None):
return boards.call(action, request.state.person, payload,
request.headers.get("idempotency-key"))
@router.get("/api/session")
def session(request: Request):
person: Person = request.state.person
return {"sub": person.sub, "email": person.email, "product": "Arclet Boards"}
@router.get("/api/boards")
def list_boards(request: Request):
return call(request, "list_boards")
@router.get("/api/boards/{board_id}")
def get_board(board_id: str, request: Request):
return call(request, "get_board", {"board_id": board_id})
@router.get("/api/cards/{card_id}")
def get_card(card_id: str, request: Request):
return call(request, "get_card", {"card_id": card_id})
@router.get("/api/workspaces")
def list_workspaces(request: Request):
return call(request, "list_workspaces")
@router.get("/api/tasks/{kind}")
def tasks(kind: str, request: Request):
if kind not in ("mine", "overdue"):
raise Invalid("Choose mine or overdue")
return call(request, "list_my_cards" if kind == "mine" else "list_overdue")
@router.post("/api/actions/{action}")
def action(action: str, body: ActionBody, request: Request):
if action not in READS and action not in WRITES:
raise Invalid("Unknown action")
return call(request, action, body.arguments)
@router.get("/")
def index():
return FileResponse(STATIC / "index.html", media_type="text/html")
return router