"use server"; import { AtpBaseClient } from "lexicons/api"; import { AppBskyActorDefs, Agent as BskyAgent } from "@atproto/api"; import { getIdentityData } from "actions/getIdentityData"; import { createOauthClient } from "src/atproto-oauth"; import { TID } from "@atproto/common"; import { supabaseServerClient } from "supabase/serverClient"; import { revalidatePath } from "next/cache"; import { AtUri } from "@atproto/syntax"; import { redirect } from "next/navigation"; import { encodeActionToSearchParam } from "app/api/oauth/[route]/afterSignInActions"; import { Json } from "supabase/database.types"; import { IdResolver } from "@atproto/identity"; import { Notification, pingIdentityToUpdateNotification, } from "src/notifications"; import { v7 } from "uuid"; let leafletFeedURI = "at://did:plc:btxrwcaeyodrap5mnjw2fvmz/app.bsky.feed.generator/subscribedPublications"; let idResolver = new IdResolver(); export async function subscribeToPublication( publication: string, redirectRoute?: string, ) { const oauthClient = await createOauthClient(); let identity = await getIdentityData(); if (!identity || !identity.atp_did) { return redirect( `/api/oauth/login?redirect_url=${redirectRoute}&action=${encodeActionToSearchParam({ action: "subscribe", publication })}`, ); } let credentialSession = await oauthClient.restore(identity.atp_did); let agent = new AtpBaseClient( credentialSession.fetchHandler.bind(credentialSession), ); let record = await agent.pub.leaflet.graph.subscription.create( { repo: credentialSession.did!, rkey: TID.nextStr() }, { publication, }, ); let { error } = await supabaseServerClient .from("publication_subscriptions") .insert({ uri: record.uri, record, publication, identity: credentialSession.did!, }); // Create notification for the publication owner let publicationOwner = new AtUri(publication).host; if (publicationOwner !== credentialSession.did) { let notification: Notification = { id: v7(), recipient: publicationOwner, data: { type: "subscribe", subscription_uri: record.uri, }, }; await supabaseServerClient.from("notifications").insert(notification); await pingIdentityToUpdateNotification(publicationOwner); } let bsky = new BskyAgent(credentialSession); let [prefs, profile, resolveDid] = await Promise.all([ bsky.app.bsky.actor.getPreferences(), bsky.app.bsky.actor.profile .get({ repo: credentialSession.did!, rkey: "self", }) .catch(), idResolver.did.resolve(credentialSession.did!), ]); if (!identity.bsky_profiles && profile.value) { await supabaseServerClient.from("bsky_profiles").insert({ did: identity.atp_did, record: profile.value as Json, handle: resolveDid?.alsoKnownAs?.[0]?.slice(5), }); } let savedFeeds = prefs.data.preferences.find( (pref) => pref.$type === "app.bsky.actor.defs#savedFeedsPrefV2", ) as AppBskyActorDefs.SavedFeedsPrefV2; revalidatePath("/lish/[did]/[publication]", "layout"); return { hasFeed: !!savedFeeds.items.find((feed) => feed.value === leafletFeedURI), }; } export async function unsubscribeToPublication(publication: string) { const oauthClient = await createOauthClient(); let identity = await getIdentityData(); if (!identity || !identity.atp_did) return; let credentialSession = await oauthClient.restore(identity.atp_did); let agent = new AtpBaseClient( credentialSession.fetchHandler.bind(credentialSession), ); let { data: existingSubscription } = await supabaseServerClient .from("publication_subscriptions") .select("*") .eq("identity", identity.atp_did) .eq("publication", publication) .single(); if (!existingSubscription) return; await agent.pub.leaflet.graph.subscription.delete({ repo: credentialSession.did!, rkey: new AtUri(existingSubscription.uri).rkey, }); await supabaseServerClient .from("publication_subscriptions") .delete() .eq("identity", identity.atp_did) .eq("publication", publication); revalidatePath("/lish/[did]/[publication]", "layout"); }