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