|
# retoor <retoor@molodetz.nl>
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
from fastapi import APIRouter, Request
|
|
from fastapi.responses import RedirectResponse, JSONResponse, HTMLResponse
|
|
from devplacepy.database import get_table, db, paginate, resolve_object_url, _now_iso
|
|
from devplacepy.utils import generate_uid, require_user, time_ago
|
|
from devplacepy.seo import base_seo_context
|
|
from devplacepy.responses import respond
|
|
from devplacepy.schemas import SavedOut
|
|
from devplacepy.services.audit import record as audit
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
BOOKMARKABLE: set[str] = {"post", "gist", "project", "news"}
|
|
|
|
TABLE_BY_TYPE: dict[str, str] = {
|
|
"post": "posts",
|
|
"gist": "gists",
|
|
"project": "projects",
|
|
"news": "news",
|
|
}
|
|
|
|
LABEL_BY_TYPE: dict[str, str] = {
|
|
"post": "Post",
|
|
"gist": "Gist",
|
|
"project": "Project",
|
|
"news": "Article",
|
|
}
|
|
|
|
|
|
@router.get("/saved", response_class=HTMLResponse)
|
|
async def saved_page(request: Request, before: str = None):
|
|
user = require_user(request)
|
|
bookmarks = get_table("bookmarks")
|
|
rows, next_cursor = paginate(bookmarks, before=before, user_uid=user["uid"])
|
|
|
|
uids_by_type: dict[str, list] = {}
|
|
for row in rows:
|
|
uids_by_type.setdefault(row["target_type"], []).append(row["target_uid"])
|
|
|
|
resolved: dict[tuple, dict] = {}
|
|
for target_type, uids in uids_by_type.items():
|
|
table_name = TABLE_BY_TYPE.get(target_type)
|
|
if not table_name or table_name not in db.tables:
|
|
continue
|
|
table = get_table(table_name)
|
|
clauses = [table.table.columns.uid.in_(uids)]
|
|
if table.has_column("deleted_at"):
|
|
clauses.append(table.table.columns.deleted_at.is_(None))
|
|
for obj in table.find(*clauses):
|
|
resolved[(target_type, obj["uid"])] = obj
|
|
|
|
items = []
|
|
for row in rows:
|
|
obj = resolved.get((row["target_type"], row["target_uid"]))
|
|
if not obj:
|
|
continue
|
|
title = obj.get("title") or (obj.get("content", "") or "")[:80] or "Untitled"
|
|
items.append(
|
|
{
|
|
"target_type": row["target_type"],
|
|
"type_label": LABEL_BY_TYPE.get(
|
|
row["target_type"], row["target_type"].title()
|
|
),
|
|
"title": title,
|
|
"url": resolve_object_url(row["target_type"], row["target_uid"]),
|
|
"time_ago": time_ago(row["created_at"]),
|
|
}
|
|
)
|
|
|
|
seo_ctx = base_seo_context(
|
|
request,
|
|
title="Saved",
|
|
description="Your saved content on DevPlace.",
|
|
robots="noindex,nofollow",
|
|
)
|
|
return respond(
|
|
request,
|
|
"saved.html",
|
|
{
|
|
**seo_ctx,
|
|
"request": request,
|
|
"user": user,
|
|
"items": items,
|
|
"next_cursor": next_cursor,
|
|
},
|
|
model=SavedOut,
|
|
)
|
|
|
|
|
|
@router.post("/{target_type}/{target_uid}")
|
|
async def toggle_bookmark(request: Request, target_type: str, target_uid: str):
|
|
user = require_user(request)
|
|
if target_type not in BOOKMARKABLE:
|
|
return JSONResponse({"error": "Invalid target"}, status_code=400)
|
|
|
|
bookmarks = get_table("bookmarks")
|
|
existing = bookmarks.find_one(
|
|
user_uid=user["uid"], target_uid=target_uid, target_type=target_type
|
|
)
|
|
if existing and not existing.get("deleted_at"):
|
|
bookmarks.update(
|
|
{"id": existing["id"], "deleted_at": _now_iso(), "deleted_by": user["uid"]},
|
|
["id"],
|
|
)
|
|
saved = False
|
|
elif existing:
|
|
bookmarks.update(
|
|
{"id": existing["id"], "deleted_at": None, "deleted_by": None}, ["id"]
|
|
)
|
|
saved = True
|
|
else:
|
|
bookmarks.insert(
|
|
{
|
|
"uid": generate_uid(),
|
|
"user_uid": user["uid"],
|
|
"target_uid": target_uid,
|
|
"target_type": target_type,
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
"deleted_at": None,
|
|
"deleted_by": None,
|
|
}
|
|
)
|
|
saved = True
|
|
|
|
audit.record(
|
|
request,
|
|
"bookmark.add" if saved else "bookmark.remove",
|
|
user=user,
|
|
target_type=target_type,
|
|
target_uid=target_uid,
|
|
summary=f"{user['username']} {'bookmarked' if saved else 'removed bookmark from'} {target_type} {target_uid}",
|
|
links=[audit.target(target_type, target_uid)],
|
|
)
|
|
|
|
if request.headers.get("x-requested-with") == "fetch":
|
|
return JSONResponse({"saved": saved})
|
|
return RedirectResponse(
|
|
url=request.headers.get("Referer", "/feed"), status_code=302
|
|
)
|