tangled
alpha
login
or
join now
leaflet.pub
/
leaflet
289
fork
atom
a tool for shared writing and social publishing
289
fork
atom
overview
issues
27
pulls
pipelines
add get profile posts rpc
awarm.space
1 week ago
3bbb4ddc
52258dbc
+100
-55
3 changed files
expand all
collapse all
unified
split
app
(home-pages)
p
[didOrHandle]
getProfilePosts.ts
supabase
database.types.ts
migrations
20260305000000_add_get_profile_posts_function.sql
+35
-55
app/(home-pages)/p/[didOrHandle]/getProfilePosts.ts
···
20
): Promise<{ posts: Post[]; nextCursor: Cursor | null }> {
21
const limit = 20;
22
23
-
let query = supabaseServerClient
24
-
.from("documents")
25
-
.select(
26
-
`*,
27
-
comments_on_documents(count),
28
-
document_mentions_in_bsky(count),
29
-
recommends_on_documents(count),
30
-
documents_in_publications(publications(*))`,
31
-
)
32
-
.like("uri", `at://${did}/%`)
33
-
.order("sort_date", { ascending: false })
34
-
.order("uri", { ascending: false })
35
-
.limit(limit);
36
37
-
if (cursor) {
38
-
query = query.or(
39
-
`sort_date.lt.${cursor.sort_date},and(sort_date.eq.${cursor.sort_date},uri.lt.${cursor.uri})`,
40
-
);
41
}
42
43
-
let [{ data: rawDocs }, { data: rawPubs }, { data: profile }] =
44
-
await Promise.all([
45
-
query,
46
-
supabaseServerClient
47
-
.from("publications")
48
-
.select("*")
49
-
.eq("identity_did", did),
50
-
supabaseServerClient
51
-
.from("bsky_profiles")
52
-
.select("handle")
53
-
.eq("did", did)
54
-
.single(),
55
-
]);
56
-
57
-
// Deduplicate records that may exist under both pub.leaflet and site.standard namespaces
58
-
const docs = deduplicateByUriOrdered(rawDocs || []);
59
-
const pubs = deduplicateByUriOrdered(rawPubs || []);
60
61
-
// Build a map of publications for quick lookup
62
-
let pubMap = new Map<string, NonNullable<typeof pubs>[number]>();
63
-
for (let pub of pubs || []) {
64
-
pubMap.set(pub.uri, pub);
65
-
}
66
-
67
-
// Transform data to Post[] format
68
let handle = profile?.handle ? `@${profile.handle}` : null;
69
let posts: Post[] = [];
70
71
-
for (let doc of docs || []) {
72
-
// Normalize records - filter out unrecognized formats
73
-
const normalizedData = normalizeDocumentRecord(doc.data, doc.uri);
74
if (!normalizedData) continue;
75
76
-
let pubFromDoc = doc.documents_in_publications?.[0]?.publications;
77
-
let pub = pubFromDoc ? pubMap.get(pubFromDoc.uri) || pubFromDoc : null;
0
78
79
let post: Post = {
80
author: handle,
81
documents: {
82
data: normalizedData,
83
-
uri: doc.uri,
84
-
sort_date: doc.sort_date,
85
-
comments_on_documents: doc.comments_on_documents,
86
-
document_mentions_in_bsky: doc.document_mentions_in_bsky,
87
-
recommends_on_documents: doc.recommends_on_documents,
88
},
89
};
90
91
-
if (pub) {
92
post.publication = {
93
-
href: getPublicationURL(pub),
94
-
pubRecord: normalizePublicationRecord(pub.record),
95
-
uri: pub.uri,
0
0
0
96
};
97
}
98
···
20
): Promise<{ posts: Post[]; nextCursor: Cursor | null }> {
21
const limit = 20;
22
23
+
let [{ data: rawFeed, error }, { data: profile }] = await Promise.all([
24
+
supabaseServerClient.rpc("get_profile_posts", {
25
+
p_did: did,
26
+
p_cursor_sort_date: cursor?.sort_date ?? null,
27
+
p_cursor_uri: cursor?.uri ?? null,
28
+
p_limit: limit,
29
+
}),
30
+
supabaseServerClient
31
+
.from("bsky_profiles")
32
+
.select("handle")
33
+
.eq("did", did)
34
+
.single(),
35
+
]);
36
37
+
if (error) {
38
+
console.error("[getProfilePosts] rpc error:", error);
39
+
return { posts: [], nextCursor: null };
0
40
}
41
42
+
let feed = deduplicateByUriOrdered(rawFeed || []);
43
+
if (feed.length === 0) return { posts: [], nextCursor: null };
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
44
0
0
0
0
0
0
0
45
let handle = profile?.handle ? `@${profile.handle}` : null;
46
let posts: Post[] = [];
47
48
+
for (let row of feed) {
49
+
const normalizedData = normalizeDocumentRecord(row.data, row.uri);
0
50
if (!normalizedData) continue;
51
52
+
const normalizedPubRecord = row.publication_record
53
+
? normalizePublicationRecord(row.publication_record)
54
+
: null;
55
56
let post: Post = {
57
author: handle,
58
documents: {
59
data: normalizedData,
60
+
uri: row.uri,
61
+
sort_date: row.sort_date,
62
+
comments_on_documents: [{ count: Number(row.comments_count) }],
63
+
document_mentions_in_bsky: [{ count: Number(row.mentions_count) }],
64
+
recommends_on_documents: [{ count: Number(row.recommends_count) }],
65
},
66
};
67
68
+
if (row.publication_uri) {
69
post.publication = {
70
+
href: getPublicationURL({
71
+
uri: row.publication_uri,
72
+
record: row.publication_record,
73
+
}),
74
+
pubRecord: normalizedPubRecord,
75
+
uri: row.publication_uri,
76
};
77
}
78
+19
supabase/database.types.ts
···
1360
like: unknown
1361
}[]
1362
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
1363
get_reader_feed: {
1364
Args: {
1365
p_identity: string
···
1360
like: unknown
1361
}[]
1362
}
1363
+
get_profile_posts: {
1364
+
Args: {
1365
+
p_did: string
1366
+
p_cursor_sort_date?: string | null
1367
+
p_cursor_uri?: string | null
1368
+
p_limit?: number
1369
+
}
1370
+
Returns: {
1371
+
uri: string
1372
+
data: Json
1373
+
sort_date: string
1374
+
comments_count: number
1375
+
mentions_count: number
1376
+
recommends_count: number
1377
+
publication_uri: string
1378
+
publication_record: Json
1379
+
publication_name: string
1380
+
}[]
1381
+
}
1382
get_reader_feed: {
1383
Args: {
1384
p_identity: string
+46
supabase/migrations/20260305000000_add_get_profile_posts_function.sql
···
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
1
+
CREATE OR REPLACE FUNCTION get_profile_posts(
2
+
p_did text,
3
+
p_cursor_sort_date timestamptz DEFAULT NULL,
4
+
p_cursor_uri text DEFAULT NULL,
5
+
p_limit int DEFAULT 20
6
+
)
7
+
RETURNS TABLE (
8
+
uri text,
9
+
data jsonb,
10
+
sort_date timestamptz,
11
+
comments_count bigint,
12
+
mentions_count bigint,
13
+
recommends_count bigint,
14
+
publication_uri text,
15
+
publication_record jsonb,
16
+
publication_name text
17
+
)
18
+
LANGUAGE sql STABLE
19
+
AS $$
20
+
SELECT
21
+
d.uri,
22
+
d.data,
23
+
d.sort_date,
24
+
(SELECT count(*) FROM comments_on_documents c WHERE c.document = d.uri),
25
+
(SELECT count(*) FROM document_mentions_in_bsky m WHERE m.document = d.uri),
26
+
(SELECT count(*) FROM recommends_on_documents r WHERE r.document = d.uri),
27
+
pub.uri,
28
+
pub.record,
29
+
pub.name
30
+
FROM documents d
31
+
LEFT JOIN LATERAL (
32
+
SELECT p.uri, p.record, p.name
33
+
FROM documents_in_publications dip
34
+
JOIN publications p ON p.uri = dip.publication
35
+
WHERE dip.document = d.uri
36
+
LIMIT 1
37
+
) pub ON true
38
+
WHERE d.uri LIKE 'at://' || p_did || '/%'
39
+
AND (
40
+
p_cursor_sort_date IS NULL
41
+
OR d.sort_date < p_cursor_sort_date
42
+
OR (d.sort_date = p_cursor_sort_date AND d.uri < p_cursor_uri)
43
+
)
44
+
ORDER BY d.sort_date DESC, d.uri DESC
45
+
LIMIT p_limit;
46
+
$$;