a tool for shared writing and social publishing
1"use server";
2
3import { getIdentityData } from "actions/getIdentityData";
4import { supabaseServerClient } from "supabase/serverClient";
5import type {
6 NormalizedDocument,
7 NormalizedPublication,
8} from "src/utils/normalizeRecords";
9import { enrichDocumentToPost } from "./enrichPost";
10
11export type Cursor = {
12 timestamp: string;
13 uri: string;
14};
15
16export async function getReaderFeed(
17 cursor?: Cursor | null,
18): Promise<{ posts: Post[]; nextCursor: Cursor | null }> {
19 let auth_res = await getIdentityData();
20 if (!auth_res?.atp_did) return { posts: [], nextCursor: null };
21
22 const { data: rawFeed, error } = await supabaseServerClient.rpc(
23 "get_reader_feed",
24 {
25 p_identity: auth_res.atp_did,
26 p_cursor_timestamp: cursor?.timestamp,
27 p_cursor_uri: cursor?.uri,
28 p_limit: 25,
29 },
30 );
31 if (error) {
32 console.error("[getReaderFeed] rpc error:", error);
33 return { posts: [], nextCursor: null };
34 }
35
36 if (rawFeed.length === 0) return { posts: [], nextCursor: null };
37
38 // Reshape rows to match the structure enrichDocumentToPost expects
39 const feed = rawFeed.map((row: any) => ({
40 uri: row.uri,
41 data: row.data,
42 sort_date: row.sort_date,
43 comments_on_documents: [{ count: Number(row.comments_count) }],
44 document_mentions_in_bsky: [{ count: Number(row.mentions_count) }],
45 recommends_on_documents: [{ count: Number(row.recommends_count) }],
46 documents_in_publications: row.publication_uri
47 ? [
48 {
49 publications: {
50 uri: row.publication_uri,
51 record: row.publication_record,
52 name: row.publication_name,
53 },
54 },
55 ]
56 : [],
57 }));
58
59 let posts = (
60 await Promise.all(feed.map((post) => enrichDocumentToPost(post as any)))
61 ).filter((post): post is Post => post !== null);
62 if (feed.length > 0 && posts.length !== feed.length) {
63 console.log(`[getReaderFeed] ${feed.length - posts.length}/${feed.length} posts dropped during enrichment`);
64 }
65
66 const nextCursor =
67 posts.length > 0
68 ? {
69 timestamp: posts[posts.length - 1].documents.sort_date,
70 uri: posts[posts.length - 1].documents.uri,
71 }
72 : null;
73
74 return {
75 posts,
76 nextCursor,
77 };
78}
79
80export type Post = {
81 author: string | null;
82 publication?: {
83 href: string;
84 pubRecord: NormalizedPublication | null;
85 uri: string;
86 };
87 documents: {
88 data: NormalizedDocument | null;
89 uri: string;
90 sort_date: string;
91 comments_on_documents: { count: number }[] | undefined;
92 document_mentions_in_bsky: { count: number }[] | undefined;
93 recommends_on_documents: { count: number }[] | undefined;
94 mentionsCount?: number;
95 };
96};