continue fixing up sync

This commit is contained in:
Daniel Bulant 2026-04-20 17:58:50 +02:00
parent d4f9a8c2dd
commit 332ddd76cf
No known key found for this signature in database
2 changed files with 222 additions and 171 deletions

View file

@ -43,30 +43,39 @@ type DbTransaction = Parameters<typeof db.transaction>[0] extends (
type DbLike = DbClient | DbTransaction; type DbLike = DbClient | DbTransaction;
export async function upsertImages(images: Image[], dbClient: DbLike = db) { export async function upsertImages(images: Image[], dbClient: DbLike = db) {
await dbClient.insert(platformImage).values( await dbClient
images.map(({ url, height, width }) => ({ .insert(platformImage)
platform: PLATFORM_SPOTIFY, .values(
url, images.map(({ url, height, width }) => ({
height, platform: PLATFORM_SPOTIFY,
width, url,
})), height,
); width,
})),
)
.onConflictDoNothing();
} }
export async function upsertGenres(genres: string[], dbClient: DbLike = db) { export async function upsertGenres(genres: string[], dbClient: DbLike = db) {
await dbClient.insert(genre).values(genres.map((name) => ({ name }))); await dbClient
.insert(genre)
.values(genres.map((name) => ({ name })))
.onConflictDoNothing();
} }
export async function upsertArtists(artists: Artist[], dbClient: DbLike = db) { export async function upsertArtists(artists: Artist[], dbClient: DbLike = db) {
await dbClient.insert(artist).values( await dbClient
artists.map(({ id, name, images, genres, popularity, type }) => ({ .insert(artist)
platform: PLATFORM_SPOTIFY, .values(
platform_id: id, artists.map(({ id, name, images, genres, popularity, type }) => ({
name, platform: PLATFORM_SPOTIFY,
popularity, platform_id: id,
type, name,
})), popularity,
); type,
})),
)
.onConflictDoNothing();
await upsertImages( await upsertImages(
artists.flatMap((a) => a.images), artists.flatMap((a) => a.images),
dbClient, dbClient,
@ -76,34 +85,40 @@ export async function upsertArtists(artists: Artist[], dbClient: DbLike = db) {
dbClient, dbClient,
); );
for (const spotifyArtist of artists) { for (const spotifyArtist of artists) {
await dbClient.insert(artistImage).select( await dbClient
dbClient .insert(artistImage)
.select({ .select(
artistId: artist.id, dbClient
imageId: platformImage.id, .select({
}) artistId: artist.id,
.from(platformImage) imageId: platformImage.id,
.where( })
and( .from(platformImage)
eq(platformImage.platform, PLATFORM_SPOTIFY), .where(
inArray( and(
platformImage.url, eq(platformImage.platform, PLATFORM_SPOTIFY),
spotifyArtist.images.map((t) => t.url), inArray(
platformImage.url,
spotifyArtist.images.map((t) => t.url),
),
), ),
), )
) .innerJoin(artist, eq(artist.platform_id, spotifyArtist.id)),
.innerJoin(artist, eq(artist.platform_id, spotifyArtist.id)), )
); .onConflictDoNothing();
await dbClient.insert(artistGenre).select( await dbClient
dbClient .insert(artistGenre)
.select({ .select(
artistId: artist.id, dbClient
genreId: genre.id, .select({
}) artistId: artist.id,
.from(genre) genreId: genre.id,
.where(inArray(genre.name, spotifyArtist.genres)) })
.innerJoin(artist, eq(artist.platform_id, spotifyArtist.id)), .from(genre)
); .where(inArray(genre.name, spotifyArtist.genres))
.innerJoin(artist, eq(artist.platform_id, spotifyArtist.id)),
)
.onConflictDoNothing();
} }
} }
@ -191,17 +206,20 @@ export async function upsertAlbums(
albums.flatMap((a) => a.artists), albums.flatMap((a) => a.artists),
dbClient, dbClient,
); );
await dbClient.insert(album).values( await dbClient
albums.map(({ id, name, type, popularity, release_date, label }) => ({ .insert(album)
platform: PLATFORM_SPOTIFY, .values(
platform_id: id, albums.map(({ id, name, type, popularity, release_date, label }) => ({
name, platform: PLATFORM_SPOTIFY,
type, platform_id: id,
popularity, name,
release_date: new Date(release_date), type,
label, popularity,
})), release_date: new Date(release_date),
); label,
})),
)
.onConflictDoNothing();
await upsertImages( await upsertImages(
albums.flatMap((a) => a.images), albums.flatMap((a) => a.images),
dbClient, dbClient,
@ -211,49 +229,58 @@ export async function upsertAlbums(
dbClient, dbClient,
); );
for (const spotifyAlbum of albums) { for (const spotifyAlbum of albums) {
await dbClient.insert(albumImage).select( await dbClient
dbClient .insert(albumImage)
.select({ .select(
albumId: album.id, dbClient
imageId: platformImage.id, .select({
}) albumId: album.id,
.from(platformImage) imageId: platformImage.id,
.where( })
and( .from(platformImage)
eq(platformImage.platform, PLATFORM_SPOTIFY), .where(
inArray( and(
platformImage.url, eq(platformImage.platform, PLATFORM_SPOTIFY),
spotifyAlbum.images.map((t) => t.url), inArray(
platformImage.url,
spotifyAlbum.images.map((t) => t.url),
),
), ),
), )
) .innerJoin(album, eq(album.platform_id, spotifyAlbum.id)),
.innerJoin(album, eq(album.platform_id, spotifyAlbum.id)), )
); .onConflictDoNothing();
await dbClient.insert(albumArtist).select( await dbClient
dbClient .insert(albumArtist)
.select({ .select(
albumId: album.id, dbClient
artistId: artist.id, .select({
}) albumId: album.id,
.from(artist) artistId: artist.id,
.where( })
inArray( .from(artist)
artist.platform_id, .where(
spotifyAlbum.artists.map((t) => t.id), inArray(
), artist.platform_id,
) spotifyAlbum.artists.map((t) => t.id),
.innerJoin(album, eq(album.platform_id, spotifyAlbum.id)), ),
); )
await dbClient.insert(albumGenre).select( .innerJoin(album, eq(album.platform_id, spotifyAlbum.id)),
dbClient )
.select({ .onConflictDoNothing();
albumId: album.id, await dbClient
genreId: genre.id, .insert(albumGenre)
}) .select(
.from(genre) dbClient
.where(inArray(genre.name, spotifyAlbum.genres)) .select({
.innerJoin(album, eq(album.platform_id, spotifyAlbum.id)), albumId: album.id,
); genreId: genre.id,
})
.from(genre)
.where(inArray(genre.name, spotifyAlbum.genres))
.innerJoin(album, eq(album.platform_id, spotifyAlbum.id)),
)
.onConflictDoNothing();
} }
} }
@ -271,35 +298,41 @@ export async function upsertTracks(tracks: Track[], dbClient: DbLike = db) {
tracks.map((t) => t.album.id), tracks.map((t) => t.album.id),
dbClient, dbClient,
); );
await dbClient.insert(track).values( await dbClient
tracks.map((spotifyTrack) => ({ .insert(track)
albumId: albumIdMap.get(spotifyTrack.album.id)!, .values(
name: spotifyTrack.name, tracks.map((spotifyTrack) => ({
platform: PLATFORM_SPOTIFY, albumId: albumIdMap.get(spotifyTrack.album.id)!,
platform_id: spotifyTrack.id, name: spotifyTrack.name,
popularity: spotifyTrack.popularity, platform: PLATFORM_SPOTIFY,
duration: spotifyTrack.duration_ms, platform_id: spotifyTrack.id,
explicit: spotifyTrack.explicit, popularity: spotifyTrack.popularity,
disc_number: spotifyTrack.disc_number, duration: spotifyTrack.duration_ms,
track_number: spotifyTrack.track_number, explicit: spotifyTrack.explicit,
})), disc_number: spotifyTrack.disc_number,
); track_number: spotifyTrack.track_number,
})),
)
.onConflictDoNothing();
for (const spotifyTrack of tracks) { for (const spotifyTrack of tracks) {
await dbClient.insert(trackArtist).select( await dbClient
dbClient .insert(trackArtist)
.select({ .select(
trackId: track.id, dbClient
artistId: artist.id, .select({
}) trackId: track.id,
.from(artist) artistId: artist.id,
.where( })
inArray( .from(artist)
artist.platform_id, .where(
spotifyTrack.artists.map((t) => t.id), inArray(
), artist.platform_id,
) spotifyTrack.artists.map((t) => t.id),
.innerJoin(track, eq(track.platform_id, spotifyTrack.id)), ),
); )
.innerJoin(track, eq(track.platform_id, spotifyTrack.id)),
)
.onConflictDoNothing();
} }
} }
@ -315,14 +348,17 @@ export async function upsertTopArtists(
artists.map((t) => t.id), artists.map((t) => t.id),
dbClient, dbClient,
); );
await dbClient.insert(topArtist).values( await dbClient
artists.map((spotifyArtist, index) => ({ .insert(topArtist)
artistId: artistIdMap.get(spotifyArtist.id)!, .values(
position: index + 1, artists.map((spotifyArtist, index) => ({
userId, artistId: artistIdMap.get(spotifyArtist.id)!,
timeline, position: index + 1,
})), userId,
); timeline,
})),
)
.onConflictDoNothing();
} }
export async function upsertTopTracks( export async function upsertTopTracks(
@ -337,14 +373,17 @@ export async function upsertTopTracks(
tracks.map((t) => t.id), tracks.map((t) => t.id),
dbClient, dbClient,
); );
await dbClient.insert(topTrack).values( await dbClient
tracks.map((spotifyTrack, index) => ({ .insert(topTrack)
trackId: trackIdMap.get(spotifyTrack.id)!, .values(
position: index + 1, tracks.map((spotifyTrack, index) => ({
userId, trackId: trackIdMap.get(spotifyTrack.id)!,
timeline, position: index + 1,
})), userId,
); timeline,
})),
)
.onConflictDoNothing();
} }
export async function upsertSavedAlbums( export async function upsertSavedAlbums(
@ -359,13 +398,16 @@ export async function upsertSavedAlbums(
albums.map((t) => t.id), albums.map((t) => t.id),
dbClient, dbClient,
); );
await dbClient.insert(savedAlbum).values( await dbClient
saved.map((item) => ({ .insert(savedAlbum)
albumId: albumIdMap.get(item.album.id)!, .values(
userId, saved.map((item) => ({
saved_at: new Date(item.added_at), albumId: albumIdMap.get(item.album.id)!,
})), userId,
); saved_at: new Date(item.added_at),
})),
)
.onConflictDoNothing();
} }
export async function upsertSavedTracks( export async function upsertSavedTracks(
@ -380,13 +422,16 @@ export async function upsertSavedTracks(
tracks.map((t) => t.id), tracks.map((t) => t.id),
dbClient, dbClient,
); );
await dbClient.insert(savedTrack).values( await dbClient
saved.map((item) => ({ .insert(savedTrack)
trackId: trackIdMap.get(item.track.id)!, .values(
userId, saved.map((item) => ({
saved_at: new Date(item.added_at), trackId: trackIdMap.get(item.track.id)!,
})), userId,
); saved_at: new Date(item.added_at),
})),
)
.onConflictDoNothing();
} }
export async function upsertFollowedArtists( export async function upsertFollowedArtists(
@ -400,12 +445,15 @@ export async function upsertFollowedArtists(
artists.map((t) => t.id), artists.map((t) => t.id),
dbClient, dbClient,
); );
await dbClient.insert(followedArtist).values( await dbClient
artists.map((spotifyArtist) => ({ .insert(followedArtist)
artistId: artistIdMap.get(spotifyArtist.id)!, .values(
userId, artists.map((spotifyArtist) => ({
})), artistId: artistIdMap.get(spotifyArtist.id)!,
); userId,
})),
)
.onConflictDoNothing();
} }
export async function upsertPlaybackHistory( export async function upsertPlaybackHistory(
@ -420,11 +468,14 @@ export async function upsertPlaybackHistory(
tracks.map((t) => t.id), tracks.map((t) => t.id),
dbClient, dbClient,
); );
await dbClient.insert(playbackHistory).values( await dbClient
items.map((item) => ({ .insert(playbackHistory)
trackId: trackIdMap.get(item.track.id)!, .values(
userId, items.map((item) => ({
played_at: new Date(item.played_at), trackId: trackIdMap.get(item.track.id)!,
})), userId,
); played_at: new Date(item.played_at),
})),
)
.onConflictDoNothing();
} }

View file

@ -140,8 +140,8 @@ export class SpotifySyncWorkflow extends ConfiguredInstance {
const page = await sdk.currentUser.followedArtists(after, 50); const page = await sdk.currentUser.followedArtists(after, 50);
const artists = page.artists; const artists = page.artists;
followed.push(...artists.items); followed.push(...artists.items);
if (!artists.next) break; if (!artists.next || artists.items.length === 0) break;
after = artists.next; after = artists.items[artists.items.length - 1]!.id;
} }
return followed; return followed;
} }