opacacms 0.1.21 → 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/README.md +792 -50
- package/dist/admin/auth-client.d.ts +39 -39
- package/dist/admin/index.js +2360 -1392
- 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.js +8 -0
- package/dist/admin/webcomponent.js +20 -2
- package/dist/admin.css +1 -1
- package/dist/auth/index.d.ts +101 -41
- package/dist/{chunk-fqastxq9.js → chunk-06ks4ggh.js} +133 -44
- package/dist/{chunk-xrfhhz85.js → chunk-2es275xs.js} +480 -85
- 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-0sdceeys.js → chunk-6d1vdfwa.js} +121 -31
- package/dist/{chunk-wmvjvn7b.js → chunk-6qq3ne6b.js} +39 -1
- package/dist/{chunk-0am1m47g.js → chunk-6v1fw7q7.js} +5 -5
- package/dist/{chunk-t9v845m2.js → chunk-7y1nbmw6.js} +34 -3
- package/dist/chunk-8scgdznr.js +44 -0
- package/dist/{chunk-mycmsjd9.js → chunk-b3kr8w41.js} +57 -6
- package/dist/chunk-bexcv7xe.js +36 -0
- package/dist/{chunk-ekxkvqjm.js → chunk-bygjkgrx.js} +124 -34
- package/dist/{chunk-16vgcf3k.js → chunk-byq8g0rd.js} +1 -1
- package/dist/{chunk-cpw2y3pn.js → chunk-dykn5hr6.js} +7 -7
- package/dist/chunk-fj19qccp.js +78 -0
- package/dist/{chunk-n1xraw7j.js → chunk-g1jb60xd.js} +1 -1
- package/dist/{chunk-xa7rjsn2.js → chunk-j53pz21t.js} +2 -2
- package/dist/{chunk-nb7ctdg8.js → chunk-jdfw4v3r.js} +1 -1
- package/dist/chunk-mkn49zmy.js +102 -0
- package/dist/{chunk-59sg3pw9.js → chunk-n133qpsm.js} +128 -34
- 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-t0zg026p.js +71 -0
- package/dist/{chunk-61kwqve4.js → chunk-tfnaf41w.js} +118 -37
- 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/adapter.d.ts +2 -2
- package/dist/db/better-sqlite.d.ts +2 -1
- package/dist/db/better-sqlite.js +5 -5
- package/dist/db/bun-sqlite.d.ts +2 -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 +3 -3
- package/dist/db/postgres.js +5 -5
- package/dist/db/sqlite.d.ts +2 -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 -26
- 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 -7
- package/dist/schema/index.js +22 -6
- 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/storage/index.js +34 -25
- package/dist/types.d.ts +224 -17
- package/dist/utils/logger.d.ts +13 -35
- package/dist/validation.d.ts +40 -0
- package/dist/validator.d.ts +1 -1
- package/package.json +41 -27
- 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
|
@@ -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,31 +3,31 @@ 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
|
+
import {
|
|
19
|
+
__require
|
|
20
|
+
} from "./chunk-8sqjbsgt.js";
|
|
18
21
|
|
|
19
22
|
// src/db/sqlite.ts
|
|
20
23
|
import fs from "node:fs/promises";
|
|
21
24
|
import path from "node:path";
|
|
22
|
-
import { createRequire } from "node:module";
|
|
23
25
|
import { CompiledQuery, FileMigrationProvider, Kysely, Migrator, SqliteDialect } from "kysely";
|
|
24
|
-
var require2 = createRequire(import.meta.url);
|
|
25
|
-
var Database = require2("better-sqlite3");
|
|
26
|
-
|
|
27
26
|
class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
27
|
+
path;
|
|
28
28
|
name = "sqlite";
|
|
29
29
|
_rawDb;
|
|
30
|
-
_db;
|
|
30
|
+
_db = null;
|
|
31
31
|
_collections = [];
|
|
32
32
|
_globals = [];
|
|
33
33
|
push;
|
|
@@ -37,27 +37,37 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
37
37
|
return this._rawDb;
|
|
38
38
|
}
|
|
39
39
|
get db() {
|
|
40
|
+
if (!this._db)
|
|
41
|
+
throw new Error("Database not connected. Call connect() first.");
|
|
40
42
|
return this._db;
|
|
41
43
|
}
|
|
42
44
|
constructor(path2, options) {
|
|
43
45
|
super();
|
|
44
|
-
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);
|
|
45
58
|
this._db = new Kysely({
|
|
46
59
|
dialect: new SqliteDialect({
|
|
47
60
|
database: this._rawDb
|
|
48
61
|
})
|
|
49
62
|
});
|
|
50
|
-
this.push = options?.push ?? true;
|
|
51
|
-
this.pushDestructive = options?.pushDestructive ?? false;
|
|
52
|
-
this.migrationDir = options?.migrationDir ?? "./migrations";
|
|
53
63
|
}
|
|
54
|
-
async connect() {}
|
|
55
64
|
async disconnect() {
|
|
56
|
-
|
|
65
|
+
if (this._db)
|
|
66
|
+
await this.db.destroy();
|
|
57
67
|
}
|
|
58
68
|
async unsafe(query, params) {
|
|
59
69
|
const compiled = CompiledQuery.raw(query, params || []);
|
|
60
|
-
const result = await this.
|
|
70
|
+
const result = await this.db.executeQuery(compiled);
|
|
61
71
|
return result.rows;
|
|
62
72
|
}
|
|
63
73
|
async coerceData(collection, data) {
|
|
@@ -100,7 +110,7 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
100
110
|
}
|
|
101
111
|
async count(collection, query) {
|
|
102
112
|
const tableName = toSnakeCase(collection);
|
|
103
|
-
let qb = this.
|
|
113
|
+
let qb = this.db.selectFrom(tableName).select((eb) => eb.fn.count("id").as("count"));
|
|
104
114
|
if (query && Object.keys(query).length > 0) {
|
|
105
115
|
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
106
116
|
}
|
|
@@ -109,7 +119,7 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
109
119
|
}
|
|
110
120
|
async create(collection, data) {
|
|
111
121
|
const tableName = toSnakeCase(collection);
|
|
112
|
-
return this.
|
|
122
|
+
return this.db.transaction().execute(async (tx) => {
|
|
113
123
|
const colDef = this._collections.find((c) => c.slug === collection);
|
|
114
124
|
const jsonFields = colDef?.fields.filter((f) => f.name && (["richtext", "json", "file"].includes(f.type) || f.localized)).map((f) => f.name) || [];
|
|
115
125
|
const flatData = flattenPayload(data, "", jsonFields);
|
|
@@ -186,7 +196,7 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
186
196
|
}
|
|
187
197
|
async findOne(collection, query, tx) {
|
|
188
198
|
const tableName = toSnakeCase(collection);
|
|
189
|
-
const executor = tx || this.
|
|
199
|
+
const executor = tx || this.db;
|
|
190
200
|
let qb = executor.selectFrom(tableName).selectAll();
|
|
191
201
|
if (query && Object.keys(query).length > 0) {
|
|
192
202
|
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
@@ -252,7 +262,7 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
252
262
|
const offset = (page - 1) * limit;
|
|
253
263
|
const total = await this.count(collection, query);
|
|
254
264
|
const tableName = toSnakeCase(collection);
|
|
255
|
-
let qb = this.
|
|
265
|
+
let qb = this.db.selectFrom(tableName).selectAll().limit(limit).offset(offset);
|
|
256
266
|
if (query && Object.keys(query).length > 0) {
|
|
257
267
|
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
258
268
|
}
|
|
@@ -265,7 +275,87 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
265
275
|
qb = qb.orderBy("created_at", "desc");
|
|
266
276
|
}
|
|
267
277
|
const rows = await qb.execute();
|
|
268
|
-
|
|
278
|
+
if (rows.length === 0) {
|
|
279
|
+
const totalPages2 = Math.ceil(total / limit);
|
|
280
|
+
return {
|
|
281
|
+
docs: [],
|
|
282
|
+
totalDocs: total,
|
|
283
|
+
limit,
|
|
284
|
+
totalPages: totalPages2,
|
|
285
|
+
page,
|
|
286
|
+
pagingCounter: offset + 1,
|
|
287
|
+
hasNextPage: page * limit < total,
|
|
288
|
+
hasPrevPage: page > 1,
|
|
289
|
+
prevPage: page > 1 ? page - 1 : null,
|
|
290
|
+
nextPage: page < totalPages2 ? page + 1 : null
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const rowIds = rows.map((r) => r.id);
|
|
294
|
+
const docs = rows.map((r) => unflattenRow(r));
|
|
295
|
+
const colDef = this._collections.find((c) => c.slug === collection);
|
|
296
|
+
if (colDef) {
|
|
297
|
+
const relationalFields = getRelationalFields(colDef.fields);
|
|
298
|
+
for (const field of relationalFields) {
|
|
299
|
+
if (!field.name)
|
|
300
|
+
continue;
|
|
301
|
+
const snakeName = toSnakeCase(field.name);
|
|
302
|
+
if (field.type === "relationship" && "hasMany" in field && field.hasMany) {
|
|
303
|
+
const joinTableName = `${toSnakeCase(collection)}_${snakeName}_relations`.toLowerCase();
|
|
304
|
+
try {
|
|
305
|
+
const allRelations = await this.db.selectFrom(joinTableName).selectAll().where("source_id", "in", rowIds).orderBy("order", "asc").execute();
|
|
306
|
+
const relationsBySource = allRelations.reduce((acc, r) => {
|
|
307
|
+
if (!acc[r.source_id])
|
|
308
|
+
acc[r.source_id] = [];
|
|
309
|
+
acc[r.source_id].push(r.target_id);
|
|
310
|
+
return acc;
|
|
311
|
+
}, {});
|
|
312
|
+
const parts = field.name.split("__");
|
|
313
|
+
for (let docIdx = 0;docIdx < docs.length; docIdx++) {
|
|
314
|
+
const rowId = rowIds[docIdx];
|
|
315
|
+
let current = docs[docIdx];
|
|
316
|
+
for (let i = 0;i < parts.length - 1; i++) {
|
|
317
|
+
if (!current[parts[i]])
|
|
318
|
+
current[parts[i]] = {};
|
|
319
|
+
current = current[parts[i]];
|
|
320
|
+
}
|
|
321
|
+
current[parts[parts.length - 1]] = relationsBySource[rowId] || [];
|
|
322
|
+
}
|
|
323
|
+
} catch (e) {}
|
|
324
|
+
} else if (field.type === "blocks" && field.blocks) {
|
|
325
|
+
const blocksBySource = {};
|
|
326
|
+
for (const b of field.blocks) {
|
|
327
|
+
const blockTableName = `${collection}_${snakeName}_${toSnakeCase(b.slug)}`.toLowerCase();
|
|
328
|
+
try {
|
|
329
|
+
const allBlocks = await this.db.selectFrom(blockTableName).selectAll().where("_parent_id", "in", rowIds).execute();
|
|
330
|
+
for (const blk of allBlocks) {
|
|
331
|
+
const uf = unflattenRow(blk);
|
|
332
|
+
uf.blockType = blk.block_type;
|
|
333
|
+
if (!blocksBySource[blk._parent_id])
|
|
334
|
+
blocksBySource[blk._parent_id] = [];
|
|
335
|
+
blocksBySource[blk._parent_id].push(uf);
|
|
336
|
+
}
|
|
337
|
+
} catch (e) {}
|
|
338
|
+
}
|
|
339
|
+
const parts = field.name.split("__");
|
|
340
|
+
for (let docIdx = 0;docIdx < docs.length; docIdx++) {
|
|
341
|
+
const rowId = rowIds[docIdx];
|
|
342
|
+
const blockData = blocksBySource[rowId] || [];
|
|
343
|
+
blockData.sort((a, b) => a._order - b._order);
|
|
344
|
+
blockData.forEach((b) => {
|
|
345
|
+
delete b._order;
|
|
346
|
+
delete b._parentId;
|
|
347
|
+
});
|
|
348
|
+
let current = docs[docIdx];
|
|
349
|
+
for (let i = 0;i < parts.length - 1; i++) {
|
|
350
|
+
if (!current[parts[i]])
|
|
351
|
+
current[parts[i]] = {};
|
|
352
|
+
current = current[parts[i]];
|
|
353
|
+
}
|
|
354
|
+
current[parts[parts.length - 1]] = blockData;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
269
359
|
const totalPages = Math.ceil(total / limit);
|
|
270
360
|
return {
|
|
271
361
|
docs: docs.filter(Boolean),
|
|
@@ -281,7 +371,7 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
281
371
|
};
|
|
282
372
|
}
|
|
283
373
|
async update(collection, query, data) {
|
|
284
|
-
return this.
|
|
374
|
+
return this.db.transaction().execute(async (tx) => {
|
|
285
375
|
let normalizedQuery = query;
|
|
286
376
|
if (typeof query !== "object" || query === null) {
|
|
287
377
|
normalizedQuery = { id: query };
|
|
@@ -374,7 +464,7 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
374
464
|
if (!current)
|
|
375
465
|
return false;
|
|
376
466
|
const tableName = toSnakeCase(collection);
|
|
377
|
-
await this.
|
|
467
|
+
await this.db.transaction().execute(async (tx) => {
|
|
378
468
|
const colDef = this._collections.find((c) => c.slug === collection);
|
|
379
469
|
if (colDef) {
|
|
380
470
|
const relationalFields = getRelationalFields(colDef.fields);
|
|
@@ -403,7 +493,7 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
403
493
|
}
|
|
404
494
|
async updateMany(collection, query, data) {
|
|
405
495
|
const tableName = toSnakeCase(collection);
|
|
406
|
-
return this.
|
|
496
|
+
return this.db.transaction().execute(async (tx) => {
|
|
407
497
|
let qb = tx.updateTable(tableName);
|
|
408
498
|
if (query && Object.keys(query).length > 0) {
|
|
409
499
|
qb = qb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
@@ -432,7 +522,7 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
432
522
|
}
|
|
433
523
|
async deleteMany(collection, query) {
|
|
434
524
|
const tableName = toSnakeCase(collection);
|
|
435
|
-
return this.
|
|
525
|
+
return this.db.transaction().execute(async (tx) => {
|
|
436
526
|
let selectQb = tx.selectFrom(tableName).select("id");
|
|
437
527
|
if (query && Object.keys(query).length > 0) {
|
|
438
528
|
selectQb = selectQb.where((eb) => buildKyselyWhere(eb, query) || eb.val(true));
|
|
@@ -469,7 +559,7 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
469
559
|
}
|
|
470
560
|
async findGlobal(slug) {
|
|
471
561
|
const tableName = toSnakeCase(slug);
|
|
472
|
-
const row = await this.
|
|
562
|
+
const row = await this.db.selectFrom(tableName).selectAll().limit(1).executeTakeFirst();
|
|
473
563
|
return row ? unflattenRow(row) : null;
|
|
474
564
|
}
|
|
475
565
|
async updateGlobal(slug, data) {
|
|
@@ -482,15 +572,15 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
482
572
|
if (!flatData.id)
|
|
483
573
|
flatData.id = "global";
|
|
484
574
|
flatData[toSnakeCase("createdAt")] = now;
|
|
485
|
-
await this.
|
|
575
|
+
await this.db.insertInto(tableName).values(flatData).execute();
|
|
486
576
|
} else {
|
|
487
|
-
await this.
|
|
577
|
+
await this.db.updateTable(tableName).set(flatData).where("id", "=", existing.id).execute();
|
|
488
578
|
}
|
|
489
579
|
return this.findGlobal(slug);
|
|
490
580
|
}
|
|
491
581
|
async runMigrations() {
|
|
492
582
|
const migrator = new Migrator({
|
|
493
|
-
db: this.
|
|
583
|
+
db: this.db,
|
|
494
584
|
provider: new FileMigrationProvider({
|
|
495
585
|
fs,
|
|
496
586
|
path,
|
|
@@ -513,7 +603,7 @@ class SQLiteAdapter extends BaseDatabaseAdapter {
|
|
|
513
603
|
this._collections = collections;
|
|
514
604
|
this._globals = globals;
|
|
515
605
|
if (this.push) {
|
|
516
|
-
await pushSchema(this.
|
|
606
|
+
await pushSchema(this.db, "sqlite", collections, globals, {
|
|
517
607
|
pushDestructive: this.pushDestructive
|
|
518
608
|
});
|
|
519
609
|
}
|
|
@@ -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({
|
|
@@ -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
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 };
|