@easy-cms/core 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +98 -1
- package/dist/index.js +358 -48
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -245,6 +245,25 @@ interface EmailAdapter {
|
|
|
245
245
|
send(message: EmailMessage & {
|
|
246
246
|
readonly from: string;
|
|
247
247
|
}): Promise<void>;
|
|
248
|
+
/**
|
|
249
|
+
* The settings in use, shown to admins on Settings → Email. Never return secrets: say whether
|
|
250
|
+
* a password is set, not what it is.
|
|
251
|
+
*/
|
|
252
|
+
describe?(): readonly EmailSetting[];
|
|
253
|
+
/** Checks the connection without sending (Settings → Email); throws with the reason. */
|
|
254
|
+
verify?(): Promise<void>;
|
|
255
|
+
}
|
|
256
|
+
/** One setting an email adapter shows to admins (`describe()`). */
|
|
257
|
+
interface EmailSetting {
|
|
258
|
+
/**
|
|
259
|
+
* `host`, `port`, `secure`, `user`, `password`, `from` and `delivery` (`log`: printed, not sent)
|
|
260
|
+
* are translated in the admin; others are shown as they are.
|
|
261
|
+
*/
|
|
262
|
+
readonly key: string;
|
|
263
|
+
/** `null`: not set. */
|
|
264
|
+
readonly value: string | null;
|
|
265
|
+
/** Where it comes from, e.g. `SMTP_HOST` or `smtp({ host })`. */
|
|
266
|
+
readonly source?: string;
|
|
248
267
|
}
|
|
249
268
|
/** Prints emails to the log instead of sending them: for development and tests. */
|
|
250
269
|
declare function consoleEmail(options?: {
|
|
@@ -1065,6 +1084,18 @@ declare class EasyCMS<C extends Config = Config> {
|
|
|
1065
1084
|
})[]>;
|
|
1066
1085
|
private pendingJobs;
|
|
1067
1086
|
private removeJob;
|
|
1087
|
+
/**
|
|
1088
|
+
* @internal One attempt now for a saved webhook delivery or email (the admin's Retry): removed
|
|
1089
|
+
* when it is sent, otherwise kept as failed with the new error.
|
|
1090
|
+
*/
|
|
1091
|
+
retryDelivery(kind: 'webhook' | 'email', id: ID): Promise<{
|
|
1092
|
+
ok: true;
|
|
1093
|
+
} | {
|
|
1094
|
+
ok: false;
|
|
1095
|
+
error: string;
|
|
1096
|
+
}>;
|
|
1097
|
+
/** Removes webhook deliveries and emails that failed more than 30 days ago. */
|
|
1098
|
+
private pruneFailedDeliveries;
|
|
1068
1099
|
/** @internal */
|
|
1069
1100
|
collection(slug: string): CollectionConfig;
|
|
1070
1101
|
private global;
|
|
@@ -2027,6 +2058,67 @@ declare function applyPlugins(input: Config): Promise<Config>;
|
|
|
2027
2058
|
*/
|
|
2028
2059
|
declare function resolveConfig(input: Config | ResolvedConfig): Promise<ResolvedConfig>;
|
|
2029
2060
|
|
|
2061
|
+
type DeliveryKind = 'webhook' | 'email';
|
|
2062
|
+
type DeliveryState = 'failed' | 'pending';
|
|
2063
|
+
interface DeliveryBase {
|
|
2064
|
+
id: ID;
|
|
2065
|
+
state: DeliveryState;
|
|
2066
|
+
attempts: number;
|
|
2067
|
+
/** The last attempt's error. */
|
|
2068
|
+
error: string | null;
|
|
2069
|
+
createdAt: string;
|
|
2070
|
+
updatedAt: string;
|
|
2071
|
+
/** When a pending one is tried again. */
|
|
2072
|
+
nextAttemptAt: string;
|
|
2073
|
+
}
|
|
2074
|
+
/** A webhook delivery as the admin shows it (`GET <api>/admin/deliveries?kind=webhook`). */
|
|
2075
|
+
interface AdminWebhookDelivery extends DeliveryBase {
|
|
2076
|
+
kind: 'webhook';
|
|
2077
|
+
url: string;
|
|
2078
|
+
event: string;
|
|
2079
|
+
/** What changed: a document of a collection, or a global. */
|
|
2080
|
+
collection?: string;
|
|
2081
|
+
doc?: ID;
|
|
2082
|
+
global?: string;
|
|
2083
|
+
/** The JSON body as sent. */
|
|
2084
|
+
body: string;
|
|
2085
|
+
}
|
|
2086
|
+
/** An email as the admin shows it: who to and the subject, not the content. */
|
|
2087
|
+
interface AdminEmailDelivery extends DeliveryBase {
|
|
2088
|
+
kind: 'email';
|
|
2089
|
+
to: string[];
|
|
2090
|
+
subject: string;
|
|
2091
|
+
}
|
|
2092
|
+
type AdminDelivery = AdminWebhookDelivery | AdminEmailDelivery;
|
|
2093
|
+
interface AdminDeliveries {
|
|
2094
|
+
docs: AdminDelivery[];
|
|
2095
|
+
totalDocs: number;
|
|
2096
|
+
page: number;
|
|
2097
|
+
totalPages: number;
|
|
2098
|
+
/** How many there are of each state, for the filter. */
|
|
2099
|
+
counts: Record<DeliveryState, number>;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
/** Settings → Email in the admin (`GET <api>/admin/email`). */
|
|
2103
|
+
interface AdminEmail {
|
|
2104
|
+
/** An `email` adapter is set in the config. */
|
|
2105
|
+
configured: boolean;
|
|
2106
|
+
/** The adapter's name, e.g. `smtp`; `custom` when it has none. */
|
|
2107
|
+
name: string | null;
|
|
2108
|
+
/** The default sender. */
|
|
2109
|
+
from: string | null;
|
|
2110
|
+
/** What the adapter tells about itself (`describe()`), never secrets. */
|
|
2111
|
+
settings: EmailSetting[];
|
|
2112
|
+
/** The adapter can check its connection without sending (`verify()`). */
|
|
2113
|
+
canVerify: boolean;
|
|
2114
|
+
}
|
|
2115
|
+
type EmailCheck = {
|
|
2116
|
+
ok: true;
|
|
2117
|
+
} | {
|
|
2118
|
+
ok: false;
|
|
2119
|
+
error: string;
|
|
2120
|
+
};
|
|
2121
|
+
|
|
2030
2122
|
/**
|
|
2031
2123
|
* Finds the file of an admin module (`admin.modules`): a package export or a path relative to
|
|
2032
2124
|
* the project root. Looks from the project root first, then from the server's entry file
|
|
@@ -2170,6 +2262,11 @@ interface AdminSchema {
|
|
|
2170
2262
|
modules: string[];
|
|
2171
2263
|
/** Files can be uploaded from links (`upload.fromURL`). */
|
|
2172
2264
|
uploadFromURL: boolean;
|
|
2265
|
+
/** For admins: which saved deliveries the admin can show (webhooks, emails). */
|
|
2266
|
+
deliveries?: {
|
|
2267
|
+
webhook: boolean;
|
|
2268
|
+
email: boolean;
|
|
2269
|
+
};
|
|
2173
2270
|
/** Pages this user may open (`admin.pages`). */
|
|
2174
2271
|
pages: AdminPageRef[];
|
|
2175
2272
|
/** Dashboard panels this user may see (`admin.dashboard`). */
|
|
@@ -2262,4 +2359,4 @@ declare function validateConfig(config: Config): ConfigIssue[];
|
|
|
2262
2359
|
/** The version of `@easy-cms/core`, set when the package is built. */
|
|
2263
2360
|
declare const VERSION: string;
|
|
2264
2361
|
|
|
2265
|
-
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminAttention, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminPage, type AdminPageRef, type AdminSchema, type AdminStatus, type AdminViewAccess, type AdminWidgetRef, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, Auth, type AuthConfig, type AuthUser, BUILTIN_COLLECTIONS, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, type CustomField, type CustomFieldTypes, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type DashboardWidget, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DepthOptions, type DocumentOf, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailField, type EmailMessage, type Endpoint, type EndpointRequest, FIELD_TYPES, FIELD_TYPE_BASES, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldType, type FieldTypeBase, type FieldTypeDefinition, type FieldTypeValidateContext, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PluginInfo, type PluginTypes, type PreviewTarget, type PreviewURL, QueryError, type RawDocument, type ReadOptions, type RelationshipField, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoutesConfig, SESSION_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type SlugOf, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, type TypedPlugin, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, type UploadFromURLConfig, VERSION, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, defineFieldType, definePlugin, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
|
2362
|
+
export { ADMIN_ICONS, API_KEYS, API_KEY_GLOBAL_OPERATIONS, API_KEY_OPERATIONS, type Access, type AccessArgs, type AccessOptions, type AdminAttention, type AdminBrand, type AdminCollection, type AdminComponent, type AdminComponentRef, type AdminConfig, type AdminDeliveries, type AdminDelivery, type AdminEmail, type AdminEmailDelivery, type AdminField, type AdminGlobal, type AdminIcon, type AdminLocale, type AdminPage, type AdminPageRef, type AdminSchema, type AdminStatus, type AdminViewAccess, type AdminWebhookDelivery, type AdminWidgetRef, type AfterChangeHook, type AfterDeleteHook, type AfterReadHook, type ApiKeyContext, type ApiKeyGlobalOperation, type ApiKeyOperation, type ApiKeyPermissions, type ArrayField, Auth, type AuthConfig, type AuthUser, BUILTIN_COLLECTIONS, type BeforeChangeHook, type BeforeDeleteHook, type BeforeValidateHook, type Block, type BlocksField, type BooleanField, type BuiltinApiKeysCollection, type BuiltinMediaCollection, type BuiltinUsersCollection, type BundleHints, CONFIG_FILE_NAMES, CSRF_COOKIE, CSRF_HEADER, type CliCommand, type CollectionAccess, type CollectionAdmin, type CollectionConfig, type CollectionDocument, type CollectionHooks, type CollectionListAdmin, type CollectionSlug, type Config, ConfigError, type ConfigIssue, type ContainerAdmin, type CopyProgress, type CopyResult, type CreateEasyCMSOptions, type CreateInput, type CreateMigrationResult, type CustomField, type CustomFieldTypes, DEFAULT_ADMIN_PATH, DEFAULT_API_PATH, DEFAULT_DEPTH, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_VERSIONS, DEFAULT_PASSWORD_EMAILS, DEFAULT_TOKEN_EXPIRATION, type DashboardWidget, type Database, type DatabaseAdapter, type DatabaseInitArgs, type DatabaseTransfer, type DateField, type DeliveryKind, type DeliveryState, type DepthOptions, type DocumentOf, EXTENSIONS, EasyCMS, EasyCMSError, type EmailAdapter, type EmailCheck, type EmailField, type EmailMessage, type EmailSetting, type Endpoint, type EndpointRequest, FIELD_TYPES, FIELD_TYPE_BASES, type Field, type FieldAccess, type FieldAccessFn, type FieldAdmin, type FieldError, type FieldType, type FieldTypeBase, type FieldTypeDefinition, type FieldTypeValidateContext, type FieldValidate, type FieldValidateContext, type FieldValue, type FieldsInput, type FieldsValue, type FilterOptions, type FilterOptionsArgs, type FindArgs, type FindOptions, ForbiddenError, type GlobalAccess, type GlobalConfig, type GlobalDocument, type GlobalHooks, type GlobalInput, type GlobalSlug, type GroupField, type ID, INTERNAL_COLLECTIONS, type ImageSize, type InferCollection, type InferGlobal, type JsonField, type Label, type LivePreview, type LoadConfigOptions, type LocalStorageOptions, type LocalizationConfig, type Logger, type LoginArgs, MAX_DEPTH, MEDIA, MIN_PASSWORD_LENGTH, MIN_SECRET_LENGTH, type MediaDocument, type MediaSize, type MigrationInfo, NotFoundError, type NumberField, type Operation, type PaginatedDocs, type PasswordEmail, type PasswordEmailArgs, type PasswordEmailFn, type PasswordLinkOptions, PayloadTooLargeError, type Plugin, type PluginInfo, type PluginTypes, type PreviewTarget, type PreviewURL, QueryError, type RawDocument, type ReadOptions, type RelationshipField, type ResolvedConfig, type RestHandler, type RestHandlerOptions, type RichTextDocument, type RichTextField, type Role, type RoutesConfig, SESSION_COOKIE, type ScheduledJob, SchemaError, type SchemaMode, type SelectField, type SelectOption, type Session, type SlugField, type SlugOf, type StorageAdapter, type StoredFile, type TextField, type TextareaField, TooManyRequestsError, type TypedPlugin, UnauthorizedError, type UpdateInput, type UpdateOptions, type UploadConfig, type UploadField, type UploadFromURLConfig, VERSION, ValidationError, type Version, type VersionSummary, type VersionsConfig, WEBHOOK_EVENTS, type WebhookConfig, type WebhookEvent, type WebhookPayload, type Where, type WhereOperators, anyone, applyDefaults, applyPlugins, configSignature, consoleEmail, consoleLogger, copyDatabase, createEasyCMS, createRestHandler, createRootEndpointHandler, defineConfig, defineFieldType, definePlugin, fillMissing, findConfigFile, generateSlugs, generateTypes, hasRows, hashPassword, imageDimensions, importConfig, isAdmin, isHasMany, isLoggedIn, keyAllows, loadConfig, localStorage, mergeForUpdate, mimeAllowed, mimeAllowedBy, parseId, populate, readCookie, resolveAdminModule, resolveConfig, rowFields, silentLogger, singularize, slugify, sniffMimeType, validateConfig, validateFields, verifyPassword };
|
package/dist/index.js
CHANGED
|
@@ -150,8 +150,8 @@ ${lines.join("\n")}`;
|
|
|
150
150
|
};
|
|
151
151
|
var EasyCMSError = class extends Error {
|
|
152
152
|
status;
|
|
153
|
-
constructor(
|
|
154
|
-
super(
|
|
153
|
+
constructor(message2, status) {
|
|
154
|
+
super(message2);
|
|
155
155
|
this.name = "EasyCMSError";
|
|
156
156
|
this.status = status;
|
|
157
157
|
}
|
|
@@ -172,38 +172,38 @@ var NotFoundError = class extends EasyCMSError {
|
|
|
172
172
|
}
|
|
173
173
|
};
|
|
174
174
|
var QueryError = class extends EasyCMSError {
|
|
175
|
-
constructor(
|
|
176
|
-
super(
|
|
175
|
+
constructor(message2) {
|
|
176
|
+
super(message2, 400);
|
|
177
177
|
this.name = "QueryError";
|
|
178
178
|
}
|
|
179
179
|
};
|
|
180
180
|
var SchemaError = class extends EasyCMSError {
|
|
181
|
-
constructor(
|
|
182
|
-
super(
|
|
181
|
+
constructor(message2) {
|
|
182
|
+
super(message2, 500);
|
|
183
183
|
this.name = "SchemaError";
|
|
184
184
|
}
|
|
185
185
|
};
|
|
186
186
|
var UnauthorizedError = class extends EasyCMSError {
|
|
187
|
-
constructor(
|
|
188
|
-
super(
|
|
187
|
+
constructor(message2 = "You must be logged in") {
|
|
188
|
+
super(message2, 401);
|
|
189
189
|
this.name = "UnauthorizedError";
|
|
190
190
|
}
|
|
191
191
|
};
|
|
192
192
|
var ForbiddenError = class extends EasyCMSError {
|
|
193
|
-
constructor(
|
|
194
|
-
super(
|
|
193
|
+
constructor(message2 = "You are not allowed to do this") {
|
|
194
|
+
super(message2, 403);
|
|
195
195
|
this.name = "ForbiddenError";
|
|
196
196
|
}
|
|
197
197
|
};
|
|
198
198
|
var TooManyRequestsError = class extends EasyCMSError {
|
|
199
|
-
constructor(
|
|
200
|
-
super(
|
|
199
|
+
constructor(message2) {
|
|
200
|
+
super(message2, 429);
|
|
201
201
|
this.name = "TooManyRequestsError";
|
|
202
202
|
}
|
|
203
203
|
};
|
|
204
204
|
var PayloadTooLargeError = class extends EasyCMSError {
|
|
205
|
-
constructor(
|
|
206
|
-
super(
|
|
205
|
+
constructor(message2) {
|
|
206
|
+
super(message2, 413);
|
|
207
207
|
this.name = "PayloadTooLargeError";
|
|
208
208
|
}
|
|
209
209
|
};
|
|
@@ -1331,7 +1331,7 @@ async function validateFields(fields, input, options, prefix = "") {
|
|
|
1331
1331
|
for (const field of fields) {
|
|
1332
1332
|
const path = prefix + field.name;
|
|
1333
1333
|
const raw = input[field.name];
|
|
1334
|
-
const fail = (
|
|
1334
|
+
const fail = (message2) => errors.push({ field: path, message: message2 });
|
|
1335
1335
|
if (field.type === "group") {
|
|
1336
1336
|
if (!isEmpty(raw) && !isPlainObject(raw)) {
|
|
1337
1337
|
fail("must be an object");
|
|
@@ -1360,7 +1360,7 @@ async function validateFields(fields, input, options, prefix = "") {
|
|
|
1360
1360
|
for (const locale of localization.locales) {
|
|
1361
1361
|
const own = locale === written;
|
|
1362
1362
|
const localePath = own ? path : `${path}.${locale}`;
|
|
1363
|
-
const failLocale = (
|
|
1363
|
+
const failLocale = (message2) => errors.push({ field: localePath, message: message2 });
|
|
1364
1364
|
const value2 = map[locale];
|
|
1365
1365
|
if (isEmpty(value2) || Array.isArray(value2) && value2.length === 0) {
|
|
1366
1366
|
if (own && field.required && !options.skipRequired) failLocale("is required");
|
|
@@ -1543,17 +1543,19 @@ function consoleEmail(options = {}) {
|
|
|
1543
1543
|
const sent = [];
|
|
1544
1544
|
const adapter = {
|
|
1545
1545
|
name: "console",
|
|
1546
|
+
// `log`: the admin says it prints to the server log instead of sending.
|
|
1547
|
+
describe: () => [{ key: "delivery", value: "log" }],
|
|
1546
1548
|
from: options.from ?? "Easy CMS <no-reply@localhost>",
|
|
1547
1549
|
sent,
|
|
1548
|
-
async send(
|
|
1549
|
-
sent.push(
|
|
1550
|
+
async send(message2) {
|
|
1551
|
+
sent.push(message2);
|
|
1550
1552
|
const list = (value) => value === void 0 ? void 0 : typeof value === "string" ? value : value.join(", ");
|
|
1551
1553
|
const lines = [
|
|
1552
|
-
`Email to ${list(
|
|
1553
|
-
`From: ${
|
|
1554
|
-
`Subject: ${
|
|
1554
|
+
`Email to ${list(message2.to)}${message2.cc ? `, cc ${list(message2.cc)}` : ""}`,
|
|
1555
|
+
`From: ${message2.from}${message2.replyTo ? ` (reply to ${message2.replyTo})` : ""}`,
|
|
1556
|
+
`Subject: ${message2.subject}`,
|
|
1555
1557
|
"",
|
|
1556
|
-
|
|
1558
|
+
message2.text ?? message2.html ?? ""
|
|
1557
1559
|
];
|
|
1558
1560
|
(options.log ?? console.log)(lines.join("\n"));
|
|
1559
1561
|
}
|
|
@@ -1581,14 +1583,14 @@ var Mailer = class {
|
|
|
1581
1583
|
* for the sending too. Without an `email` adapter in the config, the email is skipped with a
|
|
1582
1584
|
* warning.
|
|
1583
1585
|
*/
|
|
1584
|
-
async send(
|
|
1586
|
+
async send(message2) {
|
|
1585
1587
|
if (!this.adapter) {
|
|
1586
1588
|
this.logger.warn(
|
|
1587
|
-
`Email "${
|
|
1589
|
+
`Email "${message2.subject}" not sent: set \`email\` in the config (e.g. consoleEmail() in development)`
|
|
1588
1590
|
);
|
|
1589
1591
|
return;
|
|
1590
1592
|
}
|
|
1591
|
-
const body = JSON.stringify(
|
|
1593
|
+
const body = JSON.stringify(message2);
|
|
1592
1594
|
let id;
|
|
1593
1595
|
try {
|
|
1594
1596
|
id = await this.queue?.add({
|
|
@@ -1601,7 +1603,7 @@ var Mailer = class {
|
|
|
1601
1603
|
} catch (error) {
|
|
1602
1604
|
this.logger.error(`Could not save email: ${error.message}`);
|
|
1603
1605
|
}
|
|
1604
|
-
this.track(this.deliver(
|
|
1606
|
+
this.track(this.deliver(message2, body, id));
|
|
1605
1607
|
}
|
|
1606
1608
|
/** Waits for emails being sent, e.g. before a serverless function returns. */
|
|
1607
1609
|
async flush() {
|
|
@@ -1636,21 +1638,26 @@ var Mailer = class {
|
|
|
1636
1638
|
}
|
|
1637
1639
|
return { sent, failed };
|
|
1638
1640
|
}
|
|
1641
|
+
/** One attempt now for a saved email (the admin's Retry): `undefined` when sent, else the error. */
|
|
1642
|
+
async sendNow(message2) {
|
|
1643
|
+
if (!this.adapter) return "email is not set up (`email` in the config)";
|
|
1644
|
+
return this.attempt(JSON.parse(message2));
|
|
1645
|
+
}
|
|
1639
1646
|
track(delivery) {
|
|
1640
1647
|
const tracked = delivery.catch((error) => this.logger.error(`Email queue failed: ${error.message}`)).finally(() => this.pending.delete(tracked));
|
|
1641
1648
|
this.pending.add(tracked);
|
|
1642
1649
|
}
|
|
1643
|
-
async deliver(
|
|
1644
|
-
const error = await this.attempt(
|
|
1650
|
+
async deliver(message2, body, id) {
|
|
1651
|
+
const error = await this.attempt(message2);
|
|
1645
1652
|
if (!error) {
|
|
1646
1653
|
if (id !== void 0) await this.queue?.remove(id);
|
|
1647
1654
|
return;
|
|
1648
1655
|
}
|
|
1649
1656
|
if (id === void 0 || !this.queue) {
|
|
1650
|
-
this.logger.error(`Email "${
|
|
1657
|
+
this.logger.error(`Email "${message2.subject}" failed: ${error}`);
|
|
1651
1658
|
return;
|
|
1652
1659
|
}
|
|
1653
|
-
this.logger.warn(`Email "${
|
|
1660
|
+
this.logger.warn(`Email "${message2.subject}" failed, retrying later: ${error}`);
|
|
1654
1661
|
await this.queue.update(id, {
|
|
1655
1662
|
message: body,
|
|
1656
1663
|
attempts: 1,
|
|
@@ -1660,12 +1667,12 @@ var Mailer = class {
|
|
|
1660
1667
|
});
|
|
1661
1668
|
}
|
|
1662
1669
|
/** `undefined` when sent, else the error. */
|
|
1663
|
-
async attempt(
|
|
1670
|
+
async attempt(message2) {
|
|
1664
1671
|
const adapter = this.adapter;
|
|
1665
|
-
const from =
|
|
1672
|
+
const from = message2.from ?? adapter.from;
|
|
1666
1673
|
if (!from) return "no sender: set `from` on the message or the email adapter";
|
|
1667
1674
|
try {
|
|
1668
|
-
await adapter.send({ ...
|
|
1675
|
+
await adapter.send({ ...message2, from });
|
|
1669
1676
|
return void 0;
|
|
1670
1677
|
} catch (error) {
|
|
1671
1678
|
return error.message || String(error);
|
|
@@ -1876,6 +1883,15 @@ var Webhooks = class {
|
|
|
1876
1883
|
}
|
|
1877
1884
|
return { sent, failed };
|
|
1878
1885
|
}
|
|
1886
|
+
/**
|
|
1887
|
+
* One attempt now for a saved delivery (the admin's Retry), with the webhook's current headers
|
|
1888
|
+
* and secret.
|
|
1889
|
+
*/
|
|
1890
|
+
async sendNow(delivery) {
|
|
1891
|
+
const hook = this.hooks.find((h) => h.url === delivery.url);
|
|
1892
|
+
if (!hook) return { ok: false, error: "this webhook is no longer in the config" };
|
|
1893
|
+
return this.attempt(hook, delivery.event, delivery.body, delivery.delivery);
|
|
1894
|
+
}
|
|
1879
1895
|
track(delivery) {
|
|
1880
1896
|
const tracked = delivery.catch((error) => this.logger.error(`Webhook queue failed: ${error.message}`)).finally(() => this.pending.delete(tracked));
|
|
1881
1897
|
this.pending.add(tracked);
|
|
@@ -1988,7 +2004,7 @@ var SLUG_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
|
1988
2004
|
var FIELD_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
1989
2005
|
function validateConfig(config) {
|
|
1990
2006
|
const issues = [];
|
|
1991
|
-
const add = (path,
|
|
2007
|
+
const add = (path, message2, hint) => issues.push(hint ? { path, message: message2, hint } : { path, message: message2 });
|
|
1992
2008
|
if (typeof config !== "object" || config === null) {
|
|
1993
2009
|
add("config", "must be an object", "export default defineConfig({ ... })");
|
|
1994
2010
|
return issues;
|
|
@@ -3053,9 +3069,9 @@ function localizeSort(fields, sort, locale, locales = []) {
|
|
|
3053
3069
|
|
|
3054
3070
|
// src/logger.ts
|
|
3055
3071
|
var consoleLogger = {
|
|
3056
|
-
info: (
|
|
3057
|
-
warn: (
|
|
3058
|
-
error: (
|
|
3072
|
+
info: (message2) => console.info(`[easy-cms] ${message2}`),
|
|
3073
|
+
warn: (message2) => console.warn(`[easy-cms] ${message2}`),
|
|
3074
|
+
error: (message2) => console.error(`[easy-cms] ${message2}`)
|
|
3059
3075
|
};
|
|
3060
3076
|
var silentLogger = { info() {
|
|
3061
3077
|
}, warn() {
|
|
@@ -3234,8 +3250,8 @@ import { BlockList, isIP } from "net";
|
|
|
3234
3250
|
var RemoteFileError = class extends Error {
|
|
3235
3251
|
/** `413` when the file is too large, otherwise `400`. */
|
|
3236
3252
|
status;
|
|
3237
|
-
constructor(
|
|
3238
|
-
super(
|
|
3253
|
+
constructor(message2, status = 400) {
|
|
3254
|
+
super(message2);
|
|
3239
3255
|
this.name = "RemoteFileError";
|
|
3240
3256
|
this.status = status;
|
|
3241
3257
|
}
|
|
@@ -4356,8 +4372,8 @@ var EasyCMS = class {
|
|
|
4356
4372
|
* the email is queued; it is sent in the background and retried from the queue if sending
|
|
4357
4373
|
* fails. Without `email` in the config, the email is skipped with a warning.
|
|
4358
4374
|
*/
|
|
4359
|
-
sendEmail(
|
|
4360
|
-
return this.mailer.send(
|
|
4375
|
+
sendEmail(message2) {
|
|
4376
|
+
return this.mailer.send(message2);
|
|
4361
4377
|
}
|
|
4362
4378
|
/** Waits for emails being sent, e.g. before a serverless function returns. */
|
|
4363
4379
|
flushEmails() {
|
|
@@ -4425,6 +4441,7 @@ var EasyCMS = class {
|
|
|
4425
4441
|
*/
|
|
4426
4442
|
async runJobs(now = /* @__PURE__ */ new Date()) {
|
|
4427
4443
|
const scheduled = await this.runScheduled(now);
|
|
4444
|
+
await this.pruneFailedDeliveries(now);
|
|
4428
4445
|
return {
|
|
4429
4446
|
...scheduled,
|
|
4430
4447
|
webhooks: await this.retryWebhooks(now),
|
|
@@ -4515,12 +4532,12 @@ var EasyCMS = class {
|
|
|
4515
4532
|
});
|
|
4516
4533
|
} catch (error) {
|
|
4517
4534
|
failed++;
|
|
4518
|
-
const
|
|
4519
|
-
this.logger.error(`Scheduled ${action} of ${parent} ${row.doc} failed: ${
|
|
4535
|
+
const message2 = error.message;
|
|
4536
|
+
this.logger.error(`Scheduled ${action} of ${parent} ${row.doc} failed: ${message2}`);
|
|
4520
4537
|
await this.db.update({
|
|
4521
4538
|
collection: SCHEDULED_JOBS,
|
|
4522
4539
|
id: row.id,
|
|
4523
|
-
data: { ...row, state: "failed", error:
|
|
4540
|
+
data: { ...row, state: "failed", error: message2.slice(0, 500) }
|
|
4524
4541
|
});
|
|
4525
4542
|
}
|
|
4526
4543
|
}
|
|
@@ -4653,6 +4670,51 @@ var EasyCMS = class {
|
|
|
4653
4670
|
if (!job) throw new NotFoundError("scheduled job", jobId);
|
|
4654
4671
|
await this.db.delete({ collection: SCHEDULED_JOBS, id: job.id });
|
|
4655
4672
|
}
|
|
4673
|
+
/**
|
|
4674
|
+
* @internal One attempt now for a saved webhook delivery or email (the admin's Retry): removed
|
|
4675
|
+
* when it is sent, otherwise kept as failed with the new error.
|
|
4676
|
+
*/
|
|
4677
|
+
async retryDelivery(kind, id) {
|
|
4678
|
+
const collection2 = kind === "webhook" ? WEBHOOK_DELIVERIES : EMAIL_DELIVERIES;
|
|
4679
|
+
const row = await this.db.findById({ collection: collection2, id });
|
|
4680
|
+
if (!row) throw new NotFoundError(collection2, id);
|
|
4681
|
+
const error = kind === "webhook" ? await this.webhooks.sendNow(row).then((result) => result.ok ? void 0 : result.error) : await this.mailer.sendNow(String(row.message));
|
|
4682
|
+
if (error === void 0) {
|
|
4683
|
+
await this.db.delete({ collection: collection2, id });
|
|
4684
|
+
return { ok: true };
|
|
4685
|
+
}
|
|
4686
|
+
const { id: _id, ...data } = row;
|
|
4687
|
+
await this.db.update({
|
|
4688
|
+
collection: collection2,
|
|
4689
|
+
id,
|
|
4690
|
+
data: {
|
|
4691
|
+
...data,
|
|
4692
|
+
attempts: Number(row.attempts ?? 0) + 1,
|
|
4693
|
+
state: "failed",
|
|
4694
|
+
error,
|
|
4695
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
4696
|
+
}
|
|
4697
|
+
});
|
|
4698
|
+
return { ok: false, error };
|
|
4699
|
+
}
|
|
4700
|
+
/** Removes webhook deliveries and emails that failed more than 30 days ago. */
|
|
4701
|
+
async pruneFailedDeliveries(now) {
|
|
4702
|
+
const before = new Date(now.getTime() - 30 * 864e5).toISOString();
|
|
4703
|
+
for (const collection2 of [WEBHOOK_DELIVERIES, EMAIL_DELIVERIES]) {
|
|
4704
|
+
if (!this.config.collections.some((c) => c.slug === collection2)) continue;
|
|
4705
|
+
for (; ; ) {
|
|
4706
|
+
const { docs } = await this.db.find({
|
|
4707
|
+
collection: collection2,
|
|
4708
|
+
where: { and: [{ state: { equals: "failed" } }, { updatedAt: { lt: before } }] },
|
|
4709
|
+
sort: ["updatedAt"],
|
|
4710
|
+
limit: 100,
|
|
4711
|
+
page: 1
|
|
4712
|
+
});
|
|
4713
|
+
for (const doc of docs) await this.db.delete({ collection: collection2, id: doc.id });
|
|
4714
|
+
if (docs.length < 100) break;
|
|
4715
|
+
}
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4656
4718
|
// -------------------------------------------------------------------------
|
|
4657
4719
|
/** @internal */
|
|
4658
4720
|
collection(slug) {
|
|
@@ -5204,6 +5266,199 @@ async function readAdminModule(cms, index) {
|
|
|
5204
5266
|
return { body, etag: `"${createHash3("sha1").update(body).digest("base64url")}"` };
|
|
5205
5267
|
}
|
|
5206
5268
|
|
|
5269
|
+
// src/rest/admin-deliveries.ts
|
|
5270
|
+
var PER_PAGE = 50;
|
|
5271
|
+
var collectionOf = (kind) => kind === "webhook" ? WEBHOOK_DELIVERIES : EMAIL_DELIVERIES;
|
|
5272
|
+
var exists = (cms, kind) => cms.config.collections.some((c) => c.slug === collectionOf(kind));
|
|
5273
|
+
var isDeliveryKind = (value) => value === "webhook" || value === "email";
|
|
5274
|
+
function parse(text) {
|
|
5275
|
+
try {
|
|
5276
|
+
const value = JSON.parse(String(text ?? ""));
|
|
5277
|
+
return typeof value === "object" && value !== null ? value : {};
|
|
5278
|
+
} catch {
|
|
5279
|
+
return {};
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
function toAdmin(kind, row) {
|
|
5283
|
+
const base = {
|
|
5284
|
+
id: row.id,
|
|
5285
|
+
state: row.state === "failed" ? "failed" : "pending",
|
|
5286
|
+
attempts: Number(row.attempts ?? 0),
|
|
5287
|
+
error: typeof row.error === "string" ? row.error : null,
|
|
5288
|
+
createdAt: String(row.createdAt ?? ""),
|
|
5289
|
+
updatedAt: String(row.updatedAt ?? ""),
|
|
5290
|
+
nextAttemptAt: String(row.nextAttemptAt ?? "")
|
|
5291
|
+
};
|
|
5292
|
+
if (kind === "webhook") {
|
|
5293
|
+
const payload = parse(row.body);
|
|
5294
|
+
return {
|
|
5295
|
+
...base,
|
|
5296
|
+
kind,
|
|
5297
|
+
url: String(row.url ?? ""),
|
|
5298
|
+
event: String(row.event ?? ""),
|
|
5299
|
+
...typeof payload.collection === "string" ? { collection: payload.collection } : {},
|
|
5300
|
+
...typeof payload.id === "string" || typeof payload.id === "number" ? { doc: payload.id } : {},
|
|
5301
|
+
...typeof payload.global === "string" ? { global: payload.global } : {},
|
|
5302
|
+
body: String(row.body ?? "")
|
|
5303
|
+
};
|
|
5304
|
+
}
|
|
5305
|
+
const message2 = parse(row.message);
|
|
5306
|
+
const to = message2.to;
|
|
5307
|
+
return {
|
|
5308
|
+
...base,
|
|
5309
|
+
kind,
|
|
5310
|
+
to: Array.isArray(to) ? to.map(String) : typeof to === "string" ? [to] : [],
|
|
5311
|
+
subject: String(message2.subject ?? "")
|
|
5312
|
+
};
|
|
5313
|
+
}
|
|
5314
|
+
async function listDeliveries(cms, kind, state, page) {
|
|
5315
|
+
if (!exists(cms, kind))
|
|
5316
|
+
return { docs: [], totalDocs: 0, page: 1, totalPages: 0, counts: { failed: 0, pending: 0 } };
|
|
5317
|
+
const collection2 = collectionOf(kind);
|
|
5318
|
+
const [found, failed, pending] = await Promise.all([
|
|
5319
|
+
cms.db.find({
|
|
5320
|
+
collection: collection2,
|
|
5321
|
+
where: { state: { equals: state } },
|
|
5322
|
+
sort: ["-updatedAt"],
|
|
5323
|
+
limit: PER_PAGE,
|
|
5324
|
+
page
|
|
5325
|
+
}),
|
|
5326
|
+
cms.db.count({ collection: collection2, where: { state: { equals: "failed" } } }),
|
|
5327
|
+
cms.db.count({ collection: collection2, where: { state: { equals: "pending" } } })
|
|
5328
|
+
]);
|
|
5329
|
+
return {
|
|
5330
|
+
docs: found.docs.map((row) => toAdmin(kind, row)),
|
|
5331
|
+
totalDocs: found.totalDocs,
|
|
5332
|
+
page: found.page,
|
|
5333
|
+
totalPages: found.totalPages,
|
|
5334
|
+
counts: { failed, pending }
|
|
5335
|
+
};
|
|
5336
|
+
}
|
|
5337
|
+
async function retryFailedDeliveries(cms, kind) {
|
|
5338
|
+
if (!exists(cms, kind)) return { sent: 0, failed: 0 };
|
|
5339
|
+
const collection2 = collectionOf(kind);
|
|
5340
|
+
const ids = [];
|
|
5341
|
+
for (let page = 1; ; page++) {
|
|
5342
|
+
const { docs, hasNextPage } = await cms.db.find({
|
|
5343
|
+
collection: collection2,
|
|
5344
|
+
where: { state: { equals: "failed" } },
|
|
5345
|
+
sort: ["updatedAt"],
|
|
5346
|
+
limit: 500,
|
|
5347
|
+
page
|
|
5348
|
+
});
|
|
5349
|
+
ids.push(...docs.map((row) => row.id));
|
|
5350
|
+
if (!hasNextPage) break;
|
|
5351
|
+
}
|
|
5352
|
+
let sent = 0;
|
|
5353
|
+
let failed = 0;
|
|
5354
|
+
for (const id of ids) {
|
|
5355
|
+
const result = await cms.retryDelivery(kind, id);
|
|
5356
|
+
if (result.ok) sent++;
|
|
5357
|
+
else failed++;
|
|
5358
|
+
}
|
|
5359
|
+
return { sent, failed };
|
|
5360
|
+
}
|
|
5361
|
+
async function deleteDelivery(cms, kind, id) {
|
|
5362
|
+
const collection2 = collectionOf(kind);
|
|
5363
|
+
if (!exists(cms, kind) || !await cms.db.findById({ collection: collection2, id }))
|
|
5364
|
+
throw new NotFoundError(collection2, id);
|
|
5365
|
+
await cms.db.delete({ collection: collection2, id });
|
|
5366
|
+
}
|
|
5367
|
+
async function deleteFailedDeliveries(cms, kind) {
|
|
5368
|
+
if (!exists(cms, kind)) return { deleted: 0 };
|
|
5369
|
+
const collection2 = collectionOf(kind);
|
|
5370
|
+
let deleted = 0;
|
|
5371
|
+
for (; ; ) {
|
|
5372
|
+
const { docs } = await cms.db.find({
|
|
5373
|
+
collection: collection2,
|
|
5374
|
+
where: { state: { equals: "failed" } },
|
|
5375
|
+
sort: ["updatedAt"],
|
|
5376
|
+
limit: 100,
|
|
5377
|
+
page: 1
|
|
5378
|
+
});
|
|
5379
|
+
for (const row of docs) await cms.db.delete({ collection: collection2, id: row.id });
|
|
5380
|
+
deleted += docs.length;
|
|
5381
|
+
if (docs.length < 100) break;
|
|
5382
|
+
}
|
|
5383
|
+
return { deleted };
|
|
5384
|
+
}
|
|
5385
|
+
|
|
5386
|
+
// src/rest/admin-email.ts
|
|
5387
|
+
var EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
5388
|
+
var LIMIT = 5;
|
|
5389
|
+
var WINDOW = 10 * 6e4;
|
|
5390
|
+
var sentAt = /* @__PURE__ */ new WeakMap();
|
|
5391
|
+
var message = (error) => error instanceof Error ? error.message || error.name : String(error);
|
|
5392
|
+
function adminEmail(cms) {
|
|
5393
|
+
const adapter = cms.config.email;
|
|
5394
|
+
if (!adapter) return { configured: false, name: null, from: null, settings: [], canVerify: false };
|
|
5395
|
+
let settings = [];
|
|
5396
|
+
try {
|
|
5397
|
+
settings = [...adapter.describe?.() ?? []].map((s) => ({
|
|
5398
|
+
key: String(s.key),
|
|
5399
|
+
value: s.value === null || s.value === void 0 ? null : String(s.value),
|
|
5400
|
+
...s.source ? { source: String(s.source) } : {}
|
|
5401
|
+
}));
|
|
5402
|
+
} catch {
|
|
5403
|
+
}
|
|
5404
|
+
return {
|
|
5405
|
+
configured: true,
|
|
5406
|
+
name: adapter.name ?? "custom",
|
|
5407
|
+
from: adapter.from ?? null,
|
|
5408
|
+
settings,
|
|
5409
|
+
canVerify: typeof adapter.verify === "function"
|
|
5410
|
+
};
|
|
5411
|
+
}
|
|
5412
|
+
async function verifyEmail(cms) {
|
|
5413
|
+
const adapter = cms.config.email;
|
|
5414
|
+
if (!adapter?.verify)
|
|
5415
|
+
return { ok: false, error: "This email adapter cannot check its connection" };
|
|
5416
|
+
try {
|
|
5417
|
+
await adapter.verify();
|
|
5418
|
+
return { ok: true };
|
|
5419
|
+
} catch (error) {
|
|
5420
|
+
return { ok: false, error: message(error) };
|
|
5421
|
+
}
|
|
5422
|
+
}
|
|
5423
|
+
async function sendTestEmail(cms, user, to, locale) {
|
|
5424
|
+
const adapter = cms.config.email;
|
|
5425
|
+
if (!adapter) return { ok: false, error: "Email is not set up (`email` in the config)" };
|
|
5426
|
+
const address = typeof to === "string" && to.trim() ? to.trim() : user.email;
|
|
5427
|
+
if (!EMAIL.test(address))
|
|
5428
|
+
throw new ValidationError("email", [{ field: "to", message: "must be an email address" }]);
|
|
5429
|
+
const times = sentAt.get(cms) ?? /* @__PURE__ */ new Map();
|
|
5430
|
+
sentAt.set(cms, times);
|
|
5431
|
+
const key2 = String(user.id);
|
|
5432
|
+
const now = Date.now();
|
|
5433
|
+
const recent = (times.get(key2) ?? []).filter((t) => now - t < WINDOW);
|
|
5434
|
+
if (recent.length >= LIMIT)
|
|
5435
|
+
throw new TooManyRequestsError("Too many test emails: try again in a few minutes");
|
|
5436
|
+
times.set(key2, [...recent, now]);
|
|
5437
|
+
const from = adapter.from;
|
|
5438
|
+
if (!from) return { ok: false, error: "No sender: set `from` on the email adapter" };
|
|
5439
|
+
const site = cms.config.admin.brand.name || "Easy CMS";
|
|
5440
|
+
const th = locale === "th" || locale !== "en" && cms.config.admin.locale === "th";
|
|
5441
|
+
const time = (/* @__PURE__ */ new Date()).toISOString();
|
|
5442
|
+
const subject = th ? `\u0E2D\u0E35\u0E40\u0E21\u0E25\u0E17\u0E14\u0E2A\u0E2D\u0E1A\u0E08\u0E32\u0E01 ${site}` : `Test email from ${site}`;
|
|
5443
|
+
const text = th ? `\u0E2D\u0E35\u0E40\u0E21\u0E25\u0E19\u0E35\u0E49\u0E2A\u0E48\u0E07\u0E08\u0E32\u0E01\u0E2B\u0E19\u0E49\u0E32 \u0E15\u0E31\u0E49\u0E07\u0E04\u0E48\u0E32 \u2192 \u0E2D\u0E35\u0E40\u0E21\u0E25 \u0E02\u0E2D\u0E07 ${site} \u0E40\u0E1E\u0E37\u0E48\u0E2D\u0E17\u0E14\u0E2A\u0E2D\u0E1A\u0E01\u0E32\u0E23\u0E15\u0E31\u0E49\u0E07\u0E04\u0E48\u0E32
|
|
5444
|
+
|
|
5445
|
+
\u0E2A\u0E48\u0E07\u0E42\u0E14\u0E22: ${user.email}
|
|
5446
|
+
Adapter: ${adapter.name ?? "custom"}
|
|
5447
|
+
\u0E1C\u0E39\u0E49\u0E2A\u0E48\u0E07: ${from}
|
|
5448
|
+
\u0E40\u0E27\u0E25\u0E32: ${time}` : `This email was sent from Settings \u2192 Email in ${site} to test the settings.
|
|
5449
|
+
|
|
5450
|
+
Sent by: ${user.email}
|
|
5451
|
+
Adapter: ${adapter.name ?? "custom"}
|
|
5452
|
+
From: ${from}
|
|
5453
|
+
Time: ${time}`;
|
|
5454
|
+
try {
|
|
5455
|
+
await adapter.send({ to: address, subject, text, from });
|
|
5456
|
+
return { ok: true };
|
|
5457
|
+
} catch (error) {
|
|
5458
|
+
return { ok: false, error: message(error) };
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5461
|
+
|
|
5207
5462
|
// src/rest/admin-schema.ts
|
|
5208
5463
|
var SETTINGS = /* @__PURE__ */ new Set([USERS, API_KEYS]);
|
|
5209
5464
|
function componentRef(component) {
|
|
@@ -5355,6 +5610,12 @@ async function adminSchema(cms, user, origin) {
|
|
|
5355
5610
|
globals: await Promise.all(cms.config.globals.map((g) => global(g, user, localized))),
|
|
5356
5611
|
modules: adminModuleUrls(cms),
|
|
5357
5612
|
uploadFromURL: cms.config.upload.fromURL !== void 0,
|
|
5613
|
+
...user.role === "admin" && !user.apiKey ? {
|
|
5614
|
+
deliveries: {
|
|
5615
|
+
webhook: cms.config.collections.some((c) => c.slug === WEBHOOK_DELIVERIES),
|
|
5616
|
+
email: cms.config.collections.some((c) => c.slug === EMAIL_DELIVERIES)
|
|
5617
|
+
}
|
|
5618
|
+
} : {},
|
|
5358
5619
|
pages: await pages(cms, user),
|
|
5359
5620
|
dashboard: await widgets(cms, user)
|
|
5360
5621
|
};
|
|
@@ -5391,7 +5652,7 @@ async function widgets(cms, user) {
|
|
|
5391
5652
|
}
|
|
5392
5653
|
|
|
5393
5654
|
// src/version.ts
|
|
5394
|
-
var VERSION = true ? "0.
|
|
5655
|
+
var VERSION = true ? "0.31.0" : "0.0.0-dev";
|
|
5395
5656
|
|
|
5396
5657
|
// src/rest/admin-status.ts
|
|
5397
5658
|
var DAY = 864e5;
|
|
@@ -5831,6 +6092,55 @@ async function route(cms, ctx, method, segments, options) {
|
|
|
5831
6092
|
}
|
|
5832
6093
|
return { body: await cms.runJobs() };
|
|
5833
6094
|
}
|
|
6095
|
+
if (first === "admin" && second === "email") {
|
|
6096
|
+
if (!ctx.user) throw new UnauthorizedError();
|
|
6097
|
+
if (ctx.user.role !== "admin" || ctx.user.apiKey) throw new ForbiddenError();
|
|
6098
|
+
const [, , action, extra] = segments;
|
|
6099
|
+
if (extra !== void 0) throw new HttpError("Not found", 404);
|
|
6100
|
+
if (action === void 0) {
|
|
6101
|
+
if (method !== "GET") throw methodNotAllowed(ctx, "GET");
|
|
6102
|
+
return { body: adminEmail(cms) };
|
|
6103
|
+
}
|
|
6104
|
+
if (method !== "POST") throw methodNotAllowed(ctx, "POST");
|
|
6105
|
+
if (action === "verify") return { body: await verifyEmail(cms) };
|
|
6106
|
+
if (action === "test") {
|
|
6107
|
+
const body = await readJson(ctx.request);
|
|
6108
|
+
return { body: await sendTestEmail(cms, ctx.user, body.to, body.locale) };
|
|
6109
|
+
}
|
|
6110
|
+
throw new HttpError("Not found", 404);
|
|
6111
|
+
}
|
|
6112
|
+
if (first === "admin" && second === "deliveries") {
|
|
6113
|
+
if (!ctx.user) throw new UnauthorizedError();
|
|
6114
|
+
if (ctx.user.role !== "admin" || ctx.user.apiKey) throw new ForbiddenError();
|
|
6115
|
+
const [, , kind, id2, action, extra] = segments;
|
|
6116
|
+
if (kind === void 0) {
|
|
6117
|
+
if (method !== "GET") throw methodNotAllowed(ctx, "GET");
|
|
6118
|
+
const k = ctx.url.searchParams.get("kind") ?? "webhook";
|
|
6119
|
+
const state = ctx.url.searchParams.get("state") === "pending" ? "pending" : "failed";
|
|
6120
|
+
if (!isDeliveryKind(k)) throw new HttpError("kind must be webhook or email", 400);
|
|
6121
|
+
const page = Math.max(1, Number.parseInt(ctx.url.searchParams.get("page") ?? "1", 10) || 1);
|
|
6122
|
+
return { body: await listDeliveries(cms, k, state, page) };
|
|
6123
|
+
}
|
|
6124
|
+
if (!isDeliveryKind(kind) || extra !== void 0) throw new HttpError("Not found", 404);
|
|
6125
|
+
if (id2 === "retry" && action === void 0) {
|
|
6126
|
+
if (method !== "POST") throw methodNotAllowed(ctx, "POST");
|
|
6127
|
+
return { body: await retryFailedDeliveries(cms, kind) };
|
|
6128
|
+
}
|
|
6129
|
+
if (id2 === void 0) {
|
|
6130
|
+
if (method !== "DELETE") throw methodNotAllowed(ctx, "DELETE");
|
|
6131
|
+
return { body: await deleteFailedDeliveries(cms, kind) };
|
|
6132
|
+
}
|
|
6133
|
+
const parsed = parseId(id2);
|
|
6134
|
+
if (parsed === void 0) throw new HttpError("Not found", 404);
|
|
6135
|
+
if (action === "retry") {
|
|
6136
|
+
if (method !== "POST") throw methodNotAllowed(ctx, "POST");
|
|
6137
|
+
return { body: await cms.retryDelivery(kind, parsed) };
|
|
6138
|
+
}
|
|
6139
|
+
if (action !== void 0) throw new HttpError("Not found", 404);
|
|
6140
|
+
if (method !== "DELETE") throw methodNotAllowed(ctx, "DELETE");
|
|
6141
|
+
await deleteDelivery(cms, kind, parsed);
|
|
6142
|
+
return { body: { deleted: 1 } };
|
|
6143
|
+
}
|
|
5834
6144
|
if (first === "admin") {
|
|
5835
6145
|
if (method !== "GET") throw methodNotAllowed(ctx, "GET");
|
|
5836
6146
|
if (!ctx.user) throw new UnauthorizedError();
|
|
@@ -6256,11 +6566,11 @@ function errorResponse(cms, error, headers, production) {
|
|
|
6256
6566
|
if (error instanceof UnauthorizedError) headers.set("www-authenticate", "Bearer");
|
|
6257
6567
|
return new Response(JSON.stringify({ errors }), { status: error.status, headers });
|
|
6258
6568
|
}
|
|
6259
|
-
const
|
|
6569
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
6260
6570
|
cms.logger.error(
|
|
6261
|
-
`REST request failed: ${error instanceof Error ? error.stack ??
|
|
6571
|
+
`REST request failed: ${error instanceof Error ? error.stack ?? message2 : message2}`
|
|
6262
6572
|
);
|
|
6263
|
-
const body = { errors: [{ message: production ? "Internal Server Error" :
|
|
6573
|
+
const body = { errors: [{ message: production ? "Internal Server Error" : message2 }] };
|
|
6264
6574
|
return new Response(JSON.stringify(body), { status: 500, headers });
|
|
6265
6575
|
}
|
|
6266
6576
|
|