[WIP] music platform user data scraper
teal-fm atproto

hook up api routes, set api validity to very long time

Natalie B bcf53ecf f7d7ca3a

Changed files
+116 -4
cmd
service
apikey
+108
cmd/handlers.go
··· 1 1 package main 2 2 3 3 import ( 4 + "encoding/json" 4 5 "fmt" 5 6 "log" 6 7 "net/http" ··· 308 309 jsonResponse(w, http.StatusOK, recordings) 309 310 } 310 311 } 312 + 313 + func apiMeHandler(database *db.DB) http.HandlerFunc { 314 + return func(w http.ResponseWriter, r *http.Request) { 315 + userID, authenticated := session.GetUserID(r.Context()) 316 + if !authenticated { 317 + jsonResponse(w, http.StatusUnauthorized, map[string]any{"authenticated": false, "error": "Unauthorized"}) 318 + return 319 + } 320 + 321 + user, err := database.GetUserByID(userID) 322 + if err != nil { 323 + log.Printf("apiMeHandler: Error fetching user %d: %v", userID, err) 324 + jsonResponse(w, http.StatusInternalServerError, map[string]string{"error": "Failed to retrieve user information"}) 325 + return 326 + } 327 + if user == nil { 328 + jsonResponse(w, http.StatusNotFound, map[string]string{"error": "User not found"}) 329 + return 330 + } 331 + 332 + lastfmUsername := "" 333 + if user.LastFMUsername != nil { 334 + lastfmUsername = *user.LastFMUsername 335 + } 336 + 337 + spotifyConnected := user.SpotifyID != nil 338 + 339 + response := map[string]any{ 340 + "authenticated": true, 341 + "user_id": user.ID, 342 + "did": user.ATProtoDID, 343 + "lastfm_username": lastfmUsername, 344 + "spotify_connected": spotifyConnected, 345 + } 346 + if user.LastFMUsername == nil { 347 + response["lastfm_username"] = nil 348 + } 349 + 350 + jsonResponse(w, http.StatusOK, response) 351 + } 352 + } 353 + 354 + func apiGetLastfmUserHandler(database *db.DB) http.HandlerFunc { 355 + return func(w http.ResponseWriter, r *http.Request) { 356 + userID, _ := session.GetUserID(r.Context()) // Auth middleware ensures user is present 357 + user, err := database.GetUserByID(userID) 358 + if err != nil { 359 + log.Printf("apiGetLastfmUserHandler: Error fetching user %d: %v", userID, err) 360 + jsonResponse(w, http.StatusInternalServerError, map[string]string{"error": "Failed to retrieve user information"}) 361 + return 362 + } 363 + if user == nil { 364 + jsonResponse(w, http.StatusNotFound, map[string]string{"error": "User not found"}) 365 + return 366 + } 367 + 368 + var lastfmUsername *string 369 + if user.LastFMUsername != nil { 370 + lastfmUsername = user.LastFMUsername 371 + } 372 + jsonResponse(w, http.StatusOK, map[string]*string{"lastfm_username": lastfmUsername}) 373 + } 374 + } 375 + 376 + func apiLinkLastfmHandler(database *db.DB) http.HandlerFunc { 377 + return func(w http.ResponseWriter, r *http.Request) { 378 + userID, _ := session.GetUserID(r.Context()) 379 + 380 + var reqBody struct { 381 + LastFMUsername string `json:"lastfm_username"` 382 + } 383 + if err := json.NewDecoder(r.Body).Decode(&reqBody); err != nil { 384 + jsonResponse(w, http.StatusBadRequest, map[string]string{"error": "Invalid request body: " + err.Error()}) 385 + return 386 + } 387 + 388 + if reqBody.LastFMUsername == "" { 389 + jsonResponse(w, http.StatusBadRequest, map[string]string{"error": "Last.fm username cannot be empty"}) 390 + return 391 + } 392 + 393 + err := database.AddLastFMUsername(userID, reqBody.LastFMUsername) 394 + if err != nil { 395 + log.Printf("apiLinkLastfmHandler: Error saving Last.fm username for user %d: %v", userID, err) 396 + jsonResponse(w, http.StatusInternalServerError, map[string]string{"error": "Failed to save Last.fm username"}) 397 + return 398 + } 399 + log.Printf("API: Successfully linked Last.fm username '%s' for user ID %d", reqBody.LastFMUsername, userID) 400 + jsonResponse(w, http.StatusOK, map[string]string{"message": "Last.fm username updated successfully"}) 401 + } 402 + } 403 + 404 + func apiUnlinkLastfmHandler(database *db.DB) http.HandlerFunc { 405 + return func(w http.ResponseWriter, r *http.Request) { 406 + userID, _ := session.GetUserID(r.Context()) 407 + 408 + // TODO: add a clear username for user id fn 409 + err := database.AddLastFMUsername(userID, "") 410 + if err != nil { 411 + log.Printf("apiUnlinkLastfmHandler: Error unlinking Last.fm username for user %d: %v", userID, err) 412 + jsonResponse(w, http.StatusInternalServerError, map[string]string{"error": "Failed to unlink Last.fm username"}) 413 + return 414 + } 415 + log.Printf("API: Successfully unlinked Last.fm username for user ID %d", userID) 416 + jsonResponse(w, http.StatusOK, map[string]string{"message": "Last.fm username unlinked successfully"}) 417 + } 418 + }
+4
cmd/routes.go
··· 28 28 mux.HandleFunc("/logout", app.sessionManager.HandleLogout) 29 29 mux.HandleFunc("/debug/", session.WithAuth(app.sessionManager.HandleDebug, app.sessionManager)) 30 30 31 + mux.HandleFunc("/api/v1/me", session.WithAPIAuth(apiMeHandler(app.database), app.sessionManager)) 32 + mux.HandleFunc("/api/v1/lastfm", session.WithAPIAuth(apiGetLastfmUserHandler(app.database), app.sessionManager)) 33 + mux.HandleFunc("/api/v1/lastfm/set", session.WithAPIAuth(apiLinkLastfmHandler(app.database), app.sessionManager)) 34 + mux.HandleFunc("/api/v1/lastfm/unset", session.WithAPIAuth(apiUnlinkLastfmHandler(app.database), app.sessionManager)) 31 35 mux.HandleFunc("/api/v1/current-track", session.WithAPIAuth(apiCurrentTrack(app.spotifyService), app.sessionManager)) // Spotify Current 32 36 mux.HandleFunc("/api/v1/history", session.WithAPIAuth(apiTrackHistory(app.spotifyService), app.sessionManager)) // Spotify History 33 37 mux.HandleFunc("/api/v1/musicbrainz/search", apiMusicBrainzSearch(app.mbService)) // MusicBrainz (public?)
+4 -4
service/apikey/apikey.go
··· 26 26 } 27 27 28 28 // jsonResponse is a helper to send JSON responses 29 - func jsonResponse(w http.ResponseWriter, statusCode int, data interface{}) { 29 + func jsonResponse(w http.ResponseWriter, statusCode int, data any) { 30 30 w.Header().Set("Content-Type", "application/json") 31 31 w.WriteHeader(statusCode) 32 32 if data != nil { ··· 67 67 // Ensure keys are safe for listing (e.g., no raw key string) 68 68 // GetUserApiKeys should return a slice of db_apikey.ApiKey or similar struct 69 69 // that includes ID, Name, KeyPrefix, CreatedAt, ExpiresAt. 70 - jsonResponse(w, http.StatusOK, map[string]interface{}{"api_keys": keys}) 70 + jsonResponse(w, http.StatusOK, map[string]any{"api_keys": keys}) 71 71 72 72 case http.MethodPost: 73 73 var reqBody struct { ··· 135 135 if keyName == "" { 136 136 keyName = fmt.Sprintf("API Key - %s", time.Now().Format(time.RFC3339)) 137 137 } 138 - validityDays := 30 // Default for HTML form creation 138 + validityDays := 1024 139 139 140 140 // Uses the existing CreateAPIKey, which likely doesn't return the raw key. 141 141 // The HTML flow currently redirects and shows the key ID. ··· 171 171 return 172 172 } 173 173 // AJAX client expects JSON 174 - jsonResponse(w, http.StatusOK, map[string]interface{}{"success": true}) 174 + jsonResponse(w, http.StatusOK, map[string]any{"success": true}) 175 175 return 176 176 } 177 177