tangled
alpha
login
or
join now
t1c.dev
/
rocksky
forked from
rocksky.app/rocksky
2
fork
atom
A decentralized music tracking and discovery platform built on AT Protocol 🎵
2
fork
atom
overview
issues
pulls
pipelines
update getProfile xrpc method
tsiry-sandratraina.com
8 months ago
552a5de5
dd7a78d8
+174
-33
7 changed files
expand all
collapse all
unified
split
apps
api
src
schema
dropbox-accounts.ts
google-drive-accounts.ts
index.ts
xrpc
app
rocksky
actor
getProfile.ts
web
src
api
profile.ts
pages
profile
overview
recenttracks
RecentTracks.tsx
types
scrobble.ts
+20
apps/api/src/schema/dropbox-accounts.ts
···
1
1
+
import { InferInsertModel, InferSelectModel } from "drizzle-orm";
2
2
+
import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core";
3
3
+
import users from "./users";
4
4
+
5
5
+
const dropboxAccounts = pgTable("dropbox_accounts", {
6
6
+
id: text("xata_id").primaryKey(),
7
7
+
email: text("email").unique().notNull(),
8
8
+
isBetaUser: boolean("is_beta_user").default(false).notNull(),
9
9
+
userId: text("user_id")
10
10
+
.notNull()
11
11
+
.references(() => users.id),
12
12
+
xataVersion: text("xata_version").notNull(),
13
13
+
createdAt: timestamp("xata_createdat").defaultNow().notNull(),
14
14
+
updatedAt: timestamp("xata_updatedat").defaultNow().notNull(),
15
15
+
});
16
16
+
17
17
+
export type SelectDropboxAccounts = InferSelectModel<typeof dropboxAccounts>;
18
18
+
export type InsertDropboxAccounts = InferInsertModel<typeof dropboxAccounts>;
19
19
+
20
20
+
export default dropboxAccounts;
+24
apps/api/src/schema/google-drive-accounts.ts
···
1
1
+
import { InferInsertModel, InferSelectModel } from "drizzle-orm";
2
2
+
import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core";
3
3
+
import users from "./users";
4
4
+
5
5
+
const googleDriveAccounts = pgTable("google_drive_accounts", {
6
6
+
id: text("xata_id").primaryKey(),
7
7
+
email: text("email").unique().notNull(),
8
8
+
isBetaUser: boolean("is_beta_user").default(false).notNull(),
9
9
+
userId: text("user_id")
10
10
+
.notNull()
11
11
+
.references(() => users.id),
12
12
+
xataVersion: text("xata_version").notNull(),
13
13
+
createdAt: timestamp("xata_createdat").defaultNow().notNull(),
14
14
+
updatedAt: timestamp("xata_updatedat").defaultNow().notNull(),
15
15
+
});
16
16
+
17
17
+
export type SelectGoogleDriveAccounts = InferSelectModel<
18
18
+
typeof googleDriveAccounts
19
19
+
>;
20
20
+
export type InsertGoogleDriveAccounts = InferInsertModel<
21
21
+
typeof googleDriveAccounts
22
22
+
>;
23
23
+
24
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
7
+
import dropboxAccounts from "./dropbox-accounts";
8
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
51
+
dropboxAccounts,
52
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
13
+
import { SelectDropboxAccounts } from "schema/dropbox-accounts";
14
14
+
import { SelectGoogleDriveAccounts } from "schema/google-drive-accounts";
15
15
+
import { SelectSpotifyAccount } from "schema/spotify-accounts";
16
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
164
-
}: WithUser): Effect.Effect<[Profile, string], Error> => {
168
168
+
}: WithUser): Effect.Effect<
169
169
+
[
170
170
+
Profile,
171
171
+
string,
172
172
+
SelectSpotifyAccount,
173
173
+
SelectSpotifyToken,
174
174
+
SelectGoogleDriveAccounts,
175
175
+
SelectDropboxAccounts,
176
176
+
],
177
177
+
Error
178
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
195
+
ctx.db
196
196
+
.select()
197
197
+
.from(tables.spotifyAccounts)
198
198
+
.leftJoin(
199
199
+
tables.users,
200
200
+
eq(tables.spotifyAccounts.userId, tables.users.id)
201
201
+
)
202
202
+
.where(eq(tables.users.did, did))
203
203
+
.execute()
204
204
+
.then(([result]) => result.spotify_accounts),
205
205
+
ctx.db
206
206
+
.select()
207
207
+
.from(tables.spotifyTokens)
208
208
+
.leftJoin(
209
209
+
tables.users,
210
210
+
eq(tables.spotifyTokens.userId, tables.users.id)
211
211
+
)
212
212
+
.where(eq(tables.users.did, did))
213
213
+
.execute()
214
214
+
.then(([result]) => result?.spotify_tokens),
215
215
+
ctx.db
216
216
+
.select()
217
217
+
.from(tables.googleDriveAccounts)
218
218
+
.leftJoin(
219
219
+
tables.users,
220
220
+
eq(tables.googleDriveAccounts.userId, tables.users.id)
221
221
+
)
222
222
+
.where(eq(tables.users.did, did))
223
223
+
.execute()
224
224
+
.then(([result]) => result?.google_drive_accounts),
225
225
+
ctx.db
226
226
+
.select()
227
227
+
.from(tables.dropboxAccounts)
228
228
+
.leftJoin(
229
229
+
tables.users,
230
230
+
eq(tables.dropboxAccounts.userId, tables.users.id)
231
231
+
)
232
232
+
.where(eq(tables.users.did, did))
233
233
+
.execute()
234
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
187
-
const refreshProfile = ([profile, handle]: [Profile, string]) => {
241
241
+
const refreshProfile = ([
242
242
+
profile,
243
243
+
handle,
244
244
+
selectSpotifyAccount,
245
245
+
selectSpotifyToken,
246
246
+
selectGoogleDriveAccounts,
247
247
+
selectDropboxAccounts,
248
248
+
]: [
249
249
+
Profile,
250
250
+
string,
251
251
+
SelectSpotifyAccount,
252
252
+
SelectSpotifyToken,
253
253
+
SelectGoogleDriveAccounts,
254
254
+
SelectDropboxAccounts,
255
255
+
]) => {
188
256
return Effect.tryPromise({
189
257
try: async () => {
190
190
-
return [profile, handle];
258
258
+
return [
259
259
+
profile,
260
260
+
handle,
261
261
+
selectSpotifyAccount,
262
262
+
selectSpotifyToken,
263
263
+
selectGoogleDriveAccounts,
264
264
+
selectDropboxAccounts,
265
265
+
];
191
266
},
192
267
catch: (error) => new Error(`Failed to refresh profile: ${error}`),
193
268
});
194
269
};
195
270
196
196
-
const presentation = ([profile, handle]: [Profile, string]): Effect.Effect<
197
197
-
ProfileViewDetailed,
198
198
-
never
199
199
-
> => {
271
271
+
const presentation = ([
272
272
+
profile,
273
273
+
handle,
274
274
+
spotifyUser,
275
275
+
spotifyToken,
276
276
+
googledrive,
277
277
+
dropbox,
278
278
+
]: [
279
279
+
Profile,
280
280
+
string,
281
281
+
SelectSpotifyAccount,
282
282
+
SelectSpotifyToken,
283
283
+
SelectGoogleDriveAccounts,
284
284
+
SelectDropboxAccounts,
285
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
294
+
spotifyUser,
295
295
+
spotifyToken,
296
296
+
spotifyConnected: !!spotifyToken,
297
297
+
googledrive,
298
298
+
dropbox,
208
299
}));
209
300
};
210
301
+14
-12
apps/web/src/api/profile.ts
···
1
1
-
import axios from "axios";
2
2
-
import { API_URL } from "../consts";
1
1
+
import { client } from ".";
3
2
import { Scrobble } from "../types/scrobble";
4
3
5
4
export const getProfileByDid = async (did: string) => {
6
6
-
const response = await axios.get(`${API_URL}/users/${did}`);
5
5
+
const response = await client.get("/xrpc/app.rocksky.actor.getProfile", {
6
6
+
params: { did },
7
7
+
});
7
8
return response.data;
8
9
};
9
10
10
11
export const getProfileStatsByDid = async (did: string) => {
11
11
-
const response = await axios.get(
12
12
-
`${API_URL}/xrpc/app.rocksky.stats.getStats`,
13
13
-
{ params: { did } }
14
14
-
);
12
12
+
const response = await client.get("/xrpc/app.rocksky.stats.getStats", {
13
13
+
params: { did },
14
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
21
-
size = 10
21
21
+
limit = 10
22
22
): Promise<Scrobble[]> => {
23
23
-
const response = await axios.get<Scrobble[]>(
24
24
-
`${API_URL}/users/${did}/scrobbles`,
25
25
-
{ params: { size, offset } }
23
23
+
const response = await client.get<{ scrobbles: Scrobble[] }>(
24
24
+
"/xrpc/app.rocksky.actor.getActorScrobbles",
25
25
+
{
26
26
+
params: { did, offset, limit },
27
27
+
}
26
28
);
27
27
-
return response.data;
29
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
99
-
albumArt: item.album_art,
100
100
-
albumArtist: item.album_artist,
99
99
+
albumArt: item.albumArt,
100
100
+
albumArtist: item.albumArtist,
101
101
uri: item.uri,
102
102
-
date: item.created_at.endsWith("Z")
103
103
-
? item.created_at
104
104
-
: `${item.created_at}Z`,
102
102
+
date: item.createdAt.endsWith("Z")
103
103
+
? item.createdAt
104
104
+
: `${item.createdAt}Z`,
105
105
scrobbleUri: item.uri,
106
106
-
albumUri: item.album_uri,
107
107
-
artistUri: item.artist_uri,
106
106
+
albumUri: item.albumUri,
107
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
3
-
track_id: string;
3
3
+
trackId: string;
4
4
title: string;
5
5
artist: string;
6
6
album: string;
7
7
-
album_art?: string;
8
8
-
album_artist: string;
7
7
+
albumArt?: string;
8
8
+
albumArtist: string;
9
9
handle: string;
10
10
-
track_uri: string;
11
11
-
album_uri: string;
12
12
-
artist_uri: string;
10
10
+
trackUri: string;
11
11
+
albumUri: string;
12
12
+
artistUri: string;
13
13
uri: string;
14
14
-
created_at: string;
14
14
+
createdAt: string;
15
15
};