@aphexcms/cms-core 0.1.12 → 0.1.14
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/api/documents.d.ts +1 -0
- package/dist/api/documents.d.ts.map +1 -1
- package/dist/api/documents.js +9 -1
- package/dist/api/types.d.ts +24 -2
- package/dist/api/types.d.ts.map +1 -1
- package/dist/auth/auth-hooks.d.ts.map +1 -1
- package/dist/auth/auth-hooks.js +18 -4
- package/dist/cli/generate-types.js +218 -0
- package/dist/cli/index.js +86 -0
- package/dist/components/AdminApp.svelte +15 -12
- package/dist/components/AdminApp.svelte.d.ts.map +1 -1
- package/dist/components/admin/DocumentEditor.svelte +60 -14
- package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ImageField.svelte +22 -13
- package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/ReferenceField.svelte +2 -3
- package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts +8 -1
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -1
- package/dist/db/interfaces/asset.d.ts +22 -0
- package/dist/db/interfaces/asset.d.ts.map +1 -1
- package/dist/db/interfaces/document.d.ts +25 -0
- package/dist/db/interfaces/document.d.ts.map +1 -1
- package/dist/field-validation/utils.d.ts +19 -1
- package/dist/field-validation/utils.d.ts.map +1 -1
- package/dist/field-validation/utils.js +33 -0
- package/dist/hooks.d.ts +2 -0
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +80 -12
- package/dist/lib/auth/provider.js +1 -0
- package/dist/lib/db/index.js +4 -0
- package/dist/lib/db/interfaces/asset.js +1 -0
- package/dist/lib/db/interfaces/document.js +1 -0
- package/dist/lib/db/interfaces/index.js +1 -0
- package/dist/lib/db/interfaces/organization.js +1 -0
- package/dist/lib/db/interfaces/schema.js +1 -0
- package/dist/lib/db/interfaces/user.js +1 -0
- package/dist/lib/email/index.js +4 -0
- package/dist/lib/email/interfaces/email.js +1 -0
- package/dist/lib/field-validation/rule.js +221 -0
- package/dist/lib/field-validation/utils.js +99 -0
- package/dist/lib/storage/interfaces/index.js +2 -0
- package/dist/lib/storage/interfaces/storage.js +1 -0
- package/dist/lib/types/asset.js +2 -0
- package/dist/lib/types/auth.js +41 -0
- package/dist/lib/types/config.js +1 -0
- package/dist/lib/types/document.js +1 -0
- package/dist/lib/types/filters.js +5 -0
- package/dist/lib/types/index.js +9 -0
- package/dist/lib/types/organization.js +3 -0
- package/dist/lib/types/schemas.js +1 -0
- package/dist/lib/types/sidebar.js +1 -0
- package/dist/lib/types/user.js +1 -0
- package/dist/local-api/auth-helpers.d.ts +65 -0
- package/dist/local-api/auth-helpers.d.ts.map +1 -0
- package/dist/local-api/auth-helpers.js +102 -0
- package/dist/local-api/collection-api.d.ts +138 -0
- package/dist/local-api/collection-api.d.ts.map +1 -0
- package/dist/local-api/collection-api.js +276 -0
- package/dist/local-api/index.d.ts +108 -0
- package/dist/local-api/index.d.ts.map +1 -0
- package/dist/local-api/index.js +157 -0
- package/dist/local-api/permissions.d.ts +45 -0
- package/dist/local-api/permissions.d.ts.map +1 -0
- package/dist/local-api/permissions.js +117 -0
- package/dist/local-api/types.d.ts +65 -0
- package/dist/local-api/types.d.ts.map +1 -0
- package/dist/local-api/types.js +4 -0
- package/dist/routes/documents-by-id.d.ts.map +1 -1
- package/dist/routes/documents-by-id.js +84 -63
- package/dist/routes/documents-publish.d.ts.map +1 -1
- package/dist/routes/documents-publish.js +57 -72
- package/dist/routes/documents-query.d.ts +24 -0
- package/dist/routes/documents-query.d.ts.map +1 -0
- package/dist/routes/documents-query.js +95 -0
- package/dist/routes/documents.d.ts.map +1 -1
- package/dist/routes/documents.js +80 -75
- package/dist/routes/index.d.ts +2 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +2 -0
- package/dist/types/config.d.ts +18 -1
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/document.d.ts +1 -0
- package/dist/types/document.d.ts.map +1 -1
- package/dist/types/filters.d.ts +173 -0
- package/dist/types/filters.d.ts.map +1 -0
- package/dist/types/filters.js +5 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/package.json +101 -95
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { LocalAPIContext } from './types.js';
|
|
2
|
+
import type { SchemaType } from '../types/schemas.js';
|
|
3
|
+
import type { CMSConfig } from '../types/config.js';
|
|
4
|
+
/**
|
|
5
|
+
* Permission error thrown when access is denied
|
|
6
|
+
*/
|
|
7
|
+
export declare class PermissionError extends Error {
|
|
8
|
+
readonly operation: string;
|
|
9
|
+
readonly resource: string;
|
|
10
|
+
constructor(message: string, operation: string, resource: string);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Checks permissions for Local API operations
|
|
14
|
+
*/
|
|
15
|
+
export declare class PermissionChecker {
|
|
16
|
+
private _config;
|
|
17
|
+
private schemas;
|
|
18
|
+
constructor(_config: CMSConfig, schemas: Map<string, SchemaType>);
|
|
19
|
+
/**
|
|
20
|
+
* Get the CMS config
|
|
21
|
+
* Available for future per-collection permission logic
|
|
22
|
+
*/
|
|
23
|
+
get config(): CMSConfig;
|
|
24
|
+
/**
|
|
25
|
+
* Check if the current context has read permissions for a collection
|
|
26
|
+
*/
|
|
27
|
+
canRead(context: LocalAPIContext, collectionName: string): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Check if the current context has write permissions (create/update)
|
|
30
|
+
*/
|
|
31
|
+
canWrite(context: LocalAPIContext, collectionName: string): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Check if the current context has delete permissions
|
|
34
|
+
*/
|
|
35
|
+
canDelete(context: LocalAPIContext, collectionName: string): Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Check if the current context has publish permissions
|
|
38
|
+
*/
|
|
39
|
+
canPublish(context: LocalAPIContext, collectionName: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Validate that a collection exists in the schema
|
|
42
|
+
*/
|
|
43
|
+
validateCollection(collectionName: string): void;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=permissions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permissions.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/permissions.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD;;GAEG;AACH,qBAAa,eAAgB,SAAQ,KAAK;aAGxB,SAAS,EAAE,MAAM;aACjB,QAAQ,EAAE,MAAM;gBAFhC,OAAO,EAAE,MAAM,EACC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM;CAKjC;AAED;;GAEG;AACH,qBAAa,iBAAiB;IAE5B,OAAO,CAAC,OAAO;IACf,OAAO,CAAC,OAAO;gBADP,OAAO,EAAE,SAAS,EAClB,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC;IAGzC;;;OAGG;IACH,IAAI,MAAM,IAAI,SAAS,CAEtB;IAED;;OAEG;IACG,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB9E;;OAEG;IACG,QAAQ,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B/E;;OAEG;IACG,SAAS,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6BhF;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA4BjF;;OAEG;IACH,kBAAkB,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI;CAShD"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// local-api/permissions.ts
|
|
2
|
+
//
|
|
3
|
+
// Permission checking for the Local API layer
|
|
4
|
+
/**
|
|
5
|
+
* Permission error thrown when access is denied
|
|
6
|
+
*/
|
|
7
|
+
export class PermissionError extends Error {
|
|
8
|
+
operation;
|
|
9
|
+
resource;
|
|
10
|
+
constructor(message, operation, resource) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.operation = operation;
|
|
13
|
+
this.resource = resource;
|
|
14
|
+
this.name = 'PermissionError';
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Checks permissions for Local API operations
|
|
19
|
+
*/
|
|
20
|
+
export class PermissionChecker {
|
|
21
|
+
_config;
|
|
22
|
+
schemas;
|
|
23
|
+
constructor(_config, schemas) {
|
|
24
|
+
this._config = _config;
|
|
25
|
+
this.schemas = schemas;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get the CMS config
|
|
29
|
+
* Available for future per-collection permission logic
|
|
30
|
+
*/
|
|
31
|
+
get config() {
|
|
32
|
+
return this._config;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if the current context has read permissions for a collection
|
|
36
|
+
*/
|
|
37
|
+
async canRead(context, collectionName) {
|
|
38
|
+
// Always allow if overrideAccess is true (system operations)
|
|
39
|
+
if (context.overrideAccess) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Require authentication for read operations
|
|
43
|
+
if (!context.user) {
|
|
44
|
+
throw new PermissionError('Authentication required for read operations', 'read', collectionName);
|
|
45
|
+
}
|
|
46
|
+
// All authenticated users can read (role-based restrictions handled by RLS)
|
|
47
|
+
// Additional per-collection permission logic can be added here in the future
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check if the current context has write permissions (create/update)
|
|
52
|
+
*/
|
|
53
|
+
async canWrite(context, collectionName) {
|
|
54
|
+
// Always allow if overrideAccess is true (system operations)
|
|
55
|
+
if (context.overrideAccess) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
// Require authentication for write operations
|
|
59
|
+
if (!context.user) {
|
|
60
|
+
throw new PermissionError('Authentication required for write operations', 'write', collectionName);
|
|
61
|
+
}
|
|
62
|
+
// Check if user has write role (not a viewer)
|
|
63
|
+
// Viewers have read-only access
|
|
64
|
+
if (context.user.role === 'viewer') {
|
|
65
|
+
throw new PermissionError('Write permission denied. Viewers have read-only access.', 'write', collectionName);
|
|
66
|
+
}
|
|
67
|
+
// Additional per-collection permission logic can be added here in the future
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Check if the current context has delete permissions
|
|
72
|
+
*/
|
|
73
|
+
async canDelete(context, collectionName) {
|
|
74
|
+
// Always allow if overrideAccess is true (system operations)
|
|
75
|
+
if (context.overrideAccess) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
// Require authentication for delete operations
|
|
79
|
+
if (!context.user) {
|
|
80
|
+
throw new PermissionError('Authentication required for delete operations', 'delete', collectionName);
|
|
81
|
+
}
|
|
82
|
+
// Check if user has sufficient role (admin or super_admin)
|
|
83
|
+
// Editors can create/update but not delete
|
|
84
|
+
if (context.user.role === 'viewer' || context.user.role === 'editor') {
|
|
85
|
+
throw new PermissionError('Delete permission denied. Only admins can delete resources.', 'delete', collectionName);
|
|
86
|
+
}
|
|
87
|
+
// Additional per-collection permission logic can be added here in the future
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if the current context has publish permissions
|
|
92
|
+
*/
|
|
93
|
+
async canPublish(context, collectionName) {
|
|
94
|
+
// Always allow if overrideAccess is true (system operations)
|
|
95
|
+
if (context.overrideAccess) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
// Require authentication for publish operations
|
|
99
|
+
if (!context.user) {
|
|
100
|
+
throw new PermissionError('Authentication required for publish operations', 'publish', collectionName);
|
|
101
|
+
}
|
|
102
|
+
// Check if user has write role (not a viewer)
|
|
103
|
+
if (context.user.role === 'viewer') {
|
|
104
|
+
throw new PermissionError('Publish permission denied. Viewers have read-only access.', 'publish', collectionName);
|
|
105
|
+
}
|
|
106
|
+
// Additional per-collection permission logic can be added here in the future
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Validate that a collection exists in the schema
|
|
111
|
+
*/
|
|
112
|
+
validateCollection(collectionName) {
|
|
113
|
+
if (!this.schemas.has(collectionName)) {
|
|
114
|
+
throw new Error(`Collection "${collectionName}" not found in schema. Available collections: ${Array.from(this.schemas.keys()).join(', ')}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { CMSUser } from '../types/user.js';
|
|
2
|
+
import type { Auth } from '../types/auth.js';
|
|
3
|
+
/**
|
|
4
|
+
* Context for Local API operations
|
|
5
|
+
* This provides the necessary information for access control and multi-tenancy
|
|
6
|
+
*/
|
|
7
|
+
export interface LocalAPIContext {
|
|
8
|
+
/**
|
|
9
|
+
* Organization ID for multi-tenancy
|
|
10
|
+
* Required for all operations to ensure proper data isolation
|
|
11
|
+
*/
|
|
12
|
+
organizationId: string;
|
|
13
|
+
/**
|
|
14
|
+
* Current user (if available)
|
|
15
|
+
* Used for permission checks and audit trails
|
|
16
|
+
*/
|
|
17
|
+
user?: CMSUser;
|
|
18
|
+
/**
|
|
19
|
+
* Override access control and RLS
|
|
20
|
+
* Set to true for system operations (seed scripts, cron jobs, admin tasks)
|
|
21
|
+
* When true, uses the system database adapter that bypasses RLS
|
|
22
|
+
* @default false
|
|
23
|
+
*/
|
|
24
|
+
overrideAccess?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Full auth object from locals.auth
|
|
27
|
+
* Preserved to allow custom permission logic to access any custom fields
|
|
28
|
+
* added via module augmentation (e.g., custom roles, permissions, metadata)
|
|
29
|
+
*/
|
|
30
|
+
auth?: Auth;
|
|
31
|
+
/**
|
|
32
|
+
* Request context for transactions
|
|
33
|
+
* Can be used to pass SvelteKit RequestEvent or similar context
|
|
34
|
+
*/
|
|
35
|
+
req?: unknown;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Options for create operations
|
|
39
|
+
*/
|
|
40
|
+
export interface CreateOptions {
|
|
41
|
+
/**
|
|
42
|
+
* Draft data for the document/asset
|
|
43
|
+
*/
|
|
44
|
+
data: Record<string, unknown>;
|
|
45
|
+
/**
|
|
46
|
+
* Whether to immediately publish after creation
|
|
47
|
+
* @default false
|
|
48
|
+
*/
|
|
49
|
+
publish?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Options for update operations
|
|
53
|
+
*/
|
|
54
|
+
export interface UpdateOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Data to update
|
|
57
|
+
*/
|
|
58
|
+
data: Record<string, unknown>;
|
|
59
|
+
/**
|
|
60
|
+
* Whether to publish after update
|
|
61
|
+
* @default false
|
|
62
|
+
*/
|
|
63
|
+
publish?: boolean;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/types.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAE1C;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC/B;;;OAGG;IACH,cAAc,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IAEf;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IAEzB;;;;OAIG;IACH,IAAI,CAAC,EAAE,IAAI,CAAC;IAEZ;;;OAGG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE9B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC7B;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAE9B;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;CAClB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents-by-id.d.ts","sourceRoot":"","sources":["../../src/lib/routes/documents-by-id.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAKpD,eAAO,MAAM,GAAG,EAAE,
|
|
1
|
+
{"version":3,"file":"documents-by-id.d.ts","sourceRoot":"","sources":["../../src/lib/routes/documents-by-id.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAKpD,eAAO,MAAM,GAAG,EAAE,cAuEjB,CAAC;AAGF,eAAO,MAAM,GAAG,EAAE,cAqEjB,CAAC;AAGF,eAAO,MAAM,MAAM,EAAE,cA+DpB,CAAC"}
|
|
@@ -1,44 +1,41 @@
|
|
|
1
1
|
// Aphex CMS Document by ID API Handlers
|
|
2
2
|
import { json } from '@sveltejs/kit';
|
|
3
|
-
import {
|
|
3
|
+
import { authToContext } from '../local-api/auth-helpers.js';
|
|
4
|
+
import { PermissionError } from '../local-api/permissions.js';
|
|
4
5
|
// GET /api/documents/[id] - Get document by ID
|
|
5
6
|
export const GET = async ({ params, url, locals }) => {
|
|
6
7
|
try {
|
|
7
|
-
const {
|
|
8
|
-
const
|
|
8
|
+
const { localAPI, databaseAdapter } = locals.aphexCMS;
|
|
9
|
+
const context = authToContext(locals.auth);
|
|
9
10
|
const { id } = params;
|
|
10
|
-
if (!auth) {
|
|
11
|
-
return json({ success: false, error: 'Unauthorized' }, { status: 401 });
|
|
12
|
-
}
|
|
13
11
|
if (!id) {
|
|
14
12
|
return json({ success: false, error: 'Document ID is required' }, { status: 400 });
|
|
15
13
|
}
|
|
16
|
-
// Parse
|
|
14
|
+
// Parse query params
|
|
17
15
|
const depthParam = url.searchParams.get('depth');
|
|
18
|
-
const depth = depthParam ? parseInt(depthParam) : 0;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
const depth = depthParam ? Math.max(0, Math.min(parseInt(depthParam), 5)) : 0;
|
|
17
|
+
const perspective = url.searchParams.get('perspective') || 'draft';
|
|
18
|
+
// First, fetch document to get its type (need this for collection-specific API)
|
|
19
|
+
const rawDoc = await databaseAdapter.findByDocId(context.organizationId, id, 0);
|
|
20
|
+
if (!rawDoc) {
|
|
22
21
|
return json({ success: false, error: 'Document not found' }, { status: 404 });
|
|
23
22
|
}
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
console.warn('[documents-by-id] Failed to populate createdBy user:', err);
|
|
41
|
-
}
|
|
23
|
+
// Get collection API (TypeScript-safe)
|
|
24
|
+
const collection = localAPI.collections[rawDoc.type];
|
|
25
|
+
if (!collection) {
|
|
26
|
+
return json({
|
|
27
|
+
success: false,
|
|
28
|
+
error: 'Invalid document type',
|
|
29
|
+
message: `Collection '${rawDoc.type}' not found`
|
|
30
|
+
}, { status: 400 });
|
|
31
|
+
}
|
|
32
|
+
// Now use LocalAPI for permission-checked retrieval
|
|
33
|
+
const document = await collection.findByID(context, id, {
|
|
34
|
+
depth,
|
|
35
|
+
perspective
|
|
36
|
+
});
|
|
37
|
+
if (!document) {
|
|
38
|
+
return json({ success: false, error: 'Document not found' }, { status: 404 });
|
|
42
39
|
}
|
|
43
40
|
return json({
|
|
44
41
|
success: true,
|
|
@@ -47,6 +44,13 @@ export const GET = async ({ params, url, locals }) => {
|
|
|
47
44
|
}
|
|
48
45
|
catch (error) {
|
|
49
46
|
console.error('Failed to fetch document:', error);
|
|
47
|
+
if (error instanceof PermissionError) {
|
|
48
|
+
return json({
|
|
49
|
+
success: false,
|
|
50
|
+
error: 'Forbidden',
|
|
51
|
+
message: error.message
|
|
52
|
+
}, { status: 403 });
|
|
53
|
+
}
|
|
50
54
|
return json({
|
|
51
55
|
success: false,
|
|
52
56
|
error: 'Failed to fetch document',
|
|
@@ -57,34 +61,33 @@ export const GET = async ({ params, url, locals }) => {
|
|
|
57
61
|
// PUT /api/documents/[id] - Update document
|
|
58
62
|
export const PUT = async ({ params, request, locals }) => {
|
|
59
63
|
try {
|
|
60
|
-
const { databaseAdapter } = locals.aphexCMS;
|
|
61
|
-
const
|
|
64
|
+
const { localAPI, databaseAdapter } = locals.aphexCMS;
|
|
65
|
+
const context = authToContext(locals.auth);
|
|
62
66
|
const { id } = params;
|
|
63
67
|
const body = await request.json();
|
|
64
|
-
if (!auth) {
|
|
65
|
-
return json({ success: false, error: 'Unauthorized' }, { status: 401 });
|
|
66
|
-
}
|
|
67
|
-
// Check write permissions (viewers are read-only)
|
|
68
|
-
if (!canWrite(auth)) {
|
|
69
|
-
return json({
|
|
70
|
-
success: false,
|
|
71
|
-
error: 'Forbidden',
|
|
72
|
-
message: 'You do not have permission to update documents. Viewers have read-only access.'
|
|
73
|
-
}, { status: 403 });
|
|
74
|
-
}
|
|
75
|
-
const documentData = body.draftData;
|
|
76
68
|
if (!id) {
|
|
77
69
|
return json({ success: false, error: 'Document ID is required' }, { status: 400 });
|
|
78
70
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
71
|
+
const documentData = body.draftData || body.data;
|
|
72
|
+
const shouldPublish = body.publish || false;
|
|
73
|
+
// Fetch document to get its type
|
|
74
|
+
const rawDoc = await databaseAdapter.findByDocId(context.organizationId, id, 0);
|
|
75
|
+
if (!rawDoc) {
|
|
76
|
+
return json({ success: false, error: 'Document not found' }, { status: 404 });
|
|
84
77
|
}
|
|
85
|
-
|
|
86
|
-
|
|
78
|
+
// Get collection API (TypeScript-safe)
|
|
79
|
+
const collection = localAPI.collections[rawDoc.type];
|
|
80
|
+
if (!collection) {
|
|
81
|
+
return json({
|
|
82
|
+
success: false,
|
|
83
|
+
error: 'Invalid document type',
|
|
84
|
+
message: `Collection '${rawDoc.type}' not found`
|
|
85
|
+
}, { status: 400 });
|
|
87
86
|
}
|
|
87
|
+
// Update via LocalAPI (permission checks happen inside)
|
|
88
|
+
const updatedDocument = await collection.update(context, id, documentData, {
|
|
89
|
+
publish: shouldPublish
|
|
90
|
+
});
|
|
88
91
|
if (!updatedDocument) {
|
|
89
92
|
return json({ success: false, error: 'Document not found' }, { status: 404 });
|
|
90
93
|
}
|
|
@@ -95,6 +98,13 @@ export const PUT = async ({ params, request, locals }) => {
|
|
|
95
98
|
}
|
|
96
99
|
catch (error) {
|
|
97
100
|
console.error('Failed to update document:', error);
|
|
101
|
+
if (error instanceof PermissionError) {
|
|
102
|
+
return json({
|
|
103
|
+
success: false,
|
|
104
|
+
error: 'Forbidden',
|
|
105
|
+
message: error.message
|
|
106
|
+
}, { status: 403 });
|
|
107
|
+
}
|
|
98
108
|
return json({
|
|
99
109
|
success: false,
|
|
100
110
|
error: 'Failed to update document',
|
|
@@ -105,24 +115,28 @@ export const PUT = async ({ params, request, locals }) => {
|
|
|
105
115
|
// DELETE /api/documents/[id] - Delete document
|
|
106
116
|
export const DELETE = async ({ params, locals }) => {
|
|
107
117
|
try {
|
|
108
|
-
const { databaseAdapter } = locals.aphexCMS;
|
|
109
|
-
const
|
|
118
|
+
const { localAPI, databaseAdapter } = locals.aphexCMS;
|
|
119
|
+
const context = authToContext(locals.auth);
|
|
110
120
|
const { id } = params;
|
|
111
|
-
if (!
|
|
112
|
-
return json({ success: false, error: '
|
|
121
|
+
if (!id) {
|
|
122
|
+
return json({ success: false, error: 'Document ID is required' }, { status: 400 });
|
|
123
|
+
}
|
|
124
|
+
// Fetch document to get its type
|
|
125
|
+
const rawDoc = await databaseAdapter.findByDocId(context.organizationId, id, 0);
|
|
126
|
+
if (!rawDoc) {
|
|
127
|
+
return json({ success: false, error: 'Document not found' }, { status: 404 });
|
|
113
128
|
}
|
|
114
|
-
//
|
|
115
|
-
|
|
129
|
+
// Get collection API (TypeScript-safe)
|
|
130
|
+
const collection = localAPI.collections[rawDoc.type];
|
|
131
|
+
if (!collection) {
|
|
116
132
|
return json({
|
|
117
133
|
success: false,
|
|
118
|
-
error: '
|
|
119
|
-
message: '
|
|
120
|
-
}, { status:
|
|
121
|
-
}
|
|
122
|
-
if (!id) {
|
|
123
|
-
return json({ success: false, error: 'Document ID is required' }, { status: 400 });
|
|
134
|
+
error: 'Invalid document type',
|
|
135
|
+
message: `Collection '${rawDoc.type}' not found`
|
|
136
|
+
}, { status: 400 });
|
|
124
137
|
}
|
|
125
|
-
|
|
138
|
+
// Delete via LocalAPI (permission checks happen inside)
|
|
139
|
+
const success = await collection.delete(context, id);
|
|
126
140
|
if (!success) {
|
|
127
141
|
return json({ success: false, error: 'Document not found' }, { status: 404 });
|
|
128
142
|
}
|
|
@@ -133,6 +147,13 @@ export const DELETE = async ({ params, locals }) => {
|
|
|
133
147
|
}
|
|
134
148
|
catch (error) {
|
|
135
149
|
console.error('Failed to delete document:', error);
|
|
150
|
+
if (error instanceof PermissionError) {
|
|
151
|
+
return json({
|
|
152
|
+
success: false,
|
|
153
|
+
error: 'Forbidden',
|
|
154
|
+
message: error.message
|
|
155
|
+
}, { status: 403 });
|
|
156
|
+
}
|
|
136
157
|
return json({
|
|
137
158
|
success: false,
|
|
138
159
|
error: 'Failed to delete document',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"documents-publish.d.ts","sourceRoot":"","sources":["../../src/lib/routes/documents-publish.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAKpD,eAAO,MAAM,IAAI,EAAE,
|
|
1
|
+
{"version":3,"file":"documents-publish.d.ts","sourceRoot":"","sources":["../../src/lib/routes/documents-publish.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAKpD,eAAO,MAAM,IAAI,EAAE,cAiGlB,CAAC;AAGF,eAAO,MAAM,MAAM,EAAE,cAqFpB,CAAC"}
|
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
// Aphex CMS Document Publish API Handlers
|
|
2
2
|
import { json } from '@sveltejs/kit';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { authToContext } from '../local-api/auth-helpers.js';
|
|
4
|
+
import { PermissionError } from '../local-api/permissions.js';
|
|
5
5
|
// POST /api/documents/[id]/publish - Publish document
|
|
6
6
|
export const POST = async ({ params, locals }) => {
|
|
7
7
|
try {
|
|
8
|
-
const {
|
|
9
|
-
const
|
|
8
|
+
const { localAPI, databaseAdapter } = locals.aphexCMS;
|
|
9
|
+
const context = authToContext(locals.auth);
|
|
10
10
|
const { id } = params;
|
|
11
|
-
if (!auth) {
|
|
12
|
-
return json({
|
|
13
|
-
success: false,
|
|
14
|
-
error: 'Unauthorized',
|
|
15
|
-
message: 'Authentication required'
|
|
16
|
-
}, { status: 401 });
|
|
17
|
-
}
|
|
18
|
-
// Check write permissions (viewers are read-only)
|
|
19
|
-
if (!canWrite(auth)) {
|
|
20
|
-
return json({
|
|
21
|
-
success: false,
|
|
22
|
-
error: 'Forbidden',
|
|
23
|
-
message: 'You do not have permission to publish documents. Viewers have read-only access.'
|
|
24
|
-
}, { status: 403 });
|
|
25
|
-
}
|
|
26
11
|
if (!id) {
|
|
27
12
|
return json({
|
|
28
13
|
success: false,
|
|
@@ -30,57 +15,31 @@ export const POST = async ({ params, locals }) => {
|
|
|
30
15
|
message: 'Document ID is required'
|
|
31
16
|
}, { status: 400 });
|
|
32
17
|
}
|
|
33
|
-
//
|
|
34
|
-
const
|
|
35
|
-
if (!
|
|
18
|
+
// Fetch document to get its type
|
|
19
|
+
const rawDoc = await databaseAdapter.findByDocId(context.organizationId, id, 0);
|
|
20
|
+
if (!rawDoc) {
|
|
36
21
|
return json({
|
|
37
22
|
success: false,
|
|
38
|
-
error: 'Document not found
|
|
39
|
-
message: 'Document may not exist
|
|
23
|
+
error: 'Document not found',
|
|
24
|
+
message: 'Document may not exist'
|
|
40
25
|
}, { status: 404 });
|
|
41
26
|
}
|
|
42
|
-
// Get
|
|
43
|
-
const
|
|
44
|
-
if (!
|
|
27
|
+
// Get collection API (TypeScript-safe)
|
|
28
|
+
const collection = localAPI.collections[rawDoc.type];
|
|
29
|
+
if (!collection) {
|
|
45
30
|
return json({
|
|
46
31
|
success: false,
|
|
47
32
|
error: 'Invalid document type',
|
|
48
|
-
message: `
|
|
49
|
-
}, { status: 400 });
|
|
50
|
-
}
|
|
51
|
-
// VALIDATE before publishing - block if errors exist
|
|
52
|
-
const validationErrors = [];
|
|
53
|
-
for (const field of schema.fields) {
|
|
54
|
-
const value = document.draftData[field.name];
|
|
55
|
-
const result = await validateField(field, value, document.draftData);
|
|
56
|
-
if (!result.isValid) {
|
|
57
|
-
const errorMessages = result.errors
|
|
58
|
-
.filter((e) => e.level === 'error')
|
|
59
|
-
.map((e) => e.message);
|
|
60
|
-
if (errorMessages.length > 0) {
|
|
61
|
-
validationErrors.push({
|
|
62
|
-
field: field.name,
|
|
63
|
-
errors: errorMessages
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
// Block publishing if validation errors exist
|
|
69
|
-
if (validationErrors.length > 0) {
|
|
70
|
-
return json({
|
|
71
|
-
success: false,
|
|
72
|
-
error: 'Cannot publish: validation errors',
|
|
73
|
-
message: 'Please fix all validation errors before publishing',
|
|
74
|
-
validationErrors
|
|
33
|
+
message: `Collection '${rawDoc.type}' not found`
|
|
75
34
|
}, { status: 400 });
|
|
76
35
|
}
|
|
77
|
-
//
|
|
78
|
-
const publishedDocument = await
|
|
36
|
+
// Publish via LocalAPI (permission checks and validation happen inside)
|
|
37
|
+
const publishedDocument = await collection.publish(context, id);
|
|
79
38
|
if (!publishedDocument) {
|
|
80
39
|
return json({
|
|
81
40
|
success: false,
|
|
82
41
|
error: 'Document not found or cannot be published',
|
|
83
|
-
message: 'Document may not
|
|
42
|
+
message: 'Document may not have draft content to publish'
|
|
84
43
|
}, { status: 404 });
|
|
85
44
|
}
|
|
86
45
|
return json({
|
|
@@ -91,6 +50,21 @@ export const POST = async ({ params, locals }) => {
|
|
|
91
50
|
}
|
|
92
51
|
catch (error) {
|
|
93
52
|
console.error('Failed to publish document:', error);
|
|
53
|
+
if (error instanceof PermissionError) {
|
|
54
|
+
return json({
|
|
55
|
+
success: false,
|
|
56
|
+
error: 'Forbidden',
|
|
57
|
+
message: error.message
|
|
58
|
+
}, { status: 403 });
|
|
59
|
+
}
|
|
60
|
+
// Validation errors from LocalAPI are regular errors with specific messages
|
|
61
|
+
if (error instanceof Error && error.message.includes('validation errors')) {
|
|
62
|
+
return json({
|
|
63
|
+
success: false,
|
|
64
|
+
error: 'Cannot publish: validation errors',
|
|
65
|
+
message: error.message
|
|
66
|
+
}, { status: 400 });
|
|
67
|
+
}
|
|
94
68
|
return json({
|
|
95
69
|
success: false,
|
|
96
70
|
error: 'Failed to publish document',
|
|
@@ -101,32 +75,36 @@ export const POST = async ({ params, locals }) => {
|
|
|
101
75
|
// DELETE /api/documents/[id]/publish - Unpublish document
|
|
102
76
|
export const DELETE = async ({ params, locals }) => {
|
|
103
77
|
try {
|
|
104
|
-
const { databaseAdapter } = locals.aphexCMS;
|
|
105
|
-
const
|
|
78
|
+
const { localAPI, databaseAdapter } = locals.aphexCMS;
|
|
79
|
+
const context = authToContext(locals.auth);
|
|
106
80
|
const { id } = params;
|
|
107
|
-
if (!
|
|
81
|
+
if (!id) {
|
|
108
82
|
return json({
|
|
109
83
|
success: false,
|
|
110
|
-
error: '
|
|
111
|
-
message: '
|
|
112
|
-
}, { status:
|
|
84
|
+
error: 'Missing document ID',
|
|
85
|
+
message: 'Document ID is required'
|
|
86
|
+
}, { status: 400 });
|
|
113
87
|
}
|
|
114
|
-
//
|
|
115
|
-
|
|
88
|
+
// Fetch document to get its type
|
|
89
|
+
const rawDoc = await databaseAdapter.findByDocId(context.organizationId, id, 0);
|
|
90
|
+
if (!rawDoc) {
|
|
116
91
|
return json({
|
|
117
92
|
success: false,
|
|
118
|
-
error: '
|
|
119
|
-
message:
|
|
120
|
-
}, { status:
|
|
93
|
+
error: 'Document not found',
|
|
94
|
+
message: `No document found with ID: ${id}`
|
|
95
|
+
}, { status: 404 });
|
|
121
96
|
}
|
|
122
|
-
|
|
97
|
+
// Get collection API (TypeScript-safe)
|
|
98
|
+
const collection = localAPI.collections[rawDoc.type];
|
|
99
|
+
if (!collection) {
|
|
123
100
|
return json({
|
|
124
101
|
success: false,
|
|
125
|
-
error: '
|
|
126
|
-
message: '
|
|
102
|
+
error: 'Invalid document type',
|
|
103
|
+
message: `Collection '${rawDoc.type}' not found`
|
|
127
104
|
}, { status: 400 });
|
|
128
105
|
}
|
|
129
|
-
|
|
106
|
+
// Unpublish via LocalAPI (permission checks happen inside)
|
|
107
|
+
const unpublishedDocument = await collection.unpublish(context, id);
|
|
130
108
|
if (!unpublishedDocument) {
|
|
131
109
|
return json({
|
|
132
110
|
success: false,
|
|
@@ -142,6 +120,13 @@ export const DELETE = async ({ params, locals }) => {
|
|
|
142
120
|
}
|
|
143
121
|
catch (error) {
|
|
144
122
|
console.error('Failed to unpublish document:', error);
|
|
123
|
+
if (error instanceof PermissionError) {
|
|
124
|
+
return json({
|
|
125
|
+
success: false,
|
|
126
|
+
error: 'Forbidden',
|
|
127
|
+
message: error.message
|
|
128
|
+
}, { status: 403 });
|
|
129
|
+
}
|
|
145
130
|
return json({
|
|
146
131
|
success: false,
|
|
147
132
|
error: 'Failed to unpublish document',
|