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

Added a dedicated logger with prefix to most services

authored by baileytownsend.dev and committed by Kyle Loveless 9faa99db bb519076

Changed files
+115 -100
cmd
oauth
service
lastfm
musicbrainz
spotify
+3 -3
cmd/main.go
··· 108 108 lastfmInterval = 30 * time.Second 109 109 } 110 110 111 - if err := spotifyService.LoadAllUsers(); err != nil { 112 - log.Printf("Warning: Failed to preload Spotify users: %v", err) 113 - } 111 + //if err := spotifyService.LoadAllUsers(); err != nil { 112 + // log.Printf("Warning: Failed to preload Spotify users: %v", err) 113 + //} 114 114 go spotifyService.StartListeningTracker(trackerInterval) 115 115 116 116 go lastfmService.StartListeningTracker(lastfmInterval)
+9 -7
oauth/oauth_manager.go
··· 15 15 services map[string]AuthService 16 16 sessionManager *session.SessionManager 17 17 mu sync.RWMutex 18 + logger *log.Logger 18 19 } 19 20 20 21 func NewOAuthServiceManager(sessionManager *session.SessionManager) *OAuthServiceManager { 21 22 return &OAuthServiceManager{ 22 23 services: make(map[string]AuthService), 23 24 sessionManager: sessionManager, 25 + logger: log.New(log.Writer(), "oauth: ", log.LstdFlags|log.Lmsgprefix), 24 26 } 25 27 } 26 28 ··· 29 31 m.mu.Lock() 30 32 defer m.mu.Unlock() 31 33 m.services[name] = service 32 - log.Printf("Registered auth service: %s", name) 34 + m.logger.Printf("Registered auth service: %s", name) 33 35 } 34 36 35 37 // get an AuthService by registered name ··· 51 53 return 52 54 } 53 55 54 - log.Printf("Auth service '%s' not found for login request", serviceName) 56 + m.logger.Printf("Auth service '%s' not found for login request", serviceName) 55 57 http.Error(w, fmt.Sprintf("Auth service '%s' not found", serviceName), http.StatusNotFound) 56 58 } 57 59 } ··· 62 64 service, exists := m.services[serviceName] 63 65 m.mu.RUnlock() 64 66 65 - log.Printf("Logging in with service %s", serviceName) 67 + m.logger.Printf("Logging in with service %s", serviceName) 66 68 67 69 if !exists { 68 - log.Printf("Auth service '%s' not found for callback request", serviceName) 70 + m.logger.Printf("Auth service '%s' not found for callback request", serviceName) 69 71 http.Error(w, fmt.Sprintf("OAuth service '%s' not found", serviceName), http.StatusNotFound) 70 72 return 71 73 } ··· 73 75 userID, err := service.HandleCallback(w, r) 74 76 75 77 if err != nil { 76 - log.Printf("Error handling callback for service '%s': %v", serviceName, err) 78 + m.logger.Printf("Error handling callback for service '%s': %v", serviceName, err) 77 79 http.Error(w, fmt.Sprintf("Error handling callback for service '%s'", serviceName), http.StatusInternalServerError) 78 80 return 79 81 } ··· 83 85 84 86 m.sessionManager.SetSessionCookie(w, session) 85 87 86 - log.Printf("Created session for user %d via service %s", userID, serviceName) 88 + m.logger.Printf("Created session for user %d via service %s", userID, serviceName) 87 89 88 90 http.Redirect(w, r, "/", http.StatusSeeOther) 89 91 } else { 90 - log.Printf("Callback for service '%s' did not result in a valid user ID.", serviceName) 92 + m.logger.Printf("Callback for service '%s' did not result in a valid user ID.", serviceName) 91 93 // todo: redirect to an error page 92 94 // right now this just redirects home but we don't want this behaviour ideally 93 95 http.Redirect(w, r, "/", http.StatusSeeOther)
+45 -40
service/lastfm/lastfm.go
··· 9 9 "log" 10 10 "net/http" 11 11 "net/url" 12 + "os" 12 13 "strconv" 13 14 "sync" 14 15 "time" ··· 40 41 atprotoService *atprotoauth.ATprotoAuthService 41 42 lastSeenNowPlaying map[string]Track 42 43 mu sync.Mutex 44 + logger *log.Logger 43 45 } 44 46 45 47 func NewLastFMService(db *db.DB, apiKey string, musicBrainzService *musicbrainz.MusicBrainzService, atprotoService *atprotoauth.ATprotoAuthService) *LastFMService { 48 + logger := log.New(os.Stdout, "lastfm: ", log.LstdFlags|log.Lmsgprefix) 49 + 46 50 return &LastFMService{ 47 51 db: db, 48 52 httpClient: &http.Client{ ··· 56 60 musicBrainzService: musicBrainzService, 57 61 lastSeenNowPlaying: make(map[string]Track), 58 62 mu: sync.Mutex{}, 63 + logger: logger, 59 64 } 60 65 } 61 66 62 67 func (l *LastFMService) loadUsernames() error { 63 68 u, err := l.db.GetAllUsersWithLastFM() 64 69 if err != nil { 65 - log.Printf("Error loading users with Last.fm from DB: %v", err) 70 + l.logger.Printf("Error loading users with Last.fm from DB: %v", err) 66 71 return fmt.Errorf("failed to load users from database: %w", err) 67 72 } 68 73 usernames := make([]string, len(u)) ··· 71 76 if user.LastFMUsername != nil { // Check if the username is set 72 77 usernames[i] = *user.LastFMUsername 73 78 } else { 74 - log.Printf("User ID %d has Last.fm enabled but no username set", user.ID) 79 + l.logger.Printf("User ID %d has Last.fm enabled but no username set", user.ID) 75 80 } 76 81 } 77 82 ··· 84 89 } 85 90 86 91 l.Usernames = filteredUsernames 87 - log.Printf("Loaded %d Last.fm usernames", len(l.Usernames)) 92 + l.logger.Printf("Loaded %d Last.fm usernames", len(l.Usernames)) 88 93 89 94 return nil 90 95 } ··· 113 118 return nil, fmt.Errorf("failed to create request for %s: %w", username, err) 114 119 } 115 120 116 - log.Printf("Fetching recent tracks for user: %s", username) 121 + l.logger.Printf("Fetching recent tracks for user: %s", username) 117 122 resp, err := l.httpClient.Do(req) 118 123 if err != nil { 119 124 return nil, fmt.Errorf("failed to fetch recent tracks for %s: %w", username, err) ··· 134 139 } 135 140 if err := json.Unmarshal(bodyBytes, &recentTracksResp); err != nil { 136 141 // Log the body content that failed to decode 137 - log.Printf("Failed to decode response body for %s: %s", username, string(bodyBytes)) 142 + l.logger.Printf("Failed to decode response body for %s: %s", username, string(bodyBytes)) 138 143 return nil, fmt.Errorf("failed to decode response for %s: %w", username, err) 139 144 } 140 145 141 146 if len(recentTracksResp.RecentTracks.Tracks) > 0 { 142 - log.Printf("Fetched %d tracks for %s. Most recent: %s - %s", 147 + l.logger.Printf("Fetched %d tracks for %s. Most recent: %s - %s", 143 148 len(recentTracksResp.RecentTracks.Tracks), 144 149 username, 145 150 recentTracksResp.RecentTracks.Tracks[0].Artist.Text, 146 151 recentTracksResp.RecentTracks.Tracks[0].Name) 147 152 } else { 148 - log.Printf("No recent tracks found for %s", username) 153 + l.logger.Printf("No recent tracks found for %s", username) 149 154 } 150 155 151 156 return &recentTracksResp, nil ··· 153 158 154 159 func (l *LastFMService) StartListeningTracker(interval time.Duration) { 155 160 if err := l.loadUsernames(); err != nil { 156 - log.Printf("Failed to perform initial username load: %v", err) 161 + l.logger.Printf("Failed to perform initial username load: %v", err) 157 162 // Decide if we should proceed without initial load or return error 158 163 } 159 164 160 165 if len(l.Usernames) == 0 { 161 - log.Println("No Last.fm users configured. Tracker will run but fetch cycles will be skipped until users are added.") 166 + l.logger.Println("No Last.fm users configured. Tracker will run but fetch cycles will be skipped until users are added.") 162 167 } else { 163 - log.Printf("Found %d Last.fm users.", len(l.Usernames)) 168 + l.logger.Printf("Found %d Last.fm users.", len(l.Usernames)) 164 169 } 165 170 166 171 ticker := time.NewTicker(interval) ··· 169 174 if len(l.Usernames) > 0 { 170 175 l.fetchAllUserTracks(context.Background()) 171 176 } else { 172 - log.Println("Skipping initial fetch cycle as no users are configured.") 177 + l.logger.Println("Skipping initial fetch cycle as no users are configured.") 173 178 } 174 179 175 180 for { ··· 177 182 case <-ticker.C: 178 183 // refresh usernames periodically from db 179 184 if err := l.loadUsernames(); err != nil { 180 - log.Printf("Error reloading usernames in ticker: %v", err) 185 + l.logger.Printf("Error reloading usernames in ticker: %v", err) 181 186 // Continue ticker loop even if reload fails? Or log and potentially stop? 182 187 continue // Continue for now 183 188 } 184 189 if len(l.Usernames) > 0 { 185 190 l.fetchAllUserTracks(context.Background()) 186 191 } else { 187 - log.Println("No Last.fm users configured. Skipping fetch cycle.") 192 + l.logger.Println("No Last.fm users configured. Skipping fetch cycle.") 188 193 } 189 194 // TODO: Implement graceful shutdown using context cancellation 190 195 // case <-ctx.Done(): 191 - // log.Println("Stopping Last.fm listening tracker.") 196 + // l.logger.Println("Stopping Last.fm listening tracker.") 192 197 // ticker.Stop() 193 198 // return 194 199 } 195 200 } 196 201 }() 197 202 198 - log.Printf("Last.fm Listening Tracker started with interval %v", interval) 203 + l.logger.Printf("Last.fm Listening Tracker started with interval %v", interval) 199 204 } 200 205 201 206 // fetchAllUserTracks iterates through users and fetches their tracks. 202 207 func (l *LastFMService) fetchAllUserTracks(ctx context.Context) { 203 - log.Printf("Starting fetch cycle for %d users...", len(l.Usernames)) 208 + l.logger.Printf("Starting fetch cycle for %d users...", len(l.Usernames)) 204 209 var wg sync.WaitGroup // Use WaitGroup to fetch concurrently (optional) 205 210 fetchErrors := make(chan error, len(l.Usernames)) // Channel for errors 206 211 207 212 for _, username := range l.Usernames { 208 213 if ctx.Err() != nil { 209 - log.Printf("Context cancelled before starting fetch for user %s.", username) 214 + l.logger.Printf("Context cancelled before starting fetch for user %s.", username) 210 215 break // Exit loop if context is cancelled 211 216 } 212 217 ··· 214 219 go func(uname string) { // Launch fetch and process in a goroutine per user 215 220 defer wg.Done() 216 221 if ctx.Err() != nil { 217 - log.Printf("Context cancelled during fetch cycle for user %s.", uname) 222 + l.logger.Printf("Context cancelled during fetch cycle for user %s.", uname) 218 223 return // Exit goroutine if context is cancelled 219 224 } 220 225 ··· 223 228 const fetchLimit = 5 224 229 recentTracks, err := l.getRecentTracks(ctx, uname, fetchLimit) 225 230 if err != nil { 226 - log.Printf("Error fetching tracks for %s: %v", uname, err) 231 + l.logger.Printf("Error fetching tracks for %s: %v", uname, err) 227 232 fetchErrors <- fmt.Errorf("fetch failed for %s: %w", uname, err) // Report error 228 233 return 229 234 } 230 235 231 236 if recentTracks == nil || len(recentTracks.RecentTracks.Tracks) == 0 { 232 - log.Printf("No tracks returned for user %s", uname) 237 + l.logger.Printf("No tracks returned for user %s", uname) 233 238 return 234 239 } 235 240 236 241 // Process the fetched tracks 237 242 if err := l.processTracks(ctx, uname, recentTracks.RecentTracks.Tracks); err != nil { 238 - log.Printf("Error processing tracks for %s: %v", uname, err) 243 + l.logger.Printf("Error processing tracks for %s: %v", uname, err) 239 244 fetchErrors <- fmt.Errorf("process failed for %s: %w", uname, err) // Report error 240 245 } 241 246 }(username) ··· 247 252 // Log any errors that occurred during the fetch cycle 248 253 errorCount := 0 249 254 for err := range fetchErrors { 250 - log.Printf("Fetch cycle error: %v", err) 255 + l.logger.Printf("Fetch cycle error: %v", err) 251 256 errorCount++ 252 257 } 253 258 254 259 if errorCount > 0 { 255 - log.Printf("Finished fetch cycle with %d errors.", errorCount) 260 + l.logger.Printf("Finished fetch cycle with %d errors.", errorCount) 256 261 } else { 257 - log.Println("Finished fetch cycle successfully.") 262 + l.logger.Println("Finished fetch cycle successfully.") 258 263 } 259 264 } 260 265 ··· 274 279 } 275 280 276 281 if lastKnownTimestamp == nil { 277 - log.Printf("no previous scrobble timestamp found for user %s. processing latest track.", username) 282 + l.logger.Printf("no previous scrobble timestamp found for user %s. processing latest track.", username) 278 283 } else { 279 - log.Printf("last known scrobble for %s was at %s", username, lastKnownTimestamp.Format(time.RFC3339)) 284 + l.logger.Printf("last known scrobble for %s was at %s", username, lastKnownTimestamp.Format(time.RFC3339)) 280 285 } 281 286 282 287 var ( ··· 287 292 // handle now playing track separately 288 293 if len(tracks) > 0 && tracks[0].Attr != nil && tracks[0].Attr.NowPlaying == "true" { 289 294 nowPlayingTrack := tracks[0] 290 - log.Printf("now playing track for %s: %s - %s", username, nowPlayingTrack.Artist.Text, nowPlayingTrack.Name) 295 + l.logger.Printf("now playing track for %s: %s - %s", username, nowPlayingTrack.Artist.Text, nowPlayingTrack.Name) 291 296 l.mu.Lock() 292 297 lastSeen, existed := l.lastSeenNowPlaying[username] 293 298 // if our current track matches with last seen 294 299 // just compare artist/album/name for now 295 300 if existed && lastSeen.Album == nowPlayingTrack.Album && lastSeen.Name == nowPlayingTrack.Name && lastSeen.Artist == nowPlayingTrack.Artist { 296 - log.Printf("current track matches last seen track for %s", username) 301 + l.logger.Printf("current track matches last seen track for %s", username) 297 302 } else { 298 - log.Printf("current track does not match last seen track for %s", username) 303 + l.logger.Printf("current track does not match last seen track for %s", username) 299 304 // aha! we record this! 300 305 l.lastSeenNowPlaying[username] = nowPlayingTrack 301 306 } ··· 312 317 } 313 318 314 319 if lastNonNowPlaying == nil { 315 - log.Printf("no non-now-playing tracks found for user %s.", username) 320 + l.logger.Printf("no non-now-playing tracks found for user %s.", username) 316 321 return nil 317 322 } 318 323 319 324 latestTrackTime := lastNonNowPlaying.Date 320 325 321 326 // print both 322 - fmt.Printf("latestTrackTime: %s\n", latestTrackTime) 323 - fmt.Printf("lastKnownTimestamp: %s\n", lastKnownTimestamp) 327 + l.logger.Printf("latestTrackTime: %s\n", latestTrackTime) 328 + l.logger.Printf("lastKnownTimestamp: %s\n", lastKnownTimestamp) 324 329 325 330 if lastKnownTimestamp != nil && lastKnownTimestamp.Equal(latestTrackTime.Time) { 326 - log.Printf("no new tracks to process for user %s.", username) 331 + l.logger.Printf("no new tracks to process for user %s.", username) 327 332 return nil 328 333 } 329 334 330 335 for _, track := range tracks { 331 336 if track.Date == nil { 332 - log.Printf("skipping track without timestamp for %s: %s - %s", username, track.Artist.Text, track.Name) 337 + l.logger.Printf("skipping track without timestamp for %s: %s - %s", username, track.Artist.Text, track.Name) 333 338 continue 334 339 } 335 340 ··· 337 342 // before or at last known 338 343 if lastKnownTimestamp != nil && (trackTime.Before(*lastKnownTimestamp) || trackTime.Equal(*lastKnownTimestamp)) { 339 344 if processedCount == 0 { 340 - log.Printf("reached already known scrobbles for user %s (track time: %s, last known: %s).", 345 + l.logger.Printf("reached already known scrobbles for user %s (track time: %s, last known: %s).", 341 346 username, trackTime.Format(time.RFC3339), lastKnownTimestamp.Format(time.RFC3339)) 342 347 } 343 348 break ··· 360 365 361 366 hydratedTrack, err := musicbrainz.HydrateTrack(l.musicBrainzService, baseTrack) 362 367 if err != nil { 363 - log.Printf("error hydrating track for user %s: %s - %s: %v", username, track.Artist.Text, track.Name, err) 368 + l.logger.Printf("error hydrating track for user %s: %s - %s: %v", username, track.Artist.Text, track.Name, err) 364 369 // we can use the track without MBIDs, it's still valid 365 370 hydratedTrack = &baseTrack 366 371 } 367 372 l.db.SaveTrack(user.ID, hydratedTrack) 368 - log.Printf("Submitting track") 373 + l.logger.Printf("Submitting track") 369 374 err = l.SubmitTrackToPDS(*user.ATProtoDID, hydratedTrack, ctx) 370 375 if err != nil { 371 - log.Printf("error submitting track for user %s: %s - %s: %v", username, track.Artist.Text, track.Name, err) 376 + l.logger.Printf("error submitting track for user %s: %s - %s: %v", username, track.Artist.Text, track.Name, err) 372 377 } 373 378 processedCount++ 374 379 ··· 382 387 } 383 388 384 389 if processedCount > 0 { 385 - log.Printf("processed %d new track(s) for user %s. latest timestamp: %s", 390 + l.logger.Printf("processed %d new track(s) for user %s. latest timestamp: %s", 386 391 processedCount, username, latestProcessedTime.Format(time.RFC3339)) 387 392 } 388 393 ··· 407 412 } 408 413 409 414 // printout the session details 410 - fmt.Printf("Submitting track for the did: %+v\n", sess.DID) 415 + l.logger.Printf("Submitting track for the did: %+v\n", sess.DID) 411 416 412 417 artists := make([]*teal.AlphaFeedDefs_Artist, 0, len(track.Artist)) 413 418 for _, a := range track.Artist {
+11 -8
service/musicbrainz/musicbrainz.go
··· 8 8 "log" 9 9 "net/http" 10 10 "net/url" 11 + "os" 11 12 "sort" 12 13 "strings" 13 14 "sync" // Added for mutex ··· 75 76 cacheMutex sync.RWMutex // Mutex to protect the cache 76 77 cacheTTL time.Duration // Time-to-live for cache entries 77 78 cleaner MetadataCleaner // Cleaner for cleaning up expired cache entries 79 + logger *log.Logger // Logger for logging 78 80 } 79 81 80 82 // NewMusicBrainzService creates a new service instance with rate limiting and caching. ··· 83 85 limiter := rate.NewLimiter(rate.Every(time.Second), 1) 84 86 // Set a default cache TTL (e.g., 1 hour) 85 87 defaultCacheTTL := 1 * time.Hour 86 - 88 + logger := log.New(os.Stdout, "musicbrainz: ", log.LstdFlags|log.Lmsgprefix) 87 89 return &MusicBrainzService{ 88 90 db: db, 89 91 httpClient: &http.Client{ ··· 94 96 cacheTTL: defaultCacheTTL, // Set the cache TTL 95 97 cleaner: *NewMetadataCleaner("Latin"), // Initialize the cleaner 96 98 // cacheMutex is zero-value ready 99 + logger: logger, 97 100 } 98 101 } 99 102 ··· 127 130 s.cacheMutex.RUnlock() 128 131 129 132 if found && now.Before(entry.expiresAt) { 130 - log.Printf("Cache hit for MusicBrainz search: key=%s", cacheKey) 133 + s.logger.Printf("Cache hit for MusicBrainz search: key=%s", cacheKey) 131 134 // Return the cached data directly. Consider if a deep copy is needed if callers modify results. 132 135 return entry.recordings, nil 133 136 } 134 137 // --- Cache Miss or Expired --- 135 138 if found { 136 - log.Printf("Cache expired for MusicBrainz search: key=%s", cacheKey) 139 + s.logger.Printf("Cache expired for MusicBrainz search: key=%s", cacheKey) 137 140 } else { 138 - log.Printf("Cache miss for MusicBrainz search: key=%s", cacheKey) 141 + s.logger.Printf("Cache miss for MusicBrainz search: key=%s", cacheKey) 139 142 } 140 143 141 144 // --- Proceed with API call --- ··· 191 194 expiresAt: time.Now().UTC().Add(s.cacheTTL), 192 195 } 193 196 s.cacheMutex.Unlock() 194 - log.Printf("Cached MusicBrainz search result for key=%s, TTL=%s", cacheKey, s.cacheTTL) 197 + s.logger.Printf("Cached MusicBrainz search result for key=%s, TTL=%s", cacheKey, s.cacheTTL) 195 198 196 199 // Return the newly fetched results 197 200 return result.Recordings, nil 198 201 } 199 202 200 203 // GetBestRelease selects the 'best' release from a list based on specific criteria. 201 - func GetBestRelease(releases []MusicBrainzRelease, trackTitle string) *MusicBrainzRelease { 204 + func (s *MusicBrainzService) GetBestRelease(releases []MusicBrainzRelease, trackTitle string) *MusicBrainzRelease { 202 205 if len(releases) == 0 { 203 206 return nil 204 207 } ··· 251 254 } 252 255 253 256 // 3. If none found, return the oldest release overall (which is the first one after sorting) 254 - log.Printf("Could not find a suitable release for '%s', picking oldest: '%s' (%s)", trackTitle, releases[0].Title, releases[0].ID) 257 + s.logger.Printf("Could not find a suitable release for '%s', picking oldest: '%s' (%s)", trackTitle, releases[0].Title, releases[0].ID) 255 258 r := releases[0] 256 259 return &r 257 260 } ··· 279 282 } 280 283 281 284 firstResult := res[0] 282 - firstResultAlbum := GetBestRelease(firstResult.Releases, firstResult.Title) 285 + firstResultAlbum := mb.GetBestRelease(firstResult.Releases, firstResult.Title) 283 286 284 287 // woof. we Might not have any ISRCs! 285 288 var bestISRC string
+47 -42
service/spotify/spotify.go
··· 9 9 "log" 10 10 "net/http" 11 11 "net/url" 12 + "os" 12 13 "strings" 13 14 "sync" 14 15 "time" ··· 34 35 userTracks map[int64]*models.Track 35 36 userTokens map[int64]string 36 37 mu sync.RWMutex 38 + logger *log.Logger 37 39 } 38 40 39 41 func NewSpotifyService(database *db.DB, atprotoService *atprotoauth.ATprotoAuthService, musicBrainzService *musicbrainz.MusicBrainzService) *SpotifyService { 42 + logger := log.New(os.Stdout, "spotify: ", log.LstdFlags|log.Lmsgprefix) 43 + 40 44 return &SpotifyService{ 41 45 DB: database, 42 46 atprotoService: atprotoService, 43 47 mb: musicBrainzService, 44 48 userTracks: make(map[int64]*models.Track), 45 49 userTokens: make(map[int64]string), 50 + logger: logger, 46 51 } 47 52 } 48 53 49 54 func (s *SpotifyService) SubmitTrackToPDS(did string, track *models.Track, ctx context.Context) error { 50 55 client, err := s.atprotoService.GetATProtoClient() 51 56 if err != nil || client == nil { 52 - log.Printf("Error getting ATProto client: %v", err) 57 + s.logger.Printf("Error getting ATProto client: %v", err) 53 58 return fmt.Errorf("failed to get ATProto client: %w", err) 54 59 } 55 60 ··· 110 115 111 116 var out atproto.RepoCreateRecord_Output 112 117 if err := xrpcClient.Do(ctx, authArgs, xrpc.Procedure, "application/json", "com.atproto.repo.createRecord", nil, input, &out); err != nil { 113 - log.Printf("Error creating record for DID %s: %v. Input: %+v", did, err, input) 118 + s.logger.Printf("Error creating record for DID %s: %v. Input: %+v", did, err, input) 114 119 return fmt.Errorf("failed to create record on PDS for DID %s: %w", did, err) 115 120 } 116 121 117 - log.Printf("Successfully submitted track '%s' to PDS for DID %s. Record URI: %s", track.Name, did, out.Uri) 122 + s.logger.Printf("Successfully submitted track '%s' to PDS for DID %s. Record URI: %s", track.Name, did, out.Uri) 118 123 return nil 119 124 } 120 125 121 126 func (s *SpotifyService) SetAccessToken(token string, refreshToken string, userId int64, hasSession bool) (int64, error) { 122 127 userID, err := s.identifyAndStoreUser(token, refreshToken, userId, hasSession) 123 128 if err != nil { 124 - log.Printf("Error identifying and storing user: %v", err) 129 + s.logger.Printf("Error identifying and storing user: %v", err) 125 130 return 0, err 126 131 } 127 132 return userID, nil ··· 130 135 func (s *SpotifyService) identifyAndStoreUser(token string, refreshToken string, userId int64, hasSession bool) (int64, error) { 131 136 userProfile, err := s.fetchSpotifyProfile(token) 132 137 if err != nil { 133 - log.Printf("Error fetching Spotify profile: %v", err) 138 + s.logger.Printf("Error fetching Spotify profile: %v", err) 134 139 return 0, err 135 140 } 136 141 137 - fmt.Printf("uid: %d hasSession: %t", userId, hasSession) 142 + s.logger.Printf("uid: %d hasSession: %t", userId, hasSession) 138 143 139 144 user, err := s.DB.GetUserBySpotifyID(userProfile.ID) 140 145 if err != nil { 141 146 // This error might mean DB connection issue, not just user not found. 142 - log.Printf("Error checking for user by Spotify ID %s: %v", userProfile.ID, err) 147 + s.logger.Printf("Error checking for user by Spotify ID %s: %v", userProfile.ID, err) 143 148 return 0, err 144 149 } 145 150 ··· 148 153 // We don't intend users to log in via spotify! 149 154 if user == nil { 150 155 if !hasSession { 151 - log.Printf("User does not seem to exist") 156 + s.logger.Printf("User does not seem to exist") 152 157 return 0, fmt.Errorf("user does not seem to exist") 153 158 } else { 154 159 // overwrite prev user 155 160 user, err = s.DB.AddSpotifySession(userId, userProfile.DisplayName, userProfile.Email, userProfile.ID, token, refreshToken, tokenExpiryTime) 156 161 if err != nil { 157 - log.Printf("Error adding Spotify session for user ID %d: %v", userId, err) 162 + s.logger.Printf("Error adding Spotify session for user ID %d: %v", userId, err) 158 163 return 0, err 159 164 } 160 165 } ··· 162 167 err = s.DB.UpdateUserToken(user.ID, token, refreshToken, tokenExpiryTime) 163 168 if err != nil { 164 169 // for now log and continue 165 - log.Printf("Error updating user token for user ID %d: %v", user.ID, err) 170 + s.logger.Printf("Error updating user token for user ID %d: %v", user.ID, err) 166 171 } else { 167 - log.Printf("Updated token for existing user: %s (ID: %d)", *user.Username, user.ID) 172 + s.logger.Printf("Updated token for existing user: %s (ID: %d)", *user.Username, user.ID) 168 173 } 169 174 } 170 175 user.AccessToken = &token ··· 174 179 s.userTokens[user.ID] = token 175 180 s.mu.Unlock() 176 181 177 - log.Printf("User authenticated via Spotify: %s (ID: %d)", *user.Username, user.ID) 182 + s.logger.Printf("User authenticated via Spotify: %s (ID: %d)", *user.Username, user.ID) 178 183 return user.ID, nil 179 184 } 180 185 ··· 203 208 token, err := s.refreshTokenInner(user.ID) 204 209 if err != nil { 205 210 //Probably should remove the access token and refresh in long run? 206 - log.Printf("Error refreshing token for user %d: %v", user.ID, err) 211 + s.logger.Printf("Error refreshing token for user %d: %v", user.ID, err) 207 212 continue 208 213 } 209 214 s.userTokens[user.ID] = token 210 215 } 211 216 } 212 217 213 - log.Printf("Loaded %d active users with valid tokens", count) 218 + s.logger.Printf("Loaded %d active users with valid tokens", count) 214 219 return nil 215 220 } 216 221 ··· 280 285 // Also clear the bad refresh token from the DB 281 286 updateErr := s.DB.UpdateUserToken(userID, "", "", time.Now().UTC()) // Clear tokens 282 287 if updateErr != nil { 283 - log.Printf("Failed to clear bad refresh token for user %d: %v", userID, updateErr) 288 + s.logger.Printf("Failed to clear bad refresh token for user %d: %v", userID, updateErr) 284 289 } 285 290 return "", fmt.Errorf("spotify token refresh failed (%d): %s", resp.StatusCode, string(body)) 286 291 } ··· 306 311 // Update DB 307 312 if err := s.DB.UpdateUserToken(userID, tokenResponse.AccessToken, newRefreshToken, newExpiry); err != nil { 308 313 // Log error but continue, as we have the token in memory 309 - log.Printf("Error updating user token in DB for user %d after refresh: %v", userID, err) 314 + s.logger.Printf("Error updating user token in DB for user %d after refresh: %v", userID, err) 310 315 } 311 316 312 317 // Update in-memory cache ··· 314 319 s.userTokens[userID] = tokenResponse.AccessToken 315 320 s.mu.Unlock() 316 321 317 - log.Printf("Successfully refreshed token for user %d", userID) 322 + s.logger.Printf("Successfully refreshed token for user %d", userID) 318 323 return tokenResponse.AccessToken, nil 319 324 } 320 325 ··· 329 334 func (s *SpotifyService) RefreshExpiredTokens() { 330 335 users, err := s.DB.GetUsersWithExpiredTokens() 331 336 if err != nil { 332 - log.Printf("Error fetching users with expired tokens: %v", err) 337 + s.logger.Printf("Error fetching users with expired tokens: %v", err) 333 338 return 334 339 } 335 340 ··· 344 349 345 350 if err != nil { 346 351 // just print out errors here for now 347 - log.Printf("Error from service/spotify/spotify.go when refreshing tokens: %s", err.Error()) 352 + s.logger.Printf("Error from service/spotify/spotify.go when refreshing tokens: %s", err.Error()) 348 353 } 349 354 350 355 refreshed++ 351 356 } 352 357 353 358 if refreshed > 0 { 354 - log.Printf("Refreshed tokens for %d users", refreshed) 359 + s.logger.Printf("Refreshed tokens for %d users", refreshed) 355 360 } 356 361 } 357 362 ··· 412 417 tracks, err := s.DB.GetRecentTracks(userID, 20) 413 418 if err != nil { 414 419 http.Error(w, "Error retrieving track history", http.StatusInternalServerError) 415 - log.Printf("Error retrieving track history: %v", err) 420 + s.logger.Printf("Error retrieving track history: %v", err) 416 421 return 417 422 } 418 423 ··· 454 459 455 460 // oops, token expired or other client error 456 461 if resp.StatusCode == 401 && attempt == 0 { // Only refresh on 401 on the first attempt 457 - log.Printf("Spotify token potentially expired for user %d, attempting refresh...", userID) 462 + s.logger.Printf("Spotify token potentially expired for user %d, attempting refresh...", userID) 458 463 newAccessToken, refreshErr := s.refreshTokenInner(userID) 459 464 if refreshErr != nil { 460 - log.Printf("Token refresh failed for user %d: %v", userID, refreshErr) 465 + s.logger.Printf("Token refresh failed for user %d: %v", userID, refreshErr) 461 466 // No point retrying if refresh failed 462 467 return nil, fmt.Errorf("spotify token expired or invalid for user %d and refresh failed: %w", userID, refreshErr) 463 468 } 464 - log.Printf("Token refreshed for user %d, retrying request...", userID) 469 + s.logger.Printf("Token refreshed for user %d, retrying request...", userID) 465 470 token = newAccessToken // Update token for the next attempt 466 471 req.Header.Set("Authorization", "Bearer "+token) // Update header for retry 467 472 continue // Go to next attempt in the loop ··· 555 560 for range ticker.C { 556 561 err := s.LoadAllUsers() 557 562 if err != nil { 558 - log.Printf("Error loading spotify users: %v", err) 563 + s.logger.Printf("Error loading spotify users: %v", err) 559 564 continue 560 565 } 561 566 // copy userIDs to avoid holding the lock too long ··· 569 574 for _, userID := range userIDs { 570 575 track, err := s.FetchCurrentTrack(userID) 571 576 if err != nil { 572 - log.Printf("Error fetching track for user %d: %v", userID, err) 577 + s.logger.Printf("Error fetching track for user %d: %v", userID, err) 573 578 continue 574 579 } 575 580 ··· 603 608 604 609 // just log when we stamp tracks 605 610 if isNewTrack && isLastTrackStamped && !currentTrack.HasStamped { 606 - log.Printf("User %d stamped (previous) track: %s by %s", userID, currentTrack.Name, currentTrack.Artist) 611 + s.logger.Printf("User %d stamped (previous) track: %s by %s", userID, currentTrack.Name, currentTrack.Artist) 607 612 currentTrack.HasStamped = true 608 613 if currentTrack.PlayID != 0 { 609 614 s.DB.UpdateTrack(currentTrack.PlayID, currentTrack) 610 615 611 - log.Printf("Updated!") 616 + s.logger.Printf("Updated!") 612 617 } 613 618 } 614 619 615 620 if isStamped && !currentTrack.HasStamped { 616 - log.Printf("User %d stamped track: %s by %s", userID, track.Name, track.Artist) 621 + s.logger.Printf("User %d stamped track: %s by %s", userID, track.Name, track.Artist) 617 622 track.HasStamped = true 618 623 // if currenttrack has a playid and the last track is the same as the current track 619 624 if !isNewTrack && currentTrack.PlayID != 0 { ··· 624 629 s.userTracks[userID] = track 625 630 s.mu.Unlock() 626 631 627 - log.Printf("Updated!") 632 + s.logger.Printf("Updated!") 628 633 } 629 634 } 630 635 631 636 if isNewTrack { 632 637 id, err := s.DB.SaveTrack(userID, track) 633 638 if err != nil { 634 - log.Printf("Error saving track for user %d: %v", userID, err) 639 + s.logger.Printf("Error saving track for user %d: %v", userID, err) 635 640 continue 636 641 } 637 642 ··· 645 650 // The 'track' variable is *models.Track and has been saved to DB, PlayID is populated. 646 651 dbUser, errUser := s.DB.GetUserByID(userID) // Fetch user by their internal ID 647 652 if errUser != nil { 648 - log.Printf("User %d: Error fetching user details for PDS submission: %v", userID, errUser) 653 + s.logger.Printf("User %d: Error fetching user details for PDS submission: %v", userID, errUser) 649 654 } else if dbUser == nil { 650 - log.Printf("User %d: User not found in DB. Skipping PDS submission.", userID) 655 + s.logger.Printf("User %d: User not found in DB. Skipping PDS submission.", userID) 651 656 } else if dbUser.ATProtoDID == nil || *dbUser.ATProtoDID == "" { 652 - log.Printf("User %d (%d): ATProto DID not set. Skipping PDS submission for track '%s'.", userID, dbUser.ATProtoDID, track.Name) 657 + s.logger.Printf("User %d (%d): ATProto DID not set. Skipping PDS submission for track '%s'.", userID, dbUser.ATProtoDID, track.Name) 653 658 } else { 654 659 // User has a DID, proceed with hydration and submission 655 660 var trackToSubmitToPDS *models.Track = track // Default to the original track (already *models.Track) ··· 658 663 // and it returns *models.Track 659 664 hydratedTrack, errHydrate := musicbrainz.HydrateTrack(s.mb, *track) 660 665 if errHydrate != nil { 661 - log.Printf("User %d (%d): Error hydrating track '%s' with MusicBrainz: %v. Proceeding with original track data for PDS.", userID, dbUser.ATProtoDID, track.Name, errHydrate) 666 + s.logger.Printf("User %d (%d): Error hydrating track '%s' with MusicBrainz: %v. Proceeding with original track data for PDS.", userID, dbUser.ATProtoDID, track.Name, errHydrate) 662 667 } else { 663 - log.Printf("User %d (%d): Successfully hydrated track '%s' with MusicBrainz.", userID, dbUser.ATProtoDID, track.Name) 668 + s.logger.Printf("User %d (%d): Successfully hydrated track '%s' with MusicBrainz.", userID, dbUser.ATProtoDID, track.Name) 664 669 trackToSubmitToPDS = hydratedTrack // hydratedTrack is *models.Track 665 670 } 666 671 } else { 667 - log.Printf("User %d (%d): MusicBrainz service not configured. Proceeding with original track data for PDS.", userID, dbUser.ATProtoDID) 672 + s.logger.Printf("User %d (%d): MusicBrainz service not configured. Proceeding with original track data for PDS.", userID, dbUser.ATProtoDID) 668 673 } 669 674 670 675 artistName := "Unknown Artist" ··· 672 677 artistName = trackToSubmitToPDS.Artist[0].Name 673 678 } 674 679 675 - log.Printf("User %d (%d): Attempting to submit track '%s' by %s to PDS (DID: %s)", userID, dbUser.ATProtoDID, trackToSubmitToPDS.Name, artistName, *dbUser.ATProtoDID) 680 + s.logger.Printf("User %d (%d): Attempting to submit track '%s' by %s to PDS (DID: %s)", userID, dbUser.ATProtoDID, trackToSubmitToPDS.Name, artistName, *dbUser.ATProtoDID) 676 681 // Use context.Background() for now, or pass down a context if available 677 682 if errPDS := s.SubmitTrackToPDS(*dbUser.ATProtoDID, trackToSubmitToPDS, context.Background()); errPDS != nil { 678 - log.Printf("User %d (%d): Error submitting track '%s' to PDS: %v", userID, dbUser.ATProtoDID, trackToSubmitToPDS.Name, errPDS) 683 + s.logger.Printf("User %d (%d): Error submitting track '%s' to PDS: %v", userID, dbUser.ATProtoDID, trackToSubmitToPDS.Name, errPDS) 679 684 } else { 680 - log.Printf("User %d (%d): Successfully submitted track '%s' to PDS.", userID, dbUser.ATProtoDID, trackToSubmitToPDS.Name) 685 + s.logger.Printf("User %d (%d): Successfully submitted track '%s' to PDS.", userID, dbUser.ATProtoDID, trackToSubmitToPDS.Name) 681 686 } 682 687 } 683 688 // End of PDS submission block 684 689 685 - log.Printf("User %d is listening to: %s by %s", userID, track.Name, track.Artist) 690 + s.logger.Printf("User %d is listening to: %s by %s", userID, track.Name, track.Artist) 686 691 } 687 692 } 688 693 689 694 //unloading users to save memory and make sure we get new signups 690 695 err = s.LoadAllUsers() 691 696 if err != nil { 692 - log.Printf("Error loading spotify users: %v", err) 697 + s.logger.Printf("Error loading spotify users: %v", err) 693 698 } 694 699 } 695 700 }