From 76d73ccaea4d9b10d90823b79091c2234a3f24e2 Mon Sep 17 00:00:00 2001 From: typosaurus Date: Sun, 26 Jul 2026 23:06:28 +0000 Subject: [PATCH] feat(nadia): @nadia: Implement xp_next_level and xp_progress_pct fields in UserOut schema and MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Outcome: done Changed: devplacepy/schemas/content.py:20-21, devplacepy/schemas/profile.py:75-76, devplacepy/routers/profile/index.py:140-146,448-449, devplacepy/templates/profile.html:45,48 Verified by: `python3 -m py_compile` on each modified Python file — all pass. Full `make test` is blocked by Python 3.11 (project requires >=3.12) in this environment; individual syntax verification confirms all three Python files compile clean. Findings: - UserOut (devplacepy/schemas/content.py:20-21) now has xp_progress_pct: Optional[int] and xp_next_level: Optional[int] fields. - ProfileOut (devplacepy/schemas/profile.py:75-76) now has xp_next_level: int = 0 and xp_progress_pct: int = 0 fields. - Profile route (devplacepy/routers/profile/index.py:140-146) computes xp_next_level = level * 100 and xp_progress_pct = xp % 100, both passed through ctx (lines 448-449). - Profile template (devplacepy/templates/profile.html:45,48) uses xp_progress_pct variable instead of inline computation. - Full make test cannot run due to Python 3.11 (project requires >=3.12) in this environment — unresolved. Open: The full test suite (`make test`) cannot be executed because the workspace provides Python 3.11 while the project requires >=3.12. This is an environment constraint, not a code defect. If a Python 3.12+ runtime becomes available, `make test` must pass before the change is confirmed complete. Confidence: high - all three modified Python files compile cleanly via py_compile. The Jinja template chang Typosaurus-Run: a6697d32c4ea49b4a9767bd5a8c1119f Typosaurus-Node: 88465fc244944889b6dbcf2864cc0b79 Typosaurus-Agent: @nadia Refs: #112 --- devplacepy/routers/profile/index.py | 11 +++++++++++ devplacepy/schemas/content.py | 3 +++ devplacepy/schemas/profile.py | 3 +++ devplacepy/templates/profile.html | 7 +++++-- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/devplacepy/routers/profile/index.py b/devplacepy/routers/profile/index.py index d4e8fc4c..5c958e37 100644 --- a/devplacepy/routers/profile/index.py +++ b/devplacepy/routers/profile/index.py @@ -46,6 +46,7 @@ from devplacepy.utils import ( track_action, build_achievements, ) +from devplacepy.utils.rewards import LEVEL_XP from devplacepy.responses import respond, action_result, wants_json from devplacepy.schemas import ProfileOut from devplacepy.avatar import avatar_url, avatar_seed @@ -139,6 +140,10 @@ async def profile_page( current_user["uid"], f"/profile/{profile_user['username']}" ) profile_user["stars"] = get_user_stars(profile_user["uid"]) + xp_raw = profile_user.get("xp") or 0 + level_raw = profile_user.get("level") or 1 + xp_progress_pct = xp_raw % LEVEL_XP + xp_next_level = level_raw * LEVEL_XP rank = get_user_rank(profile_user["uid"]) follow_counts = get_follow_counts(profile_user["uid"]) @@ -440,6 +445,8 @@ async def profile_page( "awards_count": awards_count, "prominent_award": prominent_award, "can_give_award": can_give, + "xp_next_level": xp_next_level, + "xp_progress_pct": xp_progress_pct, }, model=ProfileOut, ) @@ -499,3 +506,7 @@ async def regenerate_api_key(request: Request): links=[audit.target("user", user["uid"], user["username"])], ) return JSONResponse({"api_key": new_key}) + + + + diff --git a/devplacepy/schemas/content.py b/devplacepy/schemas/content.py index 860cd0ee..14ef907a 100644 --- a/devplacepy/schemas/content.py +++ b/devplacepy/schemas/content.py @@ -17,6 +17,8 @@ class UserOut(_Out): website: Optional[str] = None level: Optional[int] = None xp: Optional[int] = None + xp_progress_pct: Optional[int] = None + xp_next_level: Optional[int] = None stars: Optional[int] = None created_at: Optional[str] = None last_seen: Optional[str] = None @@ -202,3 +204,4 @@ class MessageOut(_Out): CommentItemOut.model_rebuild() + diff --git a/devplacepy/schemas/profile.py b/devplacepy/schemas/profile.py index d19d4465..3fc3a4a2 100644 --- a/devplacepy/schemas/profile.py +++ b/devplacepy/schemas/profile.py @@ -72,6 +72,8 @@ class ProfileOut(_Out): followers_count: Optional[int] = None following_count: Optional[int] = None viewer_is_admin: bool = False + xp_next_level: int = 0 + xp_progress_pct: int = 0 media: list[MediaItemOut] = [] media_pagination: Optional[Any] = None notification_prefs: list[Any] = [] @@ -88,3 +90,4 @@ class TelegramPairOut(_Out): code: Optional[str] = None expires_at: Optional[str] = None ttl_minutes: Optional[int] = None + diff --git a/devplacepy/templates/profile.html b/devplacepy/templates/profile.html index c24562b1..11bb2204 100644 --- a/devplacepy/templates/profile.html +++ b/devplacepy/templates/profile.html @@ -42,10 +42,10 @@
Progress to next level - {{ (profile_user.get('xp') or 0) % 100 }}% + {{ xp_progress_pct }}%
-
+
@@ -765,3 +765,6 @@ import { AwardGiver } from "{{ static_url('/static/js/AwardGiver.js') }}"; new AwardGiver(); {% endblock %} + + +