From f8685230c46969fa711f76ddba39554b248a4f4b Mon Sep 17 00:00:00 2001 From: Michael Woods Date: Wed, 24 Dec 2025 19:42:50 -0500 Subject: [PATCH] Adding more bulletin functionality. --- packetserver/http/routers/bulletins.py | 87 ++++++++++++++++++- packetserver/http/server.py | 4 +- .../http/templates/bulletin_detail.html | 23 +++++ .../http/templates/bulletin_list.html | 40 +++++++++ 4 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 packetserver/http/templates/bulletin_detail.html create mode 100644 packetserver/http/templates/bulletin_list.html diff --git a/packetserver/http/routers/bulletins.py b/packetserver/http/routers/bulletins.py index 972f3b9..6cbeb2d 100644 --- a/packetserver/http/routers/bulletins.py +++ b/packetserver/http/routers/bulletins.py @@ -1,14 +1,24 @@ # packetserver/http/routers/bulletins.py -from fastapi import APIRouter, Path, Query, Depends, HTTPException +from fastapi import APIRouter, Path, Query, Depends, HTTPException, status, Request, APIRouter as PrefixedRouter +from fastapi.responses import HTMLResponse from typing import Optional from datetime import datetime import transaction +from pydantic import BaseModel, Field, constr +from persistent.list import PersistentList from packetserver.http.dependencies import get_current_http_user from packetserver.http.auth import HttpUser +from packetserver.http.server import templates from packetserver.server.bulletin import Bulletin # core class router = APIRouter(prefix="/api/v1", tags=["bulletins"]) +html_router = APIRouter(tags=["bulletins-html"]) + + +class CreateBulletinRequest(BaseModel): + subject: constr(min_length=1, max_length=100) = Field(..., description="Bulletin subject/title") + body: constr(min_length=1) = Field(..., description="Bulletin body text") @router.get("/bulletins") @@ -71,4 +81,77 @@ async def get_bulletin( "updated_at": bull.updated_at.isoformat() + "Z", } - raise HTTPException(status_code=404, detail="Bulletin not found") \ No newline at end of file + raise HTTPException(status_code=404, detail="Bulletin not found") + +from fastapi import status +from pydantic import BaseModel, Field, constr +# datetime already imported elsewhere, but ensure + +class CreateBulletinRequest(BaseModel): + subject: constr(min_length=1, max_length=100) = Field(..., description="Bulletin subject/title") + body: constr(min_length=1) = Field(..., description="Bulletin body text") + +@router.post("/bulletins", status_code=status.HTTP_201_CREATED) +async def create_bulletin( + payload: CreateBulletinRequest, + current_user: HttpUser = Depends(get_current_http_user) +): + from packetserver.runners.http_server import get_db_connection + from packetserver.server.bulletin import Bulletin + + conn = get_db_connection() + root = conn.root() + + # Ensure the bulletins list and counter exist + if 'bulletins' not in root: + root['bulletins'] = persistent.list.PersistentList() + + new_bulletin = Bulletin( + author=current_user.username, # uppercase callsign + subject=payload.subject.strip(), + text=payload.body.strip() # note: 'text' param, assigned to body + ) + + # This assigns the ID, sets timestamps (if not already), and appends + new_id = new_bulletin.write_new(root) + + transaction.commit() + + return { + "id": new_id, # or new_bulletin.id + "author": new_bulletin.author, + "subject": new_bulletin.subject, + "body": new_bulletin.body, + "created_at": new_bulletin.created_at.isoformat() + "Z", + "updated_at": new_bulletin.updated_at.isoformat() + "Z", + } + +@html_router.get("/bulletins", response_class=HTMLResponse) +async def bulletin_list_page( + request: Request, + limit: Optional[int] = Query(50, le=100), + current_user: Optional[HttpUser] = Depends(get_current_http_user, use_cache=False) # optional auth +): + # Internal call to existing API function + api_resp = await list_bulletins(limit=limit, since=None) + bulletins = api_resp["bulletins"] + + return templates.TemplateResponse( + "bulletin_list.html", + {"request": request, "bulletins": bulletins, "current_user": current_user.username if current_user else None} + ) + +@html_router.get("/bulletins/{bid}", response_class=HTMLResponse) +async def bulletin_detail_page( + request: Request, + bid: int = Path(...), + current_user: Optional[HttpUser] = Depends(get_current_http_user, use_cache=False) +): + # Internal call to existing detail function + # Pass a dummy None user since the API requires it but doesn't enforce privileges + bulletin = await get_bulletin(bid=bid, current_user=None) + + return templates.TemplateResponse( + "bulletin_detail.html", + {"request": request, "bulletin": bulletin, "current_user": current_user.username if current_user else None} + ) \ No newline at end of file diff --git a/packetserver/http/server.py b/packetserver/http/server.py index 612cb87..f156978 100644 --- a/packetserver/http/server.py +++ b/packetserver/http/server.py @@ -5,6 +5,7 @@ from fastapi.templating import Jinja2Templates from pathlib import Path from .routers import public, profile, messages, send, bulletins +from packetserver.http.routers.bulletins import html_router BASE_DIR = Path(__file__).parent.resolve() @@ -43,4 +44,5 @@ app.include_router(profile.router) app.include_router(messages.router) app.include_router(send.router) app.include_router(dashboard.router) -app.include_router(bulletins.router) \ No newline at end of file +app.include_router(bulletins.router) +app.include_router(html_router) \ No newline at end of file diff --git a/packetserver/http/templates/bulletin_detail.html b/packetserver/http/templates/bulletin_detail.html new file mode 100644 index 0000000..db864a1 --- /dev/null +++ b/packetserver/http/templates/bulletin_detail.html @@ -0,0 +1,23 @@ + + + + + {{ bulletin.subject }} - PacketServer + + + +

{{ bulletin.subject }}

+

By {{ bulletin.author }} on {{ bulletin.created_at[:10] }}

+ +
{{ bulletin.body }}
+ +

+ ← All Bulletins | + Dashboard +

+ + \ No newline at end of file diff --git a/packetserver/http/templates/bulletin_list.html b/packetserver/http/templates/bulletin_list.html new file mode 100644 index 0000000..251061f --- /dev/null +++ b/packetserver/http/templates/bulletin_list.html @@ -0,0 +1,40 @@ + + + + + Bulletins - PacketServer + + + +

Bulletins

+ + {% if current_user %} +

Create New Bulletin

+ {% else %} +

Log in to create bulletins.

+ {% endif %} + + {% if bulletins %} + + {% else %} +

No bulletins yet.

+ {% endif %} + +

← Back to Dashboard

+ + \ No newline at end of file