a tool for shared writing and social publishing
at feature/reader 102 lines 3.6 kB view raw
1"use server"; 2 3import { AtpBaseClient } from "lexicons/api"; 4import { AppBskyActorDefs, Agent as BskyAgent } from "@atproto/api"; 5import { getIdentityData } from "actions/getIdentityData"; 6import { createOauthClient } from "src/atproto-oauth"; 7import { TID } from "@atproto/common"; 8import { supabaseServerClient } from "supabase/serverClient"; 9import { revalidatePath } from "next/cache"; 10import { AtUri } from "@atproto/syntax"; 11import { redirect } from "next/navigation"; 12import { encodeActionToSearchParam } from "app/api/oauth/[route]/afterSignInActions"; 13import { Json } from "supabase/database.types"; 14import { IdResolver } from "@atproto/identity"; 15 16let leafletFeedURI = 17 "at://did:plc:btxrwcaeyodrap5mnjw2fvmz/app.bsky.feed.generator/subscribedPublications"; 18let idResolver = new IdResolver(); 19export async function subscribeToPublication( 20 publication: string, 21 redirectRoute?: string, 22) { 23 const oauthClient = await createOauthClient(); 24 let identity = await getIdentityData(); 25 if (!identity || !identity.atp_did) { 26 return redirect( 27 `/api/oauth/login?redirect_url=${redirectRoute}&action=${encodeActionToSearchParam({ action: "subscribe", publication })}`, 28 ); 29 } 30 31 let credentialSession = await oauthClient.restore(identity.atp_did); 32 let agent = new AtpBaseClient( 33 credentialSession.fetchHandler.bind(credentialSession), 34 ); 35 let record = await agent.pub.leaflet.graph.subscription.create( 36 { repo: credentialSession.did!, rkey: TID.nextStr() }, 37 { 38 publication, 39 }, 40 ); 41 let { error } = await supabaseServerClient 42 .from("publication_subscriptions") 43 .insert({ 44 uri: record.uri, 45 record, 46 publication, 47 identity: credentialSession.did!, 48 }); 49 let bsky = new BskyAgent(credentialSession); 50 let [prefs, profile, resolveDid] = await Promise.all([ 51 bsky.app.bsky.actor.getPreferences(), 52 bsky.app.bsky.actor.profile 53 .get({ 54 repo: credentialSession.did!, 55 rkey: "self", 56 }) 57 .catch(), 58 idResolver.did.resolve(credentialSession.did!), 59 ]); 60 if (!identity.bsky_profiles && profile.value) { 61 await supabaseServerClient.from("bsky_profiles").insert({ 62 did: identity.atp_did, 63 record: profile.value as Json, 64 handle: resolveDid?.alsoKnownAs?.[0]?.slice(5), 65 }); 66 } 67 let savedFeeds = prefs.data.preferences.find( 68 (pref) => pref.$type === "app.bsky.actor.defs#savedFeedsPrefV2", 69 ) as AppBskyActorDefs.SavedFeedsPrefV2; 70 revalidatePath("/lish/[did]/[publication]", "layout"); 71 return { 72 hasFeed: !!savedFeeds.items.find((feed) => feed.value === leafletFeedURI), 73 }; 74} 75 76export async function unsubscribeToPublication(publication: string) { 77 const oauthClient = await createOauthClient(); 78 let identity = await getIdentityData(); 79 if (!identity || !identity.atp_did) return; 80 81 let credentialSession = await oauthClient.restore(identity.atp_did); 82 let agent = new AtpBaseClient( 83 credentialSession.fetchHandler.bind(credentialSession), 84 ); 85 let { data: existingSubscription } = await supabaseServerClient 86 .from("publication_subscriptions") 87 .select("*") 88 .eq("identity", identity.atp_did) 89 .eq("publication", publication) 90 .single(); 91 if (!existingSubscription) return; 92 await agent.pub.leaflet.graph.subscription.delete({ 93 repo: credentialSession.did!, 94 rkey: new AtUri(existingSubscription.uri).rkey, 95 }); 96 await supabaseServerClient 97 .from("publication_subscriptions") 98 .delete() 99 .eq("identity", identity.atp_did) 100 .eq("publication", publication); 101 revalidatePath("/lish/[did]/[publication]", "layout"); 102}