@momentumcms/server-express 0.5.9 → 0.5.11
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 +15 -0
- package/index.cjs +819 -278
- package/index.js +696 -156
- package/package.json +43 -54
- package/src/index.d.ts +1 -0
- package/src/lib/rate-limit-middleware.d.ts +18 -0
package/index.cjs
CHANGED
|
@@ -175,7 +175,7 @@ function s3StorageAdapter(options) {
|
|
|
175
175
|
baseUrl,
|
|
176
176
|
forcePathStyle = false,
|
|
177
177
|
acl = "private",
|
|
178
|
-
presignedUrlExpiry =
|
|
178
|
+
presignedUrlExpiry = 900
|
|
179
179
|
} = options;
|
|
180
180
|
let client = null;
|
|
181
181
|
async function getClient() {
|
|
@@ -569,14 +569,14 @@ var init_strip_artifacts = __esm({
|
|
|
569
569
|
|
|
570
570
|
// libs/email/src/lib/render/render-types.ts
|
|
571
571
|
function injectEmailData() {
|
|
572
|
-
return (0,
|
|
572
|
+
return (0, import_core13.inject)(EMAIL_DATA);
|
|
573
573
|
}
|
|
574
|
-
var
|
|
574
|
+
var import_core13, EMAIL_DATA;
|
|
575
575
|
var init_render_types = __esm({
|
|
576
576
|
"libs/email/src/lib/render/render-types.ts"() {
|
|
577
577
|
"use strict";
|
|
578
|
-
|
|
579
|
-
EMAIL_DATA = new
|
|
578
|
+
import_core13 = require("@angular/core");
|
|
579
|
+
EMAIL_DATA = new import_core13.InjectionToken("EMAIL_DATA");
|
|
580
580
|
}
|
|
581
581
|
});
|
|
582
582
|
|
|
@@ -588,7 +588,7 @@ async function renderEmail(component, data, options) {
|
|
|
588
588
|
const shouldInlineCss = options?.inlineCss ?? true;
|
|
589
589
|
const shouldStripArtifacts = options?.stripArtifacts ?? true;
|
|
590
590
|
const extraProviders = options?.providers ?? [];
|
|
591
|
-
const mirror = (0,
|
|
591
|
+
const mirror = (0, import_core14.reflectComponentType)(component);
|
|
592
592
|
if (!mirror) {
|
|
593
593
|
throw new Error(
|
|
594
594
|
`Cannot reflect component type: ${component.name}. Ensure it has a @Component decorator.`
|
|
@@ -610,12 +610,12 @@ async function renderEmail(component, data, options) {
|
|
|
610
610
|
}
|
|
611
611
|
return html;
|
|
612
612
|
}
|
|
613
|
-
var import_compiler,
|
|
613
|
+
var import_compiler, import_core14, import_platform_server, import_platform_browser;
|
|
614
614
|
var init_render_email = __esm({
|
|
615
615
|
"libs/email/src/lib/render/render-email.ts"() {
|
|
616
616
|
"use strict";
|
|
617
617
|
import_compiler = require("@angular/compiler");
|
|
618
|
-
|
|
618
|
+
import_core14 = require("@angular/core");
|
|
619
619
|
import_platform_server = require("@angular/platform-server");
|
|
620
620
|
import_platform_browser = require("@angular/platform-browser");
|
|
621
621
|
init_css_inliner();
|
|
@@ -1048,26 +1048,26 @@ var init_default_templates = __esm({
|
|
|
1048
1048
|
});
|
|
1049
1049
|
|
|
1050
1050
|
// libs/email/src/lib/components/eml-body.component.ts
|
|
1051
|
-
var
|
|
1051
|
+
var import_core15, EmlBody;
|
|
1052
1052
|
var init_eml_body_component = __esm({
|
|
1053
1053
|
"libs/email/src/lib/components/eml-body.component.ts"() {
|
|
1054
1054
|
"use strict";
|
|
1055
|
-
|
|
1055
|
+
import_core15 = require("@angular/core");
|
|
1056
1056
|
EmlBody = class {
|
|
1057
1057
|
constructor() {
|
|
1058
|
-
this.backgroundColor = (0,
|
|
1059
|
-
this.fontFamily = (0,
|
|
1058
|
+
this.backgroundColor = (0, import_core15.input)("#f4f4f5");
|
|
1059
|
+
this.fontFamily = (0, import_core15.input)(
|
|
1060
1060
|
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif"
|
|
1061
1061
|
);
|
|
1062
|
-
this.padding = (0,
|
|
1063
|
-
this.tableStyle = (0,
|
|
1062
|
+
this.padding = (0, import_core15.input)("40px 20px");
|
|
1063
|
+
this.tableStyle = (0, import_core15.computed)(
|
|
1064
1064
|
() => `background-color: ${this.backgroundColor()}; font-family: ${this.fontFamily()}; line-height: 1.6; margin: 0; padding: 0;`
|
|
1065
1065
|
);
|
|
1066
|
-
this.cellStyle = (0,
|
|
1066
|
+
this.cellStyle = (0, import_core15.computed)(() => `padding: ${this.padding()};`);
|
|
1067
1067
|
}
|
|
1068
1068
|
};
|
|
1069
1069
|
EmlBody = __decorateClass([
|
|
1070
|
-
(0,
|
|
1070
|
+
(0, import_core15.Component)({
|
|
1071
1071
|
selector: "eml-body",
|
|
1072
1072
|
template: `
|
|
1073
1073
|
<table
|
|
@@ -1084,33 +1084,33 @@ var init_eml_body_component = __esm({
|
|
|
1084
1084
|
</tr>
|
|
1085
1085
|
</table>
|
|
1086
1086
|
`,
|
|
1087
|
-
changeDetection:
|
|
1087
|
+
changeDetection: import_core15.ChangeDetectionStrategy.OnPush
|
|
1088
1088
|
})
|
|
1089
1089
|
], EmlBody);
|
|
1090
1090
|
}
|
|
1091
1091
|
});
|
|
1092
1092
|
|
|
1093
1093
|
// libs/email/src/lib/components/eml-container.component.ts
|
|
1094
|
-
var
|
|
1094
|
+
var import_core16, EmlContainer;
|
|
1095
1095
|
var init_eml_container_component = __esm({
|
|
1096
1096
|
"libs/email/src/lib/components/eml-container.component.ts"() {
|
|
1097
1097
|
"use strict";
|
|
1098
|
-
|
|
1098
|
+
import_core16 = require("@angular/core");
|
|
1099
1099
|
EmlContainer = class {
|
|
1100
1100
|
constructor() {
|
|
1101
|
-
this.maxWidth = (0,
|
|
1102
|
-
this.backgroundColor = (0,
|
|
1103
|
-
this.borderRadius = (0,
|
|
1104
|
-
this.padding = (0,
|
|
1105
|
-
this.shadow = (0,
|
|
1106
|
-
this.tableStyle = (0,
|
|
1101
|
+
this.maxWidth = (0, import_core16.input)("480px");
|
|
1102
|
+
this.backgroundColor = (0, import_core16.input)("#ffffff");
|
|
1103
|
+
this.borderRadius = (0, import_core16.input)("8px");
|
|
1104
|
+
this.padding = (0, import_core16.input)("40px");
|
|
1105
|
+
this.shadow = (0, import_core16.input)("0 1px 3px rgba(0,0,0,0.1)");
|
|
1106
|
+
this.tableStyle = (0, import_core16.computed)(
|
|
1107
1107
|
() => `max-width: ${this.maxWidth()}; margin: 0 auto; background-color: ${this.backgroundColor()}; border-radius: ${this.borderRadius()}; box-shadow: ${this.shadow()};`
|
|
1108
1108
|
);
|
|
1109
|
-
this.cellStyle = (0,
|
|
1109
|
+
this.cellStyle = (0, import_core16.computed)(() => `padding: ${this.padding()};`);
|
|
1110
1110
|
}
|
|
1111
1111
|
};
|
|
1112
1112
|
EmlContainer = __decorateClass([
|
|
1113
|
-
(0,
|
|
1113
|
+
(0, import_core16.Component)({
|
|
1114
1114
|
selector: "eml-container",
|
|
1115
1115
|
template: `
|
|
1116
1116
|
<table
|
|
@@ -1127,26 +1127,26 @@ var init_eml_container_component = __esm({
|
|
|
1127
1127
|
</tr>
|
|
1128
1128
|
</table>
|
|
1129
1129
|
`,
|
|
1130
|
-
changeDetection:
|
|
1130
|
+
changeDetection: import_core16.ChangeDetectionStrategy.OnPush
|
|
1131
1131
|
})
|
|
1132
1132
|
], EmlContainer);
|
|
1133
1133
|
}
|
|
1134
1134
|
});
|
|
1135
1135
|
|
|
1136
1136
|
// libs/email/src/lib/components/eml-section.component.ts
|
|
1137
|
-
var
|
|
1137
|
+
var import_core17, EmlSection;
|
|
1138
1138
|
var init_eml_section_component = __esm({
|
|
1139
1139
|
"libs/email/src/lib/components/eml-section.component.ts"() {
|
|
1140
1140
|
"use strict";
|
|
1141
|
-
|
|
1141
|
+
import_core17 = require("@angular/core");
|
|
1142
1142
|
EmlSection = class {
|
|
1143
1143
|
constructor() {
|
|
1144
|
-
this.padding = (0,
|
|
1145
|
-
this.cellStyle = (0,
|
|
1144
|
+
this.padding = (0, import_core17.input)("0");
|
|
1145
|
+
this.cellStyle = (0, import_core17.computed)(() => `padding: ${this.padding()};`);
|
|
1146
1146
|
}
|
|
1147
1147
|
};
|
|
1148
1148
|
EmlSection = __decorateClass([
|
|
1149
|
-
(0,
|
|
1149
|
+
(0, import_core17.Component)({
|
|
1150
1150
|
selector: "eml-section",
|
|
1151
1151
|
template: `
|
|
1152
1152
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
|
@@ -1157,22 +1157,22 @@ var init_eml_section_component = __esm({
|
|
|
1157
1157
|
</tr>
|
|
1158
1158
|
</table>
|
|
1159
1159
|
`,
|
|
1160
|
-
changeDetection:
|
|
1160
|
+
changeDetection: import_core17.ChangeDetectionStrategy.OnPush
|
|
1161
1161
|
})
|
|
1162
1162
|
], EmlSection);
|
|
1163
1163
|
}
|
|
1164
1164
|
});
|
|
1165
1165
|
|
|
1166
1166
|
// libs/email/src/lib/components/eml-row.component.ts
|
|
1167
|
-
var
|
|
1167
|
+
var import_core18, EmlRow;
|
|
1168
1168
|
var init_eml_row_component = __esm({
|
|
1169
1169
|
"libs/email/src/lib/components/eml-row.component.ts"() {
|
|
1170
1170
|
"use strict";
|
|
1171
|
-
|
|
1171
|
+
import_core18 = require("@angular/core");
|
|
1172
1172
|
EmlRow = class {
|
|
1173
1173
|
};
|
|
1174
1174
|
EmlRow = __decorateClass([
|
|
1175
|
-
(0,
|
|
1175
|
+
(0, import_core18.Component)({
|
|
1176
1176
|
selector: "eml-row",
|
|
1177
1177
|
template: `
|
|
1178
1178
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
|
@@ -1181,130 +1181,130 @@ var init_eml_row_component = __esm({
|
|
|
1181
1181
|
</tr>
|
|
1182
1182
|
</table>
|
|
1183
1183
|
`,
|
|
1184
|
-
changeDetection:
|
|
1184
|
+
changeDetection: import_core18.ChangeDetectionStrategy.OnPush
|
|
1185
1185
|
})
|
|
1186
1186
|
], EmlRow);
|
|
1187
1187
|
}
|
|
1188
1188
|
});
|
|
1189
1189
|
|
|
1190
1190
|
// libs/email/src/lib/components/eml-column.component.ts
|
|
1191
|
-
var
|
|
1191
|
+
var import_core19, EmlColumn;
|
|
1192
1192
|
var init_eml_column_component = __esm({
|
|
1193
1193
|
"libs/email/src/lib/components/eml-column.component.ts"() {
|
|
1194
1194
|
"use strict";
|
|
1195
|
-
|
|
1195
|
+
import_core19 = require("@angular/core");
|
|
1196
1196
|
EmlColumn = class {
|
|
1197
1197
|
constructor() {
|
|
1198
|
-
this.width = (0,
|
|
1199
|
-
this.padding = (0,
|
|
1200
|
-
this.verticalAlign = (0,
|
|
1201
|
-
this.cellStyle = (0,
|
|
1198
|
+
this.width = (0, import_core19.input)(void 0);
|
|
1199
|
+
this.padding = (0, import_core19.input)("0");
|
|
1200
|
+
this.verticalAlign = (0, import_core19.input)("top");
|
|
1201
|
+
this.cellStyle = (0, import_core19.computed)(
|
|
1202
1202
|
() => `padding: ${this.padding()}; vertical-align: ${this.verticalAlign()};`
|
|
1203
1203
|
);
|
|
1204
1204
|
}
|
|
1205
1205
|
};
|
|
1206
1206
|
EmlColumn = __decorateClass([
|
|
1207
|
-
(0,
|
|
1207
|
+
(0, import_core19.Component)({
|
|
1208
1208
|
selector: "eml-column",
|
|
1209
1209
|
template: `
|
|
1210
1210
|
<td [attr.style]="cellStyle()" [attr.width]="width()" valign="top">
|
|
1211
1211
|
<ng-content />
|
|
1212
1212
|
</td>
|
|
1213
1213
|
`,
|
|
1214
|
-
changeDetection:
|
|
1214
|
+
changeDetection: import_core19.ChangeDetectionStrategy.OnPush
|
|
1215
1215
|
})
|
|
1216
1216
|
], EmlColumn);
|
|
1217
1217
|
}
|
|
1218
1218
|
});
|
|
1219
1219
|
|
|
1220
1220
|
// libs/email/src/lib/components/eml-text.component.ts
|
|
1221
|
-
var
|
|
1221
|
+
var import_core20, EmlText;
|
|
1222
1222
|
var init_eml_text_component = __esm({
|
|
1223
1223
|
"libs/email/src/lib/components/eml-text.component.ts"() {
|
|
1224
1224
|
"use strict";
|
|
1225
|
-
|
|
1225
|
+
import_core20 = require("@angular/core");
|
|
1226
1226
|
EmlText = class {
|
|
1227
1227
|
constructor() {
|
|
1228
|
-
this.color = (0,
|
|
1229
|
-
this.fontSize = (0,
|
|
1230
|
-
this.lineHeight = (0,
|
|
1231
|
-
this.margin = (0,
|
|
1232
|
-
this.textAlign = (0,
|
|
1233
|
-
this.pStyle = (0,
|
|
1228
|
+
this.color = (0, import_core20.input)("#3f3f46");
|
|
1229
|
+
this.fontSize = (0, import_core20.input)("16px");
|
|
1230
|
+
this.lineHeight = (0, import_core20.input)("1.6");
|
|
1231
|
+
this.margin = (0, import_core20.input)("0 0 16px");
|
|
1232
|
+
this.textAlign = (0, import_core20.input)("left");
|
|
1233
|
+
this.pStyle = (0, import_core20.computed)(
|
|
1234
1234
|
() => `margin: ${this.margin()}; color: ${this.color()}; font-size: ${this.fontSize()}; line-height: ${this.lineHeight()}; text-align: ${this.textAlign()};`
|
|
1235
1235
|
);
|
|
1236
1236
|
}
|
|
1237
1237
|
};
|
|
1238
1238
|
EmlText = __decorateClass([
|
|
1239
|
-
(0,
|
|
1239
|
+
(0, import_core20.Component)({
|
|
1240
1240
|
selector: "eml-text",
|
|
1241
1241
|
template: `
|
|
1242
1242
|
<p [attr.style]="pStyle()">
|
|
1243
1243
|
<ng-content />
|
|
1244
1244
|
</p>
|
|
1245
1245
|
`,
|
|
1246
|
-
changeDetection:
|
|
1246
|
+
changeDetection: import_core20.ChangeDetectionStrategy.OnPush
|
|
1247
1247
|
})
|
|
1248
1248
|
], EmlText);
|
|
1249
1249
|
}
|
|
1250
1250
|
});
|
|
1251
1251
|
|
|
1252
1252
|
// libs/email/src/lib/components/eml-heading.component.ts
|
|
1253
|
-
var
|
|
1253
|
+
var import_core21, EmlHeading;
|
|
1254
1254
|
var init_eml_heading_component = __esm({
|
|
1255
1255
|
"libs/email/src/lib/components/eml-heading.component.ts"() {
|
|
1256
1256
|
"use strict";
|
|
1257
|
-
|
|
1257
|
+
import_core21 = require("@angular/core");
|
|
1258
1258
|
EmlHeading = class {
|
|
1259
1259
|
constructor() {
|
|
1260
|
-
this.level = (0,
|
|
1261
|
-
this.color = (0,
|
|
1262
|
-
this.margin = (0,
|
|
1263
|
-
this.textAlign = (0,
|
|
1260
|
+
this.level = (0, import_core21.input)(1);
|
|
1261
|
+
this.color = (0, import_core21.input)("#18181b");
|
|
1262
|
+
this.margin = (0, import_core21.input)("0 0 24px");
|
|
1263
|
+
this.textAlign = (0, import_core21.input)("left");
|
|
1264
1264
|
this.fontSizeMap = {
|
|
1265
1265
|
1: "24px",
|
|
1266
1266
|
2: "20px",
|
|
1267
1267
|
3: "16px"
|
|
1268
1268
|
};
|
|
1269
|
-
this.headingStyle = (0,
|
|
1269
|
+
this.headingStyle = (0, import_core21.computed)(
|
|
1270
1270
|
() => `margin: ${this.margin()}; font-size: ${this.fontSizeMap[this.level()] ?? "24px"}; font-weight: 600; color: ${this.color()}; text-align: ${this.textAlign()};`
|
|
1271
1271
|
);
|
|
1272
1272
|
}
|
|
1273
1273
|
};
|
|
1274
1274
|
EmlHeading = __decorateClass([
|
|
1275
|
-
(0,
|
|
1275
|
+
(0, import_core21.Component)({
|
|
1276
1276
|
selector: "eml-heading",
|
|
1277
1277
|
template: `<div [attr.style]="headingStyle()" [attr.role]="'heading'" [attr.aria-level]="level()">
|
|
1278
1278
|
<ng-content />
|
|
1279
1279
|
</div>`,
|
|
1280
|
-
changeDetection:
|
|
1280
|
+
changeDetection: import_core21.ChangeDetectionStrategy.OnPush
|
|
1281
1281
|
})
|
|
1282
1282
|
], EmlHeading);
|
|
1283
1283
|
}
|
|
1284
1284
|
});
|
|
1285
1285
|
|
|
1286
1286
|
// libs/email/src/lib/components/eml-button.component.ts
|
|
1287
|
-
var
|
|
1287
|
+
var import_core22, EmlButton;
|
|
1288
1288
|
var init_eml_button_component = __esm({
|
|
1289
1289
|
"libs/email/src/lib/components/eml-button.component.ts"() {
|
|
1290
1290
|
"use strict";
|
|
1291
|
-
|
|
1291
|
+
import_core22 = require("@angular/core");
|
|
1292
1292
|
EmlButton = class {
|
|
1293
1293
|
constructor() {
|
|
1294
|
-
this.href = (0,
|
|
1295
|
-
this.backgroundColor = (0,
|
|
1296
|
-
this.color = (0,
|
|
1297
|
-
this.borderRadius = (0,
|
|
1298
|
-
this.padding = (0,
|
|
1299
|
-
this.textAlign = (0,
|
|
1300
|
-
this.alignStyle = (0,
|
|
1301
|
-
this.linkStyle = (0,
|
|
1294
|
+
this.href = (0, import_core22.input)("");
|
|
1295
|
+
this.backgroundColor = (0, import_core22.input)("#18181b");
|
|
1296
|
+
this.color = (0, import_core22.input)("#ffffff");
|
|
1297
|
+
this.borderRadius = (0, import_core22.input)("6px");
|
|
1298
|
+
this.padding = (0, import_core22.input)("12px 24px");
|
|
1299
|
+
this.textAlign = (0, import_core22.input)("left");
|
|
1300
|
+
this.alignStyle = (0, import_core22.computed)(() => `padding: 0; text-align: ${this.textAlign()};`);
|
|
1301
|
+
this.linkStyle = (0, import_core22.computed)(
|
|
1302
1302
|
() => `display: inline-block; padding: ${this.padding()}; background-color: ${this.backgroundColor()}; color: ${this.color()}; text-decoration: none; border-radius: ${this.borderRadius()}; font-weight: 500;`
|
|
1303
1303
|
);
|
|
1304
1304
|
}
|
|
1305
1305
|
};
|
|
1306
1306
|
EmlButton = __decorateClass([
|
|
1307
|
-
(0,
|
|
1307
|
+
(0, import_core22.Component)({
|
|
1308
1308
|
selector: "eml-button",
|
|
1309
1309
|
template: `
|
|
1310
1310
|
<table role="presentation" width="100%" cellspacing="0" cellpadding="0">
|
|
@@ -1317,62 +1317,62 @@ var init_eml_button_component = __esm({
|
|
|
1317
1317
|
</tr>
|
|
1318
1318
|
</table>
|
|
1319
1319
|
`,
|
|
1320
|
-
changeDetection:
|
|
1320
|
+
changeDetection: import_core22.ChangeDetectionStrategy.OnPush
|
|
1321
1321
|
})
|
|
1322
1322
|
], EmlButton);
|
|
1323
1323
|
}
|
|
1324
1324
|
});
|
|
1325
1325
|
|
|
1326
1326
|
// libs/email/src/lib/components/eml-link.component.ts
|
|
1327
|
-
var
|
|
1327
|
+
var import_core23, EmlLink;
|
|
1328
1328
|
var init_eml_link_component = __esm({
|
|
1329
1329
|
"libs/email/src/lib/components/eml-link.component.ts"() {
|
|
1330
1330
|
"use strict";
|
|
1331
|
-
|
|
1331
|
+
import_core23 = require("@angular/core");
|
|
1332
1332
|
EmlLink = class {
|
|
1333
1333
|
constructor() {
|
|
1334
|
-
this.href = (0,
|
|
1335
|
-
this.color = (0,
|
|
1336
|
-
this.textDecoration = (0,
|
|
1337
|
-
this.linkStyle = (0,
|
|
1334
|
+
this.href = (0, import_core23.input)("");
|
|
1335
|
+
this.color = (0, import_core23.input)("#18181b");
|
|
1336
|
+
this.textDecoration = (0, import_core23.input)("underline");
|
|
1337
|
+
this.linkStyle = (0, import_core23.computed)(
|
|
1338
1338
|
() => `color: ${this.color()}; text-decoration: ${this.textDecoration()};`
|
|
1339
1339
|
);
|
|
1340
1340
|
}
|
|
1341
1341
|
};
|
|
1342
1342
|
EmlLink = __decorateClass([
|
|
1343
|
-
(0,
|
|
1343
|
+
(0, import_core23.Component)({
|
|
1344
1344
|
selector: "eml-link",
|
|
1345
1345
|
template: `
|
|
1346
1346
|
<a [attr.href]="href()" [attr.style]="linkStyle()" target="_blank">
|
|
1347
1347
|
<ng-content />
|
|
1348
1348
|
</a>
|
|
1349
1349
|
`,
|
|
1350
|
-
changeDetection:
|
|
1350
|
+
changeDetection: import_core23.ChangeDetectionStrategy.OnPush
|
|
1351
1351
|
})
|
|
1352
1352
|
], EmlLink);
|
|
1353
1353
|
}
|
|
1354
1354
|
});
|
|
1355
1355
|
|
|
1356
1356
|
// libs/email/src/lib/components/eml-image.component.ts
|
|
1357
|
-
var
|
|
1357
|
+
var import_core24, EmlImage;
|
|
1358
1358
|
var init_eml_image_component = __esm({
|
|
1359
1359
|
"libs/email/src/lib/components/eml-image.component.ts"() {
|
|
1360
1360
|
"use strict";
|
|
1361
|
-
|
|
1361
|
+
import_core24 = require("@angular/core");
|
|
1362
1362
|
EmlImage = class {
|
|
1363
1363
|
constructor() {
|
|
1364
|
-
this.src = (0,
|
|
1365
|
-
this.alt = (0,
|
|
1366
|
-
this.width = (0,
|
|
1367
|
-
this.height = (0,
|
|
1368
|
-
this.borderRadius = (0,
|
|
1369
|
-
this.imgStyle = (0,
|
|
1364
|
+
this.src = (0, import_core24.input)("");
|
|
1365
|
+
this.alt = (0, import_core24.input)("");
|
|
1366
|
+
this.width = (0, import_core24.input)(void 0);
|
|
1367
|
+
this.height = (0, import_core24.input)(void 0);
|
|
1368
|
+
this.borderRadius = (0, import_core24.input)("0");
|
|
1369
|
+
this.imgStyle = (0, import_core24.computed)(
|
|
1370
1370
|
() => `display: block; max-width: 100%; border: 0; outline: none; border-radius: ${this.borderRadius()};`
|
|
1371
1371
|
);
|
|
1372
1372
|
}
|
|
1373
1373
|
};
|
|
1374
1374
|
EmlImage = __decorateClass([
|
|
1375
|
-
(0,
|
|
1375
|
+
(0, import_core24.Component)({
|
|
1376
1376
|
selector: "eml-image",
|
|
1377
1377
|
template: `
|
|
1378
1378
|
<img
|
|
@@ -1383,104 +1383,104 @@ var init_eml_image_component = __esm({
|
|
|
1383
1383
|
[attr.style]="imgStyle()"
|
|
1384
1384
|
/>
|
|
1385
1385
|
`,
|
|
1386
|
-
changeDetection:
|
|
1386
|
+
changeDetection: import_core24.ChangeDetectionStrategy.OnPush
|
|
1387
1387
|
})
|
|
1388
1388
|
], EmlImage);
|
|
1389
1389
|
}
|
|
1390
1390
|
});
|
|
1391
1391
|
|
|
1392
1392
|
// libs/email/src/lib/components/eml-divider.component.ts
|
|
1393
|
-
var
|
|
1393
|
+
var import_core25, EmlDivider;
|
|
1394
1394
|
var init_eml_divider_component = __esm({
|
|
1395
1395
|
"libs/email/src/lib/components/eml-divider.component.ts"() {
|
|
1396
1396
|
"use strict";
|
|
1397
|
-
|
|
1397
|
+
import_core25 = require("@angular/core");
|
|
1398
1398
|
EmlDivider = class {
|
|
1399
1399
|
constructor() {
|
|
1400
|
-
this.color = (0,
|
|
1401
|
-
this.margin = (0,
|
|
1402
|
-
this.hrStyle = (0,
|
|
1400
|
+
this.color = (0, import_core25.input)("#e4e4e7");
|
|
1401
|
+
this.margin = (0, import_core25.input)("24px 0");
|
|
1402
|
+
this.hrStyle = (0, import_core25.computed)(
|
|
1403
1403
|
() => `border: none; border-top: 1px solid ${this.color()}; margin: ${this.margin()};`
|
|
1404
1404
|
);
|
|
1405
1405
|
}
|
|
1406
1406
|
};
|
|
1407
1407
|
EmlDivider = __decorateClass([
|
|
1408
|
-
(0,
|
|
1408
|
+
(0, import_core25.Component)({
|
|
1409
1409
|
selector: "eml-divider",
|
|
1410
1410
|
template: `<hr [attr.style]="hrStyle()" />`,
|
|
1411
|
-
changeDetection:
|
|
1411
|
+
changeDetection: import_core25.ChangeDetectionStrategy.OnPush
|
|
1412
1412
|
})
|
|
1413
1413
|
], EmlDivider);
|
|
1414
1414
|
}
|
|
1415
1415
|
});
|
|
1416
1416
|
|
|
1417
1417
|
// libs/email/src/lib/components/eml-preview.component.ts
|
|
1418
|
-
var
|
|
1418
|
+
var import_core26, EmlPreview;
|
|
1419
1419
|
var init_eml_preview_component = __esm({
|
|
1420
1420
|
"libs/email/src/lib/components/eml-preview.component.ts"() {
|
|
1421
1421
|
"use strict";
|
|
1422
|
-
|
|
1422
|
+
import_core26 = require("@angular/core");
|
|
1423
1423
|
EmlPreview = class {
|
|
1424
1424
|
};
|
|
1425
1425
|
EmlPreview = __decorateClass([
|
|
1426
|
-
(0,
|
|
1426
|
+
(0, import_core26.Component)({
|
|
1427
1427
|
selector: "eml-preview",
|
|
1428
1428
|
template: `
|
|
1429
1429
|
<div style="display: none; max-height: 0; overflow: hidden; mso-hide: all;">
|
|
1430
1430
|
<ng-content />
|
|
1431
1431
|
</div>
|
|
1432
1432
|
`,
|
|
1433
|
-
changeDetection:
|
|
1433
|
+
changeDetection: import_core26.ChangeDetectionStrategy.OnPush
|
|
1434
1434
|
})
|
|
1435
1435
|
], EmlPreview);
|
|
1436
1436
|
}
|
|
1437
1437
|
});
|
|
1438
1438
|
|
|
1439
1439
|
// libs/email/src/lib/components/eml-spacer.component.ts
|
|
1440
|
-
var
|
|
1440
|
+
var import_core27, EmlSpacer;
|
|
1441
1441
|
var init_eml_spacer_component = __esm({
|
|
1442
1442
|
"libs/email/src/lib/components/eml-spacer.component.ts"() {
|
|
1443
1443
|
"use strict";
|
|
1444
|
-
|
|
1444
|
+
import_core27 = require("@angular/core");
|
|
1445
1445
|
EmlSpacer = class {
|
|
1446
1446
|
constructor() {
|
|
1447
|
-
this.height = (0,
|
|
1448
|
-
this.spacerStyle = (0,
|
|
1447
|
+
this.height = (0, import_core27.input)("24px");
|
|
1448
|
+
this.spacerStyle = (0, import_core27.computed)(
|
|
1449
1449
|
() => `height: ${this.height()}; line-height: ${this.height()}; font-size: 1px;`
|
|
1450
1450
|
);
|
|
1451
1451
|
}
|
|
1452
1452
|
};
|
|
1453
1453
|
EmlSpacer = __decorateClass([
|
|
1454
|
-
(0,
|
|
1454
|
+
(0, import_core27.Component)({
|
|
1455
1455
|
selector: "eml-spacer",
|
|
1456
1456
|
template: `<div [attr.style]="spacerStyle()"></div>`,
|
|
1457
|
-
changeDetection:
|
|
1457
|
+
changeDetection: import_core27.ChangeDetectionStrategy.OnPush
|
|
1458
1458
|
})
|
|
1459
1459
|
], EmlSpacer);
|
|
1460
1460
|
}
|
|
1461
1461
|
});
|
|
1462
1462
|
|
|
1463
1463
|
// libs/email/src/lib/components/eml-footer.component.ts
|
|
1464
|
-
var
|
|
1464
|
+
var import_core28, EmlFooter;
|
|
1465
1465
|
var init_eml_footer_component = __esm({
|
|
1466
1466
|
"libs/email/src/lib/components/eml-footer.component.ts"() {
|
|
1467
1467
|
"use strict";
|
|
1468
|
-
|
|
1468
|
+
import_core28 = require("@angular/core");
|
|
1469
1469
|
EmlFooter = class {
|
|
1470
1470
|
constructor() {
|
|
1471
|
-
this.maxWidth = (0,
|
|
1472
|
-
this.color = (0,
|
|
1473
|
-
this.fontSize = (0,
|
|
1474
|
-
this.textAlign = (0,
|
|
1475
|
-
this.padding = (0,
|
|
1476
|
-
this.tableStyle = (0,
|
|
1477
|
-
this.cellStyle = (0,
|
|
1471
|
+
this.maxWidth = (0, import_core28.input)("480px");
|
|
1472
|
+
this.color = (0, import_core28.input)("#71717a");
|
|
1473
|
+
this.fontSize = (0, import_core28.input)("12px");
|
|
1474
|
+
this.textAlign = (0, import_core28.input)("center");
|
|
1475
|
+
this.padding = (0, import_core28.input)("20px 0 0");
|
|
1476
|
+
this.tableStyle = (0, import_core28.computed)(() => `max-width: ${this.maxWidth()}; margin: 0 auto;`);
|
|
1477
|
+
this.cellStyle = (0, import_core28.computed)(
|
|
1478
1478
|
() => `text-align: ${this.textAlign()}; color: ${this.color()}; font-size: ${this.fontSize()}; padding: ${this.padding()};`
|
|
1479
1479
|
);
|
|
1480
1480
|
}
|
|
1481
1481
|
};
|
|
1482
1482
|
EmlFooter = __decorateClass([
|
|
1483
|
-
(0,
|
|
1483
|
+
(0, import_core28.Component)({
|
|
1484
1484
|
selector: "eml-footer",
|
|
1485
1485
|
template: `
|
|
1486
1486
|
<table
|
|
@@ -1497,7 +1497,7 @@ var init_eml_footer_component = __esm({
|
|
|
1497
1497
|
</tr>
|
|
1498
1498
|
</table>
|
|
1499
1499
|
`,
|
|
1500
|
-
changeDetection:
|
|
1500
|
+
changeDetection: import_core28.ChangeDetectionStrategy.OnPush
|
|
1501
1501
|
})
|
|
1502
1502
|
], EmlFooter);
|
|
1503
1503
|
}
|
|
@@ -1658,6 +1658,7 @@ __export(src_exports3, {
|
|
|
1658
1658
|
createMomentumServer: () => createMomentumServer,
|
|
1659
1659
|
createOpenAPIMiddleware: () => createOpenAPIMiddleware,
|
|
1660
1660
|
createProtectMiddleware: () => createProtectMiddleware,
|
|
1661
|
+
createRateLimitMiddleware: () => createRateLimitMiddleware,
|
|
1661
1662
|
createSessionResolverMiddleware: () => createSessionResolverMiddleware,
|
|
1662
1663
|
createSetupMiddleware: () => createSetupMiddleware,
|
|
1663
1664
|
getPluginMiddleware: () => getPluginMiddleware,
|
|
@@ -2789,6 +2790,21 @@ var VersionOperationsImpl = class {
|
|
|
2789
2790
|
});
|
|
2790
2791
|
}
|
|
2791
2792
|
}
|
|
2793
|
+
if (!this.context.overrideAccess && hasFieldAccessControl(this.collectionConfig.fields)) {
|
|
2794
|
+
for (let i = 0; i < docs.length; i++) {
|
|
2795
|
+
const version = docs[i].version;
|
|
2796
|
+
if (version && typeof version === "object") {
|
|
2797
|
+
const versionRecord = version;
|
|
2798
|
+
const filtered = await filterReadableFields(
|
|
2799
|
+
this.collectionConfig.fields,
|
|
2800
|
+
versionRecord,
|
|
2801
|
+
this.buildRequestContext()
|
|
2802
|
+
);
|
|
2803
|
+
const filteredAsT = filtered;
|
|
2804
|
+
docs[i] = { ...docs[i], version: filteredAsT };
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2792
2808
|
const countOptions = {
|
|
2793
2809
|
includeAutosave: options?.includeAutosave,
|
|
2794
2810
|
status: options?.status
|
|
@@ -2818,9 +2834,21 @@ var VersionOperationsImpl = class {
|
|
|
2818
2834
|
if (parsedVersion === null) {
|
|
2819
2835
|
return null;
|
|
2820
2836
|
}
|
|
2837
|
+
let filteredVersion = parsedVersion;
|
|
2838
|
+
if (!this.context.overrideAccess && hasFieldAccessControl(this.collectionConfig.fields)) {
|
|
2839
|
+
if (filteredVersion && typeof filteredVersion === "object") {
|
|
2840
|
+
const versionRecord = filteredVersion;
|
|
2841
|
+
const filtered = await filterReadableFields(
|
|
2842
|
+
this.collectionConfig.fields,
|
|
2843
|
+
versionRecord,
|
|
2844
|
+
this.buildRequestContext()
|
|
2845
|
+
);
|
|
2846
|
+
filteredVersion = filtered;
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2821
2849
|
return {
|
|
2822
2850
|
...version,
|
|
2823
|
-
version:
|
|
2851
|
+
version: filteredVersion
|
|
2824
2852
|
};
|
|
2825
2853
|
}
|
|
2826
2854
|
async restore(options) {
|
|
@@ -3042,6 +3070,415 @@ var VersionOperationsImpl = class {
|
|
|
3042
3070
|
}
|
|
3043
3071
|
};
|
|
3044
3072
|
|
|
3073
|
+
// libs/server-core/src/lib/where-clause.ts
|
|
3074
|
+
var OPERATOR_MAP = {
|
|
3075
|
+
equals: "$eq",
|
|
3076
|
+
gt: "$gt",
|
|
3077
|
+
gte: "$gte",
|
|
3078
|
+
lt: "$lt",
|
|
3079
|
+
lte: "$lte",
|
|
3080
|
+
not_equals: "$ne",
|
|
3081
|
+
like: "$like",
|
|
3082
|
+
contains: "$contains",
|
|
3083
|
+
in: "$in",
|
|
3084
|
+
not_in: "$nin",
|
|
3085
|
+
exists: "$exists"
|
|
3086
|
+
};
|
|
3087
|
+
var MAX_WHERE_CONDITIONS = 20;
|
|
3088
|
+
var MAX_JOINS = 5;
|
|
3089
|
+
var MAX_WHERE_NESTING_DEPTH = 5;
|
|
3090
|
+
var MAX_PAGE_LIMIT = 1e3;
|
|
3091
|
+
var MAX_PAGE = 1e6;
|
|
3092
|
+
var VALID_OPERATORS = new Set(Object.keys(OPERATOR_MAP));
|
|
3093
|
+
function countWhereConditions(where, depth = 0) {
|
|
3094
|
+
if (depth > MAX_WHERE_NESTING_DEPTH) {
|
|
3095
|
+
throw new ValidationError([
|
|
3096
|
+
{
|
|
3097
|
+
field: "where",
|
|
3098
|
+
message: `Where clause nesting depth exceeds maximum of ${MAX_WHERE_NESTING_DEPTH} levels.`
|
|
3099
|
+
}
|
|
3100
|
+
]);
|
|
3101
|
+
}
|
|
3102
|
+
let count = 0;
|
|
3103
|
+
for (const [key, value] of Object.entries(where)) {
|
|
3104
|
+
if ((key === "and" || key === "or") && Array.isArray(value)) {
|
|
3105
|
+
for (const sub of value) {
|
|
3106
|
+
if (typeof sub === "object" && sub !== null) {
|
|
3107
|
+
count += countWhereConditions(sub, depth + 1);
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3110
|
+
} else {
|
|
3111
|
+
count++;
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
return count;
|
|
3115
|
+
}
|
|
3116
|
+
function flattenWhereClause(where) {
|
|
3117
|
+
if (!where)
|
|
3118
|
+
return {};
|
|
3119
|
+
const fieldCount = countWhereConditions(where);
|
|
3120
|
+
if (fieldCount > MAX_WHERE_CONDITIONS) {
|
|
3121
|
+
throw new ValidationError([
|
|
3122
|
+
{
|
|
3123
|
+
field: "where",
|
|
3124
|
+
message: `Where clause exceeds maximum of ${MAX_WHERE_CONDITIONS} conditions (got ${fieldCount}).`
|
|
3125
|
+
}
|
|
3126
|
+
]);
|
|
3127
|
+
}
|
|
3128
|
+
return flattenWhereRecursive(where, 0);
|
|
3129
|
+
}
|
|
3130
|
+
function flattenWhereRecursive(where, depth) {
|
|
3131
|
+
if (depth > MAX_WHERE_NESTING_DEPTH) {
|
|
3132
|
+
throw new ValidationError([
|
|
3133
|
+
{
|
|
3134
|
+
field: "where",
|
|
3135
|
+
message: `Where clause nesting depth exceeds maximum of ${MAX_WHERE_NESTING_DEPTH} levels.`
|
|
3136
|
+
}
|
|
3137
|
+
]);
|
|
3138
|
+
}
|
|
3139
|
+
const result = {};
|
|
3140
|
+
for (const [field, condition] of Object.entries(where)) {
|
|
3141
|
+
if (field === "and" || field === "or") {
|
|
3142
|
+
if (!Array.isArray(condition)) {
|
|
3143
|
+
throw new ValidationError([
|
|
3144
|
+
{
|
|
3145
|
+
field,
|
|
3146
|
+
message: `The "${field}" operator requires an array of conditions.`
|
|
3147
|
+
}
|
|
3148
|
+
]);
|
|
3149
|
+
}
|
|
3150
|
+
const internalKey = field === "and" ? "$and" : "$or";
|
|
3151
|
+
result[internalKey] = condition.filter((sub) => typeof sub === "object" && sub !== null).map((sub) => {
|
|
3152
|
+
if ("$join" in sub)
|
|
3153
|
+
return sub;
|
|
3154
|
+
return flattenWhereRecursive(sub, depth + 1);
|
|
3155
|
+
});
|
|
3156
|
+
continue;
|
|
3157
|
+
}
|
|
3158
|
+
if (typeof condition !== "object" || condition === null) {
|
|
3159
|
+
result[field] = condition;
|
|
3160
|
+
continue;
|
|
3161
|
+
}
|
|
3162
|
+
const condObj = condition;
|
|
3163
|
+
const ops = {};
|
|
3164
|
+
let hasOp = false;
|
|
3165
|
+
for (const [userOp, internalOp] of Object.entries(OPERATOR_MAP)) {
|
|
3166
|
+
if (userOp in condObj) {
|
|
3167
|
+
let value = condObj[userOp];
|
|
3168
|
+
if (internalOp === "$exists" && typeof value === "string") {
|
|
3169
|
+
value = value === "true";
|
|
3170
|
+
}
|
|
3171
|
+
if (typeof value === "string") {
|
|
3172
|
+
value = value.replace(/\0/g, "");
|
|
3173
|
+
}
|
|
3174
|
+
if (Array.isArray(value)) {
|
|
3175
|
+
value = value.map(
|
|
3176
|
+
(item) => typeof item === "string" ? item.replace(/\0/g, "") : item
|
|
3177
|
+
);
|
|
3178
|
+
}
|
|
3179
|
+
ops[internalOp] = value;
|
|
3180
|
+
hasOp = true;
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
for (const key of Object.keys(condObj)) {
|
|
3184
|
+
if (!VALID_OPERATORS.has(key)) {
|
|
3185
|
+
throw new ValidationError([
|
|
3186
|
+
{
|
|
3187
|
+
field,
|
|
3188
|
+
message: `Unknown operator "${key}". Valid operators: ${[...VALID_OPERATORS].sort().join(", ")}`
|
|
3189
|
+
}
|
|
3190
|
+
]);
|
|
3191
|
+
}
|
|
3192
|
+
}
|
|
3193
|
+
if (hasOp) {
|
|
3194
|
+
result[field] = ops;
|
|
3195
|
+
} else {
|
|
3196
|
+
result[field] = condition;
|
|
3197
|
+
}
|
|
3198
|
+
}
|
|
3199
|
+
return result;
|
|
3200
|
+
}
|
|
3201
|
+
function extractRelationshipJoins(where, fields, allCollections) {
|
|
3202
|
+
if (!where)
|
|
3203
|
+
return { cleanedWhere: void 0, joins: [], allJoins: [] };
|
|
3204
|
+
const dataFields = flattenDataFields(fields);
|
|
3205
|
+
const fieldMap = new Map(dataFields.map((f) => [f.name, f]));
|
|
3206
|
+
const joins = [];
|
|
3207
|
+
const allJoins = [];
|
|
3208
|
+
const cleanedWhere = {};
|
|
3209
|
+
for (const [key, condition] of Object.entries(where)) {
|
|
3210
|
+
if (key === "and" || key === "or") {
|
|
3211
|
+
if (Array.isArray(condition)) {
|
|
3212
|
+
const cleanedArray = [];
|
|
3213
|
+
for (const sub of condition) {
|
|
3214
|
+
if (typeof sub === "object" && sub !== null) {
|
|
3215
|
+
const {
|
|
3216
|
+
cleanedWhere: subCleaned,
|
|
3217
|
+
joins: subTopJoins,
|
|
3218
|
+
allJoins: subAllJoins
|
|
3219
|
+
} = extractRelationshipJoins(
|
|
3220
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- WhereClause sub-object
|
|
3221
|
+
sub,
|
|
3222
|
+
fields,
|
|
3223
|
+
allCollections
|
|
3224
|
+
);
|
|
3225
|
+
if (subCleaned)
|
|
3226
|
+
cleanedArray.push(subCleaned);
|
|
3227
|
+
for (const join2 of subTopJoins) {
|
|
3228
|
+
cleanedArray.push({ ["$join"]: join2 });
|
|
3229
|
+
}
|
|
3230
|
+
allJoins.push(...subAllJoins);
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
if (cleanedArray.length > 0) {
|
|
3234
|
+
cleanedWhere[key] = cleanedArray;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
continue;
|
|
3238
|
+
}
|
|
3239
|
+
let field = fieldMap.get(key);
|
|
3240
|
+
if (!field && key.includes(".")) {
|
|
3241
|
+
const [rootKey, ...subPath] = key.split(".");
|
|
3242
|
+
const rootField = fieldMap.get(rootKey);
|
|
3243
|
+
if (rootField && rootField.type === "relationship" && subPath.length > 0 && condition !== null) {
|
|
3244
|
+
let nested = condition;
|
|
3245
|
+
for (let i = subPath.length - 1; i >= 0; i--) {
|
|
3246
|
+
nested = { [subPath[i]]: nested };
|
|
3247
|
+
}
|
|
3248
|
+
field = rootField;
|
|
3249
|
+
const {
|
|
3250
|
+
cleanedWhere: subCleaned,
|
|
3251
|
+
joins: subJoins,
|
|
3252
|
+
allJoins: subAllJoins
|
|
3253
|
+
} = extractRelationshipJoins(
|
|
3254
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- reconstructed nested where
|
|
3255
|
+
{ [rootKey]: nested },
|
|
3256
|
+
fields,
|
|
3257
|
+
allCollections
|
|
3258
|
+
);
|
|
3259
|
+
if (subCleaned)
|
|
3260
|
+
Object.assign(cleanedWhere, subCleaned);
|
|
3261
|
+
joins.push(...subJoins);
|
|
3262
|
+
allJoins.push(...subAllJoins);
|
|
3263
|
+
continue;
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
if (!field || field.type !== "relationship" || typeof condition !== "object" || condition === null) {
|
|
3267
|
+
cleanedWhere[key] = condition;
|
|
3268
|
+
continue;
|
|
3269
|
+
}
|
|
3270
|
+
const condObj = condition;
|
|
3271
|
+
const condKeys = Object.keys(condObj);
|
|
3272
|
+
const hasOperatorKeys = condKeys.some((k) => VALID_OPERATORS.has(k));
|
|
3273
|
+
const hasNonOperatorKeys = condKeys.some((k) => !VALID_OPERATORS.has(k));
|
|
3274
|
+
if (hasOperatorKeys && hasNonOperatorKeys) {
|
|
3275
|
+
throw new ValidationError([
|
|
3276
|
+
{
|
|
3277
|
+
field: key,
|
|
3278
|
+
message: `Cannot mix operators and sub-field references on relationship field "${key}". Use either operators (e.g. { equals: 'id' }) or sub-field queries (e.g. { name: { equals: 'value' } }), not both.`
|
|
3279
|
+
}
|
|
3280
|
+
]);
|
|
3281
|
+
}
|
|
3282
|
+
if (!hasNonOperatorKeys) {
|
|
3283
|
+
cleanedWhere[key] = condition;
|
|
3284
|
+
continue;
|
|
3285
|
+
}
|
|
3286
|
+
const relField = field;
|
|
3287
|
+
let targetSlug;
|
|
3288
|
+
try {
|
|
3289
|
+
const targetConfig = relField.collection();
|
|
3290
|
+
if (targetConfig && typeof targetConfig === "object" && "slug" in targetConfig) {
|
|
3291
|
+
targetSlug = targetConfig.slug;
|
|
3292
|
+
}
|
|
3293
|
+
} catch {
|
|
3294
|
+
}
|
|
3295
|
+
if (!targetSlug) {
|
|
3296
|
+
throw new ValidationError([
|
|
3297
|
+
{
|
|
3298
|
+
field: key,
|
|
3299
|
+
message: `Cannot resolve target collection for relationship field "${key}".`
|
|
3300
|
+
}
|
|
3301
|
+
]);
|
|
3302
|
+
}
|
|
3303
|
+
const targetCollection = allCollections.find((c) => c.slug === targetSlug);
|
|
3304
|
+
if (!targetCollection) {
|
|
3305
|
+
throw new ValidationError([
|
|
3306
|
+
{
|
|
3307
|
+
field: key,
|
|
3308
|
+
message: `Target collection "${targetSlug}" not found for relationship field "${key}".`
|
|
3309
|
+
}
|
|
3310
|
+
]);
|
|
3311
|
+
}
|
|
3312
|
+
const subWhere = condition;
|
|
3313
|
+
const flattenedConditions = flattenWhereClause(subWhere);
|
|
3314
|
+
const targetTable = targetCollection.dbName ?? targetCollection.slug;
|
|
3315
|
+
const joinSpec = {
|
|
3316
|
+
targetTable,
|
|
3317
|
+
localField: key,
|
|
3318
|
+
targetField: "id",
|
|
3319
|
+
conditions: flattenedConditions,
|
|
3320
|
+
rawWhere: subWhere
|
|
3321
|
+
};
|
|
3322
|
+
joins.push(joinSpec);
|
|
3323
|
+
allJoins.push(joinSpec);
|
|
3324
|
+
}
|
|
3325
|
+
return {
|
|
3326
|
+
cleanedWhere: Object.keys(cleanedWhere).length > 0 ? cleanedWhere : void 0,
|
|
3327
|
+
joins,
|
|
3328
|
+
allJoins
|
|
3329
|
+
};
|
|
3330
|
+
}
|
|
3331
|
+
var SYSTEM_QUERYABLE_FIELDS = /* @__PURE__ */ new Set(["id", "createdAt", "updatedAt", "_status"]);
|
|
3332
|
+
async function validateWhereFields(where, fields, req) {
|
|
3333
|
+
if (!where)
|
|
3334
|
+
return;
|
|
3335
|
+
const dataFields = flattenDataFields(fields);
|
|
3336
|
+
const fieldMap = new Map(dataFields.map((f) => [f.name, f]));
|
|
3337
|
+
for (const fieldName of Object.keys(where)) {
|
|
3338
|
+
if (fieldName === "and" || fieldName === "or") {
|
|
3339
|
+
const subs = where[fieldName];
|
|
3340
|
+
if (Array.isArray(subs)) {
|
|
3341
|
+
for (const sub of subs) {
|
|
3342
|
+
if (typeof sub === "object" && sub !== null) {
|
|
3343
|
+
await validateWhereFields(sub, fields, req);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
continue;
|
|
3348
|
+
}
|
|
3349
|
+
const baseName = fieldName.includes(".") ? fieldName.split(".")[0] : fieldName;
|
|
3350
|
+
if (SYSTEM_QUERYABLE_FIELDS.has(baseName))
|
|
3351
|
+
continue;
|
|
3352
|
+
const field = fieldMap.get(baseName);
|
|
3353
|
+
if (!field) {
|
|
3354
|
+
throw new ValidationError([{ field: fieldName, message: `Unknown field: ${baseName}` }]);
|
|
3355
|
+
}
|
|
3356
|
+
if (!field.access?.read)
|
|
3357
|
+
continue;
|
|
3358
|
+
const allowed = await Promise.resolve(field.access.read({ req }));
|
|
3359
|
+
if (!allowed) {
|
|
3360
|
+
throw new AccessDeniedError("read", baseName);
|
|
3361
|
+
}
|
|
3362
|
+
}
|
|
3363
|
+
}
|
|
3364
|
+
async function validateSortField(sort, fields, req) {
|
|
3365
|
+
if (!sort)
|
|
3366
|
+
return;
|
|
3367
|
+
const fieldName = sort.startsWith("-") ? sort.slice(1) : sort;
|
|
3368
|
+
const baseName = fieldName.includes(".") ? fieldName.split(".")[0] : fieldName;
|
|
3369
|
+
const dataFields = flattenDataFields(fields);
|
|
3370
|
+
const field = dataFields.find((f) => f.name === baseName);
|
|
3371
|
+
if (!field?.access?.read)
|
|
3372
|
+
return;
|
|
3373
|
+
const allowed = await Promise.resolve(field.access.read({ req }));
|
|
3374
|
+
if (!allowed) {
|
|
3375
|
+
throw new AccessDeniedError("read", baseName);
|
|
3376
|
+
}
|
|
3377
|
+
}
|
|
3378
|
+
|
|
3379
|
+
// libs/server-core/src/lib/api-utils.ts
|
|
3380
|
+
function deepEqual(a, b) {
|
|
3381
|
+
if (a === b)
|
|
3382
|
+
return true;
|
|
3383
|
+
if (a == null || b == null)
|
|
3384
|
+
return false;
|
|
3385
|
+
if (typeof a !== "object" || typeof b !== "object")
|
|
3386
|
+
return false;
|
|
3387
|
+
if (Array.isArray(a)) {
|
|
3388
|
+
if (!Array.isArray(b) || a.length !== b.length)
|
|
3389
|
+
return false;
|
|
3390
|
+
return a.every((item, i) => deepEqual(item, b[i]));
|
|
3391
|
+
}
|
|
3392
|
+
if (Array.isArray(b))
|
|
3393
|
+
return false;
|
|
3394
|
+
const aKeys = Object.keys(a);
|
|
3395
|
+
const bKeys = Object.keys(b);
|
|
3396
|
+
if (aKeys.length !== bKeys.length)
|
|
3397
|
+
return false;
|
|
3398
|
+
const aRec = a;
|
|
3399
|
+
const bRec = b;
|
|
3400
|
+
return aKeys.every(
|
|
3401
|
+
(key) => Object.prototype.hasOwnProperty.call(bRec, key) && deepEqual(aRec[key], bRec[key])
|
|
3402
|
+
);
|
|
3403
|
+
}
|
|
3404
|
+
function stripTransientKeys(data) {
|
|
3405
|
+
const result = {};
|
|
3406
|
+
for (const [key, value] of Object.entries(data)) {
|
|
3407
|
+
if (!key.startsWith("_")) {
|
|
3408
|
+
result[key] = value;
|
|
3409
|
+
}
|
|
3410
|
+
}
|
|
3411
|
+
return result;
|
|
3412
|
+
}
|
|
3413
|
+
|
|
3414
|
+
// libs/server-core/src/lib/collection-access.ts
|
|
3415
|
+
async function checkSingleCollectionAdminAccess(collection, user) {
|
|
3416
|
+
const adminFn = collection.access?.admin;
|
|
3417
|
+
if (!adminFn) {
|
|
3418
|
+
return !!user;
|
|
3419
|
+
}
|
|
3420
|
+
const accessArgs = {
|
|
3421
|
+
req: { user }
|
|
3422
|
+
};
|
|
3423
|
+
return Promise.resolve(adminFn(accessArgs));
|
|
3424
|
+
}
|
|
3425
|
+
async function checkAccessFunction(accessFn, user, defaultIfUndefined) {
|
|
3426
|
+
if (!accessFn) {
|
|
3427
|
+
return defaultIfUndefined;
|
|
3428
|
+
}
|
|
3429
|
+
const accessArgs = {
|
|
3430
|
+
req: { user }
|
|
3431
|
+
};
|
|
3432
|
+
return Promise.resolve(accessFn(accessArgs));
|
|
3433
|
+
}
|
|
3434
|
+
async function getCollectionPermissions(config, user) {
|
|
3435
|
+
const results = await Promise.all(
|
|
3436
|
+
config.collections.map(async (collection) => {
|
|
3437
|
+
const isManaged = collection.managed === true;
|
|
3438
|
+
const [canAccess, canCreate, canRead, canUpdate, canDelete] = await Promise.all([
|
|
3439
|
+
checkSingleCollectionAdminAccess(collection, user),
|
|
3440
|
+
isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.create, user, !!user),
|
|
3441
|
+
checkAccessFunction(collection.access?.read, user, true),
|
|
3442
|
+
isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.update, user, !!user),
|
|
3443
|
+
isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.delete, user, !!user)
|
|
3444
|
+
]);
|
|
3445
|
+
return {
|
|
3446
|
+
slug: collection.slug,
|
|
3447
|
+
canAccess,
|
|
3448
|
+
canCreate,
|
|
3449
|
+
canRead,
|
|
3450
|
+
canUpdate,
|
|
3451
|
+
canDelete,
|
|
3452
|
+
...isManaged ? { managed: true } : {}
|
|
3453
|
+
};
|
|
3454
|
+
})
|
|
3455
|
+
);
|
|
3456
|
+
return results;
|
|
3457
|
+
}
|
|
3458
|
+
var ACCESS_OPS = ["read", "create", "update", "delete"];
|
|
3459
|
+
var ACCESS_DEFAULTS = {
|
|
3460
|
+
read: "public (anyone)",
|
|
3461
|
+
create: "any authenticated user",
|
|
3462
|
+
update: "any authenticated user",
|
|
3463
|
+
delete: "any authenticated user"
|
|
3464
|
+
};
|
|
3465
|
+
function warnInsecureDefaults(collections) {
|
|
3466
|
+
const logger = createLogger("Security");
|
|
3467
|
+
const warnings = [];
|
|
3468
|
+
for (const collection of collections) {
|
|
3469
|
+
if (collection.managed)
|
|
3470
|
+
continue;
|
|
3471
|
+
const missing = ACCESS_OPS.filter((op) => !collection.access?.[op]);
|
|
3472
|
+
if (missing.length > 0) {
|
|
3473
|
+
const details = missing.map((op) => `${op} (${ACCESS_DEFAULTS[op]})`).join(", ");
|
|
3474
|
+
const msg = `Collection "${collection.slug}" has no explicit access control for: ${details}. Define access functions to restrict.`;
|
|
3475
|
+
logger.warn(msg);
|
|
3476
|
+
warnings.push(msg);
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
return warnings;
|
|
3480
|
+
}
|
|
3481
|
+
|
|
3045
3482
|
// libs/server-core/src/lib/momentum-api.ts
|
|
3046
3483
|
var momentumApiInstance = null;
|
|
3047
3484
|
function initializeMomentumAPI(config) {
|
|
@@ -3049,6 +3486,7 @@ function initializeMomentumAPI(config) {
|
|
|
3049
3486
|
createLogger("API").warn("Already initialized, returning existing instance");
|
|
3050
3487
|
return momentumApiInstance;
|
|
3051
3488
|
}
|
|
3489
|
+
warnInsecureDefaults(config.collections);
|
|
3052
3490
|
momentumApiInstance = new MomentumAPIImpl(config);
|
|
3053
3491
|
return momentumApiInstance;
|
|
3054
3492
|
}
|
|
@@ -3106,70 +3544,6 @@ var MomentumAPIImpl = class _MomentumAPIImpl {
|
|
|
3106
3544
|
return { ...this.context };
|
|
3107
3545
|
}
|
|
3108
3546
|
};
|
|
3109
|
-
function deepEqual(a, b) {
|
|
3110
|
-
if (a === b)
|
|
3111
|
-
return true;
|
|
3112
|
-
if (a == null || b == null)
|
|
3113
|
-
return false;
|
|
3114
|
-
if (typeof a !== "object" || typeof b !== "object")
|
|
3115
|
-
return false;
|
|
3116
|
-
if (Array.isArray(a)) {
|
|
3117
|
-
if (!Array.isArray(b) || a.length !== b.length)
|
|
3118
|
-
return false;
|
|
3119
|
-
return a.every((item, i) => deepEqual(item, b[i]));
|
|
3120
|
-
}
|
|
3121
|
-
if (Array.isArray(b))
|
|
3122
|
-
return false;
|
|
3123
|
-
const aKeys = Object.keys(a);
|
|
3124
|
-
const bKeys = Object.keys(b);
|
|
3125
|
-
if (aKeys.length !== bKeys.length)
|
|
3126
|
-
return false;
|
|
3127
|
-
const aRec = a;
|
|
3128
|
-
const bRec = b;
|
|
3129
|
-
return aKeys.every(
|
|
3130
|
-
(key) => Object.prototype.hasOwnProperty.call(bRec, key) && deepEqual(aRec[key], bRec[key])
|
|
3131
|
-
);
|
|
3132
|
-
}
|
|
3133
|
-
function stripTransientKeys(data) {
|
|
3134
|
-
const result = {};
|
|
3135
|
-
for (const [key, value] of Object.entries(data)) {
|
|
3136
|
-
if (!key.startsWith("_")) {
|
|
3137
|
-
result[key] = value;
|
|
3138
|
-
}
|
|
3139
|
-
}
|
|
3140
|
-
return result;
|
|
3141
|
-
}
|
|
3142
|
-
var COMPARISON_OPS = ["gt", "gte", "lt", "lte"];
|
|
3143
|
-
function flattenWhereClause(where) {
|
|
3144
|
-
if (!where)
|
|
3145
|
-
return {};
|
|
3146
|
-
const result = {};
|
|
3147
|
-
for (const [field, condition] of Object.entries(where)) {
|
|
3148
|
-
if (typeof condition !== "object" || condition === null) {
|
|
3149
|
-
result[field] = condition;
|
|
3150
|
-
continue;
|
|
3151
|
-
}
|
|
3152
|
-
const condObj = condition;
|
|
3153
|
-
if ("equals" in condObj) {
|
|
3154
|
-
result[field] = condObj["equals"];
|
|
3155
|
-
continue;
|
|
3156
|
-
}
|
|
3157
|
-
const ops = {};
|
|
3158
|
-
let hasComparisonOp = false;
|
|
3159
|
-
for (const op of COMPARISON_OPS) {
|
|
3160
|
-
if (op in condObj) {
|
|
3161
|
-
ops[`$${op}`] = condObj[op];
|
|
3162
|
-
hasComparisonOp = true;
|
|
3163
|
-
}
|
|
3164
|
-
}
|
|
3165
|
-
if (hasComparisonOp) {
|
|
3166
|
-
result[field] = ops;
|
|
3167
|
-
} else {
|
|
3168
|
-
result[field] = condition;
|
|
3169
|
-
}
|
|
3170
|
-
}
|
|
3171
|
-
return result;
|
|
3172
|
-
}
|
|
3173
3547
|
var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
3174
3548
|
constructor(slug2, collectionConfig, adapter, context, allCollections = []) {
|
|
3175
3549
|
this.slug = slug2;
|
|
@@ -3180,11 +3554,37 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3180
3554
|
}
|
|
3181
3555
|
async find(options = {}) {
|
|
3182
3556
|
await this.checkAccess("read");
|
|
3557
|
+
if (!this.context.overrideAccess) {
|
|
3558
|
+
await validateWhereFields(
|
|
3559
|
+
options.where,
|
|
3560
|
+
this.collectionConfig.fields,
|
|
3561
|
+
this.buildRequestContext()
|
|
3562
|
+
);
|
|
3563
|
+
await validateSortField(
|
|
3564
|
+
options.sort,
|
|
3565
|
+
this.collectionConfig.fields,
|
|
3566
|
+
this.buildRequestContext()
|
|
3567
|
+
);
|
|
3568
|
+
}
|
|
3183
3569
|
await this.runBeforeReadHooks();
|
|
3184
|
-
const
|
|
3185
|
-
const
|
|
3570
|
+
const rawLimit = options.limit ?? 10;
|
|
3571
|
+
const rawPage = options.page ?? 1;
|
|
3572
|
+
const limit = Math.max(
|
|
3573
|
+
1,
|
|
3574
|
+
Math.min(Number.isFinite(rawLimit) ? Math.floor(rawLimit) : 10, MAX_PAGE_LIMIT)
|
|
3575
|
+
);
|
|
3576
|
+
const page = Math.max(
|
|
3577
|
+
1,
|
|
3578
|
+
Math.min(Number.isFinite(rawPage) ? Math.floor(rawPage) : 1, MAX_PAGE)
|
|
3579
|
+
);
|
|
3186
3580
|
const { depth: _depth, where, withDeleted: _wd, onlyDeleted: _od, ...queryOptions } = options;
|
|
3187
|
-
const
|
|
3581
|
+
const { cleanedWhere, joins, allJoins } = extractRelationshipJoins(
|
|
3582
|
+
where,
|
|
3583
|
+
this.collectionConfig.fields,
|
|
3584
|
+
this.allCollections
|
|
3585
|
+
);
|
|
3586
|
+
await this.enforceWhereLimits(cleanedWhere, allJoins);
|
|
3587
|
+
const whereParams = flattenWhereClause(cleanedWhere);
|
|
3188
3588
|
const softDeleteField = getSoftDeleteField(this.collectionConfig);
|
|
3189
3589
|
if (softDeleteField && !options.withDeleted && !options.onlyDeleted) {
|
|
3190
3590
|
whereParams[softDeleteField] = null;
|
|
@@ -3209,6 +3609,9 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3209
3609
|
limit,
|
|
3210
3610
|
page
|
|
3211
3611
|
};
|
|
3612
|
+
if (joins.length > 0) {
|
|
3613
|
+
query["$joins"] = joins.map(({ rawWhere: _rw, ...rest }) => rest);
|
|
3614
|
+
}
|
|
3212
3615
|
const docs = await this.adapter.find(this.slug, query);
|
|
3213
3616
|
let afterHookDocs = await this.processAfterReadHooks(docs);
|
|
3214
3617
|
const MAX_RELATIONSHIP_DEPTH = 10;
|
|
@@ -3232,14 +3635,15 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3232
3635
|
);
|
|
3233
3636
|
}
|
|
3234
3637
|
const countQuery = { ...queryOptions, ...whereParams };
|
|
3638
|
+
if (joins.length > 0) {
|
|
3639
|
+
countQuery["$joins"] = joins.map(({ rawWhere: _rw, ...rest }) => rest);
|
|
3640
|
+
}
|
|
3235
3641
|
delete countQuery["limit"];
|
|
3236
3642
|
delete countQuery["page"];
|
|
3237
|
-
const
|
|
3238
|
-
|
|
3239
|
-
limit: 0
|
|
3240
|
-
|
|
3241
|
-
});
|
|
3242
|
-
const totalDocs = allDocs.length;
|
|
3643
|
+
const totalDocs = this.adapter.count ? await this.adapter.count(this.slug, countQuery) : (
|
|
3644
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Adapter returns Record<string, unknown>[], safe cast to T[]
|
|
3645
|
+
(await this.adapter.find(this.slug, { ...countQuery, limit: 0 })).length
|
|
3646
|
+
);
|
|
3243
3647
|
const totalPages = Math.ceil(totalDocs / limit) || 1;
|
|
3244
3648
|
return {
|
|
3245
3649
|
docs: afterHookDocs,
|
|
@@ -3476,7 +3880,12 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3476
3880
|
async restore(id) {
|
|
3477
3881
|
const softDeleteField = getSoftDeleteField(this.collectionConfig);
|
|
3478
3882
|
if (!softDeleteField) {
|
|
3479
|
-
throw new
|
|
3883
|
+
throw new ValidationError([
|
|
3884
|
+
{
|
|
3885
|
+
field: "_softDelete",
|
|
3886
|
+
message: `Collection "${this.slug}" does not have soft delete enabled`
|
|
3887
|
+
}
|
|
3888
|
+
]);
|
|
3480
3889
|
}
|
|
3481
3890
|
const restoreAccessFn = this.collectionConfig.access?.restore;
|
|
3482
3891
|
if (restoreAccessFn) {
|
|
@@ -3534,20 +3943,43 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3534
3943
|
if (softDeleteField) {
|
|
3535
3944
|
softDeleteFilter[softDeleteField] = null;
|
|
3536
3945
|
}
|
|
3946
|
+
const defaultWhereFilter = {};
|
|
3947
|
+
if (this.collectionConfig.defaultWhere) {
|
|
3948
|
+
const constraints = this.collectionConfig.defaultWhere(this.buildRequestContext());
|
|
3949
|
+
if (constraints) {
|
|
3950
|
+
Object.assign(defaultWhereFilter, constraints);
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3537
3953
|
let docs;
|
|
3538
3954
|
if (this.adapter.search) {
|
|
3539
3955
|
docs = await this.adapter.search(this.slug, query, searchFields, { limit, page });
|
|
3540
3956
|
if (softDeleteField) {
|
|
3541
3957
|
docs = docs.filter((doc) => !doc[softDeleteField]);
|
|
3542
3958
|
}
|
|
3959
|
+
if (Object.keys(defaultWhereFilter).length > 0) {
|
|
3960
|
+
docs = docs.filter((doc) => {
|
|
3961
|
+
return Object.entries(defaultWhereFilter).every(([key, value]) => {
|
|
3962
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
3963
|
+
return JSON.stringify(doc[key]) === JSON.stringify(value);
|
|
3964
|
+
}
|
|
3965
|
+
return doc[key] === value;
|
|
3966
|
+
});
|
|
3967
|
+
});
|
|
3968
|
+
}
|
|
3543
3969
|
} else {
|
|
3544
|
-
docs = await this.adapter.find(this.slug, {
|
|
3970
|
+
docs = await this.adapter.find(this.slug, {
|
|
3971
|
+
...softDeleteFilter,
|
|
3972
|
+
...defaultWhereFilter,
|
|
3973
|
+
limit,
|
|
3974
|
+
page
|
|
3975
|
+
});
|
|
3545
3976
|
}
|
|
3546
3977
|
const resolvedDocs = docs;
|
|
3547
|
-
const
|
|
3978
|
+
const afterHookDocs = await this.processAfterReadHooks(resolvedDocs);
|
|
3979
|
+
const totalDocs = afterHookDocs.length;
|
|
3548
3980
|
const totalPages = Math.max(1, Math.ceil(totalDocs / limit));
|
|
3549
3981
|
return {
|
|
3550
|
-
docs:
|
|
3982
|
+
docs: afterHookDocs,
|
|
3551
3983
|
totalDocs,
|
|
3552
3984
|
totalPages,
|
|
3553
3985
|
page,
|
|
@@ -3560,13 +3992,40 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3560
3992
|
}
|
|
3561
3993
|
async count(where, options) {
|
|
3562
3994
|
await this.checkAccess("read");
|
|
3563
|
-
|
|
3995
|
+
if (!this.context.overrideAccess) {
|
|
3996
|
+
await validateWhereFields(where, this.collectionConfig.fields, this.buildRequestContext());
|
|
3997
|
+
}
|
|
3998
|
+
const { cleanedWhere, joins, allJoins } = extractRelationshipJoins(
|
|
3999
|
+
where,
|
|
4000
|
+
this.collectionConfig.fields,
|
|
4001
|
+
this.allCollections
|
|
4002
|
+
);
|
|
4003
|
+
await this.enforceWhereLimits(cleanedWhere, allJoins);
|
|
4004
|
+
const whereParams = flattenWhereClause(cleanedWhere);
|
|
3564
4005
|
const softDeleteField = getSoftDeleteField(this.collectionConfig);
|
|
3565
4006
|
if (softDeleteField && !options?.withDeleted) {
|
|
3566
4007
|
whereParams[softDeleteField] = null;
|
|
3567
4008
|
}
|
|
3568
|
-
|
|
3569
|
-
|
|
4009
|
+
if (this.collectionConfig.defaultWhere) {
|
|
4010
|
+
const constraints = this.collectionConfig.defaultWhere(this.buildRequestContext());
|
|
4011
|
+
if (constraints) {
|
|
4012
|
+
Object.assign(whereParams, constraints);
|
|
4013
|
+
}
|
|
4014
|
+
}
|
|
4015
|
+
if (hasVersionDrafts(this.collectionConfig) && !this.context.overrideAccess) {
|
|
4016
|
+
const canSeeDrafts = await this.canReadDrafts();
|
|
4017
|
+
if (!canSeeDrafts) {
|
|
4018
|
+
whereParams["_status"] = "published";
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
const query = { ...whereParams };
|
|
4022
|
+
if (joins.length > 0) {
|
|
4023
|
+
query["$joins"] = joins.map(({ rawWhere: _rw, ...rest }) => rest);
|
|
4024
|
+
}
|
|
4025
|
+
if (this.adapter.count) {
|
|
4026
|
+
return this.adapter.count(this.slug, query);
|
|
4027
|
+
}
|
|
4028
|
+
const docs = await this.adapter.find(this.slug, { ...query, limit: 0 });
|
|
3570
4029
|
return docs.length;
|
|
3571
4030
|
}
|
|
3572
4031
|
async batchCreate(items) {
|
|
@@ -3726,6 +4185,46 @@ var CollectionOperationsImpl = class _CollectionOperationsImpl {
|
|
|
3726
4185
|
return false;
|
|
3727
4186
|
}
|
|
3728
4187
|
}
|
|
4188
|
+
async enforceWhereLimits(cleanedWhere, joins) {
|
|
4189
|
+
if (joins.length > MAX_JOINS) {
|
|
4190
|
+
throw new ValidationError([
|
|
4191
|
+
{
|
|
4192
|
+
field: "where",
|
|
4193
|
+
message: `Number of relationship joins (${joins.length}) exceeds maximum of ${MAX_JOINS}.`
|
|
4194
|
+
}
|
|
4195
|
+
]);
|
|
4196
|
+
}
|
|
4197
|
+
const mainCount = cleanedWhere ? countWhereConditions(cleanedWhere) : 0;
|
|
4198
|
+
const joinCount = joins.reduce((sum, j) => sum + countWhereConditions(j.rawWhere), 0);
|
|
4199
|
+
const totalConditions = mainCount + joinCount;
|
|
4200
|
+
if (totalConditions > MAX_WHERE_CONDITIONS) {
|
|
4201
|
+
throw new ValidationError([
|
|
4202
|
+
{
|
|
4203
|
+
field: "where",
|
|
4204
|
+
message: `Where clause exceeds maximum of ${MAX_WHERE_CONDITIONS} conditions (got ${totalConditions} across main query and ${joins.length} join(s)).`
|
|
4205
|
+
}
|
|
4206
|
+
]);
|
|
4207
|
+
}
|
|
4208
|
+
if (!this.context.overrideAccess) {
|
|
4209
|
+
for (const join2 of joins) {
|
|
4210
|
+
const targetCol = this.allCollections.find(
|
|
4211
|
+
(c) => (c.dbName ?? c.slug) === join2.targetTable
|
|
4212
|
+
);
|
|
4213
|
+
if (targetCol) {
|
|
4214
|
+
const collectionAccessFn = targetCol.access?.read;
|
|
4215
|
+
if (collectionAccessFn) {
|
|
4216
|
+
const allowed = await Promise.resolve(
|
|
4217
|
+
collectionAccessFn({ req: this.buildRequestContext() })
|
|
4218
|
+
);
|
|
4219
|
+
if (!allowed) {
|
|
4220
|
+
throw new AccessDeniedError("read", targetCol.slug);
|
|
4221
|
+
}
|
|
4222
|
+
}
|
|
4223
|
+
await validateWhereFields(join2.rawWhere, targetCol.fields, this.buildRequestContext());
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
3729
4228
|
buildRequestContext() {
|
|
3730
4229
|
return {
|
|
3731
4230
|
user: this.context.user
|
|
@@ -4134,7 +4633,14 @@ function createMomentumHandlers(config) {
|
|
|
4134
4633
|
});
|
|
4135
4634
|
return {
|
|
4136
4635
|
docs: result.docs,
|
|
4137
|
-
totalDocs: result.totalDocs
|
|
4636
|
+
totalDocs: result.totalDocs,
|
|
4637
|
+
totalPages: result.totalPages,
|
|
4638
|
+
page: result.page,
|
|
4639
|
+
limit: result.limit,
|
|
4640
|
+
hasNextPage: result.hasNextPage,
|
|
4641
|
+
hasPrevPage: result.hasPrevPage,
|
|
4642
|
+
nextPage: result.nextPage,
|
|
4643
|
+
prevPage: result.prevPage
|
|
4138
4644
|
};
|
|
4139
4645
|
} catch (error) {
|
|
4140
4646
|
return handleError(error);
|
|
@@ -4222,7 +4728,14 @@ function createMomentumHandlers(config) {
|
|
|
4222
4728
|
const result = await api.collection(request.collectionSlug).search(q, { fields, limit, page });
|
|
4223
4729
|
return {
|
|
4224
4730
|
docs: result.docs,
|
|
4225
|
-
totalDocs: result.totalDocs
|
|
4731
|
+
totalDocs: result.totalDocs,
|
|
4732
|
+
totalPages: result.totalPages,
|
|
4733
|
+
page: result.page,
|
|
4734
|
+
limit: result.limit,
|
|
4735
|
+
hasNextPage: result.hasNextPage,
|
|
4736
|
+
hasPrevPage: result.hasPrevPage,
|
|
4737
|
+
nextPage: result.nextPage,
|
|
4738
|
+
prevPage: result.prevPage
|
|
4226
4739
|
};
|
|
4227
4740
|
} catch (error) {
|
|
4228
4741
|
return handleError(error);
|
|
@@ -4287,51 +4800,6 @@ function handleError(error) {
|
|
|
4287
4800
|
return { error: "Unknown error", status: 500 };
|
|
4288
4801
|
}
|
|
4289
4802
|
|
|
4290
|
-
// libs/server-core/src/lib/collection-access.ts
|
|
4291
|
-
async function checkSingleCollectionAdminAccess(collection, user) {
|
|
4292
|
-
const adminFn = collection.access?.admin;
|
|
4293
|
-
if (!adminFn) {
|
|
4294
|
-
return !!user;
|
|
4295
|
-
}
|
|
4296
|
-
const accessArgs = {
|
|
4297
|
-
req: { user }
|
|
4298
|
-
};
|
|
4299
|
-
return Promise.resolve(adminFn(accessArgs));
|
|
4300
|
-
}
|
|
4301
|
-
async function checkAccessFunction(accessFn, user, defaultIfUndefined) {
|
|
4302
|
-
if (!accessFn) {
|
|
4303
|
-
return defaultIfUndefined;
|
|
4304
|
-
}
|
|
4305
|
-
const accessArgs = {
|
|
4306
|
-
req: { user }
|
|
4307
|
-
};
|
|
4308
|
-
return Promise.resolve(accessFn(accessArgs));
|
|
4309
|
-
}
|
|
4310
|
-
async function getCollectionPermissions(config, user) {
|
|
4311
|
-
const results = await Promise.all(
|
|
4312
|
-
config.collections.map(async (collection) => {
|
|
4313
|
-
const isManaged = collection.managed === true;
|
|
4314
|
-
const [canAccess, canCreate, canRead, canUpdate, canDelete] = await Promise.all([
|
|
4315
|
-
checkSingleCollectionAdminAccess(collection, user),
|
|
4316
|
-
isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.create, user, !!user),
|
|
4317
|
-
checkAccessFunction(collection.access?.read, user, true),
|
|
4318
|
-
isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.update, user, !!user),
|
|
4319
|
-
isManaged ? Promise.resolve(false) : checkAccessFunction(collection.access?.delete, user, !!user)
|
|
4320
|
-
]);
|
|
4321
|
-
return {
|
|
4322
|
-
slug: collection.slug,
|
|
4323
|
-
canAccess,
|
|
4324
|
-
canCreate,
|
|
4325
|
-
canRead,
|
|
4326
|
-
canUpdate,
|
|
4327
|
-
canDelete,
|
|
4328
|
-
...isManaged ? { managed: true } : {}
|
|
4329
|
-
};
|
|
4330
|
-
})
|
|
4331
|
-
);
|
|
4332
|
-
return results;
|
|
4333
|
-
}
|
|
4334
|
-
|
|
4335
4803
|
// libs/server-core/src/lib/seeding/seed-tracker.ts
|
|
4336
4804
|
var import_node_crypto = require("node:crypto");
|
|
4337
4805
|
function createSeedTracker(adapter) {
|
|
@@ -5364,7 +5832,7 @@ function checkDepth(node, currentDepth, maxDepth, context) {
|
|
|
5364
5832
|
}
|
|
5365
5833
|
}
|
|
5366
5834
|
}
|
|
5367
|
-
async function executeGraphQL(schema, requestBody, context) {
|
|
5835
|
+
async function executeGraphQL(schema, requestBody, context, options) {
|
|
5368
5836
|
if (!requestBody.query) {
|
|
5369
5837
|
return {
|
|
5370
5838
|
status: 400,
|
|
@@ -5380,6 +5848,17 @@ async function executeGraphQL(schema, requestBody, context) {
|
|
|
5380
5848
|
body: { errors: [{ message: "Query parsing failed" }] }
|
|
5381
5849
|
};
|
|
5382
5850
|
}
|
|
5851
|
+
if (options?.readOnly) {
|
|
5852
|
+
const hasMutation = document.definitions.some(
|
|
5853
|
+
(def) => def.kind === "OperationDefinition" && def.operation === "mutation"
|
|
5854
|
+
);
|
|
5855
|
+
if (hasMutation) {
|
|
5856
|
+
return {
|
|
5857
|
+
status: 405,
|
|
5858
|
+
body: { errors: [{ message: "Mutations are not allowed via GET requests" }] }
|
|
5859
|
+
};
|
|
5860
|
+
}
|
|
5861
|
+
}
|
|
5383
5862
|
const depthErrors = (0, import_graphql3.validate)(schema, document, [depthLimitRule(MAX_QUERY_DEPTH)]);
|
|
5384
5863
|
if (depthErrors.length > 0) {
|
|
5385
5864
|
return {
|
|
@@ -6799,24 +7278,29 @@ function momentumApiMiddleware(config) {
|
|
|
6799
7278
|
const router = (0, import_express.Router)();
|
|
6800
7279
|
const handlers = createMomentumHandlers(config);
|
|
6801
7280
|
router.use((0, import_express.json)());
|
|
7281
|
+
router.use((_req, res, next) => {
|
|
7282
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
7283
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
7284
|
+
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
7285
|
+
res.setHeader("X-Permitted-Cross-Domain-Policies", "none");
|
|
7286
|
+
next();
|
|
7287
|
+
});
|
|
6802
7288
|
router.use((req, res, next) => {
|
|
6803
7289
|
const corsConfig = config.server?.cors ?? {};
|
|
6804
7290
|
const origins = Array.isArray(corsConfig.origin) ? corsConfig.origin : corsConfig.origin ? [corsConfig.origin] : [];
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
7291
|
+
if (origins.length === 0 || origins.includes("*")) {
|
|
7292
|
+
if (process.env["NODE_ENV"] === "production") {
|
|
7293
|
+
createLogger("CORS").warn(
|
|
7294
|
+
'Origin is set to "*" in production. Configure explicit origins via config.server.cors.origin.'
|
|
7295
|
+
);
|
|
7296
|
+
}
|
|
7297
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
6808
7298
|
} else {
|
|
6809
7299
|
const requestOrigin = req.headers["origin"] ?? "";
|
|
6810
|
-
allowOrigin = origins.includes(requestOrigin) ? requestOrigin : origins[0];
|
|
6811
|
-
}
|
|
6812
|
-
if (allowOrigin === "*" && process.env["NODE_ENV"] === "production") {
|
|
6813
|
-
createLogger("CORS").warn(
|
|
6814
|
-
'Origin is set to "*" in production. Configure explicit origins via config.server.cors.origin.'
|
|
6815
|
-
);
|
|
6816
|
-
}
|
|
6817
|
-
res.setHeader("Access-Control-Allow-Origin", allowOrigin);
|
|
6818
|
-
if (allowOrigin !== "*") {
|
|
6819
7300
|
res.setHeader("Vary", "Origin");
|
|
7301
|
+
if (origins.includes(requestOrigin)) {
|
|
7302
|
+
res.setHeader("Access-Control-Allow-Origin", requestOrigin);
|
|
7303
|
+
}
|
|
6820
7304
|
}
|
|
6821
7305
|
res.setHeader(
|
|
6822
7306
|
"Access-Control-Allow-Methods",
|
|
@@ -6899,7 +7383,12 @@ function momentumApiMiddleware(config) {
|
|
|
6899
7383
|
res.status(400).json({ errors: [{ message: "Query parameter required" }] });
|
|
6900
7384
|
return;
|
|
6901
7385
|
}
|
|
6902
|
-
const result = await executeGraphQL(
|
|
7386
|
+
const result = await executeGraphQL(
|
|
7387
|
+
graphqlSchema,
|
|
7388
|
+
{ query: queryParam },
|
|
7389
|
+
{ user },
|
|
7390
|
+
{ readOnly: true }
|
|
7391
|
+
);
|
|
6903
7392
|
res.status(result.status).json(result.body);
|
|
6904
7393
|
});
|
|
6905
7394
|
router.get("/globals/:slug", async (req, res) => {
|
|
@@ -7232,7 +7721,14 @@ function momentumApiMiddleware(config) {
|
|
|
7232
7721
|
res.json({ status });
|
|
7233
7722
|
} catch (error) {
|
|
7234
7723
|
const message = sanitizeErrorMessage(error, "Unknown error");
|
|
7235
|
-
|
|
7724
|
+
let status = 500;
|
|
7725
|
+
if (error instanceof Error) {
|
|
7726
|
+
if (error.name === "AccessDeniedError")
|
|
7727
|
+
status = 403;
|
|
7728
|
+
else if (error.name === "DocumentNotFoundError")
|
|
7729
|
+
status = 404;
|
|
7730
|
+
}
|
|
7731
|
+
res.status(status).json({ error: status === 403 ? "Access denied" : "Failed to get status", message });
|
|
7236
7732
|
}
|
|
7237
7733
|
});
|
|
7238
7734
|
async function handlePreviewRequest(req, res) {
|
|
@@ -7421,8 +7917,11 @@ function momentumApiMiddleware(config) {
|
|
|
7421
7917
|
return { docs, totalDocs: docs.length };
|
|
7422
7918
|
},
|
|
7423
7919
|
findById: (slug2, id) => txAdapter.findById(slug2, id),
|
|
7424
|
-
count: async (slug2) => {
|
|
7425
|
-
|
|
7920
|
+
count: async (slug2, where) => {
|
|
7921
|
+
if (txAdapter.count) {
|
|
7922
|
+
return txAdapter.count(slug2, where ?? {});
|
|
7923
|
+
}
|
|
7924
|
+
const docs = await txAdapter.find(slug2, where ?? {});
|
|
7426
7925
|
return docs.length;
|
|
7427
7926
|
},
|
|
7428
7927
|
create: (slug2, data) => txAdapter.create(slug2, data),
|
|
@@ -7464,7 +7963,7 @@ function momentumApiMiddleware(config) {
|
|
|
7464
7963
|
throw err;
|
|
7465
7964
|
}
|
|
7466
7965
|
},
|
|
7467
|
-
count: (slug2) => ctxApi.collection(slug2).count(),
|
|
7966
|
+
count: (slug2, where) => ctxApi.collection(slug2).count(where),
|
|
7468
7967
|
create: async (slug2, data) => {
|
|
7469
7968
|
return await ctxApi.collection(slug2).create(data);
|
|
7470
7969
|
},
|
|
@@ -7487,6 +7986,8 @@ function momentumApiMiddleware(config) {
|
|
|
7487
7986
|
collection,
|
|
7488
7987
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Express body is parsed JSON
|
|
7489
7988
|
body: req.body,
|
|
7989
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Express query params
|
|
7990
|
+
params: req.query,
|
|
7490
7991
|
query: buildQueryHelper(contextApi)
|
|
7491
7992
|
});
|
|
7492
7993
|
res.status(result.status).json(result.body);
|
|
@@ -7554,7 +8055,15 @@ function momentumApiMiddleware(config) {
|
|
|
7554
8055
|
}
|
|
7555
8056
|
} catch (error) {
|
|
7556
8057
|
const message = sanitizeErrorMessage(error, "Batch operation failed");
|
|
7557
|
-
|
|
8058
|
+
let status = 500;
|
|
8059
|
+
if (error instanceof Error) {
|
|
8060
|
+
if (error.name === "ValidationError")
|
|
8061
|
+
status = 400;
|
|
8062
|
+
else if (error.name === "DocumentNotFoundError")
|
|
8063
|
+
status = 404;
|
|
8064
|
+
else if (error.name === "AccessDeniedError")
|
|
8065
|
+
status = 403;
|
|
8066
|
+
}
|
|
7558
8067
|
res.status(status).json({ error: message });
|
|
7559
8068
|
}
|
|
7560
8069
|
});
|
|
@@ -7613,7 +8122,16 @@ function momentumApiMiddleware(config) {
|
|
|
7613
8122
|
}
|
|
7614
8123
|
} catch (error) {
|
|
7615
8124
|
const message = sanitizeErrorMessage(error, "Export failed");
|
|
7616
|
-
|
|
8125
|
+
let status = 500;
|
|
8126
|
+
if (error instanceof Error) {
|
|
8127
|
+
if (error.name === "AccessDeniedError")
|
|
8128
|
+
status = 403;
|
|
8129
|
+
else if (error.name === "ValidationError")
|
|
8130
|
+
status = 400;
|
|
8131
|
+
else if (error.name === "DocumentNotFoundError")
|
|
8132
|
+
status = 404;
|
|
8133
|
+
}
|
|
8134
|
+
res.status(status).json({ error: message });
|
|
7617
8135
|
}
|
|
7618
8136
|
});
|
|
7619
8137
|
router.post("/:collection/import", async (req, res) => {
|
|
@@ -8411,6 +8929,7 @@ function createSetupMiddleware(config) {
|
|
|
8411
8929
|
const dbConfig = isLegacyConfig(config) ? { type: "sqlite", database: config.database } : config.db;
|
|
8412
8930
|
const { auth } = config;
|
|
8413
8931
|
const router = (0, import_express3.Router)();
|
|
8932
|
+
let setupInProgress = false;
|
|
8414
8933
|
router.get("/setup/status", async (_req, res) => {
|
|
8415
8934
|
let hasUsers;
|
|
8416
8935
|
if (dbConfig.type === "sqlite") {
|
|
@@ -8425,6 +8944,13 @@ function createSetupMiddleware(config) {
|
|
|
8425
8944
|
res.json(status);
|
|
8426
8945
|
});
|
|
8427
8946
|
router.post("/setup/create-admin", async (req, res) => {
|
|
8947
|
+
if (setupInProgress) {
|
|
8948
|
+
res.status(409).json({
|
|
8949
|
+
error: { message: "Setup is already in progress." }
|
|
8950
|
+
});
|
|
8951
|
+
return;
|
|
8952
|
+
}
|
|
8953
|
+
setupInProgress = true;
|
|
8428
8954
|
try {
|
|
8429
8955
|
let hasUsers;
|
|
8430
8956
|
if (dbConfig.type === "sqlite") {
|
|
@@ -8501,9 +9027,10 @@ function createSetupMiddleware(config) {
|
|
|
8501
9027
|
updatedAt: userRow.updatedAt
|
|
8502
9028
|
}
|
|
8503
9029
|
});
|
|
8504
|
-
} catch
|
|
8505
|
-
|
|
8506
|
-
|
|
9030
|
+
} catch {
|
|
9031
|
+
res.status(500).json({ error: { message: "Failed to create admin user" } });
|
|
9032
|
+
} finally {
|
|
9033
|
+
setupInProgress = false;
|
|
8507
9034
|
}
|
|
8508
9035
|
});
|
|
8509
9036
|
return router;
|
|
@@ -8579,9 +9106,8 @@ function createApiKeyRoutes(config) {
|
|
|
8579
9106
|
return;
|
|
8580
9107
|
}
|
|
8581
9108
|
const role = body.role ?? "user";
|
|
8582
|
-
|
|
8583
|
-
|
|
8584
|
-
res.status(400).json({ error: `Invalid role. Must be one of: ${validRoles.join(", ")}` });
|
|
9109
|
+
if (!ROLE_HIERARCHY.includes(role)) {
|
|
9110
|
+
res.status(400).json({ error: `Invalid role. Must be one of: ${ROLE_HIERARCHY.join(", ")}` });
|
|
8585
9111
|
return;
|
|
8586
9112
|
}
|
|
8587
9113
|
const userRoleIndex = ROLE_HIERARCHY.indexOf(user.role ?? "viewer");
|
|
@@ -8647,7 +9173,7 @@ function createApiKeyRoutes(config) {
|
|
|
8647
9173
|
return;
|
|
8648
9174
|
}
|
|
8649
9175
|
if (existingKey.createdBy !== user.id) {
|
|
8650
|
-
res.status(
|
|
9176
|
+
res.status(404).json({ error: "API key not found" });
|
|
8651
9177
|
return;
|
|
8652
9178
|
}
|
|
8653
9179
|
}
|
|
@@ -8918,6 +9444,20 @@ function createHealthMiddleware(options = {}) {
|
|
|
8918
9444
|
return router;
|
|
8919
9445
|
}
|
|
8920
9446
|
|
|
9447
|
+
// libs/server-express/src/lib/rate-limit-middleware.ts
|
|
9448
|
+
function createRateLimitMiddleware(limiter) {
|
|
9449
|
+
return (req, res, next) => {
|
|
9450
|
+
const forwarded = req.headers["x-forwarded-for"];
|
|
9451
|
+
const key = (forwarded ? forwarded.toString().split(",")[0].trim() : req.ip) ?? "unknown";
|
|
9452
|
+
if (!limiter.isAllowed(key)) {
|
|
9453
|
+
res.setHeader("Retry-After", "60");
|
|
9454
|
+
res.status(429).json({ error: "Too many requests. Please try again later." });
|
|
9455
|
+
return;
|
|
9456
|
+
}
|
|
9457
|
+
next();
|
|
9458
|
+
};
|
|
9459
|
+
}
|
|
9460
|
+
|
|
8921
9461
|
// libs/server-express/src/lib/create-momentum-server.ts
|
|
8922
9462
|
var import_express6 = __toESM(require("express"));
|
|
8923
9463
|
async function createMomentumServer(options) {
|
|
@@ -9004,6 +9544,7 @@ async function createMomentumServer(options) {
|
|
|
9004
9544
|
createMomentumServer,
|
|
9005
9545
|
createOpenAPIMiddleware,
|
|
9006
9546
|
createProtectMiddleware,
|
|
9547
|
+
createRateLimitMiddleware,
|
|
9007
9548
|
createSessionResolverMiddleware,
|
|
9008
9549
|
createSetupMiddleware,
|
|
9009
9550
|
getPluginMiddleware,
|