More bulletins changes.

This commit is contained in:
Michael Woods
2025-12-24 19:56:18 -05:00
parent 31ea05e3cb
commit 25345d8aff

View File

@@ -1,61 +1,81 @@
from fastapi import APIRouter, Path, Query, Depends, HTTPException, Request, status from fastapi import APIRouter, Path, Query, Depends, HTTPException, Request, status
from fastapi.responses import HTMLResponse from fastapi.responses import HTMLResponse
from typing import Optional from typing import Optional, List
from pydantic import BaseModel, Field, constr from pydantic import BaseModel, Field, constr
from datetime import datetime from datetime import datetime
import transaction import transaction
from persistent.list import PersistentList from persistent.list import PersistentList
from ..dependencies import get_current_http_user # relative from ..dependencies import get_current_http_user
from ..auth import HttpUser from ..auth import HttpUser
from ..server import templates # relative import - this is now safe from ..server import templates
from packetserver.runners.http_server import get_db_connection
from packetserver.server.bulletin import Bulletin from packetserver.server.bulletin import Bulletin
# Existing API router (keep) # API router (/api/v1)
router = APIRouter(prefix="/api/v1", tags=["bulletins"]) router = APIRouter(prefix="/api/v1", tags=["bulletins"])
# ... your existing list_bulletins and get_bulletin routes ... # HTML router (pretty URLs: /bulletins, /bulletins/{bid})
# New non-prefixed router for HTML pages
html_router = APIRouter(tags=["bulletins-html"]) html_router = APIRouter(tags=["bulletins-html"])
@html_router.get("/bulletins", response_class="HTMLResponse") # --- API Endpoints ---
async def bulletin_list_page(request: Request, limit: Optional[int] = Query(50, le=100)):
# Local import to avoid circular issues
from .bulletins import list_bulletins as api_list_bulletins
api_resp = await api_list_bulletins(limit=limit, since=None)
bulletins = api_resp["bulletins"]
current_user = None async def list_bulletins(limit: int = 50, since: Optional[datetime] = None) -> dict:
try: conn = get_db_connection()
current_user = await get_current_http_user()(request) # optional call root = conn.root()
except: bulletins_list: List[Bulletin] = root.get("bulletins", [])
pass
return templates.TemplateResponse( # Newest first
"bulletin_list.html", bulletins_list = sorted(bulletins_list, key=lambda b: b.created_at, reverse=True)
{"request": request, "bulletins": bulletins, "current_user": current_user.username if current_user else None}
)
@html_router.get("/bulletins/{bid}", response_class="HTMLResponse") if since:
async def bulletin_detail_page(request: Request, bid: int): bulletins_list = [b for b in bulletins_list if b.created_at > since]
from .bulletins import get_bulletin as api_get_bulletin
try:
bulletin = await api_get_bulletin(bid=bid, current_user=None)
except HTTPException:
raise HTTPException(status_code=404, detail="Bulletin not found")
current_user = None bulletins = [
try: {
current_user = await get_current_http_user()(request) "id": b.id,
except: "author": b.author,
pass "subject": b.subject,
"body": b.body,
"created_at": b.created_at.isoformat() + "Z",
"updated_at": b.updated_at.isoformat() + "Z",
}
for b in bulletins_list[:limit]
]
return templates.TemplateResponse( return {"bulletins": bulletins}
"bulletin_detail.html",
{"request": request, "bulletin": bulletin, "current_user": current_user.username if current_user else None}
)
@router.get("/bulletins")
async def api_list_bulletins(
limit: Optional[int] = Query(50, le=100),
since: Optional[datetime] = None,
current_user: HttpUser = Depends(get_current_http_user)
):
return await list_bulletins(limit=limit, since=since)
async def get_one_bulletin(bid: int) -> dict:
conn = get_db_connection()
root = conn.root()
bulletins_list: List[Bulletin] = root.get("bulletins", [])
for b in bulletins_list:
if b.id == bid:
return {
"id": b.id,
"author": b.author,
"subject": b.subject,
"body": b.body,
"created_at": b.created_at.isoformat() + "Z",
"updated_at": b.updated_at.isoformat() + "Z",
}
raise HTTPException(status_code=404, detail="Bulletin not found")
@router.get("/bulletins/{bid}")
async def api_get_bulletin(
bid: int,
current_user: HttpUser = Depends(get_current_http_user)
):
return await get_one_bulletin(bid)
class CreateBulletinRequest(BaseModel): class CreateBulletinRequest(BaseModel):
subject: constr(min_length=1, max_length=100) = Field(..., description="Bulletin subject/title") subject: constr(min_length=1, max_length=100) = Field(..., description="Bulletin subject/title")
@@ -66,29 +86,24 @@ async def create_bulletin(
payload: CreateBulletinRequest, payload: CreateBulletinRequest,
current_user: HttpUser = Depends(get_current_http_user) 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() conn = get_db_connection()
root = conn.root() root = conn.root()
# Ensure the bulletins list and counter exist
if 'bulletins' not in root: if 'bulletins' not in root:
root['bulletins'] = PersistentList() root['bulletins'] = PersistentList()
new_bulletin = Bulletin( new_bulletin = Bulletin(
author=current_user.username, # uppercase callsign author=current_user.username,
subject=payload.subject.strip(), subject=payload.subject.strip(),
text=payload.body.strip() # note: 'text' param, assigned to body text=payload.body.strip()
) )
# This assigns the ID, sets timestamps (if not already), and appends
new_id = new_bulletin.write_new(root) new_id = new_bulletin.write_new(root)
transaction.commit() transaction.commit()
return { return {
"id": new_id, # or new_bulletin.id "id": new_id,
"author": new_bulletin.author, "author": new_bulletin.author,
"subject": new_bulletin.subject, "subject": new_bulletin.subject,
"body": new_bulletin.body, "body": new_bulletin.body,
@@ -96,32 +111,31 @@ async def create_bulletin(
"updated_at": new_bulletin.updated_at.isoformat() + "Z", "updated_at": new_bulletin.updated_at.isoformat() + "Z",
} }
# --- HTML Pages (require login) ---
@html_router.get("/bulletins", response_class=HTMLResponse) @html_router.get("/bulletins", response_class=HTMLResponse)
async def bulletin_list_page( async def bulletin_list_page(
request: Request, request: Request,
limit: Optional[int] = Query(50, le=100), limit: Optional[int] = Query(50, le=100),
current_user: Optional[HttpUser] = Depends(get_current_http_user, use_cache=False) # optional auth current_user: HttpUser = Depends(get_current_http_user)
): ):
# Internal call to existing API function
api_resp = await list_bulletins(limit=limit, since=None) api_resp = await list_bulletins(limit=limit, since=None)
bulletins = api_resp["bulletins"] bulletins = api_resp["bulletins"]
return templates.TemplateResponse( return templates.TemplateResponse(
"bulletin_list.html", "bulletin_list.html",
{"request": request, "bulletins": bulletins, "current_user": current_user.username if current_user else None} {"request": request, "bulletins": bulletins, "current_user": current_user.username}
) )
@html_router.get("/bulletins/{bid}", response_class=HTMLResponse) @html_router.get("/bulletins/{bid}", response_class=HTMLResponse)
async def bulletin_detail_page( async def bulletin_detail_page(
request: Request, request: Request,
bid: int = Path(...), bid: int = Path(...),
current_user: Optional[HttpUser] = Depends(get_current_http_user, use_cache=False) current_user: HttpUser = Depends(get_current_http_user)
): ):
# Internal call to existing detail function bulletin = await get_one_bulletin(bid=bid)
# 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( return templates.TemplateResponse(
"bulletin_detail.html", "bulletin_detail.html",
{"request": request, "bulletin": bulletin, "current_user": current_user.username if current_user else None} {"request": request, "bulletin": bulletin, "current_user": current_user.username}
) )