a tool for shared writing and social publishing
at update/reader 215 lines 6.4 kB view raw
1import { NextRequest, NextResponse } from "next/server"; 2import { AtpAgent, AtUri } from "@atproto/api"; 3import { DidResolver } from "@atproto/identity"; 4import { 5 PubLeafletDocument, 6 PubLeafletPublication, 7 PubLeafletPagesLinearDocument, 8 PubLeafletPagesCanvas, 9} from "lexicons/api"; 10 11const didResolver = new DidResolver({}); 12 13export async function GET(request: NextRequest) { 14 try { 15 const atUriString = request.nextUrl.searchParams.get("uri"); 16 17 if (!atUriString) { 18 return NextResponse.json( 19 { 20 success: false, 21 error: "Missing uri parameter", 22 }, 23 { status: 400 }, 24 ); 25 } 26 27 const uri = new AtUri(atUriString); 28 29 // Only allow document and publication collections 30 if ( 31 uri.collection !== "pub.leaflet.document" && 32 uri.collection !== "pub.leaflet.publication" 33 ) { 34 return NextResponse.json( 35 { 36 success: false, 37 error: 38 "Unsupported collection type. Must be pub.leaflet.document or pub.leaflet.publication", 39 }, 40 { status: 400 }, 41 ); 42 } 43 44 // Resolve DID to get service endpoint 45 const did = await didResolver.resolve(uri.host); 46 const service = did?.service?.[0]; 47 48 if (!service) { 49 return NextResponse.json( 50 { 51 success: false, 52 error: "Could not resolve DID service endpoint", 53 }, 54 { status: 404 }, 55 ); 56 } 57 58 // Fetch the record from AT Protocol 59 const agent = new AtpAgent({ service: service.serviceEndpoint as string }); 60 61 let recordResponse; 62 try { 63 recordResponse = await agent.com.atproto.repo.getRecord({ 64 repo: uri.host, 65 collection: uri.collection, 66 rkey: uri.rkey, 67 }); 68 } catch (e) { 69 return NextResponse.json( 70 { 71 success: false, 72 error: "Record not found", 73 }, 74 { status: 404 }, 75 ); 76 } 77 78 // Validate based on collection type 79 if (uri.collection === "pub.leaflet.document") { 80 const result = PubLeafletDocument.validateRecord( 81 recordResponse.data.value, 82 ); 83 if (result.success) { 84 return NextResponse.json({ 85 success: true, 86 collection: uri.collection, 87 record: result.value, 88 }); 89 } else { 90 // Detailed validation: validate pages and blocks individually 91 const record = recordResponse.data.value as { 92 pages?: Array<{ $type?: string; blocks?: Array<{ block?: unknown }> }>; 93 }; 94 const pageErrors: Array<{ 95 pageIndex: number; 96 pageType: string; 97 error?: unknown; 98 blockErrors?: Array<{ 99 blockIndex: number; 100 blockType: string; 101 error: unknown; 102 block: unknown; 103 }>; 104 }> = []; 105 106 if (record.pages && Array.isArray(record.pages)) { 107 for (let pageIndex = 0; pageIndex < record.pages.length; pageIndex++) { 108 const page = record.pages[pageIndex]; 109 const pageType = page?.$type || "unknown"; 110 111 // Validate page based on type 112 let pageResult; 113 if (pageType === "pub.leaflet.pages.linearDocument") { 114 pageResult = PubLeafletPagesLinearDocument.validateMain(page); 115 } else if (pageType === "pub.leaflet.pages.canvas") { 116 pageResult = PubLeafletPagesCanvas.validateMain(page); 117 } else { 118 pageErrors.push({ 119 pageIndex, 120 pageType, 121 error: `Unknown page type: ${pageType}`, 122 }); 123 continue; 124 } 125 126 if (!pageResult.success) { 127 // Page has errors, validate individual blocks 128 const blockErrors: Array<{ 129 blockIndex: number; 130 blockType: string; 131 error: unknown; 132 block: unknown; 133 }> = []; 134 135 if (page.blocks && Array.isArray(page.blocks)) { 136 for ( 137 let blockIndex = 0; 138 blockIndex < page.blocks.length; 139 blockIndex++ 140 ) { 141 const blockWrapper = page.blocks[blockIndex]; 142 const blockType = 143 (blockWrapper?.block as { $type?: string })?.$type || 144 "unknown"; 145 146 // Validate block wrapper based on page type 147 let blockResult; 148 if (pageType === "pub.leaflet.pages.linearDocument") { 149 blockResult = 150 PubLeafletPagesLinearDocument.validateBlock(blockWrapper); 151 } else { 152 blockResult = 153 PubLeafletPagesCanvas.validateBlock(blockWrapper); 154 } 155 156 if (!blockResult.success) { 157 blockErrors.push({ 158 blockIndex, 159 blockType, 160 error: blockResult.error, 161 block: blockWrapper, 162 }); 163 } 164 } 165 } 166 167 pageErrors.push({ 168 pageIndex, 169 pageType, 170 error: pageResult.error, 171 blockErrors: blockErrors.length > 0 ? blockErrors : undefined, 172 }); 173 } 174 } 175 } 176 177 return NextResponse.json({ 178 success: false, 179 collection: uri.collection, 180 error: result.error, 181 pageErrors: pageErrors.length > 0 ? pageErrors : undefined, 182 record: recordResponse.data.value, 183 }); 184 } 185 } 186 187 if (uri.collection === "pub.leaflet.publication") { 188 const result = PubLeafletPublication.validateRecord( 189 recordResponse.data.value, 190 ); 191 if (result.success) { 192 return NextResponse.json({ 193 success: true, 194 collection: uri.collection, 195 record: result.value, 196 }); 197 } else { 198 return NextResponse.json({ 199 success: false, 200 collection: uri.collection, 201 error: result.error, 202 }); 203 } 204 } 205 } catch (error) { 206 console.error("Error validating AT URI:", error); 207 return NextResponse.json( 208 { 209 success: false, 210 error: "Invalid URI or internal error", 211 }, 212 { status: 400 }, 213 ); 214 } 215}