Standalone Shonar Desktop: vendor portable sources + local engine; decouple from ~/Projects/Shonar
- shared/ = portable Android-origin sources vendored from deferred/desktop-server (app/build.gradle.kts srcDir repointed; PlaybackController.kt excluded as Android-only) - backend/ = bundled-lite engine (SQLite + inline queue); .venv symlinked from the old checkout, PYTHONPATH pins THIS backend's code over any editable install - repoRoot() resolves this project dir (env SHONAR_REPO still wins); desktop-dev.sh watches shared/ + backend/ - Verified: :app:compileKotlin + :app:test green (23 tests); engine boots on :8010, self-migrates, /healthz ok
This commit is contained in:
commit
76c867fca4
136 changed files with 21099 additions and 0 deletions
79
backend/shonar/main.py
Normal file
79
backend/shonar/main.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
"""S.H.O.N.A.R. FastAPI application entrypoint.
|
||||
|
||||
Run (dev): uvicorn shonar.main:app --reload --port 8000
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from slowapi import _rate_limit_exceeded_handler
|
||||
from slowapi.errors import RateLimitExceeded
|
||||
|
||||
from shonar import __version__
|
||||
from shonar.api.v1 import api_router
|
||||
from shonar.core.config import get_settings
|
||||
from shonar.core.ratelimit import limiter
|
||||
from shonar.db.session import dispose_engine, get_engine
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger("shonar")
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
settings = get_settings()
|
||||
get_engine() # validate URL parses; connections are lazy
|
||||
for warning in settings.validate_production():
|
||||
logger.warning("CONFIG: %s", warning)
|
||||
if settings.auto_migrate:
|
||||
from shonar.db.migrate import upgrade_head
|
||||
|
||||
await upgrade_head(settings.database_url)
|
||||
inline = settings.queue_backend.strip().lower() == "inline"
|
||||
if inline:
|
||||
from shonar.services import inline_queue
|
||||
|
||||
await inline_queue.start()
|
||||
yield
|
||||
if inline:
|
||||
from shonar.services import inline_queue
|
||||
|
||||
await inline_queue.stop()
|
||||
await dispose_engine()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
title="SHONAR API",
|
||||
description=(
|
||||
"SHONAR — Self-hosted Oral Notes and Audio Recorder. REST API. "
|
||||
"All data stays on your server."
|
||||
),
|
||||
version=__version__,
|
||||
lifespan=lifespan,
|
||||
# Interactive docs at the conventional paths.
|
||||
docs_url="/docs",
|
||||
redoc_url="/redoc",
|
||||
openapi_url="/openapi.json",
|
||||
)
|
||||
|
||||
app.state.limiter = limiter
|
||||
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
|
||||
|
||||
|
||||
@app.exception_handler(Exception)
|
||||
async def unhandled_exception_handler(request: Request, exc: Exception):
|
||||
"""Safe error surface: log details server-side, never leak internals."""
|
||||
logger.exception("Unhandled error on %s %s", request.method, request.url.path)
|
||||
return JSONResponse(status_code=500, content={"detail": "Internal server error"})
|
||||
|
||||
|
||||
app.include_router(api_router)
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
return {"app": "SHONAR", "docs": "/docs", "health": "/api/v1/healthz"}
|
||||
Loading…
Add table
Add a link
Reference in a new issue