207 lines
6.7 KiB
Python
207 lines
6.7 KiB
Python
# packetserver/http/routers/messages.py
|
||
from fastapi import APIRouter, Depends, Query, HTTPException, Path, Request, Query
|
||
from fastapi.responses import HTMLResponse
|
||
from typing import Optional
|
||
from datetime import datetime
|
||
from persistent.mapping import PersistentMapping
|
||
import persistent.list
|
||
import transaction
|
||
from pydantic import BaseModel, Field, validator
|
||
|
||
from packetserver.http.dependencies import get_current_http_user
|
||
from packetserver.http.auth import HttpUser
|
||
from packetserver.http.database import DbDependency
|
||
from packetserver.http.server import templates
|
||
|
||
|
||
html_router = APIRouter(tags=["messages-html"])
|
||
router = APIRouter(prefix="/api/v1", tags=["messages"])
|
||
|
||
# Simple request model (only allow setting to true)
|
||
class MarkRetrievedRequest(BaseModel):
|
||
retrieved: bool = Field(..., description="Set to true to mark as retrieved")
|
||
|
||
@validator("retrieved")
|
||
def must_be_true(cls, v):
|
||
if not v:
|
||
raise ValueError("retrieved must be true")
|
||
return v
|
||
|
||
|
||
@router.get("/messages", response_class=HTMLResponse)
|
||
async def message_list(
|
||
request: Request,
|
||
db: DbDependency,
|
||
current_user: HttpUser = Depends(get_current_http_user),
|
||
msg_type: str = Query("received", alias="type"), # received, sent, all
|
||
search: Optional[str] = Query(None),
|
||
page: int = Query(1, ge=1),
|
||
per_page: int = Query(50, ge=1, le=200),
|
||
):
|
||
username = current_user.username.upper().strip()
|
||
|
||
valid_types = {"received", "sent", "all"}
|
||
if msg_type not in valid_types:
|
||
msg_type = "received"
|
||
|
||
with db.transaction() as conn:
|
||
root = conn.root()
|
||
user_messages = root["messages"].get(username, [])
|
||
|
||
# Convert to list of dicts (as current code does)
|
||
messages = []
|
||
for msg_id, msg in user_messages.items():
|
||
messages.append({
|
||
"id": msg_id,
|
||
"from": msg["from"],
|
||
"to": msg["to"],
|
||
"text": msg["text"],
|
||
"timestamp": msg["timestamp"],
|
||
})
|
||
|
||
# Filter by type
|
||
if msg_type == "received":
|
||
filtered = [m for m in messages if username in m["to"]]
|
||
elif msg_type == "sent":
|
||
filtered = [m for m in messages if m["from"] == username]
|
||
else: # all
|
||
filtered = messages
|
||
|
||
# Apply search
|
||
if search:
|
||
search_lower = search.strip().lower()
|
||
filtered = [
|
||
m for m in filtered
|
||
if (search_lower in m["from"].lower() or
|
||
any(search_lower in t.lower() for t in m["to"]) or
|
||
search_lower in m["text"].lower())
|
||
]
|
||
|
||
# Sort newest first
|
||
filtered.sort(key=lambda m: m["timestamp"], reverse=True)
|
||
|
||
# Pagination
|
||
total = len(filtered)
|
||
start = (page - 1) * per_page
|
||
paginated = filtered[start:start + per_page]
|
||
total_pages = (total + per_page - 1) // per_page
|
||
|
||
return templates.TemplateResponse(
|
||
"message_list.html",
|
||
{
|
||
"request": request,
|
||
"messages": paginated,
|
||
"current_user": current_user,
|
||
"total": total,
|
||
"page": page,
|
||
"per_page": per_page,
|
||
"total_pages": total_pages,
|
||
"current_type": msg_type,
|
||
"current_search": search,
|
||
}
|
||
)
|
||
|
||
@router.get("/messages/{msg_id}")
|
||
async def get_message(
|
||
db: DbDependency,
|
||
msg_id: str = Path(..., description="UUID of the message (as string)"),
|
||
mark_retrieved: bool = Query(False, description="If true, mark message as retrieved/read"),
|
||
current_user: HttpUser = Depends(get_current_http_user)
|
||
):
|
||
with db.transaction() as conn:
|
||
root = conn.root()
|
||
|
||
username = current_user.username
|
||
|
||
messages_root = root.get('messages', {})
|
||
mailbox = messages_root.get(username)
|
||
if not mailbox:
|
||
raise HTTPException(status_code=404, detail="Mailbox not found")
|
||
|
||
# Find message by ID
|
||
target_msg = None
|
||
for msg in mailbox:
|
||
if str(msg.msg_id) == msg_id:
|
||
target_msg = msg
|
||
break
|
||
|
||
if not target_msg:
|
||
raise HTTPException(status_code=404, detail="Message not found")
|
||
|
||
# Optionally mark as retrieved
|
||
if mark_retrieved and not target_msg.retrieved:
|
||
target_msg.retrieved = True
|
||
target_msg._p_changed = True
|
||
mailbox._p_changed = True
|
||
# Explicit transaction for the write
|
||
transaction.get().commit()
|
||
|
||
return {
|
||
"id": str(target_msg.msg_id),
|
||
"from": target_msg.msg_from or "UNKNOWN",
|
||
"to": list(target_msg.msg_to),
|
||
"sent_at": target_msg.sent_at.isoformat() + "Z",
|
||
"text": target_msg.text,
|
||
"retrieved": target_msg.retrieved,
|
||
"has_attachments": len(target_msg.attachments) > 0,
|
||
# Future: "attachments": [...] metadata
|
||
}
|
||
|
||
@router.patch("/messages/{msg_id}")
|
||
async def mark_message_retrieved(
|
||
db: DbDependency,
|
||
msg_id: str = Path(..., description="Message UUID as string"),
|
||
payload: MarkRetrievedRequest = None,
|
||
current_user: HttpUser = Depends(get_current_http_user)
|
||
):
|
||
with db.transaction() as conn:
|
||
root = conn.root()
|
||
|
||
username = current_user.username
|
||
mailbox = root.get('messages', {}).get(username)
|
||
|
||
if not mailbox:
|
||
raise HTTPException(status_code=404, detail="Mailbox not found")
|
||
|
||
target_msg = None
|
||
for msg in mailbox:
|
||
if str(msg.msg_id) == msg_id:
|
||
target_msg = msg
|
||
break
|
||
|
||
if not target_msg:
|
||
raise HTTPException(status_code=404, detail="Message not found")
|
||
|
||
if target_msg.retrieved:
|
||
# Already marked – idempotent success
|
||
return {"status": "already_retrieved", "id": msg_id}
|
||
|
||
target_msg.retrieved = True
|
||
target_msg._p_changed = True
|
||
mailbox._p_changed = True
|
||
transaction.get().commit()
|
||
|
||
return {"status": "marked_retrieved", "id": msg_id}
|
||
|
||
@html_router.get("/messages", response_class=HTMLResponse)
|
||
async def message_list_page(
|
||
db: DbDependency,
|
||
request: Request,
|
||
type: str = Query("received", alias="msg_type"), # matches your filter links
|
||
limit: Optional[int] = Query(50, le=100),
|
||
current_user: HttpUser = Depends(get_current_http_user)
|
||
):
|
||
from packetserver.http.server import templates
|
||
# Directly call the existing API endpoint function
|
||
api_resp = await get_messages(db, current_user=current_user, type=type, limit=limit, since=None)
|
||
messages = api_resp["messages"]
|
||
|
||
return templates.TemplateResponse(
|
||
"message_list.html",
|
||
{
|
||
"request": request,
|
||
"messages": messages,
|
||
"msg_type": type,
|
||
"current_user": current_user.username
|
||
}
|
||
) |