@aphexcms/cms-core 0.1.12 → 0.1.13

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.
Files changed (96) hide show
  1. package/LICENSE +21 -0
  2. package/dist/api/documents.d.ts +1 -0
  3. package/dist/api/documents.d.ts.map +1 -1
  4. package/dist/api/documents.js +9 -1
  5. package/dist/api/types.d.ts +24 -2
  6. package/dist/api/types.d.ts.map +1 -1
  7. package/dist/auth/auth-hooks.d.ts.map +1 -1
  8. package/dist/auth/auth-hooks.js +18 -4
  9. package/dist/cli/generate-types.js +218 -0
  10. package/dist/cli/index.js +86 -0
  11. package/dist/components/AdminApp.svelte +26 -55
  12. package/dist/components/AdminApp.svelte.d.ts +3 -5
  13. package/dist/components/AdminApp.svelte.d.ts.map +1 -1
  14. package/dist/components/admin/DocumentEditor.svelte +60 -14
  15. package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -1
  16. package/dist/components/admin/fields/ReferenceField.svelte +2 -3
  17. package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -1
  18. package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts +8 -1
  19. package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -1
  20. package/dist/db/interfaces/asset.d.ts +22 -0
  21. package/dist/db/interfaces/asset.d.ts.map +1 -1
  22. package/dist/db/interfaces/document.d.ts +25 -0
  23. package/dist/db/interfaces/document.d.ts.map +1 -1
  24. package/dist/field-validation/utils.d.ts +19 -1
  25. package/dist/field-validation/utils.d.ts.map +1 -1
  26. package/dist/field-validation/utils.js +33 -0
  27. package/dist/hooks.d.ts +2 -0
  28. package/dist/hooks.d.ts.map +1 -1
  29. package/dist/hooks.js +80 -12
  30. package/dist/lib/auth/provider.js +1 -0
  31. package/dist/lib/db/index.js +4 -0
  32. package/dist/lib/db/interfaces/asset.js +1 -0
  33. package/dist/lib/db/interfaces/document.js +1 -0
  34. package/dist/lib/db/interfaces/index.js +1 -0
  35. package/dist/lib/db/interfaces/organization.js +1 -0
  36. package/dist/lib/db/interfaces/schema.js +1 -0
  37. package/dist/lib/db/interfaces/user.js +1 -0
  38. package/dist/lib/email/index.js +4 -0
  39. package/dist/lib/email/interfaces/email.js +1 -0
  40. package/dist/lib/field-validation/rule.js +221 -0
  41. package/dist/lib/field-validation/utils.js +99 -0
  42. package/dist/lib/storage/interfaces/index.js +2 -0
  43. package/dist/lib/storage/interfaces/storage.js +1 -0
  44. package/dist/lib/types/asset.js +2 -0
  45. package/dist/lib/types/auth.js +41 -0
  46. package/dist/lib/types/config.js +1 -0
  47. package/dist/lib/types/document.js +1 -0
  48. package/dist/lib/types/filters.js +5 -0
  49. package/dist/lib/types/index.js +9 -0
  50. package/dist/lib/types/organization.js +3 -0
  51. package/dist/lib/types/schemas.js +1 -0
  52. package/dist/lib/types/sidebar.js +1 -0
  53. package/dist/lib/types/user.js +1 -0
  54. package/dist/local-api/auth-helpers.d.ts +65 -0
  55. package/dist/local-api/auth-helpers.d.ts.map +1 -0
  56. package/dist/local-api/auth-helpers.js +102 -0
  57. package/dist/local-api/collection-api.d.ts +138 -0
  58. package/dist/local-api/collection-api.d.ts.map +1 -0
  59. package/dist/local-api/collection-api.js +276 -0
  60. package/dist/local-api/index.d.ts +108 -0
  61. package/dist/local-api/index.d.ts.map +1 -0
  62. package/dist/local-api/index.js +157 -0
  63. package/dist/local-api/permissions.d.ts +45 -0
  64. package/dist/local-api/permissions.d.ts.map +1 -0
  65. package/dist/local-api/permissions.js +117 -0
  66. package/dist/local-api/types.d.ts +65 -0
  67. package/dist/local-api/types.d.ts.map +1 -0
  68. package/dist/local-api/types.js +4 -0
  69. package/dist/routes/documents-by-id.d.ts.map +1 -1
  70. package/dist/routes/documents-by-id.js +84 -63
  71. package/dist/routes/documents-publish.d.ts.map +1 -1
  72. package/dist/routes/documents-publish.js +57 -72
  73. package/dist/routes/documents-query.d.ts +24 -0
  74. package/dist/routes/documents-query.d.ts.map +1 -0
  75. package/dist/routes/documents-query.js +95 -0
  76. package/dist/routes/documents.d.ts.map +1 -1
  77. package/dist/routes/documents.js +80 -75
  78. package/dist/routes/index.d.ts +2 -0
  79. package/dist/routes/index.d.ts.map +1 -1
  80. package/dist/routes/index.js +2 -0
  81. package/dist/server/index.d.ts +1 -0
  82. package/dist/server/index.d.ts.map +1 -1
  83. package/dist/server/index.js +2 -0
  84. package/dist/types/config.d.ts +18 -1
  85. package/dist/types/config.d.ts.map +1 -1
  86. package/dist/types/document.d.ts +1 -0
  87. package/dist/types/document.d.ts.map +1 -1
  88. package/dist/types/filters.d.ts +173 -0
  89. package/dist/types/filters.d.ts.map +1 -0
  90. package/dist/types/filters.js +5 -0
  91. package/dist/types/index.d.ts +1 -0
  92. package/dist/types/index.d.ts.map +1 -1
  93. package/dist/types/index.js +1 -0
  94. package/dist/types/schemas.d.ts +0 -5
  95. package/dist/types/schemas.d.ts.map +1 -1
  96. 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"}
