@actuate-media/cms-core 0.10.0 → 0.10.2

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.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=admin-contracts.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-contracts.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/api/admin-contracts.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,172 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { handleActuateAPI } from '../../api/index.js';
3
+ import { createSession } from '../../auth/session.js';
4
+ import { initDB } from '../../db.js';
5
+ const SECRET = 'test-secret-for-admin-contracts-1234567890';
6
+ async function authHeaders() {
7
+ const token = await createSession({ userId: 'admin-1', role: 'ADMIN', sessionId: 'session-1' }, { secret: SECRET });
8
+ return { cookie: `actuate_session=${token}` };
9
+ }
10
+ function createMockDB() {
11
+ return {
12
+ session: {
13
+ findUnique: async () => ({ revokedAt: null }),
14
+ },
15
+ user: {},
16
+ media: {
17
+ findMany: async () => [
18
+ {
19
+ id: 'media-1',
20
+ filename: 'hero.jpg',
21
+ mimeType: 'image/jpeg',
22
+ fileSize: 2048,
23
+ storageKey: 'uploads/hero.jpg',
24
+ altText: 'Hero image',
25
+ title: 'Hero',
26
+ width: 1200,
27
+ height: 800,
28
+ createdAt: new Date('2026-01-01T00:00:00.000Z'),
29
+ updatedAt: new Date('2026-01-02T00:00:00.000Z'),
30
+ },
31
+ ],
32
+ count: async () => 1,
33
+ },
34
+ document: {
35
+ findMany: async (args) => {
36
+ if (args?.where?.collection === 'forms') {
37
+ return [
38
+ {
39
+ id: 'form-1',
40
+ title: 'Legacy Title',
41
+ data: {
42
+ name: 'Contact Form',
43
+ description: 'Contact us',
44
+ fields: [{ name: 'email' }, { name: 'message' }],
45
+ },
46
+ createdAt: new Date('2026-01-03T00:00:00.000Z'),
47
+ updatedAt: new Date('2026-01-04T00:00:00.000Z'),
48
+ },
49
+ ];
50
+ }
51
+ return [
52
+ {
53
+ id: 'page-1',
54
+ collection: 'pages',
55
+ title: 'Home',
56
+ slug: 'home',
57
+ data: {
58
+ title: 'Home',
59
+ slug: 'home',
60
+ metaTitle: 'Home Meta',
61
+ metaDescription: 'Home description',
62
+ canonical: 'https://example.com/',
63
+ schemaType: 'WebPage',
64
+ },
65
+ structuredData: { score: 82, issues: ['missing-og'] },
66
+ updatedAt: new Date('2026-01-05T00:00:00.000Z'),
67
+ },
68
+ ];
69
+ },
70
+ },
71
+ formSubmission: {
72
+ findMany: async () => [
73
+ {
74
+ id: 'submission-1',
75
+ data: { name: 'Ada', email: 'ada@example.com', message: 'Hello' },
76
+ attribution: { source: 'google', medium: 'cpc' },
77
+ status: 'new',
78
+ submittedAt: new Date('2026-01-06T00:00:00.000Z'),
79
+ },
80
+ ],
81
+ count: async () => 1,
82
+ },
83
+ redirect: {
84
+ findMany: async () => [
85
+ {
86
+ id: 'redirect-1',
87
+ source: '/old',
88
+ destination: '/new',
89
+ statusCode: 301,
90
+ createdAt: new Date('2026-01-07T00:00:00.000Z'),
91
+ updatedAt: new Date('2026-01-08T00:00:00.000Z'),
92
+ },
93
+ ],
94
+ },
95
+ };
96
+ }
97
+ describe('admin API response contracts', () => {
98
+ beforeEach(() => {
99
+ process.env.CMS_SECRET = SECRET;
100
+ const db = createMockDB();
101
+ initDB(db);
102
+ });
103
+ it('normalizes media list items to the shape consumed by cms-admin', async () => {
104
+ const handler = handleActuateAPI({ prismaClient: createMockDB() });
105
+ const response = await handler(new Request('https://example.com/api/cms/media', {
106
+ headers: await authHeaders(),
107
+ }));
108
+ await expect(response.json()).resolves.toMatchObject({
109
+ data: {
110
+ data: [
111
+ {
112
+ id: 'media-1',
113
+ name: 'hero.jpg',
114
+ type: 'image/jpeg',
115
+ size: '2.0 KB',
116
+ sizeBytes: 2048,
117
+ url: '',
118
+ altTag: 'Hero image',
119
+ title: 'Hero',
120
+ dimensions: '1200x800',
121
+ },
122
+ ],
123
+ total: 1,
124
+ },
125
+ });
126
+ });
127
+ it('normalizes forms, submissions, redirects, and SEO pages for cms-admin', async () => {
128
+ const handler = handleActuateAPI({ prismaClient: createMockDB() });
129
+ const headers = await authHeaders();
130
+ const [forms, submissions, redirects, seoPages] = await Promise.all([
131
+ handler(new Request('https://example.com/api/cms/forms', { headers })),
132
+ handler(new Request('https://example.com/api/cms/forms/form-1/submissions', { headers })),
133
+ handler(new Request('https://example.com/api/cms/redirects', { headers })),
134
+ handler(new Request('https://example.com/api/cms/seo/pages', { headers })),
135
+ ]);
136
+ await expect(forms.json()).resolves.toMatchObject({
137
+ data: [{ id: 'form-1', name: 'Contact Form', fields: 2, submissions: 1 }],
138
+ });
139
+ await expect(submissions.json()).resolves.toMatchObject({
140
+ data: {
141
+ submissions: [
142
+ {
143
+ id: 'submission-1',
144
+ name: 'Ada',
145
+ email: 'ada@example.com',
146
+ message: 'Hello',
147
+ status: 'new',
148
+ attribution: { source: 'google', medium: 'cpc' },
149
+ },
150
+ ],
151
+ total: 1,
152
+ },
153
+ });
154
+ await expect(redirects.json()).resolves.toMatchObject({
155
+ data: [{ id: 'redirect-1', from: '/old', to: '/new', type: '301' }],
156
+ });
157
+ await expect(seoPages.json()).resolves.toMatchObject({
158
+ data: [
159
+ {
160
+ id: 'page-1',
161
+ url: '/',
162
+ title: 'Home',
163
+ score: 82,
164
+ issues: 1,
165
+ metaTitle: 'Home Meta',
166
+ metaDescription: 'Home description',
167
+ },
168
+ ],
169
+ });
170
+ });
171
+ });
172
+ //# sourceMappingURL=admin-contracts.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin-contracts.test.js","sourceRoot":"","sources":["../../../src/__tests__/api/admin-contracts.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=public-globals.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-globals.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/api/public-globals.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,92 @@
1
+ import { beforeEach, describe, expect, it } from 'vitest';
2
+ import { handleActuateAPI } from '../../api/index.js';
3
+ import { initDB } from '../../db.js';
4
+ function createMockDB(data) {
5
+ return {
6
+ document: {
7
+ findFirst: async () => data
8
+ ? {
9
+ id: 'global-1',
10
+ collection: 'site-settings',
11
+ data,
12
+ deletedAt: null,
13
+ }
14
+ : null,
15
+ },
16
+ user: {},
17
+ session: {},
18
+ media: {},
19
+ };
20
+ }
21
+ describe('public globals API', () => {
22
+ beforeEach(() => {
23
+ delete globalThis.__actuateConfig;
24
+ });
25
+ it('returns readable global data without a session', async () => {
26
+ initDB(createMockDB({ siteName: 'MaidPro' }));
27
+ const handler = handleActuateAPI({
28
+ prismaClient: createMockDB({ siteName: 'MaidPro' }),
29
+ config: {
30
+ collections: {},
31
+ globals: {
32
+ 'site-settings': {
33
+ slug: 'site-settings',
34
+ label: 'Site Settings',
35
+ fields: {},
36
+ access: { read: () => true },
37
+ },
38
+ },
39
+ },
40
+ });
41
+ const response = await handler(new Request('https://example.com/api/cms/public/globals/site-settings'));
42
+ await expect(response.json()).resolves.toEqual({ data: { siteName: 'MaidPro' } });
43
+ expect(response.status).toBe(200);
44
+ });
45
+ it('denies public global reads when access.read returns false', async () => {
46
+ initDB(createMockDB({ siteName: 'Private' }));
47
+ const handler = handleActuateAPI({
48
+ prismaClient: createMockDB({ siteName: 'Private' }),
49
+ config: {
50
+ collections: {},
51
+ globals: {
52
+ 'site-settings': {
53
+ slug: 'site-settings',
54
+ label: 'Site Settings',
55
+ fields: {},
56
+ access: { read: () => false },
57
+ },
58
+ },
59
+ },
60
+ });
61
+ const response = await handler(new Request('https://example.com/api/cms/public/globals/site-settings'));
62
+ expect(response.status).toBe(403);
63
+ });
64
+ it('denies declared globals that do not explicitly allow public reads', async () => {
65
+ initDB(createMockDB({ siteName: 'Implicitly Private' }));
66
+ const handler = handleActuateAPI({
67
+ prismaClient: createMockDB({ siteName: 'Implicitly Private' }),
68
+ config: {
69
+ collections: {},
70
+ globals: {
71
+ 'site-settings': {
72
+ slug: 'site-settings',
73
+ label: 'Site Settings',
74
+ fields: {},
75
+ },
76
+ },
77
+ },
78
+ });
79
+ const response = await handler(new Request('https://example.com/api/cms/public/globals/site-settings'));
80
+ expect(response.status).toBe(403);
81
+ });
82
+ it('does not expose documents for undeclared globals', async () => {
83
+ initDB(createMockDB({ siteName: 'Hidden' }));
84
+ const handler = handleActuateAPI({
85
+ prismaClient: createMockDB({ siteName: 'Hidden' }),
86
+ config: { collections: {}, globals: {} },
87
+ });
88
+ const response = await handler(new Request('https://example.com/api/cms/public/globals/site-settings'));
89
+ expect(response.status).toBe(404);
90
+ });
91
+ });
92
+ //# sourceMappingURL=public-globals.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"public-globals.test.js","sourceRoot":"","sources":["../../../src/__tests__/api/public-globals.test.ts"],"names":[],"mappings":"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"}
@@ -63,5 +63,41 @@ describe('withActuateCMS', () => {
63
63
  expect(config.transpilePackages).toContain('custom-transpile');
64
64
  expect(config.transpilePackages).not.toContain('@actuate-media/cms-core');
65
65
  });
