# SPDX-License-Identifier: GPL-3.0-only """OpenScribe server - FastAPI app. M0 scaffold: wires the routes from api/openapi.yaml with in-memory stubs so the API shape is real and browsable at /docs. The AI pipeline (faster-whisper transcription in M5, Ollama summaries in M6) and real storage/DB replace the stubs in later milestones. """ from __future__ import annotations from fastapi import FastAPI, HTTPException from .config import settings from .models import Recording, RecordingPage, Summary, Transcript app = FastAPI( title="OpenScribe API", version="0.1.0", description="Self-hosted AI voice recorder server. See api/openapi.yaml.", ) # In-memory store stands in for the DB + object storage until M5. _recordings: dict[str, Recording] = {} @app.get("/health") def health() -> dict: return { "status": "ok", "storage_backend": settings.storage_backend, "whisper_model": settings.whisper_model, "ollama_model": settings.ollama_model, } @app.get("/api/v1/recordings", response_model=RecordingPage, tags=["recordings"]) def list_recordings(limit: int = 50) -> RecordingPage: return RecordingPage(items=list(_recordings.values())[:limit], next_cursor=None) @app.get("/api/v1/recordings/{rec_id}", response_model=Recording, tags=["recordings"]) def get_recording(rec_id: str) -> Recording: rec = _recordings.get(rec_id) if rec is None: raise HTTPException(status_code=404, detail="No such recording") return rec @app.get("/api/v1/recordings/{rec_id}/transcript", response_model=Transcript, tags=["server"]) def get_transcript(rec_id: str) -> Transcript: # Implemented in M5 (faster-whisper). Until then, signal "not transcribed yet". if rec_id not in _recordings: raise HTTPException(status_code=404, detail="No such recording") raise HTTPException(status_code=409, detail="Not transcribed yet (M5)") @app.get("/api/v1/recordings/{rec_id}/summary", response_model=Summary, tags=["server"]) def get_summary(rec_id: str) -> Summary: # Implemented in M6 (Ollama). Until then, signal "not summarised yet". if rec_id not in _recordings: raise HTTPException(status_code=404, detail="No such recording") raise HTTPException(status_code=409, detail="Not summarised yet (M6)") @app.post("/api/v1/ingest", response_model=Recording, status_code=202, tags=["server"]) def ingest(recording: Recording) -> Recording: # M5 will store audio to the object store and queue transcription + summary. _recordings[recording.id] = recording return recording