Adding more bulletin functionality.

This commit is contained in:
Michael Woods
2025-12-24 19:42:50 -05:00
parent b074976fd6
commit f8685230c4
4 changed files with 151 additions and 3 deletions

View File

@@ -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")
@@ -72,3 +82,76 @@ async def get_bulletin(
}
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}
)

View File

@@ -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()
@@ -44,3 +45,4 @@ app.include_router(messages.router)
app.include_router(send.router)
app.include_router(dashboard.router)
app.include_router(bulletins.router)
app.include_router(html_router)

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{ bulletin.subject }} - PacketServer</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 2em auto; padding: 1em; background: #f9f9f9; }
pre { background: white; padding: 1em; border-radius: 8px; white-space: pre-wrap; }
.meta { color: #666; }
</style>
</head>
<body>
<h1>{{ bulletin.subject }}</h1>
<p class="meta">By {{ bulletin.author }} on {{ bulletin.created_at[:10] }}</p>
<pre>{{ bulletin.body }}</pre>
<p>
<a href="/bulletins">← All Bulletins</a> |
<a href="/dashboard">Dashboard</a>
</p>
</body>
</html>

View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Bulletins - PacketServer</title>
<style>
body { font-family: Arial, sans-serif; max-width: 900px; margin: 2em auto; padding: 1em; background: #f9f9f9; }
h1 { text-align: center; }
ul { list-style: none; padding: 0; }
li { background: white; margin: 1em 0; padding: 1em; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
.meta { color: #666; font-size: 0.9em; }
.preview { margin-top: 0.5em; }
</style>
</head>
<body>
<h1>Bulletins</h1>
{% if current_user %}
<p><a href="/bulletins/new">Create New Bulletin</a></p>
{% else %}
<p><em>Log in to create bulletins.</em></p>
{% endif %}
{% if bulletins %}
<ul>
{% for bull in bulletins %}
<li>
<strong><a href="/bulletins/{{ bull.id }}">{{ bull.subject }}</a></strong>
<div class="meta">by {{ bull.author }} on {{ bull.created_at[:10] }}</div>
<div class="preview">{{ bull.body[:200] }}{% if bull.body|length > 200 %}...{% endif %}</div>
</li>
{% endfor %}
</ul>
{% else %}
<p>No bulletins yet.</p>
{% endif %}
<p><a href="/dashboard">← Back to Dashboard</a></p>
</body>
</html>