+1
.gitignore
+1
.gitignore
+2
-2
db/db.go
+2
-2
db/db.go
···
159
user := &models.User{}
160
161
err := db.QueryRow(`
162
-
SELECT id, username, email, spotify_id, access_token, refresh_token, token_expiry, lastfm_username, created_at, updated_at
163
FROM users WHERE id = ?`, ID).Scan(
164
-
&user.ID, &user.Username, &user.Email, &user.SpotifyID,
165
&user.AccessToken, &user.RefreshToken, &user.TokenExpiry,
166
&user.LastFMUsername,
167
&user.CreatedAt, &user.UpdatedAt)
···
159
user := &models.User{}
160
161
err := db.QueryRow(`
162
+
SELECT id, username, email, atproto_did, spotify_id, access_token, refresh_token, token_expiry, lastfm_username, created_at, updated_at
163
FROM users WHERE id = ?`, ID).Scan(
164
+
&user.ID, &user.Username, &user.Email, &user.ATProtoDID, &user.SpotifyID,
165
&user.AccessToken, &user.RefreshToken, &user.TokenExpiry,
166
&user.LastFMUsername,
167
&user.CreatedAt, &user.UpdatedAt)
+4
-15
service/lastfm/lastfm.go
+4
-15
service/lastfm/lastfm.go
···
316
return nil
317
}
318
319
-
uts, err := strconv.ParseInt(lastNonNowPlaying.Date.UTS, 10, 64)
320
-
if err != nil {
321
-
log.Printf("error parsing timestamp '%s' for track %s - %s: %v",
322
-
lastNonNowPlaying.Date.UTS, lastNonNowPlaying.Artist.Text, lastNonNowPlaying.Name, err)
323
-
}
324
-
latestTrackTime := time.Unix(uts, 0)
325
326
// print both
327
fmt.Printf("latestTrackTime: %s\n", latestTrackTime)
328
fmt.Printf("lastKnownTimestamp: %s\n", lastKnownTimestamp)
329
330
-
if lastKnownTimestamp != nil && lastKnownTimestamp.Equal(latestTrackTime) {
331
log.Printf("no new tracks to process for user %s.", username)
332
return nil
333
}
334
335
for _, track := range tracks {
336
-
if track.Date == nil || track.Date.UTS == "" {
337
log.Printf("skipping track without timestamp for %s: %s - %s", username, track.Artist.Text, track.Name)
338
continue
339
}
340
341
-
uts, err := strconv.ParseInt(track.Date.UTS, 10, 64)
342
-
if err != nil {
343
-
log.Printf("error parsing timestamp '%s' for track %s - %s: %v", track.Date.UTS, track.Artist.Text, track.Name, err)
344
-
continue
345
-
}
346
-
trackTime := time.Unix(uts, 0)
347
-
348
// before or at last known
349
if lastKnownTimestamp != nil && (trackTime.Before(*lastKnownTimestamp) || trackTime.Equal(*lastKnownTimestamp)) {
350
if processedCount == 0 {
···
316
return nil
317
}
318
319
+
latestTrackTime := lastNonNowPlaying.Date
320
321
// print both
322
fmt.Printf("latestTrackTime: %s\n", latestTrackTime)
323
fmt.Printf("lastKnownTimestamp: %s\n", lastKnownTimestamp)
324
325
+
if lastKnownTimestamp != nil && lastKnownTimestamp.Equal(latestTrackTime.Time) {
326
log.Printf("no new tracks to process for user %s.", username)
327
return nil
328
}
329
330
for _, track := range tracks {
331
+
if track.Date == nil {
332
log.Printf("skipping track without timestamp for %s: %s - %s", username, track.Artist.Text, track.Name)
333
continue
334
}
335
336
+
trackTime := track.Date.Time
337
// before or at last known
338
if lastKnownTimestamp != nil && (trackTime.Before(*lastKnownTimestamp) || trackTime.Equal(*lastKnownTimestamp)) {
339
if processedCount == 0 {
+36
-2
service/lastfm/model.go
+36
-2
service/lastfm/model.go
···
1
package lastfm
2
3
// Structs to represent the Last.fm API response for user.getrecenttracks
4
type RecentTracksResponse struct {
5
RecentTracks RecentTracks `json:"recenttracks"`
···
19
Name string `json:"name"`
20
URL string `json:"url"`
21
Date *TrackDate `json:"date,omitempty"` // Use pointer for optional fields
22
-
Attr *struct { // Custom handling for @attr.nowplaying
23
NowPlaying string `json:"nowplaying"` // Field name corrected to match struct tag
24
} `json:"@attr,omitempty"` // This captures the @attr object within the track
25
}
···
39
Text string `json:"#text"` // Album name
40
}
41
42
-
type TrackDate struct {
43
UTS string `json:"uts"` // Unix timestamp string
44
Text string `json:"#text"` // Human-readable date string
45
}
46
47
type TrackXMLAttr struct {
···
1
package lastfm
2
3
+
import (
4
+
"encoding/json"
5
+
"strconv"
6
+
"time"
7
+
)
8
+
9
// Structs to represent the Last.fm API response for user.getrecenttracks
10
type RecentTracksResponse struct {
11
RecentTracks RecentTracks `json:"recenttracks"`
···
25
Name string `json:"name"`
26
URL string `json:"url"`
27
Date *TrackDate `json:"date,omitempty"` // Use pointer for optional fields
28
+
Attr *struct { // Custom handling for @attr.nowplaying
29
NowPlaying string `json:"nowplaying"` // Field name corrected to match struct tag
30
} `json:"@attr,omitempty"` // This captures the @attr object within the track
31
}
···
45
Text string `json:"#text"` // Album name
46
}
47
48
+
// ApiTrackDate This is the real structure returned from lastFM.
49
+
// Represents a date associated with a track, including both a Unix timestamp and a human-readable string.
50
+
// UTS is a Unix timestamp stored as a string.
51
+
// Text contains the human-readable date string.
52
+
type ApiTrackDate struct {
53
UTS string `json:"uts"` // Unix timestamp string
54
Text string `json:"#text"` // Human-readable date string
55
+
}
56
+
57
+
// TrackDate This is the struct we use to represent a date associated with a track.
58
+
// It is a wrapper around time.Time that implements json.Unmarshaler.
59
+
type TrackDate struct {
60
+
time.Time
61
+
}
62
+
63
+
// UnmarshalJSON Implements json.Unmarshaler.
64
+
// Parses the UTS field from the API response and converts it to a time.Time.
65
+
// The time.Time is stored in the Time field.
66
+
// The Text field is ignored since it can be parsed from the Time field if needed.
67
+
func (t *TrackDate) UnmarshalJSON(b []byte) (err error) {
68
+
var apiTrackDate ApiTrackDate
69
+
if err := json.Unmarshal(b, &apiTrackDate); err != nil {
70
+
return err
71
+
}
72
+
uts, err := strconv.ParseInt(apiTrackDate.UTS, 10, 64)
73
+
if err != nil {
74
+
return err
75
+
}
76
+
date := time.Unix(uts, 0).UTC()
77
+
t.Time = date
78
+
return
79
}
80
81
type TrackXMLAttr struct {
+1
-11
service/spotify/spotify.go
+1
-11
service/spotify/spotify.go
···
507
return nil, fmt.Errorf("failed to unmarshal spotify response: %w", err)
508
}
509
510
-
body, ioErr := io.ReadAll(resp.Body)
511
-
if ioErr != nil {
512
-
return nil, ioErr
513
-
}
514
-
515
-
err = json.Unmarshal(body, &response)
516
-
if err != nil {
517
-
return nil, err
518
-
}
519
-
520
var artists []models.Artist
521
for _, artist := range response.Item.Artists {
522
artists = append(artists, models.Artist{
···
536
ServiceBaseUrl: "open.spotify.com",
537
ISRC: response.Item.ExternalIDs.ISRC,
538
HasStamped: false,
539
-
Timestamp: time.Now(),
540
}
541
542
return track, nil
···
507
return nil, fmt.Errorf("failed to unmarshal spotify response: %w", err)
508
}
509
510
var artists []models.Artist
511
for _, artist := range response.Item.Artists {
512
artists = append(artists, models.Artist{
···
526
ServiceBaseUrl: "open.spotify.com",
527
ISRC: response.Item.ExternalIDs.ISRC,
528
HasStamped: false,
529
+
Timestamp: time.Now().UTC(),
530
}
531
532
return track, nil