Compare commits

..
Author SHA1 Message Date
Typosaurus 6a6e6716b1 ticket #112 attempt 1
DevPlace CI / test (pull_request) Failing after 11s
2026-07-23 02:49:57 +00:00
7 changed files with 48 additions and 4 deletions
File diff suppressed because one or more lines are too long
+5
View File
@@ -12,6 +12,7 @@ from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse, RedirectResponse from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from fastapi.exceptions import RequestValidationError from fastapi.exceptions import RequestValidationError
from starlette.middleware.gzip import GZipMiddleware
from devplacepy.config import ( from devplacepy.config import (
STATIC_DIR, STATIC_DIR,
STATIC_VERSION, STATIC_VERSION,
@@ -609,6 +610,10 @@ async def response_timing(request: Request, call_next):
response.headers["X-Response-Time"] = f"{(time.perf_counter() - start) * 1000:.1f}ms" response.headers["X-Response-Time"] = f"{(time.perf_counter() - start) * 1000:.1f}ms"
return response return response
app.add_middleware(GZipMiddleware, minimum_size=512, compresslevel=5)
_home_cache = TTLCache(ttl=int(os.environ.get("DEVPLACE_HOME_CACHE_TTL", "60")), max_size=4) _home_cache = TTLCache(ttl=int(os.environ.get("DEVPLACE_HOME_CACHE_TTL", "60")), max_size=4)
+7
View File
@@ -46,6 +46,7 @@ from devplacepy.utils import (
track_action, track_action,
build_achievements, build_achievements,
) )
from devplacepy.utils.rewards import LEVEL_XP
from devplacepy.responses import respond, action_result, wants_json from devplacepy.responses import respond, action_result, wants_json
from devplacepy.schemas import ProfileOut from devplacepy.schemas import ProfileOut
from devplacepy.avatar import avatar_url, avatar_seed from devplacepy.avatar import avatar_url, avatar_seed
@@ -380,6 +381,10 @@ async def profile_page(
], ],
) )
xp = profile_user.get("xp") or 0
xp_progress_pct = xp % LEVEL_XP
xp_next_level = ((xp // LEVEL_XP) + 1) * LEVEL_XP
return respond( return respond(
request, request,
"profile.html", "profile.html",
@@ -439,6 +444,8 @@ async def profile_page(
"awards_pagination": awards_pagination, "awards_pagination": awards_pagination,
"awards_count": awards_count, "awards_count": awards_count,
"prominent_award": prominent_award, "prominent_award": prominent_award,
"xp_progress_pct": xp_progress_pct,
"xp_next_level": xp_next_level,
"can_give_award": can_give, "can_give_award": can_give,
}, },
model=ProfileOut, model=ProfileOut,
+2
View File
@@ -80,6 +80,8 @@ class ProfileOut(_Out):
awards_count: int = 0 awards_count: int = 0
prominent_award: Optional[AwardOut] = None prominent_award: Optional[AwardOut] = None
can_give_award: bool = False can_give_award: bool = False
xp_progress_pct: Optional[int] = None
xp_next_level: Optional[int] = None
class TelegramPairOut(_Out): class TelegramPairOut(_Out):
@@ -11,7 +11,7 @@ How a request flows through middleware to a router, how the database layer is bu
## Middleware ## Middleware
Six HTTP middlewares run as a stack around every request, listed outermost first. `response_timing` is the outermost and `refresh_db_snapshot` the innermost. Compression is handled by nginx in production; the Python application does not compress responses itself. Seven HTTP middlewares run as a stack around every request, listed outermost first. `response_timing` is the outermost, `refresh_db_snapshot` the innermost, and a `GZipMiddleware` (responses over 512 bytes) wraps the whole stack on top:
| Middleware (outermost first) | Responsibility | | Middleware (outermost first) | Responsibility |
|------------|----------------| |------------|----------------|
+1 -2
View File
@@ -4,8 +4,7 @@ version = "1.0.0"
description = "DevPlace - The Developer Social Network" description = "DevPlace - The Developer Social Network"
requires-python = ">=3.12" requires-python = ">=3.12"
dependencies = [ dependencies = [
"fastapi>=0.110.0", "fastapi",
"starlette>=0.37.0",
"uvicorn[standard]", "uvicorn[standard]",
"jinja2", "jinja2",
"python-multipart", "python-multipart",
+31
View File
@@ -267,6 +267,37 @@ def test_activity_comment_card_renders_overlay_link(app_server):
assert f'class="card-link" href="/posts/{post_slug}#comment-{comment_uid}"' in r.text assert f'class="card-link" href="/posts/{post_slug}#comment-{comment_uid}"' in r.text
def test_profile_json_contains_xp_progress(app_server):
from devplacepy.database import get_table, refresh_snapshot
import time
name = f"xp{int(time.time() * 1000)}"
session = requests.Session()
session.post(
f"{BASE_URL}/auth/signup",
data={
"username": name,
"email": f"{name}@t.dev",
"password": "secret123",
"confirm_password": "secret123",
},
allow_redirects=True,
)
user = get_table("users").find_one(username=name)
assert user is not None
get_table("users").update({"uid": user["uid"], "xp": 250}, ["uid"])
refresh_snapshot()
r = session.get(
f"{BASE_URL}/profile/{name}", headers={"Accept": "application/json"}
)
assert r.status_code == 200, f"status {r.status_code}: {r.text[:200]}"
body = r.json()
assert body["xp_progress_pct"] == 50
assert body["xp_next_level"] == 300
def test_profile_json_exposes_online_presence(app_server): def test_profile_json_exposes_online_presence(app_server):
import time import time