import logging
from datetime import datetime, timedelta, timezone
from fastapi import APIRouter, Request
from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
from devplacepy.database import get_table, db
from devplacepy.templating import (
templates,
clear_unread_cache,
jinja_unread_count,
jinja_unread_messages,
)
from devplacepy.utils import require_user, get_current_user, time_ago
from devplacepy.seo import base_seo_context
logger = logging.getLogger(__name__)
router = APIRouter()
PAGE_SIZE = 25
def _group_label(created_at: str) -> str:
try:
dt = datetime.fromisoformat(created_at)
now = datetime.now(timezone.utc)
today = now.date()
date = dt.date()
if date == today:
return "Today"
if date == today - timedelta(days=1):
return "Yesterday"
if (today - date).days < 7:
return "This week"
return "Older"
except Exception:
return "Older"
@router.get("", response_class=HTMLResponse)
async def notifications_page(request: Request, before: str = None):
user = require_user(request)
next_cursor = None
try:
notifications_table = get_table("notifications")
filters = {"user_uid": user["uid"]}
if before:
filters["created_at"] = {"<": before}
raw_notifications = list(
notifications_table.find(**filters, order_by=["-created_at"], _limit=PAGE_SIZE + 1)
)
has_more = len(raw_notifications) > PAGE_SIZE
raw_notifications = raw_notifications[:PAGE_SIZE]
if has_more and raw_notifications:
next_cursor = raw_notifications[-1]["created_at"]
enriched = []
if raw_notifications:
from devplacepy.database import get_users_by_uids
actor_uids = [n.get("related_uid") for n in raw_notifications if n.get("related_uid")]
actors = get_users_by_uids(actor_uids)
else:
actors = {}
for n in raw_notifications:
enriched.append({
"notification": n,
"actor": actors.get(n.get("related_uid")),
"time_ago": time_ago(n["created_at"]),
})
groups = []
current_label = None
current_entries = []
for item in enriched:
label = _group_label(item["notification"]["created_at"])
if label != current_label:
if current_entries:
groups.append({"label": current_label, "entries": current_entries})
current_label = label
current_entries = []
current_entries.append(item)
if current_entries:
groups.append({"label": current_label, "entries": current_entries})
except Exception as e:
logger.exception(f"Error loading notifications: {e}")
groups = []
seo_ctx = base_seo_context(
request,
title="Notifications",
robots="noindex,nofollow",
breadcrumbs=[
{"name": "Home", "url": "/feed"},
{"name": "Notifications", "url": "/notifications"},
],
)
return templates.TemplateResponse(request, "notifications.html", {
**seo_ctx,
"request": request,
"user": user,
"notification_groups": groups,
"next_cursor": next_cursor,
})
@router.get("/counts")
async def unread_counts(request: Request):
user = get_current_user(request)
if not user:
return JSONResponse({"notifications": 0, "messages": 0})
return JSONResponse({
"notifications": jinja_unread_count(user["uid"]),
"messages": jinja_unread_messages(user["uid"]),
})
@router.get("/open/{notification_uid}")
async def open_notification(request: Request, notification_uid: str):
user = require_user(request)
notifications_table = get_table("notifications")
n = notifications_table.find_one(uid=notification_uid)
if not n or n["user_uid"] != user["uid"]:
return RedirectResponse(url="/notifications", status_code=302)
if not n["read"]:
notifications_table.update({"id": n["id"], "read": True}, ["id"])
clear_unread_cache(user["uid"])
return RedirectResponse(url=n.get("target_url") or "/notifications", status_code=302)
@router.post("/mark-read/{notification_uid}")
async def mark_read(request: Request, notification_uid: str):
user = require_user(request)
notifications_table = get_table("notifications")
n = notifications_table.find_one(uid=notification_uid)
if n and n["user_uid"] == user["uid"]:
notifications_table.update({"id": n["id"], "read": True}, ["id"])
clear_unread_cache(user["uid"])
return RedirectResponse(url="/notifications", status_code=302)
@router.post("/mark-all-read")
async def mark_all_read(request: Request):
user = require_user(request)
with db:
db.query("UPDATE notifications SET read = 1 WHERE user_uid = :u AND read = 0", u=user["uid"])
clear_unread_cache(user["uid"])
return RedirectResponse(url="/notifications", status_code=302)