Skip to content

Add user profiles, avatar upload, and publisher image uploads - #10

Merged
ClausValcaTD merged 2 commits into
mainfrom
feat/user-profiles-image-upload
Nov 3, 2026
Merged

ClausValcaTD merged 2 commits into
mainfrom
feat/user-profiles-image-upload

Conversation

@ClausValcaTD

@ClausValcaTD ClausValcaTD commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

What changed and why

Users can now customize their own account and upload real images for avatars and publisher branding — no external image host, no upload endpoint.

Three user-facing additions:

  1. Account settings (/dashboard/settings) — set a custom display name and avatar.
  2. Image uploads — the avatar and publisher avatar/banner fields accept a file directly.
  3. Consistent rendering — the custom avatar appears in the navbar, dashboard, and publisher pages.

The key design decision

The session is a stateless HMAC-signed cookie capped at ~4 KB. A base64 avatar blows past that, so the custom image lives in D1 and is overlaid on the session at render time:

session cookie (github id, username, name, avatar)   ← signed, ~4KB cap
        │
        ▼
toUserProfile(session, d1Row)   ← D1 custom values win when profile_customized = 1
        │
        ▼
Navbar / dashboard / settings  render the effective profile

Because GitHub supplies name/avatar_url and they are refreshed on every login, a saved custom profile sets a profile_customized flag that tells the login upsert to leave the user's own values alone. Without it, a custom avatar would be wiped by the next sign-in.

How uploads work

No /api/upload endpoint — the browser does the work:

file input → decode → downscale to field max → canvas.toDataURL('image/webp', q)
           → shrink scale until ≤ 1.5MB → hidden input value + live preview

The preview is the submitted value. The server then re-validates from scratch (src/lib/image.ts): declared MIME vs real magic bytes, decoded size, and dimensions parsed from the file header.

Field Max edge Max size Formats
User / publisher avatar 512 px 1.5 MB PNG, JPEG, WebP
Publisher banner 1600 px 1.5 MB PNG, JPEG, WebP

1.5 MB/image keeps a row holding both an avatar and a banner under D1's 2 MB row limit. A plain http(s) URL is still accepted in place of an upload.

Files

Area Files
Schema migrations/0004_user_profiles.sql
Validation (shared) src/lib/image.ts, src/lib/media.ts
Browser optimizer src/lib/image-client.ts, src/components/ImageField.astro
API src/pages/api/user/profile.ts (new GET/PATCH), src/pages/api/publishers/[id].ts
UI src/pages/dashboard/settings.astro, src/pages/dashboard.astro, src/components/Navbar.astro, src/layouts/Layout.astro, src/pages/dashboard/publisher/[id].astro
Docs / tests README.md, tests/user-profile.test.ts

Verification

  • npm run build ✅ · npx tsc --noEmit ✅ clean · npm test ✅ 38/38 (15 new)
  • Exercised against wrangler pages dev with a real local D1 and a genuinely signed session cookie:
    • anonymous requests to PATCH /api/user/profile and PATCH /api/publishers/[id] → 401
    • saving a custom name + avatar → 200, then rendered on the settings page, navbar, and publisher page
    • replaying the login upsert left the custom profile intact
    • rejected an over-size avatar (400), a PNG labelled as WebP (400), and javascript: URLs (400)

Reviewer notes

  • Deploy order: migration 0004 must be applied to the remote D1 (npx wrangler d1 migrations apply openrom-store --remote) before or with the Pages deploy, or profile saves fail against the missing column.
  • src/lib/media.ts imports its siblings with an explicit .ts extension because Node's --experimental-strip-types loader (used by npm test) does not resolve extensionless relative imports. The Astro/Vite build accepts it.
  • Navbar.astro now types its prop as a local NavbarUser interface rather than SessionUser, since the value it receives is the D1-overlaid profile.

Summary by CodeRabbit

  • New Features
    • Added account settings for editing a display name and avatar.
    • Added avatar and banner image uploads with browser-side resizing and WebP conversion, plus validation for image size, format, and dimensions.
    • Added support for image URLs alongside uploads.
    • Customized profile details are preserved during GitHub sign-in updates and displayed across the dashboard and navigation.
  • Documentation
    • Added guidance on account settings, profile APIs, image uploads, accepted image URLs, and image validation limits.

Users can now customize their account at /dashboard/settings and upload
avatars/banners in the publisher editor. Images are optimized in the browser
(downscale + WebP) and validated server-side against their real bytes.

- migrations/0004_user_profiles.sql: users.profile_customized flag so the
  login upsert stops overwriting a saved custom name/avatar.
- src/lib/image.ts: shared image validation (magic bytes vs declared type,
  size, header-parsed dimensions). Avatars cap at 512px, banners at 1600px,
  1.5MB per image (D1 rows cap at 2MB).
- src/lib/image-client.ts: browser-only canvas optimizer; the preview is
  exactly the bytes submitted.
- src/lib/media.ts: accepts an uploaded data URL or a plain http(s) URL.
- src/pages/api/user/profile.ts: GET/PATCH the signed-in user's profile.
- src/pages/api/publishers/[id].ts: avatar/banner now accept image uploads.
- src/components/ImageField.astro + settings page + navbar/dashboard avatar.

The custom avatar lives in D1 and is overlaid on the session at render time,
since a base64 image cannot fit the ~4KB signed session cookie.

Tests: 38 passing (15 new).
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Deploying openrom-store with  Cloudflare Pages  Cloudflare Pages

