a tool for shared writing and social publishing
at update/delete-leaflets 110 lines 3.1 kB view raw
1"use server"; 2import { TID } from "@atproto/common"; 3import { AtpBaseClient, PubLeafletPublication } from "lexicons/api"; 4import { createOauthClient } from "src/atproto-oauth"; 5import { getIdentityData } from "actions/getIdentityData"; 6import { supabaseServerClient } from "supabase/serverClient"; 7import { Un$Typed } from "@atproto/api"; 8import { Json } from "supabase/database.types"; 9import { Vercel } from "@vercel/sdk"; 10import { isProductionDomain } from "src/utils/isProductionDeployment"; 11import { string } from "zod"; 12 13const VERCEL_TOKEN = process.env.VERCEL_TOKEN; 14const vercel = new Vercel({ 15 bearerToken: VERCEL_TOKEN, 16}); 17let subdomainValidator = string() 18 .min(3) 19 .max(63) 20 .regex(/^[a-z0-9-]+$/); 21export async function createPublication({ 22 name, 23 description, 24 iconFile, 25 subdomain, 26 preferences, 27}: { 28 name: string; 29 description: string; 30 iconFile: File | null; 31 subdomain: string; 32 preferences: Omit<PubLeafletPublication.Preferences, "$type">; 33}) { 34 let isSubdomainValid = subdomainValidator.safeParse(subdomain); 35 if (!isSubdomainValid.success) { 36 return { success: false }; 37 } 38 const oauthClient = await createOauthClient(); 39 let identity = await getIdentityData(); 40 if (!identity || !identity.atp_did) return; 41 42 let domain = `${subdomain}.leaflet.pub`; 43 44 let credentialSession = await oauthClient.restore(identity.atp_did); 45 let agent = new AtpBaseClient( 46 credentialSession.fetchHandler.bind(credentialSession), 47 ); 48 let record: Un$Typed<PubLeafletPublication.Record> = { 49 name, 50 base_path: domain, 51 preferences, 52 }; 53 54 if (description) { 55 record.description = description; 56 } 57 58 // Upload the icon if provided 59 if (iconFile && iconFile.size > 0) { 60 const buffer = await iconFile.arrayBuffer(); 61 const uploadResult = await agent.com.atproto.repo.uploadBlob( 62 new Uint8Array(buffer), 63 { encoding: iconFile.type }, 64 ); 65 66 if (uploadResult.data.blob) { 67 record.icon = uploadResult.data.blob; 68 } 69 } 70 71 let result = await agent.pub.leaflet.publication.create( 72 { repo: credentialSession.did!, rkey: TID.nextStr(), validate: false }, 73 record, 74 ); 75 76 //optimistically write to our db! 77 let { data: publication } = await supabaseServerClient 78 .from("publications") 79 .upsert({ 80 uri: result.uri, 81 identity_did: credentialSession.did!, 82 name: record.name, 83 record: { 84 ...record, 85 $type: "pub.leaflet.publication", 86 } as unknown as Json, 87 }) 88 .select() 89 .single(); 90 91 // Create the custom domain 92 if (isProductionDomain()) { 93 await vercel.projects.addProjectDomain({ 94 idOrName: "prj_9jX4tmYCISnm176frFxk07fF74kG", 95 teamId: "team_42xaJiZMTw9Sr7i0DcLTae9d", 96 requestBody: { 97 name: domain, 98 }, 99 }); 100 } 101 await supabaseServerClient 102 .from("custom_domains") 103 .insert({ domain, confirmed: true, identity: null }); 104 105 await supabaseServerClient 106 .from("publication_domains") 107 .insert({ domain, publication: result.uri, identity: identity.atp_did }); 108 109 return { success: true, publication }; 110}