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

Add artist tags and include in getActorArtists

Add optional tags array to artist lexicon, pkl and TypeScript defs.
Populate tags by querying artists.genres from the DB and merging
them into the analytics response in getActorArtists.

+71 -14
+12
apps/api/lexicons/artist/defs.json
··· 35 35 "type": "integer", 36 36 "description": "The number of unique listeners who have played the artist.", 37 37 "minimum": 0 38 + }, 39 + "tags": { 40 + "type": "array", 41 + "items": { 42 + "type": "string" 43 + } 38 44 } 39 45 } 40 46 }, ··· 71 77 "type": "integer", 72 78 "description": "The number of unique listeners who have played the artist.", 73 79 "minimum": 0 80 + }, 81 + "tags": { 82 + "type": "array", 83 + "items": { 84 + "type": "string" 85 + } 74 86 } 75 87 } 76 88 },
+13 -3
apps/api/pkl/defs/artist/defs.pkl
··· 1 - amends "../../schema/lexicon.pkl" 1 + amends "../../schema/lexicon.pkl" 2 2 3 3 lexicon = 1 4 4 id = "app.rocksky.artist.defs" ··· 44 44 minimum = 0 45 45 } 46 46 47 + ["tags"] = new Array { 48 + type = "array" 49 + items = new StringType { 50 + type = "string" 51 + } 52 + } 47 53 } 48 54 } 49 55 ··· 88 94 minimum = 0 89 95 } 90 96 97 + ["tags"] = new Array { 98 + type = "array" 99 + items = new StringType { 100 + type = "string" 101 + } 102 + } 91 103 } 92 104 } 93 105 ··· 110 122 description = "The number of times the song has been played." 111 123 minimum = 0 112 124 } 113 - 114 125 } 115 126 } 116 127 ··· 158 169 description = "The rank of the listener among all listeners of the artist." 159 170 minimum = 1 160 171 } 161 - 162 172 } 163 173 } 164 174
+12
apps/api/src/lexicon/lexicons.ts
··· 1835 1835 "The number of unique listeners who have played the artist.", 1836 1836 minimum: 0, 1837 1837 }, 1838 + tags: { 1839 + type: "array", 1840 + items: { 1841 + type: "string", 1842 + }, 1843 + }, 1838 1844 }, 1839 1845 }, 1840 1846 artistViewDetailed: { ··· 1871 1877 description: 1872 1878 "The number of unique listeners who have played the artist.", 1873 1879 minimum: 0, 1880 + }, 1881 + tags: { 1882 + type: "array", 1883 + items: { 1884 + type: "string", 1885 + }, 1874 1886 }, 1875 1887 }, 1876 1888 },
+2
apps/api/src/lexicon/types/app/rocksky/artist/defs.ts
··· 21 21 playCount?: number; 22 22 /** The number of unique listeners who have played the artist. */ 23 23 uniqueListeners?: number; 24 + tags?: string[]; 24 25 [k: string]: unknown; 25 26 } 26 27 ··· 51 52 playCount?: number; 52 53 /** The number of unique listeners who have played the artist. */ 53 54 uniqueListeners?: number; 55 + tags?: string[]; 54 56 [k: string]: unknown; 55 57 } 56 58
+30 -9
apps/api/src/xrpc/app/rocksky/actor/getActorArtists.ts
··· 5 5 import type { QueryParams } from "lexicon/types/app/rocksky/actor/getActorArtists"; 6 6 import type { ArtistViewBasic } from "lexicon/types/app/rocksky/artist/defs"; 7 7 import { deepCamelCaseKeys } from "lib"; 8 + import { inArray } from "drizzle-orm"; 9 + import tables from "schema"; 10 + import { indexBy, prop } from "ramda"; 8 11 9 12 export default function (server: Server, ctx: Context) { 10 13 const getActorArtists = (params: QueryParams) => ··· 38 41 ctx: Context; 39 42 }): Effect.Effect<{ data: Artist[] }, Error> => { 40 43 return Effect.tryPromise({ 41 - try: () => 42 - ctx.analytics.post("library.getTopArtists", { 43 - user_did: params.did, 44 - pagination: { 45 - skip: params.offset || 0, 46 - take: params.limit || 10, 44 + try: async () => { 45 + const response = await ctx.analytics.post<Artist[]>( 46 + "library.getTopArtists", 47 + { 48 + user_did: params.did, 49 + pagination: { 50 + skip: params.offset || 0, 51 + take: params.limit || 10, 52 + }, 53 + start_date: params.startDate, 54 + end_date: params.endDate, 47 55 }, 48 - start_date: params.startDate, 49 - end_date: params.endDate, 50 - }), 56 + ); 57 + const ids = response.data.map((x) => x.id); 58 + const artists = await ctx.db 59 + .select() 60 + .from(tables.artists) 61 + .where(inArray(tables.artists.id, ids)) 62 + .execute(); 63 + const indexedArtists = indexBy(prop("id"), artists); 64 + return { 65 + data: response.data.map((x) => ({ 66 + ...x, 67 + tags: indexedArtists[x.id]?.genres, 68 + })), 69 + }; 70 + }, 51 71 catch: (error) => new Error(`Failed to retrieve artists: ${error}`), 52 72 }); 53 73 }; ··· 68 88 sha256: string; 69 89 unique_listeners: number; 70 90 uri: string; 91 + tags?: string[]; 71 92 };
+1 -1
apps/api/src/xrpc/app/rocksky/feed/getFeed.ts
··· 13 13 import axios from "axios"; 14 14 import type { HandlerAuth } from "@atproto/xrpc-server"; 15 15 import { env } from "lib/env"; 16 - import { SelectArtist } from "schema/artists"; 16 + import type { SelectArtist } from "schema/artists"; 17 17 18 18 export default function (server: Server, ctx: Context) { 19 19 const getFeed = (params: QueryParams, auth: HandlerAuth) =>
+1 -1
apps/api/src/xrpc/app/rocksky/scrobble/getScrobbles.ts
··· 10 10 import type { SelectScrobble } from "schema/scrobbles"; 11 11 import type { SelectTrack } from "schema/tracks"; 12 12 import type { SelectUser } from "schema/users"; 13 - import { SelectArtist } from "schema/artists"; 13 + import type { SelectArtist } from "schema/artists"; 14 14 15 15 export default function (server: Server, ctx: Context) { 16 16 const getScrobbles = (params: QueryParams) =>