66
+ it('deep-merges existing server action settings', () => {
67
+ const config = withActuateCMS(createFullConfig(), {
68
+ experimental: {
69
+ serverActions: {
70
+ bodySizeLimit: '4mb',
71
+ allowedOrigins: ['cms.example.com'],
72
+ },
73
+ },
74
+ });
75
+ expect(config.experimental).toEqual({
76
+ serverActions: {
77
+ bodySizeLimit: '4mb',
78
+ allowedOrigins: ['cms.example.com'],
79
+ },
80
+ });
81
+ });
82
+ it('adds the Vercel Blob image remote pattern once', () => {
83
+ const config = withActuateCMS(createFullConfig(), {
84
+ images: {
85
+ remotePatterns: [
86
+ { protocol: 'https', hostname: 'images.example.com' },
87
+ ],
88
+ },
89
+ });
90
+ expect(config.images).toEqual({
91
+ remotePatterns: [
92
+ { protocol: 'https', hostname: 'images.example.com' },
93
+ { protocol: 'https', hostname: '**.blob.vercel-storage.com' },
94
+ ],
95
+ });
96
+ const secondPass = withActuateCMS(createFullConfig(), config);
97
+ expect(secondPass.images.remotePatterns).toEqual([
98
+ { protocol: 'https', hostname: 'images.example.com' },
99
+ { protocol: 'https', hostname: '**.blob.vercel-storage.com' },
100
+ ]);
101
+ });
66
102
  });
