@seo-console/package 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/index.d.mts +48 -3
- package/dist/components/index.d.ts +48 -3
- package/dist/components/index.js +3 -1
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +3 -1
- package/dist/components/index.mjs.map +1 -1
- package/dist/hooks/index.js +443 -3
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +443 -3
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +71 -66
- package/dist/index.d.ts +71 -66
- package/dist/index.js +804 -692
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +788 -682
- package/dist/index.mjs.map +1 -1
- package/dist/robots-generator-B1KOf8vn.d.ts +166 -0
- package/dist/robots-generator-D6T5HVNx.d.mts +166 -0
- package/dist/{index-6lAOwFXQ.d.mts → seo-schema-D8EwzllB.d.mts} +1 -46
- package/dist/{index-6lAOwFXQ.d.ts → seo-schema-D8EwzllB.d.ts} +1 -46
- package/dist/server.d.mts +88 -0
- package/dist/server.d.ts +88 -0
- package/dist/server.js +1547 -0
- package/dist/server.js.map +1 -0
- package/dist/server.mjs +1485 -0
- package/dist/server.mjs.map +1 -0
- package/package.json +13 -3
package/dist/index.mjs
CHANGED
|
@@ -1,415 +1,3 @@
|
|
|
1
|
-
// src/lib/supabase/server.ts
|
|
2
|
-
import { createServerClient } from "@supabase/ssr";
|
|
3
|
-
import { cookies } from "next/headers";
|
|
4
|
-
async function createClient() {
|
|
5
|
-
const cookieStore = await cookies();
|
|
6
|
-
return createServerClient(
|
|
7
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
|
8
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
|
9
|
-
{
|
|
10
|
-
cookies: {
|
|
11
|
-
getAll() {
|
|
12
|
-
return cookieStore.getAll();
|
|
13
|
-
},
|
|
14
|
-
setAll(cookiesToSet) {
|
|
15
|
-
try {
|
|
16
|
-
cookiesToSet.forEach(
|
|
17
|
-
({ name, value, options }) => cookieStore.set(name, value, options)
|
|
18
|
-
);
|
|
19
|
-
} catch {
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// src/lib/database/seo-records.ts
|
|
28
|
-
function transformRowToSEORecord(row) {
|
|
29
|
-
return {
|
|
30
|
-
id: row.id,
|
|
31
|
-
userId: row.user_id,
|
|
32
|
-
routePath: row.route_path,
|
|
33
|
-
title: row.title ?? void 0,
|
|
34
|
-
description: row.description ?? void 0,
|
|
35
|
-
keywords: row.keywords ?? void 0,
|
|
36
|
-
ogTitle: row.og_title ?? void 0,
|
|
37
|
-
ogDescription: row.og_description ?? void 0,
|
|
38
|
-
ogImageUrl: row.og_image_url ?? void 0,
|
|
39
|
-
ogImageWidth: row.og_image_width ?? void 0,
|
|
40
|
-
ogImageHeight: row.og_image_height ?? void 0,
|
|
41
|
-
ogType: row.og_type ?? void 0,
|
|
42
|
-
ogUrl: row.og_url ?? void 0,
|
|
43
|
-
ogSiteName: row.og_site_name ?? void 0,
|
|
44
|
-
twitterCard: row.twitter_card ?? void 0,
|
|
45
|
-
twitterTitle: row.twitter_title ?? void 0,
|
|
46
|
-
twitterDescription: row.twitter_description ?? void 0,
|
|
47
|
-
twitterImageUrl: row.twitter_image_url ?? void 0,
|
|
48
|
-
twitterSite: row.twitter_site ?? void 0,
|
|
49
|
-
twitterCreator: row.twitter_creator ?? void 0,
|
|
50
|
-
canonicalUrl: row.canonical_url ?? void 0,
|
|
51
|
-
robots: row.robots ?? void 0,
|
|
52
|
-
author: row.author ?? void 0,
|
|
53
|
-
publishedTime: row.published_time ? new Date(row.published_time) : void 0,
|
|
54
|
-
modifiedTime: row.modified_time ? new Date(row.modified_time) : void 0,
|
|
55
|
-
structuredData: row.structured_data ? row.structured_data : void 0,
|
|
56
|
-
validationStatus: row.validation_status ?? void 0,
|
|
57
|
-
lastValidatedAt: row.last_validated_at ? new Date(row.last_validated_at) : void 0,
|
|
58
|
-
validationErrors: row.validation_errors ? row.validation_errors : void 0,
|
|
59
|
-
createdAt: new Date(row.created_at),
|
|
60
|
-
updatedAt: new Date(row.updated_at)
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
function transformToInsert(record) {
|
|
64
|
-
return {
|
|
65
|
-
user_id: record.userId,
|
|
66
|
-
route_path: record.routePath,
|
|
67
|
-
title: record.title ?? null,
|
|
68
|
-
description: record.description ?? null,
|
|
69
|
-
keywords: record.keywords ?? null,
|
|
70
|
-
og_title: record.ogTitle ?? null,
|
|
71
|
-
og_description: record.ogDescription ?? null,
|
|
72
|
-
og_image_url: record.ogImageUrl ?? null,
|
|
73
|
-
og_image_width: record.ogImageWidth ?? null,
|
|
74
|
-
og_image_height: record.ogImageHeight ?? null,
|
|
75
|
-
og_type: record.ogType ?? null,
|
|
76
|
-
og_url: record.ogUrl ?? null,
|
|
77
|
-
og_site_name: record.ogSiteName ?? null,
|
|
78
|
-
twitter_card: record.twitterCard ?? null,
|
|
79
|
-
twitter_title: record.twitterTitle ?? null,
|
|
80
|
-
twitter_description: record.twitterDescription ?? null,
|
|
81
|
-
twitter_image_url: record.twitterImageUrl ?? null,
|
|
82
|
-
twitter_site: record.twitterSite ?? null,
|
|
83
|
-
twitter_creator: record.twitterCreator ?? null,
|
|
84
|
-
canonical_url: record.canonicalUrl ?? null,
|
|
85
|
-
robots: record.robots ?? null,
|
|
86
|
-
author: record.author ?? null,
|
|
87
|
-
published_time: record.publishedTime?.toISOString() ?? null,
|
|
88
|
-
modified_time: record.modifiedTime?.toISOString() ?? null,
|
|
89
|
-
structured_data: record.structuredData ?? null
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
function transformToUpdate(record) {
|
|
93
|
-
const update = {};
|
|
94
|
-
if (record.routePath !== void 0) update.route_path = record.routePath;
|
|
95
|
-
if (record.title !== void 0) update.title = record.title ?? null;
|
|
96
|
-
if (record.description !== void 0)
|
|
97
|
-
update.description = record.description ?? null;
|
|
98
|
-
if (record.keywords !== void 0) update.keywords = record.keywords ?? null;
|
|
99
|
-
if (record.ogTitle !== void 0) update.og_title = record.ogTitle ?? null;
|
|
100
|
-
if (record.ogDescription !== void 0)
|
|
101
|
-
update.og_description = record.ogDescription ?? null;
|
|
102
|
-
if (record.ogImageUrl !== void 0)
|
|
103
|
-
update.og_image_url = record.ogImageUrl ?? null;
|
|
104
|
-
if (record.ogImageWidth !== void 0)
|
|
105
|
-
update.og_image_width = record.ogImageWidth ?? null;
|
|
106
|
-
if (record.ogImageHeight !== void 0)
|
|
107
|
-
update.og_image_height = record.ogImageHeight ?? null;
|
|
108
|
-
if (record.ogType !== void 0) update.og_type = record.ogType ?? null;
|
|
109
|
-
if (record.ogUrl !== void 0) update.og_url = record.ogUrl ?? null;
|
|
110
|
-
if (record.ogSiteName !== void 0)
|
|
111
|
-
update.og_site_name = record.ogSiteName ?? null;
|
|
112
|
-
if (record.twitterCard !== void 0)
|
|
113
|
-
update.twitter_card = record.twitterCard ?? null;
|
|
114
|
-
if (record.twitterTitle !== void 0)
|
|
115
|
-
update.twitter_title = record.twitterTitle ?? null;
|
|
116
|
-
if (record.twitterDescription !== void 0)
|
|
117
|
-
update.twitter_description = record.twitterDescription ?? null;
|
|
118
|
-
if (record.twitterImageUrl !== void 0)
|
|
119
|
-
update.twitter_image_url = record.twitterImageUrl ?? null;
|
|
120
|
-
if (record.twitterSite !== void 0)
|
|
121
|
-
update.twitter_site = record.twitterSite ?? null;
|
|
122
|
-
if (record.twitterCreator !== void 0)
|
|
123
|
-
update.twitter_creator = record.twitterCreator ?? null;
|
|
124
|
-
if (record.canonicalUrl !== void 0)
|
|
125
|
-
update.canonical_url = record.canonicalUrl ?? null;
|
|
126
|
-
if (record.robots !== void 0) update.robots = record.robots ?? null;
|
|
127
|
-
if (record.author !== void 0) update.author = record.author ?? null;
|
|
128
|
-
if (record.publishedTime !== void 0)
|
|
129
|
-
update.published_time = record.publishedTime?.toISOString() ?? null;
|
|
130
|
-
if (record.modifiedTime !== void 0)
|
|
131
|
-
update.modified_time = record.modifiedTime?.toISOString() ?? null;
|
|
132
|
-
if (record.structuredData !== void 0)
|
|
133
|
-
update.structured_data = record.structuredData ?? null;
|
|
134
|
-
if (record.validationStatus !== void 0)
|
|
135
|
-
update.validation_status = record.validationStatus ?? null;
|
|
136
|
-
if (record.lastValidatedAt !== void 0)
|
|
137
|
-
update.last_validated_at = record.lastValidatedAt?.toISOString() ?? null;
|
|
138
|
-
if (record.validationErrors !== void 0)
|
|
139
|
-
update.validation_errors = record.validationErrors ?? null;
|
|
140
|
-
return update;
|
|
141
|
-
}
|
|
142
|
-
async function getSEORecords() {
|
|
143
|
-
try {
|
|
144
|
-
const supabase = await createClient();
|
|
145
|
-
const {
|
|
146
|
-
data: { user }
|
|
147
|
-
} = await supabase.auth.getUser();
|
|
148
|
-
if (!user) {
|
|
149
|
-
return {
|
|
150
|
-
success: false,
|
|
151
|
-
error: new Error("User not authenticated")
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
const { data, error } = await supabase.from("seo_records").select("*").eq("user_id", user.id).order("created_at", { ascending: false });
|
|
155
|
-
if (error) {
|
|
156
|
-
return { success: false, error };
|
|
157
|
-
}
|
|
158
|
-
const records = (data || []).map(transformRowToSEORecord);
|
|
159
|
-
return { success: true, data: records };
|
|
160
|
-
} catch (error) {
|
|
161
|
-
return {
|
|
162
|
-
success: false,
|
|
163
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
async function getSEORecordById(id) {
|
|
168
|
-
try {
|
|
169
|
-
const supabase = await createClient();
|
|
170
|
-
const {
|
|
171
|
-
data: { user }
|
|
172
|
-
} = await supabase.auth.getUser();
|
|
173
|
-
if (!user) {
|
|
174
|
-
return {
|
|
175
|
-
success: false,
|
|
176
|
-
error: new Error("User not authenticated")
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
const { data, error } = await supabase.from("seo_records").select("*").eq("id", id).eq("user_id", user.id).single();
|
|
180
|
-
if (error) {
|
|
181
|
-
return { success: false, error };
|
|
182
|
-
}
|
|
183
|
-
if (!data) {
|
|
184
|
-
return {
|
|
185
|
-
success: false,
|
|
186
|
-
error: new Error("SEO record not found")
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
return { success: true, data: transformRowToSEORecord(data) };
|
|
190
|
-
} catch (error) {
|
|
191
|
-
return {
|
|
192
|
-
success: false,
|
|
193
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
async function getSEORecordByRoute(routePath) {
|
|
198
|
-
try {
|
|
199
|
-
const supabase = await createClient();
|
|
200
|
-
const {
|
|
201
|
-
data: { user }
|
|
202
|
-
} = await supabase.auth.getUser();
|
|
203
|
-
if (!user) {
|
|
204
|
-
return {
|
|
205
|
-
success: false,
|
|
206
|
-
error: new Error("User not authenticated")
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
const { data, error } = await supabase.from("seo_records").select("*").eq("route_path", routePath).eq("user_id", user.id).maybeSingle();
|
|
210
|
-
if (error) {
|
|
211
|
-
return { success: false, error };
|
|
212
|
-
}
|
|
213
|
-
if (!data) {
|
|
214
|
-
return { success: true, data: null };
|
|
215
|
-
}
|
|
216
|
-
return { success: true, data: transformRowToSEORecord(data) };
|
|
217
|
-
} catch (error) {
|
|
218
|
-
return {
|
|
219
|
-
success: false,
|
|
220
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
async function createSEORecord(record) {
|
|
225
|
-
try {
|
|
226
|
-
const supabase = await createClient();
|
|
227
|
-
const {
|
|
228
|
-
data: { user }
|
|
229
|
-
} = await supabase.auth.getUser();
|
|
230
|
-
if (!user) {
|
|
231
|
-
return {
|
|
232
|
-
success: false,
|
|
233
|
-
error: new Error("User not authenticated")
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
const insertData = transformToInsert({ ...record, userId: user.id });
|
|
237
|
-
const { data, error } = await supabase.from("seo_records").insert(insertData).select().single();
|
|
238
|
-
if (error) {
|
|
239
|
-
return { success: false, error };
|
|
240
|
-
}
|
|
241
|
-
return { success: true, data: transformRowToSEORecord(data) };
|
|
242
|
-
} catch (error) {
|
|
243
|
-
return {
|
|
244
|
-
success: false,
|
|
245
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
async function updateSEORecord(record) {
|
|
250
|
-
try {
|
|
251
|
-
const supabase = await createClient();
|
|
252
|
-
const {
|
|
253
|
-
data: { user }
|
|
254
|
-
} = await supabase.auth.getUser();
|
|
255
|
-
if (!user) {
|
|
256
|
-
return {
|
|
257
|
-
success: false,
|
|
258
|
-
error: new Error("User not authenticated")
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
const { id, ...updateData } = record;
|
|
262
|
-
const transformedUpdate = transformToUpdate(updateData);
|
|
263
|
-
const { data, error } = await supabase.from("seo_records").update(transformedUpdate).eq("id", id).eq("user_id", user.id).select().single();
|
|
264
|
-
if (error) {
|
|
265
|
-
return { success: false, error };
|
|
266
|
-
}
|
|
267
|
-
if (!data) {
|
|
268
|
-
return {
|
|
269
|
-
success: false,
|
|
270
|
-
error: new Error("SEO record not found")
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
return { success: true, data: transformRowToSEORecord(data) };
|
|
274
|
-
} catch (error) {
|
|
275
|
-
return {
|
|
276
|
-
success: false,
|
|
277
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
278
|
-
};
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
async function deleteSEORecord(id) {
|
|
282
|
-
try {
|
|
283
|
-
const supabase = await createClient();
|
|
284
|
-
const {
|
|
285
|
-
data: { user }
|
|
286
|
-
} = await supabase.auth.getUser();
|
|
287
|
-
if (!user) {
|
|
288
|
-
return {
|
|
289
|
-
success: false,
|
|
290
|
-
error: new Error("User not authenticated")
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
const { error } = await supabase.from("seo_records").delete().eq("id", id).eq("user_id", user.id);
|
|
294
|
-
if (error) {
|
|
295
|
-
return { success: false, error };
|
|
296
|
-
}
|
|
297
|
-
return { success: true, data: void 0 };
|
|
298
|
-
} catch (error) {
|
|
299
|
-
return {
|
|
300
|
-
success: false,
|
|
301
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// src/hooks/useGenerateMetadata.ts
|
|
307
|
-
async function useGenerateMetadata(options = {}) {
|
|
308
|
-
const { routePath, fallback = {} } = options;
|
|
309
|
-
if (!routePath) {
|
|
310
|
-
return {
|
|
311
|
-
title: fallback.title,
|
|
312
|
-
description: fallback.description,
|
|
313
|
-
...fallback
|
|
314
|
-
};
|
|
315
|
-
}
|
|
316
|
-
const result = await getSEORecordByRoute(routePath);
|
|
317
|
-
if (!result.success || !result.data) {
|
|
318
|
-
return {
|
|
319
|
-
title: fallback.title,
|
|
320
|
-
description: fallback.description,
|
|
321
|
-
...fallback
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
const record = result.data;
|
|
325
|
-
const metadata = {};
|
|
326
|
-
if (record.title) {
|
|
327
|
-
metadata.title = record.title;
|
|
328
|
-
}
|
|
329
|
-
if (record.description) {
|
|
330
|
-
metadata.description = record.description;
|
|
331
|
-
}
|
|
332
|
-
if (record.keywords && record.keywords.length > 0) {
|
|
333
|
-
metadata.keywords = record.keywords;
|
|
334
|
-
}
|
|
335
|
-
if (record.author) {
|
|
336
|
-
metadata.authors = [{ name: record.author }];
|
|
337
|
-
}
|
|
338
|
-
if (record.ogTitle || record.ogDescription || record.ogImageUrl || record.ogType) {
|
|
339
|
-
const supportedOGTypes = ["website", "article", "book", "profile"];
|
|
340
|
-
const ogType = record.ogType && supportedOGTypes.includes(record.ogType) ? record.ogType : "website";
|
|
341
|
-
const openGraph = {
|
|
342
|
-
type: ogType,
|
|
343
|
-
title: record.ogTitle || record.title || void 0,
|
|
344
|
-
description: record.ogDescription || record.description || void 0,
|
|
345
|
-
url: record.ogUrl || void 0,
|
|
346
|
-
siteName: record.ogSiteName || void 0
|
|
347
|
-
};
|
|
348
|
-
if (record.ogImageUrl) {
|
|
349
|
-
openGraph.images = [
|
|
350
|
-
{
|
|
351
|
-
url: record.ogImageUrl,
|
|
352
|
-
width: record.ogImageWidth || void 0,
|
|
353
|
-
height: record.ogImageHeight || void 0,
|
|
354
|
-
alt: record.ogTitle || record.title || void 0
|
|
355
|
-
}
|
|
356
|
-
];
|
|
357
|
-
}
|
|
358
|
-
if (ogType === "article") {
|
|
359
|
-
const articleOpenGraph = {
|
|
360
|
-
...openGraph,
|
|
361
|
-
...record.publishedTime && {
|
|
362
|
-
publishedTime: record.publishedTime.toISOString()
|
|
363
|
-
},
|
|
364
|
-
...record.modifiedTime && {
|
|
365
|
-
modifiedTime: record.modifiedTime.toISOString()
|
|
366
|
-
}
|
|
367
|
-
};
|
|
368
|
-
metadata.openGraph = articleOpenGraph;
|
|
369
|
-
} else {
|
|
370
|
-
metadata.openGraph = openGraph;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
if (record.twitterCard || record.twitterTitle || record.twitterDescription || record.twitterImageUrl) {
|
|
374
|
-
metadata.twitter = {
|
|
375
|
-
card: record.twitterCard || "summary",
|
|
376
|
-
title: record.twitterTitle || record.ogTitle || record.title || void 0,
|
|
377
|
-
description: record.twitterDescription || record.ogDescription || record.description || void 0,
|
|
378
|
-
images: record.twitterImageUrl ? [record.twitterImageUrl] : void 0,
|
|
379
|
-
site: record.twitterSite || void 0,
|
|
380
|
-
creator: record.twitterCreator || void 0
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
if (record.canonicalUrl) {
|
|
384
|
-
metadata.alternates = {
|
|
385
|
-
canonical: record.canonicalUrl
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
if (record.robots) {
|
|
389
|
-
metadata.robots = record.robots;
|
|
390
|
-
}
|
|
391
|
-
return {
|
|
392
|
-
...fallback,
|
|
393
|
-
...metadata,
|
|
394
|
-
// Ensure title and description from record override fallback if present
|
|
395
|
-
title: record.title || fallback.title,
|
|
396
|
-
description: record.description || fallback.description,
|
|
397
|
-
// Merge openGraph if both exist
|
|
398
|
-
openGraph: fallback.openGraph ? { ...metadata.openGraph, ...fallback.openGraph } : metadata.openGraph,
|
|
399
|
-
// Merge twitter if both exist
|
|
400
|
-
twitter: fallback.twitter ? { ...metadata.twitter, ...fallback.twitter } : metadata.twitter
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
function getRoutePathFromParams(params, pattern) {
|
|
404
|
-
let routePath = pattern;
|
|
405
|
-
for (const [key, value] of Object.entries(params)) {
|
|
406
|
-
const paramValue = Array.isArray(value) ? value.join("/") : value;
|
|
407
|
-
routePath = routePath.replace(`[${key}]`, paramValue);
|
|
408
|
-
routePath = routePath.replace(`[...${key}]`, paramValue);
|
|
409
|
-
}
|
|
410
|
-
return routePath;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
1
|
// src/components/seo/SEORecordList.tsx
|
|
414
2
|
import { useState as useState3, useEffect as useEffect2 } from "react";
|
|
415
3
|
|
|
@@ -3776,7 +3364,9 @@ function SEORecordList() {
|
|
|
3776
3364
|
// src/components/seo/ValidationDashboard.tsx
|
|
3777
3365
|
import { useState as useState4, useEffect as useEffect3 } from "react";
|
|
3778
3366
|
import { Fragment as Fragment2, jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
3779
|
-
var Link = ({ href, children, ...props }) =>
|
|
3367
|
+
var Link = ({ href, children, ...props }) => {
|
|
3368
|
+
return /* @__PURE__ */ jsx8("a", { href, ...props, children });
|
|
3369
|
+
};
|
|
3780
3370
|
function ValidationDashboard() {
|
|
3781
3371
|
const [records, setRecords] = useState4([]);
|
|
3782
3372
|
const [loading, setLoading] = useState4(true);
|
|
@@ -3987,303 +3577,813 @@ function ValidationDashboard() {
|
|
|
3987
3577
|
] })
|
|
3988
3578
|
] });
|
|
3989
3579
|
}
|
|
3990
|
-
|
|
3991
|
-
// src/lib/
|
|
3992
|
-
import
|
|
3993
|
-
|
|
3994
|
-
|
|
3580
|
+
|
|
3581
|
+
// src/lib/route-discovery.ts
|
|
3582
|
+
import { join } from "path";
|
|
3583
|
+
import { glob } from "glob";
|
|
3584
|
+
async function discoverNextJSRoutes(appDir = "app", rootDir = process.cwd()) {
|
|
3585
|
+
const routes = [];
|
|
3586
|
+
const appPath = join(rootDir, appDir);
|
|
3587
|
+
try {
|
|
3588
|
+
const pageFiles = await glob("**/page.tsx", {
|
|
3589
|
+
cwd: appPath,
|
|
3590
|
+
absolute: false,
|
|
3591
|
+
ignore: ["**/node_modules/**", "**/.next/**"]
|
|
3592
|
+
});
|
|
3593
|
+
for (const file of pageFiles) {
|
|
3594
|
+
const route = fileToRoute(file, appDir);
|
|
3595
|
+
if (route) {
|
|
3596
|
+
routes.push(route);
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
} catch (error) {
|
|
3600
|
+
console.error("Error discovering routes:", error);
|
|
3601
|
+
}
|
|
3602
|
+
return routes;
|
|
3603
|
+
}
|
|
3604
|
+
function fileToRoute(filePath, appDir) {
|
|
3605
|
+
let routePath = filePath.replace(/^app\//, "").replace(/\/page\.tsx$/, "").replace(/\/page$/, "");
|
|
3606
|
+
if (routePath === "page" || routePath === "") {
|
|
3607
|
+
routePath = "/";
|
|
3608
|
+
} else {
|
|
3609
|
+
routePath = "/" + routePath;
|
|
3610
|
+
}
|
|
3611
|
+
const segments = routePath.split("/").filter(Boolean);
|
|
3612
|
+
const params = [];
|
|
3613
|
+
let isDynamic = false;
|
|
3614
|
+
let isCatchAll = false;
|
|
3615
|
+
for (const segment of segments) {
|
|
3616
|
+
if (segment.startsWith("[...") && segment.endsWith("]")) {
|
|
3617
|
+
const param = segment.slice(4, -1);
|
|
3618
|
+
params.push(param);
|
|
3619
|
+
isDynamic = true;
|
|
3620
|
+
isCatchAll = true;
|
|
3621
|
+
} else if (segment.startsWith("[") && segment.endsWith("]")) {
|
|
3622
|
+
const param = segment.slice(1, -1);
|
|
3623
|
+
params.push(param);
|
|
3624
|
+
isDynamic = true;
|
|
3625
|
+
}
|
|
3626
|
+
}
|
|
3627
|
+
return {
|
|
3628
|
+
routePath,
|
|
3629
|
+
filePath: join(appDir, filePath),
|
|
3630
|
+
isDynamic,
|
|
3631
|
+
isCatchAll,
|
|
3632
|
+
params
|
|
3633
|
+
};
|
|
3634
|
+
}
|
|
3635
|
+
function generateExamplePaths(route, count = 3) {
|
|
3636
|
+
if (!route.isDynamic) {
|
|
3637
|
+
return [route.routePath];
|
|
3638
|
+
}
|
|
3639
|
+
const examples = [];
|
|
3640
|
+
const segments = route.routePath.split("/").filter(Boolean);
|
|
3641
|
+
for (let i = 0; i < count; i++) {
|
|
3642
|
+
let examplePath = "";
|
|
3643
|
+
for (const segment of segments) {
|
|
3644
|
+
if (segment.startsWith("[...")) {
|
|
3645
|
+
examplePath += `/example-${i}-part-1/example-${i}-part-2`;
|
|
3646
|
+
} else if (segment.startsWith("[")) {
|
|
3647
|
+
examplePath += `/example-${i}`;
|
|
3648
|
+
} else {
|
|
3649
|
+
examplePath += `/${segment}`;
|
|
3650
|
+
}
|
|
3651
|
+
}
|
|
3652
|
+
examples.push(examplePath || "/");
|
|
3653
|
+
}
|
|
3654
|
+
return examples;
|
|
3655
|
+
}
|
|
3656
|
+
|
|
3657
|
+
// src/lib/metadata-extractor.ts
|
|
3658
|
+
import * as cheerio from "cheerio";
|
|
3659
|
+
function extractMetadataFromHTML(html, baseUrl) {
|
|
3660
|
+
const $ = cheerio.load(html);
|
|
3661
|
+
const metadata = {};
|
|
3662
|
+
metadata.title = $("title").text() || void 0;
|
|
3663
|
+
metadata.description = $('meta[name="description"]').attr("content") || void 0;
|
|
3664
|
+
metadata.robots = $('meta[name="robots"]').attr("content") || void 0;
|
|
3665
|
+
const keywords = $('meta[name="keywords"]').attr("content");
|
|
3666
|
+
if (keywords) {
|
|
3667
|
+
metadata.keywords = keywords.split(",").map((k) => k.trim());
|
|
3668
|
+
}
|
|
3669
|
+
metadata.ogTitle = $('meta[property="og:title"]').attr("content") || void 0;
|
|
3670
|
+
metadata.ogDescription = $('meta[property="og:description"]').attr("content") || void 0;
|
|
3671
|
+
metadata.ogImageUrl = $('meta[property="og:image"]').attr("content") || void 0;
|
|
3672
|
+
metadata.ogType = $('meta[property="og:type"]').attr("content") || void 0;
|
|
3673
|
+
metadata.ogUrl = $('meta[property="og:url"]').attr("content") || void 0;
|
|
3674
|
+
metadata.canonicalUrl = $('link[rel="canonical"]').attr("href") || void 0;
|
|
3675
|
+
if (baseUrl) {
|
|
3676
|
+
if (metadata.ogImageUrl && !metadata.ogImageUrl.startsWith("http")) {
|
|
3677
|
+
metadata.ogImageUrl = new URL(metadata.ogImageUrl, baseUrl).toString();
|
|
3678
|
+
}
|
|
3679
|
+
if (metadata.canonicalUrl && !metadata.canonicalUrl.startsWith("http")) {
|
|
3680
|
+
metadata.canonicalUrl = new URL(metadata.canonicalUrl, baseUrl).toString();
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
return metadata;
|
|
3684
|
+
}
|
|
3685
|
+
async function extractMetadataFromURL(url) {
|
|
3686
|
+
try {
|
|
3687
|
+
const response = await fetch(url, {
|
|
3688
|
+
headers: {
|
|
3689
|
+
"User-Agent": "SEO-Console/1.0"
|
|
3690
|
+
}
|
|
3691
|
+
});
|
|
3692
|
+
if (!response.ok) {
|
|
3693
|
+
throw new Error(`Failed to fetch ${url}: ${response.statusText}`);
|
|
3694
|
+
}
|
|
3695
|
+
const html = await response.text();
|
|
3696
|
+
return extractMetadataFromHTML(html, url);
|
|
3697
|
+
} catch (error) {
|
|
3698
|
+
console.error(`Error extracting metadata from ${url}:`, error);
|
|
3699
|
+
return {};
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
function metadataToSEORecord(metadata, routePath, userId = "extracted") {
|
|
3703
|
+
return {
|
|
3704
|
+
userId,
|
|
3705
|
+
routePath,
|
|
3706
|
+
title: metadata.title,
|
|
3707
|
+
description: metadata.description,
|
|
3708
|
+
keywords: metadata.keywords,
|
|
3709
|
+
ogTitle: metadata.ogTitle,
|
|
3710
|
+
ogDescription: metadata.ogDescription,
|
|
3711
|
+
ogImageUrl: metadata.ogImageUrl,
|
|
3712
|
+
ogType: metadata.ogType,
|
|
3713
|
+
ogUrl: metadata.ogUrl,
|
|
3714
|
+
canonicalUrl: metadata.canonicalUrl,
|
|
3715
|
+
robots: metadata.robots,
|
|
3716
|
+
validationStatus: "pending"
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3719
|
+
async function crawlSiteForSEO(baseUrl, routes) {
|
|
3720
|
+
const results = /* @__PURE__ */ new Map();
|
|
3721
|
+
for (const route of routes) {
|
|
3722
|
+
const url = new URL(route, baseUrl).toString();
|
|
3723
|
+
try {
|
|
3724
|
+
const metadata = await extractMetadataFromURL(url);
|
|
3725
|
+
results.set(route, metadata);
|
|
3726
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
3727
|
+
} catch (error) {
|
|
3728
|
+
console.error(`Failed to crawl ${url}:`, error);
|
|
3729
|
+
}
|
|
3730
|
+
}
|
|
3731
|
+
return results;
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
// src/lib/sitemap-generator.ts
|
|
3735
|
+
function generateSitemapXML(options) {
|
|
3736
|
+
const { baseUrl, entries } = options;
|
|
3737
|
+
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
3738
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
3739
|
+
${entries.map((entry) => {
|
|
3740
|
+
const loc = entry.loc.startsWith("http") ? entry.loc : new URL(entry.loc, baseUrl).toString();
|
|
3741
|
+
return ` <url>
|
|
3742
|
+
<loc>${escapeXML(loc)}</loc>${entry.lastmod ? `
|
|
3743
|
+
<lastmod>${entry.lastmod}</lastmod>` : ""}${entry.changefreq ? `
|
|
3744
|
+
<changefreq>${entry.changefreq}</changefreq>` : ""}${entry.priority !== void 0 ? `
|
|
3745
|
+
<priority>${entry.priority}</priority>` : ""}
|
|
3746
|
+
</url>`;
|
|
3747
|
+
}).join("\n")}
|
|
3748
|
+
</urlset>`;
|
|
3749
|
+
return xml;
|
|
3750
|
+
}
|
|
3751
|
+
function seoRecordsToSitemapEntries(records, baseUrl) {
|
|
3752
|
+
return records.filter((record) => {
|
|
3753
|
+
return record.canonicalUrl && record.canonicalUrl.trim() !== "";
|
|
3754
|
+
}).map((record) => {
|
|
3755
|
+
const entry = {
|
|
3756
|
+
loc: record.canonicalUrl
|
|
3757
|
+
};
|
|
3758
|
+
if (record.modifiedTime) {
|
|
3759
|
+
entry.lastmod = record.modifiedTime.toISOString().split("T")[0];
|
|
3760
|
+
} else if (record.lastValidatedAt) {
|
|
3761
|
+
entry.lastmod = record.lastValidatedAt.toISOString().split("T")[0];
|
|
3762
|
+
}
|
|
3763
|
+
if (record.routePath === "/") {
|
|
3764
|
+
entry.changefreq = "daily";
|
|
3765
|
+
entry.priority = 1;
|
|
3766
|
+
} else if (record.routePath.includes("/blog/") || record.routePath.includes("/posts/")) {
|
|
3767
|
+
entry.changefreq = "weekly";
|
|
3768
|
+
entry.priority = 0.8;
|
|
3769
|
+
} else {
|
|
3770
|
+
entry.changefreq = "monthly";
|
|
3771
|
+
entry.priority = 0.6;
|
|
3772
|
+
}
|
|
3773
|
+
return entry;
|
|
3774
|
+
}).sort((a, b) => {
|
|
3775
|
+
if (a.priority !== b.priority) {
|
|
3776
|
+
return (b.priority || 0) - (a.priority || 0);
|
|
3777
|
+
}
|
|
3778
|
+
return a.loc.localeCompare(b.loc);
|
|
3779
|
+
});
|
|
3780
|
+
}
|
|
3781
|
+
function generateSitemapFromRecords(records, baseUrl) {
|
|
3782
|
+
const entries = seoRecordsToSitemapEntries(records, baseUrl);
|
|
3783
|
+
return generateSitemapXML({ baseUrl, entries });
|
|
3784
|
+
}
|
|
3785
|
+
function escapeXML(str) {
|
|
3786
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3787
|
+
}
|
|
3788
|
+
function validateSitemapEntry(entry) {
|
|
3789
|
+
const errors = [];
|
|
3790
|
+
if (!entry.loc) {
|
|
3791
|
+
errors.push("Location (loc) is required");
|
|
3792
|
+
} else {
|
|
3793
|
+
try {
|
|
3794
|
+
new URL(entry.loc);
|
|
3795
|
+
} catch {
|
|
3796
|
+
errors.push("Location must be a valid URL");
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
if (entry.priority !== void 0) {
|
|
3800
|
+
if (entry.priority < 0 || entry.priority > 1) {
|
|
3801
|
+
errors.push("Priority must be between 0.0 and 1.0");
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
if (entry.lastmod) {
|
|
3805
|
+
const date = new Date(entry.lastmod);
|
|
3806
|
+
if (isNaN(date.getTime())) {
|
|
3807
|
+
errors.push("Lastmod must be a valid date (YYYY-MM-DD)");
|
|
3808
|
+
}
|
|
3809
|
+
}
|
|
3810
|
+
return {
|
|
3811
|
+
valid: errors.length === 0,
|
|
3812
|
+
errors
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
|
|
3816
|
+
// src/lib/robots-generator.ts
|
|
3817
|
+
function generateRobotsTxt(options = {}) {
|
|
3818
|
+
const { userAgents = [], sitemapUrl, crawlDelay } = options;
|
|
3819
|
+
let content = "";
|
|
3820
|
+
if (userAgents.length === 0) {
|
|
3821
|
+
content += "User-agent: *\n";
|
|
3822
|
+
if (crawlDelay) {
|
|
3823
|
+
content += `Crawl-delay: ${crawlDelay}
|
|
3824
|
+
`;
|
|
3825
|
+
}
|
|
3826
|
+
content += "Allow: /\n";
|
|
3827
|
+
content += "\n";
|
|
3828
|
+
} else {
|
|
3829
|
+
for (const ua of userAgents) {
|
|
3830
|
+
content += `User-agent: ${ua.agent}
|
|
3831
|
+
`;
|
|
3832
|
+
if (crawlDelay) {
|
|
3833
|
+
content += `Crawl-delay: ${crawlDelay}
|
|
3834
|
+
`;
|
|
3835
|
+
}
|
|
3836
|
+
if (ua.allow) {
|
|
3837
|
+
for (const path of ua.allow) {
|
|
3838
|
+
content += `Allow: ${path}
|
|
3839
|
+
`;
|
|
3840
|
+
}
|
|
3841
|
+
}
|
|
3842
|
+
if (ua.disallow) {
|
|
3843
|
+
for (const path of ua.disallow) {
|
|
3844
|
+
content += `Disallow: ${path}
|
|
3845
|
+
`;
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
content += "\n";
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
if (sitemapUrl) {
|
|
3852
|
+
content += `Sitemap: ${sitemapUrl}
|
|
3853
|
+
`;
|
|
3854
|
+
}
|
|
3855
|
+
return content.trim();
|
|
3856
|
+
}
|
|
3857
|
+
function updateRobotsTxtWithSitemap(existingContent, sitemapUrl) {
|
|
3858
|
+
const sitemapRegex = /^Sitemap:\s*.+$/m;
|
|
3859
|
+
if (sitemapRegex.test(existingContent)) {
|
|
3860
|
+
return existingContent.replace(sitemapRegex, `Sitemap: ${sitemapUrl}`);
|
|
3861
|
+
}
|
|
3862
|
+
const trimmed = existingContent.trim();
|
|
3863
|
+
return trimmed ? `${trimmed}
|
|
3864
|
+
|
|
3865
|
+
Sitemap: ${sitemapUrl}` : `Sitemap: ${sitemapUrl}`;
|
|
3866
|
+
}
|
|
3867
|
+
function extractSitemapFromRobotsTxt(content) {
|
|
3868
|
+
const match = content.match(/^Sitemap:\s*(.+)$/m);
|
|
3869
|
+
return match ? match[1].trim() : null;
|
|
3870
|
+
}
|
|
3871
|
+
|
|
3872
|
+
// src/lib/storage/file-storage.ts
|
|
3873
|
+
import { promises as fs } from "fs";
|
|
3874
|
+
var FileStorage = class {
|
|
3875
|
+
constructor(filePath = "seo-records.json") {
|
|
3876
|
+
this.records = [];
|
|
3877
|
+
this.initialized = false;
|
|
3878
|
+
this.filePath = filePath;
|
|
3879
|
+
}
|
|
3880
|
+
async ensureInitialized() {
|
|
3881
|
+
if (this.initialized) return;
|
|
3882
|
+
try {
|
|
3883
|
+
const data = await fs.readFile(this.filePath, "utf-8");
|
|
3884
|
+
this.records = JSON.parse(data);
|
|
3885
|
+
} catch (error) {
|
|
3886
|
+
if (error.code === "ENOENT") {
|
|
3887
|
+
this.records = [];
|
|
3888
|
+
await this.save();
|
|
3889
|
+
} else {
|
|
3890
|
+
throw error;
|
|
3891
|
+
}
|
|
3892
|
+
}
|
|
3893
|
+
this.initialized = true;
|
|
3894
|
+
}
|
|
3895
|
+
async save() {
|
|
3896
|
+
await fs.writeFile(this.filePath, JSON.stringify(this.records, null, 2), "utf-8");
|
|
3897
|
+
}
|
|
3898
|
+
async isAvailable() {
|
|
3899
|
+
try {
|
|
3900
|
+
const dir = this.filePath.includes("/") ? this.filePath.substring(0, this.filePath.lastIndexOf("/")) : ".";
|
|
3901
|
+
await fs.access(dir);
|
|
3902
|
+
return true;
|
|
3903
|
+
} catch {
|
|
3904
|
+
return false;
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
async getRecords() {
|
|
3908
|
+
await this.ensureInitialized();
|
|
3909
|
+
return [...this.records];
|
|
3910
|
+
}
|
|
3911
|
+
async getRecordById(id) {
|
|
3912
|
+
await this.ensureInitialized();
|
|
3913
|
+
return this.records.find((r) => r.id === id) || null;
|
|
3914
|
+
}
|
|
3915
|
+
async getRecordByRoute(routePath) {
|
|
3916
|
+
await this.ensureInitialized();
|
|
3917
|
+
return this.records.find((r) => r.routePath === routePath) || null;
|
|
3918
|
+
}
|
|
3919
|
+
async createRecord(record) {
|
|
3920
|
+
await this.ensureInitialized();
|
|
3921
|
+
const newRecord = {
|
|
3922
|
+
id: typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
3923
|
+
userId: "file-user",
|
|
3924
|
+
// File storage doesn't need user IDs
|
|
3925
|
+
routePath: record.routePath,
|
|
3926
|
+
title: record.title,
|
|
3927
|
+
description: record.description,
|
|
3928
|
+
keywords: record.keywords,
|
|
3929
|
+
ogTitle: record.ogTitle,
|
|
3930
|
+
ogDescription: record.ogDescription,
|
|
3931
|
+
ogImageUrl: record.ogImageUrl,
|
|
3932
|
+
ogImageWidth: record.ogImageWidth,
|
|
3933
|
+
ogImageHeight: record.ogImageHeight,
|
|
3934
|
+
ogType: record.ogType,
|
|
3935
|
+
ogUrl: record.ogUrl,
|
|
3936
|
+
ogSiteName: record.ogSiteName,
|
|
3937
|
+
twitterCard: record.twitterCard,
|
|
3938
|
+
twitterTitle: record.twitterTitle,
|
|
3939
|
+
twitterDescription: record.twitterDescription,
|
|
3940
|
+
twitterImageUrl: record.twitterImageUrl,
|
|
3941
|
+
twitterSite: record.twitterSite,
|
|
3942
|
+
twitterCreator: record.twitterCreator,
|
|
3943
|
+
canonicalUrl: record.canonicalUrl,
|
|
3944
|
+
robots: record.robots,
|
|
3945
|
+
author: record.author,
|
|
3946
|
+
publishedTime: record.publishedTime,
|
|
3947
|
+
modifiedTime: record.modifiedTime,
|
|
3948
|
+
structuredData: record.structuredData,
|
|
3949
|
+
validationStatus: "pending",
|
|
3950
|
+
lastValidatedAt: void 0,
|
|
3951
|
+
validationErrors: void 0
|
|
3952
|
+
};
|
|
3953
|
+
this.records.push(newRecord);
|
|
3954
|
+
await this.save();
|
|
3955
|
+
return newRecord;
|
|
3956
|
+
}
|
|
3957
|
+
async updateRecord(record) {
|
|
3958
|
+
await this.ensureInitialized();
|
|
3959
|
+
const index = this.records.findIndex((r) => r.id === record.id);
|
|
3960
|
+
if (index === -1) {
|
|
3961
|
+
throw new Error(`SEO record with id ${record.id} not found`);
|
|
3962
|
+
}
|
|
3963
|
+
const updated = {
|
|
3964
|
+
...this.records[index],
|
|
3965
|
+
...record
|
|
3966
|
+
};
|
|
3967
|
+
this.records[index] = updated;
|
|
3968
|
+
await this.save();
|
|
3969
|
+
return updated;
|
|
3970
|
+
}
|
|
3971
|
+
async deleteRecord(id) {
|
|
3972
|
+
await this.ensureInitialized();
|
|
3973
|
+
const index = this.records.findIndex((r) => r.id === id);
|
|
3974
|
+
if (index === -1) {
|
|
3975
|
+
throw new Error(`SEO record with id ${id} not found`);
|
|
3976
|
+
}
|
|
3977
|
+
this.records.splice(index, 1);
|
|
3978
|
+
await this.save();
|
|
3979
|
+
}
|
|
3980
|
+
};
|
|
3981
|
+
|
|
3982
|
+
// src/lib/supabase/server.ts
|
|
3983
|
+
import { createServerClient } from "@supabase/ssr";
|
|
3984
|
+
import { cookies } from "next/headers";
|
|
3985
|
+
async function createClient() {
|
|
3986
|
+
const cookieStore = await cookies();
|
|
3987
|
+
return createServerClient(
|
|
3988
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
|
3989
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
|
3990
|
+
{
|
|
3991
|
+
cookies: {
|
|
3992
|
+
getAll() {
|
|
3993
|
+
return cookieStore.getAll();
|
|
3994
|
+
},
|
|
3995
|
+
setAll(cookiesToSet) {
|
|
3996
|
+
try {
|
|
3997
|
+
cookiesToSet.forEach(
|
|
3998
|
+
({ name, value, options }) => cookieStore.set(name, value, options)
|
|
3999
|
+
);
|
|
4000
|
+
} catch {
|
|
4001
|
+
}
|
|
4002
|
+
}
|
|
4003
|
+
}
|
|
4004
|
+
}
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
|
|
4008
|
+
// src/lib/database/seo-records.ts
|
|
4009
|
+
function transformRowToSEORecord(row) {
|
|
4010
|
+
return {
|
|
4011
|
+
id: row.id,
|
|
4012
|
+
userId: row.user_id,
|
|
4013
|
+
routePath: row.route_path,
|
|
4014
|
+
title: row.title ?? void 0,
|
|
4015
|
+
description: row.description ?? void 0,
|
|
4016
|
+
keywords: row.keywords ?? void 0,
|
|
4017
|
+
ogTitle: row.og_title ?? void 0,
|
|
4018
|
+
ogDescription: row.og_description ?? void 0,
|
|
4019
|
+
ogImageUrl: row.og_image_url ?? void 0,
|
|
4020
|
+
ogImageWidth: row.og_image_width ?? void 0,
|
|
4021
|
+
ogImageHeight: row.og_image_height ?? void 0,
|
|
4022
|
+
ogType: row.og_type ?? void 0,
|
|
4023
|
+
ogUrl: row.og_url ?? void 0,
|
|
4024
|
+
ogSiteName: row.og_site_name ?? void 0,
|
|
4025
|
+
twitterCard: row.twitter_card ?? void 0,
|
|
4026
|
+
twitterTitle: row.twitter_title ?? void 0,
|
|
4027
|
+
twitterDescription: row.twitter_description ?? void 0,
|
|
4028
|
+
twitterImageUrl: row.twitter_image_url ?? void 0,
|
|
4029
|
+
twitterSite: row.twitter_site ?? void 0,
|
|
4030
|
+
twitterCreator: row.twitter_creator ?? void 0,
|
|
4031
|
+
canonicalUrl: row.canonical_url ?? void 0,
|
|
4032
|
+
robots: row.robots ?? void 0,
|
|
4033
|
+
author: row.author ?? void 0,
|
|
4034
|
+
publishedTime: row.published_time ? new Date(row.published_time) : void 0,
|
|
4035
|
+
modifiedTime: row.modified_time ? new Date(row.modified_time) : void 0,
|
|
4036
|
+
structuredData: row.structured_data ? row.structured_data : void 0,
|
|
4037
|
+
validationStatus: row.validation_status ?? void 0,
|
|
4038
|
+
lastValidatedAt: row.last_validated_at ? new Date(row.last_validated_at) : void 0,
|
|
4039
|
+
validationErrors: row.validation_errors ? row.validation_errors : void 0,
|
|
4040
|
+
createdAt: new Date(row.created_at),
|
|
4041
|
+
updatedAt: new Date(row.updated_at)
|
|
4042
|
+
};
|
|
4043
|
+
}
|
|
4044
|
+
function transformToInsert(record) {
|
|
4045
|
+
return {
|
|
4046
|
+
user_id: record.userId,
|
|
4047
|
+
route_path: record.routePath,
|
|
4048
|
+
title: record.title ?? null,
|
|
4049
|
+
description: record.description ?? null,
|
|
4050
|
+
keywords: record.keywords ?? null,
|
|
4051
|
+
og_title: record.ogTitle ?? null,
|
|
4052
|
+
og_description: record.ogDescription ?? null,
|
|
4053
|
+
og_image_url: record.ogImageUrl ?? null,
|
|
4054
|
+
og_image_width: record.ogImageWidth ?? null,
|
|
4055
|
+
og_image_height: record.ogImageHeight ?? null,
|
|
4056
|
+
og_type: record.ogType ?? null,
|
|
4057
|
+
og_url: record.ogUrl ?? null,
|
|
4058
|
+
og_site_name: record.ogSiteName ?? null,
|
|
4059
|
+
twitter_card: record.twitterCard ?? null,
|
|
4060
|
+
twitter_title: record.twitterTitle ?? null,
|
|
4061
|
+
twitter_description: record.twitterDescription ?? null,
|
|
4062
|
+
twitter_image_url: record.twitterImageUrl ?? null,
|
|
4063
|
+
twitter_site: record.twitterSite ?? null,
|
|
4064
|
+
twitter_creator: record.twitterCreator ?? null,
|
|
4065
|
+
canonical_url: record.canonicalUrl ?? null,
|
|
4066
|
+
robots: record.robots ?? null,
|
|
4067
|
+
author: record.author ?? null,
|
|
4068
|
+
published_time: record.publishedTime?.toISOString() ?? null,
|
|
4069
|
+
modified_time: record.modifiedTime?.toISOString() ?? null,
|
|
4070
|
+
structured_data: record.structuredData ?? null
|
|
4071
|
+
};
|
|
4072
|
+
}
|
|
4073
|
+
function transformToUpdate(record) {
|
|
4074
|
+
const update = {};
|
|
4075
|
+
if (record.routePath !== void 0) update.route_path = record.routePath;
|
|
4076
|
+
if (record.title !== void 0) update.title = record.title ?? null;
|
|
4077
|
+
if (record.description !== void 0)
|
|
4078
|
+
update.description = record.description ?? null;
|
|
4079
|
+
if (record.keywords !== void 0) update.keywords = record.keywords ?? null;
|
|
4080
|
+
if (record.ogTitle !== void 0) update.og_title = record.ogTitle ?? null;
|
|
4081
|
+
if (record.ogDescription !== void 0)
|
|
4082
|
+
update.og_description = record.ogDescription ?? null;
|
|
4083
|
+
if (record.ogImageUrl !== void 0)
|
|
4084
|
+
update.og_image_url = record.ogImageUrl ?? null;
|
|
4085
|
+
if (record.ogImageWidth !== void 0)
|
|
4086
|
+
update.og_image_width = record.ogImageWidth ?? null;
|
|
4087
|
+
if (record.ogImageHeight !== void 0)
|
|
4088
|
+
update.og_image_height = record.ogImageHeight ?? null;
|
|
4089
|
+
if (record.ogType !== void 0) update.og_type = record.ogType ?? null;
|
|
4090
|
+
if (record.ogUrl !== void 0) update.og_url = record.ogUrl ?? null;
|
|
4091
|
+
if (record.ogSiteName !== void 0)
|
|
4092
|
+
update.og_site_name = record.ogSiteName ?? null;
|
|
4093
|
+
if (record.twitterCard !== void 0)
|
|
4094
|
+
update.twitter_card = record.twitterCard ?? null;
|
|
4095
|
+
if (record.twitterTitle !== void 0)
|
|
4096
|
+
update.twitter_title = record.twitterTitle ?? null;
|
|
4097
|
+
if (record.twitterDescription !== void 0)
|
|
4098
|
+
update.twitter_description = record.twitterDescription ?? null;
|
|
4099
|
+
if (record.twitterImageUrl !== void 0)
|
|
4100
|
+
update.twitter_image_url = record.twitterImageUrl ?? null;
|
|
4101
|
+
if (record.twitterSite !== void 0)
|
|
4102
|
+
update.twitter_site = record.twitterSite ?? null;
|
|
4103
|
+
if (record.twitterCreator !== void 0)
|
|
4104
|
+
update.twitter_creator = record.twitterCreator ?? null;
|
|
4105
|
+
if (record.canonicalUrl !== void 0)
|
|
4106
|
+
update.canonical_url = record.canonicalUrl ?? null;
|
|
4107
|
+
if (record.robots !== void 0) update.robots = record.robots ?? null;
|
|
4108
|
+
if (record.author !== void 0) update.author = record.author ?? null;
|
|
4109
|
+
if (record.publishedTime !== void 0)
|
|
4110
|
+
update.published_time = record.publishedTime?.toISOString() ?? null;
|
|
4111
|
+
if (record.modifiedTime !== void 0)
|
|
4112
|
+
update.modified_time = record.modifiedTime?.toISOString() ?? null;
|
|
4113
|
+
if (record.structuredData !== void 0)
|
|
4114
|
+
update.structured_data = record.structuredData ?? null;
|
|
4115
|
+
if (record.validationStatus !== void 0)
|
|
4116
|
+
update.validation_status = record.validationStatus ?? null;
|
|
4117
|
+
if (record.lastValidatedAt !== void 0)
|
|
4118
|
+
update.last_validated_at = record.lastValidatedAt?.toISOString() ?? null;
|
|
4119
|
+
if (record.validationErrors !== void 0)
|
|
4120
|
+
update.validation_errors = record.validationErrors ?? null;
|
|
4121
|
+
return update;
|
|
4122
|
+
}
|
|
4123
|
+
async function getSEORecords() {
|
|
3995
4124
|
try {
|
|
3996
|
-
const
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
// 15 second timeout for images
|
|
4002
|
-
});
|
|
4003
|
-
if (!response.ok) {
|
|
4125
|
+
const supabase = await createClient();
|
|
4126
|
+
const {
|
|
4127
|
+
data: { user }
|
|
4128
|
+
} = await supabase.auth.getUser();
|
|
4129
|
+
if (!user) {
|
|
4004
4130
|
return {
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
{
|
|
4008
|
-
field: "image",
|
|
4009
|
-
severity: "critical",
|
|
4010
|
-
message: `Failed to fetch image: ${response.status} ${response.statusText}`,
|
|
4011
|
-
actual: imageUrl
|
|
4012
|
-
}
|
|
4013
|
-
]
|
|
4131
|
+
success: false,
|
|
4132
|
+
error: new Error("User not authenticated")
|
|
4014
4133
|
};
|
|
4015
4134
|
}
|
|
4016
|
-
const
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
if (sizeInMB > 1) {
|
|
4020
|
-
issues.push({
|
|
4021
|
-
field: "image",
|
|
4022
|
-
severity: "warning",
|
|
4023
|
-
message: "Image file size exceeds 1MB recommendation",
|
|
4024
|
-
actual: `${sizeInMB.toFixed(2)}MB`
|
|
4025
|
-
});
|
|
4135
|
+
const { data, error } = await supabase.from("seo_records").select("*").eq("user_id", user.id).order("created_at", { ascending: false });
|
|
4136
|
+
if (error) {
|
|
4137
|
+
return { success: false, error };
|
|
4026
4138
|
}
|
|
4027
|
-
const
|
|
4028
|
-
|
|
4029
|
-
|
|
4139
|
+
const records = (data || []).map(transformRowToSEORecord);
|
|
4140
|
+
return { success: true, data: records };
|
|
4141
|
+
} catch (error) {
|
|
4142
|
+
return {
|
|
4143
|
+
success: false,
|
|
4144
|
+
error: error instanceof Error ? error : new Error("Unknown error")
|
|
4145
|
+
};
|
|
4146
|
+
}
|
|
4147
|
+
}
|
|
4148
|
+
async function getSEORecordById(id) {
|
|
4149
|
+
try {
|
|
4150
|
+
const supabase = await createClient();
|
|
4151
|
+
const {
|
|
4152
|
+
data: { user }
|
|
4153
|
+
} = await supabase.auth.getUser();
|
|
4154
|
+
if (!user) {
|
|
4030
4155
|
return {
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
{
|
|
4034
|
-
field: "image",
|
|
4035
|
-
severity: "critical",
|
|
4036
|
-
message: "Could not determine image dimensions",
|
|
4037
|
-
actual: imageUrl
|
|
4038
|
-
}
|
|
4039
|
-
]
|
|
4156
|
+
success: false,
|
|
4157
|
+
error: new Error("User not authenticated")
|
|
4040
4158
|
};
|
|
4041
4159
|
}
|
|
4042
|
-
const
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
const recommendedAspectRatio = recommendedWidth / recommendedHeight;
|
|
4046
|
-
if (width < recommendedWidth || height < recommendedHeight) {
|
|
4047
|
-
issues.push({
|
|
4048
|
-
field: "image",
|
|
4049
|
-
severity: "warning",
|
|
4050
|
-
message: `Image dimensions below recommended size (${recommendedWidth}x${recommendedHeight})`,
|
|
4051
|
-
expected: `${recommendedWidth}x${recommendedHeight}`,
|
|
4052
|
-
actual: `${width}x${height}`
|
|
4053
|
-
});
|
|
4160
|
+
const { data, error } = await supabase.from("seo_records").select("*").eq("id", id).eq("user_id", user.id).single();
|
|
4161
|
+
if (error) {
|
|
4162
|
+
return { success: false, error };
|
|
4054
4163
|
}
|
|
4055
|
-
if (
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
expected: "1.91:1",
|
|
4061
|
-
actual: `${aspectRatio.toFixed(2)}:1`
|
|
4062
|
-
});
|
|
4164
|
+
if (!data) {
|
|
4165
|
+
return {
|
|
4166
|
+
success: false,
|
|
4167
|
+
error: new Error("SEO record not found")
|
|
4168
|
+
};
|
|
4063
4169
|
}
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4170
|
+
return { success: true, data: transformRowToSEORecord(data) };
|
|
4171
|
+
} catch (error) {
|
|
4172
|
+
return {
|
|
4173
|
+
success: false,
|
|
4174
|
+
error: error instanceof Error ? error : new Error("Unknown error")
|
|
4175
|
+
};
|
|
4176
|
+
}
|
|
4177
|
+
}
|
|
4178
|
+
async function getSEORecordByRoute(routePath) {
|
|
4179
|
+
try {
|
|
4180
|
+
const supabase = await createClient();
|
|
4181
|
+
const {
|
|
4182
|
+
data: { user }
|
|
4183
|
+
} = await supabase.auth.getUser();
|
|
4184
|
+
if (!user) {
|
|
4185
|
+
return {
|
|
4186
|
+
success: false,
|
|
4187
|
+
error: new Error("User not authenticated")
|
|
4188
|
+
};
|
|
4073
4189
|
}
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
severity: "warning",
|
|
4078
|
-
message: "Image width does not match expected value",
|
|
4079
|
-
expected: `${expectedWidth}px`,
|
|
4080
|
-
actual: `${width}px`
|
|
4081
|
-
});
|
|
4190
|
+
const { data, error } = await supabase.from("seo_records").select("*").eq("route_path", routePath).eq("user_id", user.id).maybeSingle();
|
|
4191
|
+
if (error) {
|
|
4192
|
+
return { success: false, error };
|
|
4082
4193
|
}
|
|
4083
|
-
if (
|
|
4084
|
-
|
|
4085
|
-
field: "image",
|
|
4086
|
-
severity: "warning",
|
|
4087
|
-
message: "Image height does not match expected value",
|
|
4088
|
-
expected: `${expectedHeight}px`,
|
|
4089
|
-
actual: `${height}px`
|
|
4090
|
-
});
|
|
4194
|
+
if (!data) {
|
|
4195
|
+
return { success: true, data: null };
|
|
4091
4196
|
}
|
|
4092
|
-
return {
|
|
4093
|
-
isValid: issues.filter((i) => i.severity === "critical").length === 0,
|
|
4094
|
-
issues,
|
|
4095
|
-
metadata: {
|
|
4096
|
-
width,
|
|
4097
|
-
height,
|
|
4098
|
-
format: format || "unknown",
|
|
4099
|
-
size: buffer.length
|
|
4100
|
-
}
|
|
4101
|
-
};
|
|
4197
|
+
return { success: true, data: transformRowToSEORecord(data) };
|
|
4102
4198
|
} catch (error) {
|
|
4103
4199
|
return {
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
{
|
|
4107
|
-
field: "image",
|
|
4108
|
-
severity: "critical",
|
|
4109
|
-
message: error instanceof Error ? error.message : "Failed to validate image",
|
|
4110
|
-
actual: imageUrl
|
|
4111
|
-
}
|
|
4112
|
-
]
|
|
4200
|
+
success: false,
|
|
4201
|
+
error: error instanceof Error ? error : new Error("Unknown error")
|
|
4113
4202
|
};
|
|
4114
4203
|
}
|
|
4115
4204
|
}
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
severity: "critical",
|
|
4128
|
-
message: "Title tag is missing",
|
|
4129
|
-
expected: record.title
|
|
4130
|
-
});
|
|
4131
|
-
} else if (title !== record.title) {
|
|
4132
|
-
issues.push({
|
|
4133
|
-
field: "title",
|
|
4134
|
-
severity: "warning",
|
|
4135
|
-
message: "Title tag does not match SEO record",
|
|
4136
|
-
expected: record.title,
|
|
4137
|
-
actual: title
|
|
4138
|
-
});
|
|
4205
|
+
async function createSEORecord(record) {
|
|
4206
|
+
try {
|
|
4207
|
+
const supabase = await createClient();
|
|
4208
|
+
const {
|
|
4209
|
+
data: { user }
|
|
4210
|
+
} = await supabase.auth.getUser();
|
|
4211
|
+
if (!user) {
|
|
4212
|
+
return {
|
|
4213
|
+
success: false,
|
|
4214
|
+
error: new Error("User not authenticated")
|
|
4215
|
+
};
|
|
4139
4216
|
}
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
message: "Title exceeds recommended 60 characters",
|
|
4145
|
-
actual: `${title.length} characters`
|
|
4146
|
-
});
|
|
4217
|
+
const insertData = transformToInsert({ ...record, userId: user.id });
|
|
4218
|
+
const { data, error } = await supabase.from("seo_records").insert(insertData).select().single();
|
|
4219
|
+
if (error) {
|
|
4220
|
+
return { success: false, error };
|
|
4147
4221
|
}
|
|
4222
|
+
return { success: true, data: transformRowToSEORecord(data) };
|
|
4223
|
+
} catch (error) {
|
|
4224
|
+
return {
|
|
4225
|
+
success: false,
|
|
4226
|
+
error: error instanceof Error ? error : new Error("Unknown error")
|
|
4227
|
+
};
|
|
4148
4228
|
}
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
severity: "warning",
|
|
4162
|
-
message: "Meta description does not match SEO record",
|
|
4163
|
-
expected: record.description,
|
|
4164
|
-
actual: metaDescription
|
|
4165
|
-
});
|
|
4229
|
+
}
|
|
4230
|
+
async function updateSEORecord(record) {
|
|
4231
|
+
try {
|
|
4232
|
+
const supabase = await createClient();
|
|
4233
|
+
const {
|
|
4234
|
+
data: { user }
|
|
4235
|
+
} = await supabase.auth.getUser();
|
|
4236
|
+
if (!user) {
|
|
4237
|
+
return {
|
|
4238
|
+
success: false,
|
|
4239
|
+
error: new Error("User not authenticated")
|
|
4240
|
+
};
|
|
4166
4241
|
}
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4242
|
+
const { id, ...updateData } = record;
|
|
4243
|
+
const transformedUpdate = transformToUpdate(updateData);
|
|
4244
|
+
const { data, error } = await supabase.from("seo_records").update(transformedUpdate).eq("id", id).eq("user_id", user.id).select().single();
|
|
4245
|
+
if (error) {
|
|
4246
|
+
return { success: false, error };
|
|
4247
|
+
}
|
|
4248
|
+
if (!data) {
|
|
4249
|
+
return {
|
|
4250
|
+
success: false,
|
|
4251
|
+
error: new Error("SEO record not found")
|
|
4252
|
+
};
|
|
4174
4253
|
}
|
|
4254
|
+
return { success: true, data: transformRowToSEORecord(data) };
|
|
4255
|
+
} catch (error) {
|
|
4256
|
+
return {
|
|
4257
|
+
success: false,
|
|
4258
|
+
error: error instanceof Error ? error : new Error("Unknown error")
|
|
4259
|
+
};
|
|
4175
4260
|
}
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
const
|
|
4180
|
-
const
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4187
|
-
|
|
4188
|
-
});
|
|
4261
|
+
}
|
|
4262
|
+
async function deleteSEORecord(id) {
|
|
4263
|
+
try {
|
|
4264
|
+
const supabase = await createClient();
|
|
4265
|
+
const {
|
|
4266
|
+
data: { user }
|
|
4267
|
+
} = await supabase.auth.getUser();
|
|
4268
|
+
if (!user) {
|
|
4269
|
+
return {
|
|
4270
|
+
success: false,
|
|
4271
|
+
error: new Error("User not authenticated")
|
|
4272
|
+
};
|
|
4189
4273
|
}
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
severity: "warning",
|
|
4194
|
-
message: "Open Graph description is missing",
|
|
4195
|
-
expected: record.ogDescription
|
|
4196
|
-
});
|
|
4274
|
+
const { error } = await supabase.from("seo_records").delete().eq("id", id).eq("user_id", user.id);
|
|
4275
|
+
if (error) {
|
|
4276
|
+
return { success: false, error };
|
|
4197
4277
|
}
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4278
|
+
return { success: true, data: void 0 };
|
|
4279
|
+
} catch (error) {
|
|
4280
|
+
return {
|
|
4281
|
+
success: false,
|
|
4282
|
+
error: error instanceof Error ? error : new Error("Unknown error")
|
|
4283
|
+
};
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
|
|
4287
|
+
// src/lib/storage/supabase-storage.ts
|
|
4288
|
+
var SupabaseStorage = class {
|
|
4289
|
+
constructor(supabaseUrl, supabaseKey) {
|
|
4290
|
+
this.supabaseUrl = supabaseUrl;
|
|
4291
|
+
this.supabaseKey = supabaseKey;
|
|
4292
|
+
if (typeof process !== "undefined") {
|
|
4293
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL = supabaseUrl;
|
|
4294
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = supabaseKey;
|
|
4205
4295
|
}
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
});
|
|
4296
|
+
}
|
|
4297
|
+
async isAvailable() {
|
|
4298
|
+
try {
|
|
4299
|
+
const result = await getSEORecords();
|
|
4300
|
+
return result.success;
|
|
4301
|
+
} catch {
|
|
4302
|
+
return false;
|
|
4214
4303
|
}
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
expected: record.ogUrl,
|
|
4221
|
-
actual: ogUrl
|
|
4222
|
-
});
|
|
4304
|
+
}
|
|
4305
|
+
async getRecords() {
|
|
4306
|
+
const result = await getSEORecords();
|
|
4307
|
+
if (!result.success) {
|
|
4308
|
+
throw new Error(result.error?.message || "Failed to get records");
|
|
4223
4309
|
}
|
|
4310
|
+
return result.data;
|
|
4224
4311
|
}
|
|
4225
|
-
|
|
4226
|
-
const
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
field: "twitter:card",
|
|
4233
|
-
severity: "warning",
|
|
4234
|
-
message: "Twitter card type does not match",
|
|
4235
|
-
expected: record.twitterCard,
|
|
4236
|
-
actual: twitterCard
|
|
4237
|
-
});
|
|
4312
|
+
async getRecordById(id) {
|
|
4313
|
+
const result = await getSEORecordById(id);
|
|
4314
|
+
if (!result.success) {
|
|
4315
|
+
if (result.error?.message?.includes("not found")) {
|
|
4316
|
+
return null;
|
|
4317
|
+
}
|
|
4318
|
+
throw new Error(result.error?.message || "Failed to get record");
|
|
4238
4319
|
}
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4320
|
+
return result.data || null;
|
|
4321
|
+
}
|
|
4322
|
+
async getRecordByRoute(routePath) {
|
|
4323
|
+
const result = await getSEORecordByRoute(routePath);
|
|
4324
|
+
if (!result.success) {
|
|
4325
|
+
if (result.error?.message?.includes("not found")) {
|
|
4326
|
+
return null;
|
|
4327
|
+
}
|
|
4328
|
+
throw new Error(result.error?.message || "Failed to get record");
|
|
4246
4329
|
}
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
});
|
|
4330
|
+
return result.data || null;
|
|
4331
|
+
}
|
|
4332
|
+
async createRecord(record) {
|
|
4333
|
+
const result = await createSEORecord(record);
|
|
4334
|
+
if (!result.success) {
|
|
4335
|
+
throw new Error(result.error?.message || "Failed to create record");
|
|
4254
4336
|
}
|
|
4337
|
+
return result.data;
|
|
4255
4338
|
}
|
|
4256
|
-
|
|
4257
|
-
const
|
|
4258
|
-
if (!
|
|
4259
|
-
|
|
4260
|
-
field: "canonical",
|
|
4261
|
-
severity: "critical",
|
|
4262
|
-
message: "Canonical URL is missing",
|
|
4263
|
-
expected: record.canonicalUrl
|
|
4264
|
-
});
|
|
4265
|
-
} else if (canonical !== record.canonicalUrl) {
|
|
4266
|
-
issues.push({
|
|
4267
|
-
field: "canonical",
|
|
4268
|
-
severity: "warning",
|
|
4269
|
-
message: "Canonical URL does not match",
|
|
4270
|
-
expected: record.canonicalUrl,
|
|
4271
|
-
actual: canonical
|
|
4272
|
-
});
|
|
4339
|
+
async updateRecord(record) {
|
|
4340
|
+
const result = await updateSEORecord(record);
|
|
4341
|
+
if (!result.success) {
|
|
4342
|
+
throw new Error(result.error?.message || "Failed to update record");
|
|
4273
4343
|
}
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
});
|
|
4344
|
+
return result.data;
|
|
4345
|
+
}
|
|
4346
|
+
async deleteRecord(id) {
|
|
4347
|
+
const result = await deleteSEORecord(id);
|
|
4348
|
+
if (!result.success) {
|
|
4349
|
+
throw new Error(result.error?.message || "Failed to delete record");
|
|
4281
4350
|
}
|
|
4282
4351
|
}
|
|
4352
|
+
};
|
|
4353
|
+
|
|
4354
|
+
// src/lib/storage/storage-factory.ts
|
|
4355
|
+
function createStorageAdapter(config) {
|
|
4356
|
+
switch (config.type) {
|
|
4357
|
+
case "file":
|
|
4358
|
+
return new FileStorage(config.filePath || "seo-records.json");
|
|
4359
|
+
case "supabase":
|
|
4360
|
+
if (!config.supabaseUrl || !config.supabaseKey) {
|
|
4361
|
+
throw new Error("Supabase URL and key are required for Supabase storage");
|
|
4362
|
+
}
|
|
4363
|
+
return new SupabaseStorage(config.supabaseUrl, config.supabaseKey);
|
|
4364
|
+
case "memory":
|
|
4365
|
+
return new FileStorage(":memory:");
|
|
4366
|
+
default:
|
|
4367
|
+
throw new Error(`Unsupported storage type: ${config.type}`);
|
|
4368
|
+
}
|
|
4369
|
+
}
|
|
4370
|
+
function detectStorageConfig() {
|
|
4371
|
+
if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
|
|
4372
|
+
return {
|
|
4373
|
+
type: "supabase",
|
|
4374
|
+
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
|
4375
|
+
supabaseKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
|
|
4376
|
+
};
|
|
4377
|
+
}
|
|
4378
|
+
if (process.env.SEO_CONSOLE_STORAGE_PATH) {
|
|
4379
|
+
return {
|
|
4380
|
+
type: "file",
|
|
4381
|
+
filePath: process.env.SEO_CONSOLE_STORAGE_PATH
|
|
4382
|
+
};
|
|
4383
|
+
}
|
|
4283
4384
|
return {
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
validatedAt: /* @__PURE__ */ new Date()
|
|
4385
|
+
type: "file",
|
|
4386
|
+
filePath: "seo-records.json"
|
|
4287
4387
|
};
|
|
4288
4388
|
}
|
|
4289
4389
|
export {
|
|
@@ -4294,23 +4394,29 @@ export {
|
|
|
4294
4394
|
CardFooter,
|
|
4295
4395
|
CardHeader,
|
|
4296
4396
|
CardTitle,
|
|
4397
|
+
FileStorage,
|
|
4297
4398
|
Input,
|
|
4298
4399
|
OGImagePreview,
|
|
4299
4400
|
SEORecordForm,
|
|
4300
4401
|
SEORecordList,
|
|
4301
4402
|
Spinner,
|
|
4302
4403
|
ValidationDashboard,
|
|
4303
|
-
|
|
4404
|
+
crawlSiteForSEO,
|
|
4304
4405
|
createSEORecordSchema,
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4406
|
+
createStorageAdapter,
|
|
4407
|
+
detectStorageConfig,
|
|
4408
|
+
discoverNextJSRoutes,
|
|
4409
|
+
extractMetadataFromHTML,
|
|
4410
|
+
extractMetadataFromURL,
|
|
4411
|
+
extractSitemapFromRobotsTxt,
|
|
4412
|
+
generateExamplePaths,
|
|
4413
|
+
generateRobotsTxt,
|
|
4414
|
+
generateSitemapFromRecords,
|
|
4415
|
+
generateSitemapXML,
|
|
4416
|
+
metadataToSEORecord,
|
|
4417
|
+
seoRecordsToSitemapEntries,
|
|
4418
|
+
updateRobotsTxtWithSitemap,
|
|
4311
4419
|
updateSEORecordSchema,
|
|
4312
|
-
|
|
4313
|
-
validateHTML,
|
|
4314
|
-
validateOGImage
|
|
4420
|
+
validateSitemapEntry
|
|
4315
4421
|
};
|
|
4316
4422
|
//# sourceMappingURL=index.mjs.map
|