@actuate-media/cms-core 0.35.0 → 0.35.1
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/__tests__/api/globals-routes.test.d.ts +2 -0
- package/dist/__tests__/api/globals-routes.test.d.ts.map +1 -0
- package/dist/__tests__/api/globals-routes.test.js +100 -0
- package/dist/__tests__/api/globals-routes.test.js.map +1 -0
- package/dist/api/handlers.d.ts.map +1 -1
- package/dist/api/handlers.js +7 -4
- package/dist/api/handlers.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"globals-routes.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/api/globals-routes.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
2
|
+
import { handleActuateAPI } from '../../api/index.js';
|
|
3
|
+
import { initDB } from '../../db.js';
|
|
4
|
+
import { generateApiKey } from '../../security/api-key-enhanced.js';
|
|
5
|
+
/**
|
|
6
|
+
* Authenticated `GET /globals/:slug` behaviour.
|
|
7
|
+
*
|
|
8
|
+
* Globals are upsert-on-write (the first PUT creates the row). A slug that has
|
|
9
|
+
* no document yet is therefore a valid EMPTY state, not a 404 — the shipped
|
|
10
|
+
* admin Settings page reads `/globals/settings` and must load cleanly on a
|
|
11
|
+
* fresh install before anything has been saved. This regression test pins
|
|
12
|
+
* that contract so we don't reintroduce the "Global not found" banner.
|
|
13
|
+
*/
|
|
14
|
+
const VALID_SECRET = 'a'.repeat(48);
|
|
15
|
+
function createMockDB(opts) {
|
|
16
|
+
const apiKey = opts.apiKey;
|
|
17
|
+
return {
|
|
18
|
+
apiKey: {
|
|
19
|
+
findMany: async () => (apiKey ? [apiKey] : []),
|
|
20
|
+
findUnique: async ({ where }) => {
|
|
21
|
+
if (!apiKey)
|
|
22
|
+
return null;
|
|
23
|
+
if (apiKey.keyHash !== where.keyHash)
|
|
24
|
+
return null;
|
|
25
|
+
return apiKey;
|
|
26
|
+
},
|
|
27
|
+
update: async () => apiKey,
|
|
28
|
+
},
|
|
29
|
+
document: {
|
|
30
|
+
findFirst: async () => opts.global ?? null,
|
|
31
|
+
findMany: async () => [],
|
|
32
|
+
count: async () => 0,
|
|
33
|
+
},
|
|
34
|
+
user: {},
|
|
35
|
+
session: {},
|
|
36
|
+
media: {},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
async function makeKey(scopes) {
|
|
40
|
+
const { key, keyHash, keyPrefix } = await generateApiKey({ prefix: 'act_sk', scopes });
|
|
41
|
+
return {
|
|
42
|
+
key,
|
|
43
|
+
record: {
|
|
44
|
+
id: 'apikey-1',
|
|
45
|
+
keyHash,
|
|
46
|
+
keyPrefix,
|
|
47
|
+
userId: 'user-1',
|
|
48
|
+
scopes,
|
|
49
|
+
ipRestrictions: null,
|
|
50
|
+
expiresAt: null,
|
|
51
|
+
lastUsedAt: null,
|
|
52
|
+
revokedAt: null,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function makeHandler(db) {
|
|
57
|
+
return handleActuateAPI({ prismaClient: db, config: { collections: {} } });
|
|
58
|
+
}
|
|
59
|
+
describe('GET /globals/:slug (authenticated)', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
delete globalThis.__actuateConfig;
|
|
62
|
+
process.env.CMS_SECRET = VALID_SECRET;
|
|
63
|
+
});
|
|
64
|
+
it('returns 200 with empty data when the global has never been saved', async () => {
|
|
65
|
+
const { key, record } = await makeKey({ globals: ['settings'] });
|
|
66
|
+
const db = createMockDB({ apiKey: record, global: null });
|
|
67
|
+
initDB(db);
|
|
68
|
+
const handler = makeHandler(db);
|
|
69
|
+
const response = await handler(new Request('https://example.com/api/cms/globals/settings', {
|
|
70
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
71
|
+
}));
|
|
72
|
+
expect(response.status).toBe(200);
|
|
73
|
+
await expect(response.json()).resolves.toEqual({ data: {} });
|
|
74
|
+
});
|
|
75
|
+
it('returns the stored data when the global document exists', async () => {
|
|
76
|
+
const { key, record } = await makeKey({ globals: ['settings'] });
|
|
77
|
+
const db = createMockDB({
|
|
78
|
+
apiKey: record,
|
|
79
|
+
global: { id: 'doc-1', collection: 'settings', data: { siteTitle: 'Actuate Media' } },
|
|
80
|
+
});
|
|
81
|
+
initDB(db);
|
|
82
|
+
const handler = makeHandler(db);
|
|
83
|
+
const response = await handler(new Request('https://example.com/api/cms/globals/settings', {
|
|
84
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
85
|
+
}));
|
|
86
|
+
expect(response.status).toBe(200);
|
|
87
|
+
await expect(response.json()).resolves.toEqual({ data: { siteTitle: 'Actuate Media' } });
|
|
88
|
+
});
|
|
89
|
+
it('rejects with 403 when the API key is not scoped to the global', async () => {
|
|
90
|
+
const { key, record } = await makeKey({ collections: ['posts'], actions: ['read'] });
|
|
91
|
+
const db = createMockDB({ apiKey: record, global: null });
|
|
92
|
+
initDB(db);
|
|
93
|
+
const handler = makeHandler(db);
|
|
94
|
+
const response = await handler(new Request('https://example.com/api/cms/globals/settings', {
|
|
95
|
+
headers: { Authorization: `Bearer ${key}` },
|
|
96
|
+
}));
|
|
97
|
+
expect(response.status).toBe(403);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
//# sourceMappingURL=globals-routes.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"globals-routes.test.js","sourceRoot":"","sources":["../../../src/__tests__/api/globals-routes.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,CAAA;AA+/B5C,iFAAiF;AACjF,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AA8JD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAyRD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/api/handlers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AA+/B5C,iFAAiF;AACjF,wBAAgB,wBAAwB,IAAI,IAAI,CAE/C;AA8JD,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS9E;AAyRD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA0tUzD"}
|
package/dist/api/handlers.js
CHANGED
|
@@ -8912,10 +8912,13 @@ export function registerCMSRoutes(router) {
|
|
|
8912
8912
|
build: async () => {
|
|
8913
8913
|
const ctx = buildActionContext(auth.session, db());
|
|
8914
8914
|
const global = await getGlobal(params.slug, ctx);
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
}
|
|
8918
|
-
|
|
8915
|
+
// Globals are upsert-on-write: a slug with no row yet (fresh install,
|
|
8916
|
+
// never-saved settings) is a valid EMPTY state, not an error. Return
|
|
8917
|
+
// {} so the admin editor (e.g. the Settings page) loads cleanly before
|
|
8918
|
+
// the first save, and the next PUT creates the document. The public
|
|
8919
|
+
// `/public/globals/:slug` route keeps its config-gated 404 for
|
|
8920
|
+
// genuinely undeclared globals.
|
|
8921
|
+
return { data: global ?? {} };
|
|
8919
8922
|
},
|
|
8920
8923
|
});
|
|
8921
8924
|
}
|