forked from retoor/devplacepy
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:
+13
-41
@@ -1,14 +1,13 @@
|
||||
import logging
|
||||
from typing import Annotated
|
||||
from datetime import datetime, timezone
|
||||
from fastapi import APIRouter, Request, HTTPException, Form
|
||||
from fastapi import APIRouter, Request, Form
|
||||
from devplacepy.models import GistForm, GistEditForm
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
from devplacepy.database import get_table, get_users_by_uids, get_gist_languages
|
||||
from devplacepy.content import load_detail, edit_content_item, delete_content_item, enrich_items
|
||||
from devplacepy.content import load_detail, edit_content_item, delete_content_item, enrich_items, create_content_item, detail_context
|
||||
from devplacepy.templating import templates
|
||||
from devplacepy.utils import generate_uid, get_current_user, require_user, make_combined_slug, create_mention_notifications
|
||||
from devplacepy.seo import base_seo_context, site_url, website_schema, software_source_code_schema
|
||||
from devplacepy.utils import get_current_user, require_user, not_found, XP_GIST
|
||||
from devplacepy.seo import base_seo_context, site_url, website_schema, software_source_code_schema, list_page_seo
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
router = APIRouter()
|
||||
@@ -24,7 +23,7 @@ LANGUAGES = [
|
||||
]
|
||||
|
||||
|
||||
def get_gists_list(user_uid=None, language=None):
|
||||
def get_gists_list(user_uid=None, language=None, viewer=None):
|
||||
gists_table = get_table("gists")
|
||||
filters = {}
|
||||
if user_uid:
|
||||
@@ -38,16 +37,15 @@ def get_gists_list(user_uid=None, language=None):
|
||||
return []
|
||||
|
||||
users_map = get_users_by_uids([g["user_uid"] for g in all_gists])
|
||||
return enrich_items(all_gists, "gist", users_map)
|
||||
return enrich_items(all_gists, "gist", users_map, user=viewer)
|
||||
|
||||
|
||||
@router.get("", response_class=HTMLResponse)
|
||||
async def gists_page(request: Request, language: str = None, user_uid: str = None):
|
||||
user = get_current_user(request)
|
||||
gists_data = get_gists_list(user_uid, language)
|
||||
gists_data = get_gists_list(user_uid, language, viewer=user)
|
||||
total_count = len(gists_data)
|
||||
base = site_url(request)
|
||||
seo_ctx = base_seo_context(
|
||||
seo_ctx = list_page_seo(
|
||||
request,
|
||||
title="Gists",
|
||||
description=f"Browse {total_count} code snippets on DevPlace.",
|
||||
@@ -55,7 +53,6 @@ async def gists_page(request: Request, language: str = None, user_uid: str = Non
|
||||
{"name": "Home", "url": "/feed"},
|
||||
{"name": "Gists", "url": "/gists"},
|
||||
],
|
||||
schemas=[website_schema(base)],
|
||||
)
|
||||
return templates.TemplateResponse(request, "gists.html", {
|
||||
**seo_ctx,
|
||||
@@ -74,7 +71,7 @@ async def gist_detail(request: Request, gist_slug: str):
|
||||
user = get_current_user(request)
|
||||
detail = load_detail("gists", "gist", gist_slug, user)
|
||||
if not detail:
|
||||
raise HTTPException(status_code=404, detail="Gist not found")
|
||||
raise not_found("Gist not found")
|
||||
gist = detail["item"]
|
||||
|
||||
base = site_url(request)
|
||||
@@ -90,19 +87,9 @@ async def gist_detail(request: Request, gist_slug: str):
|
||||
],
|
||||
schemas=[website_schema(base), software_source_code_schema(gist, base)],
|
||||
)
|
||||
return templates.TemplateResponse(request, "gist_detail.html", {
|
||||
**seo_ctx,
|
||||
"request": request,
|
||||
"user": user,
|
||||
"gist": gist,
|
||||
"author": detail["author"],
|
||||
"is_owner": detail["is_owner"],
|
||||
"star_count": detail["star_count"],
|
||||
"time_ago": detail["time_ago"],
|
||||
"comments": detail["comments"],
|
||||
return templates.TemplateResponse(request, "gist_detail.html", detail_context(request, user, detail, "gist", seo_ctx, {
|
||||
"languages": LANGUAGES,
|
||||
"attachments": detail["attachments"],
|
||||
})
|
||||
}))
|
||||
|
||||
|
||||
@router.post("/create")
|
||||
@@ -117,27 +104,12 @@ async def create_gist(request: Request, data: Annotated[GistForm, Form()]):
|
||||
if language not in valid_languages:
|
||||
language = "plaintext"
|
||||
|
||||
gists = get_table("gists")
|
||||
uid = generate_uid()
|
||||
gist_slug = make_combined_slug(title, uid)
|
||||
gists.insert({
|
||||
"uid": uid,
|
||||
"user_uid": user["uid"],
|
||||
uid, gist_slug = create_content_item("gists", "gist", user, {
|
||||
"title": title,
|
||||
"slug": gist_slug,
|
||||
"description": description or None,
|
||||
"source_code": source_code,
|
||||
"language": language,
|
||||
"stars": 0,
|
||||
"created_at": datetime.now(timezone.utc).isoformat(),
|
||||
})
|
||||
|
||||
from devplacepy.attachments import link_attachments
|
||||
link_attachments(data.attachment_uids, "gist", uid)
|
||||
|
||||
create_mention_notifications(description or "", user["uid"], f"/gists/{gist_slug}")
|
||||
|
||||
logger.info(f"Gist {uid} created by {user['username']}")
|
||||
}, title, XP_GIST, "First Gist", description or "", data.attachment_uids)
|
||||
return RedirectResponse(url=f"/gists/{gist_slug}", status_code=302)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user