@aphexcms/cms-core 2.0.3 → 2.0.5
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/cache/adapters/in-memory-cache-adapter.d.ts +21 -0
- package/dist/cache/adapters/in-memory-cache-adapter.d.ts.map +1 -0
- package/dist/cache/adapters/in-memory-cache-adapter.js +50 -0
- package/dist/cache/document-cache.d.ts +18 -0
- package/dist/cache/document-cache.d.ts.map +1 -0
- package/dist/cache/document-cache.js +45 -0
- package/dist/cache/index.d.ts +4 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +2 -0
- package/dist/cache/interfaces/cache.d.ts +18 -0
- package/dist/cache/interfaces/cache.d.ts.map +1 -0
- package/dist/cache/interfaces/cache.js +1 -0
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/components/admin/SchemaField.svelte +14 -0
- package/dist/components/admin/SchemaField.svelte.d.ts.map +1 -1
- package/dist/components/admin/fields/FileField.svelte +485 -0
- package/dist/components/admin/fields/FileField.svelte.d.ts +18 -0
- package/dist/components/admin/fields/FileField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/ReferenceField.svelte +2 -2
- package/dist/components/fields/index.d.ts +1 -0
- package/dist/components/fields/index.d.ts.map +1 -1
- package/dist/components/fields/index.js +1 -0
- package/dist/graphql/schema.d.ts.map +1 -1
- package/dist/graphql/schema.js +15 -1
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +11 -0
- package/dist/lib/cache/adapters/in-memory-cache-adapter.d.ts +21 -0
- package/dist/lib/cache/adapters/in-memory-cache-adapter.d.ts.map +1 -0
- package/dist/lib/cache/adapters/in-memory-cache-adapter.js +51 -0
- package/dist/lib/cache/adapters/in-memory-cache-adapter.js.map +1 -0
- package/dist/lib/cache/document-cache.d.ts +18 -0
- package/dist/lib/cache/document-cache.d.ts.map +1 -0
- package/dist/lib/cache/document-cache.js +46 -0
- package/dist/lib/cache/document-cache.js.map +1 -0
- package/dist/lib/cache/index.d.ts +4 -0
- package/dist/lib/cache/index.d.ts.map +1 -0
- package/dist/lib/cache/index.js +3 -0
- package/dist/lib/cache/index.js.map +1 -0
- package/dist/lib/cache/interfaces/cache.d.ts +18 -0
- package/dist/lib/cache/interfaces/cache.d.ts.map +1 -0
- package/dist/lib/cache/interfaces/cache.js +2 -0
- package/dist/lib/cache/interfaces/cache.js.map +1 -0
- package/dist/lib/client/index.d.ts +1 -0
- package/dist/lib/client/index.d.ts.map +1 -1
- package/dist/lib/client/index.js +1 -0
- package/dist/lib/client/index.js.map +1 -1
- package/dist/lib/components/fields/index.d.ts +1 -0
- package/dist/lib/components/fields/index.d.ts.map +1 -1
- package/dist/lib/components/fields/index.js +1 -0
- package/dist/lib/components/fields/index.js.map +1 -1
- package/dist/lib/graphql/schema.d.ts.map +1 -1
- package/dist/lib/graphql/schema.js +15 -1
- package/dist/lib/graphql/schema.js.map +1 -1
- package/dist/lib/hooks.d.ts.map +1 -1
- package/dist/lib/hooks.js +11 -0
- package/dist/lib/hooks.js.map +1 -1
- package/dist/lib/local-api/collection-api.d.ts +5 -1
- package/dist/lib/local-api/collection-api.d.ts.map +1 -1
- package/dist/lib/local-api/collection-api.js +63 -10
- package/dist/lib/local-api/collection-api.js.map +1 -1
- package/dist/lib/local-api/index.d.ts +13 -0
- package/dist/lib/local-api/index.d.ts.map +1 -1
- package/dist/lib/local-api/index.js +26 -2
- package/dist/lib/local-api/index.js.map +1 -1
- package/dist/lib/routes/assets.d.ts.map +1 -1
- package/dist/lib/routes/assets.js +14 -0
- package/dist/lib/routes/assets.js.map +1 -1
- package/dist/lib/routes/documents-by-id.js +18 -18
- package/dist/lib/routes/documents-by-id.js.map +1 -1
- package/dist/lib/routes/documents-publish.js +12 -12
- package/dist/lib/routes/documents-publish.js.map +1 -1
- package/dist/lib/schema-utils/validator.d.ts.map +1 -1
- package/dist/lib/schema-utils/validator.js +1 -0
- package/dist/lib/schema-utils/validator.js.map +1 -1
- package/dist/lib/server/index.d.ts +1 -0
- package/dist/lib/server/index.d.ts.map +1 -1
- package/dist/lib/server/index.js +1 -0
- package/dist/lib/server/index.js.map +1 -1
- package/dist/lib/services/hierarchy-service.d.ts +26 -0
- package/dist/lib/services/hierarchy-service.d.ts.map +1 -0
- package/dist/lib/services/hierarchy-service.js +64 -0
- package/dist/lib/services/hierarchy-service.js.map +1 -0
- package/dist/lib/services/index.d.ts +1 -0
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +1 -0
- package/dist/lib/services/index.js.map +1 -1
- package/dist/lib/storage/adapters/local-storage-adapter.d.ts.map +1 -1
- package/dist/lib/storage/adapters/local-storage-adapter.js +0 -14
- package/dist/lib/storage/adapters/local-storage-adapter.js.map +1 -1
- package/dist/lib/storage/interfaces/storage.d.ts +0 -1
- package/dist/lib/storage/interfaces/storage.d.ts.map +1 -1
- package/dist/lib/types/asset.d.ts +9 -0
- package/dist/lib/types/asset.d.ts.map +1 -1
- package/dist/lib/types/config.d.ts +7 -0
- package/dist/lib/types/config.d.ts.map +1 -1
- package/dist/lib/types/schemas.d.ts +10 -2
- package/dist/lib/types/schemas.d.ts.map +1 -1
- package/dist/lib/utils/mime-detect.d.ts +22 -0
- package/dist/lib/utils/mime-detect.d.ts.map +1 -0
- package/dist/lib/utils/mime-detect.js +201 -0
- package/dist/lib/utils/mime-detect.js.map +1 -0
- package/dist/local-api/collection-api.d.ts +5 -1
- package/dist/local-api/collection-api.d.ts.map +1 -1
- package/dist/local-api/collection-api.js +63 -10
- package/dist/local-api/index.d.ts +13 -0
- package/dist/local-api/index.d.ts.map +1 -1
- package/dist/local-api/index.js +26 -2
- package/dist/routes/assets.d.ts.map +1 -1
- package/dist/routes/assets.js +14 -0
- package/dist/routes/documents-by-id.js +18 -18
- package/dist/routes/documents-publish.js +12 -12
- package/dist/schema-utils/validator.d.ts.map +1 -1
- package/dist/schema-utils/validator.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/services/hierarchy-service.d.ts +26 -0
- package/dist/services/hierarchy-service.d.ts.map +1 -0
- package/dist/services/hierarchy-service.js +63 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -0
- package/dist/storage/adapters/local-storage-adapter.d.ts.map +1 -1
- package/dist/storage/adapters/local-storage-adapter.js +0 -14
- package/dist/storage/interfaces/storage.d.ts +0 -1
- package/dist/storage/interfaces/storage.d.ts.map +1 -1
- package/dist/types/asset.d.ts +9 -0
- package/dist/types/asset.d.ts.map +1 -1
- package/dist/types/config.d.ts +7 -0
- package/dist/types/config.d.ts.map +1 -1
- package/dist/types/schemas.d.ts +10 -2
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/utils/mime-detect.d.ts +22 -0
- package/dist/utils/mime-detect.d.ts.map +1 -0
- package/dist/utils/mime-detect.js +200 -0
- package/package.json +1 -1
|
@@ -35,11 +35,15 @@ export class CollectionAPI {
|
|
|
35
35
|
databaseAdapter;
|
|
36
36
|
_schema;
|
|
37
37
|
permissions;
|
|
38
|
-
|
|
38
|
+
documentCache;
|
|
39
|
+
hierarchyService;
|
|
40
|
+
constructor(collectionName, databaseAdapter, _schema, permissions, documentCache, hierarchyService) {
|
|
39
41
|
this.collectionName = collectionName;
|
|
40
42
|
this.databaseAdapter = databaseAdapter;
|
|
41
43
|
this._schema = _schema;
|
|
42
44
|
this.permissions = permissions;
|
|
45
|
+
this.documentCache = documentCache;
|
|
46
|
+
this.hierarchyService = hierarchyService;
|
|
43
47
|
// Validate collection exists
|
|
44
48
|
this.permissions.validateCollection(collectionName);
|
|
45
49
|
}
|
|
@@ -70,15 +74,32 @@ export class CollectionAPI {
|
|
|
70
74
|
async find(context, options = {}) {
|
|
71
75
|
// Permission check (unless overrideAccess)
|
|
72
76
|
await this.permissions.canRead(context, this.collectionName);
|
|
73
|
-
// Call adapter's advanced find method
|
|
74
|
-
const result = await this.databaseAdapter.findManyDocAdvanced(context.organizationId, this.collectionName, options);
|
|
75
|
-
// Transform documents to extract data based on perspective
|
|
76
77
|
const perspective = options.perspective || 'draft';
|
|
78
|
+
// Check cache for published queries
|
|
79
|
+
if (perspective === 'published' && this.documentCache) {
|
|
80
|
+
const cached = await this.documentCache.getQuery(context.organizationId, this.collectionName, options);
|
|
81
|
+
if (cached)
|
|
82
|
+
return cached;
|
|
83
|
+
}
|
|
84
|
+
// Resolve org IDs via hierarchy service (cached) and pass directly —
|
|
85
|
+
// this avoids the adapter opening a transaction just to set RLS context
|
|
86
|
+
const findOptions = { ...options };
|
|
87
|
+
if (this.hierarchyService && !findOptions.filterOrganizationIds) {
|
|
88
|
+
const orgIds = await this.hierarchyService.getOrgIdsWithChildren(context.organizationId);
|
|
89
|
+
findOptions.filterOrganizationIds = orgIds;
|
|
90
|
+
}
|
|
91
|
+
const result = await this.databaseAdapter.findManyDocAdvanced(context.organizationId, this.collectionName, findOptions);
|
|
92
|
+
// Transform documents to extract data based on perspective
|
|
77
93
|
const transformedDocs = result.docs.map((doc) => transformDocument(doc, perspective));
|
|
78
|
-
|
|
94
|
+
const findResult = {
|
|
79
95
|
...result,
|
|
80
96
|
docs: transformedDocs
|
|
81
97
|
};
|
|
98
|
+
// Populate cache for published queries
|
|
99
|
+
if (perspective === 'published' && this.documentCache) {
|
|
100
|
+
await this.documentCache.setQuery(context.organizationId, this.collectionName, options, findResult);
|
|
101
|
+
}
|
|
102
|
+
return findResult;
|
|
82
103
|
}
|
|
83
104
|
/**
|
|
84
105
|
* Find a single document by ID
|
|
@@ -95,13 +116,29 @@ export class CollectionAPI {
|
|
|
95
116
|
async findByID(context, id, options) {
|
|
96
117
|
// Permission check (unless overrideAccess)
|
|
97
118
|
await this.permissions.canRead(context, this.collectionName);
|
|
98
|
-
const
|
|
119
|
+
const perspective = options?.perspective || 'draft';
|
|
120
|
+
// Check cache for published lookups
|
|
121
|
+
if (perspective === 'published' && this.documentCache) {
|
|
122
|
+
const cached = await this.documentCache.getDocument(context.organizationId, id);
|
|
123
|
+
if (cached)
|
|
124
|
+
return cached;
|
|
125
|
+
}
|
|
126
|
+
// Resolve org IDs via hierarchy service (cached) — avoids RLS transaction
|
|
127
|
+
const findOptions = { ...options };
|
|
128
|
+
if (this.hierarchyService && !findOptions.filterOrganizationIds) {
|
|
129
|
+
const orgIds = await this.hierarchyService.getOrgIdsWithChildren(context.organizationId);
|
|
130
|
+
findOptions.filterOrganizationIds = orgIds;
|
|
131
|
+
}
|
|
132
|
+
const result = await this.databaseAdapter.findByDocIdAdvanced(context.organizationId, id, findOptions);
|
|
99
133
|
if (!result) {
|
|
100
134
|
return null;
|
|
101
135
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
136
|
+
const transformed = transformDocument(result, perspective);
|
|
137
|
+
// Populate cache for published lookups
|
|
138
|
+
if (perspective === 'published' && this.documentCache) {
|
|
139
|
+
await this.documentCache.setDocument(context.organizationId, id, transformed);
|
|
140
|
+
}
|
|
141
|
+
return transformed;
|
|
105
142
|
}
|
|
106
143
|
/**
|
|
107
144
|
* Count documents matching a where clause
|
|
@@ -251,7 +288,13 @@ export class CollectionAPI {
|
|
|
251
288
|
async delete(context, id) {
|
|
252
289
|
// Permission check (unless overrideAccess)
|
|
253
290
|
await this.permissions.canDelete(context, this.collectionName);
|
|
254
|
-
|
|
291
|
+
const result = await this.databaseAdapter.deleteDocById(context.organizationId, id);
|
|
292
|
+
// Invalidate cache for deleted document
|
|
293
|
+
if (result && this.documentCache) {
|
|
294
|
+
await this.documentCache.invalidateDocument(context.organizationId, id);
|
|
295
|
+
await this.documentCache.invalidateCollection(context.organizationId, this.collectionName);
|
|
296
|
+
}
|
|
297
|
+
return result;
|
|
255
298
|
}
|
|
256
299
|
/**
|
|
257
300
|
* Publish a document
|
|
@@ -285,6 +328,11 @@ export class CollectionAPI {
|
|
|
285
328
|
if (!publishedDocument) {
|
|
286
329
|
return null;
|
|
287
330
|
}
|
|
331
|
+
// Invalidate cache for this document and all collection queries
|
|
332
|
+
if (this.documentCache) {
|
|
333
|
+
await this.documentCache.invalidateDocument(context.organizationId, id);
|
|
334
|
+
await this.documentCache.invalidateCollection(context.organizationId, this.collectionName);
|
|
335
|
+
}
|
|
288
336
|
return transformDocument(publishedDocument, 'published');
|
|
289
337
|
}
|
|
290
338
|
/**
|
|
@@ -305,6 +353,11 @@ export class CollectionAPI {
|
|
|
305
353
|
if (!document) {
|
|
306
354
|
return null;
|
|
307
355
|
}
|
|
356
|
+
// Invalidate cache — document is no longer published
|
|
357
|
+
if (this.documentCache) {
|
|
358
|
+
await this.documentCache.invalidateDocument(context.organizationId, id);
|
|
359
|
+
await this.documentCache.invalidateCollection(context.organizationId, this.collectionName);
|
|
360
|
+
}
|
|
308
361
|
return transformDocument(document, 'draft');
|
|
309
362
|
}
|
|
310
363
|
}
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { CMSConfig } from '../types/config.js';
|
|
2
2
|
import type { DatabaseAdapter } from '../db/index.js';
|
|
3
|
+
import type { FindOptions } from '../types/filters.js';
|
|
3
4
|
import type { SchemaType } from '../types/schemas.js';
|
|
4
5
|
import { CollectionAPI } from './collection-api.js';
|
|
6
|
+
import type { LocalAPIContext } from './types.js';
|
|
5
7
|
/**
|
|
6
8
|
* Collections map - provides type-safe access to all collections
|
|
7
9
|
* This interface is meant to be augmented by generated types
|
|
@@ -44,6 +46,8 @@ export declare class LocalAPI {
|
|
|
44
46
|
collections: Collections;
|
|
45
47
|
private userAdapter;
|
|
46
48
|
private systemAdapter;
|
|
49
|
+
private documentCache;
|
|
50
|
+
private hierarchyService;
|
|
47
51
|
private permissions;
|
|
48
52
|
private schemas;
|
|
49
53
|
constructor(config: CMSConfig, userAdapter: DatabaseAdapter, systemAdapter?: DatabaseAdapter);
|
|
@@ -68,6 +72,15 @@ export declare class LocalAPI {
|
|
|
68
72
|
* Get schema for a collection
|
|
69
73
|
*/
|
|
70
74
|
getCollectionSchema(name: string): SchemaType | undefined;
|
|
75
|
+
/**
|
|
76
|
+
* Find a document by ID without knowing its collection type.
|
|
77
|
+
* Resolves org hierarchy and passes filterOrganizationIds to avoid RLS transactions.
|
|
78
|
+
* Returns the raw document with its type, or null if not found.
|
|
79
|
+
*/
|
|
80
|
+
findDocumentById(context: LocalAPIContext, id: string, options?: Partial<FindOptions<unknown>>): Promise<{
|
|
81
|
+
type: string;
|
|
82
|
+
document: unknown;
|
|
83
|
+
} | null>;
|
|
71
84
|
}
|
|
72
85
|
/**
|
|
73
86
|
* Create and initialize the Local API
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/local-api/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;;GAGG;AACH,MAAM,WAAW,WAAW;IAE3B,CAAC,cAAc,EAAE,MAAM,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;CAGjD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,qBAAa,QAAQ;IAUnB,OAAO,CAAC,MAAM;IATR,WAAW,EAAE,WAAW,CAAqB;IACpD,OAAO,CAAC,WAAW,CAAkB;IACrC,OAAO,CAAC,aAAa,CAAyB;IAC9C,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,gBAAgB,CAAmB;IAC3C,OAAO,CAAC,WAAW,CAAoB;IACvC,OAAO,CAAC,OAAO,CAA0B;gBAGhC,MAAM,EAAE,SAAS,EACzB,WAAW,EAAE,eAAe,EAC5B,aAAa,CAAC,EAAE,eAAe;IAqBhC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAgC7B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAOlB;;OAEG;IACH,kBAAkB,IAAI,MAAM,EAAE;IAI9B;;OAEG;IACH,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;IAIpC;;OAEG;IACH,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAIzD;;;;OAIG;IACG,gBAAgB,CACrB,OAAO,EAAE,eAAe,EACxB,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,GACrC,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;CAetD;AAKD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAC7B,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,eAAe,EAC5B,aAAa,CAAC,EAAE,eAAe,GAC7B,QAAQ,CAGV;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,IAAI,QAAQ,CAKtC;AAGD,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AACnE,YAAY,EAAE,eAAe,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/local-api/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// local-api/index.ts
|
|
2
2
|
//
|
|
3
3
|
// Main Local API singleton and factory function
|
|
4
|
+
import { DocumentCache } from '../cache/index.js';
|
|
5
|
+
import { HierarchyService } from '../services/hierarchy-service.js';
|
|
4
6
|
import { CollectionAPI } from './collection-api.js';
|
|
5
7
|
import { PermissionChecker } from './permissions.js';
|
|
6
8
|
/**
|
|
@@ -38,12 +40,16 @@ export class LocalAPI {
|
|
|
38
40
|
collections = {};
|
|
39
41
|
userAdapter;
|
|
40
42
|
systemAdapter;
|
|
43
|
+
documentCache;
|
|
44
|
+
hierarchyService;
|
|
41
45
|
permissions;
|
|
42
46
|
schemas;
|
|
43
47
|
constructor(config, userAdapter, systemAdapter) {
|
|
44
48
|
this.config = config;
|
|
45
49
|
this.userAdapter = userAdapter;
|
|
46
50
|
this.systemAdapter = systemAdapter || null;
|
|
51
|
+
this.documentCache = config.cache ? new DocumentCache(config.cache) : null;
|
|
52
|
+
this.hierarchyService = new HierarchyService(userAdapter, config.cache);
|
|
47
53
|
// Build schema map for quick lookups
|
|
48
54
|
this.schemas = new Map(config.schemaTypes
|
|
49
55
|
.filter((schema) => schema.type === 'document')
|
|
@@ -60,7 +66,7 @@ export class LocalAPI {
|
|
|
60
66
|
const documentSchemas = this.config.schemaTypes.filter((s) => s.type === 'document');
|
|
61
67
|
for (const schema of documentSchemas) {
|
|
62
68
|
// Create a proxy that selects the correct adapter based on context
|
|
63
|
-
const collectionAPI = new Proxy(new CollectionAPI(schema.name, this.userAdapter, schema, this.permissions), {
|
|
69
|
+
const collectionAPI = new Proxy(new CollectionAPI(schema.name, this.userAdapter, schema, this.permissions, this.documentCache, this.hierarchyService), {
|
|
64
70
|
get: (target, prop) => {
|
|
65
71
|
const method = target[prop];
|
|
66
72
|
if (typeof method === 'function') {
|
|
@@ -69,7 +75,7 @@ export class LocalAPI {
|
|
|
69
75
|
const context = args[0];
|
|
70
76
|
const adapter = this.getAdapter(context);
|
|
71
77
|
// Create new CollectionAPI with the correct adapter
|
|
72
|
-
const api = new CollectionAPI(schema.name, adapter, schema, this.permissions);
|
|
78
|
+
const api = new CollectionAPI(schema.name, adapter, schema, this.permissions, this.documentCache, this.hierarchyService);
|
|
73
79
|
// Call the method on the new instance
|
|
74
80
|
return api[prop].apply(api, args);
|
|
75
81
|
};
|
|
@@ -108,6 +114,24 @@ export class LocalAPI {
|
|
|
108
114
|
getCollectionSchema(name) {
|
|
109
115
|
return this.schemas.get(name);
|
|
110
116
|
}
|
|
117
|
+
/**
|
|
118
|
+
* Find a document by ID without knowing its collection type.
|
|
119
|
+
* Resolves org hierarchy and passes filterOrganizationIds to avoid RLS transactions.
|
|
120
|
+
* Returns the raw document with its type, or null if not found.
|
|
121
|
+
*/
|
|
122
|
+
async findDocumentById(context, id, options) {
|
|
123
|
+
const adapter = this.getAdapter(context);
|
|
124
|
+
const findOptions = { ...options };
|
|
125
|
+
// Resolve org IDs via hierarchy service to avoid withOrgContext transaction
|
|
126
|
+
if (this.hierarchyService) {
|
|
127
|
+
const orgIds = await this.hierarchyService.getOrgIdsWithChildren(context.organizationId);
|
|
128
|
+
findOptions.filterOrganizationIds = orgIds;
|
|
129
|
+
}
|
|
130
|
+
const rawDoc = await adapter.findByDocIdAdvanced(context.organizationId, id, findOptions);
|
|
131
|
+
if (!rawDoc)
|
|
132
|
+
return null;
|
|
133
|
+
return { type: rawDoc.type, document: rawDoc };
|
|
134
|
+
}
|
|
111
135
|
}
|
|
112
136
|
// Global Local API instance
|
|
113
137
|
let localAPIInstance = null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/lib/routes/assets.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/lib/routes/assets.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAIpD,eAAO,MAAM,IAAI,EAAE,cAuFlB,CAAC;AAEF,eAAO,MAAM,GAAG,EAAE,cAiEjB,CAAC"}
|
package/dist/routes/assets.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Aphex CMS Asset API Handlers
|
|
2
2
|
import { json } from '@sveltejs/kit';
|
|
3
3
|
import { cmsLogger } from '../utils/logger.js';
|
|
4
|
+
import { validateFile } from '../utils/mime-detect.js';
|
|
4
5
|
export const POST = async ({ request, locals }) => {
|
|
5
6
|
try {
|
|
6
7
|
const { assetService } = locals.aphexCMS;
|
|
@@ -16,6 +17,19 @@ export const POST = async ({ request, locals }) => {
|
|
|
16
17
|
// Convert file to buffer
|
|
17
18
|
const arrayBuffer = await file.arrayBuffer();
|
|
18
19
|
const buffer = Buffer.from(arrayBuffer);
|
|
20
|
+
// Get allowed MIME types and max size from form data (set by FileField component)
|
|
21
|
+
const allowedMimeTypesRaw = formData.get('allowedMimeTypes');
|
|
22
|
+
const maxSizeRaw = formData.get('maxSize');
|
|
23
|
+
const allowedMimeTypes = allowedMimeTypesRaw ? JSON.parse(allowedMimeTypesRaw) : undefined;
|
|
24
|
+
const maxSize = maxSizeRaw ? parseInt(maxSizeRaw, 10) : undefined;
|
|
25
|
+
// Validate file content (magic bytes, blocked types, allowed types)
|
|
26
|
+
const validation = validateFile(buffer, file.name, file.type, {
|
|
27
|
+
allowedMimeTypes,
|
|
28
|
+
maxSize
|
|
29
|
+
});
|
|
30
|
+
if (!validation.valid) {
|
|
31
|
+
return json({ success: false, error: validation.error }, { status: 400 });
|
|
32
|
+
}
|
|
19
33
|
// Get optional metadata from form data
|
|
20
34
|
const title = formData.get('title') || undefined;
|
|
21
35
|
const description = formData.get('description') || undefined;
|
|
@@ -7,7 +7,7 @@ import { cmsLogger } from '../utils/logger.js';
|
|
|
7
7
|
// TODO ENABLE CHILDREN ORG ACCESS BY DEFAULT - BECAUSE IF A PARENT ORG IS TRYING TO ACCESS A CHILD ORG. It should already have access to said id.
|
|
8
8
|
export const GET = async ({ params, url, locals }) => {
|
|
9
9
|
try {
|
|
10
|
-
const { localAPI
|
|
10
|
+
const { localAPI } = locals.aphexCMS;
|
|
11
11
|
const context = authToContext(locals.auth);
|
|
12
12
|
const { id } = params;
|
|
13
13
|
if (!id) {
|
|
@@ -17,18 +17,18 @@ export const GET = async ({ params, url, locals }) => {
|
|
|
17
17
|
const depthParam = url.searchParams.get('depth');
|
|
18
18
|
const depth = depthParam ? Math.max(0, Math.min(parseInt(depthParam), 5)) : 0;
|
|
19
19
|
const perspective = url.searchParams.get('perspective') || 'draft';
|
|
20
|
-
// First, fetch document to get its type (
|
|
21
|
-
const
|
|
22
|
-
if (!
|
|
20
|
+
// First, fetch document to get its type (uses hierarchy-aware lookup, no RLS transaction)
|
|
21
|
+
const result = await localAPI.findDocumentById(context, id);
|
|
22
|
+
if (!result) {
|
|
23
23
|
return json({ success: false, error: 'Document not found' }, { status: 404 });
|
|
24
24
|
}
|
|
25
25
|
// Get collection API (TypeScript-safe)
|
|
26
|
-
const collection = localAPI.collections[
|
|
26
|
+
const collection = localAPI.collections[result.type];
|
|
27
27
|
if (!collection) {
|
|
28
28
|
return json({
|
|
29
29
|
success: false,
|
|
30
30
|
error: 'Invalid document type',
|
|
31
|
-
message: `Collection '${
|
|
31
|
+
message: `Collection '${result.type}' not found`
|
|
32
32
|
}, { status: 400 });
|
|
33
33
|
}
|
|
34
34
|
// Now use LocalAPI for permission-checked retrieval
|
|
@@ -64,7 +64,7 @@ export const GET = async ({ params, url, locals }) => {
|
|
|
64
64
|
// TODO ENABLE CHILDREN ORG ACCESS BY DEFAULT - BECAUSE IF A PARENT ORG IS TRYING TO ACCESS A CHILD ORG. It should already have access to said id.
|
|
65
65
|
export const PUT = async ({ params, request, locals }) => {
|
|
66
66
|
try {
|
|
67
|
-
const { localAPI
|
|
67
|
+
const { localAPI } = locals.aphexCMS;
|
|
68
68
|
const context = authToContext(locals.auth);
|
|
69
69
|
const { id } = params;
|
|
70
70
|
const body = await request.json();
|
|
@@ -73,18 +73,18 @@ export const PUT = async ({ params, request, locals }) => {
|
|
|
73
73
|
}
|
|
74
74
|
const documentData = body.draftData || body.data;
|
|
75
75
|
const shouldPublish = body.publish || false;
|
|
76
|
-
// Fetch document to get its type
|
|
77
|
-
const
|
|
78
|
-
if (!
|
|
76
|
+
// Fetch document to get its type (hierarchy-aware, no RLS transaction)
|
|
77
|
+
const found = await localAPI.findDocumentById(context, id);
|
|
78
|
+
if (!found) {
|
|
79
79
|
return json({ success: false, error: 'Document not found' }, { status: 404 });
|
|
80
80
|
}
|
|
81
81
|
// Get collection API (TypeScript-safe)
|
|
82
|
-
const collection = localAPI.collections[
|
|
82
|
+
const collection = localAPI.collections[found.type];
|
|
83
83
|
if (!collection) {
|
|
84
84
|
return json({
|
|
85
85
|
success: false,
|
|
86
86
|
error: 'Invalid document type',
|
|
87
|
-
message: `Collection '${
|
|
87
|
+
message: `Collection '${found.type}' not found`
|
|
88
88
|
}, { status: 400 });
|
|
89
89
|
}
|
|
90
90
|
// Update via LocalAPI (permission checks and validation happen inside)
|
|
@@ -127,24 +127,24 @@ export const PUT = async ({ params, request, locals }) => {
|
|
|
127
127
|
// DELETE /api/documents/[id] - Delete document
|
|
128
128
|
export const DELETE = async ({ params, locals }) => {
|
|
129
129
|
try {
|
|
130
|
-
const { localAPI
|
|
130
|
+
const { localAPI } = locals.aphexCMS;
|
|
131
131
|
const context = authToContext(locals.auth);
|
|
132
132
|
const { id } = params;
|
|
133
133
|
if (!id) {
|
|
134
134
|
return json({ success: false, error: 'Document ID is required' }, { status: 400 });
|
|
135
135
|
}
|
|
136
|
-
// Fetch document to get its type
|
|
137
|
-
const
|
|
138
|
-
if (!
|
|
136
|
+
// Fetch document to get its type (hierarchy-aware, no RLS transaction)
|
|
137
|
+
const result = await localAPI.findDocumentById(context, id);
|
|
138
|
+
if (!result) {
|
|
139
139
|
return json({ success: false, error: 'Document not found' }, { status: 404 });
|
|
140
140
|
}
|
|
141
141
|
// Get collection API (TypeScript-safe)
|
|
142
|
-
const collection = localAPI.collections[
|
|
142
|
+
const collection = localAPI.collections[result.type];
|
|
143
143
|
if (!collection) {
|
|
144
144
|
return json({
|
|
145
145
|
success: false,
|
|
146
146
|
error: 'Invalid document type',
|
|
147
|
-
message: `Collection '${
|
|
147
|
+
message: `Collection '${result.type}' not found`
|
|
148
148
|
}, { status: 400 });
|
|
149
149
|
}
|
|
150
150
|
// Delete via LocalAPI (permission checks happen inside)
|
|
@@ -6,7 +6,7 @@ import { cmsLogger } from '../utils/logger.js';
|
|
|
6
6
|
// POST /api/documents/[id]/publish - Publish document
|
|
7
7
|
export const POST = async ({ params, locals }) => {
|
|
8
8
|
try {
|
|
9
|
-
const { localAPI
|
|
9
|
+
const { localAPI } = locals.aphexCMS;
|
|
10
10
|
const context = authToContext(locals.auth);
|
|
11
11
|
const { id } = params;
|
|
12
12
|
if (!id) {
|
|
@@ -16,9 +16,9 @@ export const POST = async ({ params, locals }) => {
|
|
|
16
16
|
message: 'Document ID is required'
|
|
17
17
|
}, { status: 400 });
|
|
18
18
|
}
|
|
19
|
-
// Fetch document to get its type
|
|
20
|
-
const
|
|
21
|
-
if (!
|
|
19
|
+
// Fetch document to get its type (hierarchy-aware, no RLS transaction)
|
|
20
|
+
const found = await localAPI.findDocumentById(context, id);
|
|
21
|
+
if (!found) {
|
|
22
22
|
return json({
|
|
23
23
|
success: false,
|
|
24
24
|
error: 'Document not found',
|
|
@@ -26,12 +26,12 @@ export const POST = async ({ params, locals }) => {
|
|
|
26
26
|
}, { status: 404 });
|
|
27
27
|
}
|
|
28
28
|
// Get collection API (TypeScript-safe)
|
|
29
|
-
const collection = localAPI.collections[
|
|
29
|
+
const collection = localAPI.collections[found.type];
|
|
30
30
|
if (!collection) {
|
|
31
31
|
return json({
|
|
32
32
|
success: false,
|
|
33
33
|
error: 'Invalid document type',
|
|
34
|
-
message: `Collection '${
|
|
34
|
+
message: `Collection '${found.type}' not found`
|
|
35
35
|
}, { status: 400 });
|
|
36
36
|
}
|
|
37
37
|
// Publish via LocalAPI (permission checks and validation happen inside)
|
|
@@ -76,7 +76,7 @@ export const POST = async ({ params, locals }) => {
|
|
|
76
76
|
// DELETE /api/documents/[id]/publish - Unpublish document
|
|
77
77
|
export const DELETE = async ({ params, locals }) => {
|
|
78
78
|
try {
|
|
79
|
-
const { localAPI
|
|
79
|
+
const { localAPI } = locals.aphexCMS;
|
|
80
80
|
const context = authToContext(locals.auth);
|
|
81
81
|
const { id } = params;
|
|
82
82
|
if (!id) {
|
|
@@ -86,9 +86,9 @@ export const DELETE = async ({ params, locals }) => {
|
|
|
86
86
|
message: 'Document ID is required'
|
|
87
87
|
}, { status: 400 });
|
|
88
88
|
}
|
|
89
|
-
// Fetch document to get its type
|
|
90
|
-
const
|
|
91
|
-
if (!
|
|
89
|
+
// Fetch document to get its type (hierarchy-aware, no RLS transaction)
|
|
90
|
+
const found = await localAPI.findDocumentById(context, id);
|
|
91
|
+
if (!found) {
|
|
92
92
|
return json({
|
|
93
93
|
success: false,
|
|
94
94
|
error: 'Document not found',
|
|
@@ -96,12 +96,12 @@ export const DELETE = async ({ params, locals }) => {
|
|
|
96
96
|
}, { status: 404 });
|
|
97
97
|
}
|
|
98
98
|
// Get collection API (TypeScript-safe)
|
|
99
|
-
const collection = localAPI.collections[
|
|
99
|
+
const collection = localAPI.collections[found.type];
|
|
100
100
|
if (!collection) {
|
|
101
101
|
return json({
|
|
102
102
|
success: false,
|
|
103
103
|
error: 'Invalid document type',
|
|
104
|
-
message: `Collection '${
|
|
104
|
+
message: `Collection '${found.type}' not found`
|
|
105
105
|
}, { status: 400 });
|
|
106
106
|
}
|
|
107
107
|
// Unpublish via LocalAPI (permission checks happen inside)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/lib/schema-utils/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAS,MAAM,kBAAkB,CAAC;AAmB1D;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../../src/lib/schema-utils/validator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAS,MAAM,kBAAkB,CAAC;AAmB1D;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE9D;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,CAqMpE"}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * from '../types/index.js';
|
|
2
2
|
export * from '../auth/provider.js';
|
|
3
|
+
export * from '../cache/index.js';
|
|
3
4
|
export * from '../email/index.js';
|
|
4
5
|
export { AuthError, type AuthErrorCode } from '../auth/auth-errors.js';
|
|
5
6
|
export { createCMSConfig } from '../config.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/server/index.ts"],"names":[],"mappings":"AAIA,cAAc,gBAAgB,CAAC;AAE/B,cAAc,kBAAkB,CAAC;AAEjC,cAAc,gBAAgB,CAAC;AAG/B,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAG5D,cAAc,wBAAwB,CAAC;AAGvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAG7C,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAIzD,cAAc,mBAAmB,CAAC;AAGlC,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,EACN,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACN,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/server/index.ts"],"names":[],"mappings":"AAIA,cAAc,gBAAgB,CAAC;AAE/B,cAAc,kBAAkB,CAAC;AAEjC,cAAc,gBAAgB,CAAC;AAE/B,cAAc,gBAAgB,CAAC;AAG/B,OAAO,EAAE,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAGpE,OAAO,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAG5C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAGtC,OAAO,EAAE,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAG5D,cAAc,wBAAwB,CAAC;AAGvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,6BAA6B,CAAC;AAC5C,cAAc,8BAA8B,CAAC;AAG7C,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAIzD,cAAc,mBAAmB,CAAC;AAGlC,cAAc,uBAAuB,CAAC;AAGtC,OAAO,EAAE,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAGhE,OAAO,EACN,oBAAoB,EACpB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,oBAAoB,EACzB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACN,QAAQ,EACR,cAAc,EACd,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,oBAAoB,CAAC"}
|
package/dist/server/index.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// Export all core types from the new central location
|
|
4
4
|
export * from '../types/index.js';
|
|
5
5
|
export * from '../auth/provider.js';
|
|
6
|
+
export * from '../cache/index.js';
|
|
6
7
|
export * from '../email/index.js';
|
|
7
8
|
// Authentication errors
|
|
8
9
|
export { AuthError } from '../auth/auth-errors.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { CacheAdapter } from '../cache/index.js';
|
|
2
|
+
import type { DatabaseAdapter } from '../db/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* HierarchyService — caches organization parent→child lookups
|
|
5
|
+
* using the shared CacheAdapter.
|
|
6
|
+
*
|
|
7
|
+
* Lives in cms-core so every adapter (PostgreSQL, SQLite, MongoDB)
|
|
8
|
+
* benefits from the same caching without reimplementing it.
|
|
9
|
+
*/
|
|
10
|
+
export declare class HierarchyService {
|
|
11
|
+
private db;
|
|
12
|
+
private cache;
|
|
13
|
+
private ttl;
|
|
14
|
+
private static DEFAULT_TTL;
|
|
15
|
+
private inflight;
|
|
16
|
+
constructor(db: DatabaseAdapter, cache?: CacheAdapter | null, ttl?: number);
|
|
17
|
+
getChildOrganizations(parentOrganizationId: string): Promise<string[]>;
|
|
18
|
+
/**
|
|
19
|
+
* Get the parent org ID plus all its child org IDs.
|
|
20
|
+
* Convenience for building filterOrganizationIds arrays.
|
|
21
|
+
*/
|
|
22
|
+
getOrgIdsWithChildren(organizationId: string): Promise<string[]>;
|
|
23
|
+
invalidate(parentOrganizationId: string): Promise<void>;
|
|
24
|
+
flush(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=hierarchy-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hierarchy-service.d.ts","sourceRoot":"","sources":["../../src/lib/services/hierarchy-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnD;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAK3B,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,KAAK;IACb,OAAO,CAAC,GAAG;IANZ,OAAO,CAAC,MAAM,CAAC,WAAW,CAAM;IAChC,OAAO,CAAC,QAAQ,CAAwC;gBAG/C,EAAE,EAAE,eAAe,EACnB,KAAK,GAAE,YAAY,GAAG,IAAW,EACjC,GAAG,GAAE,MAAqC;IAG7C,qBAAqB,CAAC,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IA8B5E;;;OAGG;IACG,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAKhE,UAAU,CAAC,oBAAoB,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMvD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAK5B"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HierarchyService — caches organization parent→child lookups
|
|
3
|
+
* using the shared CacheAdapter.
|
|
4
|
+
*
|
|
5
|
+
* Lives in cms-core so every adapter (PostgreSQL, SQLite, MongoDB)
|
|
6
|
+
* benefits from the same caching without reimplementing it.
|
|
7
|
+
*/
|
|
8
|
+
export class HierarchyService {
|
|
9
|
+
db;
|
|
10
|
+
cache;
|
|
11
|
+
ttl;
|
|
12
|
+
static DEFAULT_TTL = 60; // 60 seconds
|
|
13
|
+
inflight = new Map();
|
|
14
|
+
constructor(db, cache = null, ttl = HierarchyService.DEFAULT_TTL) {
|
|
15
|
+
this.db = db;
|
|
16
|
+
this.cache = cache;
|
|
17
|
+
this.ttl = ttl;
|
|
18
|
+
}
|
|
19
|
+
async getChildOrganizations(parentOrganizationId) {
|
|
20
|
+
if (!this.db.hierarchyEnabled) {
|
|
21
|
+
return [];
|
|
22
|
+
}
|
|
23
|
+
const key = `hierarchy:${parentOrganizationId}`;
|
|
24
|
+
// Check cache first
|
|
25
|
+
if (this.cache) {
|
|
26
|
+
const cached = await this.cache.get(key);
|
|
27
|
+
if (cached)
|
|
28
|
+
return cached;
|
|
29
|
+
}
|
|
30
|
+
// Deduplicate concurrent requests — if a fetch is already in flight
|
|
31
|
+
// for this org, wait for that instead of hitting the DB again
|
|
32
|
+
const existing = this.inflight.get(key);
|
|
33
|
+
if (existing)
|
|
34
|
+
return existing;
|
|
35
|
+
const promise = this.db.getChildOrganizations(parentOrganizationId).then(async (ids) => {
|
|
36
|
+
if (this.cache) {
|
|
37
|
+
await this.cache.set(key, ids, this.ttl);
|
|
38
|
+
}
|
|
39
|
+
this.inflight.delete(key);
|
|
40
|
+
return ids;
|
|
41
|
+
});
|
|
42
|
+
this.inflight.set(key, promise);
|
|
43
|
+
return promise;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Get the parent org ID plus all its child org IDs.
|
|
47
|
+
* Convenience for building filterOrganizationIds arrays.
|
|
48
|
+
*/
|
|
49
|
+
async getOrgIdsWithChildren(organizationId) {
|
|
50
|
+
const childIds = await this.getChildOrganizations(organizationId);
|
|
51
|
+
return childIds.length > 0 ? [organizationId, ...childIds] : [organizationId];
|
|
52
|
+
}
|
|
53
|
+
async invalidate(parentOrganizationId) {
|
|
54
|
+
if (this.cache) {
|
|
55
|
+
await this.cache.delete(`hierarchy:${parentOrganizationId}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async flush() {
|
|
59
|
+
if (this.cache) {
|
|
60
|
+
await this.cache.invalidateByPrefix('hierarchy:');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/dist/services/index.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/services/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/services/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACrE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
|
package/dist/services/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-storage-adapter.d.ts","sourceRoot":"","sources":["../../../src/lib/storage/adapters/local-storage-adapter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,cAAc,EACd,aAAa,EACb,cAAc,EACd,WAAW,EACX,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"local-storage-adapter.d.ts","sourceRoot":"","sources":["../../../src/lib/storage/adapters/local-storage-adapter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,cAAc,EACd,aAAa,EACb,cAAc,EACd,WAAW,EACX,MAAM,uBAAuB,CAAC;AAK/B;;GAEG;AACH,qBAAa,mBAAoB,YAAW,cAAc;IACzD,QAAQ,CAAC,IAAI,WAAW;IACxB,OAAO,CAAC,MAAM,CAA0B;gBAE5B,MAAM,EAAE,aAAa;IASjC;;OAEG;YACW,sBAAsB;IAcpC;;OAEG;IACH,OAAO,CAAC,aAAa;IAWrB;;OAEG;YACW,gBAAgB;IAU9B;;OAEG;IACG,KAAK,CAAC,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,WAAW,CAAC;IAoCvD;;;OAGG;IACG,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAK9C;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU5C;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS5C;;OAEG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAM5B;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAyB/E;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;CAYnC"}
|
|
@@ -3,15 +3,6 @@ import { writeFile, mkdir, unlink, stat, readdir } from 'fs/promises';
|
|
|
3
3
|
import { join, dirname } from 'path';
|
|
4
4
|
import { cmsLogger } from '../../utils/logger.js';
|
|
5
5
|
const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
6
|
-
const DEFAULT_ALLOWED_TYPES = [
|
|
7
|
-
'image/jpeg',
|
|
8
|
-
'image/png',
|
|
9
|
-
'image/webp',
|
|
10
|
-
'image/gif',
|
|
11
|
-
'image/avif',
|
|
12
|
-
'application/pdf',
|
|
13
|
-
'text/plain'
|
|
14
|
-
];
|
|
15
6
|
/**
|
|
16
7
|
* Pure local file system storage adapter - only handles files
|
|
17
8
|
*/
|
|
@@ -23,7 +14,6 @@ export class LocalStorageAdapter {
|
|
|
23
14
|
basePath: config.basePath,
|
|
24
15
|
baseUrl: config.baseUrl || '',
|
|
25
16
|
maxFileSize: config.maxFileSize || DEFAULT_MAX_FILE_SIZE,
|
|
26
|
-
allowedTypes: config.allowedTypes || DEFAULT_ALLOWED_TYPES,
|
|
27
17
|
options: config.options || {}
|
|
28
18
|
};
|
|
29
19
|
}
|
|
@@ -71,10 +61,6 @@ export class LocalStorageAdapter {
|
|
|
71
61
|
* Store a file and return storage info
|
|
72
62
|
*/
|
|
73
63
|
async store(data) {
|
|
74
|
-
// Validate file type
|
|
75
|
-
if (!this.config.allowedTypes.includes(data.mimeType)) {
|
|
76
|
-
throw new Error(`Invalid file type: ${data.mimeType}. Allowed types: ${this.config.allowedTypes.join(', ')}`);
|
|
77
|
-
}
|
|
78
64
|
// Validate file size
|
|
79
65
|
if (data.size > this.config.maxFileSize) {
|
|
80
66
|
throw new Error(`File too large: ${data.size} bytes. Maximum size: ${this.config.maxFileSize} bytes`);
|