opacacms 0.1.20 → 0.2.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 +638 -732
- package/dist/admin/auth-client.d.ts +39 -39
- package/dist/admin/index.d.ts +1 -0
- package/dist/admin/index.js +2397 -1405
- package/dist/admin/react.d.ts +1 -1
- package/dist/admin/react.js +8 -0
- package/dist/admin/router.d.ts +1 -0
- package/dist/admin/stores/ui.d.ts +10 -0
- package/dist/admin/ui/admin-layout.d.ts +4 -4
- package/dist/admin/ui/components/DataDetailView.d.ts +1 -1
- package/dist/admin/ui/components/DetailSheet.d.ts +19 -0
- package/dist/admin/ui/components/PluginSettingsForm.d.ts +11 -0
- package/dist/admin/ui/components/fields/BooleanField.d.ts +2 -1
- package/dist/admin/ui/components/fields/DateField.d.ts +1 -1
- package/dist/admin/ui/components/fields/FieldLabel.d.ts +11 -0
- package/dist/admin/ui/components/fields/FileField.d.ts +1 -1
- package/dist/admin/ui/components/fields/NumberField.d.ts +1 -1
- package/dist/admin/ui/components/fields/RadioField.d.ts +1 -1
- package/dist/admin/ui/components/fields/RelationshipField.d.ts +3 -1
- package/dist/admin/ui/components/fields/SelectField.d.ts +1 -1
- package/dist/admin/ui/components/fields/TextAreaField.d.ts +1 -1
- package/dist/admin/ui/components/fields/TextField.d.ts +1 -1
- package/dist/admin/ui/components/fields/VirtualField.d.ts +1 -0
- package/dist/admin/ui/components/fields/index.d.ts +16 -16
- package/dist/admin/ui/components/fields/richtext-editor/index.d.ts +1 -1
- package/dist/admin/ui/components/media/AssetManagerModal.d.ts +1 -1
- package/dist/admin/ui/components/toast.d.ts +1 -1
- package/dist/admin/ui/components/ui/accordion.d.ts +1 -1
- package/dist/admin/ui/components/ui/button.d.ts +1 -1
- package/dist/admin/ui/components/ui/collapsible.d.ts +1 -1
- package/dist/admin/ui/components/ui/dialog.d.ts +1 -1
- package/dist/admin/ui/components/ui/group.d.ts +1 -1
- package/dist/admin/ui/components/ui/index.d.ts +17 -17
- package/dist/admin/ui/components/ui/input.d.ts +1 -1
- package/dist/admin/ui/components/ui/label.d.ts +1 -1
- package/dist/admin/ui/components/ui/radio-group.d.ts +1 -1
- package/dist/admin/ui/components/ui/relationship.d.ts +4 -4
- package/dist/admin/ui/components/ui/select.d.ts +1 -1
- package/dist/admin/ui/components/ui/sheet.d.ts +1 -1
- package/dist/admin/ui/components/ui/tabs.d.ts +1 -1
- package/dist/admin/ui/components/versions-sheet.d.ts +11 -0
- package/dist/admin/ui/views/media-registry-view.d.ts +1 -1
- package/dist/admin/ui/views/settings-view.d.ts +2 -2
- package/dist/admin/vue.d.ts +17 -0
- package/dist/admin/vue.js +8 -0
- package/dist/admin/webcomponent.js +2 -2
- package/dist/admin.css +1 -1
- package/dist/auth/index.d.ts +101 -41
- package/dist/{chunk-qkn1ykrj.js → chunk-0bq155dy.js} +94 -31
- package/dist/{chunk-2z8wxx9g.js → chunk-0gtxnxmd.js} +98 -25
- package/dist/{chunk-v521d72w.js → chunk-3rdhbedb.js} +1 -1
- package/dist/chunk-51z3x7kq.js +20 -0
- package/dist/{chunk-7fyepksb.js → chunk-526a3gqx.js} +1 -1
- package/dist/{chunk-wmvjvn7b.js → chunk-6qq3ne6b.js} +39 -1
- package/dist/{chunk-0am1m47g.js → chunk-6v1fw7q7.js} +5 -5
- package/dist/{chunk-zvwb67nd.js → chunk-7y1nbmw6.js} +36 -5
- package/dist/chunk-8scgdznr.js +44 -0
- package/dist/{chunk-erh6x75p.js → chunk-b3kr8w41.js} +58 -7
- package/dist/chunk-bexcv7xe.js +36 -0
- package/dist/{chunk-16vgcf3k.js → chunk-byq8g0rd.js} +1 -1
- package/dist/{chunk-wq314kkx.js → chunk-d1asgtke.js} +94 -31
- package/dist/{chunk-xtwc125q.js → chunk-dykn5hr6.js} +8 -8
- package/dist/{chunk-pxh5encs.js → chunk-esrg9qj0.js} +102 -44
- package/dist/chunk-fj19qccp.js +78 -0
- package/dist/{chunk-x2ejaftz.js → chunk-gmee4mdc.js} +98 -27
- package/dist/{chunk-xa7rjsn2.js → chunk-j53pz21t.js} +2 -2
- package/dist/{chunk-9y3m1xkx.js → chunk-kc4jfnv7.js} +480 -85
- package/dist/chunk-mkn49zmy.js +102 -0
- package/dist/{chunk-n1xraw7j.js → chunk-qb6ztvw9.js} +1 -1
- package/dist/{chunk-2kyhqvhc.js → chunk-qxt9vge8.js} +1 -1
- package/dist/chunk-r39em4yj.js +29 -0
- package/dist/chunk-rqyjjqgy.js +91 -0
- package/dist/chunk-rsf0tpy1.js +8 -0
- package/dist/chunk-swtcpvhf.js +2442 -0
- package/dist/chunk-t0zg026p.js +71 -0
- package/dist/chunk-twpvxfce.js +64 -0
- package/dist/{chunk-ybbbqj63.js → chunk-v9z61v3g.js} +15 -0
- package/dist/{chunk-jwjk85ze.js → chunk-ywm4t2gm.js} +6 -2
- package/dist/cli/commands/plugin-build.d.ts +1 -0
- package/dist/cli/commands/plugin-init.d.ts +1 -0
- package/dist/cli/commands/plugin-sync.d.ts +1 -0
- package/dist/cli/index.js +24 -6
- package/dist/config-utils.d.ts +1 -1
- package/dist/config.d.ts +21 -4
- package/dist/db/better-sqlite.d.ts +1 -1
- package/dist/db/better-sqlite.js +5 -5
- package/dist/db/bun-sqlite.d.ts +1 -1
- package/dist/db/bun-sqlite.js +5 -5
- package/dist/db/d1.d.ts +1 -1
- package/dist/db/d1.js +5 -5
- package/dist/db/index.js +9 -9
- package/dist/db/postgres.d.ts +1 -1
- package/dist/db/postgres.js +5 -5
- package/dist/db/sqlite.d.ts +1 -1
- package/dist/db/sqlite.js +5 -5
- package/dist/index.js +4 -3
- package/dist/plugins/index.d.ts +1 -0
- package/dist/plugins/ui-bridge.d.ts +12 -0
- package/dist/plugins/utils.d.ts +5 -0
- package/dist/runtimes/bun.js +13 -7
- package/dist/runtimes/cloudflare-workers.js +5 -5
- package/dist/runtimes/next.js +5 -5
- package/dist/runtimes/node.js +13 -7
- package/dist/schema/collection.d.ts +9 -30
- package/dist/schema/fields/base.d.ts +3 -2
- package/dist/schema/fields/index.d.ts +12 -0
- package/dist/schema/fields/validation.test.d.ts +1 -0
- package/dist/schema/global.d.ts +10 -15
- package/dist/schema/index.js +22 -14
- package/dist/server/admin-router.d.ts +2 -2
- package/dist/server/admin.d.ts +2 -1
- package/dist/server/collection-router.d.ts +1 -1
- package/dist/server/handlers.d.ts +10 -0
- package/dist/server/middlewares/admin.d.ts +2 -2
- package/dist/server/middlewares/auth.d.ts +1 -1
- package/dist/server/middlewares/context.d.ts +2 -0
- package/dist/server/middlewares/rate-limit.d.ts +1 -1
- package/dist/server/openapi.d.ts +2 -0
- package/dist/server/plugins-loader.d.ts +6 -0
- package/dist/server/router.d.ts +3 -3
- package/dist/server/routers/admin.d.ts +2 -2
- package/dist/server/routers/auth.d.ts +1 -1
- package/dist/server/routers/collections.d.ts +1 -1
- package/dist/server/routers/plugins.d.ts +18 -0
- package/dist/server/setup-middlewares.d.ts +2 -2
- package/dist/server/system-router.d.ts +1 -1
- package/dist/server.js +11 -7
- package/dist/storage/adapters/local.d.ts +1 -1
- package/dist/storage/adapters/s3.d.ts +1 -1
- package/dist/types.d.ts +227 -12
- package/dist/utils/logger.d.ts +13 -35
- package/dist/validation.d.ts +60 -8
- package/dist/validator.d.ts +1 -1
- package/package.json +21 -7
- package/dist/admin/ui/components/DataDetailSheet.d.ts +0 -13
- package/dist/admin/ui/components/ui/relationship-detail-sheet.d.ts +0 -9
- package/dist/chunk-62ev8gnc.js +0 -41
- package/dist/chunk-j4d50hrx.js +0 -20
- package/dist/chunk-nb7ctdg8.js +0 -311
|
@@ -3,18 +3,18 @@ import {
|
|
|
3
3
|
flattenPayload,
|
|
4
4
|
pushSchema,
|
|
5
5
|
unflattenRow
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-dykn5hr6.js";
|
|
7
7
|
import {
|
|
8
8
|
BaseDatabaseAdapter
|
|
9
9
|
} from "./chunk-s8mqwnm1.js";
|
|
10
|
-
import {
|
|
11
|
-
logger
|
|
12
|
-
} from "./chunk-62ev8gnc.js";
|
|
13
10
|
import {
|
|
14
11
|
flattenFields,
|
|
15
12
|
getRelationalFields,
|
|
16
13
|
toSnakeCase
|
|
17
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-qxt9vge8.js";
|
|
15
|
+
import {
|
|
16
|
+
logger
|
|
17
|
+
} from "./chunk-t0zg026p.js";
|
|
18
18
|
|
|
19
19
|
// src/db/bun-sqlite.ts
|
|
20
20
|
import { Database } from "bun:sqlite";
|
|
@@ -82,7 +82,10 @@ class BunSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
82
82
|
if (value instanceof Date) {
|
|
83
83
|
result[colName] = value.toISOString();
|
|
84
84
|
} else if (typeof value === "string") {
|
|
85
|
-
|
|
85
|
+
const d = new Date(value);
|
|
86
|
+
if (!isNaN(d.getTime())) {
|
|
87
|
+
result[colName] = d.toISOString();
|
|
88
|
+
}
|
|
86
89
|
}
|
|
87
90
|
break;
|
|
88
91
|
case "richtext":
|
|
@@ -134,12 +137,11 @@ class BunSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
134
137
|
const dbFields = flattenFields(colDef?.fields || []);
|
|
135
138
|
const validCols = new Set(dbFields.map((f) => toSnakeCase(f.name)));
|
|
136
139
|
validCols.add("id");
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
140
|
+
validCols.add(toSnakeCase("createdAt"));
|
|
141
|
+
validCols.add(toSnakeCase("updatedAt"));
|
|
142
|
+
const now = new Date().toISOString();
|
|
143
|
+
flatData[toSnakeCase("createdAt")] = now;
|
|
144
|
+
flatData[toSnakeCase("updatedAt")] = now;
|
|
143
145
|
const filteredData = {};
|
|
144
146
|
for (const col of Object.keys(flatData)) {
|
|
145
147
|
if (validCols.has(col)) {
|
|
@@ -267,7 +269,87 @@ class BunSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
267
269
|
qb = qb.orderBy("created_at", "desc");
|
|
268
270
|
}
|
|
269
271
|
const rows = await qb.execute();
|
|
270
|
-
|
|
272
|
+
if (rows.length === 0) {
|
|
273
|
+
const totalPages2 = Math.ceil(total / limit);
|
|
274
|
+
return {
|
|
275
|
+
docs: [],
|
|
276
|
+
totalDocs: total,
|
|
277
|
+
limit,
|
|
278
|
+
totalPages: totalPages2,
|
|
279
|
+
page,
|
|
280
|
+
pagingCounter: offset + 1,
|
|
281
|
+
hasNextPage: page * limit < total,
|
|
282
|
+
hasPrevPage: page > 1,
|
|
283
|
+
prevPage: page > 1 ? page - 1 : null,
|
|
284
|
+
nextPage: page < totalPages2 ? page + 1 : null
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const rowIds = rows.map((r) => r.id);
|
|
288
|
+
const docs = rows.map((r) => unflattenRow(r));
|
|
289
|
+
const colDef = this._collections.find((c) => c.slug === collection);
|
|
290
|
+
if (colDef) {
|
|
291
|
+
const relationalFields = getRelationalFields(colDef.fields);
|
|
292
|
+
for (const field of relationalFields) {
|
|
293
|
+
if (!field.name)
|
|
294
|
+
continue;
|
|
295
|
+
const snakeName = toSnakeCase(field.name);
|
|
296
|
+
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
297
|
+
const joinTableName = `${toSnakeCase(collection)}_${snakeName}_relations`.toLowerCase();
|
|
298
|
+
try {
|
|
299
|
+
const allRelations = await this._db.selectFrom(joinTableName).selectAll().where("source_id", "in", rowIds).orderBy("order", "asc").execute();
|
|
300
|
+
const relationsBySource = allRelations.reduce((acc, r) => {
|
|
301
|
+
if (!acc[r.source_id])
|
|
302
|
+
acc[r.source_id] = [];
|
|
303
|
+
acc[r.source_id].push(r.target_id);
|
|
304
|
+
return acc;
|
|
305
|
+
}, {});
|
|
306
|
+
const parts = field.name.split("__");
|
|
307
|
+
for (let docIdx = 0;docIdx < docs.length; docIdx++) {
|
|
308
|
+
const rowId = rowIds[docIdx];
|
|
309
|
+
let current = docs[docIdx];
|
|
310
|
+
for (let i = 0;i < parts.length - 1; i++) {
|
|
311
|
+
if (!current[parts[i]])
|
|
312
|
+
current[parts[i]] = {};
|
|
313
|
+
current = current[parts[i]];
|
|
314
|
+
}
|
|
315
|
+
current[parts[parts.length - 1]] = relationsBySource[rowId] || [];
|
|
316
|
+
}
|
|
317
|
+
} catch (e) {}
|
|
318
|
+
} else if (field.type === "blocks" && field.blocks) {
|
|
319
|
+
const blocksBySource = {};
|
|
320
|
+
for (const b of field.blocks) {
|
|
321
|
+
const blockTableName = `${collection}_${snakeName}_${toSnakeCase(b.slug)}`.toLowerCase();
|
|
322
|
+
try {
|
|
323
|
+
const allBlocks = await this._db.selectFrom(blockTableName).selectAll().where("_parent_id", "in", rowIds).execute();
|
|
324
|
+
for (const blk of allBlocks) {
|
|
325
|
+
const uf = unflattenRow(blk);
|
|
326
|
+
uf.blockType = blk.block_type;
|
|
327
|
+
if (!blocksBySource[blk._parent_id])
|
|
328
|
+
blocksBySource[blk._parent_id] = [];
|
|
329
|
+
blocksBySource[blk._parent_id].push(uf);
|
|
330
|
+
}
|
|
331
|
+
} catch (e) {}
|
|
332
|
+
}
|
|
333
|
+
const parts = field.name.split("__");
|
|
334
|
+
for (let docIdx = 0;docIdx < docs.length; docIdx++) {
|
|
335
|
+
const rowId = rowIds[docIdx];
|
|
336
|
+
const blockData = blocksBySource[rowId] || [];
|
|
337
|
+
blockData.sort((a, b) => a._order - b._order);
|
|
338
|
+
blockData.forEach((b) => {
|
|
339
|
+
delete b._order;
|
|
340
|
+
delete b._parentId;
|
|
341
|
+
});
|
|
342
|
+
let current = docs[docIdx];
|
|
343
|
+
for (let i = 0;i < parts.length - 1; i++) {
|
|
344
|
+
if (!current[parts[i]])
|
|
345
|
+
current[parts[i]] = {};
|
|
346
|
+
current = current[parts[i]];
|
|
347
|
+
}
|
|
348
|
+
current[parts[parts.length - 1]] = blockData;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
271
353
|
const totalPages = Math.ceil(total / limit);
|
|
272
354
|
return {
|
|
273
355
|
docs: docs.filter(Boolean),
|
|
@@ -475,21 +557,12 @@ class BunSQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
475
557
|
const existing = await this.findGlobal(slug);
|
|
476
558
|
const flatData = flattenPayload(data);
|
|
477
559
|
const globalDef = this._globals.find((g) => g.slug === slug);
|
|
478
|
-
const
|
|
479
|
-
|
|
480
|
-
const config = typeof globalDef?.timestamps === "object" ? globalDef.timestamps : {};
|
|
481
|
-
const updatedField = toSnakeCase(config.updatedAt || "updatedAt");
|
|
482
|
-
flatData[updatedField] = new Date().toISOString();
|
|
483
|
-
}
|
|
560
|
+
const now = new Date().toISOString();
|
|
561
|
+
flatData[toSnakeCase("updatedAt")] = now;
|
|
484
562
|
if (!existing) {
|
|
485
563
|
if (!flatData.id)
|
|
486
564
|
flatData.id = "global";
|
|
487
|
-
|
|
488
|
-
const config = typeof globalDef?.timestamps === "object" ? globalDef.timestamps : {};
|
|
489
|
-
const createdField = toSnakeCase(config.createdAt || "createdAt");
|
|
490
|
-
if (!flatData[createdField])
|
|
491
|
-
flatData[createdField] = flatData[toSnakeCase(config.updatedAt || "updatedAt")];
|
|
492
|
-
}
|
|
565
|
+
flatData[toSnakeCase("createdAt")] = now;
|
|
493
566
|
await this._db.insertInto(tableName).values(flatData).execute();
|
|
494
567
|
} else {
|
|
495
568
|
await this._db.updateTable(tableName).set(flatData).where("id", "=", existing.id).execute();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// src/server/plugins-loader.ts
|
|
2
|
+
async function loadPluginSettings(config) {
|
|
3
|
+
if (!config.db)
|
|
4
|
+
return {};
|
|
5
|
+
try {
|
|
6
|
+
const result = await config.db.find("_opaca_plugin_settings");
|
|
7
|
+
if (!result || !result.docs)
|
|
8
|
+
return {};
|
|
9
|
+
return result.docs.reduce((acc, curr) => {
|
|
10
|
+
if (curr.pluginName && curr.config) {
|
|
11
|
+
acc[curr.pluginName] = curr.config;
|
|
12
|
+
}
|
|
13
|
+
return acc;
|
|
14
|
+
}, {});
|
|
15
|
+
} catch (e) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export { loadPluginSettings };
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
mapFieldToPostgresType,
|
|
4
4
|
mapFieldToSQLiteType,
|
|
5
5
|
toSnakeCase
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-qxt9vge8.js";
|
|
7
7
|
import"./chunk-8sqjbsgt.js";
|
|
8
8
|
|
|
9
9
|
// src/db/kysely/migration-generator.ts
|
|
@@ -60,6 +60,26 @@ function generateMigrationCode(collections, globals = [], dialect) {
|
|
|
60
60
|
`;
|
|
61
61
|
downCode = ` await db.schema.dropTable('${slug}').execute();
|
|
62
62
|
` + downCode;
|
|
63
|
+
if ("versions" in collection && collection.versions) {
|
|
64
|
+
const versionsTable = `${toSnakeCase(slug)}_versions`;
|
|
65
|
+
upCode += ` await db.schema.createTable('${versionsTable}')
|
|
66
|
+
`;
|
|
67
|
+
upCode += ` .addColumn('id', 'text', (col) => col.primaryKey())
|
|
68
|
+
`;
|
|
69
|
+
upCode += ` .addColumn('_parent_id', 'text', (col) => col.notNull())
|
|
70
|
+
`;
|
|
71
|
+
upCode += ` .addColumn('_version_data', '${dialect === "postgres" ? "jsonb" : "text"}', (col) => col.notNull())
|
|
72
|
+
`;
|
|
73
|
+
upCode += ` .addColumn('_status', 'text')
|
|
74
|
+
`;
|
|
75
|
+
upCode += ` .addColumn('created_at', '${tsType}', (col) => col.defaultTo(sql\`CURRENT_TIMESTAMP\`))
|
|
76
|
+
`;
|
|
77
|
+
upCode += ` .execute();
|
|
78
|
+
|
|
79
|
+
`;
|
|
80
|
+
downCode = ` await db.schema.dropTable('${versionsTable}').execute();
|
|
81
|
+
` + downCode;
|
|
82
|
+
}
|
|
63
83
|
for (const field of flattenedFields) {
|
|
64
84
|
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
65
85
|
const colName = toSnakeCase(field.name);
|
|
@@ -188,6 +208,24 @@ function generateSQLCode(collections, globals = []) {
|
|
|
188
208
|
);
|
|
189
209
|
|
|
190
210
|
`;
|
|
211
|
+
if ("versions" in collection && collection.versions) {
|
|
212
|
+
const versionsTable = `${toSnakeCase(slug)}_versions`;
|
|
213
|
+
sql += `CREATE TABLE IF NOT EXISTS "${versionsTable}" (
|
|
214
|
+
`;
|
|
215
|
+
sql += ` "id" TEXT PRIMARY KEY,
|
|
216
|
+
`;
|
|
217
|
+
sql += ` "_parent_id" TEXT NOT NULL,
|
|
218
|
+
`;
|
|
219
|
+
sql += ` "_version_data" TEXT NOT NULL,
|
|
220
|
+
`;
|
|
221
|
+
sql += ` "_status" TEXT,
|
|
222
|
+
`;
|
|
223
|
+
sql += ` "created_at" TEXT DEFAULT CURRENT_TIMESTAMP
|
|
224
|
+
`;
|
|
225
|
+
sql += `);
|
|
226
|
+
|
|
227
|
+
`;
|
|
228
|
+
}
|
|
191
229
|
for (const field of flattenedFields) {
|
|
192
230
|
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
193
231
|
const colName = toSnakeCase(field.name);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
logger
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-t0zg026p.js";
|
|
4
4
|
import {
|
|
5
5
|
__require
|
|
6
6
|
} from "./chunk-8sqjbsgt.js";
|
|
@@ -17,8 +17,8 @@ async function migrateCreateCommand(opaca, migrationName = "migration", outDir)
|
|
|
17
17
|
fs.mkdirSync(fullMigrationDir, { recursive: true });
|
|
18
18
|
}
|
|
19
19
|
const timestamp = new Date().toISOString().replace(/[-:T]/g, "").split(".")[0];
|
|
20
|
-
const { generateMigrationCode, generateSQLCode } = await import("./chunk-
|
|
21
|
-
const { getSystemCollections } = await import("./chunk-
|
|
20
|
+
const { generateMigrationCode, generateSQLCode } = await import("./chunk-6qq3ne6b.js");
|
|
21
|
+
const { getSystemCollections } = await import("./chunk-3rdhbedb.js");
|
|
22
22
|
let dialect = "sqlite";
|
|
23
23
|
if (db.name === "postgres")
|
|
24
24
|
dialect = "postgres";
|
|
@@ -104,9 +104,9 @@ async function migrateD1Command(migrationDir, isRemote, binding = "DB") {
|
|
|
104
104
|
isRemote ? "--remote" : "--local",
|
|
105
105
|
"--yes"
|
|
106
106
|
];
|
|
107
|
-
const result = spawnSync("
|
|
107
|
+
const result = spawnSync("bun", ["x", ...wranglerArgs], {
|
|
108
108
|
stdio: "inherit",
|
|
109
|
-
shell:
|
|
109
|
+
shell: false,
|
|
110
110
|
cwd: process.cwd()
|
|
111
111
|
});
|
|
112
112
|
if (result.status !== 0) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
logger
|
|
3
|
+
} from "./chunk-t0zg026p.js";
|
|
4
|
+
|
|
1
5
|
// src/validation.ts
|
|
2
6
|
import { z } from "zod";
|
|
3
7
|
var FieldTypeSchema = z.enum([
|
|
@@ -20,7 +24,8 @@ var FieldTypeSchema = z.enum([
|
|
|
20
24
|
"tabs",
|
|
21
25
|
"join",
|
|
22
26
|
"array",
|
|
23
|
-
"virtual"
|
|
27
|
+
"virtual",
|
|
28
|
+
"ui"
|
|
24
29
|
]);
|
|
25
30
|
var BaseFieldSchema = z.object({
|
|
26
31
|
name: z.string(),
|
|
@@ -41,7 +46,8 @@ var BaseFieldSchema = z.object({
|
|
|
41
46
|
components: z.object({
|
|
42
47
|
Field: z.string().optional(),
|
|
43
48
|
Cell: z.string().optional()
|
|
44
|
-
}).optional()
|
|
49
|
+
}).optional(),
|
|
50
|
+
customProps: z.record(z.string(), z.any()).optional()
|
|
45
51
|
}).optional()
|
|
46
52
|
});
|
|
47
53
|
var FieldSchema = z.lazy(() => z.discriminatedUnion("type", [
|
|
@@ -154,6 +160,9 @@ var FieldSchema = z.lazy(() => z.discriminatedUnion("type", [
|
|
|
154
160
|
type: z.literal("virtual"),
|
|
155
161
|
resolve: z.function(),
|
|
156
162
|
returnType: z.string().optional()
|
|
163
|
+
}),
|
|
164
|
+
BaseFieldSchema.extend({
|
|
165
|
+
type: z.literal("ui")
|
|
157
166
|
})
|
|
158
167
|
]));
|
|
159
168
|
var AccessConfigSchema = z.object({
|
|
@@ -183,7 +192,7 @@ var CollectionSchema = z.object({
|
|
|
183
192
|
createdAt: z.string().optional(),
|
|
184
193
|
updatedAt: z.string().optional()
|
|
185
194
|
})
|
|
186
|
-
]).optional(),
|
|
195
|
+
]).optional().transform(() => true),
|
|
187
196
|
auth: z.boolean().optional(),
|
|
188
197
|
hooks: CollectionHooksSchema.optional(),
|
|
189
198
|
access: AccessConfigSchema.optional(),
|
|
@@ -228,7 +237,7 @@ var GlobalSchema = z.object({
|
|
|
228
237
|
createdAt: z.string().optional(),
|
|
229
238
|
updatedAt: z.string().optional()
|
|
230
239
|
})
|
|
231
|
-
]).optional(),
|
|
240
|
+
]).optional().transform(() => true),
|
|
232
241
|
access: AccessConfigSchema.optional()
|
|
233
242
|
});
|
|
234
243
|
var AdminConfigSchema = z.object({
|
|
@@ -266,6 +275,11 @@ var OpacaAuthConfigSchema = z.object({
|
|
|
266
275
|
enabled: z.boolean(),
|
|
267
276
|
issuer: z.string()
|
|
268
277
|
}).optional()
|
|
278
|
+
}).optional(),
|
|
279
|
+
logger: z.object({
|
|
280
|
+
disabled: z.boolean().optional(),
|
|
281
|
+
disableColors: z.boolean().optional(),
|
|
282
|
+
level: z.enum(["debug", "info", "warn", "error"]).optional()
|
|
269
283
|
}).optional()
|
|
270
284
|
});
|
|
271
285
|
var ApiConfigSchema = z.object({
|
|
@@ -277,9 +291,19 @@ var ApiConfigSchema = z.object({
|
|
|
277
291
|
store: z.any().optional(),
|
|
278
292
|
provider: z.function().optional(),
|
|
279
293
|
keyGenerator: z.function().optional()
|
|
294
|
+
}).optional(),
|
|
295
|
+
openAPI: z.object({
|
|
296
|
+
enabled: z.boolean().optional().default(false),
|
|
297
|
+
path: z.string().optional().default("/reference"),
|
|
298
|
+
theme: z.string().optional(),
|
|
299
|
+
layout: z.string().optional(),
|
|
300
|
+
hideModels: z.boolean().optional(),
|
|
301
|
+
hideDownloadButton: z.boolean().optional(),
|
|
302
|
+
customCss: z.string().optional()
|
|
280
303
|
}).optional()
|
|
281
304
|
});
|
|
282
305
|
var OpacaConfigSchema = z.object({
|
|
306
|
+
plugins: z.array(z.any()).optional(),
|
|
283
307
|
collections: z.array(CollectionSchema),
|
|
284
308
|
globals: z.array(GlobalSchema).optional(),
|
|
285
309
|
db: z.any(),
|
|
@@ -321,7 +345,14 @@ function recursivelyBuild(obj) {
|
|
|
321
345
|
return built;
|
|
322
346
|
}
|
|
323
347
|
function defineConfig(config) {
|
|
324
|
-
|
|
348
|
+
let builtConfig = recursivelyBuild(config);
|
|
349
|
+
if (builtConfig.plugins && Array.isArray(builtConfig.plugins)) {
|
|
350
|
+
for (const plugin of builtConfig.plugins) {
|
|
351
|
+
if (plugin.onInit) {
|
|
352
|
+
builtConfig = plugin.onInit({ config: builtConfig, logger }) || builtConfig;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
}
|
|
325
356
|
const result = OpacaConfigSchema.safeParse(builtConfig);
|
|
326
357
|
if (!result.success) {
|
|
327
358
|
throw new Error(`Invalid OpacaCMS Configuration: ${result.error.message}`);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// src/admin/stores/ui.ts
|
|
2
|
+
import { persistentAtom } from "@nanostores/persistent";
|
|
3
|
+
import { atom } from "nanostores";
|
|
4
|
+
var $toasts = atom([]);
|
|
5
|
+
var $isSidebarCollapsed = persistentAtom("opaca-sidebar-collapsed", false, {
|
|
6
|
+
encode: JSON.stringify,
|
|
7
|
+
decode: JSON.parse
|
|
8
|
+
});
|
|
9
|
+
function toggleSidebar() {
|
|
10
|
+
$isSidebarCollapsed.set(!$isSidebarCollapsed.get());
|
|
11
|
+
}
|
|
12
|
+
function notify(message, type = "success") {
|
|
13
|
+
const id = Math.random().toString(36).substring(2, 9);
|
|
14
|
+
$toasts.set([...$toasts.get(), { id, message, type }]);
|
|
15
|
+
}
|
|
16
|
+
function clearToast(id) {
|
|
17
|
+
$toasts.set($toasts.get().filter((t) => t.id !== id));
|
|
18
|
+
}
|
|
19
|
+
var $customSidebarItems = atom([]);
|
|
20
|
+
function registerSidebarItem(item) {
|
|
21
|
+
const current = $customSidebarItems.get();
|
|
22
|
+
if (!current.find((i) => i.path === item.path)) {
|
|
23
|
+
if (item.render && !item.component) {
|
|
24
|
+
item.component = `opaca-plugin-render-${Math.random().toString(36).substring(2, 9)}`;
|
|
25
|
+
if (typeof customElements !== "undefined" && !customElements.get(item.component)) {
|
|
26
|
+
const componentName = item.component;
|
|
27
|
+
const renderFn = item.render;
|
|
28
|
+
|
|
29
|
+
class OpacaGenericPlugin extends HTMLElement {
|
|
30
|
+
connectedCallback() {
|
|
31
|
+
const serverUrl = this.getAttribute("server-url") || "";
|
|
32
|
+
const config = JSON.parse(this.getAttribute("config") || "{}");
|
|
33
|
+
const user = JSON.parse(this.getAttribute("user") || "{}");
|
|
34
|
+
this.innerHTML = renderFn(serverUrl, config, user);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
customElements.define(componentName, OpacaGenericPlugin);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
$customSidebarItems.set([...current, item]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { $toasts, $isSidebarCollapsed, toggleSidebar, notify, clearToast, $customSidebarItems, registerSidebarItem };
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getSystemCollections,
|
|
3
3
|
init_system_schema
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-v9z61v3g.js";
|
|
5
|
+
import {
|
|
6
|
+
OpacaLogger
|
|
7
|
+
} from "./chunk-t0zg026p.js";
|
|
5
8
|
|
|
6
9
|
// src/auth/index.ts
|
|
7
10
|
import { apiKey } from "@better-auth/api-key";
|
|
8
11
|
import { betterAuth } from "better-auth";
|
|
9
|
-
import { admin } from "better-auth/plugins";
|
|
12
|
+
import { admin, openAPI } from "better-auth/plugins";
|
|
10
13
|
import { CamelCasePlugin } from "kysely";
|
|
11
14
|
|
|
12
15
|
// src/auth/premissions.ts
|
|
@@ -81,6 +84,10 @@ async function createAuth(config) {
|
|
|
81
84
|
ac,
|
|
82
85
|
roles
|
|
83
86
|
}),
|
|
87
|
+
openAPI({
|
|
88
|
+
disableDefaultReference: true,
|
|
89
|
+
path: "/open-api"
|
|
90
|
+
}),
|
|
84
91
|
...userAuth.features?.apiKeys?.enabled ? [
|
|
85
92
|
apiKey({
|
|
86
93
|
defaultPrefix: "opaca_",
|
|
@@ -158,19 +165,50 @@ async function createAuth(config) {
|
|
|
158
165
|
}
|
|
159
166
|
}
|
|
160
167
|
},
|
|
161
|
-
plugins
|
|
168
|
+
plugins,
|
|
169
|
+
logger: {
|
|
170
|
+
disabled: config.logger?.disabled,
|
|
171
|
+
disableColors: config.logger?.disableColors,
|
|
172
|
+
level: config.logger?.level,
|
|
173
|
+
log: (level, message, ...args) => {
|
|
174
|
+
const rebrand = (msg) => {
|
|
175
|
+
if (typeof msg !== "string")
|
|
176
|
+
return String(msg);
|
|
177
|
+
return msg.replace(/\[better-auth\]\s*/g, "").replace(/BETTER_AUTH_SECRET/g, "OPACA_SECRET").replace(/`npx auth secret`/g, "`openssl rand -base64 32`").replace(/npx auth secret/g, "openssl rand -base64 32").replace(/^Warning:\s*/i, "");
|
|
178
|
+
};
|
|
179
|
+
const branded = rebrand(message);
|
|
180
|
+
const brandedArgs = args.map((a) => typeof a === "string" ? rebrand(a) : a);
|
|
181
|
+
const authLogger = new OpacaLogger(config.logger);
|
|
182
|
+
switch (level) {
|
|
183
|
+
case "debug":
|
|
184
|
+
authLogger.debug(branded, ...brandedArgs);
|
|
185
|
+
break;
|
|
186
|
+
case "info":
|
|
187
|
+
authLogger.info(branded, ...brandedArgs);
|
|
188
|
+
break;
|
|
189
|
+
case "warn":
|
|
190
|
+
authLogger.warn(branded, ...brandedArgs);
|
|
191
|
+
break;
|
|
192
|
+
case "error":
|
|
193
|
+
authLogger.error(branded, ...brandedArgs);
|
|
194
|
+
break;
|
|
195
|
+
default:
|
|
196
|
+
authLogger.info(branded, ...brandedArgs);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
162
200
|
});
|
|
163
201
|
}
|
|
164
202
|
|
|
165
203
|
// src/config-utils.ts
|
|
166
204
|
init_system_schema();
|
|
167
|
-
function sanitizeConfig(config) {
|
|
205
|
+
function sanitizeConfig(config, settings = {}) {
|
|
168
206
|
const collections = [...config.collections];
|
|
169
|
-
const supportsAuth =
|
|
207
|
+
const supportsAuth = ["sqlite", "postgres", "d1", "bun-sqlite", "better-sqlite3"].includes(config.db.name);
|
|
170
208
|
const systemCollections = getSystemCollections();
|
|
171
209
|
for (const systemCol of systemCollections) {
|
|
172
210
|
const isAsset = systemCol.slug === "_opaca_assets";
|
|
173
|
-
const isAuth = ["_users", "_sessions", "_accounts", "_verifications", "_api_keys"].includes(systemCol.slug);
|
|
211
|
+
const isAuth = ["_users", "_sessions", "_accounts", "_verifications", "_api_keys", "_opaca_plugin_settings"].includes(systemCol.slug);
|
|
174
212
|
if (isAsset && config.storages || isAuth && supportsAuth) {
|
|
175
213
|
if (!collections.find((col) => col.slug === systemCol.slug)) {
|
|
176
214
|
collections.push({
|
|
@@ -235,7 +273,20 @@ function sanitizeConfig(config) {
|
|
|
235
273
|
acc[key] = {};
|
|
236
274
|
return acc;
|
|
237
275
|
}, {}) : {},
|
|
238
|
-
i18n: config.i18n
|
|
276
|
+
i18n: config.i18n,
|
|
277
|
+
plugins: config.plugins?.map((p) => ({
|
|
278
|
+
name: p.name,
|
|
279
|
+
label: p.label,
|
|
280
|
+
description: p.description,
|
|
281
|
+
version: p.version,
|
|
282
|
+
author: p.author,
|
|
283
|
+
homepage: p.homepage,
|
|
284
|
+
icon: p.icon,
|
|
285
|
+
adminAssets: p.adminAssets ? p.adminAssets() : undefined,
|
|
286
|
+
adminUI: p.adminUI,
|
|
287
|
+
settings: settings[p.name] || {},
|
|
288
|
+
configSchema: p.configSchema ? p.configSchema.map(sanitizeField) : undefined
|
|
289
|
+
}))
|
|
239
290
|
};
|
|
240
291
|
}
|
|
241
292
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineCustomField
|
|
3
|
+
} from "./chunk-rqyjjqgy.js";
|
|
4
|
+
|
|
5
|
+
// src/admin/react.tsx
|
|
6
|
+
import React from "react";
|
|
7
|
+
import { createRoot } from "react-dom/client";
|
|
8
|
+
function defineReactField(tagName, Component) {
|
|
9
|
+
defineCustomField(tagName, {
|
|
10
|
+
mount: (container, props) => {
|
|
11
|
+
const el = container;
|
|
12
|
+
const root = createRoot(el);
|
|
13
|
+
el._opacaReactRoot = root;
|
|
14
|
+
root.render(React.createElement(Component, props));
|
|
15
|
+
},
|
|
16
|
+
update: (container, props) => {
|
|
17
|
+
const el = container;
|
|
18
|
+
const root = el._opacaReactRoot;
|
|
19
|
+
if (root) {
|
|
20
|
+
root.render(React.createElement(Component, props));
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
unmount: (container) => {
|
|
24
|
+
const el = container;
|
|
25
|
+
const root = el._opacaReactRoot;
|
|
26
|
+
if (root) {
|
|
27
|
+
setTimeout(() => {
|
|
28
|
+
root.unmount();
|
|
29
|
+
delete el._opacaReactRoot;
|
|
30
|
+
}, 0);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export { defineReactField };
|