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

update getProfile xrpc method

+174 -33
+20
apps/api/src/schema/dropbox-accounts.ts
··· 1 + import { InferInsertModel, InferSelectModel } from "drizzle-orm"; 2 + import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 + import users from "./users"; 4 + 5 + const dropboxAccounts = pgTable("dropbox_accounts", { 6 + id: text("xata_id").primaryKey(), 7 + email: text("email").unique().notNull(), 8 + isBetaUser: boolean("is_beta_user").default(false).notNull(), 9 + userId: text("user_id") 10 + .notNull() 11 + .references(() => users.id), 12 + xataVersion: text("xata_version").notNull(), 13 + createdAt: timestamp("xata_createdat").defaultNow().notNull(), 14 + updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), 15 + }); 16 + 17 + export type SelectDropboxAccounts = InferSelectModel<typeof dropboxAccounts>; 18 + export type InsertDropboxAccounts = InferInsertModel<typeof dropboxAccounts>; 19 + 20 + export default dropboxAccounts;
+24
apps/api/src/schema/google-drive-accounts.ts
··· 1 + import { InferInsertModel, InferSelectModel } from "drizzle-orm"; 2 + import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core"; 3 + import users from "./users"; 4 + 5 + const googleDriveAccounts = pgTable("google_drive_accounts", { 6 + id: text("xata_id").primaryKey(), 7 + email: text("email").unique().notNull(), 8 + isBetaUser: boolean("is_beta_user").default(false).notNull(), 9 + userId: text("user_id") 10 + .notNull() 11 + .references(() => users.id), 12 + xataVersion: text("xata_version").notNull(), 13 + createdAt: timestamp("xata_createdat").defaultNow().notNull(), 14 + updatedAt: timestamp("xata_updatedat").defaultNow().notNull(), 15 + }); 16 + 17 + export type SelectGoogleDriveAccounts = InferSelectModel< 18 + typeof googleDriveAccounts 19 + >; 20 + export type InsertGoogleDriveAccounts = InferInsertModel< 21 + typeof googleDriveAccounts 22 + >; 23 + 24 + export default googleDriveAccounts;
+4
apps/api/src/schema/index.ts
··· 4 4 import artistAlbums from "./artist-albums"; 5 5 import artistTracks from "./artist-tracks"; 6 6 import artists from "./artists"; 7 + import dropboxAccounts from "./dropbox-accounts"; 8 + import googleDriveAccounts from "./google-drive-accounts"; 7 9 import lovedTracks from "./loved-tracks"; 8 10 import playlistTracks from "./playlist-tracks"; 9 11 import playlists from "./playlists"; ··· 46 48 spotifyTokens, 47 49 artistTracks, 48 50 artistAlbums, 51 + dropboxAccounts, 52 + googleDriveAccounts, 49 53 };
+98 -7
apps/api/src/xrpc/app/rocksky/actor/getProfile.ts
··· 10 10 import { createAgent } from "lib/agent"; 11 11 import _ from "lodash"; 12 12 import tables from "schema"; 13 + import { SelectDropboxAccounts } from "schema/dropbox-accounts"; 14 + import { SelectGoogleDriveAccounts } from "schema/google-drive-accounts"; 15 + import { SelectSpotifyAccount } from "schema/spotify-accounts"; 16 + import { SelectSpotifyToken } from "schema/spotify-tokens"; 13 17 import { SelectUser } from "schema/users"; 14 18 15 19 export default function (server: Server, ctx: Context) { ··· 161 165 did, 162 166 agent, 163 167 user, 164 - }: WithUser): Effect.Effect<[Profile, string], Error> => { 168 + }: WithUser): Effect.Effect< 169 + [ 170 + Profile, 171 + string, 172 + SelectSpotifyAccount, 173 + SelectSpotifyToken, 174 + SelectGoogleDriveAccounts, 175 + SelectDropboxAccounts, 176 + ], 177 + Error 178 + > => { 165 179 return Effect.tryPromise({ 166 180 try: async () => { 167 181 return Promise.all([ ··· 178 192 user, 179 193 })), 180 194 ctx.resolver.resolveDidToHandle(did), 195 + ctx.db 196 + .select() 197 + .from(tables.spotifyAccounts) 198 + .leftJoin( 199 + tables.users, 200 + eq(tables.spotifyAccounts.userId, tables.users.id) 201 + ) 202 + .where(eq(tables.users.did, did)) 203 + .execute() 204 + .then(([result]) => result.spotify_accounts), 205 + ctx.db 206 + .select() 207 + .from(tables.spotifyTokens) 208 + .leftJoin( 209 + tables.users, 210 + eq(tables.spotifyTokens.userId, tables.users.id) 211 + ) 212 + .where(eq(tables.users.did, did)) 213 + .execute() 214 + .then(([result]) => result?.spotify_tokens), 215 + ctx.db 216 + .select() 217 + .from(tables.googleDriveAccounts) 218 + .leftJoin( 219 + tables.users, 220 + eq(tables.googleDriveAccounts.userId, tables.users.id) 221 + ) 222 + .where(eq(tables.users.did, did)) 223 + .execute() 224 + .then(([result]) => result?.google_drive_accounts), 225 + ctx.db 226 + .select() 227 + .from(tables.dropboxAccounts) 228 + .leftJoin( 229 + tables.users, 230 + eq(tables.dropboxAccounts.userId, tables.users.id) 231 + ) 232 + .where(eq(tables.users.did, did)) 233 + .execute() 234 + .then(([result]) => result?.dropbox_accounts), 181 235 ]); 182 236 }, 183 237 catch: (error) => new Error(`Failed to retrieve profile: ${error}`), 184 238 }); 185 239 }; 186 240 187 - const refreshProfile = ([profile, handle]: [Profile, string]) => { 241 + const refreshProfile = ([ 242 + profile, 243 + handle, 244 + selectSpotifyAccount, 245 + selectSpotifyToken, 246 + selectGoogleDriveAccounts, 247 + selectDropboxAccounts, 248 + ]: [ 249 + Profile, 250 + string, 251 + SelectSpotifyAccount, 252 + SelectSpotifyToken, 253 + SelectGoogleDriveAccounts, 254 + SelectDropboxAccounts, 255 + ]) => { 188 256 return Effect.tryPromise({ 189 257 try: async () => { 190 - return [profile, handle]; 258 + return [ 259 + profile, 260 + handle, 261 + selectSpotifyAccount, 262 + selectSpotifyToken, 263 + selectGoogleDriveAccounts, 264 + selectDropboxAccounts, 265 + ]; 191 266 }, 192 267 catch: (error) => new Error(`Failed to refresh profile: ${error}`), 193 268 }); 194 269 }; 195 270 196 - const presentation = ([profile, handle]: [Profile, string]): Effect.Effect< 197 - ProfileViewDetailed, 198 - never 199 - > => { 271 + const presentation = ([ 272 + profile, 273 + handle, 274 + spotifyUser, 275 + spotifyToken, 276 + googledrive, 277 + dropbox, 278 + ]: [ 279 + Profile, 280 + string, 281 + SelectSpotifyAccount, 282 + SelectSpotifyToken, 283 + SelectGoogleDriveAccounts, 284 + SelectDropboxAccounts, 285 + ]): Effect.Effect<ProfileViewDetailed, never> => { 200 286 return Effect.sync(() => ({ 201 287 id: profile.user?.id, 202 288 did: profile.did, ··· 205 291 avatar: `https://cdn.bsky.app/img/avatar/plain/${profile.did}/${_.get(profile, "profileRecord.value.avatar.ref", "").toString()}@jpeg`, 206 292 createdAt: profile.user?.createdAt.toISOString(), 207 293 updatedAt: profile.user?.updatedAt.toISOString(), 294 + spotifyUser, 295 + spotifyToken, 296 + spotifyConnected: !!spotifyToken, 297 + googledrive, 298 + dropbox, 208 299 })); 209 300 }; 210 301
+14 -12
apps/web/src/api/profile.ts
··· 1 - import axios from "axios"; 2 - import { API_URL } from "../consts"; 1 + import { client } from "."; 3 2 import { Scrobble } from "../types/scrobble"; 4 3 5 4 export const getProfileByDid = async (did: string) => { 6 - const response = await axios.get(`${API_URL}/users/${did}`); 5 + const response = await client.get("/xrpc/app.rocksky.actor.getProfile", { 6 + params: { did }, 7 + }); 7 8 return response.data; 8 9 }; 9 10 10 11 export const getProfileStatsByDid = async (did: string) => { 11 - const response = await axios.get( 12 - `${API_URL}/xrpc/app.rocksky.stats.getStats`, 13 - { params: { did } } 14 - ); 12 + const response = await client.get("/xrpc/app.rocksky.stats.getStats", { 13 + params: { did }, 14 + }); 15 15 return response.data; 16 16 }; 17 17 18 18 export const getRecentTracksByDid = async ( 19 19 did: string, 20 20 offset = 0, 21 - size = 10 21 + limit = 10 22 22 ): Promise<Scrobble[]> => { 23 - const response = await axios.get<Scrobble[]>( 24 - `${API_URL}/users/${did}/scrobbles`, 25 - { params: { size, offset } } 23 + const response = await client.get<{ scrobbles: Scrobble[] }>( 24 + "/xrpc/app.rocksky.actor.getActorScrobbles", 25 + { 26 + params: { did, offset, limit }, 27 + } 26 28 ); 27 - return response.data; 29 + return response.data.scrobbles || []; 28 30 };
+7 -7
apps/web/src/pages/profile/overview/recenttracks/RecentTracks.tsx
··· 96 96 title: item.title, 97 97 artist: item.artist, 98 98 album: item.album, 99 - albumArt: item.album_art, 100 - albumArtist: item.album_artist, 99 + albumArt: item.albumArt, 100 + albumArtist: item.albumArtist, 101 101 uri: item.uri, 102 - date: item.created_at.endsWith("Z") 103 - ? item.created_at 104 - : `${item.created_at}Z`, 102 + date: item.createdAt.endsWith("Z") 103 + ? item.createdAt 104 + : `${item.createdAt}Z`, 105 105 scrobbleUri: item.uri, 106 - albumUri: item.album_uri, 107 - artistUri: item.artist_uri, 106 + albumUri: item.albumUri, 107 + artistUri: item.artistUri, 108 108 })) 109 109 ); 110 110
+7 -7
apps/web/src/types/scrobble.ts
··· 1 1 export type Scrobble = { 2 2 id: string; 3 - track_id: string; 3 + trackId: string; 4 4 title: string; 5 5 artist: string; 6 6 album: string; 7 - album_art?: string; 8 - album_artist: string; 7 + albumArt?: string; 8 + albumArtist: string; 9 9 handle: string; 10 - track_uri: string; 11 - album_uri: string; 12 - artist_uri: string; 10 + trackUri: string; 11 + albumUri: string; 12 + artistUri: string; 13 13 uri: string; 14 - created_at: string; 14 + createdAt: string; 15 15 };