From de4da1a30eb8f978e06bdcea935607d3b41509e5 Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Mon, 6 Oct 2025 12:53:16 +0300 Subject: [PATCH] feat: update xata_version fields to allow null values across multiple schemas - Changed xata_version fields in various schemas (album-tracks, artist-albums, artist-tracks, dropbox-accounts, dropbox-directories, dropbox-paths, dropbox, google-drive-accounts, google-drive-directories, google-drive-paths, googledrive, spotify-accounts, spotify-tokens, user-albums, user-artists, user-tracks) to allow null values. - Updated snapshot and journal files to reflect changes in database schema. - Implemented batch processing for syncing album tracks, artist albums, artist tracks, playlist tracks, user albums, user artists, user tracks, and user playlists in the data pull process. - Added new repository functions for handling album tracks, user albums, artist albums, artist tracks, user artists, playlist tracks, user playlists, user tracks, and their respective insert operations. - Enhanced user album, user artist, user playlist, and user track structs to include optional URI fields. --- ...lite_pixie.sql => 0000_left_swordsman.sql} | 32 +- apps/api/drizzle/meta/0000_snapshot.json | 34 +- apps/api/drizzle/meta/_journal.json | 4 +- apps/api/src/schema/album-tracks.ts | 2 +- apps/api/src/schema/artist-albums.ts | 2 +- apps/api/src/schema/artist-tracks.ts | 2 +- apps/api/src/schema/dropbox-accounts.ts | 2 +- apps/api/src/schema/dropbox-directories.ts | 2 +- apps/api/src/schema/dropbox-paths.ts | 2 +- apps/api/src/schema/dropbox.ts | 2 +- apps/api/src/schema/google-drive-accounts.ts | 2 +- .../src/schema/google-drive-directories.ts | 2 +- apps/api/src/schema/google-drive-paths.ts | 2 +- apps/api/src/schema/googledrive.ts | 2 +- apps/api/src/schema/spotify-accounts.ts | 2 +- apps/api/src/schema/spotify-tokens.ts | 2 +- apps/api/src/schema/user-albums.ts | 2 +- apps/api/src/schema/user-artists.ts | 2 +- apps/api/src/schema/user-tracks.ts | 2 +- crates/pgpull/src/lib.rs | 336 ++++++++++++++++++ crates/pgpull/src/repo/album.rs | 72 +++- crates/pgpull/src/repo/artist.rs | 114 +++++- crates/pgpull/src/repo/playlist.rs | 78 +++- crates/pgpull/src/repo/track.rs | 38 +- crates/pgpull/src/xata/user_album.rs | 1 + crates/pgpull/src/xata/user_artist.rs | 1 + crates/pgpull/src/xata/user_playlist.rs | 1 + crates/pgpull/src/xata/user_track.rs | 1 + 28 files changed, 689 insertions(+), 55 deletions(-) rename apps/api/drizzle/{0000_polite_pixie.sql => 0000_left_swordsman.sql} (97%) diff --git a/apps/api/drizzle/0000_polite_pixie.sql b/apps/api/drizzle/0000_left_swordsman.sql similarity index 97% rename from apps/api/drizzle/0000_polite_pixie.sql rename to apps/api/drizzle/0000_left_swordsman.sql index db45a5a1..03648748 100644 --- a/apps/api/drizzle/0000_polite_pixie.sql +++ b/apps/api/drizzle/0000_left_swordsman.sql @@ -4,7 +4,7 @@ CREATE TABLE "album_tracks" ( "track_id" text NOT NULL, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, - "xata_version" integer NOT NULL + "xata_version" integer ); --> statement-breakpoint CREATE TABLE "albums" ( @@ -50,7 +50,7 @@ CREATE TABLE "artist_albums" ( "album_id" text NOT NULL, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, - "xata_version" integer NOT NULL + "xata_version" integer ); --> statement-breakpoint CREATE TABLE "artist_tracks" ( @@ -59,7 +59,7 @@ CREATE TABLE "artist_tracks" ( "track_id" text NOT NULL, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, - "xata_version" integer NOT NULL + "xata_version" integer ); --> statement-breakpoint CREATE TABLE "artists" ( @@ -89,7 +89,7 @@ CREATE TABLE "dropbox_accounts" ( "email" text NOT NULL, "is_beta_user" boolean DEFAULT false NOT NULL, "user_id" text NOT NULL, - "xata_version" text NOT NULL, + "xata_version" text, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, CONSTRAINT "dropbox_accounts_email_unique" UNIQUE("email") @@ -102,7 +102,7 @@ CREATE TABLE "dropbox_directories" ( "parent_id" text, "dropbox_id" text NOT NULL, "file_id" text NOT NULL, - "xata_version" text NOT NULL, + "xata_version" text, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, CONSTRAINT "dropbox_directories_file_id_unique" UNIQUE("file_id") @@ -116,7 +116,7 @@ CREATE TABLE "dropbox_paths" ( "track_id" text NOT NULL, "directory_id" text, "file_id" text NOT NULL, - "xata_version" text NOT NULL, + "xata_version" text, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, CONSTRAINT "dropbox_paths_file_id_unique" UNIQUE("file_id") @@ -133,7 +133,7 @@ CREATE TABLE "dropbox" ( "xata_id" text PRIMARY KEY DEFAULT xata_id(), "user_id" text NOT NULL, "dropbox_token_id" text NOT NULL, - "xata_version" text NOT NULL, + "xata_version" text, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL ); @@ -143,7 +143,7 @@ CREATE TABLE "google_drive_accounts" ( "email" text NOT NULL, "is_beta_user" boolean DEFAULT false NOT NULL, "user_id" text NOT NULL, - "xata_version" text NOT NULL, + "xata_version" text, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, CONSTRAINT "google_drive_accounts_email_unique" UNIQUE("email") @@ -156,7 +156,7 @@ CREATE TABLE "google_drive_directories" ( "parent_id" text, "google_drive_id" text NOT NULL, "file_id" text NOT NULL, - "xata_version" text NOT NULL, + "xata_version" text, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, CONSTRAINT "google_drive_directories_file_id_unique" UNIQUE("file_id") @@ -169,7 +169,7 @@ CREATE TABLE "google_drive_paths" ( "name" text NOT NULL, "directory_id" text, "file_id" text NOT NULL, - "xata_version" text NOT NULL, + "xata_version" text, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, CONSTRAINT "google_drive_paths_file_id_unique" UNIQUE("file_id") @@ -186,7 +186,7 @@ CREATE TABLE "google_drive" ( "xata_id" text PRIMARY KEY DEFAULT xata_id(), "google_drive_token_id" text NOT NULL, "user_id" text NOT NULL, - "xata_version" text NOT NULL, + "xata_version" text, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL ); @@ -287,7 +287,7 @@ CREATE TABLE "shouts" ( --> statement-breakpoint CREATE TABLE "spotify_accounts" ( "xata_id" text PRIMARY KEY DEFAULT xata_id(), - "xata_version" integer NOT NULL, + "xata_version" integer, "email" text NOT NULL, "user_id" text NOT NULL, "is_beta_user" boolean DEFAULT false NOT NULL, @@ -297,7 +297,7 @@ CREATE TABLE "spotify_accounts" ( --> statement-breakpoint CREATE TABLE "spotify_tokens" ( "xata_id" text PRIMARY KEY DEFAULT xata_id(), - "xata_version" integer NOT NULL, + "xata_version" integer, "access_token" text NOT NULL, "refresh_token" text NOT NULL, "user_id" text NOT NULL, @@ -347,7 +347,7 @@ CREATE TABLE "user_albums" ( "album_id" text NOT NULL, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, - "xata_version" integer NOT NULL, + "xata_version" integer, "scrobbles" integer, "uri" text NOT NULL, CONSTRAINT "user_albums_uri_unique" UNIQUE("uri") @@ -359,7 +359,7 @@ CREATE TABLE "user_artists" ( "artist_id" text NOT NULL, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, - "xata_version" integer NOT NULL, + "xata_version" integer, "scrobbles" integer, "uri" text NOT NULL, CONSTRAINT "user_artists_uri_unique" UNIQUE("uri") @@ -380,7 +380,7 @@ CREATE TABLE "user_tracks" ( "track_id" text NOT NULL, "xata_createdat" timestamp DEFAULT now() NOT NULL, "xata_updatedat" timestamp DEFAULT now() NOT NULL, - "xata_version" integer NOT NULL, + "xata_version" integer, "uri" text NOT NULL, "scrobbles" integer, CONSTRAINT "user_tracks_uri_unique" UNIQUE("uri") diff --git a/apps/api/drizzle/meta/0000_snapshot.json b/apps/api/drizzle/meta/0000_snapshot.json index d8c4832a..f2a2f278 100644 --- a/apps/api/drizzle/meta/0000_snapshot.json +++ b/apps/api/drizzle/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "f7f59d87-3fa3-4146-91b5-692a483d2abc", + "id": "6b33dcd0-52df-4403-bdc0-31517769923d", "prevId": "00000000-0000-0000-0000-000000000000", "version": "7", "dialect": "postgresql", @@ -44,7 +44,7 @@ "name": "xata_version", "type": "integer", "primaryKey": false, - "notNull": true + "notNull": false } }, "indexes": {}, @@ -360,7 +360,7 @@ "name": "xata_version", "type": "integer", "primaryKey": false, - "notNull": true + "notNull": false } }, "indexes": {}, @@ -438,7 +438,7 @@ "name": "xata_version", "type": "integer", "primaryKey": false, - "notNull": true + "notNull": false } }, "indexes": {}, @@ -641,7 +641,7 @@ "name": "xata_version", "type": "text", "primaryKey": false, - "notNull": true + "notNull": false }, "xata_createdat": { "name": "xata_createdat", @@ -732,7 +732,7 @@ "name": "xata_version", "type": "text", "primaryKey": false, - "notNull": true + "notNull": false }, "xata_createdat": { "name": "xata_createdat", @@ -829,7 +829,7 @@ "name": "xata_version", "type": "text", "primaryKey": false, - "notNull": true + "notNull": false }, "xata_createdat": { "name": "xata_createdat", @@ -941,7 +941,7 @@ "name": "xata_version", "type": "text", "primaryKey": false, - "notNull": true + "notNull": false }, "xata_createdat": { "name": "xata_createdat", @@ -1026,7 +1026,7 @@ "name": "xata_version", "type": "text", "primaryKey": false, - "notNull": true + "notNull": false }, "xata_createdat": { "name": "xata_createdat", @@ -1117,7 +1117,7 @@ "name": "xata_version", "type": "text", "primaryKey": false, - "notNull": true + "notNull": false }, "xata_createdat": { "name": "xata_createdat", @@ -1208,7 +1208,7 @@ "name": "xata_version", "type": "text", "primaryKey": false, - "notNull": true + "notNull": false }, "xata_createdat": { "name": "xata_createdat", @@ -1320,7 +1320,7 @@ "name": "xata_version", "type": "text", "primaryKey": false, - "notNull": true + "notNull": false }, "xata_createdat": { "name": "xata_createdat", @@ -2238,7 +2238,7 @@ "name": "xata_version", "type": "integer", "primaryKey": false, - "notNull": true + "notNull": false }, "email": { "name": "email", @@ -2310,7 +2310,7 @@ "name": "xata_version", "type": "integer", "primaryKey": false, - "notNull": true + "notNull": false }, "access_token": { "name": "access_token", @@ -2628,7 +2628,7 @@ "name": "xata_version", "type": "integer", "primaryKey": false, - "notNull": true + "notNull": false }, "scrobbles": { "name": "scrobbles", @@ -2726,7 +2726,7 @@ "name": "xata_version", "type": "integer", "primaryKey": false, - "notNull": true + "notNull": false }, "scrobbles": { "name": "scrobbles", @@ -2903,7 +2903,7 @@ "name": "xata_version", "type": "integer", "primaryKey": false, - "notNull": true + "notNull": false }, "uri": { "name": "uri", diff --git a/apps/api/drizzle/meta/_journal.json b/apps/api/drizzle/meta/_journal.json index 1bdc0b78..e47597fd 100644 --- a/apps/api/drizzle/meta/_journal.json +++ b/apps/api/drizzle/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "7", - "when": 1759738835464, - "tag": "0000_polite_pixie", + "when": 1759744201158, + "tag": "0000_left_swordsman", "breakpoints": true } ] diff --git a/apps/api/src/schema/album-tracks.ts b/apps/api/src/schema/album-tracks.ts index 4b86aeda..c3cdcc26 100644 --- a/apps/api/src/schema/album-tracks.ts +++ b/apps/api/src/schema/album-tracks.ts @@ -13,7 +13,7 @@ const albumTracks = pgTable("album_tracks", { .references(() => tracks.id), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), - xataVersion: integer("xata_version").notNull(), + xataVersion: integer("xata_version"), }); export type SelectAlbumTrack = InferSelectModel; diff --git a/apps/api/src/schema/artist-albums.ts b/apps/api/src/schema/artist-albums.ts index 338c8aca..8fb83431 100644 --- a/apps/api/src/schema/artist-albums.ts +++ b/apps/api/src/schema/artist-albums.ts @@ -13,7 +13,7 @@ const artistAlbums = pgTable("artist_albums", { .references(() => albums.id), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), - xataVersion: integer("xata_version").notNull(), + xataVersion: integer("xata_version"), }); export type SelectArtistAlbum = InferSelectModel; diff --git a/apps/api/src/schema/artist-tracks.ts b/apps/api/src/schema/artist-tracks.ts index 0be4d8e7..4d69d10b 100644 --- a/apps/api/src/schema/artist-tracks.ts +++ b/apps/api/src/schema/artist-tracks.ts @@ -13,7 +13,7 @@ const artistTracks = pgTable("artist_tracks", { .references(() => tracks.id), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), - xataVersion: integer("xata_version").notNull(), + xataVersion: integer("xata_version"), }); export type SelectArtistTrack = InferSelectModel; diff --git a/apps/api/src/schema/dropbox-accounts.ts b/apps/api/src/schema/dropbox-accounts.ts index fa7cc79d..fe05a6df 100644 --- a/apps/api/src/schema/dropbox-accounts.ts +++ b/apps/api/src/schema/dropbox-accounts.ts @@ -9,7 +9,7 @@ const dropboxAccounts = pgTable("dropbox_accounts", { userId: text("user_id") .notNull() .references(() => users.id), - xataVersion: text("xata_version").notNull(), + xataVersion: text("xata_version"), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), }); diff --git a/apps/api/src/schema/dropbox-directories.ts b/apps/api/src/schema/dropbox-directories.ts index 76aae58c..b8b0433b 100644 --- a/apps/api/src/schema/dropbox-directories.ts +++ b/apps/api/src/schema/dropbox-directories.ts @@ -8,7 +8,7 @@ const dropboxDirectories = pgTable("dropbox_directories", { parentId: text("parent_id").references(() => dropboxDirectories.id), dropboxId: text("dropbox_id").notNull(), fileId: text("file_id").notNull().unique(), - xataVersion: text("xata_version").notNull(), + xataVersion: text("xata_version"), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), }); diff --git a/apps/api/src/schema/dropbox-paths.ts b/apps/api/src/schema/dropbox-paths.ts index 2fe2c8ea..a11f3dc9 100644 --- a/apps/api/src/schema/dropbox-paths.ts +++ b/apps/api/src/schema/dropbox-paths.ts @@ -10,7 +10,7 @@ const dropboxPaths = pgTable("dropbox_paths", { trackId: text("track_id").notNull(), directoryId: text("directory_id").references(() => dropboxDirectories.id), fileId: text("file_id").notNull().unique(), - xataVersion: text("xata_version").notNull(), + xataVersion: text("xata_version"), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), }); diff --git a/apps/api/src/schema/dropbox.ts b/apps/api/src/schema/dropbox.ts index ea2fd523..b341f185 100644 --- a/apps/api/src/schema/dropbox.ts +++ b/apps/api/src/schema/dropbox.ts @@ -11,7 +11,7 @@ const dropbox = pgTable("dropbox", { dropboxTokenId: text("dropbox_token_id") .notNull() .references(() => dropboxTokens.id), - xataVersion: text("xata_version").notNull(), + xataVersion: text("xata_version"), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), }); diff --git a/apps/api/src/schema/google-drive-accounts.ts b/apps/api/src/schema/google-drive-accounts.ts index 0bbb1bcb..91c3801b 100644 --- a/apps/api/src/schema/google-drive-accounts.ts +++ b/apps/api/src/schema/google-drive-accounts.ts @@ -9,7 +9,7 @@ const googleDriveAccounts = pgTable("google_drive_accounts", { userId: text("user_id") .notNull() .references(() => users.id), - xataVersion: text("xata_version").notNull(), + xataVersion: text("xata_version"), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), }); diff --git a/apps/api/src/schema/google-drive-directories.ts b/apps/api/src/schema/google-drive-directories.ts index a18ee26b..0cd224ce 100644 --- a/apps/api/src/schema/google-drive-directories.ts +++ b/apps/api/src/schema/google-drive-directories.ts @@ -8,7 +8,7 @@ const googleDriveDirectories = pgTable("google_drive_directories", { parentId: text("parent_id").references(() => googleDriveDirectories.id), googleDriveId: text("google_drive_id").notNull(), fileId: text("file_id").notNull().unique(), - xataVersion: text("xata_version").notNull(), + xataVersion: text("xata_version"), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), }); diff --git a/apps/api/src/schema/google-drive-paths.ts b/apps/api/src/schema/google-drive-paths.ts index a985196d..68d13109 100644 --- a/apps/api/src/schema/google-drive-paths.ts +++ b/apps/api/src/schema/google-drive-paths.ts @@ -9,7 +9,7 @@ const googleDrivePaths = pgTable("google_drive_paths", { name: text("name").notNull(), directoryId: text("directory_id").references(() => googleDriveDirectories.id), fileId: text("file_id").notNull().unique(), - xataVersion: text("xata_version").notNull(), + xataVersion: text("xata_version"), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), }); diff --git a/apps/api/src/schema/googledrive.ts b/apps/api/src/schema/googledrive.ts index be8b5a01..ce25088b 100644 --- a/apps/api/src/schema/googledrive.ts +++ b/apps/api/src/schema/googledrive.ts @@ -11,7 +11,7 @@ const googleDrive = pgTable("google_drive", { userId: text("user_id") .notNull() .references(() => users.id), - xataVersion: text("xata_version").notNull(), + xataVersion: text("xata_version"), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), }); diff --git a/apps/api/src/schema/spotify-accounts.ts b/apps/api/src/schema/spotify-accounts.ts index 5cb928bc..3493d8aa 100644 --- a/apps/api/src/schema/spotify-accounts.ts +++ b/apps/api/src/schema/spotify-accounts.ts @@ -10,7 +10,7 @@ import users from "./users"; const spotifyAccounts = pgTable("spotify_accounts", { id: text("xata_id").primaryKey(), - xataVersion: integer("xata_version").notNull(), + xataVersion: integer("xata_version"), email: text("email").notNull(), userId: text("user_id") .notNull() diff --git a/apps/api/src/schema/spotify-tokens.ts b/apps/api/src/schema/spotify-tokens.ts index 0315f646..a34e1bf4 100644 --- a/apps/api/src/schema/spotify-tokens.ts +++ b/apps/api/src/schema/spotify-tokens.ts @@ -4,7 +4,7 @@ import users from "./users"; const spotifyTokens = pgTable("spotify_tokens", { id: text("xata_id").primaryKey(), - xataVersion: integer("xata_version").notNull(), + xataVersion: integer("xata_version"), accessToken: text("access_token").notNull(), refreshToken: text("refresh_token").notNull(), userId: text("user_id") diff --git a/apps/api/src/schema/user-albums.ts b/apps/api/src/schema/user-albums.ts index b90e8680..2a55ff21 100644 --- a/apps/api/src/schema/user-albums.ts +++ b/apps/api/src/schema/user-albums.ts @@ -13,7 +13,7 @@ const userAlbums = pgTable("user_albums", { .references(() => albums.id), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), - xataVersion: integer("xata_version").notNull(), + xataVersion: integer("xata_version"), scrobbles: integer("scrobbles"), uri: text("uri").unique().notNull(), }); diff --git a/apps/api/src/schema/user-artists.ts b/apps/api/src/schema/user-artists.ts index fba5d21c..83226eb3 100644 --- a/apps/api/src/schema/user-artists.ts +++ b/apps/api/src/schema/user-artists.ts @@ -13,7 +13,7 @@ const userArtists = pgTable("user_artists", { .references(() => artists.id), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), - xataVersion: integer("xata_version").notNull(), + xataVersion: integer("xata_version"), scrobbles: integer("scrobbles"), uri: text("uri").unique().notNull(), }); diff --git a/apps/api/src/schema/user-tracks.ts b/apps/api/src/schema/user-tracks.ts index 4c0ea927..b6feb6fd 100644 --- a/apps/api/src/schema/user-tracks.ts +++ b/apps/api/src/schema/user-tracks.ts @@ -13,7 +13,7 @@ const userTracks = pgTable("user_tracks", { .references(() => tracks.id), createdAt: timestamp("xata_createdat").defaultNow().notNull(), updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), - xataVersion: integer("xata_version").notNull(), + xataVersion: integer("xata_version"), uri: text("uri").unique().notNull(), scrobbles: integer("scrobbles"), }); diff --git a/crates/pgpull/src/lib.rs b/crates/pgpull/src/lib.rs index f0f20004..67376d48 100644 --- a/crates/pgpull/src/lib.rs +++ b/crates/pgpull/src/lib.rs @@ -297,5 +297,341 @@ pub async fn pull_data() -> Result<(), Error> { playlist_sync.context("Playlist sync task failed")??; scrobble_sync.context("Scrobble sync task failed")??; + let pool_clone = pool.clone(); + let dest_pool_clone = dest_pool.clone(); + let album_track_sync = tokio::spawn(async move { + let total_album_tracks: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM album_tracks") + .fetch_one(&pool_clone) + .await?; + let total_album_tracks = total_album_tracks.0; + tracing::info!(total = %total_album_tracks.magenta(), "Total album tracks to sync"); + + const BATCH_SIZE: usize = 1000; + + let start = 0; + let mut i = 1; + + for offset in (start..total_album_tracks).step_by(BATCH_SIZE) { + let album_tracks = + repo::album::get_album_tracks(&pool_clone, offset as i64, BATCH_SIZE as i64) + .await?; + tracing::info!( + offset = %offset.magenta(), + end = %((offset + album_tracks.len() as i64).min(total_album_tracks)).magenta(), + total = %total_album_tracks.magenta(), + "Fetched album tracks" + ); + + for album_track in &album_tracks { + tracing::info!(album_id = %album_track.album_id.cyan(), track_id = %album_track.track_id.magenta(), i = %i.magenta(), total = %total_album_tracks.magenta(), "Inserting album track"); + match repo::album::insert_album_track(&dest_pool_clone, album_track).await { + Ok(_) => {} + Err(e) => { + tracing::error!(error = %e, "Failed to insert album track"); + } + } + i += 1; + } + } + Ok::<(), Error>(()) + }); + + let pool_clone = pool.clone(); + let dest_pool_clone = dest_pool.clone(); + let artist_album_sync = tokio::spawn(async move { + let total_artist_albums: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM artist_albums") + .fetch_one(&pool_clone) + .await?; + let total_artist_albums = total_artist_albums.0; + tracing::info!(total = %total_artist_albums.magenta(), "Total artist albums to sync"); + + const BATCH_SIZE: usize = 1000; + + let start = 0; + let mut i = 1; + + for offset in (start..total_artist_albums).step_by(BATCH_SIZE) { + let artist_albums = + repo::artist::get_artist_albums(&pool_clone, offset as i64, BATCH_SIZE as i64) + .await?; + tracing::info!( + offset = %offset.magenta(), + end = %((offset + artist_albums.len() as i64).min(total_artist_albums)).magenta(), + total = %total_artist_albums.magenta(), + "Fetched artist albums" + ); + + for artist_album in &artist_albums { + tracing::info!(artist_id = %artist_album.artist_id.cyan(), album_id = %artist_album.album_id.magenta(), i = %i.magenta(), total = %total_artist_albums.magenta(), "Inserting artist album"); + match repo::artist::insert_artist_album(&dest_pool_clone, artist_album).await { + Ok(_) => {} + Err(e) => { + tracing::error!(error = %e, "Failed to insert artist album"); + } + } + i += 1; + } + } + Ok::<(), Error>(()) + }); + + let pool_clone = pool.clone(); + let dest_pool_clone = dest_pool.clone(); + let artist_track_sync = tokio::spawn(async move { + let total_artist_tracks: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM artist_tracks") + .fetch_one(&pool_clone) + .await?; + let total_artist_tracks = total_artist_tracks.0; + tracing::info!(total = %total_artist_tracks.magenta(), "Total artist tracks to sync"); + const BATCH_SIZE: usize = 1000; + + let start = 0; + let mut i = 1; + + for offset in (start..total_artist_tracks).step_by(BATCH_SIZE) { + let artist_tracks = + repo::artist::get_artist_tracks(&pool_clone, offset as i64, BATCH_SIZE as i64) + .await?; + tracing::info!( + offset = %offset.magenta(), + end = %((offset + artist_tracks.len() as i64).min(total_artist_tracks)).magenta(), + total = %total_artist_tracks.magenta(), + "Fetched artist tracks" + ); + + for artist_track in &artist_tracks { + tracing::info!(artist_id = %artist_track.artist_id.cyan(), track_id = %artist_track.track_id.magenta(), i = %i.magenta(), total = %total_artist_tracks.magenta(), "Inserting artist track"); + match repo::artist::insert_artist_track(&dest_pool_clone, artist_track).await { + Ok(_) => {} + Err(e) => { + tracing::error!(error = %e, "Failed to insert artist track"); + } + } + i += 1; + } + } + Ok::<(), Error>(()) + }); + + let pool_clone = pool.clone(); + let dest_pool_clone = dest_pool.clone(); + let playlist_track_sync = tokio::spawn(async move { + let total_playlist_tracks: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM playlist_tracks") + .fetch_one(&pool_clone) + .await?; + let total_playlist_tracks = total_playlist_tracks.0; + tracing::info!(total = %total_playlist_tracks.magenta(), "Total playlist tracks to sync"); + + const BATCH_SIZE: usize = 1000; + + let start = 0; + let mut i = 1; + + for offset in (start..total_playlist_tracks).step_by(BATCH_SIZE) { + let playlist_tracks = + repo::playlist::get_playlist_tracks(&pool_clone, offset as i64, BATCH_SIZE as i64) + .await?; + tracing::info!( + offset = %offset.magenta(), + end = %((offset + playlist_tracks.len() as i64).min(total_playlist_tracks)).magenta(), + total = %total_playlist_tracks.magenta(), + "Fetched playlist tracks" + ); + + for playlist_track in &playlist_tracks { + tracing::info!(playlist_id = %playlist_track.playlist_id.cyan(), track_id = %playlist_track.track_id.magenta(), i = %i.magenta(), total = %total_playlist_tracks.magenta(), "Inserting playlist track"); + match repo::playlist::insert_playlist_track(&dest_pool_clone, playlist_track).await + { + Ok(_) => {} + Err(e) => { + tracing::error!(error = %e, "Failed to insert playlist track"); + } + } + i += 1; + } + } + Ok::<(), Error>(()) + }); + + let pool_clone = pool.clone(); + let dest_pool_clone = dest_pool.clone(); + let user_album_sync = tokio::spawn(async move { + let total_user_albums: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM user_albums") + .fetch_one(&pool_clone) + .await?; + let total_user_albums = total_user_albums.0; + tracing::info!(total = %total_user_albums.magenta(), "Total user albums to sync"); + const BATCH_SIZE: usize = 1000; + + let start = 0; + let mut i = 1; + + for offset in (start..total_user_albums).step_by(BATCH_SIZE) { + let user_albums = + repo::album::get_user_albums(&pool_clone, offset as i64, BATCH_SIZE as i64).await?; + tracing::info!( + offset = %offset.magenta(), + end = %((offset + user_albums.len() as i64).min(total_user_albums)).magenta(), + total = %total_user_albums.magenta(), + "Fetched user albums" + ); + + for user_album in &user_albums { + tracing::info!(user_id = %user_album.user_id.cyan(), album_id = %user_album.album_id.magenta(), i = %i.magenta(), total = %total_user_albums.magenta(), "Inserting user album"); + match repo::album::insert_user_album(&dest_pool_clone, user_album).await { + Ok(_) => {} + Err(e) => { + tracing::error!(error = %e, "Failed to insert user album"); + } + } + i += 1; + } + } + Ok::<(), Error>(()) + }); + + let pool_clone = pool.clone(); + let dest_pool_clone = dest_pool.clone(); + let user_artist_sync = tokio::spawn(async move { + let total_user_artists: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM user_artists") + .fetch_one(&pool_clone) + .await?; + let total_user_artists = total_user_artists.0; + tracing::info!(total = %total_user_artists.magenta(), "Total user artists to sync"); + const BATCH_SIZE: usize = 1000; + + let start = 0; + let mut i = 1; + + for offset in (start..total_user_artists).step_by(BATCH_SIZE) { + let user_artists = + repo::artist::get_user_artists(&pool_clone, offset as i64, BATCH_SIZE as i64) + .await?; + tracing::info!( + offset = %offset.magenta(), + end = %((offset + user_artists.len() as i64).min(total_user_artists)).magenta(), + total = %total_user_artists.magenta(), + "Fetched user artists" + ); + + for user_artist in &user_artists { + tracing::info!(user_id = %user_artist.user_id.cyan(), artist_id = %user_artist.artist_id.magenta(), i = %i.magenta(), total = %total_user_artists.magenta(), "Inserting user artist"); + match repo::artist::insert_user_artist(&dest_pool_clone, user_artist).await { + Ok(_) => {} + Err(e) => { + tracing::error!(error = %e, "Failed to insert user artist"); + } + } + i += 1; + } + } + Ok::<(), Error>(()) + }); + + let pool_clone = pool.clone(); + let dest_pool_clone = dest_pool.clone(); + let user_track_sync = tokio::spawn(async move { + let total_user_tracks: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM user_tracks") + .fetch_one(&pool_clone) + .await?; + let total_user_tracks = total_user_tracks.0; + tracing::info!(total = %total_user_tracks.magenta(), "Total user tracks to sync"); + const BATCH_SIZE: usize = 1000; + + let start = 0; + let mut i = 1; + + for offset in (start..total_user_tracks).step_by(BATCH_SIZE) { + let user_tracks = + repo::track::get_user_tracks(&pool_clone, offset as i64, BATCH_SIZE as i64).await?; + tracing::info!( + offset = %offset.magenta(), + end = %((offset + user_tracks.len() as i64).min(total_user_tracks)).magenta(), + total = %total_user_tracks.magenta(), + "Fetched user tracks" + ); + + for user_track in &user_tracks { + tracing::info!(user_id = %user_track.user_id.cyan(), track_id = %user_track.track_id.magenta(), i = %i.magenta(), total = %total_user_tracks.magenta(), "Inserting user track"); + match repo::track::insert_user_track(&dest_pool_clone, user_track).await { + Ok(_) => {} + Err(e) => { + tracing::error!(error = %e, "Failed to insert user track"); + } + } + i += 1; + } + } + Ok::<(), Error>(()) + }); + + let pool_clone = pool.clone(); + let dest_pool_clone = dest_pool.clone(); + let user_playlist_sync = tokio::spawn(async move { + let total_user_playlists: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM user_playlists") + .fetch_one(&pool_clone) + .await?; + + let total_user_playlists = total_user_playlists.0; + tracing::info!(total = %total_user_playlists.magenta(), "Total user playlists to sync"); + const BATCH_SIZE: usize = 1000; + + let start = 0; + let mut i = 1; + + for offset in (start..total_user_playlists).step_by(BATCH_SIZE) { + let user_playlists = + repo::playlist::get_user_playlists(&pool_clone, offset as i64, BATCH_SIZE as i64) + .await?; + tracing::info!( + offset = %offset.magenta(), + end = %((offset + user_playlists.len() as i64).min(total_user_playlists)).magenta(), + total = %total_user_playlists.magenta(), + "Fetched user playlists" + ); + + for user_playlist in &user_playlists { + tracing::info!(user_id = %user_playlist.user_id.cyan(), playlist_id = %user_playlist.playlist_id.magenta(), i = %i.magenta(), total = %total_user_playlists.magenta(), "Inserting user playlist"); + match repo::playlist::insert_user_playlist(&dest_pool_clone, user_playlist).await { + Ok(_) => {} + Err(e) => { + tracing::error!(error = %e, "Failed to insert user playlist"); + } + } + i += 1; + } + } + Ok::<(), Error>(()) + }); + + let ( + album_track_sync, + artist_album_sync, + artist_track_sync, + playlist_track_sync, + user_album_sync, + user_artist_sync, + user_track_sync, + user_playlist_sync, + ) = tokio::join!( + album_track_sync, + artist_album_sync, + artist_track_sync, + playlist_track_sync, + user_album_sync, + user_artist_sync, + user_track_sync, + user_playlist_sync + ); + + album_track_sync.context("Album track sync task failed")??; + artist_album_sync.context("Artist album sync task failed")??; + artist_track_sync.context("Artist track sync task failed")??; + playlist_track_sync.context("Playlist track sync task failed")??; + user_album_sync.context("User album sync task failed")??; + user_artist_sync.context("User artist sync task failed")??; + user_track_sync.context("User track sync task failed")??; + user_playlist_sync.context("User playlist sync task failed")??; + Ok(()) } diff --git a/crates/pgpull/src/repo/album.rs b/crates/pgpull/src/repo/album.rs index 1fd53219..d96c5d0d 100644 --- a/crates/pgpull/src/repo/album.rs +++ b/crates/pgpull/src/repo/album.rs @@ -1,7 +1,7 @@ use anyhow::Error; use sqlx::{Pool, Postgres}; -use crate::xata::album::Album; +use crate::xata::{album::Album, album_track::AlbumTrack, user_album::UserAlbum}; pub async fn get_albums( pool: &Pool, @@ -16,6 +16,34 @@ pub async fn get_albums( Ok(albums) } +pub async fn get_album_tracks( + pool: &Pool, + offset: i64, + limit: i64, +) -> Result, Error> { + let album_tracks: Vec = + sqlx::query_as("SELECT * FROM album_tracks OFFSET $1 LIMIT $2") + .bind(offset) + .bind(limit) + .fetch_all(pool) + .await?; + Ok(album_tracks) +} + +pub async fn get_user_albums( + pool: &Pool, + offset: i64, + limit: i64, +) -> Result, Error> { + let user_albums: Vec = + sqlx::query_as("SELECT * FROM user_albums OFFSET $1 LIMIT $2") + .bind(offset) + .bind(limit) + .fetch_all(pool) + .await?; + Ok(user_albums) +} + pub async fn insert_album(pool: &Pool, album: &Album) -> Result<(), Error> { sqlx::query( r#"INSERT INTO albums ( @@ -55,3 +83,45 @@ pub async fn insert_album(pool: &Pool, album: &Album) -> Result<(), Er Ok(()) } + +pub async fn insert_album_track( + pool: &Pool, + album_track: &AlbumTrack, +) -> Result<(), Error> { + sqlx::query( + r#"INSERT INTO album_tracks ( + xata_id, + album_id, + track_id, + xata_createdat + ) VALUES ($1, $2, $3) + ON CONFLICT (xata_id) DO NOTHING"#, + ) + .bind(&album_track.xata_id) + .bind(&album_track.album_id) + .bind(&album_track.track_id) + .execute(pool) + .await?; + Ok(()) +} + +pub async fn insert_user_album(pool: &Pool, user_album: &UserAlbum) -> Result<(), Error> { + sqlx::query( + r#"INSERT INTO user_albums ( + xata_id, + user_id, + album_id, + uri, + xata_createdat + ) VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (xata_id) DO NOTHING"#, + ) + .bind(&user_album.xata_id) + .bind(&user_album.user_id) + .bind(&user_album.album_id) + .bind(&user_album.uri) + .bind(user_album.xata_createdat) + .execute(pool) + .await?; + Ok(()) +} diff --git a/crates/pgpull/src/repo/artist.rs b/crates/pgpull/src/repo/artist.rs index 24b4e223..7e221191 100644 --- a/crates/pgpull/src/repo/artist.rs +++ b/crates/pgpull/src/repo/artist.rs @@ -1,7 +1,9 @@ use anyhow::Error; use sqlx::{Pool, Postgres}; -use crate::xata::artist::Artist; +use crate::xata::{ + artist::Artist, artist_album::ArtistAlbum, artist_track::ArtistTrack, user_artist::UserArtist, +}; pub async fn get_artists( pool: &Pool, @@ -56,3 +58,113 @@ pub async fn insert_artist(pool: &Pool, artist: &Artist) -> Result<(), .await?; Ok(()) } + +pub async fn get_artist_albums( + pool: &Pool, + offset: i64, + limit: i64, +) -> Result, Error> { + let artist_albums = + sqlx::query_as::<_, ArtistAlbum>("SELECT * FROM artist_albums OFFSET $1 LIMIT $2") + .bind(offset) + .bind(limit) + .fetch_all(pool) + .await?; + Ok(artist_albums) +} + +pub async fn get_artist_tracks( + pool: &Pool, + offset: i64, + limit: i64, +) -> Result, Error> { + let artist_tracks = + sqlx::query_as::<_, ArtistTrack>("SELECT * FROM artist_tracks OFFSET $1 LIMIT $2") + .bind(offset) + .bind(limit) + .fetch_all(pool) + .await?; + Ok(artist_tracks) +} + +pub async fn get_user_artists( + pool: &Pool, + offset: i64, + limit: i64, +) -> Result, Error> { + let user_artists = + sqlx::query_as::<_, UserArtist>("SELECT * FROM user_artists OFFSET $1 LIMIT $2") + .bind(offset) + .bind(limit) + .fetch_all(pool) + .await?; + Ok(user_artists) +} + +pub async fn insert_artist_album( + pool: &Pool, + artist_album: &ArtistAlbum, +) -> Result<(), Error> { + sqlx::query( + r#"INSERT INTO artist_albums ( + xata_id, + artist_id, + album_id, + xata_createdat + ) VALUES ($1, $2, $3, $4) + ON CONFLICT (xata_id) DO NOTHING"#, + ) + .bind(&artist_album.xata_id) + .bind(&artist_album.artist_id) + .bind(&artist_album.album_id) + .bind(artist_album.xata_createdat) + .execute(pool) + .await?; + Ok(()) +} + +pub async fn insert_artist_track( + pool: &Pool, + artist_track: &ArtistTrack, +) -> Result<(), Error> { + sqlx::query( + r#"INSERT INTO artist_tracks ( + xata_id, + artist_id, + track_id, + xata_createdat + ) VALUES ($1, $2, $3, $4) + ON CONFLICT (xata_id) DO NOTHING"#, + ) + .bind(&artist_track.xata_id) + .bind(&artist_track.artist_id) + .bind(&artist_track.track_id) // Reusing album_id field for track_id + .bind(artist_track.xata_createdat) + .execute(pool) + .await?; + Ok(()) +} + +pub async fn insert_user_artist( + pool: &Pool, + user_artist: &UserArtist, +) -> Result<(), Error> { + sqlx::query( + r#"INSERT INTO user_artists ( + xata_id, + user_id, + artist_id, + uri, + xata_createdat + ) VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (xata_id) DO NOTHING"#, + ) + .bind(&user_artist.xata_id) + .bind(&user_artist.user_id) + .bind(&user_artist.artist_id) + .bind(&user_artist.uri) + .bind(user_artist.xata_createdat) + .execute(pool) + .await?; + Ok(()) +} diff --git a/crates/pgpull/src/repo/playlist.rs b/crates/pgpull/src/repo/playlist.rs index b467e644..2ae9db4c 100644 --- a/crates/pgpull/src/repo/playlist.rs +++ b/crates/pgpull/src/repo/playlist.rs @@ -1,7 +1,7 @@ use anyhow::Error; use sqlx::{Pool, Postgres}; -use crate::xata::playlist::Playlist; +use crate::xata::{playlist::Playlist, playlist_track::PlaylistTrack, user_playlist::UserPlaylist}; pub async fn get_playlists( pool: &Pool, @@ -16,6 +16,34 @@ pub async fn get_playlists( Ok(playlists) } +pub async fn get_playlist_tracks( + pool: &Pool, + offset: i64, + limit: i64, +) -> Result, Error> { + let playlist_tracks: Vec = + sqlx::query_as("SELECT * FROM playlist_tracks OFFSET $1 LIMIT $2") + .bind(offset) + .bind(limit) + .fetch_all(pool) + .await?; + Ok(playlist_tracks) +} + +pub async fn get_user_playlists( + pool: &Pool, + offset: i64, + limit: i64, +) -> Result, Error> { + let user_playlists: Vec = + sqlx::query_as("SELECT * FROM user_playlists OFFSET $1 LIMIT $2") + .bind(offset) + .bind(limit) + .fetch_all(pool) + .await?; + Ok(user_playlists) +} + pub async fn insert_playlist(pool: &Pool, playlist: &Playlist) -> Result<(), Error> { sqlx::query( r#"INSERT INTO playlists ( @@ -48,3 +76,51 @@ pub async fn insert_playlist(pool: &Pool, playlist: &Playlist) -> Resu .await?; Ok(()) } + +pub async fn insert_playlist_track( + pool: &Pool, + playlist_track: &PlaylistTrack, +) -> Result<(), Error> { + sqlx::query( + r#"INSERT INTO playlist_tracks ( + xata_id, + playlist_id, + track_id, + added_by, + xata_createdat + ) VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (xata_id) DO NOTHING"#, + ) + .bind(&playlist_track.xata_id) + .bind(&playlist_track.playlist_id) + .bind(&playlist_track.track_id) + .bind(&playlist_track.added_by) + .bind(playlist_track.xata_createdat) + .execute(pool) + .await?; + Ok(()) +} + +pub async fn insert_user_playlist( + pool: &Pool, + user_playlist: &UserPlaylist, +) -> Result<(), Error> { + sqlx::query( + r#"INSERT INTO user_playlists ( + xata_id, + user_id, + playlist_id, + uri, + xata_createdat + ) VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (xata_id) DO NOTHING"#, + ) + .bind(&user_playlist.xata_id) + .bind(&user_playlist.user_id) + .bind(&user_playlist.playlist_id) + .bind(&user_playlist.uri) + .bind(user_playlist.xata_createdat) + .execute(pool) + .await?; + Ok(()) +} diff --git a/crates/pgpull/src/repo/track.rs b/crates/pgpull/src/repo/track.rs index 27d6575e..1e4a734d 100644 --- a/crates/pgpull/src/repo/track.rs +++ b/crates/pgpull/src/repo/track.rs @@ -1,7 +1,7 @@ use anyhow::Error; use sqlx::{Pool, Postgres}; -use crate::xata::track::Track; +use crate::xata::{track::Track, user_track::UserTrack}; pub async fn get_tracks( pool: &Pool, @@ -16,6 +16,20 @@ pub async fn get_tracks( Ok(tracks) } +pub async fn get_user_tracks( + pool: &Pool, + offset: i64, + limit: i64, +) -> Result, Error> { + let user_tracks: Vec = + sqlx::query_as("SELECT * FROM user_tracks OFFSET $1 LIMIT $2") + .bind(offset) + .bind(limit) + .fetch_all(pool) + .await?; + Ok(user_tracks) +} + pub async fn insert_track(pool: &Pool, track: &Track) -> Result<(), Error> { sqlx::query( r#"INSERT INTO tracks ( @@ -75,3 +89,25 @@ pub async fn insert_track(pool: &Pool, track: &Track) -> Result<(), Er .await?; Ok(()) } + +pub async fn insert_user_track(pool: &Pool, user_track: &UserTrack) -> Result<(), Error> { + sqlx::query( + r#"INSERT INTO user_tracks ( + xata_id, + user_id, + track_id, + uri, + xata_createdat + ) VALUES ($1, $2, $3, $4, $5) + ON CONFLICT (xata_id) DO NOTHING + "#, + ) + .bind(&user_track.xata_id) + .bind(&user_track.user_id) + .bind(&user_track.track_id) + .bind(&user_track.uri) + .bind(user_track.xata_createdat) + .execute(pool) + .await?; + Ok(()) +} diff --git a/crates/pgpull/src/xata/user_album.rs b/crates/pgpull/src/xata/user_album.rs index 67bad286..a1748431 100644 --- a/crates/pgpull/src/xata/user_album.rs +++ b/crates/pgpull/src/xata/user_album.rs @@ -5,6 +5,7 @@ pub struct UserAlbum { pub xata_id: String, pub user_id: String, pub album_id: String, + pub uri: Option, #[serde(with = "chrono::serde::ts_seconds")] pub xata_createdat: chrono::DateTime, } diff --git a/crates/pgpull/src/xata/user_artist.rs b/crates/pgpull/src/xata/user_artist.rs index 469e6a4d..e2de6e1b 100644 --- a/crates/pgpull/src/xata/user_artist.rs +++ b/crates/pgpull/src/xata/user_artist.rs @@ -5,6 +5,7 @@ pub struct UserArtist { pub xata_id: String, pub user_id: String, pub artist_id: String, + pub uri: Option, #[serde(with = "chrono::serde::ts_seconds")] pub xata_createdat: chrono::DateTime, } diff --git a/crates/pgpull/src/xata/user_playlist.rs b/crates/pgpull/src/xata/user_playlist.rs index af8f705d..8d6eaf5f 100644 --- a/crates/pgpull/src/xata/user_playlist.rs +++ b/crates/pgpull/src/xata/user_playlist.rs @@ -5,6 +5,7 @@ pub struct UserPlaylist { pub xata_id: String, pub user_id: String, pub playlist_id: String, + pub uri: Option, #[serde(with = "chrono::serde::ts_seconds")] pub xata_createdat: chrono::DateTime, } diff --git a/crates/pgpull/src/xata/user_track.rs b/crates/pgpull/src/xata/user_track.rs index 0f9be606..75157db2 100644 --- a/crates/pgpull/src/xata/user_track.rs +++ b/crates/pgpull/src/xata/user_track.rs @@ -5,6 +5,7 @@ pub struct UserTrack { pub xata_id: String, pub user_id: String, pub track_id: String, + pub uri: Option, #[serde(with = "chrono::serde::ts_seconds")] pub xata_createdat: chrono::DateTime, } -- 2.51.2