@dyrected/core 2.5.58 → 2.5.60
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-C0twGIaF.d.cts +1597 -0
- package/dist/app-config-C0twGIaF.d.ts +1597 -0
- package/dist/app-config-C3X5BAyK.d.cts +1539 -0
- package/dist/app-config-C3X5BAyK.d.ts +1539 -0
- package/dist/app-config-CstocO9M.d.cts +1580 -0
- package/dist/app-config-CstocO9M.d.ts +1580 -0
- package/dist/app-config-DIwvzbpe.d.cts +1555 -0
- package/dist/app-config-DIwvzbpe.d.ts +1555 -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-wnLtFu6c.d.cts +1538 -0
- package/dist/app-config-wnLtFu6c.d.ts +1538 -0
- package/dist/chunk-2WODKOUB.js +1627 -0
- package/dist/chunk-3CMG2BH2.js +1618 -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 +141 -42
- package/dist/index.d.cts +37 -4
- package/dist/index.d.ts +37 -4
- package/dist/index.js +8 -1
- package/dist/server.cjs +358 -57
- package/dist/server.d.cts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +226 -16
- 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-C0twGIaF.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-C0twGIaF.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-2WODKOUB.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);
|
|
@@ -425,6 +429,10 @@ async function resolveCollectionAccess(config, collection, action, access, args)
|
|
|
425
429
|
allowed: await matchesAccessConstraint(config, collection, args.id, result)
|
|
426
430
|
};
|
|
427
431
|
}
|
|
432
|
+
function resolveFieldAccessId(context) {
|
|
433
|
+
const candidate = context.id ?? context.doc?.id ?? context.data?.id;
|
|
434
|
+
return typeof candidate === "string" && candidate.length > 0 ? candidate : void 0;
|
|
435
|
+
}
|
|
428
436
|
async function applyFieldReadAccess(context, doc) {
|
|
429
437
|
if (!doc || typeof doc !== "object") return doc;
|
|
430
438
|
const result = { ...doc };
|
|
@@ -439,7 +447,8 @@ async function applyFieldReadAccess(context, doc) {
|
|
|
439
447
|
user: context.user,
|
|
440
448
|
req: context.req,
|
|
441
449
|
doc: context.doc,
|
|
442
|
-
data: context.data
|
|
450
|
+
data: context.data,
|
|
451
|
+
id: resolveFieldAccessId(context)
|
|
443
452
|
});
|
|
444
453
|
if (!canRead) {
|
|
445
454
|
delete result[field.name];
|
|
@@ -451,9 +460,10 @@ async function applyFieldReadAccess(context, doc) {
|
|
|
451
460
|
continue;
|
|
452
461
|
}
|
|
453
462
|
if (field.type === "array" && field.fields && Array.isArray(value)) {
|
|
463
|
+
const childFields = field.fields;
|
|
454
464
|
result[field.name] = await Promise.all(
|
|
455
465
|
value.map(
|
|
456
|
-
(item) => typeof item === "object" && item !== null ? applyFieldReadAccess({ ...context, fields:
|
|
466
|
+
(item) => typeof item === "object" && item !== null ? applyFieldReadAccess({ ...context, fields: childFields }, item) : item
|
|
457
467
|
)
|
|
458
468
|
);
|
|
459
469
|
continue;
|
|
@@ -464,7 +474,7 @@ async function applyFieldReadAccess(context, doc) {
|
|
|
464
474
|
if (typeof item !== "object" || item === null) return item;
|
|
465
475
|
const typedItem = item;
|
|
466
476
|
const block = field.blocks?.find((candidate) => candidate.slug === typedItem.blockType);
|
|
467
|
-
if (!block) return item;
|
|
477
|
+
if (!block || !block.fields) return item;
|
|
468
478
|
return applyFieldReadAccess({ ...context, fields: block.fields }, typedItem);
|
|
469
479
|
})
|
|
470
480
|
);
|
|
@@ -481,13 +491,15 @@ async function applyFieldWriteAccess(context, data) {
|
|
|
481
491
|
continue;
|
|
482
492
|
}
|
|
483
493
|
if (!field.name || !(field.name in result)) continue;
|
|
484
|
-
const
|
|
494
|
+
const writeRule = context.operation === "create" ? field.access?.create ?? field.access?.update : field.access?.update;
|
|
495
|
+
const canWrite = await resolveBooleanAccess(context.config, writeRule, {
|
|
485
496
|
user: context.user,
|
|
486
497
|
req: context.req,
|
|
487
498
|
doc: context.doc,
|
|
488
|
-
data: context.data
|
|
499
|
+
data: context.data,
|
|
500
|
+
id: resolveFieldAccessId(context)
|
|
489
501
|
});
|
|
490
|
-
if (!
|
|
502
|
+
if (!canWrite) {
|
|
491
503
|
delete result[field.name];
|
|
492
504
|
continue;
|
|
493
505
|
}
|
|
@@ -498,9 +510,10 @@ async function applyFieldWriteAccess(context, data) {
|
|
|
498
510
|
continue;
|
|
499
511
|
}
|
|
500
512
|
if (field.type === "array" && field.fields && Array.isArray(value)) {
|
|
513
|
+
const childFields = field.fields;
|
|
501
514
|
result[field.name] = await Promise.all(
|
|
502
515
|
value.map(
|
|
503
|
-
(item) => typeof item === "object" && item !== null ? applyFieldWriteAccess({ ...context, fields:
|
|
516
|
+
(item) => typeof item === "object" && item !== null ? applyFieldWriteAccess({ ...context, fields: childFields }, item) : item
|
|
504
517
|
)
|
|
505
518
|
);
|
|
506
519
|
continue;
|
|
@@ -511,7 +524,7 @@ async function applyFieldWriteAccess(context, data) {
|
|
|
511
524
|
if (typeof item !== "object" || item === null) return item;
|
|
512
525
|
const typedItem = item;
|
|
513
526
|
const block = field.blocks?.find((candidate) => candidate.slug === typedItem.blockType);
|
|
514
|
-
if (!block) return item;
|
|
527
|
+
if (!block || !block.fields) return item;
|
|
515
528
|
return applyFieldWriteAccess({ ...context, fields: block.fields }, typedItem);
|
|
516
529
|
})
|
|
517
530
|
);
|
|
@@ -600,7 +613,7 @@ var CollectionController = class {
|
|
|
600
613
|
const readonlyDb = createReadonlyDb(db);
|
|
601
614
|
const limit = Number(c.req.query("limit")) || 10;
|
|
602
615
|
const page = Number(c.req.query("page")) || 1;
|
|
603
|
-
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;
|
|
604
617
|
const sort = c.req.query("sort") || void 0;
|
|
605
618
|
const user = c.get("user");
|
|
606
619
|
let where = void 0;
|
|
@@ -716,7 +729,7 @@ var CollectionController = class {
|
|
|
716
729
|
}
|
|
717
730
|
const readonlyDb = createReadonlyDb(db);
|
|
718
731
|
const id = c.req.param("id");
|
|
719
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) :
|
|
732
|
+
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
720
733
|
const user = c.get("user");
|
|
721
734
|
if (!id) return c.json({ message: "Missing ID" }, 400);
|
|
722
735
|
let doc = await db.findOne({ collection: this.collection.slug, id });
|
|
@@ -805,7 +818,8 @@ var CollectionController = class {
|
|
|
805
818
|
fields: this.collection.fields,
|
|
806
819
|
user,
|
|
807
820
|
req: this.toHookRequestContext(c),
|
|
808
|
-
data
|
|
821
|
+
data,
|
|
822
|
+
operation: "create"
|
|
809
823
|
}, data);
|
|
810
824
|
data = await executeFieldBeforeChange(this.collection.fields, data, null, user, readonlyDb);
|
|
811
825
|
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
@@ -900,7 +914,8 @@ var CollectionController = class {
|
|
|
900
914
|
fields: this.collection.fields,
|
|
901
915
|
user,
|
|
902
916
|
req: this.toHookRequestContext(c),
|
|
903
|
-
data
|
|
917
|
+
data,
|
|
918
|
+
operation: "create"
|
|
904
919
|
}, data);
|
|
905
920
|
data = await executeFieldBeforeChange(this.collection.fields, data, null, user, readonlyDb);
|
|
906
921
|
data = await runCollectionHooks(this.collection.hooks?.beforeChange, {
|
|
@@ -1322,7 +1337,7 @@ var GlobalController = class {
|
|
|
1322
1337
|
const db = config.db;
|
|
1323
1338
|
if (!db) return c.json({ message: "Database not configured" }, 500);
|
|
1324
1339
|
const readonlyDb = createReadonlyDb(db);
|
|
1325
|
-
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) :
|
|
1340
|
+
const depth = c.req.query("depth") !== void 0 ? Number(c.req.query("depth")) : 1;
|
|
1326
1341
|
const user = c.get("user");
|
|
1327
1342
|
let query = void 0;
|
|
1328
1343
|
const beforeReadResult = await runCollectionHooks(this.global.hooks?.beforeRead, {
|
|
@@ -2546,6 +2561,150 @@ var PreviewController = class {
|
|
|
2546
2561
|
}
|
|
2547
2562
|
};
|
|
2548
2563
|
|
|
2564
|
+
// src/controllers/audit.controller.ts
|
|
2565
|
+
var AuditController = class {
|
|
2566
|
+
parseQuery(c) {
|
|
2567
|
+
const limit = Math.min(Number(c.req.query("limit")) || 50, 100);
|
|
2568
|
+
const page = Math.max(Number(c.req.query("page")) || 1, 1);
|
|
2569
|
+
const sort = c.req.query("sort") || "-timestamp";
|
|
2570
|
+
const whereRaw = c.req.query("where");
|
|
2571
|
+
if (!whereRaw) {
|
|
2572
|
+
return { limit, page, sort };
|
|
2573
|
+
}
|
|
2574
|
+
try {
|
|
2575
|
+
return {
|
|
2576
|
+
limit,
|
|
2577
|
+
page,
|
|
2578
|
+
sort,
|
|
2579
|
+
where: JSON.parse(decodeURIComponent(whereRaw))
|
|
2580
|
+
};
|
|
2581
|
+
} catch {
|
|
2582
|
+
return { limit, page, sort };
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
emptyResult(limit, page) {
|
|
2586
|
+
return {
|
|
2587
|
+
docs: [],
|
|
2588
|
+
total: 0,
|
|
2589
|
+
limit,
|
|
2590
|
+
page,
|
|
2591
|
+
totalPages: 0,
|
|
2592
|
+
hasNextPage: false,
|
|
2593
|
+
hasPrevPage: page > 1
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
async collectAccessibleDocumentIds(db, collection, constraint) {
|
|
2597
|
+
const ids = [];
|
|
2598
|
+
const limit = 100;
|
|
2599
|
+
let page = 1;
|
|
2600
|
+
while (true) {
|
|
2601
|
+
const result = await db.find({
|
|
2602
|
+
collection: collection.slug,
|
|
2603
|
+
where: constraint,
|
|
2604
|
+
limit,
|
|
2605
|
+
page
|
|
2606
|
+
});
|
|
2607
|
+
for (const doc of result.docs) {
|
|
2608
|
+
if (typeof doc?.id === "string" && doc.id.length > 0) {
|
|
2609
|
+
ids.push(doc.id);
|
|
2610
|
+
}
|
|
2611
|
+
}
|
|
2612
|
+
if (!result.hasNextPage || page >= result.totalPages) {
|
|
2613
|
+
break;
|
|
2614
|
+
}
|
|
2615
|
+
page += 1;
|
|
2616
|
+
}
|
|
2617
|
+
return ids;
|
|
2618
|
+
}
|
|
2619
|
+
async buildCollectionAuditScope(c, collection) {
|
|
2620
|
+
const config = c.get("config");
|
|
2621
|
+
const auditAccess = collection.access?.readAudit ?? collection.access?.read;
|
|
2622
|
+
const access = await resolveCollectionAccess(
|
|
2623
|
+
config,
|
|
2624
|
+
collection.slug,
|
|
2625
|
+
"read",
|
|
2626
|
+
auditAccess,
|
|
2627
|
+
{
|
|
2628
|
+
user: c.get("user"),
|
|
2629
|
+
req: toHookRequestContext(c.req)
|
|
2630
|
+
}
|
|
2631
|
+
);
|
|
2632
|
+
if (!access.allowed) {
|
|
2633
|
+
return { denied: true, where: {} };
|
|
2634
|
+
}
|
|
2635
|
+
let where = { collection: { equals: collection.slug } };
|
|
2636
|
+
if (access.constraint) {
|
|
2637
|
+
const ids = await this.collectAccessibleDocumentIds(config.db, collection, access.constraint);
|
|
2638
|
+
where = mergeWhereConstraint(where, { documentId: { in: ids } });
|
|
2639
|
+
}
|
|
2640
|
+
return { denied: false, where };
|
|
2641
|
+
}
|
|
2642
|
+
async getVisibleCollections(c) {
|
|
2643
|
+
const config = c.get("config");
|
|
2644
|
+
const collections = /* @__PURE__ */ new Map();
|
|
2645
|
+
for (const collection of config.collections) {
|
|
2646
|
+
collections.set(collection.slug, collection);
|
|
2647
|
+
}
|
|
2648
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
2649
|
+
if (config.onSchemaFetch && siteId) {
|
|
2650
|
+
const dynamic = await config.onSchemaFetch(siteId);
|
|
2651
|
+
for (const collection of dynamic.collections || []) {
|
|
2652
|
+
if (!collections.has(collection.slug)) {
|
|
2653
|
+
collections.set(collection.slug, collection);
|
|
2654
|
+
}
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
return Array.from(collections.values());
|
|
2658
|
+
}
|
|
2659
|
+
async findForCollection(c, collection) {
|
|
2660
|
+
const config = c.get("config");
|
|
2661
|
+
if (!config.db) return c.json({ message: "Database not configured" }, 500);
|
|
2662
|
+
if (!collection.audit) return c.json({ message: "Audit is not enabled for this collection" }, 404);
|
|
2663
|
+
const query = this.parseQuery(c);
|
|
2664
|
+
const scope = await this.buildCollectionAuditScope(c, collection);
|
|
2665
|
+
if (scope.denied) {
|
|
2666
|
+
return c.json({ error: true, message: `Access denied: read on ${collection.slug}` }, 403);
|
|
2667
|
+
}
|
|
2668
|
+
const where = query.where ? mergeWhereConstraint(scope.where, query.where) : scope.where;
|
|
2669
|
+
const result = await config.db.find({
|
|
2670
|
+
collection: "__audit",
|
|
2671
|
+
where,
|
|
2672
|
+
limit: query.limit,
|
|
2673
|
+
page: query.page,
|
|
2674
|
+
sort: query.sort
|
|
2675
|
+
});
|
|
2676
|
+
return c.json(result);
|
|
2677
|
+
}
|
|
2678
|
+
async findAll(c) {
|
|
2679
|
+
const config = c.get("config");
|
|
2680
|
+
if (!config.db) return c.json({ message: "Database not configured" }, 500);
|
|
2681
|
+
const query = this.parseQuery(c);
|
|
2682
|
+
const collections = (await this.getVisibleCollections(c)).filter((collection) => collection.audit);
|
|
2683
|
+
const scopes = [];
|
|
2684
|
+
for (const collection of collections) {
|
|
2685
|
+
const scope = await this.buildCollectionAuditScope(c, collection);
|
|
2686
|
+
if (!scope.denied) {
|
|
2687
|
+
scopes.push(scope.where);
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
if (scopes.length === 0) {
|
|
2691
|
+
return c.json(this.emptyResult(query.limit, query.page));
|
|
2692
|
+
}
|
|
2693
|
+
let where = scopes.length === 1 ? scopes[0] : { OR: scopes };
|
|
2694
|
+
if (query.where) {
|
|
2695
|
+
where = mergeWhereConstraint(where, query.where);
|
|
2696
|
+
}
|
|
2697
|
+
const result = await config.db.find({
|
|
2698
|
+
collection: "__audit",
|
|
2699
|
+
where,
|
|
2700
|
+
limit: query.limit,
|
|
2701
|
+
page: query.page,
|
|
2702
|
+
sort: query.sort
|
|
2703
|
+
});
|
|
2704
|
+
return c.json(result);
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2707
|
+
|
|
2549
2708
|
// src/middleware/auth.ts
|
|
2550
2709
|
function getBearerToken(c) {
|
|
2551
2710
|
const authHeader = c.req.header("Authorization");
|
|
@@ -2722,6 +2881,7 @@ function serializeFieldForApi(f) {
|
|
|
2722
2881
|
return serialized;
|
|
2723
2882
|
}
|
|
2724
2883
|
function registerRoutes(app, config) {
|
|
2884
|
+
const optionsCache = /* @__PURE__ */ new Map();
|
|
2725
2885
|
app.get("/api/schemas", optionalAuth(config), async (c) => {
|
|
2726
2886
|
const siteId = c.req.header("X-Site-Id");
|
|
2727
2887
|
let collections = [...config.collections];
|
|
@@ -2742,6 +2902,10 @@ function registerRoutes(app, config) {
|
|
|
2742
2902
|
const serializeAccess = async (access) => {
|
|
2743
2903
|
if (typeof access === "string") return access;
|
|
2744
2904
|
if (typeof access === "boolean") return access;
|
|
2905
|
+
if (access && typeof access === "object" && typeof access.policy === "string") {
|
|
2906
|
+
const policy = config.accessPolicies?.[access.policy];
|
|
2907
|
+
if (typeof policy === "string" || typeof policy === "boolean") return policy;
|
|
2908
|
+
}
|
|
2745
2909
|
return resolveBooleanAccess(config, access, accessArgs);
|
|
2746
2910
|
};
|
|
2747
2911
|
const filteredCollections = await Promise.all(collections.filter((col) => !siteId || col.shared || !col.siteId || col.siteId === siteId).map(async (col) => ({
|
|
@@ -2770,6 +2934,7 @@ function registerRoutes(app, config) {
|
|
|
2770
2934
|
admin: f.admin,
|
|
2771
2935
|
access: {
|
|
2772
2936
|
read: await serializeAccess(f.access?.read),
|
|
2937
|
+
create: await serializeAccess(f.access?.create),
|
|
2773
2938
|
update: await serializeAccess(f.access?.update)
|
|
2774
2939
|
}
|
|
2775
2940
|
}))),
|
|
@@ -2877,10 +3042,12 @@ function registerRoutes(app, config) {
|
|
|
2877
3042
|
return c.json({ error: true, message: `Field ${fieldName} not found in ${colSlug}` }, 404);
|
|
2878
3043
|
}
|
|
2879
3044
|
let resolver;
|
|
3045
|
+
let cacheTTL;
|
|
2880
3046
|
if (typeof field.options === "function") {
|
|
2881
3047
|
resolver = field.options;
|
|
2882
3048
|
} else if (field.options && typeof field.options === "object" && "resolve" in field.options) {
|
|
2883
3049
|
resolver = field.options.resolve;
|
|
3050
|
+
cacheTTL = field.options.cacheTTL;
|
|
2884
3051
|
}
|
|
2885
3052
|
if (!resolver) {
|
|
2886
3053
|
return c.json({ error: true, message: `Field ${fieldName} in ${colSlug} is not dynamic` }, 400);
|
|
@@ -2893,11 +3060,32 @@ function registerRoutes(app, config) {
|
|
|
2893
3060
|
headers: c.req.header(),
|
|
2894
3061
|
raw: c.req.raw
|
|
2895
3062
|
};
|
|
3063
|
+
const shouldCache = typeof cacheTTL === "number" && cacheTTL > 0;
|
|
3064
|
+
let cacheKey = "";
|
|
3065
|
+
if (shouldCache) {
|
|
3066
|
+
cacheKey = JSON.stringify([
|
|
3067
|
+
siteId ?? "",
|
|
3068
|
+
colSlug,
|
|
3069
|
+
fieldName,
|
|
3070
|
+
user?.id ?? null,
|
|
3071
|
+
queryParams
|
|
3072
|
+
]);
|
|
3073
|
+
const hit = optionsCache.get(cacheKey);
|
|
3074
|
+
if (hit && hit.expires > Date.now()) {
|
|
3075
|
+
return c.json(hit.value);
|
|
3076
|
+
}
|
|
3077
|
+
}
|
|
2896
3078
|
const result = await resolver({
|
|
2897
3079
|
db,
|
|
2898
3080
|
user,
|
|
2899
3081
|
req: reqContext
|
|
2900
3082
|
});
|
|
3083
|
+
if (shouldCache) {
|
|
3084
|
+
optionsCache.set(cacheKey, {
|
|
3085
|
+
expires: Date.now() + cacheTTL * 1e3,
|
|
3086
|
+
value: result
|
|
3087
|
+
});
|
|
3088
|
+
}
|
|
2901
3089
|
return c.json(result);
|
|
2902
3090
|
} catch (err) {
|
|
2903
3091
|
console.error(`[dyrected/core] Failed to resolve dynamic options for field ${fieldName}:`, err);
|
|
@@ -3042,6 +3230,7 @@ function registerRoutes(app, config) {
|
|
|
3042
3230
|
app.post(`${path}/invite`, requireAuth(config), (c) => authController.invite(c));
|
|
3043
3231
|
app.post(`${path}/accept-invite`, (c) => authController.acceptInvite(c));
|
|
3044
3232
|
}
|
|
3233
|
+
const auditController = new AuditController();
|
|
3045
3234
|
for (const collection of config.collections) {
|
|
3046
3235
|
const path = `/api/collections/${collection.slug}`;
|
|
3047
3236
|
const controller = new CollectionController(collection);
|
|
@@ -3049,6 +3238,9 @@ function registerRoutes(app, config) {
|
|
|
3049
3238
|
app.post(path, (c) => controller.create(c));
|
|
3050
3239
|
app.post(`${path}/media`, (c) => controller.create(c));
|
|
3051
3240
|
app.delete(`${path}/delete-many`, (c) => controller.deleteMany(c));
|
|
3241
|
+
if (collection.audit) {
|
|
3242
|
+
app.get(`${path}/__audit`, (c) => auditController.findForCollection(c, collection));
|
|
3243
|
+
}
|
|
3052
3244
|
app.get(`${path}/:id`, (c) => controller.findOne(c));
|
|
3053
3245
|
app.patch(`${path}/:id`, (c) => controller.update(c));
|
|
3054
3246
|
app.delete(`${path}/:id`, (c) => controller.delete(c));
|
|
@@ -3071,6 +3263,24 @@ function registerRoutes(app, config) {
|
|
|
3071
3263
|
const previewController = new PreviewController();
|
|
3072
3264
|
app.post("/api/preview-token", requireAuth(config), (c) => previewController.createToken(c));
|
|
3073
3265
|
app.get("/api/preview-data", (c) => previewController.getData(c));
|
|
3266
|
+
app.get("/api/audit", (c) => auditController.findAll(c));
|
|
3267
|
+
app.get("/api/collections/:slug/__audit", async (c) => {
|
|
3268
|
+
const slug = c.req.param("slug");
|
|
3269
|
+
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|
|
3270
|
+
const config2 = c.get("config");
|
|
3271
|
+
if (config2.collections.some((col) => col.slug === slug)) {
|
|
3272
|
+
return c.json({ message: "Not Found" }, 404);
|
|
3273
|
+
}
|
|
3274
|
+
if (!config2.onSchemaFetch || !siteId) {
|
|
3275
|
+
return c.json({ message: `Collection "${slug}" not found` }, 404);
|
|
3276
|
+
}
|
|
3277
|
+
const dynamic = await config2.onSchemaFetch(siteId);
|
|
3278
|
+
const collection = dynamic.collections?.find((col) => col.slug === slug);
|
|
3279
|
+
if (!collection?.audit) {
|
|
3280
|
+
return c.json({ message: `Collection "${slug}" not found or has no audit log` }, 404);
|
|
3281
|
+
}
|
|
3282
|
+
return auditController.findForCollection(c, collection);
|
|
3283
|
+
});
|
|
3074
3284
|
app.post("/api/collections/:slug/:id/transitions/:transition", requireAuth(config), async (c) => {
|
|
3075
3285
|
const slug = c.req.param("slug");
|
|
3076
3286
|
const siteId = c.req.header("X-Site-Id") || c.get("siteId");
|