a tool for shared writing and social publishing
at update/delete-leaflets 123 lines 4.2 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"; 15import { 16 Notification, 17 pingIdentityToUpdateNotification, 18} from "src/notifications"; 19import { v7 } from "uuid"; 20 21let leafletFeedURI = 22 "at://did:plc:btxrwcaeyodrap5mnjw2fvmz/app.bsky.feed.generator/subscribedPublications"; 23let idResolver = new IdResolver(); 24export async function subscribeToPublication( 25 publication: string, 26 redirectRoute?: string, 27) { 28 const oauthClient = await createOauthClient(); 29 let identity = await getIdentityData(); 30 if (!identity || !identity.atp_did) { 31 return redirect( 32 `/api/oauth/login?redirect_url=${redirectRoute}&action=${encodeActionToSearchParam({ action: "subscribe", publication })}`, 33 ); 34 } 35 36 let credentialSession = await oauthClient.restore(identity.atp_did); 37 let agent = new AtpBaseClient( 38 credentialSession.fetchHandler.bind(credentialSession), 39 ); 40 let record = await agent.pub.leaflet.graph.subscription.create( 41 { repo: credentialSession.did!, rkey: TID.nextStr() }, 42 { 43 publication, 44 }, 45 ); 46 let { error } = await supabaseServerClient 47 .from("publication_subscriptions") 48 .insert({ 49 uri: record.uri, 50 record, 51 publication, 52 identity: credentialSession.did!, 53 }); 54 55 // Create notification for the publication owner 56 let publicationOwner = new AtUri(publication).host; 57 if (publicationOwner !== credentialSession.did) { 58 let notification: Notification = { 59 id: v7(), 60 recipient: publicationOwner, 61 data: { 62 type: "subscribe", 63 subscription_uri: record.uri, 64 }, 65 }; 66 await supabaseServerClient.from("notifications").insert(notification); 67 await pingIdentityToUpdateNotification(publicationOwner); 68 } 69 70 let bsky = new BskyAgent(credentialSession); 71 let [prefs, profile, resolveDid] = await Promise.all([ 72 bsky.app.bsky.actor.getPreferences(), 73 bsky.app.bsky.actor.profile 74 .get({ 75 repo: credentialSession.did!, 76 rkey: "self", 77 }) 78 .catch(), 79 idResolver.did.resolve(credentialSession.did!), 80 ]); 81 if (!identity.bsky_profiles && profile.value) { 82 await supabaseServerClient.from("bsky_profiles").insert({ 83 did: identity.atp_did, 84 record: profile.value as Json, 85 handle: resolveDid?.alsoKnownAs?.[0]?.slice(5), 86 }); 87 } 88 let savedFeeds = prefs.data.preferences.find( 89 (pref) => pref.$type === "app.bsky.actor.defs#savedFeedsPrefV2", 90 ) as AppBskyActorDefs.SavedFeedsPrefV2; 91 revalidatePath("/lish/[did]/[publication]", "layout"); 92 return { 93 hasFeed: !!savedFeeds.items.find((feed) => feed.value === leafletFeedURI), 94 }; 95} 96 97export async function unsubscribeToPublication(publication: string) { 98 const oauthClient = await createOauthClient(); 99 let identity = await getIdentityData(); 100 if (!identity || !identity.atp_did) return; 101 102 let credentialSession = await oauthClient.restore(identity.atp_did); 103 let agent = new AtpBaseClient( 104 credentialSession.fetchHandler.bind(credentialSession), 105 ); 106 let { data: existingSubscription } = await supabaseServerClient 107 .from("publication_subscriptions") 108 .select("*") 109 .eq("identity", identity.atp_did) 110 .eq("publication", publication) 111 .single(); 112 if (!existingSubscription) return; 113 await agent.pub.leaflet.graph.subscription.delete({ 114 repo: credentialSession.did!, 115 rkey: new AtUri(existingSubscription.uri).rkey, 116 }); 117 await supabaseServerClient 118 .from("publication_subscriptions") 119 .delete() 120 .eq("identity", identity.atp_did) 121 .eq("publication", publication); 122 revalidatePath("/lish/[did]/[publication]", "layout"); 123}