Latest commit: 4cedad8
Status: ✅  Deploy successful!
Preview URL: https://9c671ddf.openrom-store.pages.dev
Branch Preview URL: https://feat-user-profiles-image-upl.openrom-store.pages.dev

View logs

@coderabbitai

coderabbitai Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Caution

Review failed

The pull request is closed.

Walkthrough

The pull request adds account profile editing and persistent custom profile values. It also adds browser-side image optimization, server-side image validation, reusable image fields, and image editing for account and publisher profiles.

Changes: Account Profiles and Image Uploads

Layer / File(s) Summary
Profile storage and customization
migrations/0004_user_profiles.sql, src/lib/users.ts, src/pages/api/auth/callback.ts, tests/user-profile.test.ts
Adds a profile customization flag, profile lookup and update functions, and GitHub-derived session identity. Tests cover profile overlays, fallbacks, and session identity.
Image processing and validation
src/lib/image.ts, src/lib/image-client.ts, src/lib/media.ts, src/components/ImageField.astro, src/components/Icon.astro, README.md, tests/user-profile.test.ts
Adds image dimension parsing and validation, browser-side image optimization and field controls, and documentation for image uploads.
Account profile API and settings
src/pages/api/user/profile.ts, src/pages/dashboard/settings.astro, src/pages/dashboard.astro, src/layouts/Layout.astro, src/components/Navbar.astro
Adds authenticated profile retrieval and updates, an account settings form, and profile data in dashboard and navbar views.
Publisher image fields
src/pages/api/publishers/[id].ts, src/pages/dashboard/publisher/[id].astro
Uses image fields for publisher avatars and banners and validates uploaded image data or URLs with field-specific limits.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant SettingsPage
  participant ImageField
  participant ProfileAPI
  participant D1
  SettingsPage->>ImageField: optimize selected avatar image
  SettingsPage->>ProfileAPI: PATCH name and avatar
  ProfileAPI->>D1: update customized profile
  D1-->>ProfileAPI: return updated user row
  ProfileAPI-->>SettingsPage: return effective profile
Loading

Possibly related PRs

Suggested reviewers: openhands-agent

Merge Risk: Moderate · up to 4ceda

Uploads can be lost or replaced, and unchanged profile fields can stop following GitHub updates. Fix these behaviors before merging.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 40.91% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title "Add user profiles, avatar upload, and publisher image uploads" matches the main changes in the changeset. The diff adds a D1 profile migration, profile GET/PATCH API, account settings page,…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title "Add user profiles, avatar upload, and publisher image uploads" matches the main changes in the changeset. The diff adds a D1 profile migration, profile GET/PATCH API, account settings page, ImageField component with browser-side WebP optimization, server-side image validation, and publisher avatar/banner uploads. The title names the primary features and does not describe anything absent from the changeset.

Full details: Docstring Coverage

Explanation

Docstring coverage is 40.91% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 22 functions across 7 files. (9 skipped: 9 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

CodeRabbit Verify

  • Run CodeRabbit Verify to test the changes in this pull request.
Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: d7d5990e-5d61-423c-bb88-71a9d01f2da8
Commits

Reviewing files that changed from the base of the PR and between 92d6231 and 49d9e3f.

Files selected for processing: 16
  • README.md
  • migrations/0004_user_profiles.sql
  • src/components/Icon.astro
  • src/components/ImageField.astro
  • src/components/Navbar.astro
  • src/layouts/Layout.astro
  • src/lib/image-client.ts
  • src/lib/image.ts
  • src/lib/media.ts
  • src/lib/users.ts
  • src/pages/api/publishers/[id].ts
  • src/pages/api/user/profile.ts
  • src/pages/dashboard.astro
  • src/pages/dashboard/publisher/[id].astro
  • src/pages/dashboard/settings.astro
  • tests/user-profile.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

@coderabbitai coderabbitai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Fix comments: Fix CodeRabbit comments on this PR

Comment thread src/components/ImageField.astro Resolved
Comment thread src/lib/image.ts Resolved
Comment thread src/lib/users.ts Resolved
Addresses three findings on #10.

1. Major — session cookie could exceed the browser limit. The auth callback
   built the cookie from the row `upsertUser` returns, and after a user saved
   an uploaded avatar that row holds the multi-megabyte data URL. A real upload
   produced a 1,066,871-byte cookie, which browsers reject — the user would
   fail to sign in again. The cookie is now built from the GitHub profile via
   `sessionUserFromGitHubProfile`, so it stays ~230 bytes and the GitHub
   name/avatar fallback survives a cleared custom avatar.

2. Minor — the image cap was checked against decoded bytes only. What D1 stores
   is the base64 text (~4/3 the decoded size), so a 1.5 MB image was ~2 MB of
   text and a row with an avatar plus a banner could exceed D1's 2 MB row limit,
   failing at the database with a generic 500. Lowered the decoded cap to 600 KB
   and added a hard 800 KB bound on the encoded string itself — which also
   catches whitespace-padded values that the decoded-size check alone missed.
   The client optimizer now targets the same encoded budget.

3. Minor — the upload label had no keyboard focus indicator (the real file
   input is sr-only). Added focus-within styles.

Tests: 41 passing (3 new).
@ClausValcaTD
ClausValcaTD merged commit 975e4aa into main Nov 3, 2026
3 of 4 checks passed
@ClausValcaTD
ClausValcaTD deleted the feat/user-profiles-image-upload branch November 3, 2026 19:56
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.

2 participants