a tool for shared writing and social publishing

add debug validate route for records

Changed files
+215
app
api
unstable_validate
+215
app/api/unstable_validate/route.ts
···
··· 1 + import { NextRequest, NextResponse } from "next/server"; 2 + import { AtpAgent, AtUri } from "@atproto/api"; 3 + import { DidResolver } from "@atproto/identity"; 4 + import { 5 + PubLeafletDocument, 6 + PubLeafletPublication, 7 + PubLeafletPagesLinearDocument, 8 + PubLeafletPagesCanvas, 9 + } from "lexicons/api"; 10 + 11 + const didResolver = new DidResolver({}); 12 + 13 + export 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 + }