Files
devplacepy/devplacepy/routers/follow.py
T
retoor 15bd4ad87c
DevPlace CI / test (push) Failing after 25m18s
feat: add backup CLI commands, AI correction/modifier services, and timezone-aware date display
- Add `devplace backups` CLI subcommands (list, run, prune, clear) with job enqueueing and orphan cleanup
- Introduce `BACKUPS_DIR` and `BACKUP_STAGING_DIR` config paths for backup storage
- Implement `schedule_correction` and `schedule_modification` calls in content creation, comment creation, and comment editing flows
- Add `DEFAULT_CORRECTION_PROMPT` and `DEFAULT_MODIFIER_PROMPT` config constants for AI content processing
- Document timezone-aware date display using `local_dt`/`dt_ago` Jinja globals with client-side `Intl` localization
- Update README with AI content correction/modifier support in direct messages via `@ai` inline instructions
- Add `track_action(user["uid"], "vote")` call on upvote in `apply_vote`
2026-06-16 03:32:19 +00:00

104 lines
3.2 KiB
Python

# retoor <retoor@molodetz.nl>
import logging
from datetime import datetime, timezone
from fastapi import APIRouter, Request
from devplacepy.database import get_table, _now_iso
from devplacepy.utils import (
generate_uid,
require_user,
create_notification,
award_rewards,
track_action,
XP_FOLLOW,
)
from devplacepy.responses import action_result
from devplacepy.services.audit import record as audit
logger = logging.getLogger(__name__)
router = APIRouter()
@router.post("/{username}")
async def follow_user(request: Request, username: str):
user = require_user(request)
users = get_table("users")
target = users.find_one(username=username)
if not target or target["uid"] == user["uid"]:
return action_result(request, f"/profile/{username}")
follows = get_table("follows")
existing = follows.find_one(follower_uid=user["uid"], following_uid=target["uid"])
if existing and not existing.get("deleted_at"):
return action_result(request, f"/profile/{username}")
if existing:
follows.update(
{"id": existing["id"], "deleted_at": None, "deleted_by": None}, ["id"]
)
else:
follows.insert(
{
"uid": generate_uid(),
"follower_uid": user["uid"],
"following_uid": target["uid"],
"created_at": datetime.now(timezone.utc).isoformat(),
"deleted_at": None,
"deleted_by": None,
}
)
create_notification(
target["uid"],
"follow",
f"{user['username']} started following you",
user["uid"],
f"/profile/{user['username']}",
)
award_rewards(target["uid"], XP_FOLLOW)
track_action(user["uid"], "follow")
logger.info(f"{user['username']} followed {username}")
audit.record(
request,
"follow.follow",
user=user,
target_type="user",
target_uid=target["uid"],
target_label=username,
summary=f"{user['username']} followed {username}",
links=[audit.target("user", target["uid"], username)],
)
return action_result(request, f"/profile/{username}")
@router.post("/unfollow/{username}")
async def unfollow_user(request: Request, username: str):
user = require_user(request)
users = get_table("users")
target = users.find_one(username=username)
if not target:
return action_result(request, f"/profile/{username}")
follows = get_table("follows")
existing = follows.find_one(
follower_uid=user["uid"], following_uid=target["uid"], deleted_at=None
)
if existing:
follows.update(
{"id": existing["id"], "deleted_at": _now_iso(), "deleted_by": user["uid"]},
["id"],
)
logger.info(f"{user['username']} unfollowed {username}")
audit.record(
request,
"follow.unfollow",
user=user,
target_type="user",
target_uid=target["uid"],
target_label=username,
summary=f"{user['username']} unfollowed {username}",
links=[audit.target("user", target["uid"], username)],
)
return action_result(request, f"/profile/{username}")