@dyrected/core 2.5.59 → 2.5.61
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/app-config-9hs8DRED.d.cts +1718 -0
- package/dist/app-config-9hs8DRED.d.ts +1718 -0
- package/dist/app-config-C0twGIaF.d.cts +1597 -0
- package/dist/app-config-C0twGIaF.d.ts +1597 -0
- package/dist/app-config-C0uxEU7Q.d.cts +1874 -0
- package/dist/app-config-C0uxEU7Q.d.ts +1874 -0
- package/dist/app-config-CBOn8IyZ.d.cts +1838 -0
- package/dist/app-config-CBOn8IyZ.d.ts +1838 -0
- package/dist/app-config-CstocO9M.d.cts +1580 -0
- package/dist/app-config-CstocO9M.d.ts +1580 -0
- package/dist/app-config-CwaU1de2.d.cts +1868 -0
- package/dist/app-config-CwaU1de2.d.ts +1868 -0
- package/dist/app-config-DIwvzbpe.d.cts +1555 -0
- package/dist/app-config-DIwvzbpe.d.ts +1555 -0
- package/dist/app-config-DVdSospO.d.cts +1842 -0
- package/dist/app-config-DVdSospO.d.ts +1842 -0
- package/dist/app-config-Dkndq5ni.d.cts +1580 -0
- package/dist/app-config-Dkndq5ni.d.ts +1580 -0
- package/dist/app-config-P-SF5nnR.d.cts +1568 -0
- package/dist/app-config-P-SF5nnR.d.ts +1568 -0
- package/dist/app-config-ouBRb6Bf.d.cts +1716 -0
- package/dist/app-config-ouBRb6Bf.d.ts +1716 -0
- package/dist/chunk-2WODKOUB.js +1627 -0
- package/dist/chunk-35NM2WPO.js +1658 -0
- package/dist/chunk-3CMG2BH2.js +1618 -0
- package/dist/chunk-CUOPCOU2.js +1644 -0
- package/dist/chunk-DCY7QHRB.js +1637 -0
- package/dist/chunk-EOW4YMAV.js +1637 -0
- package/dist/chunk-JFA22YFB.js +1625 -0
- package/dist/chunk-WF5A6PDJ.js +1618 -0
- package/dist/index.cjs +209 -47
- package/dist/index.d.cts +79 -4
- package/dist/index.d.ts +79 -4
- package/dist/index.js +42 -4
- package/dist/server.cjs +388 -56
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +224 -13
- package/package.json +1 -1
package/dist/server.d.cts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import * as hono from 'hono';
|
|
3
3
|
import { Hono, Context } from 'hono';
|
|
4
|
-
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-
|
|
4
|
+
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-C0uxEU7Q.cjs';
|
|
5
5
|
import * as hono_utils_http_status from 'hono/utils/http-status';
|
|
6
6
|
import * as hono_utils_types from 'hono/utils/types';
|
|
7
7
|
import 'lucide-react';
|
package/dist/server.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as hono_types from 'hono/types';
|
|
2
2
|
import * as hono from 'hono';
|
|
3
3
|
import { Hono, Context } from 'hono';
|
|
4
|
-
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-
|
|
4
|
+
import { D as DyrectedConfig, C as CollectionConfig, G as GlobalConfig } from './app-config-C0uxEU7Q.js';
|
|
5
5
|
import * as hono_utils_http_status from 'hono/utils/http-status';
|
|
6
6
|
import * as hono_utils_types from 'hono/utils/types';
|
|
7
7
|
import 'lucide-react';
|
package/dist/server.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
runCollectionHooks,
|
|
14
14
|
saveWorkflowDraft,
|
|
15
15
|
transitionWorkflow
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-35NM2WPO.js";
|
|
17
17
|
|
|
18
18
|
// src/app.ts
|
|
19
19
|
import { Hono } from "hono";
|
|
@@ -374,12 +374,16 @@ async function resolveAccess(config, access, args) {
|
|
|
374
374
|
}
|
|
375
375
|
}
|
|
376
376
|
if (isNamedAccessPolicy(access)) {
|
|
377
|
-
const
|
|
378
|
-
if (
|
|
377
|
+
const policy = config.accessPolicies?.[access.policy];
|
|
378
|
+
if (policy === void 0) {
|
|
379
379
|
console.error(`[dyrected/core] Unknown access policy "${access.policy}".`);
|
|
380
380
|
return false;
|
|
381
381
|
}
|
|
382
|
+
if (typeof policy === "string" || typeof policy === "boolean") {
|
|
383
|
+
return evaluateAccess(policy, args);
|
|
384
|
+
}
|
|
382
385
|
try {
|
|
386
|
+
const resolver = policy;
|
|
383
387
|
return await resolver({ ...args, params: access.params });
|
|
384
388
|
} catch (err) {
|
|
385
389
|
console.error(`[dyrected/core] Access policy "${access.policy}" failed:`, err);
|
|
@@ -487,14 +491,15 @@ async function applyFieldWriteAccess(context, data) {
|
|
|
487
491
|
continue;
|
|
488
492
|
}
|
|
489
493
|
if (!field.name || !(field.name in result)) continue;
|
|
490
|
-
const
|
|
494
|
+
const writeRule = context.operation === "create" ? field.access?.create ?? field.access?.update : field.access?.update;
|
|
495
|
+
const canWrite = await resolveBooleanAccess(context.config, writeRule, {
|
|
491
496
|
user: context.user,
|
|
492
497
|
req: context.req,
|
|
493
498
|
doc: context.doc,
|
|
494
499
|
data: context.data,
|
|
495
500
|
id: resolveFieldAccessId(context)
|
|
496
501
|
});
|
|
497
|
-
if (!
|
|
502
|
+
if (!canWrite) {
|
|
498
503
|
delete result[field.name];
|
|
499
504
|
continue;
|
|
500
505
|
}
|
|
@@ -606,9 +611,9 @@ var CollectionController = class {
|
|
|
606
611
|
});
|
|
607
612
|
}
|
|
608
613
|
const readonlyDb = createReadonlyDb(db);
|
|
609
|
-
const limit = Number(c.req.query("limit")) || 10;
|
|
614
|
+
const limit = Math.min(Number(c.req.query("limit")) || 10, 100);
|
|
610
615
|
const page = Number(c.req.query("page")) || 1;
|
|
611
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) :
|
|
616
|
+
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
612
617
|
const sort = c.req.query("sort") || void 0;
|
|
613
618
|
const user = c.get("user");
|
|
614
619
|
let where = void 0;
|
|
@@ -654,7 +659,8 @@ var CollectionController = class {
|
|
|
654
659
|
limit,
|
|
655
660
|
page,
|
|
656
661
|
sort,
|
|
657
|
-
where
|
|
662
|
+
where,
|
|
663
|
+
fields: this.collection.fields
|
|
658
664
|
});
|
|
659
665
|
if (result.total === 0 && this.collection.initialData && !where && page === 1) {
|
|
660
666
|
console.log(`[dyrected/core] Auto-seeding collection "${this.collection.slug}" from config.initialData`);
|
|
@@ -666,7 +672,8 @@ var CollectionController = class {
|
|
|
666
672
|
limit,
|
|
667
673
|
page,
|
|
668
674
|
sort,
|
|
669
|
-
where
|
|
675
|
+
where,
|
|
676
|
+
fields: this.collection.fields
|
|
670
677
|
});
|
|
671
678
|
}
|
|
672
679
|
result.docs = result.docs.map((doc) => this.collection.workflow ? materializeWorkflowDocument(doc, this.collection.workflow, user) : doc).filter((doc) => doc !== null);
|
|
@@ -724,7 +731,7 @@ var CollectionController = class {
|
|
|
724
731
|
}
|
|
725
732
|
const readonlyDb = createReadonlyDb(db);
|
|
726
733
|
const id = c.req.param("id");
|
|
727
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) :
|
|
734
|
+
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
728
735
|
const user = c.get("user");
|
|
729
736
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
730
737
|
let doc = await db.findOne({ collection: this.collection.slug, id });
|
|
@@ -813,7 +820,8 @@ var CollectionController = class {
|
|
|
813
820
|
fields: this.collection.fields,
|
|
814
821
|
user,
|
|
815
822
|
req: this.toHookRequestContext(c),
|
|
816
|
-
data
|
|
823
|
+
data,
|
|
824
|
+
operation: "create"
|
|
817
825
|
}, data);
|
|
818
826
|
data = await executeFieldBeforeChange(this.collection.fields, data, null, user, readonlyDb);
|
|
819
827
|
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
@@ -908,7 +916,8 @@ var CollectionController = class {
|
|
|
908
916
|
fields: this.collection.fields,
|
|
909
917
|
user,
|
|
910
918
|
req: this.toHookRequestContext(c),
|
|
911
|
-
data
|
|
919
|
+
data,
|
|
920
|
+
operation: "create"
|
|
912
921
|
}, data);
|
|
913
922
|
data = await executeFieldBeforeChange(this.collection.fields, data, null, user, readonlyDb);
|
|
914
923
|
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
@@ -1330,7 +1339,7 @@ var GlobalController = class {
|
|
|
1330
1339
|
const db = config.db;
|
|
1331
1340
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
1332
1341
|
const readonlyDb = createReadonlyDb(db);
|
|
1333
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) :
|
|
1342
|
+
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
1334
1343
|
const user = c.get("user");
|
|
1335
1344
|
let query = void 0;
|
|
1336
1345
|
const beforeReadResult = await runCollectionHooks(this.global.hooks?.beforeRead, {
|
|
@@ -2554,6 +2563,150 @@ var PreviewController = class {
|
|
|
2554
2563
|
}
|
|
2555
2564
|
};
|
|
2556
2565
|
|
|
2566
|
+
// src/controllers/audit.controller.ts
|
|
2567
|
+
var AuditController = class {
|
|
2568
|
+
parseQuery(c) {
|
|
2569
|
+
const limit = Math.min(Number(c.req.query("limit")) || 50, 100);
|
|
2570
|
+
const page = Math.max(Number(c.req.query("page")) || 1, 1);
|
|
2571
|
+
const sort = c.req.query("sort") || "-timestamp";
|
|
2572
|
+
const whereRaw = c.req.query("where");
|
|
2573
|
+
if (!whereRaw) {
|
|
2574
|
+
return { limit, page, sort };
|
|
2575
|
+
}
|
|
2576
|
+
try {
|
|
2577
|
+
return {
|
|
2578
|
+
limit,
|
|
2579
|
+
page,
|
|
2580
|
+
sort,
|
|
2581
|
+
where: JSON.parse(decodeURIComponent(whereRaw))
|
|
2582
|
+
};
|
|
2583
|
+
} catch {
|
|
2584
|
+
return { limit, page, sort };
|
|
2585
|
+
}
|
|
2586
|
+
}
|
|
2587
|
+
emptyResult(limit, page) {
|
|
2588
|
+
return {
|
|
2589
|
+
docs: [],
|
|
2590
|
+
total: 0,
|
|
2591
|
+
limit,
|
|
2592
|
+
page,
|
|
2593
|
+
totalPages: 0,
|
|
2594
|
+
hasNextPage: false,
|
|
2595
|
+
hasPrevPage: page > 1
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
async collectAccessibleDocumentIds(db, collection, constraint) {
|
|
2599
|
+
const ids = [];
|
|
2600
|
+
const limit = 100;
|
|
2601
|
+
let page = 1;
|
|
2602
|
+
while (true) {
|
|
2603
|
+
const result = await db.find({
|
|
2604
|
+
collection: collection.slug,
|
|
2605
|
+
where: constraint,
|
|
2606
|
+
limit,
|
|
2607
|
+
page
|
|
2608
|
+
});
|
|
2609
|
+
for (const doc of result.docs) {
|
|
2610
|
+
if (typeof doc?.id === "string" && doc.id.length > 0) {
|
|
2611
|
+
ids.push(doc.id);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
if (!result.hasNextPage || page >= result.totalPages) {
|
|
2615
|
+
break;
|
|
2616
|
+
}
|
|
2617
|
+
page += 1;
|
|
2618
|
+
}
|
|
2619
|
+
return ids;
|
|
2620
|
+
}
|
|
2621
|
+
async buildCollectionAuditScope(c, collection) {
|
|
2622
|
+
const config = c.get("config");
|
|
2623
|
+
const auditAccess = collection.access?.readAudit ?? collection.access?.read;
|
|
2624
|
+
const access = await resolveCollectionAccess(
|
|
2625
|
+
config,
|
|
2626
|
+
collection.slug,
|
|
2627
|
+
"read",
|
|
2628
|
+
auditAccess,
|
|
2629
|
+
{
|
|
2630
|
+
user: c.get("user"),
|
|
2631
|
+
req: toHookRequestContext(c.req)
|
|
2632
|
+
}
|
|
2633
|
+
);
|
|
2634
|
+
if (!access.allowed) {
|
|
2635
|
+
return { denied: true, where: {} };
|
|
2636
|
+
}
|
|
2637
|
+
let where = { collection: { equals: collection.slug } };
|
|
2638
|
+
if (access.constraint) {
|
|
2639
|
+
const ids = await this.collectAccessibleDocumentIds(config.db, collection, access.constraint);
|
|
2640
|
+
where = mergeWhereConstraint(where, { documentId: { in: ids } });
|
|
2641
|
+
}
|
|
2642
|
+
return { denied: false, where };
|
|
2643
|
+
}
|
|
2644
|
+
async getVisibleCollections(c) {
|
|
2645
|
+
const config = c.get("config");
|
|
2646
|
+
const collections = /* @__PURE__ */ new Map();
|
|
2647
|
+
for (const collection of config.collections) {
|
|
2648
|
+
collections.set(collection.slug, collection);
|
|
2649
|
+
}
|
|
2650
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
2651
|
+
if (config.onSchemaFetch && siteId) {
|
|
2652
|
+
const dynamic = await config.onSchemaFetch(siteId);
|
|
2653
|
+
for (const collection of dynamic.collections || []) {
|
|
2654
|
+
if (!collections.has(collection.slug)) {
|
|
2655
|
+
collections.set(collection.slug, collection);
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
return Array.from(collections.values());
|
|
2660
|
+
}
|
|
2661
|
+
async findForCollection(c, collection) {
|
|
2662
|
+
const config = c.get("config");
|
|
2663
|
+
if (!config.db) return c.json({ message: "Database not configured" }, 500);
|
|
2664
|
+
if (!collection.audit) return c.json({ message: "Audit is not enabled for this collection" }, 404);
|
|
2665
|
+
const query = this.parseQuery(c);
|
|
2666
|
+
const scope = await this.buildCollectionAuditScope(c, collection);
|
|
2667
|
+
if (scope.denied) {
|
|
2668
|
+
return c.json({ error: true, message: `Access denied: read on ${collection.slug}` }, 403);
|
|
2669
|
+
}
|
|
2670
|
+
const where = query.where ? mergeWhereConstraint(scope.where, query.where) : scope.where;
|
|
2671
|
+
const result = await config.db.find({
|
|
2672
|
+
collection: "__audit",
|
|
2673
|
+
where,
|
|
2674
|
+
limit: query.limit,
|
|
2675
|
+
page: query.page,
|
|
2676
|
+
sort: query.sort
|
|
2677
|
+
});
|
|
2678
|
+
return c.json(result);
|
|
2679
|
+
}
|
|
2680
|
+
async findAll(c) {
|
|
2681
|
+
const config = c.get("config");
|
|
2682
|
+
if (!config.db) return c.json({ message: "Database not configured" }, 500);
|
|
2683
|
+
const query = this.parseQuery(c);
|
|
2684
|
+
const collections = (await this.getVisibleCollections(c)).filter((collection) => collection.audit);
|
|
2685
|
+
const scopes = [];
|
|
2686
|
+
for (const collection of collections) {
|
|
2687
|
+
const scope = await this.buildCollectionAuditScope(c, collection);
|
|
2688
|
+
if (!scope.denied) {
|
|
2689
|
+
scopes.push(scope.where);
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
if (scopes.length === 0) {
|
|
2693
|
+
return c.json(this.emptyResult(query.limit, query.page));
|
|
2694
|
+
}
|
|
2695
|
+
let where = scopes.length === 1 ? scopes[0] : { OR: scopes };
|
|
2696
|
+
if (query.where) {
|
|
2697
|
+
where = mergeWhereConstraint(where, query.where);
|
|
2698
|
+
}
|
|
2699
|
+
const result = await config.db.find({
|
|
2700
|
+
collection: "__audit",
|
|
2701
|
+
where,
|
|
2702
|
+
limit: query.limit,
|
|
2703
|
+
page: query.page,
|
|
2704
|
+
sort: query.sort
|
|
2705
|
+
});
|
|
2706
|
+
return c.json(result);
|
|
2707
|
+
}
|
|
2708
|
+
};
|
|
2709
|
+
|
|
2557
2710
|
// src/middleware/auth.ts
|
|
2558
2711
|
function getBearerToken(c) {
|
|
2559
2712
|
const authHeader = c.req.header("Authorization");
|
|
@@ -2730,6 +2883,7 @@ function serializeFieldForApi(f) {
|
|
|
2730
2883
|
return serialized;
|
|
2731
2884
|
}
|
|
2732
2885
|
function registerRoutes(app, config) {
|
|
2886
|
+
const optionsCache = /* @__PURE__ */ new Map();
|
|
2733
2887
|
app.get("/api/schemas", optionalAuth(config), async (c) => {
|
|
2734
2888
|
const siteId = c.req.header("X-Site-Id");
|
|
2735
2889
|
let collections = [...config.collections];
|
|
@@ -2750,6 +2904,10 @@ function registerRoutes(app, config) {
|
|
|
2750
2904
|
const serializeAccess = async (access) => {
|
|
2751
2905
|
if (typeof access === "string") return access;
|
|
2752
2906
|
if (typeof access === "boolean") return access;
|
|
2907
|
+
if (access && typeof access === "object" && typeof access.policy === "string") {
|
|
2908
|
+
const policy = config.accessPolicies?.[access.policy];
|
|
2909
|
+
if (typeof policy === "string" || typeof policy === "boolean") return policy;
|
|
2910
|
+
}
|
|
2753
2911
|
return resolveBooleanAccess(config, access, accessArgs);
|
|
2754
2912
|
};
|
|
2755
2913
|
const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
@@ -2778,11 +2936,14 @@ function registerRoutes(app, config) {
|
|
|
2778
2936
|
admin: f.admin,
|
|
2779
2937
|
access: {
|
|
2780
2938
|
read: await serializeAccess(f.access?.read),
|
|
2939
|
+
create: await serializeAccess(f.access?.create),
|
|
2781
2940
|
update: await serializeAccess(f.access?.update)
|
|
2782
2941
|
}
|
|
2783
2942
|
}))),
|
|
2784
2943
|
upload: !!col.upload,
|
|
2785
2944
|
auth: !!col.auth,
|
|
2945
|
+
audit: !!col.audit,
|
|
2946
|
+
drafts: !!col.drafts,
|
|
2786
2947
|
admin: col.admin,
|
|
2787
2948
|
workflow: col.workflow ? {
|
|
2788
2949
|
initialState: col.workflow.initialState,
|
|
@@ -2885,10 +3046,12 @@ function registerRoutes(app, config) {
|
|
|
2885
3046
|
return c.json({ error: true, message: `Field ${fieldName} not found in ${colSlug}` }, 404);
|
|
2886
3047
|
}
|
|
2887
3048
|
let resolver;
|
|
3049
|
+
let cacheTTL;
|
|
2888
3050
|
if (typeof field.options === "function") {
|
|
2889
3051
|
resolver = field.options;
|
|
2890
3052
|
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
2891
3053
|
resolver = field.options.resolve;
|
|
3054
|
+
cacheTTL = field.options.cacheTTL;
|
|
2892
3055
|
}
|
|
2893
3056
|
if (!resolver) {
|
|
2894
3057
|
return c.json({ error: true, message: `Field ${fieldName} in ${colSlug} is not dynamic` }, 400);
|
|
@@ -2901,11 +3064,32 @@ function registerRoutes(app, config) {
|
|
|
2901
3064
|
headers: c.req.header(),
|
|
2902
3065
|
raw: c.req.raw
|
|
2903
3066
|
};
|
|
3067
|
+
const shouldCache = typeof cacheTTL === "number" && cacheTTL > 0;
|
|
3068
|
+
let cacheKey = "";
|
|
3069
|
+
if (shouldCache) {
|
|
3070
|
+
cacheKey = JSON.stringify([
|
|
3071
|
+
siteId ?? "",
|
|
3072
|
+
colSlug,
|
|
3073
|
+
fieldName,
|
|
3074
|
+
user?.id ?? null,
|
|
3075
|
+
queryParams
|
|
3076
|
+
]);
|
|
3077
|
+
const hit = optionsCache.get(cacheKey);
|
|
3078
|
+
if (hit && hit.expires > Date.now()) {
|
|
3079
|
+
return c.json(hit.value);
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
2904
3082
|
const result = await resolver({
|
|
2905
3083
|
db,
|
|
2906
3084
|
user,
|
|
2907
3085
|
req: reqContext
|
|
2908
3086
|
});
|
|
3087
|
+
if (shouldCache) {
|
|
3088
|
+
optionsCache.set(cacheKey, {
|
|
3089
|
+
expires: Date.now() + cacheTTL * 1e3,
|
|
3090
|
+
value: result
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
2909
3093
|
return c.json(result);
|
|
2910
3094
|
} catch (err) {
|
|
2911
3095
|
console.error(`[dyrected/core] Failed to resolve dynamic options for field ${fieldName}:`, err);
|
|
@@ -3050,6 +3234,7 @@ function registerRoutes(app, config) {
|
|
|
3050
3234
|
app.post(`${path}/invite`, requireAuth(config), (c) => authController.invite(c));
|
|
3051
3235
|
app.post(`${path}/accept-invite`, (c) => authController.acceptInvite(c));
|
|
3052
3236
|
}
|
|
3237
|
+
const auditController = new AuditController();
|
|
3053
3238
|
for (const collection of config.collections) {
|
|
3054
3239
|
const path = `/api/collections/${collection.slug}`;
|
|
3055
3240
|
const controller = new CollectionController(collection);
|
|
@@ -3057,6 +3242,9 @@ function registerRoutes(app, config) {
|
|
|
3057
3242
|
app.post(path, (c) => controller.create(c));
|
|
3058
3243
|
app.post(`${path}/media`, (c) => controller.create(c));
|
|
3059
3244
|
app.delete(`${path}/delete-many`, (c) => controller.deleteMany(c));
|
|
3245
|
+
if (collection.audit) {
|
|
3246
|
+
app.get(`${path}/__audit`, (c) => auditController.findForCollection(c, collection));
|
|
3247
|
+
}
|
|
3060
3248
|
app.get(`${path}/:id`, (c) => controller.findOne(c));
|
|
3061
3249
|
app.patch(`${path}/:id`, (c) => controller.update(c));
|
|
3062
3250
|
app.delete(`${path}/:id`, (c) => controller.delete(c));
|
|
@@ -3076,9 +3264,32 @@ function registerRoutes(app, config) {
|
|
|
3076
3264
|
app.patch(path, (c) => controller.update(c));
|
|
3077
3265
|
app.post(`${path}/seed`, (c) => controller.seed(c));
|
|
3078
3266
|
}
|
|
3267
|
+
if (!process.env.DYRECTED_JWT_SECRET) {
|
|
3268
|
+
console.warn(
|
|
3269
|
+
'[dyrected] DYRECTED_JWT_SECRET is not set \u2014 token-mode live preview is signing with an insecure default. Set DYRECTED_JWT_SECRET before relying on `previewMode: "token"` in production.'
|
|
3270
|
+
);
|
|
3271
|
+
}
|
|
3079
3272
|
const previewController = new PreviewController();
|
|
3080
3273
|
app.post("/api/preview-token", requireAuth(config), (c) => previewController.createToken(c));
|
|
3081
3274
|
app.get("/api/preview-data", (c) => previewController.getData(c));
|
|
3275
|
+
app.get("/api/audit", (c) => auditController.findAll(c));
|
|
3276
|
+
app.get("/api/collections/:slug/__audit", async (c) => {
|
|
3277
|
+
const slug = c.req.param("slug");
|
|
3278
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
3279
|
+
const config2 = c.get("config");
|
|
3280
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
3281
|
+
return c.json({ message: "Not Found" }, 404);
|
|
3282
|
+
}
|
|
3283
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
3284
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
3285
|
+
}
|
|
3286
|
+
const dynamic = await config2.onSchemaFetch(siteId);
|
|
3287
|
+
const collection = dynamic.collections?.find((col) => col.slug === slug);
|
|
3288
|
+
if (!collection?.audit) {
|
|
3289
|
+
return c.json({ message: `Collection "${slug}" not found or has no audit log` }, 404);
|
|
3290
|
+
}
|
|
3291
|
+
return auditController.findForCollection(c, collection);
|
|
3292
|
+
});
|
|
3082
3293
|
app.post("/api/collections/:slug/:id/transitions/:transition", requireAuth(config), async (c) => {
|
|
3083
3294
|
const slug = c.req.param("slug");
|
|
3084
3295
|
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|