Skip to content

Feature/profile schema integration#4

Open
amormousa wants to merge 5 commits into
mainfrom
feature/profile-schema-integration
Open

Feature/profile schema integration#4
amormousa wants to merge 5 commits into
mainfrom
feature/profile-schema-integration

Conversation

@amormousa

@amormousa amormousa commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Profile Page: Database Schema + Backend Bug Fixes

Summary

Adds all database schema needed to power the new /api/profile page, and
fixes five correctness bugs in the session-processing and auth code that the
profile page depends on — bugs that would otherwise have caused wrong or
missing numbers on the profile screen.


1. Database schema for the profile page

Why: the profile page (bio card, metrics row, activity heatmap,
achievements list, recent activity feed, personal-info completion ring)
needs data that didn't exist in the schema before — users only had generic
account fields, and there was no table for cached stats, achievements, or an
activity feed.

What changed:

Migration Adds Used for
005_extend_users.sql first_name, role, location, university, time_zone, last_active_at, gender, languages on users Bio card fields, "last active" display, personal-info completion checklist
006_profile_stats.sql user_profile_stats (total_coding_seconds, total_active_days, current_streak, max_streak) The coding-time metric cards and streak numbers, cached instead of recomputed on every page load
007_achievements.sql achievement_types + user_achievements The Achievements list on the profile
008_activity_log.sql activity_log, plus indexes on sessions(user_id, start_time) and heartbeats(user_id, time) The Recent Activity feed; the extra indexes speed up heatmap/metrics queries
009_problem_stats_placeholder.sql user_problem_stats, explicitly marked unused Reserves the shape for solved / totalProblems / attempting without inventing a fake problem-solving feature — nothing populates this table yet
seed_profile.sql Sample data for a testuser account Local dev / manual testing of the profile page end to end

When used:

  • user_profile_stats is written by the background session processor
    (UpdateProfileStats) every time it processes new heartbeats, not
    recomputed live on each profile view.
  • user_achievements / activity_log are read-only in this PR — nothing
    writes to them outside the seed script, since achievement-awarding logic
    is a separate feature.
  • user_problem_stats is not read or written anywhere yet; it exists so the
    /api/profile response shape can include those fields without a later
    migration, and will show zeros until a real feature lands.

2. Swagger: /api/profile documented

Added the /api/profile path (BearerAuth, 200/401) and full definitions
for models.ProfileResponse and its nested types (Achievement,
ActivityLogItem, HeatmapCell, ProfileMetric, ProfileInfoField), so
the response shape is fully typed in the docs instead of a generic
helpers.APIResponse.


3. Bug fixes affecting profile-page data accuracy

Not new features — fixes for cases where Total Coding Time, Active Days, or
Streak could come out wrong, or the page could 500 for new users.

3a. session_processor.go — session building could double-count time

  • Wrapped the heartbeat read → session insert → mark-processed flow in a
    single DB transaction, so a mid-run failure can't leave orphaned sessions
    that get rebuilt and duplicated on the next run.
  • Added a Postgres advisory lock (pg_try_advisory_lock, key 918273)
    around the whole run, so the manual POST /api/admin/process-sessions
    trigger can never run concurrently with the periodic background run. If
    the lock is already held, the run logs that it skipped and returns
    cleanly, not as an error.
  • Changed heartbeat ordering from user_id, project, time to user_id, time
    only, so sessions are built from true chronological order. Previously, a
    user switching between two projects in the same window could produce two
    sessions with overlapping start/end times, double-counting duration when
    summed into total_coding_seconds.

3b. models/profile.goGetProfileStats 500'd for brand-new users

A user with no row yet in user_profile_stats (no sessions processed) used
to hit pgx.ErrNoRows as a real error. Now matches the pattern already used
elsewhere in the codebase (models/user.go): returns (nil, nil), a normal
empty state.

3c. models/profile.goUpdateProfileStats silently wrote wrong data on query failure

The two aggregate queries (SUM(duration_seconds),
COUNT(DISTINCT start_time::date)) had their errors discarded; a transient
failure would write zeroed stats over a user's real coding-time history.
Both now return their error instead of being ignored.

3d. handlers/auth.go — cookie SameSite mismatch broke refresh tokens locally

Both setRefreshTokenCookie and the new Logout handler now consistently
use SameSite: "Lax" (previously "None", which browsers reject on http
unless Secure is also true — silently breaking cookie set/clear in local
dev).

3e. handlers/auth.go — unsafe userID type assertions could panic

Replaced userID := c.Locals("userID").(string) with a checked assertion
returning 401 Unauthorized on failure, applied consistently across every
authenticated handler project-wide — auth.go, filters.go, goals.go,
heartbeat.go, settings.go, stats.go.

3f. handlers/auth.goCompleteSignup ignored username-lookup errors

FindUserByUsername's error is now checked and returns 500 if the lookup
itself failed, instead of silently treating a failed lookup as "username
available."


Also included

  • POST /api/auth/logout: revokes the current refresh token and clears the
    cookie.
  • RefreshAccessToken now calls CleanupExpiredRefreshTokens after each
    refresh so refresh_tokens doesn't grow unbounded.
  • RevokeAllUserRefreshTokens added to models/refresh_token.go (not yet
    called anywhere — available for a future "log out everywhere" feature).
  • apps/web/angular.json / package.json / bun.lock: picked up the
    analytics id, the fdir dependency, and consistent JSON formatting from
    merging in the profile-page frontend branch. No conflict markers remain.

Testing

  • Run all new migrations against a clean local DB, confirm they apply in order.
  • Run seed_profile.sql, confirm testuser shows realistic profile data.
  • Trigger POST /api/admin/process-sessions twice in quick succession,
    confirm no duplicate sessions and the second call logs "skipped".
  • Hit an authenticated route with no/garbage auth, confirm a clean 401
    instead of a panic.
  • Log in locally over http, confirm the refresh-token cookie is set
    and /api/auth/refresh and /api/auth/logout both work.
  • bun install and go build ./... both succeed from a clean checkout.

amormousa added 5 commits July 7, 2026 06:32
- Add UpdateProfileStats to upsert cached aggregate stats after session processing
- Add GetProfileStats model function for reading cached profile stats
- Add RevokeAllUserRefreshTokens and CleanupExpiredRefreshTokens for token management
- Add POST /api/auth/logout endpoint to revoke refresh token and clear cookie
- Add periodic cleanup of expired refresh tokens on token refresh
- Populate user_profile_stats table from session processor background job
- Extend User model with all profile fields from migrations
@vercel

vercel Bot commented Jul 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
seismic Error Error Jul 7, 2026 10:11pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant