feat : add profile page

This commit is contained in:
2026-08-06 22:46:47 +07:00 Unverified
parent e5a6ad3e04
commit c1c015ef78
26 changed files with 1169 additions and 310 deletions
+42
View File
@@ -0,0 +1,42 @@
# ADR 0003: Personal guild profile read model
- Status: Accepted
- Date: 2026-08-06
## Context
Signed-in members need one private page that combines their Discord identity with XP,
level progress, voice time, and leaderboard standing. The Discord bot remains the
authoritative writer for `user_levels` and `voice_time`, while the website has only a
read-only connection to those tables. XP values use `numeric(40,0)`, which may exceed
JavaScript's safe integer range.
## Decision
1. `/profile` identifies the current member from the server-side NextAuth session and
Discord provider account. Anonymous visitors receive a Discord login prompt that
returns them to `/profile`; profiles cannot be selected by URL parameter.
2. Discord identity, XP, and voice data load independently. Session identity is the
fallback when Discord member metadata is unavailable, and unavailable activity
sources report section-level errors.
3. The stored bot level remains authoritative. Next-level progress duplicates the
bot's documented XP formula and performs all XP arithmetic with `bigint` before
converting the bounded percentage to a number. Level 200 is the maximum and does
not display a progress bar.
4. XP and voice ranks use the public leaderboards' shared competition ranking. XP
rank excludes level-0 members. Voice time and rank include only voice and Stage
channels that currently exist in the guild.
5. Missing activity rows display zero activity and no rank. The website does not
create rows or write inferred values back to the bot database.
6. One public leaderboard SSE subscription refreshes an open profile after either an
XP event or a voice-time event. Redis remains a notification path rather than the
source of truth.
## Consequences
- A signed-in member sees only the profile associated with their own Discord account.
- Profile totals and public leaderboard values share ranking and voice-channel scope.
- The bot formula is a cross-service contract. Formula or maximum-level changes must
update the bot integration document and website helper together.
- Large cumulative XP values remain exact through database reads, calculations, and
formatting.
+12
View File
@@ -63,3 +63,15 @@ tables, combined with current Discord channel metadata.
A minimal Redis notification on `erika:sse:leaderboards` that tells an open XP or VC
page to refresh its Server Component data after a bot transaction commits.
## Guild profile
The private `/profile` view for the currently authenticated Discord member. It joins
Discord identity and roles with that member's level, XP, current-channel voice time,
and leaderboard ranks without exposing a public member-profile URL.
## Level progress
The member's XP accumulated since reaching their stored level, measured against the
bot's XP requirement for the next level. Level 200 is the maximum and has no next-level
progress bar.
+22
View File
@@ -3,6 +3,28 @@
The Discord bot owns all writes to `user_levels` and `voice_time`. The website only
subscribes to notifications and then reads the committed database state.
## Level formula contract
The website's personal profile calculates next-level progress from the bot's stored
level and cumulative XP. Both services must use this exact formula:
```text
required(0) = 500
required(level) = required(level - 1) + 100 + (level * 25)
total(0) = 0
total(level) = total(level - 1) + required(level - 1)
```
`required(level)` is the XP needed to advance from that level to the next. `total(level)`
is the cumulative XP needed to reach that level. Level 200 is the maximum; the XP
requirement caps at the level-199 value and the profile does not show next-level
progress at the cap.
Store cumulative XP as an integer value and update `xp` and `level` in the same bot
transaction. Changing the formula or maximum level requires a coordinated website
update because the website intentionally does not infer the formula from database
metadata.
## Redis channel
Publish leaderboard notifications to exactly: