Adding more bulletin functionality.
This commit is contained in:
@@ -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")
|
||||
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}
|
||||
)
|
||||
@@ -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)
|
||||
app.include_router(bulletins.router)
|
||||
app.include_router(html_router)
|
||||
23
packetserver/http/templates/bulletin_detail.html
Normal file
23
packetserver/http/templates/bulletin_detail.html
Normal 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>
|
||||
40
packetserver/http/templates/bulletin_list.html
Normal file
40
packetserver/http/templates/bulletin_list.html
Normal 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>
|
||||
Reference in New Issue
Block a user