feat(01-03): integration wiring, Docker deployment, lightsync.groll.cloud

- Fix API routing: redirect_slashes=False + strip trailing slashes from route paths
- Fix StaticFiles catch-all intercepting /api/* before FastAPI redirect
- Add [tool.hatch.build.targets.wheel] to pyproject.toml for Docker builds
- Install docker-compose plugin (v2.34.0) as CLI plugin
- Create Dockerfile (python:3.11-alpine, CMD python -m lightsync)
- Create docker-compose.prod.yml (Traefik + Authelia, edge network)
- Deploy lightsync container to lightsync.groll.cloud
- Add LightSync entry to start/projects.json
- Update frontend app.js to use /api/devices (no trailing slash)
This commit is contained in:
Claude
2026-04-05 18:59:34 +00:00
parent 9f3a2fc7a1
commit a21962cbcf
7 changed files with 45 additions and 6 deletions

View File

@@ -5,12 +5,12 @@ import lightsync.main as state # access registry via module-level ref
router = APIRouter(tags=["devices"])
@router.get("/")
@router.get("")
async def list_devices() -> list[DeviceConfig]:
return state.registry.list_all()
@router.post("/", status_code=201)
@router.post("", status_code=201)
async def add_device(config: DeviceConfig) -> DeviceConfig:
state.registry.add(config)
await state.registry.save()

View File

@@ -5,7 +5,7 @@ import lightsync.main as state # access show_store via module-level ref
router = APIRouter(tags=["shows"])
@router.get("/")
@router.get("")
async def list_shows():
"""Return summary list of all shows."""
summaries = []
@@ -20,7 +20,7 @@ async def list_shows():
return summaries
@router.post("/", status_code=201)
@router.post("", status_code=201)
async def create_show(show: ShowModel) -> ShowModel:
await state.show_store.save(show)
return show

View File

@@ -58,7 +58,7 @@ client.connect();
// Load device list on startup
async function loadDevices() {
try {
const res = await fetch("/api/devices/");
const res = await fetch("/api/devices");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const devices = await res.json();
const list = document.getElementById("device-list");

View File

@@ -25,7 +25,7 @@ async def lifespan(app: FastAPI):
def create_app() -> FastAPI:
app = FastAPI(title="LightSync", lifespan=lifespan)
app = FastAPI(title="LightSync", lifespan=lifespan, redirect_slashes=False)
# API routes (registered before static mount — see Pattern 7)
from lightsync.api import shows, devices, ws