67
103
  //# sourceMappingURL=next.test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"next.test.js","sourceRoot":"","sources":["../../src/__tests__/next.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAE9C,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5C,SAAS,gBAAgB;IACvB,OAAO;QACL,QAAQ,EAAE;YACR,QAAQ,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE;YAClC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE;SAC/B;QACD,IAAI,EAAE;YACJ,SAAS,EAAE,EAAE;YACb,WAAW,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE;SAC/B;QACD,KAAK,EAAE;YACL,IAAI,EAAE,YAAY;SACnB;QACD,WAAW,EAAE;YACX,KAAK,EAAE;gBACL,IAAI,EAAE,OAAO;gBACb,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE;gBAC7C,MAAM,EAAE;oBACN,KAAK,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE;iBACxC;aACF;SACF;QACD,OAAO,EAAE;YACP,eAAe,EAAE;gBACf,IAAI,EAAE,eAAe;gBACrB,KAAK,EAAE,eAAe;gBACtB,MAAM,EAAE;oBACN,QAAQ,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,MAAM,EAAE;iBAC/C;aACF;SACF;QACD,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,oBAAoB;gBAC1B,IAAI,CAAC,MAAM;oBACT,OAAO,MAAM,CAAC;gBAChB,CAAC;aACF;SACF;KACF,CAAC;AACJ,CAAC;AAED,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;IAC9B,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,MAAM,GAAG,cAAc,CAAC,gBAAgB,EAAE,EAAE;YAChD,GAAG,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE;SAC1B,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC;YACzB,QAAQ,EAAE,MAAM;YAChB,kBAAkB,EAAE,YAAY;YAChC,mBAAmB,EAAE,OAAO;YAC5B,eAAe,EAAE,eAAe;SACjC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,qEAAqE,EAAE,GAAG,EAAE;QAC7E,MAAM,MAAM,GAAG,cAAc,CAAC,gBAAgB,EAAE,EAAE;YAChD,sBAAsB,EAAE,CAAC,iBAAiB,CAAC;YAC3C,iBAAiB,EAAE,CAAC,kBAAkB,EAAE,yBAAyB,CAAC;SACnE,CAAC,CAAC;QAEH,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QACnE,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAAC,CAAC,SAAS,CAAC,gBAAgB,CAAC,CAAC;QAClE,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;QAC/D,MAAM,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;IAC5E,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"next.test.js","sourceRoot":"","sources":["../../src/__tests__/next.test.ts"],"names":[],"mappings":"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"}
