@momentumcms/server-express 0.5.3 → 0.5.5
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/CHANGELOG.md +56 -0
- package/index.cjs +323 -156
- package/index.js +201 -34
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -2202,8 +2202,27 @@ var MediaCollection = defineCollection({
|
|
|
2202
2202
|
}
|
|
2203
2203
|
});
|
|
2204
2204
|
|
|
2205
|
+
// libs/core/src/lib/migrations.ts
|
|
2206
|
+
function resolveMigrationMode(mode) {
|
|
2207
|
+
if (mode === "push" || mode === "migrate")
|
|
2208
|
+
return mode;
|
|
2209
|
+
const env = globalThis["process"]?.env?.["NODE_ENV"];
|
|
2210
|
+
if (env === "production")
|
|
2211
|
+
return "migrate";
|
|
2212
|
+
return "push";
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2205
2215
|
// libs/core/src/lib/config.ts
|
|
2206
2216
|
var MIN_PASSWORD_LENGTH = 8;
|
|
2217
|
+
function shouldSyncSchema(config) {
|
|
2218
|
+
const explicit = config.db.syncSchema ?? "auto";
|
|
2219
|
+
if (typeof explicit === "boolean")
|
|
2220
|
+
return explicit;
|
|
2221
|
+
if (!config.migrations)
|
|
2222
|
+
return true;
|
|
2223
|
+
const mode = resolveMigrationMode(config.migrations.mode);
|
|
2224
|
+
return mode !== "migrate";
|
|
2225
|
+
}
|
|
2207
2226
|
|
|
2208
2227
|
// libs/core/src/lib/seeding/seeding.types.ts
|
|
2209
2228
|
var SEED_TRACKING_COLLECTION_SLUG = "_momentum_seeds";
|
|
@@ -2291,6 +2310,14 @@ function createSeedHelpers() {
|
|
|
2291
2310
|
};
|
|
2292
2311
|
}
|
|
2293
2312
|
|
|
2313
|
+
// libs/core/src/lib/versions/version.types.ts
|
|
2314
|
+
function hasVersionDrafts(collection) {
|
|
2315
|
+
const v = collection.versions;
|
|
2316
|
+
if (!v || typeof v === "boolean")
|
|
2317
|
+
return false;
|
|
2318
|
+
return !!v.drafts;
|
|
2319
|
+
}
|
|
2320
|
+
|
|
2294
2321
|
// libs/server-core/src/lib/field-access.ts
|
|
2295
2322
|
function hasFieldAccessControl(fields) {
|
|
2296
2323
|
for (const field of fields) {
|
|
@@ -2645,6 +2672,12 @@ var DocumentNotFoundError = class extends Error {
|
|
|
2645
2672
|
this.name = "DocumentNotFoundError";
|
|
2646
2673
|
}
|
|
2647
2674
|
};
|
|
2675
|
+
var DraftNotVisibleError = class extends Error {
|
|
2676
|
+
constructor(collection, id) {
|
|
2677
|
+
super(`Draft "${id}" in collection "${collection}" is not visible to the current user`);
|
|
2678
|
+
this.name = "DraftNotVisibleError";
|
|
2679
|
+
}
|
|
2680
|
+
};
|
|
2648
2681
|
var AccessDeniedError = class extends Error {
|
|
2649
2682
|
constructor(operation, collection) {
|
|
2650
2683
|
super(`Access denied for ${operation} on collection "${collection}"`);
|
|
@@ -2877,13 +2910,18 @@ var VersionOperationsImpl = class {
|
|
|
2877
2910
|
}
|
|
2878
2911
|
return "draft";
|
|
2879
2912
|
}
|
|
2880
|
-
async compare(versionId1, versionId2) {
|
|
2913
|
+
async compare(versionId1, versionId2, parentId) {
|
|
2881
2914
|
await this.checkAccess("readVersions");
|
|
2882
2915
|
const version1 = await this.findVersionById(versionId1);
|
|
2883
2916
|
const version2 = await this.findVersionById(versionId2);
|
|
2884
2917
|
if (!version1 || !version2) {
|
|
2885
2918
|
throw new Error("One or both versions not found");
|
|
2886
2919
|
}
|
|
2920
|
+
if (parentId) {
|
|
2921
|
+
if (version1.parent !== parentId || version2.parent !== parentId) {
|
|
2922
|
+
throw new Error("Version does not belong to the specified document");
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2887
2925
|
const data1 = version1.version;
|
|
2888
2926
|
const data2 = version2.version;
|
|
2889
2927
|
if (!isRecord(data1) || !isRecord(data2)) {
|
|
@@ -2938,6 +2976,9 @@ var VersionOperationsImpl = class {
|
|
|
2938
2976
|
// Private Helpers
|
|
2939
2977
|
// ============================================
|
|
2940
2978
|
async checkAccess(operation) {
|
|
2979
|
+
if (this.context.overrideAccess) {
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2941
2982
|
const accessFn = this.collectionConfig.access?.[operation];
|
|
2942
2983
|
if (!accessFn) {
|
|
2943
2984
|
return;
|
|
@@ -3054,13 +3095,31 @@ function stripTransientKeys(data) {
|
|
|
3054
3095
|
}
|
|
3055
3096
|
return result;
|
|
3056
3097
|
}
|
|
3098
|
+
var COMPARISON_OPS = ["gt", "gte", "lt", "lte"];
|
|
3057
3099
|
function flattenWhereClause(where) {
|
|
3058
3100
|
if (!where)
|
|
3059
3101
|
return {};
|
|
3060
3102
|
const result = {};
|
|
3061
3103
|
for (const [field, condition] of Object.entries(where)) {
|
|
3062
|
-
if (typeof condition
|
|
3063
|
-
result[field] = condition
|
|
3104
|
+
if (typeof condition !== "object" || condition === null) {
|
|
3105
|
+
result[field] = condition;
|
|
3106
|
+
continue;
|
|
3107
|
+
}
|
|
3108
|
+
const condObj = condition;
|
|
3109
|
+
if ("equals" in condObj) {
|
|
3110
|
+
result[field] = condObj["equals"];
|
|
3111
|
+
continue;
|
|
3112
|
+
}
|
|
3113
|
+
const ops = {};
|
|
3114
|
+
let hasComparisonOp = false;
|
|
3115
|
+
for (const op of COMPARISON_OPS) {
|
|
3116
|
+
if (op in condObj) {
|
|
3117
|
+
ops[`$${op}`] = condObj[op];
|
|
3118
|
+
hasComparisonOp = true;
|
|
3119
|
+
}
|
|
3120
|
+
}
|
|
3121
|
+
if (hasComparisonOp) {
|
|
3122
|
+
result[field] = ops;
|
|
3064
3123
|
} else {
|
|
3065
3124
|
result[field] = condition;
|
|
3066
3125
|
}
|
|
@@ -3094,6 +3153,12 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3094
3153
|
Object.assign(whereParams, constraints);
|
|
3095
3154
|
}
|
|
3096
3155
|
}
|
|
3156
|
+
if (hasVersionDrafts(this.collectionConfig) && !this.context.overrideAccess) {
|
|
3157
|
+
const canSeeDrafts = await this.canReadDrafts();
|
|
3158
|
+
if (!canSeeDrafts) {
|
|
3159
|
+
whereParams["_status"] = "published";
|
|
3160
|
+
}
|
|
3161
|
+
}
|
|
3097
3162
|
const query = {
|
|
3098
3163
|
...queryOptions,
|
|
3099
3164
|
...whereParams,
|
|
@@ -3149,15 +3214,24 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3149
3214
|
await this.runBeforeReadHooks();
|
|
3150
3215
|
const doc = await this.adapter.findById(this.slug, id);
|
|
3151
3216
|
if (!doc) {
|
|
3152
|
-
|
|
3217
|
+
throw new DocumentNotFoundError(this.slug, id);
|
|
3153
3218
|
}
|
|
3154
3219
|
const softDeleteField = getSoftDeleteField(this.collectionConfig);
|
|
3155
3220
|
if (softDeleteField && !options?.withDeleted && // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- T is compatible with Record<string, unknown>
|
|
3156
3221
|
doc[softDeleteField]) {
|
|
3157
|
-
|
|
3222
|
+
throw new DocumentNotFoundError(this.slug, id);
|
|
3223
|
+
}
|
|
3224
|
+
if (hasVersionDrafts(this.collectionConfig) && !this.context.overrideAccess) {
|
|
3225
|
+
const canSeeDrafts = await this.canReadDrafts();
|
|
3226
|
+
if (!canSeeDrafts) {
|
|
3227
|
+
const record = doc;
|
|
3228
|
+
if (record["_status"] !== "published") {
|
|
3229
|
+
throw new DraftNotVisibleError(this.slug, id);
|
|
3230
|
+
}
|
|
3231
|
+
}
|
|
3158
3232
|
}
|
|
3159
3233
|
if (!this.matchesDefaultWhereConstraints(doc)) {
|
|
3160
|
-
|
|
3234
|
+
throw new DocumentNotFoundError(this.slug, id);
|
|
3161
3235
|
}
|
|
3162
3236
|
const [processed] = await this.processAfterReadHooks([doc]);
|
|
3163
3237
|
const MAX_RELATIONSHIP_DEPTH = 10;
|
|
@@ -3241,6 +3315,15 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3241
3315
|
if (!this.matchesDefaultWhereConstraints(originalDoc)) {
|
|
3242
3316
|
throw new DocumentNotFoundError(this.slug, id);
|
|
3243
3317
|
}
|
|
3318
|
+
if (hasVersionDrafts(this.collectionConfig) && this.adapter.createVersion) {
|
|
3319
|
+
const status = originalDoc["_status"] ?? "draft";
|
|
3320
|
+
await this.adapter.createVersion(this.slug, id, originalDoc, { status });
|
|
3321
|
+
const versionsConfig = this.collectionConfig.versions;
|
|
3322
|
+
const maxPerDoc = typeof versionsConfig === "object" && versionsConfig !== null ? versionsConfig.maxPerDoc : void 0;
|
|
3323
|
+
if (maxPerDoc && this.adapter.deleteVersions) {
|
|
3324
|
+
await this.adapter.deleteVersions(this.slug, id, maxPerDoc);
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3244
3327
|
let processedData = data;
|
|
3245
3328
|
const softDeleteField = getSoftDeleteField(this.collectionConfig);
|
|
3246
3329
|
if (softDeleteField && softDeleteField in processedData) {
|
|
@@ -3572,6 +3655,30 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3572
3655
|
throw new AccessDeniedError(operation, this.slug);
|
|
3573
3656
|
}
|
|
3574
3657
|
}
|
|
3658
|
+
/**
|
|
3659
|
+
* Check if the current user can see draft documents (non-throwing).
|
|
3660
|
+
* Uses `access.readDrafts` if configured, otherwise falls back to `access.update`.
|
|
3661
|
+
*/
|
|
3662
|
+
async canReadDrafts() {
|
|
3663
|
+
if (!this.context.user)
|
|
3664
|
+
return false;
|
|
3665
|
+
const readDraftsFn = this.collectionConfig.access?.readDrafts;
|
|
3666
|
+
if (readDraftsFn) {
|
|
3667
|
+
try {
|
|
3668
|
+
return !!await Promise.resolve(readDraftsFn({ req: this.buildRequestContext() }));
|
|
3669
|
+
} catch {
|
|
3670
|
+
return false;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
const updateFn = this.collectionConfig.access?.update;
|
|
3674
|
+
if (!updateFn)
|
|
3675
|
+
return true;
|
|
3676
|
+
try {
|
|
3677
|
+
return !!await Promise.resolve(updateFn({ req: this.buildRequestContext() }));
|
|
3678
|
+
} catch {
|
|
3679
|
+
return false;
|
|
3680
|
+
}
|
|
3681
|
+
}
|
|
3575
3682
|
buildRequestContext() {
|
|
3576
3683
|
return {
|
|
3577
3684
|
user: this.context.user
|
|
@@ -3995,9 +4102,6 @@ function createMomentumHandlers(config) {
|
|
|
3995
4102
|
const depth = typeof request.query?.["depth"] === "number" ? request.query["depth"] : void 0;
|
|
3996
4103
|
const withDeleted = request.query?.["withDeleted"] === true;
|
|
3997
4104
|
const doc = await api.collection(request.collectionSlug).findById(request.id, { depth, withDeleted });
|
|
3998
|
-
if (!doc) {
|
|
3999
|
-
return { error: "Document not found", status: 404 };
|
|
4000
|
-
}
|
|
4001
4105
|
return { doc };
|
|
4002
4106
|
} catch (error) {
|
|
4003
4107
|
return handleError(error);
|
|
@@ -4114,6 +4218,9 @@ function handleError(error) {
|
|
|
4114
4218
|
if (error instanceof DocumentNotFoundError) {
|
|
4115
4219
|
return { error: error.message, status: 404 };
|
|
4116
4220
|
}
|
|
4221
|
+
if (error instanceof DraftNotVisibleError) {
|
|
4222
|
+
return { doc: null };
|
|
4223
|
+
}
|
|
4117
4224
|
if (error instanceof AccessDeniedError) {
|
|
4118
4225
|
return { error: error.message, status: 403 };
|
|
4119
4226
|
}
|
|
@@ -4566,7 +4673,10 @@ async function sendWebhook(webhook, payload, attempt = 0) {
|
|
|
4566
4673
|
webhookLogger.warn(`Blocked request to disallowed URL: ${webhook.url}`);
|
|
4567
4674
|
return;
|
|
4568
4675
|
}
|
|
4569
|
-
const body = JSON.stringify(
|
|
4676
|
+
const body = JSON.stringify(
|
|
4677
|
+
payload,
|
|
4678
|
+
(_key, value) => typeof value === "bigint" ? Number(value) : value
|
|
4679
|
+
);
|
|
4570
4680
|
const maxRetries = webhook.retries ?? 0;
|
|
4571
4681
|
const headers = {
|
|
4572
4682
|
"Content-Type": "application/json",
|
|
@@ -4708,7 +4818,7 @@ function startPublishScheduler(adapter, collections, options) {
|
|
|
4708
4818
|
const scheduled = await adapter.findScheduledDocuments(collection.slug, now);
|
|
4709
4819
|
for (const doc of scheduled) {
|
|
4710
4820
|
try {
|
|
4711
|
-
const api = getMomentumAPI();
|
|
4821
|
+
const api = getMomentumAPI().setContext({ overrideAccess: true });
|
|
4712
4822
|
const versionOps = api.collection(collection.slug).versions();
|
|
4713
4823
|
if (!versionOps)
|
|
4714
4824
|
continue;
|
|
@@ -4828,8 +4938,9 @@ function buildGraphQLSchema(collections) {
|
|
|
4828
4938
|
}
|
|
4829
4939
|
function getOrCreateEnum(field, parentName) {
|
|
4830
4940
|
const key = `${parentName}_${field.name}`;
|
|
4831
|
-
|
|
4832
|
-
|
|
4941
|
+
const cached = enumCache.get(key);
|
|
4942
|
+
if (cached) {
|
|
4943
|
+
return cached;
|
|
4833
4944
|
}
|
|
4834
4945
|
const values = {};
|
|
4835
4946
|
for (const opt of field.options) {
|
|
@@ -4940,8 +5051,9 @@ function buildGraphQLSchema(collections) {
|
|
|
4940
5051
|
}
|
|
4941
5052
|
}
|
|
4942
5053
|
function getOrCreateObjectType(fields, typeName) {
|
|
4943
|
-
|
|
4944
|
-
|
|
5054
|
+
const cachedType = typeCache.get(typeName);
|
|
5055
|
+
if (cachedType) {
|
|
5056
|
+
return cachedType;
|
|
4945
5057
|
}
|
|
4946
5058
|
const objType = new GraphQLObjectType({
|
|
4947
5059
|
name: typeName,
|
|
@@ -4962,8 +5074,9 @@ function buildGraphQLSchema(collections) {
|
|
|
4962
5074
|
}
|
|
4963
5075
|
function getOrCreateInputType(fields, typeName) {
|
|
4964
5076
|
const inputName = `${typeName}Input`;
|
|
4965
|
-
|
|
4966
|
-
|
|
5077
|
+
const cachedInput = inputCache.get(inputName);
|
|
5078
|
+
if (cachedInput) {
|
|
5079
|
+
return cachedInput;
|
|
4967
5080
|
}
|
|
4968
5081
|
const inputType = new GraphQLInputObjectType({
|
|
4969
5082
|
name: inputName,
|
|
@@ -5084,7 +5197,14 @@ function buildGraphQLSchema(collections) {
|
|
|
5084
5197
|
const api = getMomentumAPI();
|
|
5085
5198
|
const ctx = buildAPIContext(context);
|
|
5086
5199
|
const contextApi = Object.keys(ctx).length > 0 ? api.setContext(ctx) : api;
|
|
5087
|
-
|
|
5200
|
+
try {
|
|
5201
|
+
return await contextApi.collection(col.slug).findById(args.id);
|
|
5202
|
+
} catch (err) {
|
|
5203
|
+
if (err instanceof Error && err.name === "DocumentNotFoundError") {
|
|
5204
|
+
return null;
|
|
5205
|
+
}
|
|
5206
|
+
throw err;
|
|
5207
|
+
}
|
|
5088
5208
|
}
|
|
5089
5209
|
};
|
|
5090
5210
|
queryFields[plural.charAt(0).toLowerCase() + plural.slice(1)] = {
|
|
@@ -6375,6 +6495,22 @@ var RateLimiter = class _RateLimiter {
|
|
|
6375
6495
|
}
|
|
6376
6496
|
};
|
|
6377
6497
|
|
|
6498
|
+
// libs/server-core/src/lib/schema-sync.ts
|
|
6499
|
+
async function syncDatabaseSchema(config, log) {
|
|
6500
|
+
if (shouldSyncSchema(config)) {
|
|
6501
|
+
if (config.db.adapter.initialize) {
|
|
6502
|
+
log.info("Initializing database schema...");
|
|
6503
|
+
await config.db.adapter.initialize(config.collections);
|
|
6504
|
+
}
|
|
6505
|
+
if (config.db.adapter.initializeGlobals && config.globals && config.globals.length > 0) {
|
|
6506
|
+
log.info(`Initializing globals table for ${config.globals.length} global(s)...`);
|
|
6507
|
+
await config.db.adapter.initializeGlobals(config.globals);
|
|
6508
|
+
}
|
|
6509
|
+
} else {
|
|
6510
|
+
log.info("Skipping automatic schema sync (migration mode). Run migrations separately.");
|
|
6511
|
+
}
|
|
6512
|
+
}
|
|
6513
|
+
|
|
6378
6514
|
// libs/server-core/src/lib/shared-server-utils.ts
|
|
6379
6515
|
function sanitizeErrorMessage(error, fallback) {
|
|
6380
6516
|
if (!(error instanceof Error))
|
|
@@ -6792,6 +6928,10 @@ function momentumApiMiddleware(config) {
|
|
|
6792
6928
|
res.json(result);
|
|
6793
6929
|
} catch (error) {
|
|
6794
6930
|
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
6931
|
+
if (error instanceof Error && error.name === "AccessDeniedError") {
|
|
6932
|
+
res.status(403).json({ error: "Access denied" });
|
|
6933
|
+
return;
|
|
6934
|
+
}
|
|
6795
6935
|
res.status(500).json({ error: "Failed to fetch versions", message });
|
|
6796
6936
|
}
|
|
6797
6937
|
});
|
|
@@ -6820,6 +6960,10 @@ function momentumApiMiddleware(config) {
|
|
|
6820
6960
|
res.json(version);
|
|
6821
6961
|
} catch (error) {
|
|
6822
6962
|
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
6963
|
+
if (error instanceof Error && error.name === "AccessDeniedError") {
|
|
6964
|
+
res.status(403).json({ error: "Access denied" });
|
|
6965
|
+
return;
|
|
6966
|
+
}
|
|
6823
6967
|
res.status(500).json({ error: "Failed to fetch version", message });
|
|
6824
6968
|
}
|
|
6825
6969
|
});
|
|
@@ -6858,6 +7002,10 @@ function momentumApiMiddleware(config) {
|
|
|
6858
7002
|
res.status(400).json({ error: "Version parent mismatch", message });
|
|
6859
7003
|
return;
|
|
6860
7004
|
}
|
|
7005
|
+
if (error instanceof Error && error.name === "AccessDeniedError") {
|
|
7006
|
+
res.status(403).json({ error: "Access denied" });
|
|
7007
|
+
return;
|
|
7008
|
+
}
|
|
6861
7009
|
res.status(500).json({ error: "Failed to restore version", message });
|
|
6862
7010
|
}
|
|
6863
7011
|
});
|
|
@@ -6879,6 +7027,10 @@ function momentumApiMiddleware(config) {
|
|
|
6879
7027
|
res.json({ doc: published, message: "Document published successfully" });
|
|
6880
7028
|
} catch (error) {
|
|
6881
7029
|
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
7030
|
+
if (error instanceof Error && error.name === "AccessDeniedError") {
|
|
7031
|
+
res.status(403).json({ error: "Access denied" });
|
|
7032
|
+
return;
|
|
7033
|
+
}
|
|
6882
7034
|
res.status(500).json({ error: "Failed to publish document", message });
|
|
6883
7035
|
}
|
|
6884
7036
|
});
|
|
@@ -6908,6 +7060,10 @@ function momentumApiMiddleware(config) {
|
|
|
6908
7060
|
res.json(result);
|
|
6909
7061
|
} catch (error) {
|
|
6910
7062
|
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
7063
|
+
if (error instanceof Error && error.name === "AccessDeniedError") {
|
|
7064
|
+
res.status(403).json({ error: "Access denied" });
|
|
7065
|
+
return;
|
|
7066
|
+
}
|
|
6911
7067
|
res.status(500).json({ error: "Failed to schedule publish", message });
|
|
6912
7068
|
}
|
|
6913
7069
|
});
|
|
@@ -6929,6 +7085,10 @@ function momentumApiMiddleware(config) {
|
|
|
6929
7085
|
res.json({ message: "Scheduled publish cancelled" });
|
|
6930
7086
|
} catch (error) {
|
|
6931
7087
|
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
7088
|
+
if (error instanceof Error && error.name === "AccessDeniedError") {
|
|
7089
|
+
res.status(403).json({ error: "Access denied" });
|
|
7090
|
+
return;
|
|
7091
|
+
}
|
|
6932
7092
|
res.status(500).json({ error: "Failed to cancel scheduled publish", message });
|
|
6933
7093
|
}
|
|
6934
7094
|
});
|
|
@@ -6950,6 +7110,10 @@ function momentumApiMiddleware(config) {
|
|
|
6950
7110
|
res.json({ doc: unpublished, message: "Document unpublished successfully" });
|
|
6951
7111
|
} catch (error) {
|
|
6952
7112
|
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
7113
|
+
if (error instanceof Error && error.name === "AccessDeniedError") {
|
|
7114
|
+
res.status(403).json({ error: "Access denied" });
|
|
7115
|
+
return;
|
|
7116
|
+
}
|
|
6953
7117
|
res.status(500).json({ error: "Failed to unpublish document", message });
|
|
6954
7118
|
}
|
|
6955
7119
|
});
|
|
@@ -6972,6 +7136,10 @@ function momentumApiMiddleware(config) {
|
|
|
6972
7136
|
res.json({ version: draft, message: "Draft saved successfully" });
|
|
6973
7137
|
} catch (error) {
|
|
6974
7138
|
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
7139
|
+
if (error instanceof Error && error.name === "AccessDeniedError") {
|
|
7140
|
+
res.status(403).json({ error: "Access denied" });
|
|
7141
|
+
return;
|
|
7142
|
+
}
|
|
6975
7143
|
res.status(500).json({ error: "Failed to save draft", message });
|
|
6976
7144
|
}
|
|
6977
7145
|
});
|
|
@@ -6997,10 +7165,15 @@ function momentumApiMiddleware(config) {
|
|
|
6997
7165
|
});
|
|
6998
7166
|
return;
|
|
6999
7167
|
}
|
|
7000
|
-
const
|
|
7168
|
+
const parentId = req.params["id"];
|
|
7169
|
+
const differences = await versionOps.compare(versionId1, versionId2, parentId);
|
|
7001
7170
|
res.json({ differences });
|
|
7002
7171
|
} catch (error) {
|
|
7003
7172
|
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
7173
|
+
if (error instanceof Error && error.name === "AccessDeniedError") {
|
|
7174
|
+
res.status(403).json({ error: "Access denied" });
|
|
7175
|
+
return;
|
|
7176
|
+
}
|
|
7004
7177
|
res.status(500).json({ error: "Failed to compare versions", message });
|
|
7005
7178
|
}
|
|
7006
7179
|
});
|
|
@@ -7053,12 +7226,7 @@ function momentumApiMiddleware(config) {
|
|
|
7053
7226
|
} else {
|
|
7054
7227
|
const api = getMomentumAPI();
|
|
7055
7228
|
const contextApi = user ? api.setContext({ user }) : api;
|
|
7056
|
-
|
|
7057
|
-
if (!dbDoc) {
|
|
7058
|
-
res.status(404).json({ error: "Document not found" });
|
|
7059
|
-
return;
|
|
7060
|
-
}
|
|
7061
|
-
doc = dbDoc;
|
|
7229
|
+
doc = await contextApi.collection(slug2).findById(id);
|
|
7062
7230
|
}
|
|
7063
7231
|
const emailField = getEmailBuilderFieldName(collectionConfig);
|
|
7064
7232
|
const html = emailField ? await renderEmailPreviewHTML(doc, emailField) : renderPreviewHTML({ doc, collection: collectionConfig });
|
|
@@ -7251,7 +7419,13 @@ function momentumApiMiddleware(config) {
|
|
|
7251
7419
|
return { docs: r.docs, totalDocs: r.totalDocs };
|
|
7252
7420
|
},
|
|
7253
7421
|
findById: async (slug2, id) => {
|
|
7254
|
-
|
|
7422
|
+
try {
|
|
7423
|
+
return await ctxApi.collection(slug2).findById(id);
|
|
7424
|
+
} catch (err) {
|
|
7425
|
+
if (err instanceof Error && err.name === "DocumentNotFoundError")
|
|
7426
|
+
return null;
|
|
7427
|
+
throw err;
|
|
7428
|
+
}
|
|
7255
7429
|
},
|
|
7256
7430
|
count: (slug2) => ctxApi.collection(slug2).count(),
|
|
7257
7431
|
create: async (slug2, data) => {
|
|
@@ -8649,14 +8823,7 @@ function initializeMomentum(config, options = {}) {
|
|
|
8649
8823
|
}
|
|
8650
8824
|
}
|
|
8651
8825
|
setPluginMiddleware(pluginMiddleware2);
|
|
8652
|
-
|
|
8653
|
-
log.info("Initializing database schema...");
|
|
8654
|
-
await config.db.adapter.initialize(config.collections);
|
|
8655
|
-
}
|
|
8656
|
-
if (config.db.adapter.initializeGlobals && config.globals && config.globals.length > 0) {
|
|
8657
|
-
log.info(`Initializing globals table for ${config.globals.length} global(s)...`);
|
|
8658
|
-
await config.db.adapter.initializeGlobals(config.globals);
|
|
8659
|
-
}
|
|
8826
|
+
await syncDatabaseSchema(config, log);
|
|
8660
8827
|
log.info("Initializing API...");
|
|
8661
8828
|
const api = initializeMomentumAPI(config);
|
|
8662
8829
|
const runOnStart = config.seeding?.options?.runOnStart ?? "development";
|