a tool for shared writing and social publishing
1"use server";
2
3import { AtpAgent } from "@atproto/api";
4import { ProfileViewDetailed } from "@atproto/api/dist/client/types/app/bsky/actor/defs";
5import { getIdentityData } from "actions/getIdentityData";
6import { Json } from "supabase/database.types";
7import { supabaseServerClient } from "supabase/serverClient";
8import { idResolver } from "./idResolver";
9import { Cursor } from "./getReaderFeed";
10import {
11 normalizePublicationRecord,
12 type NormalizedPublication,
13} from "src/utils/normalizeRecords";
14
15export async function getSubscriptions(
16 did?: string | null,
17 cursor?: Cursor | null,
18): Promise<{
19 nextCursor: null | Cursor;
20 subscriptions: PublicationSubscription[];
21}> {
22 // If no DID provided, use logged-in user's DID
23 let identity = did;
24 if (!identity) {
25 const auth_res = await getIdentityData();
26 if (!auth_res?.atp_did) return { subscriptions: [], nextCursor: null };
27 identity = auth_res.atp_did;
28 }
29
30 let query = supabaseServerClient
31 .from("publication_subscriptions")
32 .select(
33 `*, publications(*, publication_subscriptions(*), documents_in_publications(*, documents(*)))`,
34 )
35 .order(`created_at`, { ascending: false })
36 .order(`uri`, { ascending: false })
37 .order("documents(sort_date)", {
38 ascending: false,
39 referencedTable: "publications.documents_in_publications",
40 })
41 .limit(1, { referencedTable: "publications.documents_in_publications" })
42 .limit(25)
43 .eq("identity", identity);
44
45 if (cursor) {
46 query = query.or(
47 `created_at.lt.${cursor.timestamp},and(created_at.eq.${cursor.timestamp},uri.lt.${cursor.uri})`,
48 );
49 }
50 let { data: pubs, error } = await query;
51
52 const hydratedSubscriptions = (
53 await Promise.all(
54 pubs?.map(async (pub) => {
55 const normalizedRecord = normalizePublicationRecord(
56 pub.publications?.record,
57 );
58 if (!normalizedRecord) return null;
59 let id = await idResolver.did.resolve(pub.publications?.identity_did!);
60 return {
61 ...pub.publications!,
62 record: normalizedRecord,
63 authorProfile: id?.alsoKnownAs?.[0]
64 ? { handle: `@${id.alsoKnownAs[0].slice(5)}` }
65 : undefined,
66 } as PublicationSubscription;
67 }) || [],
68 )
69 ).filter((sub): sub is PublicationSubscription => sub !== null);
70 const nextCursor =
71 pubs && pubs.length > 0
72 ? {
73 timestamp: pubs[pubs.length - 1].created_at,
74 uri: pubs[pubs.length - 1].uri,
75 }
76 : null;
77
78 return {
79 subscriptions: hydratedSubscriptions,
80 nextCursor,
81 };
82}
83
84export type PublicationSubscription = {
85 authorProfile?: { handle: string };
86 record: NormalizedPublication;
87 publication_subscriptions: { identity: string }[];
88 uri: string;
89 documents_in_publications: {
90 documents: { data?: Json; sort_date: string } | null;
91 }[];
92};