feat(auth): add /api/me endpoint, avatar upload, and profile picture support
Build and Deploy / build-and-push (push) Successful in 5m7s

- Add migration 005: nullable avatar_url column on users table
- Add GET /api/me: returns id, username, avatar_url from DB (fixes UUID-on-profile bug)
- Add PUT /api/me/avatar: accepts raw image bytes (≤1 MB, jpeg/png/webp/gif),
  writes to avatars/ dir, updates avatar_url in DB
- Serve /avatars via ServeDir so uploaded images are publicly accessible
- Update account.html: fetch username from /api/me instead of parsing JWT;
  add circular avatar display with initials fallback and click-to-upload
- Add SolitaireServerClient::fetch_me() for desktop/Android profile display
- Add avatar_url field to SyncBackend::SolitaireServer settings (serde default None)
- Update sqlx offline query cache for new avatar_url queries

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-14 17:14:36 -07:00
parent eb906fe968
commit 407cae2040
11 changed files with 354 additions and 30 deletions
+5 -1
View File
@@ -19,9 +19,10 @@ use axum::{
http::{HeaderValue, Request},
middleware as axum_middleware,
response::{Html, Response},
routing::{delete, get, post},
routing::{delete, get, post, put},
Router,
};
use jsonwebtoken::{decode, DecodingKey, Validation};
use sqlx::SqlitePool;
use std::sync::Arc;
@@ -143,6 +144,8 @@ fn build_router_inner(state: AppState, rate_limit: bool) -> Router {
.route("/api/leaderboard/opt-in", post(leaderboard::opt_in))
.route("/api/leaderboard/opt-in", delete(leaderboard::opt_out))
.route("/api/account", delete(auth::delete_account))
.route("/api/me", get(auth::get_me))
.route("/api/me/avatar", put(auth::upload_avatar))
.layer(axum_middleware::from_fn_with_state(
state.clone(),
middleware::require_auth,
@@ -228,6 +231,7 @@ fn build_router_inner(state: AppState, rate_limit: bool) -> Router {
)
.nest_service("/web", ServeDir::new("solitaire_server/web"))
.nest_service("/assets", ServeDir::new("assets"))
.nest_service("/avatars", ServeDir::new("avatars"))
.layer(axum_middleware::from_fn(security_headers));
Router::new()