# retoor import logging import secrets from datetime import datetime, timezone from fastapi import APIRouter, Request from devplacepy.database import get_table from devplacepy.utils import XP_POST from devplacepy.content import ( create_content_item, delete_content_item, apply_vote, create_comment_record, set_bookmark, is_owner, is_admin, ) from devplacepy.services.audit import record as audit from devplacepy.services.correction import schedule_correction from devplacepy.services.ai_modifier import schedule_modification from devplacepy.services.devrant.params import merge_params from devplacepy.services.devrant.tokens import resolve_user from devplacepy.services.devrant.ids import as_int, post_by_id from devplacepy.services.devrant.feed import list_rants, search_rants, load_rant_detail from devplacepy.services.devrant.serializers import encode_tags from devplacepy.routers.devrant._shared import dr_ok, dr_error, unauthorized logger = logging.getLogger(__name__) router = APIRouter() DEFAULT_LIMIT = 20 MAX_LIMIT = 50 def _parse_tags(raw: object) -> list: if not raw: return [] if isinstance(raw, list): return [str(tag).strip() for tag in raw if str(tag).strip()] return [tag.strip() for tag in str(raw).split(",") if tag.strip()] @router.get("/devrant/rants") async def rant_feed(request: Request): params = await merge_params(request) viewer = resolve_user(params) sort = params.get("sort") or "recent" limit = min(MAX_LIMIT, max(1, as_int(params.get("limit"), DEFAULT_LIMIT))) skip = max(0, as_int(params.get("skip"), 0)) rants = list_rants(sort, limit, skip, viewer) num_notifs = 0 if viewer: num_notifs = get_table("notifications").count( user_uid=viewer["uid"], read=False ) return dr_ok( rants=rants, settings={"notif_state": -1, "notif_token": ""}, set=secrets.token_hex(6), wrw=0, dpp=0, num_notifs=num_notifs, unread={"total": num_notifs}, news=None, ) @router.get("/devrant/search") async def search(request: Request): params = await merge_params(request) viewer = resolve_user(params) term = (params.get("term") or "").strip() return dr_ok(results=search_rants(term, viewer) if term else []) @router.post("/devrant/rants") async def create_rant(request: Request): params = await merge_params(request) user = resolve_user(params) if not user: return unauthorized() text = (params.get("rant") or "").strip() if len(text) < 1: return dr_error("Your rant is too short.") if len(text) > 125000: return dr_error("Your rant is too long.") tags = _parse_tags(params.get("tags")) uid, slug = create_content_item( "posts", "post", user, { "title": None, "content": text, "topic": "rant", "project_uid": None, "image": None, "tags": encode_tags(tags), }, text[:50], XP_POST, "First Post", text, None, request, ) post = get_table("posts").find_one(uid=uid) return dr_ok(rant_id=int(post["id"])) @router.get("/devrant/rants/{rant_id}") async def get_rant(request: Request, rant_id: str): params = await merge_params(request) viewer = resolve_user(params) post = post_by_id(rant_id) if not post: return dr_error("This rant does not exist.") detail = load_rant_detail(post, viewer) return dr_ok(rant=detail["rant"], comments=detail["comments"], subscribed=0) @router.post("/devrant/rants/{rant_id}") async def edit_rant(request: Request, rant_id: str): params = await merge_params(request) user = resolve_user(params) if not user: return unauthorized() post = post_by_id(rant_id) if not post: return dr_error("This rant does not exist.", fail_reason="not_found") if not is_owner(post, user): return dr_error("You can only edit your own rants.", fail_reason="not_owner") text = (params.get("rant") or "").strip() if len(text) < 1 or len(text) > 125000: return dr_error("Invalid rant length.", fail_reason="length") tags = _parse_tags(params.get("tags")) get_table("posts").update( { "uid": post["uid"], "content": text, "tags": encode_tags(tags), "updated_at": datetime.now(timezone.utc).isoformat(), }, ["uid"], ) schedule_correction(user, "posts", post["uid"], request) schedule_modification(user, "posts", post["uid"], request) audit.record( request, "post.edit", user=user, target_type="post", target_uid=post["uid"], origin="devrant", summary=f"{user['username']} edited rant {post['id']}", links=[audit.target("post", post["uid"])], ) return dr_ok() @router.delete("/devrant/rants/{rant_id}") async def delete_rant(request: Request, rant_id: str): params = await merge_params(request) user = resolve_user(params) if not user: return unauthorized() post = post_by_id(rant_id) if not post: return dr_error("This rant does not exist.") if not (is_owner(post, user) or is_admin(user)): return dr_error("You can only delete your own rants.") delete_content_item(request, "posts", "post", user, post["slug"], "/feed") return dr_ok() @router.post("/devrant/rants/{rant_id}/vote") async def vote_rant(request: Request, rant_id: str): params = await merge_params(request) user = resolve_user(params) if not user: return unauthorized() post = post_by_id(rant_id) if not post: return dr_error("This rant does not exist.") value = as_int(params.get("vote")) if value not in (-1, 0, 1): return dr_error("Invalid vote.") apply_vote(request, user, "post", post["uid"], value) fresh = get_table("posts").find_one(uid=post["uid"]) detail = load_rant_detail(fresh, user) return dr_ok(rant=detail["rant"]) @router.post("/devrant/rants/{rant_id}/favorite") async def favorite_rant(request: Request, rant_id: str): return await _set_favorite(request, rant_id, True) @router.post("/devrant/rants/{rant_id}/unfavorite") async def unfavorite_rant(request: Request, rant_id: str): return await _set_favorite(request, rant_id, False) async def _set_favorite(request: Request, rant_id: str, saved: bool): params = await merge_params(request) user = resolve_user(params) if not user: return unauthorized() post = post_by_id(rant_id) if not post: return dr_error("This rant does not exist.") set_bookmark(request, user, "post", post["uid"], saved) return dr_ok() @router.post("/devrant/rants/{rant_id}/comments") async def comment_rant(request: Request, rant_id: str): params = await merge_params(request) user = resolve_user(params) if not user: return unauthorized() post = post_by_id(rant_id) if not post: return dr_error("This rant does not exist.") text = (params.get("comment") or "").strip() if len(text) < 1: return dr_error("Your comment is too short.") if len(text) > 1000: return dr_error("Your comment is too long.") create_comment_record(request, user, "post", post["uid"], text) return dr_ok()