@actuate-media/cms-core 0.1.0 → 0.2.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/LICENSE +21 -0
- package/dist/__tests__/actions/document-crud.test.js +1 -1
- package/dist/__tests__/actions/document-crud.test.js.map +1 -1
- package/dist/__tests__/scheduling/scheduling.test.js +1 -1
- package/dist/__tests__/scheduling/scheduling.test.js.map +1 -1
- package/dist/__tests__/security/access.test.js +1 -1
- package/dist/__tests__/security/access.test.js.map +1 -1
- package/dist/__tests__/security/reauth.test.js +1 -1
- package/dist/__tests__/security/reauth.test.js.map +1 -1
- package/dist/__tests__/security/sanitize.test.js +1 -1
- package/dist/__tests__/security/sanitize.test.js.map +1 -1
- package/dist/__tests__/webhooks/webhooks.test.js +2 -2
- package/dist/__tests__/webhooks/webhooks.test.js.map +1 -1
- package/dist/actions.js +4 -4
- package/dist/actions.js.map +1 -1
- package/dist/api/handler-factory.d.ts.map +1 -1
- package/dist/api/handler-factory.js +26 -7
- package/dist/api/handler-factory.js.map +1 -1
- package/dist/api/handlers.d.ts +1 -1
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +339 -75
- package/dist/api/handlers.js.map +1 -1
- package/dist/api/index.d.ts +3 -3
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -2
- package/dist/api/index.js.map +1 -1
- package/dist/auth/index.d.ts +10 -10
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +8 -8
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/oauth.d.ts +1 -1
- package/dist/auth/oauth.d.ts.map +1 -1
- package/dist/auth/oauth.js +1 -1
- package/dist/auth/oauth.js.map +1 -1
- package/dist/auth/password.d.ts +2 -2
- package/dist/auth/password.d.ts.map +1 -1
- package/dist/auth/password.js +1 -1
- package/dist/auth/password.js.map +1 -1
- package/dist/auth/providers/github.d.ts +1 -1
- package/dist/auth/providers/github.d.ts.map +1 -1
- package/dist/auth/providers/google.d.ts +1 -1
- package/dist/auth/providers/google.d.ts.map +1 -1
- package/dist/auth/providers/microsoft.d.ts +1 -1
- package/dist/auth/providers/microsoft.d.ts.map +1 -1
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/codegen/index.d.ts.map +1 -1
- package/dist/codegen/index.js +2 -2
- package/dist/codegen/index.js.map +1 -1
- package/dist/collections/index.d.ts +1 -1
- package/dist/collections/index.d.ts.map +1 -1
- package/dist/config/define.d.ts +8 -0
- package/dist/config/define.d.ts.map +1 -0
- package/dist/config/define.js +7 -0
- package/dist/config/define.js.map +1 -0
- package/dist/config/index.d.ts +3 -3
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/types.d.ts +25 -3
- package/dist/config/types.d.ts.map +1 -1
- package/dist/content/index.d.ts +7 -7
- package/dist/content/index.d.ts.map +1 -1
- package/dist/content/index.js +4 -4
- package/dist/content/index.js.map +1 -1
- package/dist/db/adapters/mysql.js +1 -1
- package/dist/db/adapters/mysql.js.map +1 -1
- package/dist/db/adapters/postgres.js +1 -1
- package/dist/db/adapters/postgres.js.map +1 -1
- package/dist/db/adapters/sqlite.js +1 -1
- package/dist/db/adapters/sqlite.js.map +1 -1
- package/dist/fields/index.d.ts +1 -1
- package/dist/fields/index.d.ts.map +1 -1
- package/dist/forms/index.d.ts +4 -4
- package/dist/forms/index.d.ts.map +1 -1
- package/dist/forms/index.js +2 -2
- package/dist/forms/index.js.map +1 -1
- package/dist/graphql/index.d.ts +1 -1
- package/dist/graphql/index.d.ts.map +1 -1
- package/dist/graphql/index.js +4 -4
- package/dist/graphql/index.js.map +1 -1
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/index.d.ts +72 -72
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +40 -40
- package/dist/index.js.map +1 -1
- package/dist/media/index.d.ts +2 -2
- package/dist/media/index.d.ts.map +1 -1
- package/dist/media/index.js +1 -1
- package/dist/media/index.js.map +1 -1
- package/dist/middleware.d.ts +10 -2
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +1 -1
- package/dist/middleware.js.map +1 -1
- package/dist/next/preview.js +1 -1
- package/dist/next/preview.js.map +1 -1
- package/dist/next.d.ts +2 -2
- package/dist/next.d.ts.map +1 -1
- package/dist/next.js +31 -1
- package/dist/next.js.map +1 -1
- package/dist/search/index.js +1 -1
- package/dist/search/index.js.map +1 -1
- package/dist/security/access.d.ts +1 -1
- package/dist/security/access.d.ts.map +1 -1
- package/dist/security/audit.js +2 -2
- package/dist/security/audit.js.map +1 -1
- package/dist/security/captcha.d.ts +32 -0
- package/dist/security/captcha.d.ts.map +1 -0
- package/dist/security/captcha.js +101 -0
- package/dist/security/captcha.js.map +1 -0
- package/dist/security/index.d.ts +32 -30
- package/dist/security/index.d.ts.map +1 -1
- package/dist/security/index.js +20 -19
- package/dist/security/index.js.map +1 -1
- package/dist/security/middleware.d.ts +2 -2
- package/dist/security/middleware.d.ts.map +1 -1
- package/dist/security/middleware.js +2 -2
- package/dist/security/middleware.js.map +1 -1
- package/dist/security/reauth.js +2 -2
- package/dist/security/reauth.js.map +1 -1
- package/dist/seo/index.d.ts +8 -8
- package/dist/seo/index.d.ts.map +1 -1
- package/dist/seo/index.js +4 -4
- package/dist/seo/index.js.map +1 -1
- package/dist/setup/index.js +1 -1
- package/dist/setup/index.js.map +1 -1
- package/dist/upgrade/index.d.ts +6 -6
- package/dist/upgrade/index.d.ts.map +1 -1
- package/dist/upgrade/index.js +3 -3
- package/dist/upgrade/index.js.map +1 -1
- package/dist/upgrade/upgrade-pr.d.ts +1 -1
- package/dist/upgrade/upgrade-pr.d.ts.map +1 -1
- package/dist/upgrade/upgrade-pr.js +107 -17
- package/dist/upgrade/upgrade-pr.js.map +1 -1
- package/dist/upgrade/version-check.d.ts +10 -2
- package/dist/upgrade/version-check.d.ts.map +1 -1
- package/dist/upgrade/version-check.js +57 -11
- package/dist/upgrade/version-check.js.map +1 -1
- package/dist/webhooks/index.js +2 -2
- package/dist/webhooks/index.js.map +1 -1
- package/dist/workflow/index.js +1 -1
- package/dist/workflow/index.js.map +1 -1
- package/package.json +21 -13
- package/prisma/cms-schema.prisma +237 -0
- package/prisma/migrations/0001_init/migration.sql +384 -0
- package/prisma/migrations/0002_folders/migration.sql +39 -0
- package/prisma/migrations/0003_search_and_webhooks/migration.sql +50 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +485 -0
- package/prisma/seed.ts +82 -0
package/dist/api/handlers.js
CHANGED
|
@@ -1,18 +1,28 @@
|
|
|
1
|
-
import { listDocuments, getDocument, createDocument, updateDocument, deleteDocument, getGlobal, updateGlobal, } from '../actions';
|
|
2
|
-
import { verifyPassword } from '../auth/password';
|
|
3
|
-
import { createSession, verifySession, revokeSession } from '../auth/session';
|
|
4
|
-
import { checkSetupRequired, createInitialAdmin } from '../setup/index';
|
|
5
|
-
import { getDB } from '../db';
|
|
6
|
-
import { generateCodeVerifier, generateCodeChallenge, generateState, getAuthorizationUrl, handleOAuthCallback, } from '../auth/oauth';
|
|
7
|
-
import { optimizeImage, formatBytes } from '../media/optimize';
|
|
8
|
-
import { generateToken as generateCsrfToken } from '../security/csrf';
|
|
9
|
-
import { logEvent } from '../security/audit';
|
|
10
|
-
import { applyFieldAccess } from '../security/access';
|
|
11
|
-
import { createPreviewAdapter } from '../preview/index';
|
|
12
|
-
import { schedulingCronHandler } from '../scheduling/index';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
1
|
+
import { listDocuments, getDocument, createDocument, updateDocument, deleteDocument, getGlobal, updateGlobal, } from '../actions.js';
|
|
2
|
+
import { verifyPassword } from '../auth/password.js';
|
|
3
|
+
import { createSession, verifySession, revokeSession } from '../auth/session.js';
|
|
4
|
+
import { checkSetupRequired, createInitialAdmin } from '../setup/index.js';
|
|
5
|
+
import { getDB } from '../db.js';
|
|
6
|
+
import { generateCodeVerifier, generateCodeChallenge, generateState, getAuthorizationUrl, handleOAuthCallback, } from '../auth/oauth.js';
|
|
7
|
+
import { optimizeImage, formatBytes } from '../media/optimize.js';
|
|
8
|
+
import { generateToken as generateCsrfToken } from '../security/csrf.js';
|
|
9
|
+
import { logEvent } from '../security/audit.js';
|
|
10
|
+
import { applyFieldAccess } from '../security/access.js';
|
|
11
|
+
import { createPreviewAdapter } from '../preview/index.js';
|
|
12
|
+
import { schedulingCronHandler } from '../scheduling/index.js';
|
|
13
|
+
import { verifyCaptcha, getCaptchaConfig } from '../security/captcha.js';
|
|
14
|
+
import { checkForUpdates } from '../upgrade/version-check.js';
|
|
15
|
+
import { createUpgradePR } from '../upgrade/upgrade-pr.js';
|
|
16
|
+
import { encryptField, decryptField } from '../security/encrypted-fields.js';
|
|
17
|
+
import { createRateLimiter } from '../security/rate-limit.js';
|
|
18
|
+
import { generateOpenAPISpec } from './openapi.js';
|
|
19
|
+
import { createSSEPresenceAdapter } from '../presence/index.js';
|
|
20
|
+
// Opaque dynamic import so Turbopack/webpack won't statically analyze the specifier.
|
|
21
|
+
// Returns { put, del, ... } from @vercel/blob when available.
|
|
22
|
+
async function importBlobStorage() {
|
|
23
|
+
const mod = '@vercel/' + 'blob';
|
|
24
|
+
return import(/* webpackIgnore: true */ mod);
|
|
25
|
+
}
|
|
16
26
|
const SECURITY_HEADERS = {
|
|
17
27
|
'Content-Type': 'application/json',
|
|
18
28
|
'X-Content-Type-Options': 'nosniff',
|
|
@@ -29,6 +39,9 @@ function errorResponse(message, status) {
|
|
|
29
39
|
return json({ error: message }, status);
|
|
30
40
|
}
|
|
31
41
|
function internalError(err, context) {
|
|
42
|
+
if (err instanceof ModelNotAvailableError) {
|
|
43
|
+
return modelNotAvailable(err.model);
|
|
44
|
+
}
|
|
32
45
|
const msg = err instanceof Error ? err.message : String(err);
|
|
33
46
|
console.error(`[actuate][api]${context ? ` ${context}:` : ''} ${msg}`);
|
|
34
47
|
return errorResponse('Internal server error', 500);
|
|
@@ -37,6 +50,38 @@ function clampPageSize(raw, max = 100, fallback = 20) {
|
|
|
37
50
|
const n = Number(raw) || fallback;
|
|
38
51
|
return Math.min(Math.max(1, n), max);
|
|
39
52
|
}
|
|
53
|
+
function hasModel(d, name) {
|
|
54
|
+
try {
|
|
55
|
+
return d[name] && typeof d[name].findMany === 'function';
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function modelNotAvailable(name) {
|
|
62
|
+
return errorResponse(`The "${name}" model is not available in your Prisma schema. `
|
|
63
|
+
+ 'See https://actuatecms.dev/docs/database-setup for required models.', 501);
|
|
64
|
+
}
|
|
65
|
+
async function safeCount(model, where) {
|
|
66
|
+
try {
|
|
67
|
+
if (!model?.count)
|
|
68
|
+
return 0;
|
|
69
|
+
return await model.count(where ? { where } : undefined);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function safeFindMany(model, args) {
|
|
76
|
+
try {
|
|
77
|
+
if (!model?.findMany)
|
|
78
|
+
return [];
|
|
79
|
+
return await model.findMany(args);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
40
85
|
function isAllowedStorageUrl(url) {
|
|
41
86
|
try {
|
|
42
87
|
const parsed = new URL(url);
|
|
@@ -63,11 +108,18 @@ const ALLOWED_SORT_FIELDS = new Set([
|
|
|
63
108
|
'createdAt', 'updatedAt', 'publishedAt', 'status', 'collection',
|
|
64
109
|
]);
|
|
65
110
|
function getSessionSecret() {
|
|
66
|
-
const secret = process.env.CMS_SECRET
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (secret
|
|
70
|
-
throw new Error('CMS_SECRET
|
|
111
|
+
const secret = process.env.CMS_SECRET
|
|
112
|
+
?? process.env.CMS_SESSION_SECRET
|
|
113
|
+
?? globalThis.__actuateConfig?.secret;
|
|
114
|
+
if (!secret) {
|
|
115
|
+
throw new Error('[Actuate CMS] Missing CMS secret. Set the CMS_SECRET environment variable (min 32 characters) '
|
|
116
|
+
+ 'or pass `secret` in your actuate.config.ts. '
|
|
117
|
+
+ 'Generate one with: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"');
|
|
118
|
+
}
|
|
119
|
+
if (secret.length < 32) {
|
|
120
|
+
throw new Error('[Actuate CMS] CMS secret must be at least 32 characters (got ' + secret.length + '). '
|
|
121
|
+
+ 'Generate a secure value with: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"');
|
|
122
|
+
}
|
|
71
123
|
return secret;
|
|
72
124
|
}
|
|
73
125
|
async function extractSession(request) {
|
|
@@ -85,13 +137,15 @@ async function extractSession(request) {
|
|
|
85
137
|
return null;
|
|
86
138
|
try {
|
|
87
139
|
const payload = await verifySession(token, { secret: getSessionSecret() });
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
140
|
+
const d = getDB();
|
|
141
|
+
if (hasModel(d, 'session')) {
|
|
142
|
+
const dbSession = await d.session.findUnique({
|
|
143
|
+
where: { id: payload.sessionId },
|
|
144
|
+
select: { revokedAt: true },
|
|
145
|
+
});
|
|
146
|
+
if (!dbSession || dbSession.revokedAt)
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
95
149
|
return payload;
|
|
96
150
|
}
|
|
97
151
|
catch {
|
|
@@ -137,8 +191,35 @@ async function checkRateLimitAsync(limiter, key) {
|
|
|
137
191
|
const result = await limiter.check(key);
|
|
138
192
|
return result.allowed;
|
|
139
193
|
}
|
|
194
|
+
function getAdminPath() {
|
|
195
|
+
return process.env.ACTUATE_ADMIN_PATH
|
|
196
|
+
?? globalThis.__actuateConfig?.admin?.path
|
|
197
|
+
?? '/admin';
|
|
198
|
+
}
|
|
199
|
+
class ModelNotAvailableError extends Error {
|
|
200
|
+
model;
|
|
201
|
+
constructor(model) {
|
|
202
|
+
super(`Model "${model}" is not available in the Prisma schema.`);
|
|
203
|
+
this.model = model;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
140
206
|
export function registerCMSRoutes(router) {
|
|
141
|
-
const
|
|
207
|
+
const rawDb = () => getDB();
|
|
208
|
+
const db = () => {
|
|
209
|
+
const d = rawDb();
|
|
210
|
+
return new Proxy(d, {
|
|
211
|
+
get(target, prop) {
|
|
212
|
+
const val = target[prop];
|
|
213
|
+
if (val !== undefined)
|
|
214
|
+
return val;
|
|
215
|
+
return new Proxy({}, {
|
|
216
|
+
get() {
|
|
217
|
+
throw new ModelNotAvailableError(String(prop));
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
};
|
|
142
223
|
const presenceAdapter = createSSEPresenceAdapter();
|
|
143
224
|
// ---------------------------------------------------------------------------
|
|
144
225
|
// CSRF token endpoint
|
|
@@ -203,7 +284,17 @@ export function registerCMSRoutes(router) {
|
|
|
203
284
|
if (!(await checkRateLimitAsync(loginLimiter, `login:${clientIp}`))) {
|
|
204
285
|
return errorResponse('Too many login attempts. Please try again later.', 429);
|
|
205
286
|
}
|
|
206
|
-
const
|
|
287
|
+
const captchaConfig = getCaptchaConfig();
|
|
288
|
+
if (captchaConfig.provider !== 'none') {
|
|
289
|
+
const captchaResult = await verifyCaptcha(body.captchaToken ?? '', captchaConfig, clientIp);
|
|
290
|
+
if (!captchaResult.success) {
|
|
291
|
+
return errorResponse('CAPTCHA verification failed. Please try again.', 403);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
const d = db();
|
|
295
|
+
if (!hasModel(d, 'user'))
|
|
296
|
+
return modelNotAvailable('user');
|
|
297
|
+
const user = await d.user.findFirst({
|
|
207
298
|
where: { email: email.toLowerCase().trim() },
|
|
208
299
|
});
|
|
209
300
|
if (!user) {
|
|
@@ -276,7 +367,7 @@ export function registerCMSRoutes(router) {
|
|
|
276
367
|
return response;
|
|
277
368
|
}
|
|
278
369
|
catch (err) {
|
|
279
|
-
return
|
|
370
|
+
return internalError(err, 'login');
|
|
280
371
|
}
|
|
281
372
|
});
|
|
282
373
|
router.post('/auth/logout', async (request) => {
|
|
@@ -295,7 +386,7 @@ export function registerCMSRoutes(router) {
|
|
|
295
386
|
return response;
|
|
296
387
|
}
|
|
297
388
|
catch (err) {
|
|
298
|
-
return
|
|
389
|
+
return internalError(err, 'logout');
|
|
299
390
|
}
|
|
300
391
|
});
|
|
301
392
|
router.get('/auth/me', async (request) => {
|
|
@@ -313,7 +404,7 @@ export function registerCMSRoutes(router) {
|
|
|
313
404
|
return json({ data: user });
|
|
314
405
|
}
|
|
315
406
|
catch (err) {
|
|
316
|
-
return
|
|
407
|
+
return internalError(err, 'auth/me');
|
|
317
408
|
}
|
|
318
409
|
});
|
|
319
410
|
// ---------------------------------------------------------------------------
|
|
@@ -324,7 +415,7 @@ export function registerCMSRoutes(router) {
|
|
|
324
415
|
const auth = await requireAuth(request);
|
|
325
416
|
if (auth.error)
|
|
326
417
|
return auth.error;
|
|
327
|
-
const { generateTOTPSecret, generateTOTPUri, generateBackupCodes } = await import('../auth/totp');
|
|
418
|
+
const { generateTOTPSecret, generateTOTPUri, generateBackupCodes } = await import('../auth/totp.js');
|
|
328
419
|
const user = await db().user.findUnique({ where: { id: auth.session.userId }, select: { email: true, totpEnabled: true } });
|
|
329
420
|
if (!user)
|
|
330
421
|
return errorResponse('User not found', 404);
|
|
@@ -348,7 +439,7 @@ export function registerCMSRoutes(router) {
|
|
|
348
439
|
const body = await request.json();
|
|
349
440
|
if (!body.code)
|
|
350
441
|
return errorResponse('Code is required', 400);
|
|
351
|
-
const { verifyTOTP } = await import('../auth/totp');
|
|
442
|
+
const { verifyTOTP } = await import('../auth/totp.js');
|
|
352
443
|
const user = await db().user.findUnique({ where: { id: auth.session.userId }, select: { totpSecret: true } });
|
|
353
444
|
if (!user?.totpSecret)
|
|
354
445
|
return errorResponse('TOTP not set up', 400);
|
|
@@ -379,7 +470,7 @@ export function registerCMSRoutes(router) {
|
|
|
379
470
|
const body = await request.json();
|
|
380
471
|
if (!body.userId || !body.code)
|
|
381
472
|
return errorResponse('userId and code are required', 400);
|
|
382
|
-
const { verifyTOTP } = await import('../auth/totp');
|
|
473
|
+
const { verifyTOTP } = await import('../auth/totp.js');
|
|
383
474
|
const user = await db().user.findUnique({ where: { id: body.userId }, select: { id: true, email: true, role: true, totpSecret: true, totpEnabled: true, isActive: true } });
|
|
384
475
|
if (!user || !user.isActive || !user.totpEnabled || !user.totpSecret)
|
|
385
476
|
return errorResponse('Invalid request', 400);
|
|
@@ -422,7 +513,7 @@ export function registerCMSRoutes(router) {
|
|
|
422
513
|
};
|
|
423
514
|
const codeVerifier = generateCodeVerifier();
|
|
424
515
|
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
425
|
-
const state = await generateState(provider, codeVerifier,
|
|
516
|
+
const state = await generateState(provider, codeVerifier, getAdminPath(), secret);
|
|
426
517
|
const url = getAuthorizationUrl(provider, oauthProviders[provider], state, codeChallenge);
|
|
427
518
|
return json({ data: { url } });
|
|
428
519
|
}
|
|
@@ -444,7 +535,7 @@ export function registerCMSRoutes(router) {
|
|
|
444
535
|
const desc = url.searchParams.get('error_description') ?? errorParam;
|
|
445
536
|
return new Response(null, {
|
|
446
537
|
status: 302,
|
|
447
|
-
headers: { Location:
|
|
538
|
+
headers: { Location: `${getAdminPath()}?error=${encodeURIComponent(desc)}` },
|
|
448
539
|
});
|
|
449
540
|
}
|
|
450
541
|
if (!code || !stateToken) {
|
|
@@ -473,7 +564,7 @@ export function registerCMSRoutes(router) {
|
|
|
473
564
|
return new Response(null, {
|
|
474
565
|
status: 302,
|
|
475
566
|
headers: {
|
|
476
|
-
Location:
|
|
567
|
+
Location: getAdminPath(),
|
|
477
568
|
'Set-Cookie': cookieFlags.join('; '),
|
|
478
569
|
},
|
|
479
570
|
});
|
|
@@ -482,7 +573,7 @@ export function registerCMSRoutes(router) {
|
|
|
482
573
|
const message = err instanceof Error ? err.message : 'OAuth callback failed';
|
|
483
574
|
return new Response(null, {
|
|
484
575
|
status: 302,
|
|
485
|
-
headers: { Location:
|
|
576
|
+
headers: { Location: `${getAdminPath()}?error=${encodeURIComponent(message)}` },
|
|
486
577
|
});
|
|
487
578
|
}
|
|
488
579
|
});
|
|
@@ -572,7 +663,7 @@ export function registerCMSRoutes(router) {
|
|
|
572
663
|
return json({ data: doc }, 201);
|
|
573
664
|
}
|
|
574
665
|
catch (err) {
|
|
575
|
-
return
|
|
666
|
+
return internalError(err, 'create document');
|
|
576
667
|
}
|
|
577
668
|
});
|
|
578
669
|
router.put('/collections/:slug/:id', async (request, params) => {
|
|
@@ -594,7 +685,7 @@ export function registerCMSRoutes(router) {
|
|
|
594
685
|
return json({ data: doc });
|
|
595
686
|
}
|
|
596
687
|
catch (err) {
|
|
597
|
-
return
|
|
688
|
+
return internalError(err, 'update document');
|
|
598
689
|
}
|
|
599
690
|
});
|
|
600
691
|
router.delete('/collections/:slug/:id', async (request, params) => {
|
|
@@ -615,7 +706,7 @@ export function registerCMSRoutes(router) {
|
|
|
615
706
|
return json({ data: { success: true } });
|
|
616
707
|
}
|
|
617
708
|
catch (err) {
|
|
618
|
-
return
|
|
709
|
+
return internalError(err, 'delete document');
|
|
619
710
|
}
|
|
620
711
|
});
|
|
621
712
|
// ---------------------------------------------------------------------------
|
|
@@ -721,8 +812,7 @@ export function registerCMSRoutes(router) {
|
|
|
721
812
|
const storageKey = `actuate/media/${Date.now()}-${sanitizedName}`;
|
|
722
813
|
let publicUrl = '';
|
|
723
814
|
try {
|
|
724
|
-
|
|
725
|
-
const blob = await import('@vercel/blob');
|
|
815
|
+
const blob = await importBlobStorage();
|
|
726
816
|
const result = await blob.put(storageKey, uploadBuffer, {
|
|
727
817
|
access: 'public',
|
|
728
818
|
contentType: finalMimeType,
|
|
@@ -808,8 +898,7 @@ export function registerCMSRoutes(router) {
|
|
|
808
898
|
const newStorageKey = `actuate/media/${Date.now()}-${sanitizedName}`;
|
|
809
899
|
let newPublicUrl = '';
|
|
810
900
|
try {
|
|
811
|
-
|
|
812
|
-
const blob = await import('@vercel/blob');
|
|
901
|
+
const blob = await importBlobStorage();
|
|
813
902
|
const uploadResult = await blob.put(newStorageKey, result.buffer, {
|
|
814
903
|
access: 'public',
|
|
815
904
|
contentType: result.mimeType,
|
|
@@ -893,8 +982,7 @@ export function registerCMSRoutes(router) {
|
|
|
893
982
|
return errorResponse('Media not found', 404);
|
|
894
983
|
}
|
|
895
984
|
try {
|
|
896
|
-
|
|
897
|
-
const blob = await import('@vercel/blob');
|
|
985
|
+
const blob = await importBlobStorage();
|
|
898
986
|
await blob.del(media.storageKey);
|
|
899
987
|
}
|
|
900
988
|
catch {
|
|
@@ -915,6 +1003,172 @@ export function registerCMSRoutes(router) {
|
|
|
915
1003
|
// ---------------------------------------------------------------------------
|
|
916
1004
|
// Setup routes
|
|
917
1005
|
// ---------------------------------------------------------------------------
|
|
1006
|
+
// ---------------------------------------------------------------------------
|
|
1007
|
+
// Update routes
|
|
1008
|
+
// ---------------------------------------------------------------------------
|
|
1009
|
+
const UPDATE_CONFIG_KEY = '_cms_update_config';
|
|
1010
|
+
async function getUpdateConfig() {
|
|
1011
|
+
const encKey = process.env.CMS_ENCRYPTION_KEY;
|
|
1012
|
+
if (!encKey)
|
|
1013
|
+
return {};
|
|
1014
|
+
try {
|
|
1015
|
+
const doc = await db().document.findFirst({
|
|
1016
|
+
where: { collection: UPDATE_CONFIG_KEY, deletedAt: null },
|
|
1017
|
+
});
|
|
1018
|
+
if (!doc?.data)
|
|
1019
|
+
return {};
|
|
1020
|
+
const raw = doc.data;
|
|
1021
|
+
const result = {};
|
|
1022
|
+
if (raw.githubToken)
|
|
1023
|
+
result.githubToken = await decryptField(raw.githubToken, encKey);
|
|
1024
|
+
if (raw.githubRepo)
|
|
1025
|
+
result.githubRepo = await decryptField(raw.githubRepo, encKey);
|
|
1026
|
+
return result;
|
|
1027
|
+
}
|
|
1028
|
+
catch {
|
|
1029
|
+
return {};
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
router.get('/updates/check', async (request) => {
|
|
1033
|
+
try {
|
|
1034
|
+
const session = await extractSession(request);
|
|
1035
|
+
if (!session)
|
|
1036
|
+
return errorResponse('Unauthorized', 401);
|
|
1037
|
+
const coreVersion = globalThis.__actuateCoreVersion ?? '0.1.0';
|
|
1038
|
+
const info = await checkForUpdates(coreVersion);
|
|
1039
|
+
const saved = await getUpdateConfig();
|
|
1040
|
+
return json({
|
|
1041
|
+
data: {
|
|
1042
|
+
...info,
|
|
1043
|
+
hasGithubToken: !!(saved.githubToken || process.env.ACTUATE_GITHUB_TOKEN),
|
|
1044
|
+
githubRepo: saved.githubRepo || process.env.ACTUATE_GITHUB_REPO || '',
|
|
1045
|
+
},
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
catch (err) {
|
|
1049
|
+
return internalError(err);
|
|
1050
|
+
}
|
|
1051
|
+
});
|
|
1052
|
+
router.get('/updates/config', async (request) => {
|
|
1053
|
+
try {
|
|
1054
|
+
const session = await extractSession(request);
|
|
1055
|
+
if (!session || session.role !== 'admin') {
|
|
1056
|
+
return errorResponse('Unauthorized — admin only', 403);
|
|
1057
|
+
}
|
|
1058
|
+
const saved = await getUpdateConfig();
|
|
1059
|
+
return json({
|
|
1060
|
+
data: {
|
|
1061
|
+
hasGithubToken: !!(saved.githubToken || process.env.ACTUATE_GITHUB_TOKEN),
|
|
1062
|
+
githubRepo: saved.githubRepo || process.env.ACTUATE_GITHUB_REPO || '',
|
|
1063
|
+
},
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
catch (err) {
|
|
1067
|
+
return internalError(err);
|
|
1068
|
+
}
|
|
1069
|
+
});
|
|
1070
|
+
router.put('/updates/config', async (request) => {
|
|
1071
|
+
try {
|
|
1072
|
+
const session = await extractSession(request);
|
|
1073
|
+
if (!session || session.role !== 'admin') {
|
|
1074
|
+
return errorResponse('Unauthorized — admin only', 403);
|
|
1075
|
+
}
|
|
1076
|
+
const encKey = process.env.CMS_ENCRYPTION_KEY;
|
|
1077
|
+
if (!encKey) {
|
|
1078
|
+
return errorResponse('CMS_ENCRYPTION_KEY is required to store encrypted credentials.', 400);
|
|
1079
|
+
}
|
|
1080
|
+
const body = await request.json();
|
|
1081
|
+
if (body.githubRepo && !/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(body.githubRepo)) {
|
|
1082
|
+
return errorResponse('Invalid repository format. Use owner/repo.', 400);
|
|
1083
|
+
}
|
|
1084
|
+
const encrypted = {};
|
|
1085
|
+
if (body.githubToken)
|
|
1086
|
+
encrypted.githubToken = await encryptField(body.githubToken, encKey);
|
|
1087
|
+
if (body.githubRepo)
|
|
1088
|
+
encrypted.githubRepo = await encryptField(body.githubRepo, encKey);
|
|
1089
|
+
await db().document.upsert({
|
|
1090
|
+
where: { collection_slug: { collection: UPDATE_CONFIG_KEY, slug: UPDATE_CONFIG_KEY } },
|
|
1091
|
+
create: {
|
|
1092
|
+
collection: UPDATE_CONFIG_KEY,
|
|
1093
|
+
slug: UPDATE_CONFIG_KEY,
|
|
1094
|
+
title: 'Update Configuration',
|
|
1095
|
+
data: encrypted,
|
|
1096
|
+
status: 'PUBLISHED',
|
|
1097
|
+
createdById: session.userId,
|
|
1098
|
+
updatedById: session.userId,
|
|
1099
|
+
},
|
|
1100
|
+
update: {
|
|
1101
|
+
data: encrypted,
|
|
1102
|
+
updatedById: session.userId,
|
|
1103
|
+
},
|
|
1104
|
+
});
|
|
1105
|
+
await logEvent({
|
|
1106
|
+
event: 'settings_changed',
|
|
1107
|
+
userId: session.userId,
|
|
1108
|
+
details: { setting: 'update_config' },
|
|
1109
|
+
});
|
|
1110
|
+
return json({ data: { success: true } });
|
|
1111
|
+
}
|
|
1112
|
+
catch (err) {
|
|
1113
|
+
return internalError(err);
|
|
1114
|
+
}
|
|
1115
|
+
});
|
|
1116
|
+
router.post('/updates/apply', async (request) => {
|
|
1117
|
+
try {
|
|
1118
|
+
const session = await extractSession(request);
|
|
1119
|
+
if (!session || session.role !== 'admin') {
|
|
1120
|
+
return errorResponse('Unauthorized — admin only', 403);
|
|
1121
|
+
}
|
|
1122
|
+
const body = await request.json();
|
|
1123
|
+
if (!body.targetVersion) {
|
|
1124
|
+
return errorResponse('targetVersion is required', 400);
|
|
1125
|
+
}
|
|
1126
|
+
if (!/^\d+\.\d+\.\d+(-[\w.]+)?$/.test(body.targetVersion)) {
|
|
1127
|
+
return errorResponse('Invalid version format', 400);
|
|
1128
|
+
}
|
|
1129
|
+
const saved = await getUpdateConfig();
|
|
1130
|
+
const githubToken = saved.githubToken || process.env.ACTUATE_GITHUB_TOKEN;
|
|
1131
|
+
const githubRepo = saved.githubRepo || process.env.ACTUATE_GITHUB_REPO;
|
|
1132
|
+
const owner = githubRepo?.split('/')[0];
|
|
1133
|
+
const repo = githubRepo?.split('/')[1];
|
|
1134
|
+
if (!githubToken) {
|
|
1135
|
+
return errorResponse('GitHub token not configured. Go to Settings > Updates to add one.', 400);
|
|
1136
|
+
}
|
|
1137
|
+
if (!owner || !repo) {
|
|
1138
|
+
return errorResponse('GitHub repository not configured. Go to Settings > Updates to add one.', 400);
|
|
1139
|
+
}
|
|
1140
|
+
const coreVersion = globalThis.__actuateCoreVersion ?? '0.1.0';
|
|
1141
|
+
const result = await createUpgradePR({
|
|
1142
|
+
owner,
|
|
1143
|
+
repo,
|
|
1144
|
+
targetVersion: body.targetVersion,
|
|
1145
|
+
changesDescription: `Upgrade Actuate CMS from ${coreVersion} to ${body.targetVersion}.\n\nRun \`npx prisma migrate deploy\` after merging.`,
|
|
1146
|
+
githubToken,
|
|
1147
|
+
});
|
|
1148
|
+
await logEvent({
|
|
1149
|
+
event: 'cms_update_initiated',
|
|
1150
|
+
userId: session.userId,
|
|
1151
|
+
details: {
|
|
1152
|
+
fromVersion: coreVersion,
|
|
1153
|
+
toVersion: body.targetVersion,
|
|
1154
|
+
prUrl: result.prUrl,
|
|
1155
|
+
},
|
|
1156
|
+
});
|
|
1157
|
+
return json({ data: result });
|
|
1158
|
+
}
|
|
1159
|
+
catch (err) {
|
|
1160
|
+
return internalError(err);
|
|
1161
|
+
}
|
|
1162
|
+
});
|
|
1163
|
+
router.get('/captcha/config', async () => {
|
|
1164
|
+
const captchaConfig = getCaptchaConfig();
|
|
1165
|
+
return json({
|
|
1166
|
+
data: {
|
|
1167
|
+
provider: captchaConfig.provider,
|
|
1168
|
+
siteKey: captchaConfig.provider !== 'none' ? captchaConfig.siteKey : null,
|
|
1169
|
+
},
|
|
1170
|
+
});
|
|
1171
|
+
});
|
|
918
1172
|
router.get('/setup/status', async () => {
|
|
919
1173
|
try {
|
|
920
1174
|
const status = await checkSetupRequired(db());
|
|
@@ -956,11 +1210,12 @@ export function registerCMSRoutes(router) {
|
|
|
956
1210
|
const auth = await requireAuth(request);
|
|
957
1211
|
if (auth.error)
|
|
958
1212
|
return auth.error;
|
|
1213
|
+
const d = db();
|
|
959
1214
|
const [totalDocuments, totalMedia, totalUsers, recentDocuments] = await Promise.all([
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1215
|
+
safeCount(d.document, { deletedAt: null }),
|
|
1216
|
+
safeCount(d.media),
|
|
1217
|
+
safeCount(d.user),
|
|
1218
|
+
safeFindMany(d.document, {
|
|
964
1219
|
where: { deletedAt: null },
|
|
965
1220
|
orderBy: { updatedAt: 'desc' },
|
|
966
1221
|
take: 10,
|
|
@@ -984,20 +1239,21 @@ export function registerCMSRoutes(router) {
|
|
|
984
1239
|
const q = (url.searchParams.get('q') ?? '').trim();
|
|
985
1240
|
if (!q)
|
|
986
1241
|
return json({ data: { documents: [], media: [], users: [] } });
|
|
1242
|
+
const d = db();
|
|
987
1243
|
const [documents, media, users] = await Promise.all([
|
|
988
|
-
|
|
1244
|
+
safeFindMany(d.document, {
|
|
989
1245
|
where: { deletedAt: null, OR: [{ title: { contains: q, mode: 'insensitive' } }, { plainText: { contains: q, mode: 'insensitive' } }] },
|
|
990
1246
|
take: 10,
|
|
991
1247
|
orderBy: { updatedAt: 'desc' },
|
|
992
1248
|
select: { id: true, title: true, slug: true, collection: true, status: true, updatedAt: true },
|
|
993
1249
|
}),
|
|
994
|
-
|
|
1250
|
+
safeFindMany(d.media, {
|
|
995
1251
|
where: { OR: [{ filename: { contains: q, mode: 'insensitive' } }, { altText: { contains: q, mode: 'insensitive' } }] },
|
|
996
1252
|
take: 5,
|
|
997
1253
|
orderBy: { createdAt: 'desc' },
|
|
998
1254
|
select: { id: true, filename: true, altText: true, mimeType: true, storageKey: true },
|
|
999
1255
|
}),
|
|
1000
|
-
|
|
1256
|
+
safeFindMany(d.user, {
|
|
1001
1257
|
where: { isActive: true, OR: [{ name: { contains: q, mode: 'insensitive' } }, { email: { contains: q, mode: 'insensitive' } }] },
|
|
1002
1258
|
take: 5,
|
|
1003
1259
|
select: { id: true, name: true, email: true, role: true },
|
|
@@ -1124,6 +1380,14 @@ export function registerCMSRoutes(router) {
|
|
|
1124
1380
|
if (!body.fields || typeof body.fields !== 'object') {
|
|
1125
1381
|
return errorResponse('Missing or invalid "fields" in request body', 400);
|
|
1126
1382
|
}
|
|
1383
|
+
const captchaConfig = getCaptchaConfig();
|
|
1384
|
+
if (captchaConfig.provider !== 'none') {
|
|
1385
|
+
const ip = request.headers.get('x-forwarded-for')?.split(',')[0]?.trim();
|
|
1386
|
+
const captchaResult = await verifyCaptcha(body.captchaToken ?? '', captchaConfig, ip);
|
|
1387
|
+
if (!captchaResult.success) {
|
|
1388
|
+
return errorResponse('CAPTCHA verification failed. Please try again.', 403);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1127
1391
|
const submission = await db().formSubmission.create({
|
|
1128
1392
|
data: {
|
|
1129
1393
|
formId,
|
|
@@ -1218,7 +1482,7 @@ export function registerCMSRoutes(router) {
|
|
|
1218
1482
|
return json({ data: redirect }, 201);
|
|
1219
1483
|
}
|
|
1220
1484
|
catch (err) {
|
|
1221
|
-
return
|
|
1485
|
+
return internalError(err, 'create redirect');
|
|
1222
1486
|
}
|
|
1223
1487
|
});
|
|
1224
1488
|
router.delete('/redirects/:id', async (request, params) => {
|
|
@@ -1232,7 +1496,7 @@ export function registerCMSRoutes(router) {
|
|
|
1232
1496
|
return json({ data: { success: true } });
|
|
1233
1497
|
}
|
|
1234
1498
|
catch (err) {
|
|
1235
|
-
return
|
|
1499
|
+
return internalError(err, 'delete redirect');
|
|
1236
1500
|
}
|
|
1237
1501
|
});
|
|
1238
1502
|
// ---------------------------------------------------------------------------
|
|
@@ -1369,7 +1633,7 @@ export function registerCMSRoutes(router) {
|
|
|
1369
1633
|
const doc = await db().document.findUnique({ where: { id: params.documentId } });
|
|
1370
1634
|
if (!doc)
|
|
1371
1635
|
return errorResponse('Not found', 404);
|
|
1372
|
-
const { analyzeContent } = await import('../seo/analysis');
|
|
1636
|
+
const { analyzeContent } = await import('../seo/analysis.js');
|
|
1373
1637
|
const data = doc.data || {};
|
|
1374
1638
|
const result = analyzeContent({
|
|
1375
1639
|
title: doc.title || data.title || '',
|
|
@@ -1398,7 +1662,7 @@ export function registerCMSRoutes(router) {
|
|
|
1398
1662
|
const doc = await db().document.findUnique({ where: { id: params.documentId } });
|
|
1399
1663
|
if (!doc)
|
|
1400
1664
|
return errorResponse('Not found', 404);
|
|
1401
|
-
const { calculateReadability, stripHtmlTags } = await import('../seo/analysis');
|
|
1665
|
+
const { calculateReadability, stripHtmlTags } = await import('../seo/analysis.js');
|
|
1402
1666
|
const data = doc.data || {};
|
|
1403
1667
|
const text = stripHtmlTags(data.content || data.body || '');
|
|
1404
1668
|
const result = calculateReadability(text);
|
|
@@ -1454,7 +1718,7 @@ export function registerCMSRoutes(router) {
|
|
|
1454
1718
|
orderBy: { updatedAt: 'desc' },
|
|
1455
1719
|
take: 50,
|
|
1456
1720
|
});
|
|
1457
|
-
const { generateLlmsTxt } = await import('../seo/llms-txt');
|
|
1721
|
+
const { generateLlmsTxt } = await import('../seo/llms-txt.js');
|
|
1458
1722
|
const pages = docs.map((d) => {
|
|
1459
1723
|
const data = d.data || {};
|
|
1460
1724
|
return {
|
|
@@ -1487,7 +1751,7 @@ export function registerCMSRoutes(router) {
|
|
|
1487
1751
|
const doc = await db().document.findUnique({ where: { id: params.documentId } });
|
|
1488
1752
|
if (!doc)
|
|
1489
1753
|
return errorResponse('Not found', 404);
|
|
1490
|
-
const { buildSchemaGraph } = await import('../content/structured-data');
|
|
1754
|
+
const { buildSchemaGraph } = await import('../content/structured-data.js');
|
|
1491
1755
|
const data = doc.data || {};
|
|
1492
1756
|
const graph = buildSchemaGraph({
|
|
1493
1757
|
siteName: 'Actuate CMS',
|
|
@@ -1516,7 +1780,7 @@ export function registerCMSRoutes(router) {
|
|
|
1516
1780
|
const doc = await db().document.findUnique({ where: { id: params.documentId } });
|
|
1517
1781
|
if (!doc)
|
|
1518
1782
|
return errorResponse('Not found', 404);
|
|
1519
|
-
const { generateMetaTags } = await import('../seo/meta-tags');
|
|
1783
|
+
const { generateMetaTags } = await import('../seo/meta-tags.js');
|
|
1520
1784
|
const data = doc.data || {};
|
|
1521
1785
|
const tags = generateMetaTags({
|
|
1522
1786
|
title: data.metaTitle || doc.title || data.title || '',
|
|
@@ -1764,7 +2028,7 @@ export function registerCMSRoutes(router) {
|
|
|
1764
2028
|
return json({ data: { token: session.token, expiresAt: session.expiresAt } });
|
|
1765
2029
|
}
|
|
1766
2030
|
catch (err) {
|
|
1767
|
-
return
|
|
2031
|
+
return internalError(err, 'create preview token');
|
|
1768
2032
|
}
|
|
1769
2033
|
});
|
|
1770
2034
|
router.get('/preview/:collection/:id', async (request, params) => {
|
|
@@ -1786,7 +2050,7 @@ export function registerCMSRoutes(router) {
|
|
|
1786
2050
|
return json({ data });
|
|
1787
2051
|
}
|
|
1788
2052
|
catch (err) {
|
|
1789
|
-
return
|
|
2053
|
+
return internalError(err, 'fetch preview data');
|
|
1790
2054
|
}
|
|
1791
2055
|
});
|
|
1792
2056
|
// ---------------------------------------------------------------------------
|
|
@@ -1800,7 +2064,7 @@ export function registerCMSRoutes(router) {
|
|
|
1800
2064
|
const body = await request.json();
|
|
1801
2065
|
if (!body.stage)
|
|
1802
2066
|
return errorResponse('Stage is required', 400);
|
|
1803
|
-
const { transitionDocument } = await import('../workflow/index');
|
|
2067
|
+
const { transitionDocument } = await import('../workflow/index.js');
|
|
1804
2068
|
const result = await transitionDocument(params.id, body.stage, auth.session.userId, auth.session.role, body.note);
|
|
1805
2069
|
if (!result.success)
|
|
1806
2070
|
return errorResponse(result.error ?? 'Transition failed', 400);
|
|
@@ -1815,7 +2079,7 @@ export function registerCMSRoutes(router) {
|
|
|
1815
2079
|
const auth = await requireAuth(request);
|
|
1816
2080
|
if (auth.error)
|
|
1817
2081
|
return auth.error;
|
|
1818
|
-
const { getAvailableTransitions } = await import('../workflow/index');
|
|
2082
|
+
const { getAvailableTransitions } = await import('../workflow/index.js');
|
|
1819
2083
|
const doc = await db().document.findFirst({
|
|
1820
2084
|
where: { id: params.id, deletedAt: null },
|
|
1821
2085
|
select: { workflowStage: true, reviewerId: true, reviewNote: true },
|
|
@@ -1890,7 +2154,7 @@ export function registerCMSRoutes(router) {
|
|
|
1890
2154
|
return json({ data: doc });
|
|
1891
2155
|
}
|
|
1892
2156
|
catch (err) {
|
|
1893
|
-
return
|
|
2157
|
+
return internalError(err, 'restore version');
|
|
1894
2158
|
}
|
|
1895
2159
|
});
|
|
1896
2160
|
// ---------------------------------------------------------------------------
|
|
@@ -1908,7 +2172,7 @@ export function registerCMSRoutes(router) {
|
|
|
1908
2172
|
return json({ data: result });
|
|
1909
2173
|
}
|
|
1910
2174
|
catch (err) {
|
|
1911
|
-
return
|
|
2175
|
+
return internalError(err, 'scheduling run');
|
|
1912
2176
|
}
|
|
1913
2177
|
});
|
|
1914
2178
|
router.get('/scheduling/calendar', async (request) => {
|
|
@@ -1921,7 +2185,7 @@ export function registerCMSRoutes(router) {
|
|
|
1921
2185
|
const toStr = url.searchParams.get('to');
|
|
1922
2186
|
const from = fromStr ? new Date(fromStr) : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
|
|
1923
2187
|
const to = toStr ? new Date(toStr) : new Date(Date.now() + 90 * 24 * 60 * 60 * 1000);
|
|
1924
|
-
const { getScheduleCalendar } = await import('../scheduling/index');
|
|
2188
|
+
const { getScheduleCalendar } = await import('../scheduling/index.js');
|
|
1925
2189
|
const entries = await getScheduleCalendar(from, to, db());
|
|
1926
2190
|
return json({ data: entries });
|
|
1927
2191
|
}
|
|
@@ -1976,7 +2240,7 @@ export function registerCMSRoutes(router) {
|
|
|
1976
2240
|
const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
|
|
1977
2241
|
if (roleErr)
|
|
1978
2242
|
return roleErr;
|
|
1979
|
-
const { listEndpoints } = await import('../webhooks/index');
|
|
2243
|
+
const { listEndpoints } = await import('../webhooks/index.js');
|
|
1980
2244
|
const endpoints = await listEndpoints();
|
|
1981
2245
|
return json({ data: endpoints });
|
|
1982
2246
|
}
|
|
@@ -1996,7 +2260,7 @@ export function registerCMSRoutes(router) {
|
|
|
1996
2260
|
if (!body.url || !body.events?.length) {
|
|
1997
2261
|
return errorResponse('url and events are required', 400);
|
|
1998
2262
|
}
|
|
1999
|
-
const { createEndpoint } = await import('../webhooks/index');
|
|
2263
|
+
const { createEndpoint } = await import('../webhooks/index.js');
|
|
2000
2264
|
const secret = body.secret || crypto.randomUUID();
|
|
2001
2265
|
const endpoint = await createEndpoint({
|
|
2002
2266
|
url: body.url,
|
|
@@ -2025,7 +2289,7 @@ export function registerCMSRoutes(router) {
|
|
|
2025
2289
|
if (roleErr)
|
|
2026
2290
|
return roleErr;
|
|
2027
2291
|
const body = await request.json();
|
|
2028
|
-
const { updateEndpoint } = await import('../webhooks/index');
|
|
2292
|
+
const { updateEndpoint } = await import('../webhooks/index.js');
|
|
2029
2293
|
const updated = await updateEndpoint(params.id, body);
|
|
2030
2294
|
return json({ data: updated });
|
|
2031
2295
|
}
|
|
@@ -2041,7 +2305,7 @@ export function registerCMSRoutes(router) {
|
|
|
2041
2305
|
const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
|
|
2042
2306
|
if (roleErr)
|
|
2043
2307
|
return roleErr;
|
|
2044
|
-
const { deleteEndpoint } = await import('../webhooks/index');
|
|
2308
|
+
const { deleteEndpoint } = await import('../webhooks/index.js');
|
|
2045
2309
|
await deleteEndpoint(params.id);
|
|
2046
2310
|
await logEvent({
|
|
2047
2311
|
event: 'settings_changed',
|
|
@@ -2062,7 +2326,7 @@ export function registerCMSRoutes(router) {
|
|
|
2062
2326
|
const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
|
|
2063
2327
|
if (roleErr)
|
|
2064
2328
|
return roleErr;
|
|
2065
|
-
const { getDeliveries } = await import('../webhooks/index');
|
|
2329
|
+
const { getDeliveries } = await import('../webhooks/index.js');
|
|
2066
2330
|
const deliveries = await getDeliveries(params.id);
|
|
2067
2331
|
return json({ data: deliveries });
|
|
2068
2332
|
}
|
|
@@ -2078,7 +2342,7 @@ export function registerCMSRoutes(router) {
|
|
|
2078
2342
|
const roleErr = requireRole(auth.session.role, ADMIN_ROLES);
|
|
2079
2343
|
if (roleErr)
|
|
2080
2344
|
return roleErr;
|
|
2081
|
-
const { processRetries } = await import('../webhooks/index');
|
|
2345
|
+
const { processRetries } = await import('../webhooks/index.js');
|
|
2082
2346
|
const result = await processRetries();
|
|
2083
2347
|
return json({ data: result });
|
|
2084
2348
|
}
|