@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/hooks/index.mjs
CHANGED
|
@@ -60,6 +60,140 @@ function transformRowToSEORecord(row) {
|
|
|
60
60
|
updatedAt: new Date(row.updated_at)
|
|
61
61
|
};
|
|
62
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
|
+
}
|
|
63
197
|
async function getSEORecordByRoute(routePath) {
|
|
64
198
|
try {
|
|
65
199
|
const supabase = await createClient();
|
|
@@ -87,6 +221,300 @@ async function getSEORecordByRoute(routePath) {
|
|
|
87
221
|
};
|
|
88
222
|
}
|
|
89
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/lib/storage/file-storage.ts
|
|
307
|
+
import { promises as fs } from "fs";
|
|
308
|
+
var FileStorage = class {
|
|
309
|
+
constructor(filePath = "seo-records.json") {
|
|
310
|
+
this.records = [];
|
|
311
|
+
this.initialized = false;
|
|
312
|
+
this.filePath = filePath;
|
|
313
|
+
}
|
|
314
|
+
async ensureInitialized() {
|
|
315
|
+
if (this.initialized) return;
|
|
316
|
+
try {
|
|
317
|
+
const data = await fs.readFile(this.filePath, "utf-8");
|
|
318
|
+
this.records = JSON.parse(data);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
if (error.code === "ENOENT") {
|
|
321
|
+
this.records = [];
|
|
322
|
+
await this.save();
|
|
323
|
+
} else {
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
this.initialized = true;
|
|
328
|
+
}
|
|
329
|
+
async save() {
|
|
330
|
+
await fs.writeFile(this.filePath, JSON.stringify(this.records, null, 2), "utf-8");
|
|
331
|
+
}
|
|
332
|
+
async isAvailable() {
|
|
333
|
+
try {
|
|
334
|
+
const dir = this.filePath.includes("/") ? this.filePath.substring(0, this.filePath.lastIndexOf("/")) : ".";
|
|
335
|
+
await fs.access(dir);
|
|
336
|
+
return true;
|
|
337
|
+
} catch {
|
|
338
|
+
return false;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
async getRecords() {
|
|
342
|
+
await this.ensureInitialized();
|
|
343
|
+
return [...this.records];
|
|
344
|
+
}
|
|
345
|
+
async getRecordById(id) {
|
|
346
|
+
await this.ensureInitialized();
|
|
347
|
+
return this.records.find((r) => r.id === id) || null;
|
|
348
|
+
}
|
|
349
|
+
async getRecordByRoute(routePath) {
|
|
350
|
+
await this.ensureInitialized();
|
|
351
|
+
return this.records.find((r) => r.routePath === routePath) || null;
|
|
352
|
+
}
|
|
353
|
+
async createRecord(record) {
|
|
354
|
+
await this.ensureInitialized();
|
|
355
|
+
const newRecord = {
|
|
356
|
+
id: typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,
|
|
357
|
+
userId: "file-user",
|
|
358
|
+
// File storage doesn't need user IDs
|
|
359
|
+
routePath: record.routePath,
|
|
360
|
+
title: record.title,
|
|
361
|
+
description: record.description,
|
|
362
|
+
keywords: record.keywords,
|
|
363
|
+
ogTitle: record.ogTitle,
|
|
364
|
+
ogDescription: record.ogDescription,
|
|
365
|
+
ogImageUrl: record.ogImageUrl,
|
|
366
|
+
ogImageWidth: record.ogImageWidth,
|
|
367
|
+
ogImageHeight: record.ogImageHeight,
|
|
368
|
+
ogType: record.ogType,
|
|
369
|
+
ogUrl: record.ogUrl,
|
|
370
|
+
ogSiteName: record.ogSiteName,
|
|
371
|
+
twitterCard: record.twitterCard,
|
|
372
|
+
twitterTitle: record.twitterTitle,
|
|
373
|
+
twitterDescription: record.twitterDescription,
|
|
374
|
+
twitterImageUrl: record.twitterImageUrl,
|
|
375
|
+
twitterSite: record.twitterSite,
|
|
376
|
+
twitterCreator: record.twitterCreator,
|
|
377
|
+
canonicalUrl: record.canonicalUrl,
|
|
378
|
+
robots: record.robots,
|
|
379
|
+
author: record.author,
|
|
380
|
+
publishedTime: record.publishedTime,
|
|
381
|
+
modifiedTime: record.modifiedTime,
|
|
382
|
+
structuredData: record.structuredData,
|
|
383
|
+
validationStatus: "pending",
|
|
384
|
+
lastValidatedAt: void 0,
|
|
385
|
+
validationErrors: void 0
|
|
386
|
+
};
|
|
387
|
+
this.records.push(newRecord);
|
|
388
|
+
await this.save();
|
|
389
|
+
return newRecord;
|
|
390
|
+
}
|
|
391
|
+
async updateRecord(record) {
|
|
392
|
+
await this.ensureInitialized();
|
|
393
|
+
const index = this.records.findIndex((r) => r.id === record.id);
|
|
394
|
+
if (index === -1) {
|
|
395
|
+
throw new Error(`SEO record with id ${record.id} not found`);
|
|
396
|
+
}
|
|
397
|
+
const updated = {
|
|
398
|
+
...this.records[index],
|
|
399
|
+
...record
|
|
400
|
+
};
|
|
401
|
+
this.records[index] = updated;
|
|
402
|
+
await this.save();
|
|
403
|
+
return updated;
|
|
404
|
+
}
|
|
405
|
+
async deleteRecord(id) {
|
|
406
|
+
await this.ensureInitialized();
|
|
407
|
+
const index = this.records.findIndex((r) => r.id === id);
|
|
408
|
+
if (index === -1) {
|
|
409
|
+
throw new Error(`SEO record with id ${id} not found`);
|
|
410
|
+
}
|
|
411
|
+
this.records.splice(index, 1);
|
|
412
|
+
await this.save();
|
|
413
|
+
}
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
// src/lib/storage/supabase-storage.ts
|
|
417
|
+
var SupabaseStorage = class {
|
|
418
|
+
constructor(supabaseUrl, supabaseKey) {
|
|
419
|
+
this.supabaseUrl = supabaseUrl;
|
|
420
|
+
this.supabaseKey = supabaseKey;
|
|
421
|
+
if (typeof process !== "undefined") {
|
|
422
|
+
process.env.NEXT_PUBLIC_SUPABASE_URL = supabaseUrl;
|
|
423
|
+
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = supabaseKey;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
async isAvailable() {
|
|
427
|
+
try {
|
|
428
|
+
const result = await getSEORecords();
|
|
429
|
+
return result.success;
|
|
430
|
+
} catch {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
async getRecords() {
|
|
435
|
+
const result = await getSEORecords();
|
|
436
|
+
if (!result.success) {
|
|
437
|
+
throw new Error(result.error?.message || "Failed to get records");
|
|
438
|
+
}
|
|
439
|
+
return result.data;
|
|
440
|
+
}
|
|
441
|
+
async getRecordById(id) {
|
|
442
|
+
const result = await getSEORecordById(id);
|
|
443
|
+
if (!result.success) {
|
|
444
|
+
if (result.error?.message?.includes("not found")) {
|
|
445
|
+
return null;
|
|
446
|
+
}
|
|
447
|
+
throw new Error(result.error?.message || "Failed to get record");
|
|
448
|
+
}
|
|
449
|
+
return result.data || null;
|
|
450
|
+
}
|
|
451
|
+
async getRecordByRoute(routePath) {
|
|
452
|
+
const result = await getSEORecordByRoute(routePath);
|
|
453
|
+
if (!result.success) {
|
|
454
|
+
if (result.error?.message?.includes("not found")) {
|
|
455
|
+
return null;
|
|
456
|
+
}
|
|
457
|
+
throw new Error(result.error?.message || "Failed to get record");
|
|
458
|
+
}
|
|
459
|
+
return result.data || null;
|
|
460
|
+
}
|
|
461
|
+
async createRecord(record) {
|
|
462
|
+
const result = await createSEORecord(record);
|
|
463
|
+
if (!result.success) {
|
|
464
|
+
throw new Error(result.error?.message || "Failed to create record");
|
|
465
|
+
}
|
|
466
|
+
return result.data;
|
|
467
|
+
}
|
|
468
|
+
async updateRecord(record) {
|
|
469
|
+
const result = await updateSEORecord(record);
|
|
470
|
+
if (!result.success) {
|
|
471
|
+
throw new Error(result.error?.message || "Failed to update record");
|
|
472
|
+
}
|
|
473
|
+
return result.data;
|
|
474
|
+
}
|
|
475
|
+
async deleteRecord(id) {
|
|
476
|
+
const result = await deleteSEORecord(id);
|
|
477
|
+
if (!result.success) {
|
|
478
|
+
throw new Error(result.error?.message || "Failed to delete record");
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// src/lib/storage/storage-factory.ts
|
|
484
|
+
function createStorageAdapter(config) {
|
|
485
|
+
switch (config.type) {
|
|
486
|
+
case "file":
|
|
487
|
+
return new FileStorage(config.filePath || "seo-records.json");
|
|
488
|
+
case "supabase":
|
|
489
|
+
if (!config.supabaseUrl || !config.supabaseKey) {
|
|
490
|
+
throw new Error("Supabase URL and key are required for Supabase storage");
|
|
491
|
+
}
|
|
492
|
+
return new SupabaseStorage(config.supabaseUrl, config.supabaseKey);
|
|
493
|
+
case "memory":
|
|
494
|
+
return new FileStorage(":memory:");
|
|
495
|
+
default:
|
|
496
|
+
throw new Error(`Unsupported storage type: ${config.type}`);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function detectStorageConfig() {
|
|
500
|
+
if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
|
|
501
|
+
return {
|
|
502
|
+
type: "supabase",
|
|
503
|
+
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
|
504
|
+
supabaseKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
if (process.env.SEO_CONSOLE_STORAGE_PATH) {
|
|
508
|
+
return {
|
|
509
|
+
type: "file",
|
|
510
|
+
filePath: process.env.SEO_CONSOLE_STORAGE_PATH
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
return {
|
|
514
|
+
type: "file",
|
|
515
|
+
filePath: "seo-records.json"
|
|
516
|
+
};
|
|
517
|
+
}
|
|
90
518
|
|
|
91
519
|
// src/hooks/useGenerateMetadata.ts
|
|
92
520
|
async function useGenerateMetadata(options = {}) {
|
|
@@ -98,15 +526,27 @@ async function useGenerateMetadata(options = {}) {
|
|
|
98
526
|
...fallback
|
|
99
527
|
};
|
|
100
528
|
}
|
|
101
|
-
|
|
102
|
-
|
|
529
|
+
let record = null;
|
|
530
|
+
try {
|
|
531
|
+
const storageConfig = detectStorageConfig();
|
|
532
|
+
if (storageConfig.type === "file" || storageConfig.type === "memory") {
|
|
533
|
+
const storage = createStorageAdapter(storageConfig);
|
|
534
|
+
record = await storage.getRecordByRoute(routePath);
|
|
535
|
+
} else {
|
|
536
|
+
const result = await getSEORecordByRoute(routePath);
|
|
537
|
+
record = result.success ? result.data || null : null;
|
|
538
|
+
}
|
|
539
|
+
} catch (error) {
|
|
540
|
+
const result = await getSEORecordByRoute(routePath);
|
|
541
|
+
record = result.success ? result.data || null : null;
|
|
542
|
+
}
|
|
543
|
+
if (!record) {
|
|
103
544
|
return {
|
|
104
545
|
title: fallback.title,
|
|
105
546
|
description: fallback.description,
|
|
106
547
|
...fallback
|
|
107
548
|
};
|
|
108
549
|
}
|
|
109
|
-
const record = result.data;
|
|
110
550
|
const metadata = {};
|
|
111
551
|
if (record.title) {
|
|
112
552
|
metadata.title = record.title;
|
package/dist/hooks/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/lib/supabase/server.ts","../../src/lib/database/seo-records.ts","../../src/hooks/useGenerateMetadata.ts"],"sourcesContent":["import { createServerClient } from \"@supabase/ssr\";\nimport { cookies } from \"next/headers\";\n\nexport async function createClient() {\n const cookieStore = await cookies();\n\n return createServerClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return cookieStore.getAll();\n },\n setAll(cookiesToSet: Array<{ name: string; value: string; options?: unknown }>) {\n try {\n cookiesToSet.forEach(({ name, value, options }: { name: string; value: string; options?: unknown }) =>\n cookieStore.set(name, value, options as { [key: string]: unknown })\n );\n } catch {\n // Called from Server Component - ignore\n }\n },\n },\n }\n );\n}\n","import { createClient } from \"../supabase/server\";\r\nimport type { Database } from \"../../types/database.types\";\r\nimport type {\r\n CreateSEORecord,\r\n UpdateSEORecord,\r\n SEORecord,\r\n} from \"../validation/seo-schema\";\r\n\r\ntype SEORecordRow = Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"];\r\ntype SEORecordInsert = Database[\"public\"][\"Tables\"][\"seo_records\"][\"Insert\"];\r\ntype SEORecordUpdate = Database[\"public\"][\"Tables\"][\"seo_records\"][\"Update\"];\r\n\r\n// Transform database row to SEORecord type\r\nfunction transformRowToSEORecord(row: SEORecordRow): SEORecord {\r\n return {\r\n id: row.id,\r\n userId: row.user_id,\r\n routePath: row.route_path,\r\n title: row.title ?? undefined,\r\n description: row.description ?? undefined,\r\n keywords: row.keywords ?? undefined,\r\n ogTitle: row.og_title ?? undefined,\r\n ogDescription: row.og_description ?? undefined,\r\n ogImageUrl: row.og_image_url ?? undefined,\r\n ogImageWidth: row.og_image_width ?? undefined,\r\n ogImageHeight: row.og_image_height ?? undefined,\r\n ogType: (row.og_type as SEORecord[\"ogType\"]) ?? undefined,\r\n ogUrl: row.og_url ?? undefined,\r\n ogSiteName: row.og_site_name ?? undefined,\r\n twitterCard: (row.twitter_card as SEORecord[\"twitterCard\"]) ?? undefined,\r\n twitterTitle: row.twitter_title ?? undefined,\r\n twitterDescription: row.twitter_description ?? undefined,\r\n twitterImageUrl: row.twitter_image_url ?? undefined,\r\n twitterSite: row.twitter_site ?? undefined,\r\n twitterCreator: row.twitter_creator ?? undefined,\r\n canonicalUrl: row.canonical_url ?? undefined,\r\n robots: row.robots ?? undefined,\r\n author: row.author ?? undefined,\r\n publishedTime: row.published_time\r\n ? new Date(row.published_time)\r\n : undefined,\r\n modifiedTime: row.modified_time\r\n ? new Date(row.modified_time)\r\n : undefined,\r\n structuredData: row.structured_data\r\n ? (row.structured_data as unknown as Record<string, unknown>)\r\n : undefined,\r\n validationStatus: (row.validation_status as SEORecord[\"validationStatus\"]) ?? undefined,\r\n lastValidatedAt: row.last_validated_at\r\n ? new Date(row.last_validated_at)\r\n : undefined,\r\n validationErrors: row.validation_errors\r\n ? (row.validation_errors as unknown as Record<string, unknown>)\r\n : undefined,\r\n createdAt: new Date(row.created_at),\r\n updatedAt: new Date(row.updated_at),\r\n };\r\n}\r\n\r\n// Transform SEORecord to database insert format\r\nfunction transformToInsert(\r\n record: CreateSEORecord\r\n): Omit<SEORecordInsert, \"id\" | \"created_at\" | \"updated_at\"> {\r\n return {\r\n user_id: record.userId,\r\n route_path: record.routePath,\r\n title: record.title ?? null,\r\n description: record.description ?? null,\r\n keywords: record.keywords ?? null,\r\n og_title: record.ogTitle ?? null,\r\n og_description: record.ogDescription ?? null,\r\n og_image_url: record.ogImageUrl ?? null,\r\n og_image_width: record.ogImageWidth ?? null,\r\n og_image_height: record.ogImageHeight ?? null,\r\n og_type: record.ogType ?? null,\r\n og_url: record.ogUrl ?? null,\r\n og_site_name: record.ogSiteName ?? null,\r\n twitter_card: record.twitterCard ?? null,\r\n twitter_title: record.twitterTitle ?? null,\r\n twitter_description: record.twitterDescription ?? null,\r\n twitter_image_url: record.twitterImageUrl ?? null,\r\n twitter_site: record.twitterSite ?? null,\r\n twitter_creator: record.twitterCreator ?? null,\r\n canonical_url: record.canonicalUrl ?? null,\r\n robots: record.robots ?? null,\r\n author: record.author ?? null,\r\n published_time: record.publishedTime?.toISOString() ?? null,\r\n modified_time: record.modifiedTime?.toISOString() ?? null,\r\n structured_data: (record.structuredData as unknown as Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"][\"structured_data\"]) ?? null,\r\n };\r\n}\r\n\r\n// Transform SEORecord to database update format\r\nfunction transformToUpdate(\r\n record: Omit<UpdateSEORecord, \"id\">\r\n): Omit<SEORecordUpdate, \"updated_at\"> {\r\n const update: Partial<SEORecordUpdate> = {};\r\n\r\n if (record.routePath !== undefined) update.route_path = record.routePath;\r\n if (record.title !== undefined) update.title = record.title ?? null;\r\n if (record.description !== undefined)\r\n update.description = record.description ?? null;\r\n if (record.keywords !== undefined) update.keywords = record.keywords ?? null;\r\n if (record.ogTitle !== undefined) update.og_title = record.ogTitle ?? null;\r\n if (record.ogDescription !== undefined)\r\n update.og_description = record.ogDescription ?? null;\r\n if (record.ogImageUrl !== undefined)\r\n update.og_image_url = record.ogImageUrl ?? null;\r\n if (record.ogImageWidth !== undefined)\r\n update.og_image_width = record.ogImageWidth ?? null;\r\n if (record.ogImageHeight !== undefined)\r\n update.og_image_height = record.ogImageHeight ?? null;\r\n if (record.ogType !== undefined) update.og_type = record.ogType ?? null;\r\n if (record.ogUrl !== undefined) update.og_url = record.ogUrl ?? null;\r\n if (record.ogSiteName !== undefined)\r\n update.og_site_name = record.ogSiteName ?? null;\r\n if (record.twitterCard !== undefined)\r\n update.twitter_card = record.twitterCard ?? null;\r\n if (record.twitterTitle !== undefined)\r\n update.twitter_title = record.twitterTitle ?? null;\r\n if (record.twitterDescription !== undefined)\r\n update.twitter_description = record.twitterDescription ?? null;\r\n if (record.twitterImageUrl !== undefined)\r\n update.twitter_image_url = record.twitterImageUrl ?? null;\r\n if (record.twitterSite !== undefined)\r\n update.twitter_site = record.twitterSite ?? null;\r\n if (record.twitterCreator !== undefined)\r\n update.twitter_creator = record.twitterCreator ?? null;\r\n if (record.canonicalUrl !== undefined)\r\n update.canonical_url = record.canonicalUrl ?? null;\r\n if (record.robots !== undefined) update.robots = record.robots ?? null;\r\n if (record.author !== undefined) update.author = record.author ?? null;\r\n if (record.publishedTime !== undefined)\r\n update.published_time = record.publishedTime?.toISOString() ?? null;\r\n if (record.modifiedTime !== undefined)\r\n update.modified_time = record.modifiedTime?.toISOString() ?? null;\r\n if (record.structuredData !== undefined)\r\n update.structured_data = (record.structuredData as unknown as Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"][\"structured_data\"]) ?? null;\r\n if (record.validationStatus !== undefined)\r\n update.validation_status = record.validationStatus ?? null;\r\n if (record.lastValidatedAt !== undefined)\r\n update.last_validated_at = record.lastValidatedAt?.toISOString() ?? null;\r\n if (record.validationErrors !== undefined)\r\n update.validation_errors = (record.validationErrors as unknown as Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"][\"validation_errors\"]) ?? null;\r\n\r\n return update;\r\n}\r\n\r\n// Result type for operations\r\nexport type Result<T, E = Error> =\r\n | { success: true; data: T }\r\n | { success: false; error: E };\r\n\r\n/**\r\n * Get all SEO records for the current user\r\n */\r\nexport async function getSEORecords(): Promise<Result<SEORecord[]>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .select(\"*\")\r\n .eq(\"user_id\", user.id)\r\n .order(\"created_at\", { ascending: false });\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n const records = (data || []).map(transformRowToSEORecord);\r\n return { success: true, data: records };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Get a single SEO record by ID\r\n */\r\nexport async function getSEORecordById(\r\n id: string\r\n): Promise<Result<SEORecord>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .select(\"*\")\r\n .eq(\"id\", id)\r\n .eq(\"user_id\", user.id)\r\n .single();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n if (!data) {\r\n return {\r\n success: false,\r\n error: new Error(\"SEO record not found\"),\r\n };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Get SEO record by route path\r\n */\r\nexport async function getSEORecordByRoute(\r\n routePath: string\r\n): Promise<Result<SEORecord | null>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .select(\"*\")\r\n .eq(\"route_path\", routePath)\r\n .eq(\"user_id\", user.id)\r\n .maybeSingle();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n if (!data) {\r\n return { success: true, data: null };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Create a new SEO record\r\n */\r\nexport async function createSEORecord(\r\n record: CreateSEORecord\r\n): Promise<Result<SEORecord>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const insertData = transformToInsert({ ...record, userId: user.id });\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .insert(insertData)\r\n .select()\r\n .single();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Update an existing SEO record\r\n */\r\nexport async function updateSEORecord(\r\n record: UpdateSEORecord\r\n): Promise<Result<SEORecord>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { id, ...updateData } = record;\r\n const transformedUpdate = transformToUpdate(updateData);\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .update(transformedUpdate)\r\n .eq(\"id\", id)\r\n .eq(\"user_id\", user.id)\r\n .select()\r\n .single();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n if (!data) {\r\n return {\r\n success: false,\r\n error: new Error(\"SEO record not found\"),\r\n };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Delete an SEO record\r\n */\r\nexport async function deleteSEORecord(id: string): Promise<Result<void>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { error } = await supabase\r\n .from(\"seo_records\")\r\n .delete()\r\n .eq(\"id\", id)\r\n .eq(\"user_id\", user.id);\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n return { success: true, data: undefined };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n","import type { Metadata } from \"next\";\r\nimport { getSEORecordByRoute } from \"../lib/database/seo-records\";\r\n\r\nexport interface GenerateMetadataOptions {\r\n routePath?: string;\r\n fallback?: Partial<Metadata>;\r\n}\r\n\r\n/**\r\n * Generate Next.js metadata from SEO records\r\n * \r\n * @param options - Configuration options\r\n * @param options.routePath - The route path to look up (defaults to current route)\r\n * @param options.fallback - Fallback metadata if no SEO record is found\r\n * @returns Next.js Metadata object\r\n * \r\n * @example\r\n * ```ts\r\n * export async function generateMetadata(): Promise<Metadata> {\r\n * return useGenerateMetadata({\r\n * routePath: \"/about\",\r\n * fallback: {\r\n * title: \"About Us\",\r\n * description: \"Learn more about our company\"\r\n * }\r\n * });\r\n * }\r\n * ```\r\n */\r\nexport async function useGenerateMetadata(\r\n options: GenerateMetadataOptions = {} as GenerateMetadataOptions\r\n): Promise<Metadata> {\r\n const { routePath, fallback = {} as Partial<Metadata> } = options;\r\n\r\n // If no route path provided, return fallback only\r\n if (!routePath) {\r\n return {\r\n title: fallback.title,\r\n description: fallback.description,\r\n ...fallback,\r\n };\r\n }\r\n\r\n // Fetch SEO record from database\r\n const result = await getSEORecordByRoute(routePath);\r\n\r\n if (!result.success || !result.data) {\r\n // Return fallback if record not found or error occurred\r\n return {\r\n title: fallback.title,\r\n description: fallback.description,\r\n ...fallback,\r\n };\r\n }\r\n\r\n const record = result.data;\r\n const metadata: Partial<Metadata> = {};\r\n\r\n // Basic metadata\r\n if (record.title) {\r\n metadata.title = record.title;\r\n }\r\n if (record.description) {\r\n metadata.description = record.description;\r\n }\r\n if (record.keywords && record.keywords.length > 0) {\r\n metadata.keywords = record.keywords;\r\n }\r\n if (record.author) {\r\n metadata.authors = [{ name: record.author }];\r\n }\r\n\r\n // Open Graph metadata\r\n if (\r\n record.ogTitle ||\r\n record.ogDescription ||\r\n record.ogImageUrl ||\r\n record.ogType\r\n ) {\r\n // Next.js only supports specific OG types\r\n const supportedOGTypes = [\"website\", \"article\", \"book\", \"profile\"] as const;\r\n const ogType = record.ogType && supportedOGTypes.includes(record.ogType as typeof supportedOGTypes[number])\r\n ? (record.ogType as typeof supportedOGTypes[number])\r\n : \"website\";\r\n\r\n const openGraph: NonNullable<Metadata[\"openGraph\"]> = {\r\n type: ogType,\r\n title: record.ogTitle || record.title || undefined,\r\n description: record.ogDescription || record.description || undefined,\r\n url: record.ogUrl || undefined,\r\n siteName: record.ogSiteName || undefined,\r\n };\r\n\r\n if (record.ogImageUrl) {\r\n openGraph.images = [\r\n {\r\n url: record.ogImageUrl,\r\n width: record.ogImageWidth || undefined,\r\n height: record.ogImageHeight || undefined,\r\n alt: record.ogTitle || record.title || undefined,\r\n },\r\n ];\r\n }\r\n\r\n // For article type, add published/modified times\r\n if (ogType === \"article\") {\r\n const articleOpenGraph = {\r\n ...openGraph,\r\n ...(record.publishedTime && {\r\n publishedTime: record.publishedTime.toISOString(),\r\n }),\r\n ...(record.modifiedTime && {\r\n modifiedTime: record.modifiedTime.toISOString(),\r\n }),\r\n } as Metadata[\"openGraph\"];\r\n metadata.openGraph = articleOpenGraph;\r\n } else {\r\n metadata.openGraph = openGraph;\r\n }\r\n }\r\n\r\n // Twitter Card metadata\r\n if (\r\n record.twitterCard ||\r\n record.twitterTitle ||\r\n record.twitterDescription ||\r\n record.twitterImageUrl\r\n ) {\r\n metadata.twitter = {\r\n card: record.twitterCard || \"summary\",\r\n title: record.twitterTitle || record.ogTitle || record.title || undefined,\r\n description:\r\n record.twitterDescription ||\r\n record.ogDescription ||\r\n record.description ||\r\n undefined,\r\n images: record.twitterImageUrl\r\n ? [record.twitterImageUrl]\r\n : undefined,\r\n site: record.twitterSite || undefined,\r\n creator: record.twitterCreator || undefined,\r\n };\r\n }\r\n\r\n // Canonical URL\r\n if (record.canonicalUrl) {\r\n metadata.alternates = {\r\n canonical: record.canonicalUrl,\r\n };\r\n }\r\n\r\n // Robots\r\n if (record.robots) {\r\n metadata.robots = record.robots as Metadata[\"robots\"];\r\n }\r\n\r\n // Merge with fallback (fallback takes precedence for missing values)\r\n return {\r\n ...fallback,\r\n ...metadata,\r\n // Ensure title and description from record override fallback if present\r\n title: record.title || fallback.title,\r\n description: record.description || fallback.description,\r\n // Merge openGraph if both exist\r\n openGraph: fallback.openGraph\r\n ? { ...metadata.openGraph, ...fallback.openGraph }\r\n : metadata.openGraph,\r\n // Merge twitter if both exist\r\n twitter: fallback.twitter\r\n ? { ...metadata.twitter, ...fallback.twitter }\r\n : metadata.twitter,\r\n };\r\n}\r\n\r\n/**\r\n * Helper to get route path from Next.js params\r\n * Useful for dynamic routes\r\n * \r\n * @example\r\n * ```ts\r\n * export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {\r\n * const routePath = getRoutePathFromParams(params, \"/blog/[slug]\");\r\n * return useGenerateMetadata({ routePath });\r\n * }\r\n * ```\r\n */\r\nexport function getRoutePathFromParams(\r\n params: Record<string, string | string[]>,\r\n pattern: string\r\n): string {\r\n let routePath = pattern;\r\n\r\n // Replace [param] and [...param] patterns with actual values\r\n for (const [key, value] of Object.entries(params)) {\r\n const paramValue = Array.isArray(value) ? value.join(\"/\") : value;\r\n routePath = routePath.replace(`[${key}]`, paramValue);\r\n routePath = routePath.replace(`[...${key}]`, paramValue);\r\n }\r\n\r\n return routePath;\r\n}\r\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/lib/supabase/server.ts","../../src/lib/database/seo-records.ts","../../src/lib/storage/file-storage.ts","../../src/lib/storage/supabase-storage.ts","../../src/lib/storage/storage-factory.ts","../../src/hooks/useGenerateMetadata.ts"],"sourcesContent":["import { createServerClient } from \"@supabase/ssr\";\nimport { cookies } from \"next/headers\";\n\nexport async function createClient() {\n const cookieStore = await cookies();\n\n return createServerClient(\n process.env.NEXT_PUBLIC_SUPABASE_URL!,\n process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,\n {\n cookies: {\n getAll() {\n return cookieStore.getAll();\n },\n setAll(cookiesToSet: Array<{ name: string; value: string; options?: unknown }>) {\n try {\n cookiesToSet.forEach(({ name, value, options }: { name: string; value: string; options?: unknown }) =>\n cookieStore.set(name, value, options as { [key: string]: unknown })\n );\n } catch {\n // Called from Server Component - ignore\n }\n },\n },\n }\n );\n}\n","import { createClient } from \"../supabase/server\";\r\nimport type { Database } from \"../../types/database.types\";\r\nimport type {\r\n CreateSEORecord,\r\n UpdateSEORecord,\r\n SEORecord,\r\n} from \"../validation/seo-schema\";\r\n\r\ntype SEORecordRow = Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"];\r\ntype SEORecordInsert = Database[\"public\"][\"Tables\"][\"seo_records\"][\"Insert\"];\r\ntype SEORecordUpdate = Database[\"public\"][\"Tables\"][\"seo_records\"][\"Update\"];\r\n\r\n// Transform database row to SEORecord type\r\nfunction transformRowToSEORecord(row: SEORecordRow): SEORecord {\r\n return {\r\n id: row.id,\r\n userId: row.user_id,\r\n routePath: row.route_path,\r\n title: row.title ?? undefined,\r\n description: row.description ?? undefined,\r\n keywords: row.keywords ?? undefined,\r\n ogTitle: row.og_title ?? undefined,\r\n ogDescription: row.og_description ?? undefined,\r\n ogImageUrl: row.og_image_url ?? undefined,\r\n ogImageWidth: row.og_image_width ?? undefined,\r\n ogImageHeight: row.og_image_height ?? undefined,\r\n ogType: (row.og_type as SEORecord[\"ogType\"]) ?? undefined,\r\n ogUrl: row.og_url ?? undefined,\r\n ogSiteName: row.og_site_name ?? undefined,\r\n twitterCard: (row.twitter_card as SEORecord[\"twitterCard\"]) ?? undefined,\r\n twitterTitle: row.twitter_title ?? undefined,\r\n twitterDescription: row.twitter_description ?? undefined,\r\n twitterImageUrl: row.twitter_image_url ?? undefined,\r\n twitterSite: row.twitter_site ?? undefined,\r\n twitterCreator: row.twitter_creator ?? undefined,\r\n canonicalUrl: row.canonical_url ?? undefined,\r\n robots: row.robots ?? undefined,\r\n author: row.author ?? undefined,\r\n publishedTime: row.published_time\r\n ? new Date(row.published_time)\r\n : undefined,\r\n modifiedTime: row.modified_time\r\n ? new Date(row.modified_time)\r\n : undefined,\r\n structuredData: row.structured_data\r\n ? (row.structured_data as unknown as Record<string, unknown>)\r\n : undefined,\r\n validationStatus: (row.validation_status as SEORecord[\"validationStatus\"]) ?? undefined,\r\n lastValidatedAt: row.last_validated_at\r\n ? new Date(row.last_validated_at)\r\n : undefined,\r\n validationErrors: row.validation_errors\r\n ? (row.validation_errors as unknown as Record<string, unknown>)\r\n : undefined,\r\n createdAt: new Date(row.created_at),\r\n updatedAt: new Date(row.updated_at),\r\n };\r\n}\r\n\r\n// Transform SEORecord to database insert format\r\nfunction transformToInsert(\r\n record: CreateSEORecord\r\n): Omit<SEORecordInsert, \"id\" | \"created_at\" | \"updated_at\"> {\r\n return {\r\n user_id: record.userId,\r\n route_path: record.routePath,\r\n title: record.title ?? null,\r\n description: record.description ?? null,\r\n keywords: record.keywords ?? null,\r\n og_title: record.ogTitle ?? null,\r\n og_description: record.ogDescription ?? null,\r\n og_image_url: record.ogImageUrl ?? null,\r\n og_image_width: record.ogImageWidth ?? null,\r\n og_image_height: record.ogImageHeight ?? null,\r\n og_type: record.ogType ?? null,\r\n og_url: record.ogUrl ?? null,\r\n og_site_name: record.ogSiteName ?? null,\r\n twitter_card: record.twitterCard ?? null,\r\n twitter_title: record.twitterTitle ?? null,\r\n twitter_description: record.twitterDescription ?? null,\r\n twitter_image_url: record.twitterImageUrl ?? null,\r\n twitter_site: record.twitterSite ?? null,\r\n twitter_creator: record.twitterCreator ?? null,\r\n canonical_url: record.canonicalUrl ?? null,\r\n robots: record.robots ?? null,\r\n author: record.author ?? null,\r\n published_time: record.publishedTime?.toISOString() ?? null,\r\n modified_time: record.modifiedTime?.toISOString() ?? null,\r\n structured_data: (record.structuredData as unknown as Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"][\"structured_data\"]) ?? null,\r\n };\r\n}\r\n\r\n// Transform SEORecord to database update format\r\nfunction transformToUpdate(\r\n record: Omit<UpdateSEORecord, \"id\">\r\n): Omit<SEORecordUpdate, \"updated_at\"> {\r\n const update: Partial<SEORecordUpdate> = {};\r\n\r\n if (record.routePath !== undefined) update.route_path = record.routePath;\r\n if (record.title !== undefined) update.title = record.title ?? null;\r\n if (record.description !== undefined)\r\n update.description = record.description ?? null;\r\n if (record.keywords !== undefined) update.keywords = record.keywords ?? null;\r\n if (record.ogTitle !== undefined) update.og_title = record.ogTitle ?? null;\r\n if (record.ogDescription !== undefined)\r\n update.og_description = record.ogDescription ?? null;\r\n if (record.ogImageUrl !== undefined)\r\n update.og_image_url = record.ogImageUrl ?? null;\r\n if (record.ogImageWidth !== undefined)\r\n update.og_image_width = record.ogImageWidth ?? null;\r\n if (record.ogImageHeight !== undefined)\r\n update.og_image_height = record.ogImageHeight ?? null;\r\n if (record.ogType !== undefined) update.og_type = record.ogType ?? null;\r\n if (record.ogUrl !== undefined) update.og_url = record.ogUrl ?? null;\r\n if (record.ogSiteName !== undefined)\r\n update.og_site_name = record.ogSiteName ?? null;\r\n if (record.twitterCard !== undefined)\r\n update.twitter_card = record.twitterCard ?? null;\r\n if (record.twitterTitle !== undefined)\r\n update.twitter_title = record.twitterTitle ?? null;\r\n if (record.twitterDescription !== undefined)\r\n update.twitter_description = record.twitterDescription ?? null;\r\n if (record.twitterImageUrl !== undefined)\r\n update.twitter_image_url = record.twitterImageUrl ?? null;\r\n if (record.twitterSite !== undefined)\r\n update.twitter_site = record.twitterSite ?? null;\r\n if (record.twitterCreator !== undefined)\r\n update.twitter_creator = record.twitterCreator ?? null;\r\n if (record.canonicalUrl !== undefined)\r\n update.canonical_url = record.canonicalUrl ?? null;\r\n if (record.robots !== undefined) update.robots = record.robots ?? null;\r\n if (record.author !== undefined) update.author = record.author ?? null;\r\n if (record.publishedTime !== undefined)\r\n update.published_time = record.publishedTime?.toISOString() ?? null;\r\n if (record.modifiedTime !== undefined)\r\n update.modified_time = record.modifiedTime?.toISOString() ?? null;\r\n if (record.structuredData !== undefined)\r\n update.structured_data = (record.structuredData as unknown as Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"][\"structured_data\"]) ?? null;\r\n if (record.validationStatus !== undefined)\r\n update.validation_status = record.validationStatus ?? null;\r\n if (record.lastValidatedAt !== undefined)\r\n update.last_validated_at = record.lastValidatedAt?.toISOString() ?? null;\r\n if (record.validationErrors !== undefined)\r\n update.validation_errors = (record.validationErrors as unknown as Database[\"public\"][\"Tables\"][\"seo_records\"][\"Row\"][\"validation_errors\"]) ?? null;\r\n\r\n return update;\r\n}\r\n\r\n// Result type for operations\r\nexport type Result<T, E = Error> =\r\n | { success: true; data: T }\r\n | { success: false; error: E };\r\n\r\n/**\r\n * Get all SEO records for the current user\r\n */\r\nexport async function getSEORecords(): Promise<Result<SEORecord[]>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .select(\"*\")\r\n .eq(\"user_id\", user.id)\r\n .order(\"created_at\", { ascending: false });\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n const records = (data || []).map(transformRowToSEORecord);\r\n return { success: true, data: records };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Get a single SEO record by ID\r\n */\r\nexport async function getSEORecordById(\r\n id: string\r\n): Promise<Result<SEORecord>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .select(\"*\")\r\n .eq(\"id\", id)\r\n .eq(\"user_id\", user.id)\r\n .single();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n if (!data) {\r\n return {\r\n success: false,\r\n error: new Error(\"SEO record not found\"),\r\n };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Get SEO record by route path\r\n */\r\nexport async function getSEORecordByRoute(\r\n routePath: string\r\n): Promise<Result<SEORecord | null>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .select(\"*\")\r\n .eq(\"route_path\", routePath)\r\n .eq(\"user_id\", user.id)\r\n .maybeSingle();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n if (!data) {\r\n return { success: true, data: null };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Create a new SEO record\r\n */\r\nexport async function createSEORecord(\r\n record: CreateSEORecord\r\n): Promise<Result<SEORecord>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const insertData = transformToInsert({ ...record, userId: user.id });\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .insert(insertData)\r\n .select()\r\n .single();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Update an existing SEO record\r\n */\r\nexport async function updateSEORecord(\r\n record: UpdateSEORecord\r\n): Promise<Result<SEORecord>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { id, ...updateData } = record;\r\n const transformedUpdate = transformToUpdate(updateData);\r\n\r\n const { data, error } = await supabase\r\n .from(\"seo_records\")\r\n .update(transformedUpdate)\r\n .eq(\"id\", id)\r\n .eq(\"user_id\", user.id)\r\n .select()\r\n .single();\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n if (!data) {\r\n return {\r\n success: false,\r\n error: new Error(\"SEO record not found\"),\r\n };\r\n }\r\n\r\n return { success: true, data: transformRowToSEORecord(data) };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Delete an SEO record\r\n */\r\nexport async function deleteSEORecord(id: string): Promise<Result<void>> {\r\n try {\r\n const supabase = await createClient();\r\n const {\r\n data: { user },\r\n } = await supabase.auth.getUser();\r\n\r\n if (!user) {\r\n return {\r\n success: false,\r\n error: new Error(\"User not authenticated\"),\r\n };\r\n }\r\n\r\n const { error } = await supabase\r\n .from(\"seo_records\")\r\n .delete()\r\n .eq(\"id\", id)\r\n .eq(\"user_id\", user.id);\r\n\r\n if (error) {\r\n return { success: false, error };\r\n }\r\n\r\n return { success: true, data: undefined };\r\n } catch (error) {\r\n return {\r\n success: false,\r\n error: error instanceof Error ? error : new Error(\"Unknown error\"),\r\n };\r\n }\r\n}\r\n","/**\r\n * File-based storage adapter\r\n * Stores SEO records in a JSON file\r\n * No database required!\r\n */\r\n\r\nimport { promises as fs } from \"fs\";\r\nimport { join } from \"path\";\r\nimport type { StorageAdapter } from \"./storage-adapter\";\r\nimport type { CreateSEORecord, UpdateSEORecord, SEORecord } from \"../../lib/validation/seo-schema\";\r\n\r\nexport class FileStorage implements StorageAdapter {\r\n private filePath: string;\r\n private records: SEORecord[] = [];\r\n private initialized = false;\r\n\r\n constructor(filePath: string = \"seo-records.json\") {\r\n this.filePath = filePath;\r\n }\r\n\r\n private async ensureInitialized() {\r\n if (this.initialized) return;\r\n\r\n try {\r\n const data = await fs.readFile(this.filePath, \"utf-8\");\r\n this.records = JSON.parse(data);\r\n } catch (error: any) {\r\n if (error.code === \"ENOENT\") {\r\n // File doesn't exist, start with empty array\r\n this.records = [];\r\n await this.save();\r\n } else {\r\n throw error;\r\n }\r\n }\r\n this.initialized = true;\r\n }\r\n\r\n private async save() {\r\n await fs.writeFile(this.filePath, JSON.stringify(this.records, null, 2), \"utf-8\");\r\n }\r\n\r\n async isAvailable(): Promise<boolean> {\r\n try {\r\n // Check if we can write to the directory\r\n const dir = this.filePath.includes(\"/\") ? this.filePath.substring(0, this.filePath.lastIndexOf(\"/\")) : \".\";\r\n await fs.access(dir);\r\n return true;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n async getRecords(): Promise<SEORecord[]> {\r\n await this.ensureInitialized();\r\n return [...this.records];\r\n }\r\n\r\n async getRecordById(id: string): Promise<SEORecord | null> {\r\n await this.ensureInitialized();\r\n return this.records.find((r) => r.id === id) || null;\r\n }\r\n\r\n async getRecordByRoute(routePath: string): Promise<SEORecord | null> {\r\n await this.ensureInitialized();\r\n return this.records.find((r) => r.routePath === routePath) || null;\r\n }\r\n\r\n async createRecord(record: CreateSEORecord): Promise<SEORecord> {\r\n await this.ensureInitialized();\r\n \r\n const newRecord: SEORecord = {\r\n id: typeof crypto !== \"undefined\" && crypto.randomUUID \r\n ? crypto.randomUUID() \r\n : `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`,\r\n userId: \"file-user\", // File storage doesn't need user IDs\r\n routePath: record.routePath,\r\n title: record.title,\r\n description: record.description,\r\n keywords: record.keywords,\r\n ogTitle: record.ogTitle,\r\n ogDescription: record.ogDescription,\r\n ogImageUrl: record.ogImageUrl,\r\n ogImageWidth: record.ogImageWidth,\r\n ogImageHeight: record.ogImageHeight,\r\n ogType: record.ogType,\r\n ogUrl: record.ogUrl,\r\n ogSiteName: record.ogSiteName,\r\n twitterCard: record.twitterCard,\r\n twitterTitle: record.twitterTitle,\r\n twitterDescription: record.twitterDescription,\r\n twitterImageUrl: record.twitterImageUrl,\r\n twitterSite: record.twitterSite,\r\n twitterCreator: record.twitterCreator,\r\n canonicalUrl: record.canonicalUrl,\r\n robots: record.robots,\r\n author: record.author,\r\n publishedTime: record.publishedTime,\r\n modifiedTime: record.modifiedTime,\r\n structuredData: record.structuredData,\r\n validationStatus: \"pending\",\r\n lastValidatedAt: undefined,\r\n validationErrors: undefined,\r\n };\r\n\r\n this.records.push(newRecord);\r\n await this.save();\r\n return newRecord;\r\n }\r\n\r\n async updateRecord(record: UpdateSEORecord): Promise<SEORecord> {\r\n await this.ensureInitialized();\r\n \r\n const index = this.records.findIndex((r) => r.id === record.id);\r\n if (index === -1) {\r\n throw new Error(`SEO record with id ${record.id} not found`);\r\n }\r\n\r\n const updated: SEORecord = {\r\n ...this.records[index],\r\n ...record,\r\n };\r\n\r\n this.records[index] = updated;\r\n await this.save();\r\n return updated;\r\n }\r\n\r\n async deleteRecord(id: string): Promise<void> {\r\n await this.ensureInitialized();\r\n \r\n const index = this.records.findIndex((r) => r.id === id);\r\n if (index === -1) {\r\n throw new Error(`SEO record with id ${id} not found`);\r\n }\r\n\r\n this.records.splice(index, 1);\r\n await this.save();\r\n }\r\n}\r\n","/**\r\n * Supabase Storage Adapter\r\n * Wraps the existing Supabase database functions\r\n */\r\n\r\nimport type { StorageAdapter } from \"./storage-adapter\";\r\nimport type { CreateSEORecord, UpdateSEORecord, SEORecord } from \"../../lib/validation/seo-schema\";\r\nimport {\r\n getSEORecords,\r\n getSEORecordById,\r\n getSEORecordByRoute,\r\n createSEORecord,\r\n updateSEORecord,\r\n deleteSEORecord,\r\n} from \"../database/seo-records\";\r\n\r\nexport class SupabaseStorage implements StorageAdapter {\r\n constructor(\r\n private supabaseUrl: string,\r\n private supabaseKey: string\r\n ) {\r\n // Set environment variables for database functions\r\n if (typeof process !== \"undefined\") {\r\n process.env.NEXT_PUBLIC_SUPABASE_URL = supabaseUrl;\r\n process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = supabaseKey;\r\n }\r\n }\r\n\r\n async isAvailable(): Promise<boolean> {\r\n try {\r\n const result = await getSEORecords();\r\n return result.success;\r\n } catch {\r\n return false;\r\n }\r\n }\r\n\r\n async getRecords(): Promise<SEORecord[]> {\r\n const result = await getSEORecords();\r\n if (!result.success) {\r\n throw new Error(result.error?.message || \"Failed to get records\");\r\n }\r\n return result.data;\r\n }\r\n\r\n async getRecordById(id: string): Promise<SEORecord | null> {\r\n const result = await getSEORecordById(id);\r\n if (!result.success) {\r\n if (result.error?.message?.includes(\"not found\")) {\r\n return null;\r\n }\r\n throw new Error(result.error?.message || \"Failed to get record\");\r\n }\r\n return result.data || null;\r\n }\r\n\r\n async getRecordByRoute(routePath: string): Promise<SEORecord | null> {\r\n const result = await getSEORecordByRoute(routePath);\r\n if (!result.success) {\r\n if (result.error?.message?.includes(\"not found\")) {\r\n return null;\r\n }\r\n throw new Error(result.error?.message || \"Failed to get record\");\r\n }\r\n return result.data || null;\r\n }\r\n\r\n async createRecord(record: CreateSEORecord): Promise<SEORecord> {\r\n const result = await createSEORecord(record);\r\n if (!result.success) {\r\n throw new Error(result.error?.message || \"Failed to create record\");\r\n }\r\n return result.data;\r\n }\r\n\r\n async updateRecord(record: UpdateSEORecord): Promise<SEORecord> {\r\n const result = await updateSEORecord(record);\r\n if (!result.success) {\r\n throw new Error(result.error?.message || \"Failed to update record\");\r\n }\r\n return result.data;\r\n }\r\n\r\n async deleteRecord(id: string): Promise<void> {\r\n const result = await deleteSEORecord(id);\r\n if (!result.success) {\r\n throw new Error(result.error?.message || \"Failed to delete record\");\r\n }\r\n }\r\n}\r\n","/**\r\n * Storage Factory\r\n * Creates the appropriate storage adapter based on configuration\r\n */\r\n\r\nimport type { StorageAdapter, StorageConfig } from \"./storage-adapter\";\r\nimport { FileStorage } from \"./file-storage\";\r\nimport { SupabaseStorage } from \"./supabase-storage\";\r\n\r\nexport function createStorageAdapter(config: StorageConfig): StorageAdapter {\r\n switch (config.type) {\r\n case \"file\":\r\n return new FileStorage(config.filePath || \"seo-records.json\");\r\n \r\n case \"supabase\":\r\n if (!config.supabaseUrl || !config.supabaseKey) {\r\n throw new Error(\"Supabase URL and key are required for Supabase storage\");\r\n }\r\n return new SupabaseStorage(config.supabaseUrl, config.supabaseKey);\r\n \r\n case \"memory\":\r\n // For testing - stores in memory only\r\n return new FileStorage(\":memory:\");\r\n \r\n default:\r\n throw new Error(`Unsupported storage type: ${config.type}`);\r\n }\r\n}\r\n\r\n/**\r\n * Auto-detect storage type from environment\r\n */\r\nexport function detectStorageConfig(): StorageConfig {\r\n // Check for Supabase config\r\n if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {\r\n return {\r\n type: \"supabase\",\r\n supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,\r\n supabaseKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,\r\n };\r\n }\r\n\r\n // Check for file storage config\r\n if (process.env.SEO_CONSOLE_STORAGE_PATH) {\r\n return {\r\n type: \"file\",\r\n filePath: process.env.SEO_CONSOLE_STORAGE_PATH,\r\n };\r\n }\r\n\r\n // Default to file storage\r\n return {\r\n type: \"file\",\r\n filePath: \"seo-records.json\",\r\n };\r\n}\r\n","import type { Metadata } from \"next\";\r\nimport { getSEORecordByRoute } from \"../lib/database/seo-records\";\r\nimport { detectStorageConfig, createStorageAdapter } from \"../lib/storage/storage-factory\";\r\nimport type { SEORecord } from \"../lib/validation/seo-schema\";\r\n\r\nexport interface GenerateMetadataOptions {\r\n routePath?: string;\r\n fallback?: Partial<Metadata>;\r\n}\r\n\r\n/**\r\n * Generate Next.js metadata from SEO records\r\n * \r\n * @param options - Configuration options\r\n * @param options.routePath - The route path to look up (defaults to current route)\r\n * @param options.fallback - Fallback metadata if no SEO record is found\r\n * @returns Next.js Metadata object\r\n * \r\n * @example\r\n * ```ts\r\n * export async function generateMetadata(): Promise<Metadata> {\r\n * return useGenerateMetadata({\r\n * routePath: \"/about\",\r\n * fallback: {\r\n * title: \"About Us\",\r\n * description: \"Learn more about our company\"\r\n * }\r\n * });\r\n * }\r\n * ```\r\n */\r\nexport async function useGenerateMetadata(\r\n options: GenerateMetadataOptions = {} as GenerateMetadataOptions\r\n): Promise<Metadata> {\r\n const { routePath, fallback = {} as Partial<Metadata> } = options;\r\n\r\n // If no route path provided, return fallback only\r\n if (!routePath) {\r\n return {\r\n title: fallback.title,\r\n description: fallback.description,\r\n ...fallback,\r\n };\r\n }\r\n\r\n // Try to use storage adapter first (for file storage support)\r\n // Fall back to direct Supabase if storage adapter not available\r\n let record: SEORecord | null = null;\r\n\r\n try {\r\n const storageConfig = detectStorageConfig();\r\n if (storageConfig.type === \"file\" || storageConfig.type === \"memory\") {\r\n const storage = createStorageAdapter(storageConfig);\r\n record = await storage.getRecordByRoute(routePath);\r\n } else {\r\n // Use existing Supabase function\r\n const result = await getSEORecordByRoute(routePath);\r\n record = result.success ? result.data || null : null;\r\n }\r\n } catch (error) {\r\n // Fallback to Supabase if storage adapter fails\r\n const result = await getSEORecordByRoute(routePath);\r\n record = result.success ? result.data || null : null;\r\n }\r\n\r\n if (!record) {\r\n // Return fallback if record not found or error occurred\r\n return {\r\n title: fallback.title,\r\n description: fallback.description,\r\n ...fallback,\r\n };\r\n }\r\n const metadata: Partial<Metadata> = {};\r\n\r\n // Basic metadata\r\n if (record.title) {\r\n metadata.title = record.title;\r\n }\r\n if (record.description) {\r\n metadata.description = record.description;\r\n }\r\n if (record.keywords && record.keywords.length > 0) {\r\n metadata.keywords = record.keywords;\r\n }\r\n if (record.author) {\r\n metadata.authors = [{ name: record.author }];\r\n }\r\n\r\n // Open Graph metadata\r\n if (\r\n record.ogTitle ||\r\n record.ogDescription ||\r\n record.ogImageUrl ||\r\n record.ogType\r\n ) {\r\n // Next.js only supports specific OG types\r\n const supportedOGTypes = [\"website\", \"article\", \"book\", \"profile\"] as const;\r\n const ogType = record.ogType && supportedOGTypes.includes(record.ogType as typeof supportedOGTypes[number])\r\n ? (record.ogType as typeof supportedOGTypes[number])\r\n : \"website\";\r\n\r\n const openGraph: NonNullable<Metadata[\"openGraph\"]> = {\r\n type: ogType,\r\n title: record.ogTitle || record.title || undefined,\r\n description: record.ogDescription || record.description || undefined,\r\n url: record.ogUrl || undefined,\r\n siteName: record.ogSiteName || undefined,\r\n };\r\n\r\n if (record.ogImageUrl) {\r\n openGraph.images = [\r\n {\r\n url: record.ogImageUrl,\r\n width: record.ogImageWidth || undefined,\r\n height: record.ogImageHeight || undefined,\r\n alt: record.ogTitle || record.title || undefined,\r\n },\r\n ];\r\n }\r\n\r\n // For article type, add published/modified times\r\n if (ogType === \"article\") {\r\n const articleOpenGraph = {\r\n ...openGraph,\r\n ...(record.publishedTime && {\r\n publishedTime: record.publishedTime.toISOString(),\r\n }),\r\n ...(record.modifiedTime && {\r\n modifiedTime: record.modifiedTime.toISOString(),\r\n }),\r\n } as Metadata[\"openGraph\"];\r\n metadata.openGraph = articleOpenGraph;\r\n } else {\r\n metadata.openGraph = openGraph;\r\n }\r\n }\r\n\r\n // Twitter Card metadata\r\n if (\r\n record.twitterCard ||\r\n record.twitterTitle ||\r\n record.twitterDescription ||\r\n record.twitterImageUrl\r\n ) {\r\n metadata.twitter = {\r\n card: record.twitterCard || \"summary\",\r\n title: record.twitterTitle || record.ogTitle || record.title || undefined,\r\n description:\r\n record.twitterDescription ||\r\n record.ogDescription ||\r\n record.description ||\r\n undefined,\r\n images: record.twitterImageUrl\r\n ? [record.twitterImageUrl]\r\n : undefined,\r\n site: record.twitterSite || undefined,\r\n creator: record.twitterCreator || undefined,\r\n };\r\n }\r\n\r\n // Canonical URL\r\n if (record.canonicalUrl) {\r\n metadata.alternates = {\r\n canonical: record.canonicalUrl,\r\n };\r\n }\r\n\r\n // Robots\r\n if (record.robots) {\r\n metadata.robots = record.robots as Metadata[\"robots\"];\r\n }\r\n\r\n // Merge with fallback (fallback takes precedence for missing values)\r\n return {\r\n ...fallback,\r\n ...metadata,\r\n // Ensure title and description from record override fallback if present\r\n title: record.title || fallback.title,\r\n description: record.description || fallback.description,\r\n // Merge openGraph if both exist\r\n openGraph: fallback.openGraph\r\n ? { ...metadata.openGraph, ...fallback.openGraph }\r\n : metadata.openGraph,\r\n // Merge twitter if both exist\r\n twitter: fallback.twitter\r\n ? { ...metadata.twitter, ...fallback.twitter }\r\n : metadata.twitter,\r\n };\r\n}\r\n\r\n/**\r\n * Helper to get route path from Next.js params\r\n * Useful for dynamic routes\r\n * \r\n * @example\r\n * ```ts\r\n * export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {\r\n * const routePath = getRoutePathFromParams(params, \"/blog/[slug]\");\r\n * return useGenerateMetadata({ routePath });\r\n * }\r\n * ```\r\n */\r\nexport function getRoutePathFromParams(\r\n params: Record<string, string | string[]>,\r\n pattern: string\r\n): string {\r\n let routePath = pattern;\r\n\r\n // Replace [param] and [...param] patterns with actual values\r\n for (const [key, value] of Object.entries(params)) {\r\n const paramValue = Array.isArray(value) ? value.join(\"/\") : value;\r\n routePath = routePath.replace(`[${key}]`, paramValue);\r\n routePath = routePath.replace(`[...${key}]`, paramValue);\r\n }\r\n\r\n return routePath;\r\n}\r\n"],"mappings":"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","names":[]}
|