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

Add following filter to getScrobbles API

When following=true and a viewer DID is provided, retrieve the viewer's
followed subject DIDs and restrict scrobbles to those actors. Return an
empty array if the viewer follows no one. Update lexicon and type defs
and import inArray for the DB query.

+47 -8
+4
apps/api/lexicons/scrobble/getScrobbles.json
··· 13 13 "description": "The DID or handle of the actor", 14 14 "format": "at-identifier" 15 15 }, 16 + "following": { 17 + "type": "boolean", 18 + "description": "If true, only return scrobbles from actors the viewer is following." 19 + }, 16 20 "limit": { 17 21 "type": "integer", 18 22 "description": "The maximum number of scrobbles to return",
+6 -2
apps/api/pkl/defs/scrobble/getScrobbles.pkl
··· 1 - amends "../../schema/lexicon.pkl" 1 + amends "../../schema/lexicon.pkl" 2 2 3 3 lexicon = 1 4 4 id = "app.rocksky.scrobble.getScrobbles" ··· 11 11 ["did"] = new StringType { 12 12 description = "The DID or handle of the actor" 13 13 format = "at-identifier" 14 + } 15 + ["following"] = new BooleanType { 16 + type = "boolean" 17 + description = "If true, only return scrobbles from actors the viewer is following." 14 18 } 15 19 ["limit"] = new IntegerType { 16 20 type = "integer" ··· 39 43 } 40 44 } 41 45 } 42 - } 46 + }
+5
apps/api/src/lexicon/lexicons.ts
··· 4384 4384 description: "The DID or handle of the actor", 4385 4385 format: "at-identifier", 4386 4386 }, 4387 + following: { 4388 + type: "boolean", 4389 + description: 4390 + "If true, only return scrobbles from actors the viewer is following.", 4391 + }, 4387 4392 limit: { 4388 4393 type: "integer", 4389 4394 description: "The maximum number of scrobbles to return",
+2
apps/api/src/lexicon/types/app/rocksky/scrobble/getScrobbles.ts
··· 12 12 export interface QueryParams { 13 13 /** The DID or handle of the actor */ 14 14 did?: string; 15 + /** If true, only return scrobbles from actors the viewer is following. */ 16 + following?: boolean; 15 17 /** The maximum number of scrobbles to return */ 16 18 limit?: number; 17 19 /** The offset for pagination */
+30 -6
apps/api/src/xrpc/app/rocksky/scrobble/getScrobbles.ts
··· 1 1 import type { Context } from "context"; 2 - import { desc, eq } from "drizzle-orm"; 2 + import { and, desc, eq, inArray } from "drizzle-orm"; 3 3 import { Effect, pipe } from "effect"; 4 4 import type { Server } from "lexicon"; 5 5 import type { ScrobbleViewBasic } from "lexicon/types/app/rocksky/scrobble/defs"; ··· 11 11 import type { SelectUser } from "schema/users"; 12 12 13 13 export default function (server: Server, ctx: Context) { 14 - const getScrobbles = (params) => 14 + const getScrobbles = (params: QueryParams) => 15 15 pipe( 16 16 { params, ctx }, 17 17 retrieve, ··· 42 42 ctx: Context; 43 43 }): Effect.Effect<Scrobbles | undefined, Error> => { 44 44 return Effect.tryPromise({ 45 - try: () => 46 - ctx.db 45 + try: async () => { 46 + const baseQuery = ctx.db 47 47 .select() 48 48 .from(tables.scrobbles) 49 49 .leftJoin(tables.tracks, eq(tables.scrobbles.trackId, tables.tracks.id)) 50 - .leftJoin(tables.users, eq(tables.scrobbles.userId, tables.users.id)) 50 + .leftJoin(tables.users, eq(tables.scrobbles.userId, tables.users.id)); 51 + 52 + if (params.did && params.following) { 53 + const followedUsers = await ctx.db 54 + .select({ subjectDid: tables.follows.subject_did }) 55 + .from(tables.follows) 56 + .where(eq(tables.follows.follower_did, params.did)) 57 + .execute(); 58 + 59 + const followedDids = followedUsers.map((f) => f.subjectDid); 60 + 61 + if (followedDids.length > 0) { 62 + return baseQuery 63 + .where(inArray(tables.users.did, followedDids)) 64 + .orderBy(desc(tables.scrobbles.timestamp)) 65 + .offset(params.offset || 0) 66 + .limit(params.limit || 20) 67 + .execute(); 68 + } else { 69 + return []; 70 + } 71 + } 72 + 73 + return baseQuery 51 74 .orderBy(desc(tables.scrobbles.timestamp)) 52 75 .offset(params.offset || 0) 53 76 .limit(params.limit || 20) 54 - .execute(), 77 + .execute(); 78 + }, 55 79 56 80 catch: (error) => new Error(`Failed to retrieve scrobbles: ${error}`), 57 81 });