feat: add user support with play event tracking

Backend (Rust API):
- Add users and play_events tables (migration 0005)
- Extract full user identity from JWT (sub, username, email, name)
  and pass AuthUser via request extensions to all handlers
- Auto-upsert user in background on every authenticated request
- POST /api/tracks/:slug/play endpoint to record play events
- Allow POST method in CORS

Frontend (Node player):
- Call recordPlay() when a track starts playing
- Add user profile avatar with dropdown menu (name, email, sign out)
- Pass user info from App through FurumiPlayer to Header

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ultradesu
2026-04-08 16:51:53 +01:00
parent 5bc2b55ffd
commit 4fdd56dae4
10 changed files with 283 additions and 25 deletions
+4 -3
View File
@@ -5,7 +5,7 @@ use std::sync::Arc;
use std::path::PathBuf;
use std::time::Duration;
use axum::{Router, routing::get, middleware};
use axum::{Router, routing::{get, post}, middleware};
use axum::http::{header, Method};
use sqlx::PgPool;
use tower_http::cors::{Any, CorsLayer};
@@ -29,7 +29,8 @@ pub fn build_router(state: Arc<AppState>) -> Router {
.route("/tracks/:slug", get(api::get_track_detail))
.route("/tracks/:slug/cover", get(api::track_cover))
.route("/stream/:slug", get(api::stream_track))
.route("/search", get(api::search));
.route("/search", get(api::search))
.route("/tracks/:slug/play", post(api::record_play));
let api = Router::new()
.nest("/api", library);
@@ -44,7 +45,7 @@ pub fn build_router(state: Arc<AppState>) -> Router {
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods([Method::GET, Method::OPTIONS, Method::HEAD])
.allow_methods([Method::GET, Method::POST, Method::OPTIONS, Method::HEAD])
.allow_headers([header::ACCEPT, header::CONTENT_TYPE, header::AUTHORIZATION])
.max_age(Duration::from_secs(600));