forked from retoor/devplacepy
fix: correct "bugs" to "issues" in routing table and README references across multiple documentation files
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from devplacepy.routers.devrant._shared import ensure_enabled
|
||||
from devplacepy.routers.devrant import auth, rants, comments, notifs
|
||||
|
||||
router = APIRouter(dependencies=[Depends(ensure_enabled)])
|
||||
router.include_router(auth.router)
|
||||
router.include_router(rants.router)
|
||||
router.include_router(comments.router)
|
||||
router.include_router(notifs.router)
|
||||
@@ -0,0 +1,37 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import HTTPException
|
||||
from fastapi.responses import JSONResponse
|
||||
|
||||
from devplacepy.database import get_setting
|
||||
|
||||
|
||||
def api_enabled() -> bool:
|
||||
return get_setting("devrant_api_enabled", "1") == "1"
|
||||
|
||||
|
||||
def ensure_enabled() -> None:
|
||||
if not api_enabled():
|
||||
raise HTTPException(status_code=404)
|
||||
|
||||
|
||||
def dr_json(payload: dict, status_code: int = 200) -> JSONResponse:
|
||||
return JSONResponse(payload, status_code=status_code)
|
||||
|
||||
|
||||
def dr_ok(**fields) -> JSONResponse:
|
||||
return dr_json({"success": True, **fields})
|
||||
|
||||
|
||||
def dr_error(message: str, status_code: int = 200, **extra) -> JSONResponse:
|
||||
return dr_json({"success": False, "error": message, **extra}, status_code=status_code)
|
||||
|
||||
|
||||
def unauthorized() -> JSONResponse:
|
||||
return dr_error("Authentication required.", 401)
|
||||
|
||||
|
||||
def not_found(message: str = "Not found.") -> Optional[JSONResponse]:
|
||||
return dr_error(message)
|
||||
@@ -0,0 +1,222 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import logging
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import Response
|
||||
|
||||
from devplacepy.cache import TTLCache
|
||||
from devplacepy.config import SECONDS_PER_DAY
|
||||
from devplacepy.database import get_table, get_setting
|
||||
from devplacepy.utils import verify_password, register_account
|
||||
from devplacepy.services.audit import record as audit
|
||||
from devplacepy.services.devrant.params import merge_params
|
||||
from devplacepy.services.devrant.tokens import issue_token, resolve_user, revoke_all
|
||||
from devplacepy.services.devrant.profile import build_profile
|
||||
from devplacepy.services.devrant.ids import user_by_id
|
||||
from devplacepy.services.devrant.avatar import render_png
|
||||
from devplacepy.routers.devrant._shared import dr_ok, dr_error, unauthorized
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
USERNAME_PATTERN = re.compile(r"^[A-Za-z0-9_-]{3,32}$")
|
||||
PASSWORD_MIN = 6
|
||||
|
||||
_avatar_cache = TTLCache(ttl=SECONDS_PER_DAY, max_size=4096)
|
||||
|
||||
|
||||
@router.post("/users/auth-token")
|
||||
async def auth_token(request: Request):
|
||||
params = await merge_params(request)
|
||||
identifier = (params.get("username") or "").strip()
|
||||
password = params.get("password") or ""
|
||||
invalid = "Invalid login credentials entered. Please try again."
|
||||
if not identifier or not password:
|
||||
return dr_error(invalid, 400)
|
||||
users = get_table("users")
|
||||
user = users.find_one(username=identifier) or users.find_one(
|
||||
email=identifier.lower()
|
||||
)
|
||||
if (
|
||||
not user
|
||||
or not user.get("is_active", True)
|
||||
or not verify_password(password, user["password_hash"])
|
||||
):
|
||||
audit.record(
|
||||
request,
|
||||
"auth.login.failure",
|
||||
user=None,
|
||||
actor_kind="guest",
|
||||
result="failure",
|
||||
origin="devrant",
|
||||
metadata={"identifier": identifier},
|
||||
summary=f"failed devrant login for {identifier}",
|
||||
)
|
||||
return dr_error(invalid, 400)
|
||||
token = issue_token(user)
|
||||
logger.info("devrant login for %s", user["username"])
|
||||
audit.record(
|
||||
request,
|
||||
"auth.login.success",
|
||||
user=user,
|
||||
target_type="user",
|
||||
target_uid=user["uid"],
|
||||
target_label=user["username"],
|
||||
origin="devrant",
|
||||
summary=f"user {user['username']} logged in via devrant",
|
||||
links=[audit.target("user", user["uid"], user["username"])],
|
||||
)
|
||||
return dr_ok(auth_token=token)
|
||||
|
||||
|
||||
@router.post("/users")
|
||||
async def register(request: Request):
|
||||
params = await merge_params(request)
|
||||
username = (params.get("username") or "").strip()
|
||||
email = (params.get("email") or "").strip().lower()
|
||||
password = params.get("password") or ""
|
||||
if get_setting("registration_open", "1") != "1":
|
||||
return dr_error("Registration is closed.", error_field="username")
|
||||
if not USERNAME_PATTERN.match(username):
|
||||
return dr_error(
|
||||
"Username must be 3-32 letters, numbers, hyphens or underscores.",
|
||||
error_field="username",
|
||||
)
|
||||
if "@" not in email:
|
||||
return dr_error("Please enter a valid email address.", error_field="email")
|
||||
if len(password) < PASSWORD_MIN:
|
||||
return dr_error(
|
||||
"Password must be at least 6 characters.", error_field="password"
|
||||
)
|
||||
users = get_table("users")
|
||||
if users.find_one(username=username):
|
||||
return dr_error("That username is already taken.", error_field="username")
|
||||
if users.find_one(email=email):
|
||||
return dr_error("That email is already registered.", error_field="email")
|
||||
uid, role, is_first = register_account(username, email, password)
|
||||
logger.info("devrant registration for %s", username)
|
||||
audit.record(
|
||||
request,
|
||||
"auth.signup",
|
||||
user={"uid": uid, "username": username, "role": role},
|
||||
target_type="user",
|
||||
target_uid=uid,
|
||||
target_label=username,
|
||||
new_value=role,
|
||||
origin="devrant",
|
||||
metadata={"first_user": is_first},
|
||||
summary=f"user {username} registered via devrant",
|
||||
links=[audit.target("user", uid, username)],
|
||||
)
|
||||
user = users.find_one(uid=uid)
|
||||
return dr_ok(auth_token=issue_token(user))
|
||||
|
||||
|
||||
@router.get("/get-user-id")
|
||||
async def get_user_id(request: Request):
|
||||
params = await merge_params(request)
|
||||
username = (params.get("username") or "").strip()
|
||||
user = get_table("users").find_one(username=username) if username else None
|
||||
if not user:
|
||||
return dr_error("User not found.")
|
||||
return dr_ok(user_id=int(user["id"]))
|
||||
|
||||
|
||||
@router.get("/users/{user_id}")
|
||||
async def profile(request: Request, user_id: str):
|
||||
params = await merge_params(request)
|
||||
user = user_by_id(user_id)
|
||||
if not user:
|
||||
return dr_error("User not found.")
|
||||
viewer = resolve_user(params)
|
||||
return dr_ok(profile=build_profile(user, viewer))
|
||||
|
||||
|
||||
@router.post("/users/me/edit-profile")
|
||||
async def edit_profile(request: Request):
|
||||
params = await merge_params(request)
|
||||
user = resolve_user(params)
|
||||
if not user:
|
||||
return unauthorized()
|
||||
updates = {"uid": user["uid"]}
|
||||
field_map = {
|
||||
"profile_about": "bio",
|
||||
"profile_location": "location",
|
||||
"profile_github": "git_link",
|
||||
"profile_website": "website",
|
||||
}
|
||||
for source, column in field_map.items():
|
||||
if source in params:
|
||||
updates[column] = (params.get(source) or "")[:500]
|
||||
if len(updates) > 1:
|
||||
get_table("users").update(updates, ["uid"])
|
||||
audit.record(
|
||||
request,
|
||||
"user.profile.edit",
|
||||
user=user,
|
||||
target_type="user",
|
||||
target_uid=user["uid"],
|
||||
target_label=user["username"],
|
||||
origin="devrant",
|
||||
summary=f"{user['username']} edited profile via devrant",
|
||||
links=[audit.target("user", user["uid"], user["username"])],
|
||||
)
|
||||
return dr_ok()
|
||||
|
||||
|
||||
@router.post("/users/forgot-password")
|
||||
async def forgot_password(request: Request):
|
||||
await merge_params(request)
|
||||
return dr_ok()
|
||||
|
||||
|
||||
@router.post("/users/me/mark-news-read")
|
||||
async def mark_news_read(request: Request):
|
||||
await merge_params(request)
|
||||
return dr_ok()
|
||||
|
||||
|
||||
@router.post("/users/me/resend-confirm")
|
||||
async def resend_confirm(request: Request):
|
||||
await merge_params(request)
|
||||
return dr_ok()
|
||||
|
||||
|
||||
@router.delete("/users/me")
|
||||
async def delete_account(request: Request):
|
||||
params = await merge_params(request)
|
||||
user = resolve_user(params)
|
||||
if not user:
|
||||
return unauthorized()
|
||||
get_table("users").update(
|
||||
{"uid": user["uid"], "is_active": False}, ["uid"]
|
||||
)
|
||||
revoke_all(user["uid"])
|
||||
logger.info("devrant account deactivated for %s", user["username"])
|
||||
audit.record(
|
||||
request,
|
||||
"auth.account.disable",
|
||||
user=user,
|
||||
target_type="user",
|
||||
target_uid=user["uid"],
|
||||
target_label=user["username"],
|
||||
origin="devrant",
|
||||
summary=f"{user['username']} deactivated account via devrant",
|
||||
links=[audit.target("user", user["uid"], user["username"])],
|
||||
)
|
||||
return dr_ok()
|
||||
|
||||
|
||||
@router.get("/avatars/u/{seed}.png")
|
||||
async def avatar_image(request: Request, seed: str, size: int = 128):
|
||||
size = max(16, min(512, size))
|
||||
cache_key = f"{seed}:{size}"
|
||||
png = _avatar_cache.get(cache_key)
|
||||
if png is None:
|
||||
png = render_png(seed, size)
|
||||
_avatar_cache.set(cache_key, png)
|
||||
headers = {"Cache-Control": f"public, max-age={SECONDS_PER_DAY}, immutable"}
|
||||
return Response(content=png, media_type="image/png", headers=headers)
|
||||
@@ -0,0 +1,121 @@
|
||||
# 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()
|
||||
@@ -0,0 +1,32 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
import logging
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
|
||||
from devplacepy.services.devrant.params import merge_params
|
||||
from devplacepy.services.devrant.tokens import resolve_user
|
||||
from devplacepy.services.devrant.notifications import build_notif_feed, clear_notifications
|
||||
from devplacepy.routers.devrant._shared import dr_ok, unauthorized
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/users/me/notif-feed")
|
||||
async def notif_feed(request: Request):
|
||||
params = await merge_params(request)
|
||||
user = resolve_user(params)
|
||||
if not user:
|
||||
return unauthorized()
|
||||
return dr_ok(data=build_notif_feed(user))
|
||||
|
||||
|
||||
@router.delete("/users/me/notif-feed")
|
||||
async def clear_notif_feed(request: Request):
|
||||
params = await merge_params(request)
|
||||
user = resolve_user(params)
|
||||
if not user:
|
||||
return unauthorized()
|
||||
clear_notifications(user)
|
||||
return dr_ok()
|
||||
@@ -0,0 +1,229 @@
|
||||
# retoor <retoor@molodetz.nl>
|
||||
|
||||
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.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"],
|
||||
)
|
||||
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()
|
||||
Reference in New Issue
Block a user