feat: add per-user avatar seed regeneration with irreversible random avatar replacement

Implement a new `avatar_seed` column on the users table that overrides the username-based seed for Multiavatar generation. Introduce a null-safe `avatar_seed(user)` choke point in `avatar.py` that resolves `user.get("avatar_seed") or user.get("username")`, registered as a Jinja global so every render site (`_avatar_link.html`, `avatar_url(...)` calls, SEO `og_image`, issues ad-hoc dicts, devRant payload/PNG) propagates a regenerated seed. Add `POST /profile/{username}/regenerate-avatar` endpoint (owner-or-admin only) that writes a fresh `generate_uid()` to `avatar_seed`, invalidates the target's user cache, and audits `profile.avatar.regenerate`. The previous seed is overwritten and never stored, making regeneration irreversible. Document the feature in `AGENTS.md` and `README.md`, add the API endpoint to `docs_api.py`, and include the `regenerate_avatar` Devii tool in `CONFIRM_REQUIRED`.
This commit is contained in:
2026-06-27 22:31:34 +00:00
parent e773067106
commit 6f340a6818
60 changed files with 1626 additions and 97 deletions
+74
View File
@@ -594,3 +594,77 @@ def test_text_search_clause_still_matches_text_fields(local_db):
assert clause is not None
rows = list(posts.find(clause, deleted_at=None))
assert post_uid in {r["uid"] for r in rows}
def _notification(user_uid, target_url, read=False):
uid = generate_uid()
get_table("notifications").insert(
{
"uid": uid,
"user_uid": user_uid,
"type": "comment",
"message": "someone replied",
"related_uid": generate_uid(),
"target_url": target_url,
"read": read,
"created_at": _now_db_helpers(),
}
)
return uid
def _is_read(uid):
return bool(get_table("notifications").find_one(uid=uid)["read"])
def test_mark_notifications_marks_exact_url(local_db):
from devplacepy.database import mark_notifications_read_by_target
user = _user_db_helpers()
uid = _notification(user, "/posts/abc-post")
assert mark_notifications_read_by_target(user, "/posts/abc-post") == 1
assert _is_read(uid) is True
def test_mark_notifications_matches_anchor_prefix(local_db):
from devplacepy.database import mark_notifications_read_by_target
user = _user_db_helpers()
uid = _notification(user, "/posts/abc-post#comment-5")
assert mark_notifications_read_by_target(user, "/posts/abc-post") == 1
assert _is_read(uid) is True
def test_mark_notifications_leaves_other_targets(local_db):
from devplacepy.database import mark_notifications_read_by_target
user = _user_db_helpers()
keep = _notification(user, "/posts/other-post")
assert mark_notifications_read_by_target(user, "/posts/abc-post") == 0
assert _is_read(keep) is False
def test_mark_notifications_only_unread_counted(local_db):
from devplacepy.database import mark_notifications_read_by_target
user = _user_db_helpers()
_notification(user, "/posts/abc-post", read=True)
assert mark_notifications_read_by_target(user, "/posts/abc-post") == 0
def test_mark_notifications_empty_args(local_db):
from devplacepy.database import mark_notifications_read_by_target
user = _user_db_helpers()
assert mark_notifications_read_by_target("", "/posts/abc") == 0
assert mark_notifications_read_by_target(user, "") == 0
def test_mark_notifications_scoped_per_user(local_db):
from devplacepy.database import mark_notifications_read_by_target
owner = _user_db_helpers()
other = _user_db_helpers()
other_uid = _notification(other, "/posts/abc-post")
assert mark_notifications_read_by_target(owner, "/posts/abc-post") == 0
assert _is_read(other_uid) is False