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