feat: add leaderboard router, gamification backfill, and shared content creation helpers

Introduce a new `/leaderboard` endpoint ranking top 50 members by total stars, wire it into the app router and documentation. Implement `_backfill_gamification()` in `database.py` to compute XP/levels for existing accounts from prior posts, comments, votes, and follows. Extract `is_owner()`, `create_content_item()`, and `detail_context()` into `content.py` to centralize content creation, reward awarding, mention notifications, and attachment linking. Update comment creation/deletion in `comments.py` to use `award_rewards()` with `XP_COMMENT` and the new `is_owner()` helper. Add shared template partials documentation for vote bars, star buttons, post headers, and topic selectors to `AGENTS.md`.
This commit is contained in:
2026-05-30 18:16:39 +00:00
parent ab4861562b
commit a136287d36
57 changed files with 1310 additions and 674 deletions
+4 -12
View File
@@ -1,4 +1,5 @@
{% extends "base.html" %}
{% from "_macros.html" import modal %}
{% block extra_head %}
<link rel="stylesheet" href="/static/css/gists.css">
<link rel="stylesheet" href="/static/css/post.css">
@@ -45,10 +46,7 @@
<div class="gist-detail-actions">
<button type="button" class="gist-star-btn" data-share="/gists/{{ gist['slug'] or gist['uid'] }}">&#x1F517; Share</button>
{% if user %}
<form method="POST" action="/votes/gist/{{ gist['uid'] }}" style="display:inline;">
<input type="hidden" name="value" value="1">
<button type="submit" class="gist-star-btn">&#x2606; <span class="vote-count-value" data-vote-count="{{ gist['uid'] }}">{{ star_count }}</span></button>
</form>
{% set _type = "gist" %}{% set _uid = gist['uid'] %}{% set _my_vote = my_vote %}{% set _count = star_count %}{% set _btn_class = "gist-star-btn" %}{% include "_star_vote.html" %}
{% endif %}
{% if is_owner %}
<button class="gist-star-btn" data-modal="edit-gist-modal"><span class="icon">&#x270F;&#xFE0F;</span>Edit</button>
@@ -60,12 +58,7 @@
</article>
{% if is_owner %}
<div class="modal-overlay" id="edit-gist-modal">
<div class="card modal-card modal-card-wide">
<div class="modal-header">
<h3>Edit Gist</h3>
<button type="button" class="modal-close btn-ghost btn-icon modal-close-btn">&times;</button>
</div>
{% call modal('edit-gist-modal', 'Edit Gist', wide=true) %}
<form method="POST" action="/gists/edit/{{ gist['slug'] or gist['uid'] }}">
<div class="auth-field auth-field-gap">
<label for="edit-gist-title">Title</label>
@@ -93,8 +86,7 @@
<button type="submit" class="btn btn-primary"><span class="icon">&#x1F4BE;</span>Save Changes</button>
</div>
</form>
</div>
</div>
{% endcall %}
{% endif %}
{% with target_uid=gist['uid'], target_type="gist" %}