···1-import { TealContext } from "@/ctx";
2-import { artists, db, plays, playToArtists } from "@teal/db";
3-import { eq, and, lt, desc, sql } from "drizzle-orm";
4-import { OutputSchema } from "@teal/lexicons/src/types/fm/teal/alpha/feed/getActorFeed";
56export default async function getActorFeed(c: TealContext) {
7 const params = c.req.query();
8- if (!params.authorDid) {
9- throw new Error("authorDid is required");
10 }
1112 let limit = 20;
1314 if (params.limit) {
15 limit = Number(params.limit);
16- if (limit > 50) throw new Error("Limit is over max allowed.");
17 }
1819 // 'and' is here for typing reasons
20- let whereClause = and(eq(plays.did, params.authorDid));
2122 // Add cursor pagination if provided
23 if (params.cursor) {
···30 const cursorPlay = cursorResult[0]?.playedTime;
3132 if (!cursorPlay) {
33- throw new Error("Cursor not found");
34 }
3536 whereClause = and(whereClause, lt(plays.playedTime, cursorPlay as any));
···53 submissionClientAgent: plays.submissionClientAgent,
54 musicServiceBaseDomain: plays.musicServiceBaseDomain,
55 artists: sql<Array<{ mbid: string; name: string }>>`
56-COALESCE
57-array_agg(
58-CASE WHEN ${playToArtists.artistMbid} IS NOT NULL THEN
59- jsonb_build_object(
60- 'mbid', ${playToArtists.artistMbid},
61- 'name', ${playToArtists.artistName}
62- )
63-END
64-) FILTER (WHERE ${playToArtists.artistName} IS NOT NULL),
65-ARRAY[]::jsonb[]
66-)
67-`.as("artists"),
68 })
69 .from(plays)
70 .leftJoin(playToArtists, sql`${plays.uri} = ${playToArtists.playUri}`)
···88 )
89 .orderBy(desc(plays.playedTime))
90 .limit(limit);
91-92- if (playRes.length === 0) {
93- throw new Error("Play not found");
94- }
9596 return {
097 plays: playRes.map(
98 ({
99- uri,
100- did: authorDid,
101- processedTime: createdAt,
102- processedTime: indexedAt,
103 trackName,
104- cid: trackMbId,
105 recordingMbid,
106 duration,
107- artists,
108 releaseName,
109 releaseMbid,
110 isrc,
···112 musicServiceBaseDomain,
113 submissionClientAgent,
114 playedTime,
0115 }) => ({
116- uri,
117- authorDid,
118- createdAt: createdAt?.toISOString(),
119- indexedAt: indexedAt?.toISOString(),
120- trackName,
121- trackMbId,
122- recordingMbId: recordingMbid,
123- duration,
124- artistNames: artists.map((artist) => artist.name),
125- artistMbIds: artists.map((artist) => artist.mbid),
126- releaseName,
127- releaseMbId: releaseMbid,
128- isrc,
129- originUrl,
130- musicServiceBaseDomain,
131- submissionClientAgent,
132- playedTime: playedTime?.toISOString(),
000000133 }),
134 ),
0135 } as OutputSchema;
136}
···1+import { TealContext } from '@/ctx';
2+import { artists, db, plays, playToArtists } from '@teal/db';
3+import { eq, and, lt, desc, sql } from 'drizzle-orm';
4+import { OutputSchema } from '@teal/lexicons/src/types/fm/teal/alpha/feed/getActorFeed';
56export default async function getActorFeed(c: TealContext) {
7 const params = c.req.query();
8+ if (!params.authorDID) {
9+ throw new Error('authorDID is required');
10 }
1112 let limit = 20;
1314 if (params.limit) {
15 limit = Number(params.limit);
16+ if (limit > 50) throw new Error('Limit is over max allowed.');
17 }
1819 // 'and' is here for typing reasons
20+ let whereClause = and(eq(plays.did, params.authorDID));
2122 // Add cursor pagination if provided
23 if (params.cursor) {
···30 const cursorPlay = cursorResult[0]?.playedTime;
3132 if (!cursorPlay) {
33+ throw new Error('Cursor not found');
34 }
3536 whereClause = and(whereClause, lt(plays.playedTime, cursorPlay as any));
···53 submissionClientAgent: plays.submissionClientAgent,
54 musicServiceBaseDomain: plays.musicServiceBaseDomain,
55 artists: sql<Array<{ mbid: string; name: string }>>`
56+ COALESCE(
57+ (
58+ SELECT jsonb_agg(jsonb_build_object('mbid', pa.artist_mbid, 'name', pa.artist_name))
59+ FROM ${playToArtists} pa
60+ WHERE pa.play_uri = ${plays.uri}
61+ AND pa.artist_mbid IS NOT NULL
62+ AND pa.artist_name IS NOT NULL -- Ensure both are non-null
63+ ),
64+ '[]'::jsonb -- Correct empty JSONB array literal
65+ )`.as('artists'),
0066 })
67 .from(plays)
68 .leftJoin(playToArtists, sql`${plays.uri} = ${playToArtists.playUri}`)
···86 )
87 .orderBy(desc(plays.playedTime))
88 .limit(limit);
89+ const cursor =
90+ playRes.length === limit ? playRes[playRes.length - 1]?.uri : undefined;
009192 return {
93+ cursor: cursor ?? undefined, // Ensure cursor itself can be undefined
94 plays: playRes.map(
95 ({
96+ // Destructure fields from the DB result
00097 trackName,
98+ cid: trackMbId, // Note the alias was used here in the DB query select
99 recordingMbid,
100 duration,
101+ artists, // This is guaranteed to be an array '[]' if no artists, due to COALESCE
102 releaseName,
103 releaseMbid,
104 isrc,
···106 musicServiceBaseDomain,
107 submissionClientAgent,
108 playedTime,
109+ // Other destructured fields like uri, did, etc. are not directly used here by name
110 }) => ({
111+ // Apply '?? undefined' to each potentially nullable/undefined scalar field
112+ trackName: trackName ?? undefined,
113+ trackMbId: trackMbId ?? undefined,
114+ recordingMbId: recordingMbid ?? undefined,
115+ duration: duration ?? undefined,
116+117+ // For arrays derived from a guaranteed array, map is safe.
118+ // The SQL query ensures `artists` is '[]'::jsonb if empty.
119+ // The SQL query also ensures artist.name/mbid are NOT NULL within the jsonb_agg
120+ artistNames: artists.map((artist) => artist.name), // Will be [] if artists is []
121+ artistMbIds: artists.map((artist) => artist.mbid), // Will be [] if artists is []
122+123+ releaseName: releaseName ?? undefined,
124+ releaseMbId: releaseMbid ?? undefined,
125+ isrc: isrc ?? undefined,
126+ originUrl: originUrl ?? undefined,
127+ musicServiceBaseDomain: musicServiceBaseDomain ?? undefined,
128+ submissionClientAgent: submissionClientAgent ?? undefined,
129+130+ // playedTime specific handling: convert to ISO string if exists, else undefined
131+ playedTime: playedTime ? playedTime.toISOString() : undefined,
132+ // Alternative using optional chaining (effectively the same)
133+ // playedTime: playedTime?.toISOString(),
134 }),
135 ),
136+ // Explicitly cast to OutputSchema. Make sure OutputSchema allows undefined for these fields.
137 } as OutputSchema;
138}