@@ -11,7 +11,7 @@ describe('site helpers', () => {
11
11
  title: 'MaidPro',
12
12
  phone: '555-0100',
13
13
  });
14
- expect(fetchMock).toHaveBeenCalledWith('https://example.com/api/cms/globals/site-settings', expect.objectContaining({ headers: { 'Content-Type': 'application/json' } }));
14
+ expect(fetchMock).toHaveBeenCalledWith('https://example.com/api/cms/public/globals/site-settings', expect.objectContaining({ headers: { 'Content-Type': 'application/json' } }));
15
15
  });
16
16
  it('resolves documents through the public CMS API', async () => {
17
17
  const fetchMock = vi.fn(async () => new Response(JSON.stringify({ data: { id: 'doc-1', collection: 'pages', data: { title: 'Home' } } }), { status: 200, headers: { 'Content-Type': 'application/json' } }));
@@ -1 +1 @@
1
- {"version":3,"file":"site.test.js","sourceRoot":"","sources":["../../src/__tests__/site.test.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"site.test.js","sourceRoot":"","sources":["../../src/__tests__/site.test.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA4N7C,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAiED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAggHzD"}
1
+ {"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AA6T7C,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAiED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA0jHzD"}
@@ -58,6 +58,97 @@ function clampPageSize(raw, max = 100, fallback = 20) {
58
58
  const n = Number(raw) || fallback;
59
59
  return Math.min(Math.max(1, n), max);
60
60
  }
61
+ function mediaUrl(storageKey) {
62
+ const value = String(storageKey ?? '');
63
+ if (!value)
64
+ return '';
65
+ return value.startsWith('http://') || value.startsWith('https://') || value.startsWith('/') ? value : '';
66
+ }
67
+ function normalizeMediaItem(media) {
68
+ const width = typeof media.width === 'number' ? media.width : null;
69
+ const height = typeof media.height === 'number' ? media.height : null;
70
+ return {
71
+ ...media,
72
+ name: media.filename ?? media.name ?? '',
73
+ type: media.mimeType ?? media.type ?? '',
74
+ size: formatBytes(Number(media.fileSize ?? media.sizeBytes ?? 0)),
75
+ sizeBytes: Number(media.fileSize ?? media.sizeBytes ?? 0),
76
+ date: media.createdAt ?? media.updatedAt ?? null,
77
+ url: mediaUrl(media.storageKey ?? media.url),
78
+ dimensions: width && height ? `${width}x${height}` : undefined,
79
+ format: typeof media.mimeType === 'string' ? media.mimeType.split('/')[1] : undefined,
80
+ altTag: media.altText ?? media.altTag ?? '',
81
+ title: media.title ?? '',
82
+ };
83
+ }
84
+ function asRecord(value) {
85
+ return value && typeof value === 'object' && !Array.isArray(value)
86
+ ? value
87
+ : {};
88
+ }
89
+ function normalizeFormDocument(form, submissions = 0) {
90
+ const data = asRecord(form.data);
91
+ const fields = Array.isArray(data.fields) ? data.fields.length : Number(data.fields ?? 0);
92
+ return {
93
+ ...form,
94
+ name: data.name ?? form.name ?? form.title ?? 'Untitled form',
95
+ description: data.description ?? form.description ?? '',
96
+ fields,
97
+ submissions,
98
+ };
99
+ }
100
+ function normalizeSubmission(submission) {
101
+ const data = asRecord(submission.data);
102
+ const attribution = asRecord(submission.attribution);
103
+ return {
104
+ ...submission,
105
+ name: String(data.name ?? submission.name ?? ''),
106
+ email: String(data.email ?? submission.email ?? ''),
107
+ phone: data.phone ?? submission.phone,
108
+ message: String(data.message ?? submission.message ?? ''),
109
+ status: submission.status ?? 'new',
110
+ attribution: {
111
+ source: String(attribution.source ?? '(direct)'),
112
+ medium: String(attribution.medium ?? '(none)'),
113
+ campaign: String(attribution.campaign ?? ''),
114
+ term: String(attribution.term ?? ''),
115
+ content: String(attribution.content ?? ''),
116
+ landingPage: String(attribution.landingPage ?? ''),
117
+ referrer: String(attribution.referrer ?? ''),
118
+ deviceType: attribution.deviceType ?? 'Desktop',
119
+ clickIds: asRecord(attribution.clickIds),
120
+ capturedAt: attribution.capturedAt ?? submission.submittedAt ?? submission.createdAt ?? null,
121
+ },
122
+ };
123
+ }
124
+ function normalizeRedirect(redirect) {
125
+ return {
126
+ ...redirect,
127
+ from: redirect.from ?? redirect.source ?? '',
128
+ to: redirect.to ?? redirect.destination ?? '',
129
+ type: String(redirect.type ?? redirect.statusCode ?? 301),
130
+ hits: Number(redirect.hits ?? 0),
131
+ active: redirect.active ?? true,
132
+ };
133
+ }
134
+ function normalizeSeoPage(page) {
135
+ const data = asRecord(page.data);
136
+ const structuredData = asRecord(page.structuredData);
137
+ const issueValue = structuredData.issues ?? data.issues;
138
+ const issueCount = Array.isArray(issueValue) ? issueValue.length : Number(issueValue ?? 0);
139
+ const slug = String(data.slug ?? page.slug ?? page.id ?? '');
140
+ return {
141
+ ...page,
142
+ url: data.url ?? (slug === 'home' ? '/' : `/${slug}`),
143
+ title: data.title ?? page.title ?? 'Untitled',
144
+ score: Number(structuredData.score ?? data.score ?? 0),
145
+ issues: issueCount,
146
+ metaTitle: data.metaTitle ?? '',
147
+ metaDescription: data.metaDescription ?? '',
148
+ canonical: data.canonical ?? '',
149
+ schemaType: data.schemaType ?? structuredData.schemaType ?? '',
150
+ };
151
+ }
61
152
  function hasModel(d, name) {
62
153
  try {
63
154
  return d[name] && typeof d[name].findMany === 'function';
@@ -876,8 +967,16 @@ export function registerCMSRoutes(router) {
876
967
  db().media.findMany({ where, skip, take: pageSize, orderBy: { createdAt: 'desc' } }),
877
968
  db().media.count({ where }),
878
969
  ]);
970
+ const normalized = items.map(normalizeMediaItem);
879
971
  return json({
880
- data: { items, total, page, pageSize, totalPages: Math.ceil(total / pageSize) },
972
+ data: {
973
+ data: normalized,
974
+ items: normalized,
975
+ total,
976
+ page,
977
+ pageSize,
978
+ totalPages: Math.ceil(total / pageSize),
979
+ },
881
980
  });
882
981
  }
883
982
  catch (err) {
@@ -1629,11 +1728,18 @@ export function registerCMSRoutes(router) {
1629
1728
  const auth = await requireAuth(request);
1630
1729
  if (auth.error)
1631
1730
  return auth.error;
1632
- const forms = await db().document.findMany({
1731
+ const d = db();
1732
+ const forms = await d.document.findMany({
1633
1733
  where: { collection: 'forms', deletedAt: null },
1634
1734
  orderBy: { createdAt: 'desc' },
1635
1735
  });
1636
- return json({ data: forms });
1736
+ const normalized = await Promise.all(forms.map(async (form) => {
1737
+ const submissions = hasModel(d, 'formSubmission')
1738
+ ? await d.formSubmission.count({ where: { formId: form.id } })
1739
+ : 0;
1740
+ return normalizeFormDocument(form, submissions);
1741
+ }));
1742
+ return json({ data: normalized });
1637
1743
  }
1638
1744
  catch (err) {
1639
1745
  return internalError(err);
@@ -1658,7 +1764,13 @@ export function registerCMSRoutes(router) {
1658
1764
  db().formSubmission.count({ where: { formId: params.id } }),
1659
1765
  ]);
1660
1766
  return json({
1661
- data: { submissions, total, page, pageSize, totalPages: Math.ceil(total / pageSize) },
1767
+ data: {
1768
+ submissions: submissions.map(normalizeSubmission),
1769
+ total,
1770
+ page,
1771
+ pageSize,
1772
+ totalPages: Math.ceil(total / pageSize),
1773
+ },
1662
1774
  });
1663
1775
  }
1664
1776
  catch (err) {
@@ -1743,7 +1855,7 @@ export function registerCMSRoutes(router) {
1743
1855
  if (auth.error)
1744
1856
  return auth.error;
1745
1857
  const redirects = await db().redirect.findMany({ orderBy: { createdAt: 'desc' } });
1746
- return json({ data: redirects });
1858
+ return json({ data: redirects.map(normalizeRedirect) });
1747
1859
  }
1748
1860
  catch (err) {
1749
1861
  return internalError(err);
@@ -1757,8 +1869,9 @@ export function registerCMSRoutes(router) {
1757
1869
  if (auth.session.role !== 'ADMIN')
1758
1870
  return errorResponse('Admin access required', 403);
1759
1871
  const body = await request.json();
1760
- const source = String(body.source ?? '').trim();
1761
- const destination = String(body.destination ?? '').trim();
1872
+ const source = String(body.source ?? body.from ?? '').trim();
1873
+ const destination = String(body.destination ?? body.to ?? '').trim();
1874
+ const requestedStatus = Number(body.statusCode ?? body.type);
1762
1875
  if (!source || !destination) {
1763
1876
  return errorResponse('source and destination are required', 400);
1764
1877
  }
@@ -1777,12 +1890,12 @@ export function registerCMSRoutes(router) {
1777
1890
  data: {
1778
1891
  source,
1779
1892
  destination,
1780
- statusCode: [301, 302, 307, 308].includes(Number(body.statusCode)) ? Number(body.statusCode) : 301,
1893
+ statusCode: [301, 302, 307, 308].includes(requestedStatus) ? requestedStatus : 301,
1781
1894
  isRegex: body.isRegex === true,
1782
1895
  notes: typeof body.notes === 'string' ? body.notes : null,
1783
1896
  },
1784
1897
  });
1785
- return json({ data: redirect }, 201);
1898
+ return json({ data: normalizeRedirect(redirect) }, 201);
1786
1899
  }
1787
1900
  catch (err) {
1788
1901
  return internalError(err, 'create redirect');
@@ -1821,7 +1934,7 @@ export function registerCMSRoutes(router) {
1821
1934
  },
1822
1935
  orderBy: { updatedAt: 'desc' },
1823
1936
  });
1824
- return json({ data: pages });
1937
+ return json({ data: pages.map(normalizeSeoPage) });
1825
1938
  }
1826
1939
  catch (err) {
1827
1940
  return internalError(err);
@@ -2677,6 +2790,36 @@ export function registerCMSRoutes(router) {
2677
2790
  // ---------------------------------------------------------------------------
2678
2791
  // Globals routes
2679
2792
  // ---------------------------------------------------------------------------
2793
+ router.get('/public/globals/:slug', async (_request, params) => {
2794
+ try {
2795
+ const slug = params.slug;
2796
+ const globalConfig = globalThis.__actuateConfig?.globals?.[slug];
2797
+ if (!globalConfig) {
2798
+ return errorResponse('Global not found', 404);
2799
+ }
2800
+ const doc = await db().document.findFirst({
2801
+ where: { collection: slug, deletedAt: null },
2802
+ });
2803
+ if (!doc) {
2804
+ return errorResponse('Global not found', 404);
2805
+ }
2806
+ const readAccess = globalConfig.access?.read;
2807
+ const allowed = readAccess
2808
+ ? await readAccess({ user: null, doc })
2809
+ : false;
2810
+ if (!allowed) {
2811
+ return errorResponse('Forbidden', 403);
2812
+ }
2813
+ return json({
2814
+ data: doc.data && typeof doc.data === 'object'
2815
+ ? doc.data
2816
+ : {},
2817
+ });
2818
+ }
2819
+ catch (err) {
2820
+ return internalError(err);
2821
+ }
2822
+ });
2680
2823
  router.get('/globals/:slug', async (request, params) => {
2681
2824
  try {
2682
2825
  const auth = await requireAuth(request);