From 64b3d4658637e31a511413857c044ded7ce96de7 Mon Sep 17 00:00:00 2001 From: Michael Woods Date: Sun, 21 Dec 2025 22:44:08 -0500 Subject: [PATCH] Adding bulletin support. --- packetserver/http/routers/bulletins.py | 74 ++++++++++++++++++++++++++ packetserver/http/server.py | 5 +- 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 packetserver/http/routers/bulletins.py diff --git a/packetserver/http/routers/bulletins.py b/packetserver/http/routers/bulletins.py new file mode 100644 index 0000000..972f3b9 --- /dev/null +++ b/packetserver/http/routers/bulletins.py @@ -0,0 +1,74 @@ +# packetserver/http/routers/bulletins.py +from fastapi import APIRouter, Path, Query, Depends, HTTPException +from typing import Optional +from datetime import datetime +import transaction + +from packetserver.http.dependencies import get_current_http_user +from packetserver.http.auth import HttpUser +from packetserver.server.bulletin import Bulletin # core class + +router = APIRouter(prefix="/api/v1", tags=["bulletins"]) + + +@router.get("/bulletins") +async def list_bulletins( + limit: Optional[int] = Query(20, le=100, description="Max bulletins to return"), + since: Optional[str] = Query(None, description="ISO UTC timestamp filter") +): + from packetserver.runners.http_server import get_db_connection + conn = get_db_connection() + root = conn.root() + + bulletins_list = root.get('bulletins', []) + if not bulletins_list: + return {"bulletins": [], "total": 0} + + since_dt = None + if since: + try: + since_dt = datetime.fromisoformat(since.replace("Z", "+00:00")) + except ValueError: + raise HTTPException(status_code=400, detail="Invalid 'since' format") + + filtered = [] + for bull in reversed(bulletins_list): # newest first (assuming appended order) + if since_dt and bull.created_at < since_dt: + continue + filtered.append({ + "id": bull.id, + "author": bull.author, + "subject": bull.subject, + "body": bull.body, + "created_at": bull.created_at.isoformat() + "Z", + "updated_at": bull.updated_at.isoformat() + "Z", + }) + + return { + "bulletins": filtered[:limit], + "total": len(filtered) + } + + +@router.get("/bulletins/{bid}") +async def get_bulletin( + bid: int = Path(..., description="Bulletin ID"), + current_user: HttpUser = Depends(get_current_http_user) # require auth, but public read +): + from packetserver.runners.http_server import get_db_connection + conn = get_db_connection() + root = conn.root() + + bulletins_list = root.get('bulletins', []) + for bull in bulletins_list: + if bull.id == bid: + return { + "id": bull.id, + "author": bull.author, + "subject": bull.subject, + "body": bull.body, + "created_at": bull.created_at.isoformat() + "Z", + "updated_at": bull.updated_at.isoformat() + "Z", + } + + raise HTTPException(status_code=404, detail="Bulletin not found") \ No newline at end of file diff --git a/packetserver/http/server.py b/packetserver/http/server.py index e789347..0a113b5 100644 --- a/packetserver/http/server.py +++ b/packetserver/http/server.py @@ -4,7 +4,7 @@ from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates from pathlib import Path -from .routers import public, profile, messages, send # dashboard removed for now +from .routers import public, profile, messages, send, bulletins BASE_DIR = Path(__file__).parent.resolve() @@ -28,4 +28,5 @@ app.include_router(public.router) app.include_router(profile.router) app.include_router(messages.router) app.include_router(send.router) -app.include_router(dashboard.router) # now works \ No newline at end of file +app.include_router(dashboard.router) +app.include_router(bulletins.router) \ No newline at end of file