A decentralized music tracking and discovery platform built on AT Protocol 🎵

Add consola logger and scrobble subscriber

+594 -279
+1
apps/api/package.json
··· 58 58 "better-sqlite3": "^12.4.1", 59 59 "chalk": "^5.4.1", 60 60 "chanfana": "^2.0.2", 61 + "consola": "^3.4.2", 61 62 "cors": "^2.8.5", 62 63 "dayjs": "^1.11.13", 63 64 "dotenv": "^16.4.7",
+3 -2
apps/api/scripts/pkl.ts
··· 2 2 import { readdirSync, statSync } from "fs"; 3 3 import { join } from "path"; 4 4 import { $ } from "zx"; 5 + import { consola } from "consola"; 5 6 6 7 function getPklFilesRecursive(dir: string): string[] { 7 8 const entries = readdirSync(dir); ··· 28 29 29 30 await Promise.all( 30 31 files.map(async (fullPath) => { 31 - console.log(`pkl eval ${chalk.cyan(fullPath)}`); 32 + consola.info(`pkl eval ${chalk.cyan(fullPath)}`); 32 33 await $`pkl eval -f json ${fullPath} > ${fullPath.replace(/\.pkl$/, ".json").replace(/pkl[\\\/]defs/g, "lexicons")}`; 33 - }) 34 + }), 34 35 );
+3 -2
apps/api/src/bsky/app.ts
··· 1 1 import { AtpAgent } from "@atproto/api"; 2 + import { consola } from "consola"; 2 3 import type { BlobRef } from "@atproto/lexicon"; 3 4 import { isValidHandle } from "@atproto/syntax"; 4 5 import { ctx } from "context"; ··· 134 135 ); 135 136 ctx.kv.set(did, token); 136 137 } catch (err) { 137 - console.error({ err }, "oauth callback failed"); 138 + consola.error({ err }, "oauth callback failed"); 138 139 return c.redirect(`${env.FRONTEND_URL}?error=1`); 139 140 } 140 141 ··· 205 206 .execute(); 206 207 } catch (e) { 207 208 if (!e.message.includes("invalid record: column [did]: is not unique")) { 208 - console.error(e.message); 209 + consola.error(e.message); 209 210 } else { 210 211 await ctx.db 211 212 .update(users)
+2 -1
apps/api/src/context.ts
··· 1 1 import { createClient } from "auth/client"; 2 2 import axios from "axios"; 3 + import { consola } from "consola"; 3 4 import { createDb, migrateToLatest } from "db"; 4 5 import drizzle from "drizzle"; 5 6 import authVerifier from "lib/authVerifier"; ··· 35 36 redis: await redis 36 37 .createClient({ url: env.REDIS_URL }) 37 38 .on("error", (err) => { 38 - console.error("Uncaught Redis Client Error", err); 39 + consola.error("Uncaught Redis Client Error", err); 39 40 process.exit(1); 40 41 }) 41 42 .connect(),
+6 -5
apps/api/src/db.ts
··· 9 9 SqliteDialect, 10 10 } from "kysely"; 11 11 import { createAgent } from "lib/agent"; 12 + import { consola } from "consola"; 12 13 13 14 // Types 14 15 ··· 113 114 export const updateExpiresAt = async (db: Database) => { 114 115 // get all sessions that have expiresAt is null 115 116 const sessions = await db.selectFrom("auth_session").selectAll().execute(); 116 - console.log("Found", sessions.length, "sessions to update"); 117 + consola.info("Found", sessions.length, "sessions to update"); 117 118 for (const session of sessions) { 118 119 const data = JSON.parse(session.session) as { 119 120 tokenSet: { expires_at?: string | null }; 120 121 }; 121 - console.log(session.key, data.tokenSet.expires_at); 122 + consola.info(session.key, data.tokenSet.expires_at); 122 123 await db 123 124 .updateTable("auth_session") 124 125 .set({ expiresAt: data.tokenSet.expires_at }) ··· 126 127 .execute(); 127 128 } 128 129 129 - console.log(`Updated ${chalk.greenBright(sessions.length)} sessions`); 130 + consola.info(`Updated ${chalk.greenBright(sessions.length)} sessions`); 130 131 }; 131 132 132 133 export const refreshSessionsAboutToExpire = async ( ··· 144 145 .execute(); 145 146 146 147 for (const session of sessions) { 147 - console.log( 148 + consola.info( 148 149 "Session about to expire:", 149 150 chalk.cyan(session.key), 150 151 session.expiresAt, ··· 155 156 await new Promise((r) => setTimeout(r, 200)); 156 157 } 157 158 158 - console.log( 159 + consola.info( 159 160 `Found ${chalk.yellowBright(sessions.length)} sessions to refresh`, 160 161 ); 161 162 };
+2 -1
apps/api/src/dropbox/app.ts
··· 1 1 import axios from "axios"; 2 + import { consola } from "consola"; 2 3 import { ctx } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Hono } from "hono"; ··· 159 160 if ( 160 161 !e.message.includes("invalid record: column [user_id]: is not unique") 161 162 ) { 162 - console.error(e.message); 163 + consola.error(e.message); 163 164 } else { 164 165 throw e; 165 166 }
+3 -2
apps/api/src/googledrive/app.ts
··· 1 1 import axios from "axios"; 2 + import { consola } from "consola"; 2 3 import { ctx } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import fs from "fs"; ··· 185 186 }); 186 187 } catch (e) { 187 188 if (!e.message.includes("duplicate key value violates unique constraint")) { 188 - console.error(e.message); 189 + consola.error(e.message); 189 190 } else { 190 191 throw e; 191 192 } ··· 263 264 return c.json(data); 264 265 } catch (error) { 265 266 if (axios.isAxiosError(error)) { 266 - console.error("Axios error:", error.response?.data || error.message); 267 + consola.error("Axios error:", error.response?.data || error.message); 267 268 268 269 const credentials = JSON.parse( 269 270 fs.readFileSync("credentials.json").toString("utf-8"),
+2 -1
apps/api/src/index.ts
··· 1 1 import { serve } from "@hono/node-server"; 2 2 import { createNodeWebSocket } from "@hono/node-ws"; 3 3 import { trace } from "@opentelemetry/api"; 4 + import { consola } from "consola"; 4 5 import { ctx } from "context"; 5 6 import { and, desc, eq, isNotNull, or } from "drizzle-orm"; 6 7 import { Hono } from "hono"; ··· 466 467 await saveTrack(ctx, track, agent); 467 468 } catch (e) { 468 469 if (!e.message.includes("duplicate key value violates unique constraint")) { 469 - console.error("[tracks]", e.message); 470 + consola.error("[tracks]", e.message); 470 471 } 471 472 } 472 473
+7 -6
apps/api/src/lib/agent.ts
··· 1 1 import { Agent, AtpAgent } from "@atproto/api"; 2 2 import type { NodeOAuthClient } from "@atproto/oauth-client-node"; 3 + import { consola } from "consola"; 3 4 import extractPdsFromDid from "./extractPdsFromDid"; 4 5 import { ctx } from "context"; 5 6 ··· 29 30 try { 30 31 await atpAgent.resumeSession(JSON.parse(result.session)); 31 32 } catch (e) { 32 - console.log("Error resuming session"); 33 - console.log(did); 34 - console.log(e); 33 + consola.info("Error resuming session"); 34 + consola.info(did); 35 + consola.info(e); 35 36 await ctx.sqliteDb 36 37 .deleteFrom("auth_session") 37 38 .where("key", "=", `atp:${did}`) ··· 47 48 retry += 1; 48 49 } 49 50 } catch (e) { 50 - console.log("Error creating agent"); 51 - console.log(did); 52 - console.log(e); 51 + consola.info("Error creating agent"); 52 + consola.info(did); 53 + consola.info(e); 53 54 await new Promise((r) => setTimeout(r, 1000)); 54 55 retry += 1; 55 56 }
+4 -3
apps/api/src/lovedtracks/lovedtracks.service.ts
··· 1 1 import { AtpAgent, type Agent } from "@atproto/api"; 2 2 import { TID } from "@atproto/common"; 3 + import { consola } from "consola"; 3 4 import type { Context } from "context"; 4 5 import { and, desc, eq, type SQLWrapper } from "drizzle-orm"; 5 6 import * as LikeLexicon from "lexicon/types/app/rocksky/like"; ··· 279 280 }; 280 281 281 282 if (!LikeLexicon.validateRecord(record).success) { 282 - console.log(LikeLexicon.validateRecord(record)); 283 + consola.info(LikeLexicon.validateRecord(record)); 283 284 throw new Error("Invalid record"); 284 285 } 285 286 ··· 292 293 validate: false, 293 294 }); 294 295 const uri = res.data.uri; 295 - console.log(`Like record created at: ${uri}`); 296 + consola.info(`Like record created at: ${uri}`); 296 297 297 298 [created] = await ctx.db 298 299 .update(lovedTracks) ··· 300 301 .where(eq(lovedTracks.id, created.id)) 301 302 .returning(); 302 303 } catch (e) { 303 - console.error(`Error creating like record: ${e.message}`); 304 + consola.error(`Error creating like record: ${e.message}`); 304 305 } 305 306 } 306 307
+29 -28
apps/api/src/nowplaying/nowplaying.service.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 2 import { TID } from "@atproto/common"; 3 3 import chalk from "chalk"; 4 + import { consola } from "consola"; 4 5 import type { Context } from "context"; 5 6 import dayjs from "dayjs"; 6 7 import { and, eq, gte, lte, or } from "drizzle-orm"; ··· 38 39 }; 39 40 40 41 if (!Artist.validateRecord(record).success) { 41 - console.log(Artist.validateRecord(record)); 42 - console.log(JSON.stringify(record, null, 2)); 42 + consola.info(Artist.validateRecord(record)); 43 + consola.info(JSON.stringify(record, null, 2)); 43 44 throw new Error("Invalid record"); 44 45 } 45 46 ··· 52 53 validate: false, 53 54 }); 54 55 const uri = res.data.uri; 55 - console.log(`Artist record created at ${uri}`); 56 + consola.info(`Artist record created at ${uri}`); 56 57 return uri; 57 58 } catch (e) { 58 - console.error("Error creating artist record", e); 59 + consola.error("Error creating artist record", e); 59 60 return null; 60 61 } 61 62 } ··· 79 80 }; 80 81 81 82 if (!Album.validateRecord(record).success) { 82 - console.log(Album.validateRecord(record)); 83 - console.log(JSON.stringify(record, null, 2)); 83 + consola.info(Album.validateRecord(record)); 84 + consola.info(JSON.stringify(record, null, 2)); 84 85 throw new Error("Invalid record"); 85 86 } 86 87 ··· 93 94 validate: false, 94 95 }); 95 96 const uri = res.data.uri; 96 - console.log(`Album record created at ${uri}`); 97 + consola.info(`Album record created at ${uri}`); 97 98 return uri; 98 99 } catch (e) { 99 - console.error("Error creating album record", e); 100 + consola.error("Error creating album record", e); 100 101 return null; 101 102 } 102 103 } ··· 134 135 }; 135 136 136 137 if (!Song.validateRecord(record).success) { 137 - console.log(Song.validateRecord(record)); 138 - console.log(chalk.cyan(JSON.stringify(record, null, 2))); 138 + consola.info(Song.validateRecord(record)); 139 + consola.info(chalk.cyan(JSON.stringify(record, null, 2))); 139 140 throw new Error("Invalid record"); 140 141 } 141 142 ··· 148 149 validate: false, 149 150 }); 150 151 const uri = res.data.uri; 151 - console.log(`Song record created at ${uri}`); 152 + consola.info(`Song record created at ${uri}`); 152 153 return uri; 153 154 } catch (e) { 154 - console.error("Error creating song record", e); 155 + consola.error("Error creating song record", e); 155 156 return null; 156 157 } 157 158 } ··· 192 193 }; 193 194 194 195 if (!Scrobble.validateRecord(record).success) { 195 - console.log(Scrobble.validateRecord(record)); 196 - console.log(JSON.stringify(record, null, 2)); 196 + consola.info(Scrobble.validateRecord(record)); 197 + consola.info(JSON.stringify(record, null, 2)); 197 198 throw new Error("Invalid record"); 198 199 } 199 200 ··· 206 207 validate: false, 207 208 }); 208 209 const uri = res.data.uri; 209 - console.log(`Scrobble record created at ${uri}`); 210 + consola.info(`Scrobble record created at ${uri}`); 210 211 return uri; 211 212 } catch (e) { 212 - console.error("Error creating scrobble record", e); 213 + consola.error("Error creating scrobble record", e); 213 214 return null; 214 215 } 215 216 } ··· 531 532 .then((rows) => rows[0]); 532 533 533 534 if (existingScrobble) { 534 - console.log( 535 + consola.info( 535 536 `Scrobble already exists for ${chalk.cyan(track.title)} at ${chalk.cyan( 536 537 scrobbleTime.format("YYYY-MM-DD HH:mm:ss"), 537 538 )}`, ··· 641 642 name: artist.name, 642 643 })); 643 644 } catch (error) { 644 - console.error("Error fetching MusicBrainz data"); 645 + consola.error("Error fetching MusicBrainz data"); 645 646 } 646 647 647 648 if (!existingTrack?.uri || !userTrack?.userTrack.uri?.includes(userDid)) { ··· 664 665 665 666 let tries = 0; 666 667 while (!existingTrack && tries < 30) { 667 - console.log(`Song not found, trying again: ${chalk.magenta(tries + 1)}`); 668 + consola.info(`Song not found, trying again: ${chalk.magenta(tries + 1)}`); 668 669 existingTrack = await ctx.db 669 670 .select() 670 671 .from(tracks) ··· 685 686 } 686 687 687 688 if (tries === 30 && !existingTrack) { 688 - console.log(`Song not found after ${chalk.magenta("30 tries")}`); 689 + consola.info(`Song not found after ${chalk.magenta("30 tries")}`); 689 690 } 690 691 691 692 if (existingTrack) { 692 - console.log( 693 + consola.info( 693 694 `Song found: ${chalk.cyan(existingTrack.id)} - ${track.title}, after ${chalk.magenta(tries)} tries`, 694 695 ); 695 696 } ··· 768 769 .then((rows) => rows[0]); 769 770 770 771 while (!existingTrack?.artistUri && !existingTrack?.albumUri && tries < 30) { 771 - console.log( 772 + consola.info( 772 773 `Artist uri not ready, trying again: ${chalk.magenta(tries + 1)}`, 773 774 ); 774 775 existingTrack = await ctx.db ··· 847 848 } 848 849 849 850 if (tries === 30 && !existingTrack?.artistUri) { 850 - console.log(`Artist uri not ready after ${chalk.magenta("30 tries")}`); 851 + consola.info(`Artist uri not ready after ${chalk.magenta("30 tries")}`); 851 852 } 852 853 853 854 if (existingTrack?.artistUri) { 854 - console.log( 855 + consola.info( 855 856 `Artist uri ready: ${chalk.cyan(existingTrack.id)} - ${track.title}, after ${chalk.magenta(tries)} tries`, 856 857 ); 857 858 } ··· 925 926 scrobble.track.artistUri && 926 927 scrobble.track.albumUri 927 928 ) { 928 - console.log("Scrobble found after ", chalk.magenta(tries + 1), " tries"); 929 + consola.info("Scrobble found after ", chalk.magenta(tries + 1), " tries"); 929 930 await publishScrobble(ctx, scrobble.scrobble.id); 930 - console.log("Scrobble published"); 931 + consola.info("Scrobble published"); 931 932 break; 932 933 } 933 934 tries += 1; 934 - console.log("Scrobble not found, trying again: ", chalk.magenta(tries)); 935 + consola.info("Scrobble not found, trying again: ", chalk.magenta(tries)); 935 936 await new Promise((resolve) => setTimeout(resolve, 1000)); 936 937 } 937 938 938 939 if (tries === 30 && !scrobble) { 939 - console.log(`Scrobble not found after ${chalk.magenta("30 tries")}`); 940 + consola.info(`Scrobble not found after ${chalk.magenta("30 tries")}`); 940 941 } 941 942 942 943 ctx.nc.publish("rocksky.user.scrobble.sync", Buffer.from(userDid));
+10 -9
apps/api/src/scripts/avatar.ts
··· 1 1 import chalk from "chalk"; 2 + import { consola } from "consola"; 2 3 import { ctx } from "context"; 3 4 import { eq, or } from "drizzle-orm"; 4 5 import _ from "lodash"; ··· 15 16 16 17 const serviceEndpoint = _.get(plc, "service.0.serviceEndpoint"); 17 18 if (!serviceEndpoint) { 18 - console.log(`Service endpoint not found for ${user.did}`); 19 + consola.info(`Service endpoint not found for ${user.did}`); 19 20 return; 20 21 } 21 22 ··· 33 34 .where(eq(users.did, user.did)) 34 35 .execute(); 35 36 } else { 36 - console.log(`Skipping avatar update for ${user.did}`); 37 + consola.info(`Skipping avatar update for ${user.did}`); 37 38 } 38 39 39 40 const [u] = await ctx.db ··· 54 55 xata_version: u.xataVersion, 55 56 }; 56 57 57 - console.log(userPayload); 58 + consola.info(userPayload); 58 59 ctx.nc.publish("rocksky.user", Buffer.from(JSON.stringify(userPayload))); 59 60 } 60 61 ··· 67 68 .limit(1) 68 69 .execute(); 69 70 if (!user) { 70 - console.log(`User ${did} not found`); 71 + consola.info(`User ${did} not found`); 71 72 continue; 72 73 } 73 74 ··· 77 78 let offset = 0; 78 79 let processedCount = 0; 79 80 80 - console.log("Processing all users..."); 81 + consola.info("Processing all users..."); 81 82 82 83 while (true) { 83 84 const batch = await ctx.db ··· 91 92 break; // No more users to process 92 93 } 93 94 94 - console.log( 95 + consola.info( 95 96 `Processing batch ${Math.floor(offset / BATCH_SIZE) + 1}, users ${offset + 1}-${offset + batch.length}`, 96 97 ); 97 98 ··· 100 101 await processUser(user); 101 102 processedCount++; 102 103 } catch (error) { 103 - console.error(`Error processing user ${user.did}:`, error); 104 + consola.error(`Error processing user ${user.did}:`, error); 104 105 } 105 106 } 106 107 ··· 110 111 await new Promise((resolve) => setTimeout(resolve, 100)); 111 112 } 112 113 113 - console.log(`Processed ${chalk.greenBright(processedCount)} users total`); 114 + consola.info(`Processed ${chalk.greenBright(processedCount)} users total`); 114 115 } 115 116 116 117 // Ensure all messages are flushed before exiting 117 118 await ctx.nc.flush(); 118 119 119 - console.log("Done"); 120 + consola.info("Done"); 120 121 121 122 process.exit(0);
+8 -7
apps/api/src/scripts/dedup.ts
··· 1 1 import chalk from "chalk"; 2 + import { consola } from "consola"; 2 3 import { ctx } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { createAgent } from "lib/agent"; ··· 7 8 const args = process.argv.slice(2); 8 9 9 10 if (args.length === 0) { 10 - console.error("Please provide user author identifier (handle or DID)."); 11 - console.log(`Usage: ${chalk.cyan("npm run feed -- <handle|did>")}`); 11 + consola.error("Please provide user author identifier (handle or DID)."); 12 + consola.info(`Usage: ${chalk.cyan("npm run feed -- <handle|did>")}`); 12 13 process.exit(1); 13 14 } 14 15 ··· 36 37 .where(eq(tables.scrobbles.uri, record.uri)) 37 38 .limit(1); 38 39 if (result.length === 0) { 39 - console.log(`${i} Deleting record:`); 40 - console.log(record); 40 + consola.info(`${i} Deleting record:`); 41 + consola.info(record); 41 42 const rkey = record.uri.split("/").pop(); 42 43 await agent.com.atproto.repo.deleteRecord({ 43 44 repo: agent.assertDid, ··· 46 47 }); 47 48 await new Promise((resolve) => setTimeout(resolve, 1000)); // rate limit 48 49 } else { 49 - console.log(chalk.greenBright(`${i} Keeping record:`)); 50 - console.log(record); 50 + consola.info(chalk.greenBright(`${i} Keeping record:`)); 51 + consola.info(record); 51 52 } 52 53 i += 1; 53 54 } 54 55 cursor = records.data.cursor; 55 56 } while (cursor); 56 57 57 - console.log(chalk.greenBright("Deduplication complete.")); 58 + consola.info(chalk.greenBright("Deduplication complete.")); 58 59 59 60 process.exit(0);
+3 -2
apps/api/src/scripts/exp.ts
··· 1 + import { consola } from "consola"; 1 2 import { ctx, db } from "context"; 2 3 import { refreshSessionsAboutToExpire, updateExpiresAt } from "db"; 3 4 import { env } from "lib/env"; 4 5 import cron from "node-cron"; 5 6 6 - console.log("DB Path:", env.DB_PATH); 7 + consola.info("DB Path:", env.DB_PATH); 7 8 8 9 await updateExpiresAt(db); 9 10 ··· 11 12 12 13 // run every 1 minute 13 14 cron.schedule("* * * * *", async () => { 14 - console.log("Running session refresh job..."); 15 + consola.info("Running session refresh job..."); 15 16 await refreshSessionsAboutToExpire(db, ctx); 16 17 });
+16 -15
apps/api/src/scripts/feed.ts
··· 1 1 import chalk from "chalk"; 2 + import { consola } from "consola"; 2 3 import { ctx } from "context"; 3 4 import type * as FeedGenerator from "lexicon/types/app/rocksky/feed/generator"; 4 5 import { createAgent } from "lib/agent"; ··· 7 8 const args = process.argv.slice(2); 8 9 9 10 if (args.length === 0) { 10 - console.error("Please provide user author identifier (handle or DID)."); 11 + consola.error("Please provide user author identifier (handle or DID)."); 11 12 console.log(`Usage: ${chalk.cyan("npm run feed -- <handle|did>")}`); 12 13 process.exit(1); 13 14 } ··· 19 20 }); 20 21 21 22 if (name.value.length < 3 || name.value.length > 240) { 22 - console.error("Feed name must be between 3 and 240 characters."); 23 + consola.error("Feed name must be between 3 and 240 characters."); 23 24 process.exit(1); 24 25 } 25 26 ··· 30 31 }); 31 32 32 33 if (description.value.length > 3000) { 33 - console.error("Description is too long. Maximum length is 3000 characters."); 34 + consola.error("Description is too long. Maximum length is 3000 characters."); 34 35 process.exit(1); 35 36 } 36 37 ··· 41 42 }); 42 43 43 44 if (!/^did:web:[a-zA-Z0-9_.-]{3,30}$/.test(did.value)) { 44 - console.error( 45 + consola.error( 45 46 "Invalid DID format. It should start with 'did:web:' followed by 3 to 30 alphanumeric characters, underscores, hyphens, or periods.", 46 47 ); 47 48 process.exit(1); ··· 54 55 }); 55 56 56 57 if (!/^[a-zA-Z0-9_-]{3,30}$/.test(rkey.value)) { 57 - console.error( 58 + consola.error( 58 59 "Invalid record key. Only alphanumeric characters, underscores, and hyphens are allowed. Length must be between 3 and 30 characters.", 59 60 ); 60 61 process.exit(1); 61 62 } 62 63 63 - console.log("Creating feed with the following details:"); 64 - 65 - console.log("Feed name:", name.value); 66 - console.log("Description:", description.value); 67 - console.log("DID:", did.value); 68 - console.log("Record key (rkey):", rkey.value); 64 + consola.info("Creating feed with the following details:"); 65 + consola.info("---"); 66 + consola.info("Feed name:", name.value); 67 + consola.info("Description:", description.value); 68 + consola.info("DID:", did.value); 69 + consola.info("Record key (rkey):", rkey.value); 69 70 70 71 const confirm = await prompts({ 71 72 type: "confirm", ··· 75 76 }); 76 77 77 78 if (!confirm.value) { 78 - console.log("Feed creation cancelled."); 79 + consola.info("Feed creation cancelled."); 79 80 process.exit(0); 80 81 } 81 82 ··· 87 88 88 89 const agent = await createAgent(ctx.oauthClient, userDid); 89 90 90 - console.log( 91 + consola.info( 91 92 `Writing ${chalk.greenBright("app.rocksky.feed.generator")} record...`, 92 93 ); 93 94 ··· 106 107 rkey: rkey.value, 107 108 }); 108 109 109 - console.log(chalk.greenBright("Feed created successfully!")); 110 - console.log(`Record created at: ${chalk.cyan(res.data.uri)}`); 110 + consola.info(chalk.greenBright("Feed created successfully!")); 111 + consola.info(`Record created at: ${chalk.cyan(res.data.uri)}`); 111 112 112 113 process.exit(0);
+4 -3
apps/api/src/scripts/genres.ts
··· 1 + import { consola } from "consola"; 1 2 import { ctx } from "context"; 2 3 import { eq, isNull } from "drizzle-orm"; 3 4 import { decrypt } from "lib/crypto"; ··· 78 79 .then(async (data) => _.get(data, "artists.items.0")); 79 80 80 81 if (result) { 81 - console.log(JSON.stringify(result, null, 2), "\n"); 82 + consola.info(JSON.stringify(result, null, 2), "\n"); 82 83 if (result.genres && result.genres.length > 0) { 83 84 await ctx.db 84 85 .update(tables.artists) ··· 97 98 } 98 99 break; // exit the retry loop on success 99 100 } catch (error) { 100 - console.error("Error fetching genres for artist:", artist.name, error); 101 + consola.error("Error fetching genres for artist:", artist.name, error); 101 102 // wait for a while before retrying 102 103 await new Promise((resolve) => setTimeout(resolve, 1000)); 103 104 } ··· 130 131 await getGenresAndPicture(artists); 131 132 } 132 133 133 - console.log(`Artists without genres: ${count}`); 134 + consola.info(`Artists without genres: ${count}`); 134 135 135 136 process.exit(0);
+4 -3
apps/api/src/scripts/likes.ts
··· 1 + import chalk from "chalk"; 2 + import { consola } from "consola"; 1 3 import { ctx } from "context"; 2 4 import lovedTracks from "../schema/loved-tracks"; 3 - import chalk from "chalk"; 4 5 5 6 const likes = await ctx.db.select().from(lovedTracks).execute(); 6 7 ··· 14 15 xata_updatedat: like.createdAt.toISOString(), 15 16 xata_version: 0, 16 17 }); 17 - console.log("Publishing like:", chalk.cyanBright(like.uri)); 18 + consola.info("Publishing like:", chalk.cyanBright(like.uri)); 18 19 ctx.nc.publish("rocksky.like", Buffer.from(message)); 19 20 } 20 21 21 22 await ctx.nc.flush(); 22 23 23 - console.log("Done"); 24 + consola.info("Done"); 24 25 25 26 process.exit(0);
+8 -7
apps/api/src/scripts/meili.ts
··· 1 1 import chalk from "chalk"; 2 + import { consola } from "consola"; 2 3 import { ctx } from "context"; 3 4 import { count } from "drizzle-orm"; 4 5 import tables from "schema"; 5 6 6 7 async function main() { 7 - console.log(chalk.cyan("Starting Meilisearch sync...")); 8 + consola.info(chalk.cyan("Starting Meilisearch sync...")); 8 9 9 10 try { 10 11 await Promise.all([ ··· 13 14 createTracks(), 14 15 createUsers(), 15 16 ]); 16 - console.log(chalk.green("Meilisearch sync completed successfully.")); 17 + consola.info(chalk.green("Meilisearch sync completed successfully.")); 17 18 } catch (error) { 18 - console.error(chalk.red("Error during Meilisearch sync:"), error); 19 + consola.error(chalk.red("Error during Meilisearch sync:"), error); 19 20 } 20 21 } 21 22 ··· 31 32 .then(([row]) => row.value); 32 33 for (let i = 0; i < total; i += size) { 33 34 const skip = i; 34 - console.log( 35 + consola.info( 35 36 `Processing ${chalk.magentaBright("albums")}: ${chalk.magentaBright(skip)} to ${chalk.magentaBright(skip + size)}`, 36 37 ); 37 38 const results = await ctx.db ··· 55 56 .then(([row]) => row.value); 56 57 for (let i = 0; i < total; i += size) { 57 58 const skip = i; 58 - console.log( 59 + consola.info( 59 60 `Processing ${chalk.magentaBright("artists")}: ${chalk.magentaBright(skip)} to ${chalk.magentaBright(skip + size)}`, 60 61 ); 61 62 const results = await ctx.db ··· 79 80 .then(([row]) => row.value); 80 81 for (let i = 0; i < total; i += size) { 81 82 const skip = i; 82 - console.log( 83 + consola.info( 83 84 `Processing ${chalk.magentaBright("tracks")}: ${chalk.magentaBright(skip)} to ${chalk.magentaBright(skip + size)}`, 84 85 ); 85 86 const results = await ctx.db ··· 104 105 105 106 for (let i = 0; i < total; i += size) { 106 107 const skip = i; 107 - console.log( 108 + consola.info( 108 109 `Processing ${chalk.magentaBright("users")}: ${chalk.magentaBright(skip)} to ${chalk.magentaBright(skip + size)}`, 109 110 ); 110 111 const results = await ctx.db
+5 -4
apps/api/src/scripts/seed-feed.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 2 import chalk from "chalk"; 3 + import { consola } from "consola"; 3 4 import { ctx } from "context"; 5 + import { eq } from "drizzle-orm"; 4 6 import { createAgent } from "lib/agent"; 5 7 import * as FeedGenerator from "lexicon/types/app/rocksky/feed/generator"; 6 8 import tables from "schema"; 7 9 import type { InsertFeed } from "schema/feeds"; 8 - import { eq } from "drizzle-orm"; 9 10 10 11 const args = process.argv.slice(2); 11 12 12 13 if (args.length === 0) { 13 - console.error("Please provide user author identifier (handle or DID)."); 14 - console.log(`Usage: ${chalk.cyan("npm run seed:feed -- <handle|did>")}`); 14 + consola.error("Please provide user author identifier (handle or DID)."); 15 + consola.info(`Usage: ${chalk.cyan("npm run seed:feed -- <handle|did>")}`); 15 16 process.exit(1); 16 17 } 17 18 ··· 57 58 } satisfies InsertFeed) 58 59 .onConflictDoNothing() 59 60 .execute(); 60 - console.log( 61 + consola.info( 61 62 `Feed ${chalk.cyanBright(feed.value.displayName)} seeded successfully.`, 62 63 ); 63 64 }
+3 -2
apps/api/src/scripts/spotify.ts
··· 1 1 import chalk from "chalk"; 2 + import { consola } from "consola"; 2 3 import { ctx } from "context"; 3 4 import { encrypt } from "lib/crypto"; 4 5 import { env } from "lib/env"; ··· 9 10 const clientSecret = args[1]; 10 11 11 12 if (!clientId || !clientSecret) { 12 - console.error( 13 + consola.error( 13 14 "Please provide Spotify Client ID and Client Secret as command line arguments", 14 15 ); 15 - console.log( 16 + consola.info( 16 17 chalk.greenBright("Usage: ts-node spotify.ts <client_id> <client_secret>"), 17 18 ); 18 19 process.exit(1);
+14 -11
apps/api/src/scripts/sync-library.ts
··· 1 1 import chalk from "chalk"; 2 + import { consola } from "consola"; 2 3 import { ctx } from "context"; 3 4 import { and, count, eq } from "drizzle-orm"; 4 5 import tables from "schema"; ··· 11 12 .execute() 12 13 .then(([row]) => row.value); 13 14 14 - console.log(`Total tracks to process: ${chalk.magentaBright(total)}`); 15 + consola.info(`Total tracks to process: ${chalk.magentaBright(total)}`); 15 16 16 17 for (let i = 0; i < total; i += size) { 17 18 const skip = i; 18 - console.log( 19 + consola.info( 19 20 `Processing ${chalk.magentaBright("tracks")}: ${chalk.magentaBright(skip)} to ${chalk.magentaBright(skip + size)}`, 20 21 ); 21 22 const results = await ctx.db ··· 27 28 28 29 for (const track of results) { 29 30 if (!track.artistUri || !track.albumUri) { 30 - console.log( 31 - `Skipping track ${chalk.cyan(track.title)} due to missing artist or album URI`, 31 + consola.info( 32 + `Deleting album-track relationship for track: ${chalk.redBright(track.uri)}`, 32 33 ); 33 - console.log("artistUri", track.artistUri); 34 - console.log("albumUri", track.albumUri); 34 + consola.info("artistUri", track.artistUri); 35 + consola.info("albumUri", track.albumUri); 35 36 continue; 36 37 } 37 38 ··· 57 58 .then((rows) => rows.length > 0); 58 59 59 60 if (!found) { 60 - console.log(`Creating artist-album relationship for track: ${track.uri}`); 61 + consola.info( 62 + `Creating artist-album relationship for track: ${track.uri}`, 63 + ); 61 64 const [artist, album] = await Promise.all([ 62 65 ctx.db 63 66 .select() ··· 76 79 ]); 77 80 78 81 if (!artist || !album) { 79 - console.error( 80 - `Artist or album not found for track: ${track.uri}. Skipping...`, 82 + consola.error( 83 + `Artist-album relationship already exists for track: ${chalk.redBright(track.uri)}`, 81 84 ); 82 - console.log("artist", artist); 83 - console.log("album", album); 85 + consola.info("artist", artist); 86 + consola.info("album", album); 84 87 continue; 85 88 } 86 89
+12 -11
apps/api/src/scripts/sync.ts
··· 1 1 import chalk from "chalk"; 2 + import { consola } from "consola"; 2 3 import { ctx } from "context"; 3 4 import { desc, eq, or } from "drizzle-orm"; 4 5 import { createHash } from "node:crypto"; ··· 38 39 .then((rows) => rows[0]); 39 40 40 41 if (existingTrack && !existingTrack.albumUri) { 41 - console.log(`Updating album uri for ${chalk.cyan(track.id)} ...`); 42 + consola.info(`Updating album uri for ${chalk.cyan(track.id)} ...`); 42 43 43 44 const albumHash = createHash("sha256") 44 45 .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) ··· 60 61 } 61 62 62 63 if (existingTrack && !existingTrack.artistUri) { 63 - console.log(`Updating artist uri for ${chalk.cyan(track.id)} ...`); 64 + consola.info(`Updating artist uri for ${chalk.cyan(track.id)} ...`); 64 65 65 66 const artistHash = createHash("sha256") 66 67 .update(track.albumArtist.toLowerCase()) ··· 93 94 .then((rows) => rows[0]); 94 95 95 96 if (existingTrack && album && !album.artistUri) { 96 - console.log(`Updating artist uri for ${chalk.cyan(album.id)} ...`); 97 + consola.info(`Updating artist uri for ${chalk.cyan(album.id)} ...`); 97 98 98 99 const artistHash = createHash("sha256") 99 100 .update(track.albumArtist.toLowerCase()) ··· 117 118 } 118 119 119 120 if (args.includes("--background")) { 120 - console.log("Wait for new scrobbles to sync ..."); 121 + consola.info("Wait for new scrobbles to sync ..."); 121 122 const sub = ctx.nc.subscribe("rocksky.user.scrobble.sync"); 122 123 for await (const m of sub) { 123 124 const did = new TextDecoder().decode(m.data); 124 125 // wait for 15 seconds to ensure the scrobble is fully created 125 126 await new Promise((resolve) => setTimeout(resolve, 15000)); 126 - console.log(`Syncing scrobbles ${chalk.magenta(did)} ...`); 127 + consola.info(`Syncing scrobbles ${chalk.magenta(did)} ...`); 127 128 await updateUris(did); 128 129 129 130 const records = await ctx.db ··· 137 138 .limit(5); 138 139 139 140 for (const { scrobble } of records) { 140 - console.log(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`); 141 + consola.info(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`); 141 142 try { 142 143 await publishScrobble(ctx, scrobble.id); 143 144 } catch (err) { 144 - console.error( 145 + consola.error( 145 146 `Failed to sync scrobble ${chalk.cyan(scrobble.id)}:`, 146 147 err, 147 148 ); ··· 152 153 } 153 154 154 155 for (const arg of args) { 155 - console.log(`Syncing scrobbles ${chalk.magenta(arg)} ...`); 156 + consola.info(`Syncing scrobbles ${chalk.magenta(arg)} ...`); 156 157 await updateUris(arg); 157 158 158 159 const records = await ctx.db ··· 166 167 .limit(process.env.SYNC_SIZE ? parseInt(process.env.SYNC_SIZE, 10) : 20); 167 168 168 169 for (const { scrobble } of records) { 169 - console.log(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`); 170 + consola.info(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`); 170 171 try { 171 172 await publishScrobble(ctx, scrobble.id); 172 173 } catch (err) { 173 - console.error(`Failed to sync scrobble ${chalk.cyan(scrobble.id)}:`, err); 174 + consola.error(`Failed to sync scrobble ${chalk.cyan(scrobble.id)}:`, err); 174 175 } 175 176 } 176 - console.log(`Synced ${chalk.greenBright(records.length)} scrobbles`); 177 + consola.info(`Synced ${chalk.greenBright(records.length)} scrobbles`); 177 178 } 178 179 179 180 process.exit(0);
+2 -1
apps/api/src/server.ts
··· 1 + import { consola } from "consola"; 1 2 import { ctx } from "context"; 2 3 import cors from "cors"; 3 4 import type { Request, Response } from "express"; ··· 30 31 app.use(proxyMiddleware); 31 32 32 33 app.listen(process.env.ROCKSKY_XPRC_PORT || 3004, () => { 33 - console.log( 34 + consola.info( 34 35 `Rocksky XRPC API is running on port ${process.env.ROCKSKY_XRPC_PORT || 3004}`, 35 36 ); 36 37 });
+11 -10
apps/api/src/shouts/shouts.service.ts
··· 1 1 import { type Agent, AtpAgent } from "@atproto/api"; 2 + import { consola } from "consola"; 2 3 import { TID } from "@atproto/common"; 3 4 import type { Context } from "context"; 4 5 import { and, eq } from "drizzle-orm"; ··· 98 99 cid: subjectRecord.data.cid, 99 100 }); 100 101 if (!subjectRef.success) { 101 - console.log(subjectRef); 102 + consola.info(subjectRef); 102 103 throw new Error("Invalid ref"); 103 104 } 104 105 ··· 111 112 }; 112 113 113 114 if (!ShoutLexicon.validateRecord(record).success) { 114 - console.log(ShoutLexicon.validateRecord(record)); 115 + consola.info(ShoutLexicon.validateRecord(record)); 115 116 throw new Error("[shout] invalid record"); 116 117 } 117 118 ··· 125 126 }); 126 127 const uri = res.data.uri; 127 128 128 - console.log(`Shout record created at: ${uri}`); 129 + consola.info(`Shout record created at: ${uri}`); 129 130 130 131 const createdShout = await ctx.db 131 132 .insert(shouts) ··· 148 149 }); 149 150 } 150 151 } catch (e) { 151 - console.error(`Error creating shout record: ${e.message}`); 152 + consola.error(`Error creating shout record: ${e.message}`); 152 153 } 153 154 } 154 155 ··· 269 270 }; 270 271 271 272 if (!ShoutLexicon.validateRecord(record).success) { 272 - console.log(ShoutLexicon.validateRecord(record)); 273 + consola.info(ShoutLexicon.validateRecord(record)); 273 274 throw new Error("Invalid record"); 274 275 } 275 276 ··· 283 284 }); 284 285 const uri = res.data.uri; 285 286 286 - console.log(`Reply record created at: ${uri}`); 287 + consola.info(`Reply record created at: ${uri}`); 287 288 288 289 const createdShout = await ctx.db 289 290 .insert(shouts) ··· 314 315 }); 315 316 } 316 317 } catch (e) { 317 - console.error(`Error creating reply record: ${e.message}`); 318 + consola.error(`Error creating reply record: ${e.message}`); 318 319 } 319 320 } 320 321 ··· 370 371 }; 371 372 372 373 if (!LikeLexicon.validateRecord(record).success) { 373 - console.log(LikeLexicon.validateRecord(record)); 374 + consola.info(LikeLexicon.validateRecord(record)); 374 375 throw new Error("Invalid record"); 375 376 } 376 377 ··· 383 384 validate: false, 384 385 }); 385 386 const uri = res.data.uri; 386 - console.log(`Like record created at: ${uri}`); 387 + consola.info(`Like record created at: ${uri}`); 387 388 388 389 const shout = await ctx.db 389 390 .select() ··· 402 403 uri, 403 404 }); 404 405 } catch (e) { 405 - console.error(`Error creating like record: ${e.message}`); 406 + consola.error(`Error creating like record: ${e.message}`); 406 407 } 407 408 } 408 409
+2 -1
apps/api/src/spotify/app.ts
··· 1 + import { consola } from "consola"; 1 2 import { ctx } from "context"; 2 3 import { and, eq, or, sql } from "drizzle-orm"; 3 4 import { Hono } from "hono"; ··· 249 250 }); 250 251 } catch (e) { 251 252 if (!e.message.includes("duplicate key value violates unique constraint")) { 252 - console.error(e.message); 253 + consola.error(e.message); 253 254 } else { 254 255 throw e; 255 256 }
+2
apps/api/src/subscribers/index.ts
··· 2 2 import { onNewPlaylist } from "./playlist"; 3 3 import { onNewTrack } from "./track"; 4 4 import { onNewUser } from "./user"; 5 + import { onNewScrobble } from "./scrobble"; 5 6 6 7 export default function subscribe(ctx: Context) { 7 8 onNewPlaylist(ctx); 8 9 onNewTrack(ctx); 9 10 onNewUser(ctx); 11 + onNewScrobble(ctx); 10 12 }
+6 -5
apps/api/src/subscribers/playlist.ts
··· 1 1 import { TID } from "@atproto/common"; 2 + import { consola } from "consola"; 2 3 import chalk from "chalk"; 3 4 import type { Context } from "context"; 4 5 import { eq } from "drizzle-orm"; ··· 16 17 id: string; 17 18 did: string; 18 19 } = JSON.parse(sc.decode(m.data)); 19 - console.log( 20 + consola.info( 20 21 `New playlist: ${chalk.cyan(payload.did)} - ${chalk.greenBright(payload.id)}`, 21 22 ); 22 23 await putPlaylistRecord(ctx, payload); ··· 31 32 const agent = await createAgent(ctx.oauthClient, payload.did); 32 33 33 34 if (!agent) { 34 - console.error( 35 + consola.error( 35 36 `Failed to create agent, skipping playlist: ${chalk.cyan(payload.id)} for ${chalk.greenBright(payload.did)}`, 36 37 ); 37 38 return; ··· 69 70 }; 70 71 71 72 if (!Playlist.validateRecord(record)) { 72 - console.error(`Invalid record: ${chalk.redBright(JSON.stringify(record))}`); 73 + consola.error(`Invalid record: ${chalk.redBright(JSON.stringify(record))}`); 73 74 return; 74 75 } 75 76 ··· 82 83 validate: false, 83 84 }); 84 85 const uri = res.data.uri; 85 - console.log(`Playlist record created: ${chalk.greenBright(uri)}`); 86 + consola.info(`Playlist record created: ${chalk.greenBright(uri)}`); 86 87 await ctx.db 87 88 .update(tables.playlists) 88 89 .set({ uri }) 89 90 .where(eq(tables.playlists.id, payload.id)) 90 91 .execute(); 91 92 } catch (e) { 92 - console.error(`Failed to put record: ${chalk.redBright(e.message)}`); 93 + consola.error(`Failed to put record: ${chalk.redBright(e.message)}`); 93 94 } 94 95 95 96 const [updatedPlaylist] = await ctx.db
+209
apps/api/src/subscribers/scrobble.ts
··· 1 + import { consola } from "consola"; 2 + import type { Context } from "context"; 3 + import { eq } from "drizzle-orm"; 4 + import _ from "lodash"; 5 + import { StringCodec } from "nats"; 6 + import tables from "schema"; 7 + 8 + export function onNewScrobble(ctx: Context) { 9 + const sc = StringCodec(); 10 + const sub = ctx.nc.subscribe("rocksky.scrobble.new"); 11 + (async () => { 12 + for await (const m of sub) { 13 + const scrobbleId = sc.decode(m.data); 14 + const result = await ctx.db 15 + .select() 16 + .from(tables.scrobbles) 17 + .where(eq(tables.scrobbles.id, scrobbleId)) 18 + .execute() 19 + .then((rows) => rows[0]); 20 + 21 + if (!result) { 22 + consola.info(`Scrobble with ID ${scrobbleId} not found, skipping`); 23 + } 24 + } 25 + })(); 26 + } 27 + 28 + /* 29 + import chalk from "chalk"; 30 + import { ctx } from "context"; 31 + import { desc, eq, or } from "drizzle-orm"; 32 + import { createHash } from "node:crypto"; 33 + import { publishScrobble } from "nowplaying/nowplaying.service"; 34 + import albums from "../schema/albums"; 35 + import artists from "../schema/artists"; 36 + import scrobbles from "../schema/scrobbles"; 37 + import tracks from "../schema/tracks"; 38 + import users from "../schema/users"; 39 + 40 + const args = process.argv.slice(2); 41 + 42 + async function updateUris(did: string) { 43 + // Get scrobbles with track and user data 44 + const records = await ctx.db 45 + .select({ 46 + track: tracks, 47 + user: users, 48 + }) 49 + .from(scrobbles) 50 + .innerJoin(tracks, eq(scrobbles.trackId, tracks.id)) 51 + .innerJoin(users, eq(scrobbles.userId, users.id)) 52 + .where(or(eq(users.did, did), eq(users.handle, did))) 53 + .orderBy(desc(scrobbles.createdAt)) 54 + .limit(process.env.SYNC_SIZE ? parseInt(process.env.SYNC_SIZE, 10) : 20); 55 + 56 + for (const { track } of records) { 57 + const trackHash = createHash("sha256") 58 + .update(`${track.title} - ${track.artist} - ${track.album}`.toLowerCase()) 59 + .digest("hex"); 60 + 61 + const existingTrack = await ctx.db 62 + .select() 63 + .from(tracks) 64 + .where(eq(tracks.sha256, trackHash)) 65 + .limit(1) 66 + .then((rows) => rows[0]); 67 + 68 + if (existingTrack && !existingTrack.albumUri) { 69 + consola.info(`Updating album uri for ${chalk.cyan(track.id)} ...`); 70 + 71 + const albumHash = createHash("sha256") 72 + .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 73 + .digest("hex"); 74 + 75 + const album = await ctx.db 76 + .select() 77 + .from(albums) 78 + .where(eq(albums.sha256, albumHash)) 79 + .limit(1) 80 + .then((rows) => rows[0]); 81 + 82 + if (album) { 83 + await ctx.db 84 + .update(tracks) 85 + .set({ albumUri: album.uri }) 86 + .where(eq(tracks.id, existingTrack.id)); 87 + } 88 + } 89 + 90 + if (existingTrack && !existingTrack.artistUri) { 91 + consola.info(`Updating artist uri for ${chalk.cyan(track.id)} ...`); 92 + 93 + const artistHash = createHash("sha256") 94 + .update(track.albumArtist.toLowerCase()) 95 + .digest("hex"); 96 + 97 + const artist = await ctx.db 98 + .select() 99 + .from(artists) 100 + .where(eq(artists.sha256, artistHash)) 101 + .limit(1) 102 + .then((rows) => rows[0]); 103 + 104 + if (artist) { 105 + await ctx.db 106 + .update(tracks) 107 + .set({ artistUri: artist.uri }) 108 + .where(eq(tracks.id, existingTrack.id)); 109 + } 110 + } 111 + 112 + const albumHash = createHash("sha256") 113 + .update(`${track.album} - ${track.albumArtist}`.toLowerCase()) 114 + .digest("hex"); 115 + 116 + const album = await ctx.db 117 + .select() 118 + .from(albums) 119 + .where(eq(albums.sha256, albumHash)) 120 + .limit(1) 121 + .then((rows) => rows[0]); 122 + 123 + if (existingTrack && album && !album.artistUri) { 124 + consola.info(`Updating artist uri for ${chalk.cyan(album.id)} ...`); 125 + 126 + const artistHash = createHash("sha256") 127 + .update(track.albumArtist.toLowerCase()) 128 + .digest("hex"); 129 + 130 + const artist = await ctx.db 131 + .select() 132 + .from(artists) 133 + .where(eq(artists.sha256, artistHash)) 134 + .limit(1) 135 + .then((rows) => rows[0]); 136 + 137 + if (artist) { 138 + await ctx.db 139 + .update(albums) 140 + .set({ artistUri: artist.uri }) 141 + .where(eq(albums.id, album.id)); 142 + } 143 + } 144 + } 145 + } 146 + 147 + if (args.includes("--background")) { 148 + consola.info("Wait for new scrobbles to sync ..."); 149 + const sub = ctx.nc.subscribe("rocksky.user.scrobble.sync"); 150 + for await (const m of sub) { 151 + const did = new TextDecoder().decode(m.data); 152 + // wait for 15 seconds to ensure the scrobble is fully created 153 + await new Promise((resolve) => setTimeout(resolve, 15000)); 154 + consola.info(`Syncing scrobbles ${chalk.magenta(did)} ...`); 155 + await updateUris(did); 156 + 157 + const records = await ctx.db 158 + .select({ 159 + scrobble: scrobbles, 160 + }) 161 + .from(scrobbles) 162 + .innerJoin(users, eq(scrobbles.userId, users.id)) 163 + .where(or(eq(users.did, did), eq(users.handle, did))) 164 + .orderBy(desc(scrobbles.createdAt)) 165 + .limit(5); 166 + 167 + for (const { scrobble } of records) { 168 + consola.info(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`); 169 + try { 170 + await publishScrobble(ctx, scrobble.id); 171 + } catch (err) { 172 + consola.error( 173 + `Failed to sync scrobble ${chalk.cyan(scrobble.id)}:`, 174 + err, 175 + ); 176 + } 177 + } 178 + } 179 + process.exit(0); 180 + } 181 + 182 + for (const arg of args) { 183 + consola.info(`Syncing scrobbles ${chalk.magenta(arg)} ...`); 184 + await updateUris(arg); 185 + 186 + const records = await ctx.db 187 + .select({ 188 + scrobble: scrobbles, 189 + }) 190 + .from(scrobbles) 191 + .innerJoin(users, eq(scrobbles.userId, users.id)) 192 + .where(or(eq(users.did, arg), eq(users.handle, arg))) 193 + .orderBy(desc(scrobbles.createdAt)) 194 + .limit(process.env.SYNC_SIZE ? parseInt(process.env.SYNC_SIZE, 10) : 20); 195 + 196 + for (const { scrobble } of records) { 197 + consola.info(`Syncing scrobble ${chalk.cyan(scrobble.id)} ...`); 198 + try { 199 + await publishScrobble(ctx, scrobble.id); 200 + } catch (err) { 201 + consola.error(`Failed to sync scrobble ${chalk.cyan(scrobble.id)}:`, err); 202 + } 203 + } 204 + consola.info(`Synced ${chalk.greenBright(records.length)} scrobbles`); 205 + } 206 + 207 + process.exit(0); 208 + 209 + */
+2 -1
apps/api/src/subscribers/track.ts
··· 1 1 import chalk from "chalk"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import _ from "lodash"; ··· 36 37 .execute(), 37 38 ]); 38 39 39 - console.log(`New track: ${chalk.cyan(_.get(tracks, "0.title"))}`); 40 + consola.info(`New track: ${chalk.cyan(_.get(tracks, "0.title"))}`); 40 41 41 42 await Promise.all([ 42 43 ctx.meilisearch.post(`indexes/albums/documents?primaryKey=id`, albums),
+2 -1
apps/api/src/subscribers/user.ts
··· 1 1 import chalk from "chalk"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import _ from "lodash"; ··· 19 20 .where(eq(tables.users.id, payload.xata_id)) 20 21 .execute(); 21 22 22 - console.log(`New user: ${chalk.cyan(_.get(results, "0.handle"))}`); 23 + consola.info(`New user: ${chalk.cyan(_.get(results, "0.handle"))}`); 23 24 24 25 await ctx.meilisearch.post( 25 26 `/indexes/users/documents?primaryKey=id`,
+10 -7
apps/api/src/tealfm/index.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 2 import { TID } from "@atproto/common"; 3 3 import chalk from "chalk"; 4 + import { consola } from "consola"; 4 5 import type * as Status from "lexicon/types/fm/teal/alpha/actor/status"; 5 6 import type { PlayView } from "lexicon/types/fm/teal/alpha/feed/defs"; 6 7 import * as Play from "lexicon/types/fm/teal/alpha/feed/play"; ··· 24 25 duration: number, 25 26 ) { 26 27 if (env.DISABLED_TEALFM.includes(agent.assertDid)) { 27 - console.log(`teal.fm is disabled for ${chalk.cyanBright(agent.assertDid)}`); 28 + consola.info( 29 + `teal.fm is disabled for ${chalk.cyanBright(agent.assertDid)}`, 30 + ); 28 31 return; 29 32 } 30 33 ··· 47 50 ); 48 51 }); 49 52 if (alreadyPlayed) { 50 - console.log( 53 + consola.info( 51 54 `Track ${chalk.cyan(track.name)} by ${chalk.cyan( 52 55 track.artist.map((a) => a.name).join(", "), 53 56 )} already played recently. Skipping...`, ··· 72 75 }; 73 76 74 77 if (!Play.validateRecord(record).success) { 75 - console.log(Play.validateRecord(record)); 76 - console.log(chalk.cyan(JSON.stringify(record, null, 2))); 78 + consola.info(Play.validateRecord(record)); 79 + consola.info(chalk.cyan(JSON.stringify(record, null, 2))); 77 80 throw new Error("Invalid record"); 78 81 } 79 82 ··· 85 88 validate: false, 86 89 }); 87 90 const uri = res.data.uri; 88 - console.log(`tealfm Play record created at ${uri}`); 91 + consola.info(`tealfm Play record created at ${uri}`); 89 92 90 93 await publishStatus(agent, track, duration); 91 94 } catch (error) { 92 - console.error("Error publishing teal.fm record:", error); 95 + consola.error("Error publishing teal.fm record:", error); 93 96 } 94 97 } 95 98 ··· 127 130 record, 128 131 swapRecord, 129 132 }); 130 - console.log(`tealfm Status record published at ${res.data.uri}`); 133 + consola.info(`tealfm Status record published at ${res.data.uri}`); 131 134 } 132 135 133 136 async function getStatusSwapRecord(agent: Agent): Promise<string | undefined> {
+15 -14
apps/api/src/tracks/tracks.service.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { and, eq } from "drizzle-orm"; 4 5 import { deepSnakeCaseKeys } from "lib"; ··· 130 131 .then((results) => results[0]); 131 132 132 133 if (!track_id || !album_id || !artist_id) { 133 - console.log( 134 + consola.info( 134 135 "Track not yet saved (uri not saved), retrying...", 135 136 tries + 1, 136 137 ); ··· 218 219 track_id.albumUri && 219 220 track_id.artistUri 220 221 ) { 221 - console.log("Track saved successfully after", tries + 1, "tries"); 222 + consola.info("Track saved successfully after", tries + 1, "tries"); 222 223 223 224 const message = JSON.stringify( 224 225 deepSnakeCaseKeys({ ··· 275 276 } 276 277 277 278 tries += 1; 278 - console.log("Track not yet saved, retrying...", tries + 1); 279 + consola.info("Track not yet saved, retrying...", tries + 1); 279 280 if (tries === 15) { 280 - console.log(">>>"); 281 - console.log(album_track); 282 - console.log(artist_track); 283 - console.log(artist_album); 284 - console.log(artist_id); 285 - console.log(album_id); 286 - console.log(track_id); 287 - console.log(track_id.albumUri); 288 - console.log(track_id.artistUri); 289 - console.log("<<<"); 281 + consola.info(">>>"); 282 + consola.info(album_track); 283 + consola.info(artist_track); 284 + consola.info(artist_album); 285 + consola.info(artist_id); 286 + consola.info(album_id); 287 + consola.info(track_id); 288 + consola.info(track_id.albumUri); 289 + consola.info(track_id.artistUri); 290 + consola.info("<<<"); 290 291 } 291 292 await new Promise((resolve) => setTimeout(resolve, 1000)); 292 293 } 293 294 294 295 if (tries === 15) { 295 - console.log("Failed to save track after 15 tries"); 296 + consola.info("Failed to save track after 15 tries"); 296 297 } 297 298 }
+12 -11
apps/api/src/websocket/handler.ts
··· 1 1 import chalk from "chalk"; 2 + import { consola } from "consola"; 2 3 import { ctx } from "context"; 3 4 import { and, eq } from "drizzle-orm"; 4 5 import type { Context } from "hono"; ··· 174 175 const { did } = jwt.verify(token, env.JWT_SECRET, { 175 176 ignoreExpiration: true, 176 177 }); 177 - console.log( 178 + consola.info( 178 179 `Control message: ${chalk.greenBright(type)}, ${chalk.greenBright(target)}, ${chalk.greenBright(action)}, ${chalk.greenBright(args)}, ${chalk.greenBright("***")}`, 179 180 ); 180 181 // Handle control message ··· 183 184 const targetDevice = devices[deviceId]; 184 185 if (targetDevice) { 185 186 targetDevice.send(JSON.stringify({ type, action, args })); 186 - console.log( 187 + consola.info( 187 188 `Control message sent to device: ${chalk.greenBright(deviceId)}, ${chalk.greenBright(target)}`, 188 189 ); 189 190 return; 190 191 } 191 - console.error(`Device not found: ${target}`); 192 + consola.error(`Device not found: ${target}`); 192 193 return; 193 194 } 194 195 userDevices[did]?.forEach((id) => { 195 196 const targetDevice = devices[id]; 196 197 if (targetDevice) { 197 198 targetDevice.send(JSON.stringify({ type, action, args })); 198 - console.log( 199 + consola.info( 199 200 `Control message sent to all devices: ${chalk.greenBright(id)}, ${chalk.greenBright(target)}`, 200 201 ); 201 202 } 202 203 }); 203 204 204 - console.error(`Device ID not found for target: ${target}`); 205 + consola.error(`Device ID not found for target: ${target}`); 205 206 return; 206 207 } 207 208 208 209 if (registerMessage.success) { 209 210 const { type, clientName, token } = registerMessage.data; 210 - console.log( 211 + consola.info( 211 212 `Register message: ${chalk.greenBright(type)}, ${chalk.greenBright(clientName)}, ${chalk.greenBright("****")}`, 212 213 ); 213 214 // Handle register Message ··· 220 221 devices[deviceId] = ws; 221 222 deviceNames[deviceId] = clientName; 222 223 userDevices[did] = [...(userDevices[did] || []), deviceId]; 223 - console.log( 224 + consola.info( 224 225 `Device registered: ${chalk.greenBright(deviceId)}, ${chalk.greenBright(clientName)}`, 225 226 ); 226 227 ··· 244 245 return; 245 246 } 246 247 } catch (e) { 247 - console.error("Error parsing message:", e); 248 + consola.error("Error parsing message:", e); 248 249 } 249 250 }, 250 251 onClose: (_, ws) => { 251 - console.log("Connection closed"); 252 + consola.info("Connection closed"); 252 253 // remove device from devices 253 254 const deviceId = ws.deviceId; 254 255 const did = ws.did; 255 256 if (deviceId && devices[deviceId]) { 256 257 delete devices[deviceId]; 257 - console.log(`Device removed: ${chalk.redBright(deviceId)}`); 258 + consola.info(`Device removed: ${chalk.redBright(deviceId)}`); 258 259 } 259 260 if (did && userDevices[did]) { 260 261 userDevices[did] = userDevices[did].filter((id) => id !== deviceId); ··· 265 266 if (deviceId && deviceNames[deviceId]) { 266 267 const clientName = deviceNames[deviceId]; 267 268 delete deviceNames[deviceId]; 268 - console.log( 269 + consola.info( 269 270 `Device name removed: ${chalk.redBright(deviceId)}, ${chalk.redBright(clientName)}`, 270 271 ); 271 272 }
+2 -1
apps/api/src/xrpc/app/rocksky/actor/getActorAlbums.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorAlbums"; ··· 14 15 Effect.retry({ times: 3 }), 15 16 Effect.timeout("120 seconds"), 16 17 Effect.catchAll((err) => { 17 - console.error(err); 18 + consola.error(err); 18 19 return Effect.succeed({ artists: [] }); 19 20 }), 20 21 );
+2 -1
apps/api/src/xrpc/app/rocksky/actor/getActorArtists.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorArtists"; ··· 14 15 Effect.retry({ times: 3 }), 15 16 Effect.timeout("120 seconds"), 16 17 Effect.catchAll((err) => { 17 - console.error(err); 18 + consola.error(err); 18 19 return Effect.succeed({ artists: [] }); 19 20 }), 20 21 );
+2 -1
apps/api/src/xrpc/app/rocksky/actor/getActorCompatibility.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorCompatibility"; ··· 17 18 Effect.retry({ times: 3 }), 18 19 Effect.timeout("120 seconds"), 19 20 Effect.catchAll((err) => { 20 - console.error(err); 21 + consola.error(err); 21 22 return Effect.succeed({ comptibility: null }); 22 23 }), 23 24 );
+2 -1
apps/api/src/xrpc/app/rocksky/actor/getActorLovedSongs.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { and, desc, eq, isNotNull, not, or } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 16 17 Effect.retry({ times: 3 }), 17 18 Effect.timeout("120 seconds"), 18 19 Effect.catchAll((err) => { 19 - console.error(err); 20 + consola.error(err); 20 21 return Effect.succeed({ tracks: [] }); 21 22 }), 22 23 );
+2 -1
apps/api/src/xrpc/app/rocksky/actor/getActorNeighbours.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorNeighbours"; ··· 16 17 Effect.retry({ times: 3 }), 17 18 Effect.timeout("120 seconds"), 18 19 Effect.catchAll((err) => { 19 - console.error(err); 20 + consola.error(err); 20 21 return Effect.succeed({ neighbours: [] }); 21 22 }), 22 23 );
+2 -1
apps/api/src/xrpc/app/rocksky/actor/getActorPlaylists.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { eq, or, sql } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 16 17 Effect.retry({ times: 3 }), 17 18 Effect.timeout("120 seconds"), 18 19 Effect.catchAll((err) => { 19 - console.error(err); 20 + consola.error(err); 20 21 return Effect.succeed({ playlists: [] }); 21 22 }), 22 23 );
+2 -1
apps/api/src/xrpc/app/rocksky/actor/getActorScrobbles.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorScrobbles"; ··· 14 15 Effect.retry({ times: 3 }), 15 16 Effect.timeout("120 seconds"), 16 17 Effect.catchAll((err) => { 17 - console.error(err); 18 + consola.error(err); 18 19 return Effect.succeed({ scrobbles: [] }); 19 20 }), 20 21 );
+2 -1
apps/api/src/xrpc/app/rocksky/actor/getActorSongs.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorSongs"; ··· 14 15 Effect.retry({ times: 3 }), 15 16 Effect.timeout("120 seconds"), 16 17 Effect.catchAll((err) => { 17 - console.error(err); 18 + consola.error(err); 18 19 return Effect.succeed({ tracks: [] }); 19 20 }), 20 21 );
+3 -2
apps/api/src/xrpc/app/rocksky/actor/getProfile.ts
··· 1 1 import { type Agent, AtpAgent } from "@atproto/api"; 2 + import { consola } from "consola"; 2 3 import type { OutputSchema } from "@atproto/api/dist/client/types/com/atproto/repo/getRecord"; 3 4 import type { HandlerAuth } from "@atproto/xrpc-server"; 4 5 import type { Context } from "context"; ··· 31 32 Effect.retry({ times: 3 }), 32 33 Effect.timeout("120 seconds"), 33 34 Effect.catchAll((err) => { 34 - console.error(err); 35 + consola.error(err); 35 36 return Effect.succeed({}); 36 37 }), 37 38 ); ··· 139 140 }: WithAgent): Effect.Effect<WithUser, Error> => { 140 141 return Effect.tryPromise({ 141 142 try: async () => { 142 - console.log(">> did", did); 143 + consola.info(">> did", did); 143 144 return ctx.db 144 145 .select() 145 146 .from(tables.users)
+3 -2
apps/api/src/xrpc/app/rocksky/album/getAlbum.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { asc, count, eq, or } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 19 20 Effect.retry({ times: 3 }), 20 21 Effect.timeout("120 seconds"), 21 22 Effect.catchAll((err) => { 22 - console.error(err); 23 + consola.error(err); 23 24 return Effect.succeed({}); 24 25 }), 25 26 ); ··· 91 92 ]); 92 93 }, 93 94 catch: (error) => { 94 - console.log("Error retrieving album:", error); 95 + consola.info("Error retrieving album:", error); 95 96 return new Error(`Failed to retrieve album: ${error}`); 96 97 }, 97 98 });
+2 -1
apps/api/src/xrpc/app/rocksky/album/getAlbumTracks.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { asc, eq } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 17 18 Effect.retry({ times: 3 }), 18 19 Effect.timeout("120 seconds"), 19 20 Effect.catchAll((err) => { 20 - console.error(err); 21 + consola.error(err); 21 22 return Effect.succeed({}); 22 23 }), 23 24 );
+2 -1
apps/api/src/xrpc/app/rocksky/album/getAlbums.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { AlbumViewBasic } from "lexicon/types/app/rocksky/album/defs"; ··· 14 15 Effect.retry({ times: 3 }), 15 16 Effect.timeout("120 seconds"), 16 17 Effect.catchAll((err) => { 17 - console.error(err); 18 + consola.error(err); 18 19 return Effect.succeed({ albums: [] }); 19 20 }), 20 21 );
+2 -1
apps/api/src/xrpc/app/rocksky/apikey/createApikey.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({}); 20 21 }), 21 22 );
+2 -1
apps/api/src/xrpc/app/rocksky/apikey/getApikeys.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 12 13 Effect.retry({ times: 3 }), 13 14 Effect.timeout("10 seconds"), 14 15 Effect.catchAll((err) => { 15 - console.error(err); 16 + consola.error(err); 16 17 return Effect.succeed({}); 17 18 }), 18 19 );
+2 -1
apps/api/src/xrpc/app/rocksky/apikey/removeApikey.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({}); 20 21 }), 21 22 );
+2 -1
apps/api/src/xrpc/app/rocksky/apikey/updateApikey.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { eq } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 14 15 Effect.retry({ times: 3 }), 15 16 Effect.timeout("10 seconds"), 16 17 Effect.catchAll((err) => { 17 - console.error(err); 18 + consola.error(err); 18 19 return Effect.succeed({}); 19 20 }), 20 21 );
+2 -1
apps/api/src/xrpc/app/rocksky/artist/getArtist.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { count, eq, or } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 16 17 Effect.retry({ times: 3 }), 17 18 Effect.timeout("10 seconds"), 18 19 Effect.catchAll((err) => { 19 - console.error(err); 20 + consola.error(err); 20 21 return Effect.succeed({}); 21 22 }), 22 23 );
+2 -1
apps/api/src/xrpc/app/rocksky/artist/getArtistAlbums.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { AlbumViewBasic } from "lexicon/types/app/rocksky/album/defs"; ··· 14 15 Effect.retry({ times: 3 }), 15 16 Effect.timeout("10 seconds"), 16 17 Effect.catchAll((err) => { 17 - console.error(err); 18 + consola.error(err); 18 19 return Effect.succeed({ albums: [] }); 19 20 }), 20 21 );
+2 -1
apps/api/src/xrpc/app/rocksky/artist/getArtistListeners.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { ListenerViewBasic } from "lexicon/types/app/rocksky/artist/defs"; ··· 13 14 Effect.retry({ times: 3 }), 14 15 Effect.timeout("10 seconds"), 15 16 Effect.catchAll((err) => { 16 - console.error(err); 17 + consola.error(err); 17 18 return Effect.succeed({ listeners: [] }); 18 19 }), 19 20 );
+2 -1
apps/api/src/xrpc/app/rocksky/artist/getArtistTracks.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { QueryParams } from "lexicon/types/app/rocksky/artist/getArtistTracks"; ··· 14 15 Effect.retry({ times: 3 }), 15 16 Effect.timeout("10 seconds"), 16 17 Effect.catchAll((err) => { 17 - console.error(err); 18 + consola.error(err); 18 19 return Effect.succeed({ tracks: [] }); 19 20 }), 20 21 );
+2 -1
apps/api/src/xrpc/app/rocksky/artist/getArtists.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { ArtistViewBasic } from "lexicon/types/app/rocksky/artist/defs"; ··· 17 18 Effect.retry({ times: 3 }), 18 19 Effect.timeout("10 seconds"), 19 20 Effect.catchAll((err) => { 20 - console.error(err); 21 + consola.error(err); 21 22 return Effect.succeed({ artists: [] }); 22 23 }), 23 24 );
+2 -1
apps/api/src/xrpc/app/rocksky/charts/getScrobblesChart.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { eq } from "drizzle-orm"; 3 4 import { Effect, Match, pipe, Cache, Duration } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 25 26 getScrobblesCache, 26 27 Effect.flatMap((cache) => cache.get(params)), 27 28 Effect.catchAll((err) => { 28 - console.error(err); 29 + consola.error(err); 29 30 return Effect.succeed({ scrobbles: [] }); 30 31 }), 31 32 );
+3 -2
apps/api/src/xrpc/app/rocksky/dropbox/getFiles.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { and, asc, eq, or } from "drizzle-orm"; 4 5 import { alias } from "drizzle-orm/pg-core"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({ files: [], directories: [] }); 23 24 }), 24 25 ); ··· 105 106 ]); 106 107 }, 107 108 catch: (error) => { 108 - console.error("Failed to retrieve files:", error); 109 + consola.error("Failed to retrieve files:", error); 109 110 return new Error(`Failed to retrieve files: ${error}`); 110 111 }, 111 112 });
+2 -1
apps/api/src/xrpc/app/rocksky/feed/getFeed.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { desc, eq, inArray } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 23 24 Effect.retry({ times: 3 }), 24 25 Effect.timeout("10 seconds"), 25 26 Effect.catchAll((err) => { 26 - console.error("Error retrieving scrobbles:", err); 27 + consola.error("Error retrieving scrobbles:", err); 27 28 return Effect.succeed({ scrobbles: [] }); 28 29 }), 29 30 );
+2 -1
apps/api/src/xrpc/app/rocksky/feed/getNowPlayings.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { desc, eq, sql } from "drizzle-orm"; 3 4 import { Cache, Duration, Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 30 31 nowPlayingCache, 31 32 Effect.flatMap((cache) => cache.get(params)), 32 33 Effect.catchAll((err) => { 33 - console.error(err); 34 + consola.error(err); 34 35 return Effect.succeed({}); 35 36 }), 36 37 );
+3 -2
apps/api/src/xrpc/app/rocksky/googledrive/getFiles.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { and, asc, eq, or } from "drizzle-orm"; 3 4 import { alias } from "drizzle-orm/pg-core"; 4 5 import { Effect, pipe } from "effect"; ··· 17 18 Effect.retry({ times: 3 }), 18 19 Effect.timeout("10 seconds"), 19 20 Effect.catchAll((err) => { 20 - console.error(err); 21 + consola.error(err); 21 22 return Effect.succeed({ files: [], directories: [] }); 22 23 }), 23 24 ); ··· 113 114 ]); 114 115 }, 115 116 catch: (error) => { 116 - console.error("Failed to retrieve files:", error); 117 + consola.error("Failed to retrieve files:", error); 117 118 return new Error(`Failed to retrieve albums: ${error}`); 118 119 }, 119 120 });
+4 -3
apps/api/src/xrpc/app/rocksky/graph/followAccount.ts
··· 1 1 import { TID } from "@atproto/common"; 2 + import { consola } from "consola"; 2 3 import type { HandlerAuth } from "@atproto/xrpc-server"; 3 4 import type { Context } from "context"; 4 5 import { and, eq, desc } from "drizzle-orm"; ··· 20 21 Effect.retry({ times: 3 }), 21 22 Effect.timeout("120 seconds"), 22 23 Effect.catchAll((err) => { 23 - console.error(err); 24 + consola.error(err); 24 25 return Effect.succeed({ 25 26 subject: {} satisfies ProfileViewBasic, 26 27 followers: [], ··· 75 76 }; 76 77 77 78 if (!FollowLexicon.validateRecord(record).success) { 78 - console.log(FollowLexicon.validateRecord(record)); 79 + consola.info(FollowLexicon.validateRecord(record)); 79 80 throw new Error("Invalid record"); 80 81 } 81 82 ··· 87 88 validate: false, 88 89 }); 89 90 const uri = res.data.uri; 90 - console.log(`Follow record created at: ${uri}`); 91 + consola.info(`Follow record created at: ${uri}`); 91 92 92 93 await ctx.db 93 94 .insert(tables.follows)
+2 -1
apps/api/src/xrpc/app/rocksky/graph/getFollowers.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { eq, desc, and, lt, inArray, count } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 16 17 Effect.retry({ times: 3 }), 17 18 Effect.timeout("120 seconds"), 18 19 Effect.catchAll((err) => { 19 - console.error(err); 20 + consola.error(err); 20 21 return Effect.succeed({ 21 22 subject: {} satisfies ProfileViewBasic, 22 23 followers: [] as ProfileViewBasic[],
+2 -1
apps/api/src/xrpc/app/rocksky/graph/getFollows.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { eq, desc, and, lt, inArray, count } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("120 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({ 23 24 subject: undefined, 24 25 follows: [],
+2 -1
apps/api/src/xrpc/app/rocksky/graph/getKnownFollowers.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { and, eq, sql, desc, lt } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 17 18 Effect.retry({ times: 3 }), 18 19 Effect.timeout("120 seconds"), 19 20 Effect.catchAll((err) => { 20 - console.error("getKnownFollowers error:", err); 21 + consola.error("getKnownFollowers error:", err); 21 22 return Effect.succeed({ 22 23 subject: {} satisfies ProfileViewBasic, 23 24 followers: [] as ProfileViewBasic[],
+2 -1
apps/api/src/xrpc/app/rocksky/graph/unfollowAccount.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { and, eq, desc } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("120 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({ 23 24 subject: {} satisfies ProfileViewBasic, 24 25 followers: [],
+3 -2
apps/api/src/xrpc/app/rocksky/player/addItemsToQueue.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { inArray } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 20 21 Effect.retry({ times: 3 }), 21 22 Effect.timeout("10 seconds"), 22 23 Effect.catchAll((err) => { 23 - console.error(err); 24 + consola.error(err); 24 25 return Effect.succeed({}); 25 26 }), 26 27 ); ··· 79 80 }); 80 81 }, 81 82 catch: (err) => { 82 - console.error(err); 83 + consola.error(err); 83 84 return {}; 84 85 }, 85 86 });
+2 -1
apps/api/src/xrpc/app/rocksky/player/getCurrentlyPlaying.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({}); 20 21 }), 21 22 );
+3 -2
apps/api/src/xrpc/app/rocksky/player/getPlaybackQueue.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({}); 23 24 }), 24 25 ); ··· 49 50 // Logic to retrieve the playback queue would go here 50 51 }, 51 52 catch: (err) => { 52 - console.error(err); 53 + consola.error(err); 53 54 return {}; 54 55 }, 55 56 });
+2 -1
apps/api/src/xrpc/app/rocksky/player/next.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({}); 20 21 }), 21 22 );
+2 -1
apps/api/src/xrpc/app/rocksky/player/pause.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({}); 20 21 }), 21 22 );
+2 -1
apps/api/src/xrpc/app/rocksky/player/play.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({}); 20 21 }), 21 22 );
+3 -2
apps/api/src/xrpc/app/rocksky/player/playDirectory.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({}); 23 24 }), 24 25 ); ··· 47 48 }); 48 49 }, 49 50 catch: (err) => { 50 - console.error(err); 51 + consola.error(err); 51 52 return {}; 52 53 }, 53 54 });
+3 -2
apps/api/src/xrpc/app/rocksky/player/playFile.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({}); 23 24 }), 24 25 ); ··· 47 48 }); 48 49 }, 49 50 catch: (err) => { 50 - console.error(err); 51 + consola.error(err); 51 52 return {}; 52 53 }, 53 54 });
+2 -1
apps/api/src/xrpc/app/rocksky/player/previous.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({}); 20 21 }), 21 22 );
+2 -1
apps/api/src/xrpc/app/rocksky/player/seek.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({}); 20 21 }), 21 22 );
+3 -2
apps/api/src/xrpc/app/rocksky/playlist/createPlaylist.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({}); 23 24 }), 24 25 ); ··· 44 45 await ctx.db.select().from(tables.playlists).execute(); 45 46 }, 46 47 catch: (err) => { 47 - console.error(err); 48 + consola.error(err); 48 49 return {}; 49 50 }, 50 51 });
+2 -1
apps/api/src/xrpc/app/rocksky/playlist/getPlaylist.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { asc, eq, sql } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 19 20 Effect.retry({ times: 3 }), 20 21 Effect.timeout("10 seconds"), 21 22 Effect.catchAll((err) => { 22 - console.error(err); 23 + consola.error(err); 23 24 return Effect.succeed({}); 24 25 }), 25 26 );
+2 -1
apps/api/src/xrpc/app/rocksky/playlist/getPlaylists.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { desc, eq, sql } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({ playlists: [] }); 23 24 }), 24 25 );
+3 -2
apps/api/src/xrpc/app/rocksky/playlist/insertDirectory.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({}); 23 24 }), 24 25 ); ··· 46 47 }); 47 48 }, 48 49 catch: (err) => { 49 - console.error(err); 50 + consola.error(err); 50 51 return {}; 51 52 }, 52 53 });
+3 -2
apps/api/src/xrpc/app/rocksky/playlist/insertFiles.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({}); 23 24 }), 24 25 ); ··· 47 48 }); 48 49 }, 49 50 catch: (err) => { 50 - console.error(err); 51 + consola.error(err); 51 52 return {}; 52 53 }, 53 54 });
+3 -2
apps/api/src/xrpc/app/rocksky/playlist/removePlaylist.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({}); 23 24 }), 24 25 ); ··· 45 46 // Logic to remove the playlist would go here 46 47 }, 47 48 catch: (err) => { 48 - console.error(err); 49 + consola.error(err); 49 50 return {}; 50 51 }, 51 52 });
+3 -2
apps/api/src/xrpc/app/rocksky/playlist/startPlaylist.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({}); 23 24 }), 24 25 ); ··· 47 48 }); 48 49 }, 49 50 catch: (err) => { 50 - console.error(err); 51 + consola.error(err); 51 52 return {}; 52 53 }, 53 54 });
+3 -2
apps/api/src/xrpc/app/rocksky/scrobble/createScrobble.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 + import { consola } from "consola"; 2 3 import { TID } from "@atproto/common"; 3 4 import type { HandlerAuth } from "@atproto/xrpc-server"; 4 5 import chalk from "chalk"; ··· 45 46 Effect.retry({ times: 3 }), 46 47 Effect.timeout("600 seconds"), 47 48 Effect.catchAll((err) => { 48 - console.error(err); 49 + consola.error(err); 49 50 return Effect.succeed({}); 50 51 }), 51 52 ); ··· 153 154 ), 154 155 ), 155 156 Effect.catchAll((error) => { 156 - console.error(`Error creating ${collection} record`, error); 157 + consola.error(`Error creating ${collection} record`, error); 157 158 return Effect.succeed(null); 158 159 }), 159 160 );
+2 -1
apps/api/src/xrpc/app/rocksky/scrobble/getScrobble.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { count, countDistinct, eq } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 20 21 Effect.retry({ times: 3 }), 21 22 Effect.timeout("10 seconds"), 22 23 Effect.catchAll((err) => { 23 - console.error("Error retrieving scrobble:", err); 24 + consola.error("Error retrieving scrobble:", err); 24 25 return Effect.succeed({}); 25 26 }), 26 27 );
+2 -1
apps/api/src/xrpc/app/rocksky/scrobble/getScrobbles.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { desc, eq, inArray } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 19 20 Effect.retry({ times: 3 }), 20 21 Effect.timeout("10 seconds"), 21 22 Effect.catchAll((err) => { 22 - console.error("Error retrieving scrobbles:", err); 23 + consola.error("Error retrieving scrobbles:", err); 23 24 return Effect.succeed({ scrobbles: [] }); 24 25 }), 25 26 );
+2 -1
apps/api/src/xrpc/app/rocksky/shout/createShout.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 + import { consola } from "consola"; 2 3 import type { HandlerAuth } from "@atproto/xrpc-server"; 3 4 import type { Context } from "context"; 4 5 import { eq } from "drizzle-orm"; ··· 21 22 Effect.retry({ times: 3 }), 22 23 Effect.timeout("10 seconds"), 23 24 Effect.catchAll((err) => { 24 - console.error(err); 25 + consola.error(err); 25 26 return Effect.succeed({}); 26 27 }), 27 28 );
+2 -1
apps/api/src/xrpc/app/rocksky/shout/getAlbumShouts.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { count, desc, eq } from "drizzle-orm"; 4 5 import { sql } from "drizzle-orm/sql"; ··· 19 20 Effect.retry({ times: 3 }), 20 21 Effect.timeout("10 seconds"), 21 22 Effect.catchAll((err) => { 22 - console.error(err); 23 + consola.error(err); 23 24 return Effect.succeed({ shouts: [] }); 24 25 }), 25 26 );
+2 -1
apps/api/src/xrpc/app/rocksky/shout/getArtistShouts.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { count, desc, eq, sql } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 18 19 Effect.retry({ times: 3 }), 19 20 Effect.timeout("10 seconds"), 20 21 Effect.catchAll((err) => { 21 - console.error(err); 22 + consola.error(err); 22 23 return Effect.succeed({ shouts: [] }); 23 24 }), 24 25 );
+2 -1
apps/api/src/xrpc/app/rocksky/shout/getProfileShouts.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { aliasedTable, count, desc, eq, or, sql } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({ shouts: [] }); 20 21 }), 21 22 );
+2 -1
apps/api/src/xrpc/app/rocksky/shout/getShoutReplies.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { asc, eq } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 17 18 Effect.retry({ times: 3 }), 18 19 Effect.timeout("10 seconds"), 19 20 Effect.catchAll((err) => { 20 - console.error(err); 21 + consola.error(err); 21 22 return Effect.succeed({ shouts: [] }); 22 23 }), 23 24 );
+2 -1
apps/api/src/xrpc/app/rocksky/shout/getTrackShouts.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { count, desc, eq, sql } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({ shouts: [] }); 20 21 }), 21 22 );
+2 -1
apps/api/src/xrpc/app/rocksky/shout/removeShout.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 + import { consola } from "consola"; 2 3 import type { HandlerAuth } from "@atproto/xrpc-server"; 3 4 import type { Context } from "context"; 4 5 import { Effect, pipe } from "effect"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({}); 20 21 }), 21 22 );
+2 -1
apps/api/src/xrpc/app/rocksky/shout/replyShout.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 2 import type { HandlerAuth } from "@atproto/xrpc-server"; 3 + import { consola } from "consola"; 3 4 import type { Context } from "context"; 4 5 import { Effect, pipe } from "effect"; 5 6 import type { Server } from "lexicon"; ··· 15 16 Effect.retry({ times: 3 }), 16 17 Effect.timeout("10 seconds"), 17 18 Effect.catchAll((err) => { 18 - console.error(err); 19 + consola.error(err); 19 20 return Effect.succeed({ albums: [] }); 20 21 }), 21 22 );
+2 -1
apps/api/src/xrpc/app/rocksky/shout/reportShout.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 12 13 Effect.retry({ times: 3 }), 13 14 Effect.timeout("10 seconds"), 14 15 Effect.catchAll((err) => { 15 - console.error(err); 16 + consola.error(err); 16 17 return Effect.succeed({}); 17 18 }), 18 19 );
+3 -2
apps/api/src/xrpc/app/rocksky/song/createSong.ts
··· 1 1 import type { Agent } from "@atproto/api"; 2 + import { consola } from "consola"; 2 3 import { TID } from "@atproto/common"; 3 4 import type { HandlerAuth } from "@atproto/xrpc-server"; 4 5 import chalk from "chalk"; ··· 41 42 Effect.retry({ times: 3 }), 42 43 Effect.timeout("120 seconds"), 43 44 Effect.catchAll((err) => { 44 - console.error(err); 45 + consola.error(err); 45 46 return Effect.succeed({}); 46 47 }), 47 48 ); ··· 213 214 ), 214 215 ), 215 216 Effect.catchAll((error) => { 216 - console.error(`Error creating ${collection} record`, error); 217 + consola.error(`Error creating ${collection} record`, error); 217 218 return Effect.fail(error); 218 219 }), 219 220 );
+2 -1
apps/api/src/xrpc/app/rocksky/song/getSong.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { count, eq, or } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 16 17 Effect.retry({ times: 3 }), 17 18 Effect.timeout("10 seconds"), 18 19 Effect.catchAll((err) => { 19 - console.error(err); 20 + consola.error(err); 20 21 return Effect.succeed({}); 21 22 }), 22 23 );
+2 -1
apps/api/src/xrpc/app/rocksky/song/getSongs.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { Effect, pipe } from "effect"; 3 4 import type { Server } from "lexicon"; 4 5 import type { SongViewBasic } from "lexicon/types/app/rocksky/song/defs"; ··· 14 15 Effect.retry({ times: 3 }), 15 16 Effect.timeout("10 seconds"), 16 17 Effect.catchAll((err) => { 17 - console.error(err); 18 + consola.error(err); 18 19 return Effect.succeed({ songs: [] }); 19 20 }), 20 21 );
+8 -7
apps/api/src/xrpc/app/rocksky/song/matchSong.ts
··· 1 1 import type { Context } from "context"; 2 + import { consola } from "consola"; 2 3 import { and, count, eq, or, sql } from "drizzle-orm"; 3 4 import { Effect, pipe } from "effect"; 4 5 import type { Server } from "lexicon"; ··· 8 9 import { env } from "lib/env"; 9 10 import tables from "schema"; 10 11 import type { SelectTrack } from "schema/tracks"; 11 - import { 12 + import type { 12 13 Album, 13 14 Artist, 14 15 MusicBrainzArtist, 15 16 SearchResponse, 16 17 Track, 17 18 } from "./types"; 18 - import { MusicbrainzTrack } from "types/track"; 19 + import type { MusicbrainzTrack } from "types/track"; 19 20 20 21 export default function (server: Server, ctx: Context) { 21 22 const matchSong = (params: QueryParams) => ··· 26 27 Effect.retry({ times: 3 }), 27 28 Effect.timeout("10 seconds"), 28 29 Effect.catchAll((err) => { 29 - console.error(err); 30 + consola.error(err); 30 31 return Effect.succeed({}); 31 32 }), 32 33 ); ··· 44 45 const retrieve = ({ params, ctx }: { params: QueryParams; ctx: Context }) => { 45 46 return Effect.tryPromise({ 46 47 try: async () => { 47 - let record = await ctx.db 48 + const record = await ctx.db 48 49 .select() 49 50 .from(tables.tracks) 50 51 .leftJoin( ··· 227 228 .execute(); 228 229 229 230 if (!spotifyTokens || spotifyTokens.length === 0) { 230 - console.warn("No Spotify tokens available for beta users"); 231 + consola.warn("No Spotify tokens available for beta users"); 231 232 return undefined; 232 233 } 233 234 ··· 235 236 spotifyTokens[Math.floor(Math.random() * spotifyTokens.length)]; 236 237 237 238 if (!spotify_tokens || !spotify_apps) { 238 - console.warn("Invalid Spotify token or app data"); 239 + consola.warn("Invalid Spotify token or app data"); 239 240 return undefined; 240 241 } 241 242 ··· 339 340 artists, 340 341 }; 341 342 } catch (error) { 342 - console.error("Error fetching MusicBrainz data"); 343 + consola.error("Error fetching MusicBrainz data"); 343 344 } 344 345 345 346 return {
+2 -1
apps/api/src/xrpc/app/rocksky/spotify/getCurrentlyPlaying.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { and, eq, or } from "drizzle-orm"; 4 5 import { Effect, Match, pipe } from "effect"; ··· 21 22 Effect.retry({ times: 3 }), 22 23 Effect.timeout("10 seconds"), 23 24 Effect.catchAll((err) => { 24 - console.error(err); 25 + consola.error(err); 25 26 return Effect.succeed({}); 26 27 }), 27 28 );
+3 -2
apps/api/src/xrpc/app/rocksky/spotify/next.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 21 22 Effect.retry({ times: 3 }), 22 23 Effect.timeout("10 seconds"), 23 24 Effect.catchAll((err) => { 24 - console.error(err); 25 + consola.error(err); 25 26 return Effect.succeed({}); 26 27 }), 27 28 ); ··· 143 144 144 145 const presentation = (result): Effect.Effect<{}, never> => { 145 146 // Logic to format the result for presentation 146 - console.log("Next action result:", result); 147 + consola.info("Next action result:", result); 147 148 return Effect.sync(() => ({})); 148 149 };
+3 -2
apps/api/src/xrpc/app/rocksky/spotify/pause.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 21 22 Effect.retry({ times: 3 }), 22 23 Effect.timeout("10 seconds"), 23 24 Effect.catchAll((err) => { 24 - console.error(err); 25 + consola.error(err); 25 26 return Effect.succeed({}); 26 27 }), 27 28 ); ··· 143 144 144 145 const presentation = (result): Effect.Effect<{}, never> => { 145 146 // Logic to format the result for presentation 146 - console.log("Pause action result:", result); 147 + consola.info("Pause action result:", result); 147 148 return Effect.sync(() => ({})); 148 149 };
+3 -2
apps/api/src/xrpc/app/rocksky/spotify/play.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 21 22 Effect.retry({ times: 3 }), 22 23 Effect.timeout("10 seconds"), 23 24 Effect.catchAll((err) => { 24 - console.error(err); 25 + consola.error(err); 25 26 return Effect.succeed({}); 26 27 }), 27 28 ); ··· 142 143 }; 143 144 144 145 const presentation = (result) => { 145 - console.log("Play action result:", result); 146 + consola.info("Play action result:", result); 146 147 return Effect.sync(() => ({})); 147 148 };
+3 -2
apps/api/src/xrpc/app/rocksky/spotify/previous.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 21 22 Effect.retry({ times: 3 }), 22 23 Effect.timeout("10 seconds"), 23 24 Effect.catchAll((err) => { 24 - console.error(err); 25 + consola.error(err); 25 26 return Effect.succeed({}); 26 27 }), 27 28 ); ··· 143 144 144 145 const presentation = (result) => { 145 146 // Logic to format the result for presentation 146 - console.log("Previous action result:", result); 147 + consola.info("Previous action result:", result); 147 148 return Effect.sync(() => ({})); 148 149 };
+3 -2
apps/api/src/xrpc/app/rocksky/spotify/seek.ts
··· 1 1 import type { HandlerAuth } from "@atproto/xrpc-server"; 2 + import { consola } from "consola"; 2 3 import type { Context } from "context"; 3 4 import { eq } from "drizzle-orm"; 4 5 import { Effect, pipe } from "effect"; ··· 21 22 Effect.retry({ times: 3 }), 22 23 Effect.timeout("10 seconds"), 23 24 Effect.catchAll((err) => { 24 - console.error(err); 25 + consola.error(err); 25 26 return Effect.succeed({}); 26 27 }), 27 28 ); ··· 161 162 162 163 const presentation = (result) => { 163 164 // Logic to format the result for presentation 164 - console.log("Seek action result:", result); 165 + consola.info("Seek action result:", result); 165 166 return Effect.sync(() => ({})); 166 167 };
+4 -5
bun.lock
··· 41 41 "better-sqlite3": "^12.4.1", 42 42 "chalk": "^5.4.1", 43 43 "chanfana": "^2.0.2", 44 + "consola": "^3.4.2", 44 45 "cors": "^2.8.5", 45 46 "dayjs": "^1.11.13", 46 47 "dotenv": "^16.4.7", ··· 1573 1574 1574 1575 "confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="], 1575 1576 1577 + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], 1578 + 1576 1579 "content-disposition": ["content-disposition@1.0.0", "", { "dependencies": { "safe-buffer": "5.2.1" } }, "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg=="], 1577 1580 1578 1581 "content-type": ["content-type@1.0.5", "", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], ··· 1757 1760 1758 1761 "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], 1759 1762 1760 - "effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="], 1763 + "effect": ["effect@3.19.14", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-3vwdq0zlvQOxXzXNKRIPKTqZNMyGCdaFUBfMPqpsyzZDre67kgC1EEHDV4EoQTovJ4w5fmJW756f86kkuz7WFA=="], 1761 1764 1762 1765 "electron-to-chromium": ["electron-to-chromium@1.5.234", "", {}, "sha512-RXfEp2x+VRYn8jbKfQlRImzoJU01kyDvVPBmG39eU2iuRVhuS6vQNocB8J0/8GrIMLnPzgz4eW6WiRnJkTuNWg=="], 1763 1766 ··· 3083 3086 3084 3087 "@rocksky/cli/drizzle-orm": ["drizzle-orm@0.45.1", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@upstash/redis": ">=1.34.7", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@upstash/redis", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-Te0FOdKIistGNPMq2jscdqngBRfBpC8uMFVwqjf6gtTVJHIQ/dosgV/CLBU2N4ZJBsXL5savCba9b0YJskKdcA=="], 3085 3088 3086 - "@rocksky/cli/effect": ["effect@3.19.14", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-3vwdq0zlvQOxXzXNKRIPKTqZNMyGCdaFUBfMPqpsyzZDre67kgC1EEHDV4EoQTovJ4w5fmJW756f86kkuz7WFA=="], 3087 - 3088 3089 "@rocksky/doc/vitest": ["vitest@2.1.9", "", { "dependencies": { "@vitest/expect": "2.1.9", "@vitest/mocker": "2.1.9", "@vitest/pretty-format": "^2.1.9", "@vitest/runner": "2.1.9", "@vitest/snapshot": "2.1.9", "@vitest/spy": "2.1.9", "@vitest/utils": "2.1.9", "chai": "^5.1.2", "debug": "^4.3.7", "expect-type": "^1.1.0", "magic-string": "^0.30.12", "pathe": "^1.1.2", "std-env": "^3.8.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.1", "tinypool": "^1.0.1", "tinyrainbow": "^1.2.0", "vite": "^5.0.0", "vite-node": "2.1.9", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/node": "^18.0.0 || >=20.0.0", "@vitest/browser": "2.1.9", "@vitest/ui": "2.1.9", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q=="], 3089 3090 3090 3091 "@rocksky/spotify-proxy/@cloudflare/vitest-pool-workers": ["@cloudflare/vitest-pool-workers@0.8.71", "", { "dependencies": { "birpc": "0.2.14", "cjs-module-lexer": "^1.2.3", "devalue": "^5.3.2", "miniflare": "4.20250906.0", "semver": "^7.7.1", "wrangler": "4.35.0", "zod": "^3.22.3" }, "peerDependencies": { "@vitest/runner": "2.0.x - 3.2.x", "@vitest/snapshot": "2.0.x - 3.2.x", "vitest": "2.0.x - 3.2.x" } }, "sha512-keu2HCLQfRNwbmLBCDXJgCFpANTaYnQpE01fBOo4CNwiWHUT7SZGN7w64RKiSWRHyYppStXBuE5Ng7F42+flpg=="], ··· 3094 3095 "@rocksky/spotify-proxy/wrangler": ["wrangler@4.42.2", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.0", "@cloudflare/unenv-preset": "2.7.7", "blake3-wasm": "2.1.5", "esbuild": "0.25.4", "miniflare": "4.20251008.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.21", "workerd": "1.20251008.0" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20251008.0" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-1iTnbjB4F12KSP1zbfxQL495xarS+vdrZnulQP2SEcAxDTUGn7N9zk1O2WtFOc+Fhcgl+9/sdz/4AL9pF34Pwg=="], 3095 3096 3096 3097 "@rocksky/web/@types/ramda": ["@types/ramda@0.31.1", "", { "dependencies": { "types-ramda": "^0.31.0" } }, "sha512-Vt6sFXnuRpzaEj+yeutA0q3bcAsK7wdPuASIzR9LXqL4gJPyFw8im9qchlbp4ltuf3kDEIRmPJTD/Fkg60dn7g=="], 3097 - 3098 - "@rocksky/web/effect": ["effect@3.19.14", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-3vwdq0zlvQOxXzXNKRIPKTqZNMyGCdaFUBfMPqpsyzZDre67kgC1EEHDV4EoQTovJ4w5fmJW756f86kkuz7WFA=="], 3099 3098 3100 3099 "@rocksky/web/ramda": ["ramda@0.32.0", "", {}, "sha512-GQWAHhxhxWBWA8oIBr1XahFVjQ9Fic6MK9ikijfd4TZHfE2+urfk+irVlR5VOn48uwMgM+loRRBJd6Yjsbc0zQ=="], 3101 3100