@seo-console/package 1.0.4 → 1.1.0
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/README.md +29 -32
- package/dist/hooks/index.js +6 -408
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +6 -408
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.d.mts +6 -9
- package/dist/index.d.ts +6 -9
- package/dist/index.js +3 -399
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -399
- package/dist/index.mjs.map +1 -1
- package/dist/{robots-generator-D6T5HVNx.d.mts → robots-generator-9d9aTULk.d.mts} +1 -1
- package/dist/{robots-generator-B1KOf8vn.d.ts → robots-generator-CYA9Ofu_.d.ts} +1 -1
- package/dist/server.d.mts +3 -36
- package/dist/server.d.ts +3 -36
- package/dist/server.js +6 -422
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +6 -415
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ npm install @seo-console/package
|
|
|
12
12
|
|
|
13
13
|
### Step 1: Configure Storage (Optional)
|
|
14
14
|
|
|
15
|
-
The package uses **file storage
|
|
15
|
+
The package uses **file storage** (no database required). SEO records are stored in `seo-records.json` in your project root.
|
|
16
16
|
|
|
17
17
|
To customize the storage location, add to your `.env.local`:
|
|
18
18
|
|
|
@@ -20,14 +20,7 @@ To customize the storage location, add to your `.env.local`:
|
|
|
20
20
|
SEO_CONSOLE_STORAGE_PATH=./data/seo-records.json
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
```env
|
|
26
|
-
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
|
|
27
|
-
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
The package will automatically detect and use Supabase if these are set.
|
|
23
|
+
That's it! No database setup needed.
|
|
31
24
|
|
|
32
25
|
### Step 2: Create API Routes (REQUIRED - This is why you're getting 404 errors!)
|
|
33
26
|
|
|
@@ -43,7 +36,7 @@ import { createSEORecordSchema } from "@seo-console/package/server";
|
|
|
43
36
|
// GET - Fetch all SEO records
|
|
44
37
|
export async function GET() {
|
|
45
38
|
try {
|
|
46
|
-
//
|
|
39
|
+
// Get file storage adapter
|
|
47
40
|
const config = detectStorageConfig();
|
|
48
41
|
const storage = createStorageAdapter(config);
|
|
49
42
|
|
|
@@ -186,9 +179,7 @@ export async function DELETE(
|
|
|
186
179
|
}
|
|
187
180
|
```
|
|
188
181
|
|
|
189
|
-
> **Important:** These API routes use
|
|
190
|
-
> - **File storage** (default) - if no Supabase credentials are set
|
|
191
|
-
> - **Supabase** - if `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` are set
|
|
182
|
+
> **Important:** These API routes use file storage. SEO records are stored in `seo-records.json` in your project root (or the path specified in `SEO_CONSOLE_STORAGE_PATH`).
|
|
192
183
|
|
|
193
184
|
### Step 3: Add Admin Pages
|
|
194
185
|
|
|
@@ -344,15 +335,28 @@ const metadata = await useGenerateMetadata({
|
|
|
344
335
|
});
|
|
345
336
|
```
|
|
346
337
|
|
|
347
|
-
###
|
|
338
|
+
### Storage Functions
|
|
339
|
+
|
|
340
|
+
```typescript
|
|
341
|
+
import {
|
|
342
|
+
detectStorageConfig,
|
|
343
|
+
createStorageAdapter
|
|
344
|
+
} from "@seo-console/package";
|
|
345
|
+
|
|
346
|
+
// Get storage adapter (uses file storage)
|
|
347
|
+
const config = detectStorageConfig();
|
|
348
|
+
const storage = createStorageAdapter(config);
|
|
349
|
+
|
|
350
|
+
// Use storage methods
|
|
351
|
+
const records = await storage.getRecords();
|
|
352
|
+
const record = await storage.getRecordByRoute("/about");
|
|
353
|
+
await storage.createRecord({ routePath: "/contact", title: "Contact" });
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Other Server-Side Functions
|
|
348
357
|
|
|
349
358
|
```typescript
|
|
350
359
|
import {
|
|
351
|
-
getSEORecords,
|
|
352
|
-
getSEORecordByRoute,
|
|
353
|
-
createSEORecord,
|
|
354
|
-
updateSEORecord,
|
|
355
|
-
deleteSEORecord,
|
|
356
360
|
generateSitemapFromRecords,
|
|
357
361
|
generateRobotsTxt,
|
|
358
362
|
discoverNextJSRoutes,
|
|
@@ -360,21 +364,14 @@ import {
|
|
|
360
364
|
} from "@seo-console/package/server";
|
|
361
365
|
```
|
|
362
366
|
|
|
363
|
-
## Storage
|
|
364
|
-
|
|
365
|
-
### File Storage (Default)
|
|
366
|
-
|
|
367
|
-
- **No database required** - stores data in a JSON file
|
|
368
|
-
- **Perfect for small to medium sites** - simple and fast
|
|
369
|
-
- **File location**: `seo-records.json` (configurable via `SEO_CONSOLE_STORAGE_PATH`)
|
|
370
|
-
- **Automatic**: Works out of the box with no configuration
|
|
367
|
+
## Storage
|
|
371
368
|
|
|
372
|
-
|
|
369
|
+
The package uses **file storage** - SEO records are stored in a JSON file (`seo-records.json` by default).
|
|
373
370
|
|
|
374
|
-
- **
|
|
375
|
-
- **
|
|
376
|
-
- **
|
|
377
|
-
- **
|
|
371
|
+
- **No database required** - works out of the box
|
|
372
|
+
- **Simple and fast** - perfect for most sites
|
|
373
|
+
- **Version controlled** - the JSON file can be committed to git
|
|
374
|
+
- **Configurable** - set `SEO_CONSOLE_STORAGE_PATH` to customize the file location
|
|
378
375
|
|
|
379
376
|
## License
|
|
380
377
|
|
package/dist/hooks/index.js
CHANGED
|
@@ -25,311 +25,6 @@ __export(hooks_exports, {
|
|
|
25
25
|
});
|
|
26
26
|
module.exports = __toCommonJS(hooks_exports);
|
|
27
27
|
|
|
28
|
-
// src/lib/supabase/server.ts
|
|
29
|
-
var import_ssr = require("@supabase/ssr");
|
|
30
|
-
var import_headers = require("next/headers");
|
|
31
|
-
async function createClient() {
|
|
32
|
-
const cookieStore = await (0, import_headers.cookies)();
|
|
33
|
-
return (0, import_ssr.createServerClient)(
|
|
34
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL,
|
|
35
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
|
36
|
-
{
|
|
37
|
-
cookies: {
|
|
38
|
-
getAll() {
|
|
39
|
-
return cookieStore.getAll();
|
|
40
|
-
},
|
|
41
|
-
setAll(cookiesToSet) {
|
|
42
|
-
try {
|
|
43
|
-
cookiesToSet.forEach(
|
|
44
|
-
({ name, value, options }) => cookieStore.set(name, value, options)
|
|
45
|
-
);
|
|
46
|
-
} catch {
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// src/lib/database/seo-records.ts
|
|
55
|
-
function transformRowToSEORecord(row) {
|
|
56
|
-
return {
|
|
57
|
-
id: row.id,
|
|
58
|
-
userId: row.user_id,
|
|
59
|
-
routePath: row.route_path,
|
|
60
|
-
title: row.title ?? void 0,
|
|
61
|
-
description: row.description ?? void 0,
|
|
62
|
-
keywords: row.keywords ?? void 0,
|
|
63
|
-
ogTitle: row.og_title ?? void 0,
|
|
64
|
-
ogDescription: row.og_description ?? void 0,
|
|
65
|
-
ogImageUrl: row.og_image_url ?? void 0,
|
|
66
|
-
ogImageWidth: row.og_image_width ?? void 0,
|
|
67
|
-
ogImageHeight: row.og_image_height ?? void 0,
|
|
68
|
-
ogType: row.og_type ?? void 0,
|
|
69
|
-
ogUrl: row.og_url ?? void 0,
|
|
70
|
-
ogSiteName: row.og_site_name ?? void 0,
|
|
71
|
-
twitterCard: row.twitter_card ?? void 0,
|
|
72
|
-
twitterTitle: row.twitter_title ?? void 0,
|
|
73
|
-
twitterDescription: row.twitter_description ?? void 0,
|
|
74
|
-
twitterImageUrl: row.twitter_image_url ?? void 0,
|
|
75
|
-
twitterSite: row.twitter_site ?? void 0,
|
|
76
|
-
twitterCreator: row.twitter_creator ?? void 0,
|
|
77
|
-
canonicalUrl: row.canonical_url ?? void 0,
|
|
78
|
-
robots: row.robots ?? void 0,
|
|
79
|
-
author: row.author ?? void 0,
|
|
80
|
-
publishedTime: row.published_time ? new Date(row.published_time) : void 0,
|
|
81
|
-
modifiedTime: row.modified_time ? new Date(row.modified_time) : void 0,
|
|
82
|
-
structuredData: row.structured_data ? row.structured_data : void 0,
|
|
83
|
-
validationStatus: row.validation_status ?? void 0,
|
|
84
|
-
lastValidatedAt: row.last_validated_at ? new Date(row.last_validated_at) : void 0,
|
|
85
|
-
validationErrors: row.validation_errors ? row.validation_errors : void 0,
|
|
86
|
-
createdAt: new Date(row.created_at),
|
|
87
|
-
updatedAt: new Date(row.updated_at)
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
function transformToInsert(record) {
|
|
91
|
-
return {
|
|
92
|
-
user_id: record.userId,
|
|
93
|
-
route_path: record.routePath,
|
|
94
|
-
title: record.title ?? null,
|
|
95
|
-
description: record.description ?? null,
|
|
96
|
-
keywords: record.keywords ?? null,
|
|
97
|
-
og_title: record.ogTitle ?? null,
|
|
98
|
-
og_description: record.ogDescription ?? null,
|
|
99
|
-
og_image_url: record.ogImageUrl ?? null,
|
|
100
|
-
og_image_width: record.ogImageWidth ?? null,
|
|
101
|
-
og_image_height: record.ogImageHeight ?? null,
|
|
102
|
-
og_type: record.ogType ?? null,
|
|
103
|
-
og_url: record.ogUrl ?? null,
|
|
104
|
-
og_site_name: record.ogSiteName ?? null,
|
|
105
|
-
twitter_card: record.twitterCard ?? null,
|
|
106
|
-
twitter_title: record.twitterTitle ?? null,
|
|
107
|
-
twitter_description: record.twitterDescription ?? null,
|
|
108
|
-
twitter_image_url: record.twitterImageUrl ?? null,
|
|
109
|
-
twitter_site: record.twitterSite ?? null,
|
|
110
|
-
twitter_creator: record.twitterCreator ?? null,
|
|
111
|
-
canonical_url: record.canonicalUrl ?? null,
|
|
112
|
-
robots: record.robots ?? null,
|
|
113
|
-
author: record.author ?? null,
|
|
114
|
-
published_time: record.publishedTime?.toISOString() ?? null,
|
|
115
|
-
modified_time: record.modifiedTime?.toISOString() ?? null,
|
|
116
|
-
structured_data: record.structuredData ?? null
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
function transformToUpdate(record) {
|
|
120
|
-
const update = {};
|
|
121
|
-
if (record.routePath !== void 0) update.route_path = record.routePath;
|
|
122
|
-
if (record.title !== void 0) update.title = record.title ?? null;
|
|
123
|
-
if (record.description !== void 0)
|
|
124
|
-
update.description = record.description ?? null;
|
|
125
|
-
if (record.keywords !== void 0) update.keywords = record.keywords ?? null;
|
|
126
|
-
if (record.ogTitle !== void 0) update.og_title = record.ogTitle ?? null;
|
|
127
|
-
if (record.ogDescription !== void 0)
|
|
128
|
-
update.og_description = record.ogDescription ?? null;
|
|
129
|
-
if (record.ogImageUrl !== void 0)
|
|
130
|
-
update.og_image_url = record.ogImageUrl ?? null;
|
|
131
|
-
if (record.ogImageWidth !== void 0)
|
|
132
|
-
update.og_image_width = record.ogImageWidth ?? null;
|
|
133
|
-
if (record.ogImageHeight !== void 0)
|
|
134
|
-
update.og_image_height = record.ogImageHeight ?? null;
|
|
135
|
-
if (record.ogType !== void 0) update.og_type = record.ogType ?? null;
|
|
136
|
-
if (record.ogUrl !== void 0) update.og_url = record.ogUrl ?? null;
|
|
137
|
-
if (record.ogSiteName !== void 0)
|
|
138
|
-
update.og_site_name = record.ogSiteName ?? null;
|
|
139
|
-
if (record.twitterCard !== void 0)
|
|
140
|
-
update.twitter_card = record.twitterCard ?? null;
|
|
141
|
-
if (record.twitterTitle !== void 0)
|
|
142
|
-
update.twitter_title = record.twitterTitle ?? null;
|
|
143
|
-
if (record.twitterDescription !== void 0)
|
|
144
|
-
update.twitter_description = record.twitterDescription ?? null;
|
|
145
|
-
if (record.twitterImageUrl !== void 0)
|
|
146
|
-
update.twitter_image_url = record.twitterImageUrl ?? null;
|
|
147
|
-
if (record.twitterSite !== void 0)
|
|
148
|
-
update.twitter_site = record.twitterSite ?? null;
|
|
149
|
-
if (record.twitterCreator !== void 0)
|
|
150
|
-
update.twitter_creator = record.twitterCreator ?? null;
|
|
151
|
-
if (record.canonicalUrl !== void 0)
|
|
152
|
-
update.canonical_url = record.canonicalUrl ?? null;
|
|
153
|
-
if (record.robots !== void 0) update.robots = record.robots ?? null;
|
|
154
|
-
if (record.author !== void 0) update.author = record.author ?? null;
|
|
155
|
-
if (record.publishedTime !== void 0)
|
|
156
|
-
update.published_time = record.publishedTime?.toISOString() ?? null;
|
|
157
|
-
if (record.modifiedTime !== void 0)
|
|
158
|
-
update.modified_time = record.modifiedTime?.toISOString() ?? null;
|
|
159
|
-
if (record.structuredData !== void 0)
|
|
160
|
-
update.structured_data = record.structuredData ?? null;
|
|
161
|
-
if (record.validationStatus !== void 0)
|
|
162
|
-
update.validation_status = record.validationStatus ?? null;
|
|
163
|
-
if (record.lastValidatedAt !== void 0)
|
|
164
|
-
update.last_validated_at = record.lastValidatedAt?.toISOString() ?? null;
|
|
165
|
-
if (record.validationErrors !== void 0)
|
|
166
|
-
update.validation_errors = record.validationErrors ?? null;
|
|
167
|
-
return update;
|
|
168
|
-
}
|
|
169
|
-
async function getSEORecords() {
|
|
170
|
-
try {
|
|
171
|
-
const supabase = await createClient();
|
|
172
|
-
const {
|
|
173
|
-
data: { user }
|
|
174
|
-
} = await supabase.auth.getUser();
|
|
175
|
-
if (!user) {
|
|
176
|
-
return {
|
|
177
|
-
success: false,
|
|
178
|
-
error: new Error("User not authenticated")
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
const { data, error } = await supabase.from("seo_records").select("*").eq("user_id", user.id).order("created_at", { ascending: false });
|
|
182
|
-
if (error) {
|
|
183
|
-
return { success: false, error };
|
|
184
|
-
}
|
|
185
|
-
const records = (data || []).map(transformRowToSEORecord);
|
|
186
|
-
return { success: true, data: records };
|
|
187
|
-
} catch (error) {
|
|
188
|
-
return {
|
|
189
|
-
success: false,
|
|
190
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
async function getSEORecordById(id) {
|
|
195
|
-
try {
|
|
196
|
-
const supabase = await createClient();
|
|
197
|
-
const {
|
|
198
|
-
data: { user }
|
|
199
|
-
} = await supabase.auth.getUser();
|
|
200
|
-
if (!user) {
|
|
201
|
-
return {
|
|
202
|
-
success: false,
|
|
203
|
-
error: new Error("User not authenticated")
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
const { data, error } = await supabase.from("seo_records").select("*").eq("id", id).eq("user_id", user.id).single();
|
|
207
|
-
if (error) {
|
|
208
|
-
return { success: false, error };
|
|
209
|
-
}
|
|
210
|
-
if (!data) {
|
|
211
|
-
return {
|
|
212
|
-
success: false,
|
|
213
|
-
error: new Error("SEO record not found")
|
|
214
|
-
};
|
|
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 getSEORecordByRoute(routePath) {
|
|
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 { data, error } = await supabase.from("seo_records").select("*").eq("route_path", routePath).eq("user_id", user.id).maybeSingle();
|
|
237
|
-
if (error) {
|
|
238
|
-
return { success: false, error };
|
|
239
|
-
}
|
|
240
|
-
if (!data) {
|
|
241
|
-
return { success: true, data: null };
|
|
242
|
-
}
|
|
243
|
-
return { success: true, data: transformRowToSEORecord(data) };
|
|
244
|
-
} catch (error) {
|
|
245
|
-
return {
|
|
246
|
-
success: false,
|
|
247
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
async function createSEORecord(record) {
|
|
252
|
-
try {
|
|
253
|
-
const supabase = await createClient();
|
|
254
|
-
const {
|
|
255
|
-
data: { user }
|
|
256
|
-
} = await supabase.auth.getUser();
|
|
257
|
-
if (!user) {
|
|
258
|
-
return {
|
|
259
|
-
success: false,
|
|
260
|
-
error: new Error("User not authenticated")
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
const insertData = transformToInsert({ ...record, userId: user.id });
|
|
264
|
-
const { data, error } = await supabase.from("seo_records").insert(insertData).select().single();
|
|
265
|
-
if (error) {
|
|
266
|
-
return { success: false, error };
|
|
267
|
-
}
|
|
268
|
-
return { success: true, data: transformRowToSEORecord(data) };
|
|
269
|
-
} catch (error) {
|
|
270
|
-
return {
|
|
271
|
-
success: false,
|
|
272
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
async function updateSEORecord(record) {
|
|
277
|
-
try {
|
|
278
|
-
const supabase = await createClient();
|
|
279
|
-
const {
|
|
280
|
-
data: { user }
|
|
281
|
-
} = await supabase.auth.getUser();
|
|
282
|
-
if (!user) {
|
|
283
|
-
return {
|
|
284
|
-
success: false,
|
|
285
|
-
error: new Error("User not authenticated")
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
const { id, ...updateData } = record;
|
|
289
|
-
const transformedUpdate = transformToUpdate(updateData);
|
|
290
|
-
const { data, error } = await supabase.from("seo_records").update(transformedUpdate).eq("id", id).eq("user_id", user.id).select().single();
|
|
291
|
-
if (error) {
|
|
292
|
-
return { success: false, error };
|
|
293
|
-
}
|
|
294
|
-
if (!data) {
|
|
295
|
-
return {
|
|
296
|
-
success: false,
|
|
297
|
-
error: new Error("SEO record not found")
|
|
298
|
-
};
|
|
299
|
-
}
|
|
300
|
-
return { success: true, data: transformRowToSEORecord(data) };
|
|
301
|
-
} catch (error) {
|
|
302
|
-
return {
|
|
303
|
-
success: false,
|
|
304
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
async function deleteSEORecord(id) {
|
|
309
|
-
try {
|
|
310
|
-
const supabase = await createClient();
|
|
311
|
-
const {
|
|
312
|
-
data: { user }
|
|
313
|
-
} = await supabase.auth.getUser();
|
|
314
|
-
if (!user) {
|
|
315
|
-
return {
|
|
316
|
-
success: false,
|
|
317
|
-
error: new Error("User not authenticated")
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
const { error } = await supabase.from("seo_records").delete().eq("id", id).eq("user_id", user.id);
|
|
321
|
-
if (error) {
|
|
322
|
-
return { success: false, error };
|
|
323
|
-
}
|
|
324
|
-
return { success: true, data: void 0 };
|
|
325
|
-
} catch (error) {
|
|
326
|
-
return {
|
|
327
|
-
success: false,
|
|
328
|
-
error: error instanceof Error ? error : new Error("Unknown error")
|
|
329
|
-
};
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
28
|
// src/lib/storage/file-storage.ts
|
|
334
29
|
var import_fs = require("fs");
|
|
335
30
|
var FileStorage = class {
|
|
@@ -440,106 +135,15 @@ var FileStorage = class {
|
|
|
440
135
|
}
|
|
441
136
|
};
|
|
442
137
|
|
|
443
|
-
// src/lib/storage/supabase-storage.ts
|
|
444
|
-
var SupabaseStorage = class {
|
|
445
|
-
constructor(supabaseUrl, supabaseKey) {
|
|
446
|
-
this.supabaseUrl = supabaseUrl;
|
|
447
|
-
this.supabaseKey = supabaseKey;
|
|
448
|
-
if (typeof process !== "undefined") {
|
|
449
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL = supabaseUrl;
|
|
450
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY = supabaseKey;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
async isAvailable() {
|
|
454
|
-
try {
|
|
455
|
-
const result = await getSEORecords();
|
|
456
|
-
return result.success;
|
|
457
|
-
} catch {
|
|
458
|
-
return false;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
async getRecords() {
|
|
462
|
-
const result = await getSEORecords();
|
|
463
|
-
if (!result.success) {
|
|
464
|
-
throw new Error(result.error?.message || "Failed to get records");
|
|
465
|
-
}
|
|
466
|
-
return result.data;
|
|
467
|
-
}
|
|
468
|
-
async getRecordById(id) {
|
|
469
|
-
const result = await getSEORecordById(id);
|
|
470
|
-
if (!result.success) {
|
|
471
|
-
if (result.error?.message?.includes("not found")) {
|
|
472
|
-
return null;
|
|
473
|
-
}
|
|
474
|
-
throw new Error(result.error?.message || "Failed to get record");
|
|
475
|
-
}
|
|
476
|
-
return result.data || null;
|
|
477
|
-
}
|
|
478
|
-
async getRecordByRoute(routePath) {
|
|
479
|
-
const result = await getSEORecordByRoute(routePath);
|
|
480
|
-
if (!result.success) {
|
|
481
|
-
if (result.error?.message?.includes("not found")) {
|
|
482
|
-
return null;
|
|
483
|
-
}
|
|
484
|
-
throw new Error(result.error?.message || "Failed to get record");
|
|
485
|
-
}
|
|
486
|
-
return result.data || null;
|
|
487
|
-
}
|
|
488
|
-
async createRecord(record) {
|
|
489
|
-
const result = await createSEORecord(record);
|
|
490
|
-
if (!result.success) {
|
|
491
|
-
throw new Error(result.error?.message || "Failed to create record");
|
|
492
|
-
}
|
|
493
|
-
return result.data;
|
|
494
|
-
}
|
|
495
|
-
async updateRecord(record) {
|
|
496
|
-
const result = await updateSEORecord(record);
|
|
497
|
-
if (!result.success) {
|
|
498
|
-
throw new Error(result.error?.message || "Failed to update record");
|
|
499
|
-
}
|
|
500
|
-
return result.data;
|
|
501
|
-
}
|
|
502
|
-
async deleteRecord(id) {
|
|
503
|
-
const result = await deleteSEORecord(id);
|
|
504
|
-
if (!result.success) {
|
|
505
|
-
throw new Error(result.error?.message || "Failed to delete record");
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
|
|
510
138
|
// src/lib/storage/storage-factory.ts
|
|
511
139
|
function createStorageAdapter(config) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
return new FileStorage(config.filePath || "seo-records.json");
|
|
515
|
-
case "supabase":
|
|
516
|
-
if (!config.supabaseUrl || !config.supabaseKey) {
|
|
517
|
-
throw new Error("Supabase URL and key are required for Supabase storage");
|
|
518
|
-
}
|
|
519
|
-
return new SupabaseStorage(config.supabaseUrl, config.supabaseKey);
|
|
520
|
-
case "memory":
|
|
521
|
-
return new FileStorage(":memory:");
|
|
522
|
-
default:
|
|
523
|
-
throw new Error(`Unsupported storage type: ${config.type}`);
|
|
524
|
-
}
|
|
140
|
+
const filePath = config?.filePath || process.env.SEO_CONSOLE_STORAGE_PATH || "seo-records.json";
|
|
141
|
+
return new FileStorage(filePath);
|
|
525
142
|
}
|
|
526
143
|
function detectStorageConfig() {
|
|
527
|
-
if (process.env.NEXT_PUBLIC_SUPABASE_URL && process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY) {
|
|
528
|
-
return {
|
|
529
|
-
type: "supabase",
|
|
530
|
-
supabaseUrl: process.env.NEXT_PUBLIC_SUPABASE_URL,
|
|
531
|
-
supabaseKey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
|
|
532
|
-
};
|
|
533
|
-
}
|
|
534
|
-
if (process.env.SEO_CONSOLE_STORAGE_PATH) {
|
|
535
|
-
return {
|
|
536
|
-
type: "file",
|
|
537
|
-
filePath: process.env.SEO_CONSOLE_STORAGE_PATH
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
144
|
return {
|
|
541
145
|
type: "file",
|
|
542
|
-
filePath: "seo-records.json"
|
|
146
|
+
filePath: process.env.SEO_CONSOLE_STORAGE_PATH || "seo-records.json"
|
|
543
147
|
};
|
|
544
148
|
}
|
|
545
149
|
|
|
@@ -556,16 +160,10 @@ async function useGenerateMetadata(options = {}) {
|
|
|
556
160
|
let record = null;
|
|
557
161
|
try {
|
|
558
162
|
const storageConfig = detectStorageConfig();
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
record = await storage.getRecordByRoute(routePath);
|
|
562
|
-
} else {
|
|
563
|
-
const result = await getSEORecordByRoute(routePath);
|
|
564
|
-
record = result.success ? result.data || null : null;
|
|
565
|
-
}
|
|
163
|
+
const storage = createStorageAdapter(storageConfig);
|
|
164
|
+
record = await storage.getRecordByRoute(routePath);
|
|
566
165
|
} catch (error) {
|
|
567
|
-
|
|
568
|
-
record = result.success ? result.data || null : null;
|
|
166
|
+
console.warn("Failed to load SEO record from storage:", error);
|
|
569
167
|
}
|
|
570
168
|
if (!record) {
|
|
571
169
|
return {
|