# retoor <retoor@molodetz.nl>
import logging
from datetime import datetime, timezone
from fastapi import APIRouter, Request
from devplacepy.database import (
get_table,
get_users_by_uids,
get_user_votes,
get_vote_counts,
get_user_stars,
)
from devplacepy.content import apply_vote, delete_comment_record, is_owner, is_admin
from devplacepy.services.audit import record as audit
from devplacepy.services.devrant.params import merge_params
from devplacepy.services.devrant.tokens import resolve_user
from devplacepy.services.devrant.ids import as_int, comment_by_id
from devplacepy.services.devrant.serializers import serialize_comment
from devplacepy.routers.devrant._shared import dr_ok, dr_error, unauthorized
logger = logging.getLogger(__name__)
router = APIRouter()
def _rant_id_for(comment: dict) -> int:
post = get_table("posts").find_one(uid=comment.get("target_uid"))
return int(post["id"]) if post else 0
def _serialize_single(comment: dict, viewer) -> dict:
authors = get_users_by_uids([comment["user_uid"]])
user_scores = {comment["user_uid"]: get_user_stars(comment["user_uid"])}
ups, downs = get_vote_counts([comment["uid"]])
score_map = {comment["uid"]: ups.get(comment["uid"], 0) - downs.get(comment["uid"], 0)}
my_votes = get_user_votes(viewer["uid"], [comment["uid"]]) if viewer else {}
return serialize_comment(
comment,
_rant_id_for(comment),
authors=authors,
user_scores=user_scores,
my_votes=my_votes,
score_map=score_map,
)
@router.get("/comments/{comment_id}")
async def get_comment(request: Request, comment_id: str):
params = await merge_params(request)
viewer = resolve_user(params)
comment = comment_by_id(comment_id)
if not comment:
return dr_error("Invalid comment specified in path.")
return dr_ok(comment=_serialize_single(comment, viewer))
@router.post("/comments/{comment_id}")
async def edit_comment(request: Request, comment_id: str):
params = await merge_params(request)
user = resolve_user(params)
if not user:
return unauthorized()
comment = comment_by_id(comment_id)
if not comment:
return dr_error("Invalid comment specified in path.")
if not is_owner(comment, user):
return dr_error("You can only edit your own comments.", fail_reason="not_owner")
text = (params.get("comment") or "").strip()
if len(text) < 1 or len(text) > 1000:
return dr_error("Invalid comment length.", fail_reason="length")
get_table("comments").update(
{
"uid": comment["uid"],
"content": text,
"updated_at": datetime.now(timezone.utc).isoformat(),
},
["uid"],
)
audit.record(
request,
"comment.edit",
user=user,
target_type="comment",
target_uid=comment["uid"],
origin="devrant",
summary=f"{user['username']} edited comment {comment['id']}",
links=[audit.target("comment", comment["uid"])],
)
return dr_ok()
@router.delete("/comments/{comment_id}")
async def delete_comment(request: Request, comment_id: str):
params = await merge_params(request)
user = resolve_user(params)
if not user:
return unauthorized()
comment = comment_by_id(comment_id)
if not comment:
return dr_error("Invalid comment specified in path.")
if not (is_owner(comment, user) or is_admin(user)):
return dr_error("You can only delete your own comments.")
delete_comment_record(request, user, comment)
return dr_ok()
@router.post("/comments/{comment_id}/vote")
async def vote_comment(request: Request, comment_id: str):
params = await merge_params(request)
user = resolve_user(params)
if not user:
return unauthorized()
comment = comment_by_id(comment_id)
if not comment:
return dr_error("Invalid comment specified in path.")
value = as_int(params.get("vote"))
if value not in (-1, 0, 1):
return dr_error("Invalid vote.")
apply_vote(request, user, "comment", comment["uid"], value)
return dr_ok()