|
# retoor <retoor@molodetz.nl>
|
|
import logging
|
|
from typing import Annotated
|
|
from datetime import datetime, timezone
|
|
from fastapi import Depends, APIRouter, Request
|
|
from fastapi.responses import RedirectResponse, JSONResponse
|
|
from devplacepy.database import get_table, db, _now_iso
|
|
from devplacepy.utils import generate_uid, require_user, track_action, redirect_back
|
|
from devplacepy.models import ReactionForm
|
|
from devplacepy.services.audit import record as audit
|
|
from devplacepy.dependencies import json_or_form
|
|
|
|
logger = logging.getLogger(__name__)
|
|
router = APIRouter()
|
|
|
|
REACTABLE: set[str] = {"post", "comment", "gist", "project", "quiz"}
|
|
|
|
@router.post("/{target_type}/{target_uid}")
|
|
async def react(
|
|
request: Request,
|
|
target_type: str,
|
|
target_uid: str,
|
|
data: Annotated[ReactionForm, Depends(json_or_form(ReactionForm))],
|
|
):
|
|
user = require_user(request)
|
|
if target_type not in REACTABLE:
|
|
return JSONResponse({"error": "Invalid target"}, status_code=400)
|
|
|
|
emoji = data.emoji
|
|
reactions = get_table("reactions")
|
|
existing = reactions.find_one(
|
|
user_uid=user["uid"],
|
|
target_uid=target_uid,
|
|
target_type=target_type,
|
|
emoji=emoji,
|
|
)
|
|
if existing and existing.get("deleted_at"):
|
|
reactions.update(
|
|
{"id": existing["id"], "deleted_at": None, "deleted_by": None}, ["id"]
|
|
)
|
|
added = True
|
|
elif existing:
|
|
reactions.update(
|
|
{"id": existing["id"], "deleted_at": _now_iso(), "deleted_by": user["uid"]},
|
|
["id"],
|
|
)
|
|
added = False
|
|
else:
|
|
reactions.insert(
|
|
{
|
|
"uid": generate_uid(),
|
|
"user_uid": user["uid"],
|
|
"target_uid": target_uid,
|
|
"target_type": target_type,
|
|
"emoji": emoji,
|
|
"created_at": datetime.now(timezone.utc).isoformat(),
|
|
"deleted_at": None,
|
|
"deleted_by": None,
|
|
}
|
|
)
|
|
added = True
|
|
|
|
reaction_event = "reaction.add" if added else "reaction.remove"
|
|
if added:
|
|
track_action(user["uid"], "reaction")
|
|
|
|
audit.record(
|
|
request,
|
|
reaction_event,
|
|
user=user,
|
|
target_type=target_type,
|
|
target_uid=target_uid,
|
|
metadata={"emoji": emoji},
|
|
summary=f"{user['username']} {'added' if added else 'removed'} {emoji} on {target_type} {target_uid}",
|
|
links=[audit.target(target_type, target_uid)],
|
|
)
|
|
|
|
counts = {
|
|
row["emoji"]: row["c"]
|
|
for row in db.query(
|
|
"SELECT emoji, COUNT(*) as c FROM reactions WHERE target_type=:tt AND target_uid=:tu AND deleted_at IS NULL GROUP BY emoji",
|
|
tt=target_type,
|
|
tu=target_uid,
|
|
)
|
|
}
|
|
mine = [
|
|
row["emoji"]
|
|
for row in reactions.find(
|
|
user_uid=user["uid"],
|
|
target_uid=target_uid,
|
|
target_type=target_type,
|
|
deleted_at=None,
|
|
)
|
|
]
|
|
|
|
if request.headers.get("x-requested-with") == "fetch":
|
|
return JSONResponse({"counts": counts, "mine": mine})
|
|
return RedirectResponse(url=redirect_back(request), status_code=302)
|