@@ -0,0 +1,4 @@
1
+ // local-api/types.ts
2
+ //
3
+ // Core types for the Local API layer
4
+ export {};
@@ -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,cA2DjB,CAAC;AAGF,eAAO,MAAM,GAAG,EAAE,cAoEjB,CAAC;AAGF,eAAO,MAAM,MAAM,EAAE,cA+CpB,CAAC"}
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 { canWrite } from '../types/auth.js';
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 { databaseAdapter, auth: authProvider } = locals.aphexCMS;
8
- const auth = locals.auth;
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 depth parameter
14
+ // Parse query params
17
15
  const depthParam = url.searchParams.get('depth');
18
- const depth = depthParam ? parseInt(depthParam) : 0;
19
- const clampedDepth = isNaN(depth) ? 0 : Math.max(0, Math.min(depth, 5)); // Clamp between 0-5
20
- const document = await databaseAdapter.findByDocId(auth.organizationId, id, clampedDepth);
21
- if (!document) {
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
- // Populate createdBy user info if available
25
- if (document.createdBy && authProvider) {
26
- try {
27
- if (typeof document.createdBy === 'string') {
28
- const user = await authProvider.getUserById(document.createdBy);
29
- if (user) {
30
- document.createdBy = {
31
- id: user.id,
32
- name: user.name,
33
- email: user.email
34
- };
35
- }
36
- }
37
- }
38
- catch (err) {
39
- // If user fetch fails, keep the user ID
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 auth = locals.auth;
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
- let updatedDocument;
80
- // NO VALIDATION FOR DRAFTS - Sanity-style: drafts can have any state
81
- // Validation only happens on publish
82
- if (auth.type == 'session') {
83
- updatedDocument = await databaseAdapter.updateDocDraft(auth.organizationId, id, documentData, auth.user.id);
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
- else {
86
- updatedDocument = await databaseAdapter.updateDocDraft(auth.organizationId, id, documentData, auth.keyId);
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 auth = locals.auth;
118
+ const { localAPI, databaseAdapter } = locals.aphexCMS;
119
+ const context = authToContext(locals.auth);
110
120
  const { id } = params;
111
- if (!auth) {
112
- return json({ success: false, error: 'Unauthorized' }, { status: 401 });
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
- // Check write permissions (viewers are read-only)
115
- if (!canWrite(auth)) {
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: 'Forbidden',
119
- message: 'You do not have permission to delete documents. Viewers have read-only access.'
120
- }, { status: 403 });
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
- const success = await databaseAdapter.deleteDocById(auth.organizationId, id);
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,cAkIlB,CAAC;AAGF,eAAO,MAAM,MAAM,EAAE,cAsEpB,CAAC"}
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 { validateField } from '../field-validation/utils.js';
4
- import { canWrite } from '../types/auth.js';
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 { databaseAdapter, cmsEngine } = locals.aphexCMS;
9
- const auth = locals.auth;
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
- // Get document to validate
34
- const document = await databaseAdapter.findByDocId(auth.organizationId, id);
35
- if (!document || !document.draftData) {
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 or cannot be published',
39
- message: 'Document may not exist or may not have draft content'
23
+ error: 'Document not found',
24
+ message: 'Document may not exist'
40
25
  }, { status: 404 });
41
26
  }
42
- // Get schema for validation (from config to preserve validation functions)
43
- const schema = cmsEngine.getSchemaTypeByName(document.type);
44
- if (!schema) {
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: `Schema type '${document.type}' not found`
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
- // All validation passed - proceed with publish
78
- const publishedDocument = await databaseAdapter.publishDoc(auth.organizationId, id);
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 exist or may not have draft content'
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 auth = locals.auth;
78
+ const { localAPI, databaseAdapter } = locals.aphexCMS;
79
+ const context = authToContext(locals.auth);
106
80
  const { id } = params;
107
- if (!auth) {
81
+ if (!id) {
108
82
  return json({
109
83
  success: false,
110
- error: 'Unauthorized',
111
- message: 'Authentication required'
112
- }, { status: 401 });
84
+ error: 'Missing document ID',
85
+ message: 'Document ID is required'
86
+ }, { status: 400 });
113
87
  }
114
- // Check write permissions (viewers are read-only)
115
- if (!canWrite(auth)) {
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: 'Forbidden',
119
- message: 'You do not have permission to unpublish documents. Viewers have read-only access.'
120
- }, { status: 403 });
93
+ error: 'Document not found',
94
+ message: `No document found with ID: ${id}`
95
+ }, { status: 404 });
121
96
  }
122
- if (!id) {
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: 'Missing document ID',
126
- message: 'Document ID is required'
102
+ error: 'Invalid document type',
103
+ message: `Collection '${rawDoc.type}' not found`
127
104
  }, { status: 400 });
128
105
  }
129
- const unpublishedDocument = await databaseAdapter.unpublishDoc(auth.organizationId, id);
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',