opacacms 0.2.0 → 0.2.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/admin/webcomponent.js +20 -2
- package/dist/{chunk-d1asgtke.js → chunk-06ks4ggh.js} +49 -40
- package/dist/{chunk-kc4jfnv7.js → chunk-2es275xs.js} +17 -17
- package/dist/{chunk-0bq155dy.js → chunk-6d1vdfwa.js} +37 -27
- package/dist/{chunk-gmee4mdc.js → chunk-bygjkgrx.js} +36 -27
- package/dist/{chunk-qb6ztvw9.js → chunk-g1jb60xd.js} +1 -1
- package/dist/chunk-jdfw4v3r.js +311 -0
- package/dist/{chunk-0gtxnxmd.js → chunk-n133qpsm.js} +40 -29
- package/dist/{chunk-esrg9qj0.js → chunk-tfnaf41w.js} +30 -30
- package/dist/cli/index.js +1 -1
- package/dist/db/adapter.d.ts +2 -2
- package/dist/db/better-sqlite.d.ts +1 -0
- package/dist/db/better-sqlite.js +1 -1
- package/dist/db/bun-sqlite.d.ts +1 -0
- package/dist/db/bun-sqlite.js +1 -1
- package/dist/db/d1.js +1 -1
- package/dist/db/index.js +5 -5
- package/dist/db/postgres.d.ts +2 -2
- package/dist/db/postgres.js +1 -1
- package/dist/db/sqlite.d.ts +1 -0
- package/dist/db/sqlite.js +1 -1
- package/dist/runtimes/bun.js +1 -1
- package/dist/runtimes/cloudflare-workers.js +1 -1
- package/dist/runtimes/next.js +1 -1
- package/dist/runtimes/node.js +1 -1
- package/dist/server.js +1 -1
- package/dist/storage/index.js +34 -25
- package/dist/types.d.ts +2 -2
- package/package.json +32 -32
- package/dist/chunk-swtcpvhf.js +0 -2442
|
@@ -22,15 +22,12 @@ import {
|
|
|
22
22
|
// src/db/better-sqlite.ts
|
|
23
23
|
import fs from "node:fs/promises";
|
|
24
24
|
import path from "node:path";
|
|
25
|
-
import { createRequire } from "node:module";
|
|
26
25
|
import { CompiledQuery, FileMigrationProvider, Kysely, Migrator, SqliteDialect } from "kysely";
|
|
27
|
-
var require2 = createRequire(import.meta.url);
|
|
28
|
-
var Database = require2("better-sqlite3");
|
|
29
|
-
|
|
30
26
|
class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
27
|
+
path;
|
|
31
28
|
name = "better-sqlite3";
|
|
32
29
|
_rawDb;
|
|
33
|
-
_db;
|
|
30
|
+
_db = null;
|
|
34
31
|
_collections = [];
|
|
35
32
|
_globals = [];
|
|
36
33
|
push;
|
|
@@ -40,27 +37,39 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
40
37
|
return this._rawDb;
|
|
41
38
|
}
|
|
42
39
|
get db() {
|
|
40
|
+
if (!this._db)
|
|
41
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
43
42
|
return this._db;
|
|
44
43
|
}
|
|
45
44
|
constructor(path2, options) {
|
|
46
45
|
super();
|
|
47
|
-
this.
|
|
46
|
+
this.path = path2;
|
|
47
|
+
this.push = options?.push ?? true;
|
|
48
|
+
this.pushDestructive = options?.pushDestructive ?? false;
|
|
49
|
+
this.migrationDir = options?.migrationDir ?? "./migrations";
|
|
50
|
+
}
|
|
51
|
+
async connect() {
|
|
52
|
+
if (this._db)
|
|
53
|
+
return;
|
|
54
|
+
const { createRequire } = await import("node:module");
|
|
55
|
+
const require2 = createRequire(import.meta.url);
|
|
56
|
+
const Database = require2("better-sqlite3");
|
|
57
|
+
this._rawDb = new Database(this.path);
|
|
48
58
|
this._db = new Kysely({
|
|
49
59
|
dialect: new SqliteDialect({
|
|
50
60
|
database: this._rawDb
|
|
51
61
|
})
|
|
52
62
|
});
|
|
53
|
-
this.push = options?.push ?? true;
|
|
54
|
-
this.pushDestructive = options?.pushDestructive ?? false;
|
|
55
|
-
this.migrationDir = options?.migrationDir ?? "./migrations";
|
|
56
63
|
}
|
|
57
|
-
async connect() {}
|
|
58
64
|
async disconnect() {
|
|
59
|
-
|
|
65
|
+
if (this._db)
|
|
66
|
+
await this.db.destroy();
|
|
60
67
|
}
|
|
61
68
|
async unsafe(query, params) {
|
|
69
|
+
if (!this._db)
|
|
70
|
+
await this.connect();
|
|
62
71
|
const compiled = CompiledQuery.raw(query, params || []);
|
|
63
|
-
const result = await this.
|
|
72
|
+
const result = await this.db.executeQuery(compiled);
|
|
64
73
|
return result.rows;
|
|
65
74
|
}
|
|
66
75
|
async coerceData(collection, data) {
|
|
@@ -104,7 +113,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
104
113
|
}
|
|
105
114
|
async count(collection, query) {
|
|
106
115
|
const tableName = toSnakeCase(collection);
|
|
107
|
-
let qb = this.
|
|
116
|
+
let qb = this.db.selectFrom(tableName).select((eb) => eb.fn.count("id").as("count"));
|
|
108
117
|
if (query && Object.keys(query).length > 0) {
|
|
109
118
|
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
110
119
|
}
|
|
@@ -113,7 +122,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
113
122
|
}
|
|
114
123
|
async create(collection, data) {
|
|
115
124
|
const tableName = toSnakeCase(collection);
|
|
116
|
-
return this.
|
|
125
|
+
return this.db.transaction().execute(async (tx) => {
|
|
117
126
|
const colDef = this._collections.find((c) => c.slug === collection);
|
|
118
127
|
const jsonFields = colDef?.fields.filter((f) => ["richtext", "json", "file"].includes(f.type)).map((f) => f.name) || [];
|
|
119
128
|
const flatData = flattenPayload(data, "", jsonFields);
|
|
@@ -194,7 +203,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
194
203
|
}
|
|
195
204
|
async findOne(collection, query, tx) {
|
|
196
205
|
const tableName = toSnakeCase(collection);
|
|
197
|
-
const executor = tx || this.
|
|
206
|
+
const executor = tx || this.db;
|
|
198
207
|
let qb = executor.selectFrom(tableName).selectAll();
|
|
199
208
|
if (query && Object.keys(query).length > 0) {
|
|
200
209
|
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
@@ -247,7 +256,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
247
256
|
const offset = (page - 1) * limit;
|
|
248
257
|
const total = await this.count(collection, query);
|
|
249
258
|
const tableName = toSnakeCase(collection);
|
|
250
|
-
let qb = this.
|
|
259
|
+
let qb = this.db.selectFrom(tableName).selectAll().limit(limit).offset(offset);
|
|
251
260
|
if (query && Object.keys(query).length > 0) {
|
|
252
261
|
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
253
262
|
}
|
|
@@ -286,7 +295,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
286
295
|
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
287
296
|
const joinTableName = `${toSnakeCase(collection)}_${snakeName}_relations`.toLowerCase();
|
|
288
297
|
try {
|
|
289
|
-
const allRelations = await this.
|
|
298
|
+
const allRelations = await this.db.selectFrom(joinTableName).selectAll().where("source_id", "in", rowIds).orderBy("order", "asc").execute();
|
|
290
299
|
const relationsBySource = allRelations.reduce((acc, r) => {
|
|
291
300
|
if (!acc[r.source_id])
|
|
292
301
|
acc[r.source_id] = [];
|
|
@@ -310,7 +319,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
310
319
|
for (const b of field.blocks) {
|
|
311
320
|
const blockTableName = `${collection}_${snakeName}_${toSnakeCase(b.slug)}`.toLowerCase();
|
|
312
321
|
try {
|
|
313
|
-
const allBlocks = await this.
|
|
322
|
+
const allBlocks = await this.db.selectFrom(blockTableName).selectAll().where("_parent_id", "in", rowIds).execute();
|
|
314
323
|
for (const blk of allBlocks) {
|
|
315
324
|
const uf = unflattenRow(blk);
|
|
316
325
|
uf.blockType = blk.block_type;
|
|
@@ -356,7 +365,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
356
365
|
}
|
|
357
366
|
async update(collection, query, data) {
|
|
358
367
|
const tableName = toSnakeCase(collection);
|
|
359
|
-
return this.
|
|
368
|
+
return this.db.transaction().execute(async (tx) => {
|
|
360
369
|
let normalizedQuery = query;
|
|
361
370
|
if (typeof query !== "object" || query === null) {
|
|
362
371
|
normalizedQuery = { id: query };
|
|
@@ -447,7 +456,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
447
456
|
const current = await this.findOne(collection, normalizedQuery);
|
|
448
457
|
if (!current)
|
|
449
458
|
return false;
|
|
450
|
-
await this.
|
|
459
|
+
await this.db.transaction().execute(async (tx) => {
|
|
451
460
|
const colDef = this._collections.find((c) => c.slug === collection);
|
|
452
461
|
if (colDef) {
|
|
453
462
|
for (const field of colDef.fields) {
|
|
@@ -473,7 +482,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
473
482
|
}
|
|
474
483
|
async updateMany(collection, query, data) {
|
|
475
484
|
const tableName = toSnakeCase(collection);
|
|
476
|
-
return this.
|
|
485
|
+
return this.db.transaction().execute(async (tx) => {
|
|
477
486
|
let qb = tx.updateTable(tableName);
|
|
478
487
|
if (query && Object.keys(query).length > 0) {
|
|
479
488
|
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
@@ -502,7 +511,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
502
511
|
}
|
|
503
512
|
async deleteMany(collection, query) {
|
|
504
513
|
const tableName = toSnakeCase(collection);
|
|
505
|
-
return this.
|
|
514
|
+
return this.db.transaction().execute(async (tx) => {
|
|
506
515
|
let selectQb = tx.selectFrom(tableName).select("id");
|
|
507
516
|
if (query && Object.keys(query).length > 0) {
|
|
508
517
|
selectQb = selectQb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
@@ -536,7 +545,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
536
545
|
}
|
|
537
546
|
async findGlobal(slug) {
|
|
538
547
|
const tableName = toSnakeCase(slug);
|
|
539
|
-
const row = await this.
|
|
548
|
+
const row = await this.db.selectFrom(tableName).selectAll().limit(1).executeTakeFirst();
|
|
540
549
|
return row ? unflattenRow(row) : null;
|
|
541
550
|
}
|
|
542
551
|
async updateGlobal(slug, data) {
|
|
@@ -550,15 +559,15 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
550
559
|
if (!flatData.id)
|
|
551
560
|
flatData.id = "global";
|
|
552
561
|
flatData[toSnakeCase("createdAt")] = now;
|
|
553
|
-
await this.
|
|
562
|
+
await this.db.insertInto(tableName).values(flatData).execute();
|
|
554
563
|
} else {
|
|
555
|
-
await this.
|
|
564
|
+
await this.db.updateTable(tableName).set(flatData).where("id", "=", existing.id).execute();
|
|
556
565
|
}
|
|
557
566
|
return this.findGlobal(slug);
|
|
558
567
|
}
|
|
559
568
|
async runMigrations() {
|
|
560
569
|
const migrator = new Migrator({
|
|
561
|
-
db: this.
|
|
570
|
+
db: this.db,
|
|
562
571
|
provider: new FileMigrationProvider({
|
|
563
572
|
fs,
|
|
564
573
|
path,
|
|
@@ -581,7 +590,7 @@ class BetterSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
581
590
|
this._collections = collections;
|
|
582
591
|
this._globals = globals;
|
|
583
592
|
if (this.push) {
|
|
584
|
-
await pushSchema(this.
|
|
593
|
+
await pushSchema(this.db, "sqlite", collections, globals, {
|
|
585
594
|
pushDestructive: this.pushDestructive
|
|
586
595
|
});
|
|
587
596
|
}
|
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
|
|
5
5
|
// src/cli/commands/seed-command.ts
|
|
6
6
|
async function seedCommand(opaca, count, reset, type) {
|
|
7
|
-
const { autoSeed } = await import("./chunk-
|
|
7
|
+
const { autoSeed } = await import("./chunk-jdfw4v3r.js");
|
|
8
8
|
try {
|
|
9
9
|
await autoSeed(opaca, count, reset, type);
|
|
10
10
|
} catch (error) {
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__require
|
|
3
|
+
} from "./chunk-8sqjbsgt.js";
|
|
4
|
+
|
|
5
|
+
// src/cli/seeding.ts
|
|
6
|
+
import { faker } from "@faker-js/faker";
|
|
7
|
+
var defaultFieldGenerators = {
|
|
8
|
+
text: () => faker.lorem.words(3),
|
|
9
|
+
textarea: () => faker.lorem.paragraph(),
|
|
10
|
+
number: () => faker.number.int({ min: 1, max: 1000 }),
|
|
11
|
+
richtext: () => JSON.stringify({
|
|
12
|
+
root: {
|
|
13
|
+
children: [
|
|
14
|
+
{
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
detail: 0,
|
|
18
|
+
format: 0,
|
|
19
|
+
mode: "normal",
|
|
20
|
+
style: "",
|
|
21
|
+
text: faker.lorem.sentence(),
|
|
22
|
+
type: "text",
|
|
23
|
+
version: 1
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
direction: "ltr",
|
|
27
|
+
format: "",
|
|
28
|
+
indent: 0,
|
|
29
|
+
type: "heading",
|
|
30
|
+
tag: "h1",
|
|
31
|
+
version: 1
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
children: [
|
|
35
|
+
{
|
|
36
|
+
detail: 0,
|
|
37
|
+
format: 0,
|
|
38
|
+
mode: "normal",
|
|
39
|
+
style: "",
|
|
40
|
+
text: faker.lorem.paragraphs(2),
|
|
41
|
+
type: "text",
|
|
42
|
+
version: 1
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
direction: "ltr",
|
|
46
|
+
format: "",
|
|
47
|
+
indent: 0,
|
|
48
|
+
type: "paragraph",
|
|
49
|
+
version: 1
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
direction: "ltr",
|
|
53
|
+
format: "",
|
|
54
|
+
indent: 0,
|
|
55
|
+
type: "root",
|
|
56
|
+
version: 1
|
|
57
|
+
}
|
|
58
|
+
}),
|
|
59
|
+
boolean: () => faker.datatype.boolean(),
|
|
60
|
+
date: () => faker.date.recent().toISOString(),
|
|
61
|
+
email: () => faker.internet.email(),
|
|
62
|
+
json: () => ({ [faker.lorem.word()]: faker.lorem.sentence() }),
|
|
63
|
+
select: () => faker.lorem.word(),
|
|
64
|
+
radio: () => faker.lorem.word(),
|
|
65
|
+
blocks: () => []
|
|
66
|
+
};
|
|
67
|
+
async function getRandomIds(db, relationTo, count = 1) {
|
|
68
|
+
try {
|
|
69
|
+
const result = await db.find(relationTo, {}, { limit: 50 });
|
|
70
|
+
const docs = result.docs || [];
|
|
71
|
+
if (docs.length === 0) {
|
|
72
|
+
if (!relationTo.startsWith("_")) {
|
|
73
|
+
console.warn(`[Seeding] No documents found in '${relationTo}' for relationship.`);
|
|
74
|
+
}
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
const shuffled = [...docs].sort(() => 0.5 - Math.random());
|
|
78
|
+
const selected = shuffled.slice(0, count).map((doc) => doc.id);
|
|
79
|
+
return selected;
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.error(`[Seeding] Failed to fetch random IDs for ${relationTo}:`, e);
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
async function generateDataForFields(fields, db, locales = []) {
|
|
86
|
+
const record = {};
|
|
87
|
+
for (const field of fields) {
|
|
88
|
+
if (field.type === "row" || field.type === "collapsible") {
|
|
89
|
+
Object.assign(record, await generateDataForFields(field.fields, db, locales));
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (field.type === "tabs") {
|
|
93
|
+
for (const tab of field.tabs) {
|
|
94
|
+
Object.assign(record, await generateDataForFields(tab.fields, db, locales));
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (field.type === "group" && field.name) {
|
|
99
|
+
record[field.name] = await generateDataForFields(field.fields, db, locales);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (field.name) {
|
|
103
|
+
const isLocalized = !!field.localized && locales.length > 0;
|
|
104
|
+
const generateFieldValue = async () => {
|
|
105
|
+
if (field.type === "relationship" && "relationTo" in field) {
|
|
106
|
+
if (field.hasMany) {
|
|
107
|
+
const count = faker.number.int({ min: 1, max: 3 });
|
|
108
|
+
return await getRandomIds(db, field.relationTo, count);
|
|
109
|
+
}
|
|
110
|
+
const ids = await getRandomIds(db, field.relationTo, 1);
|
|
111
|
+
return ids[0] || null;
|
|
112
|
+
}
|
|
113
|
+
if (field.type === "blocks" && field.blocks) {
|
|
114
|
+
const blockCount = faker.number.int({ min: 1, max: 3 });
|
|
115
|
+
const generatedBlocks = [];
|
|
116
|
+
for (let i = 0;i < blockCount; i++) {
|
|
117
|
+
const blockType = field.blocks[faker.number.int({ min: 0, max: field.blocks.length - 1 })];
|
|
118
|
+
if (!blockType)
|
|
119
|
+
continue;
|
|
120
|
+
const blockData = await generateDataForFields(blockType.fields, db, locales);
|
|
121
|
+
generatedBlocks.push({
|
|
122
|
+
...blockData,
|
|
123
|
+
blockType: blockType.slug,
|
|
124
|
+
id: faker.string.uuid()
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return generatedBlocks;
|
|
128
|
+
}
|
|
129
|
+
const generator = defaultFieldGenerators[field.type];
|
|
130
|
+
if (generator) {
|
|
131
|
+
if (field.type === "select" || field.type === "radio") {
|
|
132
|
+
const options = field.options;
|
|
133
|
+
const choices = options?.choices || [];
|
|
134
|
+
if (choices.length > 0) {
|
|
135
|
+
const choice = choices[Math.floor(Math.random() * choices.length)];
|
|
136
|
+
return typeof choice === "string" ? choice : choice.value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return generator();
|
|
140
|
+
}
|
|
141
|
+
return null;
|
|
142
|
+
};
|
|
143
|
+
if (isLocalized) {
|
|
144
|
+
const localizedValue = {};
|
|
145
|
+
for (const locale of locales) {
|
|
146
|
+
localizedValue[locale] = await generateFieldValue();
|
|
147
|
+
}
|
|
148
|
+
record[field.name] = localizedValue;
|
|
149
|
+
} else {
|
|
150
|
+
record[field.name] = await generateFieldValue();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return record;
|
|
155
|
+
}
|
|
156
|
+
async function generateRecord(db, collection, locales = []) {
|
|
157
|
+
return generateDataForFields(collection.fields, db, locales);
|
|
158
|
+
}
|
|
159
|
+
function sortCollections(collections) {
|
|
160
|
+
const sorted = [];
|
|
161
|
+
const visited = new Set;
|
|
162
|
+
const visiting = new Set;
|
|
163
|
+
const visit = (collection) => {
|
|
164
|
+
if (visited.has(collection.slug))
|
|
165
|
+
return;
|
|
166
|
+
if (visiting.has(collection.slug)) {
|
|
167
|
+
throw new Error(`Circular dependency detected: ${collection.slug}`);
|
|
168
|
+
}
|
|
169
|
+
visiting.add(collection.slug);
|
|
170
|
+
const deps = [];
|
|
171
|
+
const findDeps = (fields) => {
|
|
172
|
+
for (const f of fields) {
|
|
173
|
+
if (f.type === "relationship") {
|
|
174
|
+
deps.push(f.relationTo);
|
|
175
|
+
} else if (f.type === "blocks" && f.blocks) {
|
|
176
|
+
for (const b of f.blocks) {
|
|
177
|
+
findDeps(b.fields);
|
|
178
|
+
}
|
|
179
|
+
} else if ("fields" in f && f.fields) {
|
|
180
|
+
findDeps(f.fields);
|
|
181
|
+
} else if ("tabs" in f && f.tabs) {
|
|
182
|
+
for (const t of f.tabs) {
|
|
183
|
+
findDeps(t.fields);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
findDeps(collection.fields);
|
|
189
|
+
for (const depSlug of deps) {
|
|
190
|
+
const depColl = collections.find((c) => c.slug === depSlug);
|
|
191
|
+
if (depColl)
|
|
192
|
+
visit(depColl);
|
|
193
|
+
}
|
|
194
|
+
visiting.delete(collection.slug);
|
|
195
|
+
visited.add(collection.slug);
|
|
196
|
+
sorted.push(collection);
|
|
197
|
+
};
|
|
198
|
+
for (const collection of collections) {
|
|
199
|
+
visit(collection);
|
|
200
|
+
}
|
|
201
|
+
return sorted;
|
|
202
|
+
}
|
|
203
|
+
async function autoSeed(config, countPerCollection = 10, reset = false, type = "all") {
|
|
204
|
+
const { collections, db, globals, storages, serverURL } = config;
|
|
205
|
+
console.log(`\uD83C\uDF31 Starting automatic seed (${countPerCollection} records per collection)...`);
|
|
206
|
+
const { getSystemCollections } = await import("./chunk-3rdhbedb.js");
|
|
207
|
+
const systemCollections = getSystemCollections().filter((c) => c.slug === "_opaca_assets");
|
|
208
|
+
const allCollections = [...systemCollections, ...collections];
|
|
209
|
+
let collectionsToSeed = sortCollections(allCollections);
|
|
210
|
+
if (type === "assets") {
|
|
211
|
+
collectionsToSeed = collectionsToSeed.filter((c) => c.slug === "_opaca_assets");
|
|
212
|
+
console.log("\uD83D\uDCC1 Seeding only assets...");
|
|
213
|
+
} else if (type === "collections") {
|
|
214
|
+
collectionsToSeed = collectionsToSeed.filter((c) => c.slug !== "_opaca_assets");
|
|
215
|
+
console.log("\uD83D\uDCDA Seeding only user collections...");
|
|
216
|
+
}
|
|
217
|
+
await db.connect();
|
|
218
|
+
await db.migrate(allCollections, globals || []);
|
|
219
|
+
try {
|
|
220
|
+
if (reset) {
|
|
221
|
+
console.log("\uD83E\uDDF9 Resetting data (deleting existing records)...");
|
|
222
|
+
const reversed = [...collectionsToSeed].reverse();
|
|
223
|
+
for (const collection of reversed) {
|
|
224
|
+
console.log(`Cleaning ${collection.slug}...`);
|
|
225
|
+
await db.deleteMany?.(collection.slug, {});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
const storageAdapter = storages?.default || storages;
|
|
229
|
+
const locales = config.i18n?.locales || [];
|
|
230
|
+
for (const collection of collectionsToSeed) {
|
|
231
|
+
console.log(`Seeding ${collection.slug}...`);
|
|
232
|
+
const isAssetCollection = collection.slug === "_opaca_assets";
|
|
233
|
+
for (let i = 0;i < countPerCollection; i++) {
|
|
234
|
+
let data;
|
|
235
|
+
if (isAssetCollection) {
|
|
236
|
+
const id = faker.string.uuid();
|
|
237
|
+
const width = faker.number.int({ min: 400, max: 1200 });
|
|
238
|
+
const height = faker.number.int({ min: 300, max: 800 });
|
|
239
|
+
const color = faker.color.rgb({ prefix: "" });
|
|
240
|
+
const textColor = faker.color.rgb({ prefix: "" });
|
|
241
|
+
const usePicsum = Math.random() > 0.5;
|
|
242
|
+
let imageUrl = "";
|
|
243
|
+
if (usePicsum) {
|
|
244
|
+
const categories = ["nature", "city", "tech", "people", "animals", "architecture"];
|
|
245
|
+
const category = faker.helpers.arrayElement(categories);
|
|
246
|
+
imageUrl = `https://picsum.photos/seed/${category}-${id}/${width}/${height}`;
|
|
247
|
+
} else {
|
|
248
|
+
imageUrl = `https://placehold.co/${width}x${height}/${color}/${textColor}.png?text=Seed+${i}`;
|
|
249
|
+
}
|
|
250
|
+
const res = await fetch(imageUrl);
|
|
251
|
+
if (!res.ok) {
|
|
252
|
+
throw new Error(`Failed to fetch placeholder image: ${imageUrl}`);
|
|
253
|
+
}
|
|
254
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
255
|
+
const mime_type = res.headers.get("content-type")?.split(";")[0] || "image/png";
|
|
256
|
+
const extMap = {
|
|
257
|
+
"image/jpeg": "jpg",
|
|
258
|
+
"image/png": "png",
|
|
259
|
+
"image/webp": "webp",
|
|
260
|
+
"image/gif": "gif",
|
|
261
|
+
"image/svg+xml": "svg"
|
|
262
|
+
};
|
|
263
|
+
const ext = extMap[mime_type] || "png";
|
|
264
|
+
const fileRecord = {
|
|
265
|
+
filename: `seed-image-${i}.${ext}`,
|
|
266
|
+
original_filename: `seed-image-${i}.${ext}`,
|
|
267
|
+
mime_type,
|
|
268
|
+
filesize: arrayBuffer.byteLength,
|
|
269
|
+
buffer: new Uint8Array(arrayBuffer)
|
|
270
|
+
};
|
|
271
|
+
if (!storageAdapter || typeof storageAdapter.upload !== "function") {
|
|
272
|
+
throw new Error("Storage adapter is required for seeding assets and must have an 'upload' method.");
|
|
273
|
+
}
|
|
274
|
+
const uploaded = await storageAdapter.upload(fileRecord, {
|
|
275
|
+
generateUniqueName: true,
|
|
276
|
+
customMetadata: {
|
|
277
|
+
sourceUrl: imageUrl
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
data = {
|
|
281
|
+
id,
|
|
282
|
+
key: uploaded.filename,
|
|
283
|
+
filename: uploaded.filename,
|
|
284
|
+
originalFilename: fileRecord.original_filename,
|
|
285
|
+
mimeType: uploaded.mime_type,
|
|
286
|
+
filesize: uploaded.filesize,
|
|
287
|
+
width,
|
|
288
|
+
height,
|
|
289
|
+
bucket: "default",
|
|
290
|
+
url: uploaded.url,
|
|
291
|
+
thumbnailUrl: uploaded.url,
|
|
292
|
+
altText: faker.lorem.sentence()
|
|
293
|
+
};
|
|
294
|
+
const baseURL = serverURL || "http://localhost:8787";
|
|
295
|
+
console.log(`[Asset] Source: ${imageUrl}`);
|
|
296
|
+
console.log(`[Asset] Seeded: ${baseURL}/api/assets/${id}/view (${uploaded.filename})`);
|
|
297
|
+
} else {
|
|
298
|
+
data = await generateRecord(db, collection, locales);
|
|
299
|
+
}
|
|
300
|
+
await db.create(collection.slug, data);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
console.log("✅ Seeding completed.");
|
|
304
|
+
} finally {}
|
|
305
|
+
}
|
|
306
|
+
export {
|
|
307
|
+
sortCollections,
|
|
308
|
+
generateRecord,
|
|
309
|
+
defaultFieldGenerators,
|
|
310
|
+
autoSeed
|
|
311
|
+
};
|