From 038512a291de634f8c82fbd170e81aa490baed22 Mon Sep 17 00:00:00 2001 From: Tsiry Sandratraina Date: Wed, 1 Oct 2025 15:08:08 +0300 Subject: [PATCH] refactor: clean up createSong.ts by removing unused imports and simplifying error handling - Removed unused BlobRef import and downloadImage function. - Simplified error handling in various Effect.catchAll calls. - Updated formatting for consistency and readability. - Adjusted object structures for artist and album records to use pictureUrl and albumArtUrl. - Streamlined the ensureTrack, ensureArtist, and ensureAlbum functions for clarity. --- apps/api/src/nowplaying/nowplaying.service.ts | 73 +-- apps/api/src/subscribers/playlist.ts | 22 +- .../app/rocksky/scrobble/createScrobble.ts | 420 ++++++++---------- .../src/xrpc/app/rocksky/song/createSong.ts | 309 ++++++------- 4 files changed, 332 insertions(+), 492 deletions(-) diff --git a/apps/api/src/nowplaying/nowplaying.service.ts b/apps/api/src/nowplaying/nowplaying.service.ts index 8d5388bd..2f3015f5 100644 --- a/apps/api/src/nowplaying/nowplaying.service.ts +++ b/apps/api/src/nowplaying/nowplaying.service.ts @@ -1,4 +1,4 @@ -import type { Agent, BlobRef } from "@atproto/api"; +import type { Agent } from "@atproto/api"; import { TID } from "@atproto/common"; import { equals } from "@xata.io/client"; import chalk from "chalk"; @@ -8,7 +8,6 @@ import * as Album from "lexicon/types/app/rocksky/album"; import * as Artist from "lexicon/types/app/rocksky/artist"; import * as Scrobble from "lexicon/types/app/rocksky/scrobble"; import * as Song from "lexicon/types/app/rocksky/song"; -import downloadImage, { getContentType } from "lib/downloadImage"; import { createHash } from "node:crypto"; import type { Track } from "types/track"; @@ -21,24 +20,16 @@ export async function putArtistRecord( $type: string; name: string; createdAt: string; - picture?: BlobRef; + pictureUrl?: string; tags?: string[]; } = { $type: "app.rocksky.artist", name: track.albumArtist, createdAt: new Date().toISOString(), + pictureUrl: track.artistPicture, tags: track.genres, }; - if (track.artistPicture) { - const imageBuffer = await downloadImage(track.artistPicture); - const encoding = await getContentType(track.artistPicture); - const uploadResponse = await agent.uploadBlob(imageBuffer, { - encoding, - }); - record.picture = uploadResponse.data.blob; - } - if (!Artist.validateRecord(record).success) { console.log(Artist.validateRecord(record)); throw new Error("Invalid record"); @@ -66,26 +57,6 @@ export async function putAlbumRecord( agent: Agent ): Promise { const rkey = TID.nextStr(); - let albumArt; - - if (track.albumArt) { - let options; - if (track.albumArt.endsWith(".jpeg") || track.albumArt.endsWith(".jpg")) { - options = { encoding: "image/jpeg" }; - } - - if (track.albumArt.endsWith(".png")) { - options = { encoding: "image/png" }; - } - - if (!options?.encoding) { - options = { encoding: await getContentType(track.albumArt) }; - } - - const imageBuffer = await downloadImage(track.albumArt); - const uploadResponse = await agent.uploadBlob(imageBuffer, options); - albumArt = uploadResponse.data.blob; - } const record = { $type: "app.rocksky.album", @@ -96,7 +67,7 @@ export async function putAlbumRecord( ? track.releaseDate.toISOString() : undefined, createdAt: new Date().toISOString(), - albumArt, + albumArtUrl: track.albumArt, }; if (!Album.validateRecord(record).success) { @@ -126,22 +97,6 @@ export async function putSongRecord( agent: Agent ): Promise { const rkey = TID.nextStr(); - let albumArt; - - if (track.albumArt) { - let options; - if (track.albumArt.endsWith(".jpeg") || track.albumArt.endsWith(".jpg")) { - options = { encoding: "image/jpeg" }; - } - - if (track.albumArt.endsWith(".png")) { - options = { encoding: "image/png" }; - } - - const imageBuffer = await downloadImage(track.albumArt); - const uploadResponse = await agent.uploadBlob(imageBuffer, options); - albumArt = uploadResponse.data.blob; - } const record = { $type: "app.rocksky.song", @@ -154,7 +109,7 @@ export async function putSongRecord( ? track.releaseDate.toISOString() : undefined, year: track.year, - albumArt, + albumArtUrl: track.albumArt, composer: track.composer ? track.composer : undefined, lyrics: track.lyrics ? track.lyrics : undefined, trackNumber: track.trackNumber, @@ -194,28 +149,12 @@ async function putScrobbleRecord( agent: Agent ): Promise { const rkey = TID.nextStr(); - let albumArt; - - if (track.albumArt) { - let options; - if (track.albumArt.endsWith(".jpeg") || track.albumArt.endsWith(".jpg")) { - options = { encoding: "image/jpeg" }; - } - - if (track.albumArt.endsWith(".png")) { - options = { encoding: "image/png" }; - } - - const imageBuffer = await downloadImage(track.albumArt); - const uploadResponse = await agent.uploadBlob(imageBuffer, options); - albumArt = uploadResponse.data.blob; - } const record = { $type: "app.rocksky.scrobble", title: track.title, albumArtist: track.albumArtist, - albumArt, + albumArtUrl: track.albumArt, artist: track.artist, album: track.album, duration: track.duration, diff --git a/apps/api/src/subscribers/playlist.ts b/apps/api/src/subscribers/playlist.ts index ba290622..bb9ef4f4 100644 --- a/apps/api/src/subscribers/playlist.ts +++ b/apps/api/src/subscribers/playlist.ts @@ -1,11 +1,9 @@ import { TID } from "@atproto/common"; -import type { BlobRef } from "@atproto/lexicon"; import chalk from "chalk"; import type { Context } from "context"; import { eq } from "drizzle-orm"; import * as Playlist from "lexicon/types/app/rocksky/playlist"; import { createAgent } from "lib/agent"; -import downloadImage, { getContentType } from "lib/downloadImage"; import { StringCodec } from "nats"; import tables from "schema"; @@ -19,7 +17,7 @@ export function onNewPlaylist(ctx: Context) { did: string; } = JSON.parse(sc.decode(m.data)); console.log( - `New playlist: ${chalk.cyan(payload.did)} - ${chalk.greenBright(payload.id)}`, + `New playlist: ${chalk.cyan(payload.did)} - ${chalk.greenBright(payload.id)}` ); await putPlaylistRecord(ctx, payload); } @@ -28,13 +26,13 @@ export function onNewPlaylist(ctx: Context) { async function putPlaylistRecord( ctx: Context, - payload: { id: string; did: string }, + payload: { id: string; did: string } ) { const agent = await createAgent(ctx.oauthClient, payload.did); if (!agent) { console.error( - `Failed to create agent, skipping playlist: ${chalk.cyan(payload.id)} for ${chalk.greenBright(payload.did)}`, + `Failed to create agent, skipping playlist: ${chalk.cyan(payload.id)} for ${chalk.greenBright(payload.did)}` ); return; } @@ -56,7 +54,7 @@ async function putPlaylistRecord( name: string; description?: string; createdAt: string; - picture?: BlobRef; + pictureUrl?: string; spotifyLink?: string; tidalLink?: string; appleMusicLink?: string; @@ -66,18 +64,10 @@ async function putPlaylistRecord( name: playlist.name, description: playlist.description, createdAt: new Date().toISOString(), + pictureUrl: playlist.picture, spotifyLink: playlist.spotifyLink, }; - if (playlist.picture) { - const imageBuffer = await downloadImage(playlist.picture); - const encoding = await getContentType(playlist.picture); - const uploadResponse = await agent.uploadBlob(imageBuffer, { - encoding, - }); - record.picture = uploadResponse.data.blob; - } - if (!Playlist.validateRecord(record)) { console.error(`Invalid record: ${chalk.redBright(JSON.stringify(record))}`); return; @@ -110,6 +100,6 @@ async function putPlaylistRecord( await ctx.meilisearch.post( `indexes/playlists/documents?primaryKey=id`, - updatedPlaylist, + updatedPlaylist ); } diff --git a/apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts b/apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts index 51fad27d..0b74406b 100644 --- a/apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts +++ b/apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts @@ -1,4 +1,4 @@ -import type { Agent, BlobRef } from "@atproto/api"; +import type { Agent } from "@atproto/api"; import { TID } from "@atproto/common"; import type { HandlerAuth } from "@atproto/xrpc-server"; import chalk from "chalk"; @@ -15,7 +15,6 @@ import type { ScrobbleViewBasic } from "lexicon/types/app/rocksky/scrobble/defs" import * as Song from "lexicon/types/app/rocksky/song"; import { deepSnakeCaseKeys } from "lib"; import { createAgent } from "lib/agent"; -import downloadImage from "lib/downloadImage"; import { createHash } from "node:crypto"; import tables from "schema"; import type { SelectAlbum } from "schema/albums"; @@ -38,9 +37,9 @@ export default function (server: Server, ctx: Context) { pipe( scrobbleTrack(ctx, track, agent, did), Effect.tap(() => - Effect.logInfo(`Scrobble created for ${chalk.cyan(track.title)}`), - ), - ), + Effect.logInfo(`Scrobble created for ${chalk.cyan(track.title)}`) + ) + ) ), Effect.flatMap(presentation), Effect.retry({ times: 3 }), @@ -48,7 +47,7 @@ export default function (server: Server, ctx: Context) { Effect.catchAll((err) => { console.error(err); return Effect.succeed({}); - }), + }) ); server.app.rocksky.scrobble.createScrobble({ auth: ctx.authVerifier, @@ -82,11 +81,11 @@ const withAgent = ({ ctx, did, input, - })), + })) ), Match.orElse(() => { throw new Error("Authentication required to create a scrobble"); - }), + }) ), catch: (error) => new Error(`Failed to create agent: ${error}`), }); @@ -121,40 +120,17 @@ type ValidatedInput = { const generateRkey = Effect.succeed(TID.nextStr()); -const uploadImage = (url: string, agent: Agent) => - pipe( - Effect.tryPromise(() => downloadImage(url)), - Effect.map< - Buffer, - [Buffer, { encoding: string } | undefined] - >((imageBuffer) => { - if (url.endsWith(".jpeg") || url.endsWith(".jpg")) { - return [imageBuffer, { encoding: "image/jpeg" }]; - } else if (url.endsWith(".png")) { - return [imageBuffer, { encoding: "image/png" }]; - } - return [imageBuffer, undefined]; - }), - Effect.flatMap(([imageBuffer, options]) => - pipe( - Effect.tryPromise(() => agent.uploadBlob(imageBuffer, options)), - Effect.map((uploadResponse) => uploadResponse.data.blob), - ), - ), - Effect.catchAll(() => Effect.succeed(undefined as BlobRef | undefined)), - ); - const putRecord = ( agent: Agent, collection: string, record: T, - validate: (record: T) => { success: boolean }, + validate: (record: T) => { success: boolean } ) => pipe( Effect.succeed(record), Effect.filterOrFail( (rec) => validate(rec).success, - () => new Error("Invalid record"), + () => new Error("Invalid record") ), Effect.flatMap(() => pipe( @@ -167,48 +143,38 @@ const putRecord = ( rkey, record, validate: false, - }), - ), + }) + ) ), Effect.tap((res) => - Effect.logInfo(`Record created at ${res.data.uri}`), + Effect.logInfo(`Record created at ${res.data.uri}`) ), - Effect.map((res) => res.data.uri), - ), + Effect.map((res) => res.data.uri) + ) ), Effect.catchAll((error) => { console.error(`Error creating ${collection} record`, error); return Effect.succeed(null); - }), + }) ); const putArtistRecord = (track: Track, agent: Agent) => pipe( - track.artistPicture - ? uploadImage(track.artistPicture, agent) - : Effect.succeed(undefined), - Effect.map((picture) => ({ + Effect.succeed({ $type: "app.rocksky.artist", name: track.albumArtist, createdAt: new Date().toISOString(), - picture, + pictureUrl: track.artistPicture, tags: track.genres, - })), + }), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.artist", record, Artist.validateRecord), - ), + putRecord(agent, "app.rocksky.artist", record, Artist.validateRecord) + ) ); const putAlbumRecord = (track: Track, agent: Agent) => pipe( - Match.value(track.albumArt).pipe( - Match.when( - (url) => !!url, - (url) => uploadImage(url, agent), - ), - Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)), - ), - Effect.map((albumArt) => ({ + Effect.succeed({ $type: "app.rocksky.album", title: track.album, artist: track.albumArtist, @@ -217,23 +183,16 @@ const putAlbumRecord = (track: Track, agent: Agent) => ? track.releaseDate.toISOString() : undefined, createdAt: new Date().toISOString(), - albumArt, - })), + albumArtUrl: track.albumArt, + }), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.album", record, Album.validateRecord), - ), + putRecord(agent, "app.rocksky.album", record, Album.validateRecord) + ) ); const putSongRecord = (track: Track, agent: Agent) => pipe( - Match.value(track.albumArt).pipe( - Match.when( - (url) => !!url, - (url) => uploadImage(url, agent), - ), - Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)), - ), - Effect.map((albumArt) => ({ + Effect.succeed({ $type: "app.rocksky.song", title: track.title, artist: track.artist, @@ -244,7 +203,7 @@ const putSongRecord = (track: Track, agent: Agent) => ? track.releaseDate.toISOString() : undefined, year: track.year, - albumArt, + albumArtUrl: track.albumArt, composer: track.composer ?? undefined, lyrics: track.lyrics ?? undefined, trackNumber: track.trackNumber, @@ -252,26 +211,19 @@ const putSongRecord = (track: Track, agent: Agent) => copyrightMessage: track.copyrightMessage ?? undefined, createdAt: new Date().toISOString(), spotifyLink: track.spotifyLink ?? undefined, - })), + }), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.song", record, Song.validateRecord), - ), + putRecord(agent, "app.rocksky.song", record, Song.validateRecord) + ) ); const putScrobbleRecord = (track: Track, agent: Agent) => pipe( - Match.value(track.albumArt).pipe( - Match.when( - (url) => !!url, - (url) => uploadImage(url, agent), - ), - Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)), - ), - Effect.map((albumArt) => ({ + Effect.succeed({ $type: "app.rocksky.scrobble", title: track.title, albumArtist: track.albumArtist, - albumArt, + albumArtUrl: track.albumArt, artist: track.artist, album: track.album, duration: track.duration, @@ -288,10 +240,10 @@ const putScrobbleRecord = (track: Track, agent: Agent) => ? dayjs.unix(track.timestamp).toISOString() : new Date().toISOString(), spotifyLink: track.spotifyLink ?? undefined, - })), + }), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.scrobble", record, Scrobble.validateRecord), - ), + putRecord(agent, "app.rocksky.scrobble", record, Scrobble.validateRecord) + ) ); const getScrobble = ({ ctx, id }: { ctx: Context; id: string }) => @@ -303,12 +255,12 @@ const getScrobble = ({ ctx, id }: { ctx: Context; id: string }) => .leftJoin(tables.albums, eq(tables.albums.id, tables.scrobbles.albumId)) .leftJoin( tables.artists, - eq(tables.artists.id, tables.scrobbles.artistId), + eq(tables.artists.id, tables.scrobbles.artistId) ) .leftJoin(tables.users, eq(tables.users.id, tables.scrobbles.userId)) .where(eq(tables.scrobbles.id, id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getUserAlbum = ( @@ -318,7 +270,7 @@ const getUserAlbum = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -326,7 +278,7 @@ const getUserAlbum = ( .from(tables.userAlbums) .where(eq(tables.userAlbums.albumId, scrobble.albums.id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getUserArtist = ( @@ -336,7 +288,7 @@ const getUserArtist = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -344,7 +296,7 @@ const getUserArtist = ( .from(tables.userArtists) .where(eq(tables.userArtists.id, scrobble.artists.id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getUserTrack = ( @@ -354,7 +306,7 @@ const getUserTrack = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -362,7 +314,7 @@ const getUserTrack = ( .from(tables.userTracks) .where(eq(tables.userTracks.id, scrobble.tracks.id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getAlbumTrack = ( @@ -372,7 +324,7 @@ const getAlbumTrack = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -380,7 +332,7 @@ const getAlbumTrack = ( .from(tables.albumTracks) .where(eq(tables.albumTracks.trackId, scrobble.tracks.id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getArtistTrack = ( @@ -390,7 +342,7 @@ const getArtistTrack = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -398,7 +350,7 @@ const getArtistTrack = ( .from(tables.artistTracks) .where(eq(tables.artistTracks.trackId, scrobble.tracks.id)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); const getArtistAlbum = ( @@ -408,7 +360,7 @@ const getArtistAlbum = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => Effect.tryPromise(() => ctx.db @@ -417,10 +369,10 @@ const getArtistAlbum = ( .where( and( eq(tables.artistAlbums.albumId, scrobble.albums.id), - eq(tables.artistAlbums.artistId, scrobble.artists.id), - ), + eq(tables.artistAlbums.artistId, scrobble.artists.id) + ) ) - .then(([row]) => row), + .then(([row]) => row) ); const createUserArtist = ( @@ -430,7 +382,7 @@ const createUserArtist = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => pipe( Effect.tryPromise(() => @@ -442,7 +394,7 @@ const createUserArtist = ( uri: scrobble.artists.uri, scrobbles: 1, } as InsertUserArtist) - .execute(), + .execute() ), Effect.flatMap(() => Effect.tryPromise(() => @@ -451,9 +403,9 @@ const createUserArtist = ( .from(tables.userArtists) .where(eq(tables.userArtists.artistId, scrobble.artists.id)) .execute() - .then(([row]) => row), - ), - ), + .then(([row]) => row) + ) + ) ); const createUserAlbum = ( @@ -463,7 +415,7 @@ const createUserAlbum = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => pipe( Effect.tryPromise(() => @@ -475,7 +427,7 @@ const createUserAlbum = ( uri: scrobble.albums.uri, scrobbles: 1, } as InsertUserAlbum) - .execute(), + .execute() ), Effect.flatMap(() => Effect.tryPromise(() => @@ -484,9 +436,9 @@ const createUserAlbum = ( .from(tables.userAlbums) .where(eq(tables.userAlbums.albumId, scrobble.albums.id)) .execute() - .then(([row]) => row), - ), - ), + .then(([row]) => row) + ) + ) ); const createUserTrack = ( @@ -496,7 +448,7 @@ const createUserTrack = ( artists: SelectArtist; users: SelectUser; tracks: SelectTrack; - }, + } ) => pipe( Effect.tryPromise(() => @@ -508,7 +460,7 @@ const createUserTrack = ( uri: scrobble.tracks.uri, scrobbles: 1, } as InsertUserTrack) - .execute(), + .execute() ), Effect.flatMap(() => Effect.tryPromise(() => @@ -516,9 +468,9 @@ const createUserTrack = ( .select() .from(tables.userTracks) .where(eq(tables.userTracks.trackId, scrobble.tracks.id)) - .then(([row]) => row), - ), - ), + .then(([row]) => row) + ) + ) ); const publishScrobble = (ctx: Context, id: string) => @@ -689,50 +641,50 @@ const publishScrobble = (ctx: Context, id: string) => xata_updatedat: artistAlbum.updatedAt.toISOString(), xata_version: artistAlbum.xataVersion, }, - }), - ), - ), - ), - ), + }) + ) + ) + ) + ) ), Effect.flatMap((data) => Effect.try(() => ctx.nc.publish( "rocksky.scrobble", Buffer.from( - JSON.stringify(data).replaceAll("sha_256", "sha256"), - ), - ), - ), - ), - ), - ), - ), - ), + JSON.stringify(data).replaceAll("sha_256", "sha256") + ) + ) + ) + ) + ) + ) + ) + ) ); const computeTrackHash = (track: Track): Effect.Effect => Effect.succeed( createHash("sha256") .update(`${track.title} - ${track.artist} - ${track.album}`.toLowerCase()) - .digest("hex"), + .digest("hex") ); const computeAlbumHash = (track: Track): Effect.Effect => Effect.succeed( createHash("sha256") .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) - .digest("hex"), + .digest("hex") ); const computeArtistHash = (track: Track): Effect.Effect => Effect.succeed( - createHash("sha256").update(track.albumArtist.toLowerCase()).digest("hex"), + createHash("sha256").update(track.albumArtist.toLowerCase()).digest("hex") ); const fetchExistingTrack = ( ctx: Context, - trackHash: string, + trackHash: string ): Effect.Effect => Effect.tryPromise(() => ctx.db @@ -740,14 +692,14 @@ const fetchExistingTrack = ( .from(tables.tracks) .where(eq(tables.tracks.sha256, trackHash)) .execute() - .then(([row]) => row), + .then(([row]) => row) ); // Update track metadata (album_uri and artist_uri) const updateTrackMetadata = ( ctx: Context, track: Track, - trackRecord: SelectTrack, + trackRecord: SelectTrack ) => pipe( Effect.succeed(trackRecord), @@ -762,8 +714,8 @@ const updateTrackMetadata = ( .from(tables.albums) .where(eq(tables.albums.sha256, albumHash)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.flatMap((album) => album @@ -774,12 +726,12 @@ const updateTrackMetadata = ( albumUri: album.uri, }) .where(eq(tables.tracks.id, trackRecord.id)) - .execute(), + .execute() ) - : Effect.succeed(undefined), - ), + : Effect.succeed(undefined) + ) ) - : Effect.succeed(undefined), + : Effect.succeed(undefined) ), Effect.tap((trackRecord) => !trackRecord.artistUri @@ -792,8 +744,8 @@ const updateTrackMetadata = ( .from(tables.artists) .where(eq(tables.artists.sha256, artistHash)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.flatMap((artist) => artist @@ -804,13 +756,13 @@ const updateTrackMetadata = ( artistUri: artist.uri, }) .where(eq(tables.tracks.id, trackRecord.id)) - .execute(), + .execute() ) - : Effect.succeed(undefined), - ), + : Effect.succeed(undefined) + ) ) - : Effect.succeed(undefined), - ), + : Effect.succeed(undefined) + ) ); // Ensure track exists or create it @@ -819,7 +771,7 @@ const ensureTrack = ( track: Track, agent: Agent, userDid: string, - existingTrack: SelectTrack | undefined, + existingTrack: SelectTrack | undefined ) => pipe( Effect.succeed(existingTrack), @@ -827,10 +779,10 @@ const ensureTrack = ( Match.value(trackOpt).pipe( Match.when( (value) => !!value, - () => updateTrackMetadata(ctx, track, trackOpt), + () => updateTrackMetadata(ctx, track, trackOpt) ), - Match.orElse(() => Effect.succeed(undefined)), - ), + Match.orElse(() => Effect.succeed(undefined)) + ) ), Effect.flatMap((trackOpt) => pipe( @@ -840,29 +792,29 @@ const ensureTrack = ( .from(tables.userTracks) .leftJoin( tables.tracks, - eq(tables.userTracks.trackId, tables.tracks.id), + eq(tables.userTracks.trackId, tables.tracks.id) ) .leftJoin( tables.users, - eq(tables.userTracks.userId, tables.users.id), + eq(tables.userTracks.userId, tables.users.id) ) .where( and( eq(tables.tracks.id, trackOpt?.id), - eq(tables.users.did, userDid), - ), + eq(tables.users.did, userDid) + ) ) .execute() - .then(([row]) => row.user_tracks), + .then(([row]) => row.user_tracks) ), Effect.flatMap((userTrack) => Option.isNone(Option.fromNullable(userTrack)) || !userTrack?.uri?.includes(userDid) ? putSongRecord(track, agent) - : Effect.succeed(null), - ), - ), - ), + : Effect.succeed(null) + ) + ) + ) ); // Ensure album exists or create it @@ -870,7 +822,7 @@ const ensureAlbum = ( ctx: Context, track: Track, agent: Agent, - userDid: string, + userDid: string ) => pipe( computeAlbumHash(track), @@ -881,8 +833,8 @@ const ensureAlbum = ( .from(tables.albums) .where(eq(tables.albums.sha256, albumHash)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.flatMap((existingAlbum) => pipe( @@ -894,31 +846,31 @@ const ensureAlbum = ( .from(tables.userAlbums) .leftJoin( tables.albums, - eq(tables.userAlbums.albumId, tables.albums.id), + eq(tables.userAlbums.albumId, tables.albums.id) ) .leftJoin( tables.users, - eq(tables.userAlbums.userId, tables.users.id), + eq(tables.userAlbums.userId, tables.users.id) ) .where( and( eq(tables.albums.id, album.id), - eq(tables.users.did, userDid), - ), + eq(tables.users.did, userDid) + ) ) .execute() - .then(([row]) => row.user_albums), - ), + .then(([row]) => row.user_albums) + ) ), Effect.flatMap((userAlbum) => Option.isNone(Option.fromNullable(existingAlbum)) || Option.isNone(Option.fromNullable(userAlbum)) || !userAlbum?.uri?.includes(userDid) ? putAlbumRecord(track, agent) - : Effect.succeed(null), - ), - ), - ), + : Effect.succeed(null) + ) + ) + ) ); // Ensure artist exists or create it @@ -926,7 +878,7 @@ const ensureArtist = ( ctx: Context, track: Track, agent: Agent, - userDid: string, + userDid: string ) => pipe( computeArtistHash(track), @@ -937,8 +889,8 @@ const ensureArtist = ( .from(tables.artists) .where(eq(tables.artists.sha256, artistHash)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.flatMap((existingArtist) => pipe( @@ -950,21 +902,21 @@ const ensureArtist = ( .from(tables.userArtists) .leftJoin( tables.artists, - eq(tables.userArtists.artistId, tables.artists.id), + eq(tables.userArtists.artistId, tables.artists.id) ) .leftJoin( tables.users, - eq(tables.userArtists.userId, tables.users.id), + eq(tables.userArtists.userId, tables.users.id) ) .where( and( eq(tables.artists.id, artist.id), - eq(tables.users.did, userDid), - ), + eq(tables.users.did, userDid) + ) ) .execute() - .then(([row]) => row.user_artists), - ), + .then(([row]) => row.user_artists) + ) ), Effect.flatMap((userArtist) => Effect.if( @@ -974,18 +926,18 @@ const ensureArtist = ( { onTrue: () => putArtistRecord(track, agent), onFalse: () => Effect.succeed(null), - }, - ), - ), - ), - ), + } + ) + ) + ) + ) ); // Retry fetching track until metadata is ready const retryFetchTrack = ( ctx: Context, trackHash: string, - initialTrack: SelectTrack | undefined, + initialTrack: SelectTrack | undefined ) => pipe( Effect.iterate( @@ -1001,38 +953,38 @@ const retryFetchTrack = ( .from(tables.tracks) .where(eq(tables.tracks.sha256, trackHash)) .execute() - .then(([row]) => row), + .then(([row]) => row) ), Effect.flatMap((trackRecord) => Option.fromNullable(trackRecord).pipe( Effect.flatMap((track) => - updateTrackMetadata(ctx, track, trackRecord), - ), - ), + updateTrackMetadata(ctx, track, trackRecord) + ) + ) ), Effect.tap((trackRecord) => Effect.logInfo( trackRecord ? `Track metadata ready: ${chalk.cyan(trackRecord.id)} - ${track.title}, after ${chalk.magenta(tries + 1)} tries` - : `Retrying track fetch: ${chalk.magenta(tries + 1)}`, - ), + : `Retrying track fetch: ${chalk.magenta(tries + 1)}` + ) ), Effect.map((trackRecord) => ({ tries: tries + 1, track: trackRecord, })), - Effect.delay("1 second"), + Effect.delay("1 second") ), - }, + } ), Effect.tap(({ tries, track }) => tries >= 30 && !(track?.artistUri && track?.albumUri) ? Effect.logError( - `Track metadata not ready after ${chalk.magenta("30 tries")}`, + `Track metadata not ready after ${chalk.magenta("30 tries")}` ) - : Effect.succeed(undefined), + : Effect.succeed(undefined) ), - Effect.map(({ track }) => track), + Effect.map(({ track }) => track) ); // Retry fetching scrobble until complete @@ -1070,23 +1022,23 @@ const retryFetchScrobble = (ctx: Context, scrobbleUri: string) => .from(tables.scrobbles) .leftJoin( tables.tracks, - eq(tables.scrobbles.trackId, tables.tracks.id), + eq(tables.scrobbles.trackId, tables.tracks.id) ) .leftJoin( tables.albums, - eq(tables.scrobbles.albumId, tables.albums.id), + eq(tables.scrobbles.albumId, tables.albums.id) ) .leftJoin( tables.artists, - eq(tables.scrobbles.artistId, tables.artists.id), + eq(tables.scrobbles.artistId, tables.artists.id) ) .leftJoin( tables.users, - eq(tables.scrobbles.userId, tables.users.id), + eq(tables.scrobbles.userId, tables.users.id) ) .where(eq(tables.scrobbles.uri, scrobbleUri)) .execute() - .then(([row]) => row), + .then(([row]) => row) ), Effect.tap((scrobble) => Effect.if( @@ -1103,11 +1055,11 @@ const retryFetchScrobble = (ctx: Context, scrobbleUri: string) => artistUri: scrobble.artists.uri, }) .where(eq(tables.albums.id, scrobble.albums.id)) - .execute(), + .execute() ), onFalse: () => Effect.succeed(undefined), - }, - ), + } + ) ), Effect.flatMap(() => Effect.tryPromise(() => @@ -1116,24 +1068,24 @@ const retryFetchScrobble = (ctx: Context, scrobbleUri: string) => .from(tables.scrobbles) .leftJoin( tables.tracks, - eq(tables.scrobbles.trackId, tables.tracks.id), + eq(tables.scrobbles.trackId, tables.tracks.id) ) .leftJoin( tables.albums, - eq(tables.scrobbles.albumId, tables.albums.id), + eq(tables.scrobbles.albumId, tables.albums.id) ) .leftJoin( tables.artists, - eq(tables.scrobbles.artistId, tables.artists.id), + eq(tables.scrobbles.artistId, tables.artists.id) ) .leftJoin( tables.users, - eq(tables.scrobbles.userId, tables.users.id), + eq(tables.scrobbles.userId, tables.users.id) ) .where(eq(tables.scrobbles.uri, scrobbleUri)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.map((scrobble) => ({ tries: tries + 1, @@ -1150,12 +1102,12 @@ const retryFetchScrobble = (ctx: Context, scrobbleUri: string) => scrobble.tracks.albumUri && scrobble.scrobbles ? `Scrobble found after ${chalk.magenta(tries + 1)} tries` - : `Scrobble not found, trying again: ${chalk.magenta(tries + 1)}`, - ), + : `Scrobble not found, trying again: ${chalk.magenta(tries + 1)}` + ) ), - Effect.delay("1 second"), + Effect.delay("1 second") ), - }, + } ), Effect.tap(({ tries, scrobble }) => tries >= 30 && @@ -1169,18 +1121,18 @@ const retryFetchScrobble = (ctx: Context, scrobbleUri: string) => scrobble.tracks.albumUri ) ? Effect.logError( - `Scrobble not found after ${chalk.magenta("30 tries")}`, + `Scrobble not found after ${chalk.magenta("30 tries")}` ) - : Effect.succeed(undefined), + : Effect.succeed(undefined) ), - Effect.map(({ scrobble }) => scrobble), + Effect.map(({ scrobble }) => scrobble) ); export const scrobbleTrack = ( ctx: Context, track: Track, agent: Agent, - userDid: string, + userDid: string ) => pipe( computeTrackHash(track), @@ -1193,7 +1145,7 @@ export const scrobbleTrack = ( Effect.flatMap(() => ensureAlbum(ctx, track, agent, userDid)), Effect.flatMap(() => ensureArtist(ctx, track, agent, userDid)), Effect.flatMap(() => - retryFetchTrack(ctx, trackHash, existingTrack), + retryFetchTrack(ctx, trackHash, existingTrack) ), Effect.flatMap(() => pipe( @@ -1213,17 +1165,17 @@ export const scrobbleTrack = ( ? pipe( publishScrobble(ctx, scrobble.scrobbles.id), Effect.tap(() => - Effect.logInfo("Scrobble published"), - ), + Effect.logInfo("Scrobble published") + ) ) - : Effect.succeed(undefined), - ), - ), - ), - ), - ), - ), - ), - ), - ), + : Effect.succeed(undefined) + ) + ) + ) + ) + ) + ) + ) + ) + ) ); diff --git a/apps/api/src/xrpc/app/rocksky/song/createSong.ts b/apps/api/src/xrpc/app/rocksky/song/createSong.ts index 1adce5ad..0fea42ec 100644 --- a/apps/api/src/xrpc/app/rocksky/song/createSong.ts +++ b/apps/api/src/xrpc/app/rocksky/song/createSong.ts @@ -1,4 +1,4 @@ -import type { Agent, BlobRef } from "@atproto/api"; +import type { Agent } from "@atproto/api"; import { TID } from "@atproto/common"; import type { HandlerAuth } from "@atproto/xrpc-server"; import chalk from "chalk"; @@ -14,7 +14,6 @@ import type { InputSchema } from "lexicon/types/app/rocksky/song/createSong"; import type { SongViewDetailed } from "lexicon/types/app/rocksky/song/defs"; import { deepSnakeCaseKeys } from "lib"; import { createAgent } from "lib/agent"; -import downloadImage from "lib/downloadImage"; import { createHash } from "node:crypto"; import tables from "schema"; import type { InsertAlbumTrack, SelectAlbumTrack } from "schema/album-tracks"; @@ -44,7 +43,7 @@ export default function (server: Server, ctx: Context) { Effect.catchAll((err) => { console.error(err); return Effect.succeed({}); - }), + }) ); server.app.rocksky.song.createSong({ auth: ctx.authVerifier, @@ -78,11 +77,11 @@ const withAgent = ({ ctx, did, input, - })), + })) ), Match.orElse(() => { throw new Error("Authentication required to create a song"); - }), + }) ), catch: (error) => new Error(`Failed to create agent: ${error}`), }); @@ -151,24 +150,24 @@ const computeTrackHash = (track: Track): Effect.Effect => Effect.succeed( createHash("sha256") .update(`${track.title} - ${track.artist} - ${track.album}`.toLowerCase()) - .digest("hex"), + .digest("hex") ); const computeAlbumHash = (track: Track): Effect.Effect => Effect.succeed( createHash("sha256") .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) - .digest("hex"), + .digest("hex") ); const computeArtistHash = (track: Track): Effect.Effect => Effect.succeed( - createHash("sha256").update(track.albumArtist.toLowerCase()).digest("hex"), + createHash("sha256").update(track.albumArtist.toLowerCase()).digest("hex") ); const fetchExistingTrack = ( ctx: Context, - trackHash: string, + trackHash: string ): Effect.Effect => Effect.tryPromise(() => ctx.db @@ -176,30 +175,7 @@ const fetchExistingTrack = ( .from(tables.tracks) .where(eq(tables.tracks.sha256, trackHash)) .execute() - .then(([row]) => row), - ); - -const uploadImage = (url: string, agent: Agent) => - pipe( - Effect.tryPromise(() => downloadImage(url)), - Effect.map< - Buffer, - [Buffer, { encoding: string } | undefined] - >((imageBuffer) => { - if (url.endsWith(".jpeg") || url.endsWith(".jpg")) { - return [imageBuffer, { encoding: "image/jpeg" }]; - } else if (url.endsWith(".png")) { - return [imageBuffer, { encoding: "image/png" }]; - } - return [imageBuffer, undefined]; - }), - Effect.flatMap(([imageBuffer, options]) => - pipe( - Effect.tryPromise(() => agent.uploadBlob(imageBuffer, options)), - Effect.map((uploadResponse) => uploadResponse.data.blob), - ), - ), - Effect.catchAll(() => Effect.succeed(undefined as BlobRef | undefined)), + .then(([row]) => row) ); const generateRkey = Effect.succeed(TID.nextStr()); @@ -208,13 +184,13 @@ const putRecord = ( agent: Agent, collection: string, record: T, - validate: (record: T) => { success: boolean }, + validate: (record: T) => { success: boolean } ): Effect.Effect => pipe( Effect.succeed(record), Effect.filterOrFail( (rec) => validate(rec).success, - () => new Error("Invalid record"), + () => new Error("Invalid record") ), Effect.flatMap(() => pipe( @@ -227,47 +203,37 @@ const putRecord = ( rkey, record, validate: false, - }), - ), + }) + ) ), Effect.tap((res) => - Effect.logInfo(`Record created at ${res.data.uri}`), + Effect.logInfo(`Record created at ${res.data.uri}`) ), - Effect.map((res) => res.data.uri), - ), + Effect.map((res) => res.data.uri) + ) ), Effect.catchAll((error) => { console.error(`Error creating ${collection} record`, error); return Effect.fail(error); - }), + }) ); const putArtistRecord = (track: Track, agent: Agent) => pipe( - track.artistPicture - ? uploadImage(track.artistPicture, agent) - : Effect.succeed(undefined), - Effect.map((picture) => ({ + Effect.succeed({ $type: "app.rocksky.artist", name: track.albumArtist, createdAt: new Date().toISOString(), - picture, - })), + pictureUrl: track.artistPicture, + }), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.artist", record, Artist.validateRecord), - ), + putRecord(agent, "app.rocksky.artist", record, Artist.validateRecord) + ) ); const putAlbumRecord = (track: Track, agent: Agent) => pipe( - Match.value(track.albumArt).pipe( - Match.when( - (url) => !!url, - (url) => uploadImage(url, agent), - ), - Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)), - ), - Effect.map((albumArt) => ({ + Effect.succeed({ $type: "app.rocksky.album", title: track.album, artist: track.albumArtist, @@ -276,23 +242,16 @@ const putAlbumRecord = (track: Track, agent: Agent) => ? track.releaseDate.toISOString() : undefined, createdAt: new Date().toISOString(), - albumArt, - })), + albumArtUrl: track.albumArt, + }), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.album", record, Album.validateRecord), - ), + putRecord(agent, "app.rocksky.album", record, Album.validateRecord) + ) ); const putSongRecord = (track: Track, agent: Agent) => pipe( - Match.value(track.albumArt).pipe( - Match.when( - (url) => !!url, - (url) => uploadImage(url, agent), - ), - Match.orElse(() => Effect.succeed(undefined as BlobRef | undefined)), - ), - Effect.map((albumArt) => ({ + Effect.succeed({ $type: "app.rocksky.song", title: track.title, artist: track.artist, @@ -303,7 +262,7 @@ const putSongRecord = (track: Track, agent: Agent) => ? track.releaseDate.toISOString() : undefined, year: track.year, - albumArt, + albumArtUrl: track.albumArt, composer: track.composer ?? undefined, lyrics: track.lyrics ?? undefined, trackNumber: track.trackNumber, @@ -311,10 +270,10 @@ const putSongRecord = (track: Track, agent: Agent) => copyrightMessage: track.copyrightMessage ?? undefined, createdAt: new Date().toISOString(), spotifyLink: track.spotifyLink ?? undefined, - })), + }), Effect.flatMap((record) => - putRecord(agent, "app.rocksky.song", record, Song.validateRecord), - ), + putRecord(agent, "app.rocksky.song", record, Song.validateRecord) + ) ); const ensureTrack = (ctx: Context, track: Track, agent: Agent) => @@ -329,24 +288,24 @@ const ensureTrack = (ctx: Context, track: Track, agent: Agent) => Effect.tap((trackOpt) => trackOpt ? updateTrackMetadata(ctx, track, trackOpt) - : Effect.succeed(undefined), + : Effect.succeed(undefined) ), Effect.flatMap((trackOpt) => trackOpt.uri ? Effect.succeed(trackOpt.uri) - : putSongRecord(track, agent), - ), - ), - ), - ), - ), + : putSongRecord(track, agent) + ) + ) + ) + ) + ) ); // Update track metadata (album_uri and artist_uri) const updateTrackMetadata = ( ctx: Context, track: Track, - trackRecord: SelectTrack, + trackRecord: SelectTrack ) => pipe( Effect.succeed(trackRecord), @@ -361,8 +320,8 @@ const updateTrackMetadata = ( .from(tables.albums) .where(eq(tables.albums.sha256, albumHash)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.flatMap((album) => Option.fromNullable(album).pipe( @@ -372,14 +331,14 @@ const updateTrackMetadata = ( .update(tables.tracks) .set({ albumUri: album.uri }) .where(eq(tables.tracks.id, trackRecord.id)) - .execute(), - ), + .execute() + ) ), - Effect.catchAll(() => Effect.succeed(undefined)), - ), - ), + Effect.catchAll(() => Effect.succeed(undefined)) + ) + ) ) - : Effect.succeed(undefined), + : Effect.succeed(undefined) ), Effect.tap((trackRecord) => !trackRecord.artistUri @@ -392,8 +351,8 @@ const updateTrackMetadata = ( .from(tables.artists) .where(eq(tables.artists.sha256, artistHash)) .execute() - .then(([row]) => row), - ), + .then(([row]) => row) + ) ), Effect.flatMap((artist) => Option.fromNullable(artist).pipe( @@ -403,15 +362,15 @@ const updateTrackMetadata = ( .update(tables.tracks) .set({ artistUri: artist.uri }) .where(eq(tables.tracks.id, trackRecord.id)) - .execute(), - ), + .execute() + ) ), - Effect.catchAll(() => Effect.succeed(undefined)), - ), - ), + Effect.catchAll(() => Effect.succeed(undefined)) + ) + ) ) - : Effect.succeed(undefined), - ), + : Effect.succeed(undefined) + ) ); // Ensure artist exists or create it @@ -426,7 +385,7 @@ const ensureArtist = (ctx: Context, track: Track, agent: Agent) => .from(tables.artists) .where(eq(tables.artists.sha256, artistHash)) .execute() - .then(([row]) => row), + .then(([row]) => row) ), Effect.flatMap((existingArtist) => pipe( @@ -434,12 +393,12 @@ const ensureArtist = (ctx: Context, track: Track, agent: Agent) => Effect.flatMap((artistOpt) => artistOpt.uri ? Effect.succeed(artistOpt.uri) - : putArtistRecord(track, agent), - ), - ), - ), - ), - ), + : putArtistRecord(track, agent) + ) + ) + ) + ) + ) ); // Ensure album exists or create it @@ -454,7 +413,7 @@ const ensureAlbum = (ctx: Context, track: Track, agent: Agent) => .from(tables.albums) .where(eq(tables.albums.sha256, albumHash)) .execute() - .then(([row]) => row), + .then(([row]) => row) ), Effect.flatMap((existingAlbum) => pipe( @@ -462,12 +421,12 @@ const ensureAlbum = (ctx: Context, track: Track, agent: Agent) => Effect.flatMap((albumOpt) => albumOpt.uri ? Effect.succeed(albumOpt.uri) - : putAlbumRecord(track, agent), - ), - ), - ), - ), - ), + : putAlbumRecord(track, agent) + ) + ) + ) + ) + ) ); // Fetch track, album, and artist by URIs @@ -475,7 +434,7 @@ const fetchRecordsByUris = ( ctx: Context, trackUri: string, albumUri: string, - artistUri: string, + artistUri: string ): Effect.Effect< { track: SelectTrack | null; @@ -491,7 +450,7 @@ const fetchRecordsByUris = ( .from(tables.tracks) .where(eq(tables.tracks.uri, trackUri)) .execute() - .then(([row]) => row), + .then(([row]) => row) ), album: Effect.tryPromise(() => ctx.db @@ -499,7 +458,7 @@ const fetchRecordsByUris = ( .from(tables.albums) .where(eq(tables.albums.uri, albumUri)) .execute() - .then(([row]) => row), + .then(([row]) => row) ), artist: Effect.tryPromise(() => ctx.db @@ -507,7 +466,7 @@ const fetchRecordsByUris = ( .from(tables.artists) .where(eq(tables.artists.uri, artistUri)) .execute() - .then(([row]) => row), + .then(([row]) => row) ), }); @@ -516,7 +475,7 @@ const ensureRelationships = ( ctx: Context, track: SelectTrack, album: SelectAlbum, - artist: SelectArtist, + artist: SelectArtist ) => pipe( Effect.all({ @@ -527,11 +486,11 @@ const ensureRelationships = ( .where( and( eq(tables.albumTracks.albumId, album.id), - eq(tables.albumTracks.trackId, track.id), - ), + eq(tables.albumTracks.trackId, track.id) + ) ) .execute() - .then(([row]) => row), + .then(([row]) => row) ), artistTrack: Effect.tryPromise(() => @@ -541,11 +500,11 @@ const ensureRelationships = ( .where( and( eq(tables.artistTracks.artistId, artist.id), - eq(tables.artistTracks.trackId, track.id), - ), + eq(tables.artistTracks.trackId, track.id) + ) ) .execute() - .then(([row]) => row), + .then(([row]) => row) ), artistAlbum: Effect.tryPromise(() => ctx.db @@ -554,11 +513,11 @@ const ensureRelationships = ( .where( and( eq(tables.artistAlbums.artistId, artist.id), - eq(tables.artistAlbums.albumId, album.id), - ), + eq(tables.artistAlbums.albumId, album.id) + ) ) .execute() - .then(([row]) => row), + .then(([row]) => row) ), }), Effect.flatMap(({ albumTrack, artistTrack, artistAlbum }) => @@ -576,9 +535,9 @@ const ensureRelationships = ( } as InsertAlbumTrack) .returning() .execute() - .then(([row]) => row), - ), - ), + .then(([row]) => row) + ) + ) ), pipe( Option.fromNullable(artistTrack), @@ -592,9 +551,9 @@ const ensureRelationships = ( } as InsertArtistTrack) .returning() .execute() - .then(([row]) => row), - ), - ), + .then(([row]) => row) + ) + ) ), pipe( Option.fromNullable(artistAlbum), @@ -608,18 +567,18 @@ const ensureRelationships = ( } as InsertArtistAlbum) .returning() .execute() - .then(([row]) => row), - ), - ), + .then(([row]) => row) + ) + ) ), ]), Effect.map(([albumTrack, artistTrack, artistAlbum]) => ({ albumTrack, artistTrack, artistAlbum, - })), - ), - ), + })) + ) + ) ); // Update track with album and artist URIs if missing @@ -627,7 +586,7 @@ const updateTrackUris = ( ctx: Context, track: SelectTrack, album: SelectAlbum, - artist: SelectArtist, + artist: SelectArtist ) => pipe( Effect.succeed(track), @@ -640,9 +599,9 @@ const updateTrackUris = ( albumUri: album.uri, }) .where(eq(tables.tracks.id, trackRecord.id)) - .execute(), + .execute() ) - : Effect.succeed(undefined), + : Effect.succeed(undefined) ), Effect.tap((trackRecord) => !trackRecord.artistUri @@ -653,10 +612,10 @@ const updateTrackUris = ( artistUri: artist.uri, }) .where(eq(tables.tracks.id, trackRecord.id)) - .execute(), + .execute() ) - : Effect.succeed(undefined), - ), + : Effect.succeed(undefined) + ) ); const publishTrack = ( @@ -664,7 +623,7 @@ const publishTrack = ( track: SelectTrack, albumTrack: SelectAlbumTrack, artistTrack: SelectArtistTrack, - artistAlbum: SelectArtistAlbum, + artistAlbum: SelectArtistAlbum ) => pipe( Effect.succeed( @@ -714,16 +673,16 @@ const publishTrack = ( xata_updatedat: artistAlbum.updatedAt.toISOString(), xata_version: artistAlbum.xataVersion, }, - }), + }) ), Effect.flatMap((message) => Effect.try(() => ctx.nc.publish( "rocksky.track", - Buffer.from(JSON.stringify(message).replaceAll("sha_256", "sha256")), - ), - ), - ), + Buffer.from(JSON.stringify(message).replaceAll("sha_256", "sha256")) + ) + ) + ) ); export const saveTrack = (ctx: Context, track: Track, agent: Agent) => @@ -776,29 +735,29 @@ export const saveTrack = (ctx: Context, track: Track, agent: Agent) => Effect.filterOrFail( () => !!track, () => - new Error(`Track not found for uri: ${trackUri}`), - ), + new Error(`Track not found for uri: ${trackUri}`) + ) ), Option.fromNullable(album).pipe( Effect.filterOrFail( () => !!album, () => - new Error(`Album not found for uri: ${albumUri}`), - ), + new Error(`Album not found for uri: ${albumUri}`) + ) ), Option.fromNullable(artist).pipe( Effect.filterOrFail( () => !!artist, () => - new Error(`Artist not found for uri: ${artistUri}`), - ), + new Error(`Artist not found for uri: ${artistUri}`) + ) ), ]), Effect.flatMap(([track, album, artist]) => pipe( updateTrackUris(ctx, track, album, artist), Effect.flatMap(() => - ensureRelationships(ctx, track, album, artist), + ensureRelationships(ctx, track, album, artist) ), Effect.map( ({ albumTrack, artistTrack, artistAlbum }) => ({ @@ -809,11 +768,11 @@ export const saveTrack = (ctx: Context, track: Track, agent: Agent) => albumTrack, artistTrack, artistAlbum, - }), - ), - ), - ), - ), + }) + ) + ) + ) + ) ), Effect.tap( ({ @@ -835,8 +794,8 @@ export const saveTrack = (ctx: Context, track: Track, agent: Agent) => track.albumUri && track.artistUri ? `Track saved successfully after ${chalk.magenta(tries + 1)} tries` - : `Track not yet saved, retrying... ${chalk.magenta(tries + 1)}`, - ), + : `Track not yet saved, retrying... ${chalk.magenta(tries + 1)}` + ) ), Effect.tap( ({ @@ -851,25 +810,25 @@ export const saveTrack = (ctx: Context, track: Track, agent: Agent) => tries === 15 ? pipe( Effect.logError( - "Failed to save track after 15 tries", + "Failed to save track after 15 tries" ), Effect.tap(() => Effect.logDebug( - `Debug info: track=${JSON.stringify(track)}, album=${JSON.stringify(album)}, artist=${JSON.stringify(artist)}, albumTrack=${JSON.stringify(albumTrack)}, artistTrack=${JSON.stringify(artistTrack)}, artistAlbum=${JSON.stringify(artistAlbum)}`, - ), - ), + `Debug info: track=${JSON.stringify(track)}, album=${JSON.stringify(album)}, artist=${JSON.stringify(artist)}, albumTrack=${JSON.stringify(albumTrack)}, artistTrack=${JSON.stringify(artistTrack)}, artistAlbum=${JSON.stringify(artistAlbum)}` + ) + ) ) - : Effect.succeed(undefined), + : Effect.succeed(undefined) ), - Effect.delay("1 second"), + Effect.delay("1 second") ), - }, + } ), Effect.tap(({ tries, track, albumTrack, artistTrack, artistAlbum }) => tries < 15 && track && albumTrack && artistTrack && artistAlbum ? publishTrack(ctx, track, albumTrack, artistTrack, artistAlbum) - : Effect.succeed(undefined), - ), - ), - ), + : Effect.succeed(undefined) + ) + ) + ) ); -- 2.51.2