@aphexcms/cms-core 0.1.1 → 0.1.4
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/api/assets.d.ts +48 -0
- package/dist/api/assets.d.ts.map +1 -0
- package/dist/api/assets.js +52 -0
- package/dist/api/client.d.ts +37 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +125 -0
- package/dist/api/documents.d.ts +56 -0
- package/dist/api/documents.d.ts.map +1 -0
- package/dist/api/documents.js +77 -0
- package/{src/api/index.ts → dist/api/index.d.ts} +1 -1
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +5 -0
- package/dist/api/organizations.d.ts +101 -0
- package/dist/api/organizations.d.ts.map +1 -0
- package/dist/api/organizations.js +92 -0
- package/dist/api/types.d.ts +23 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +1 -0
- package/dist/auth/auth-errors.d.ts +7 -0
- package/dist/auth/auth-errors.d.ts.map +1 -0
- package/dist/auth/auth-errors.js +13 -0
- package/dist/auth/auth-hooks.d.ts +6 -0
- package/dist/auth/auth-hooks.d.ts.map +1 -0
- package/dist/auth/auth-hooks.js +108 -0
- package/dist/auth/provider.d.ts +17 -0
- package/dist/auth/provider.d.ts.map +1 -0
- package/dist/auth/provider.js +1 -0
- package/dist/client/index.d.ts +24 -0
- package/dist/client/index.d.ts.map +1 -0
- package/{src/client/index.ts → dist/client/index.js} +0 -16
- package/{src → dist}/components/AdminApp.svelte +7 -8
- package/dist/components/AdminApp.svelte.d.ts +24 -0
- package/dist/components/AdminApp.svelte.d.ts.map +1 -0
- package/dist/components/admin/AdminLayout.svelte.d.ts +15 -0
- package/dist/components/admin/AdminLayout.svelte.d.ts.map +1 -0
- package/dist/components/admin/DocumentEditor.svelte.d.ts +18 -0
- package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -0
- package/dist/components/admin/DocumentTypesList.svelte.d.ts +14 -0
- package/dist/components/admin/DocumentTypesList.svelte.d.ts.map +1 -0
- package/dist/components/admin/ObjectModal.svelte.d.ts +15 -0
- package/dist/components/admin/ObjectModal.svelte.d.ts.map +1 -0
- package/dist/components/admin/SchemaField.svelte.d.ts +19 -0
- package/dist/components/admin/SchemaField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/ArrayField.svelte.d.ts +12 -0
- package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/BooleanField.svelte.d.ts +13 -0
- package/dist/components/admin/fields/BooleanField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/ImageField.svelte.d.ts +15 -0
- package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/NumberField.svelte.d.ts +14 -0
- package/dist/components/admin/fields/NumberField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/ReferenceField.svelte.d.ts +12 -0
- package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/SlugField.svelte.d.ts +15 -0
- package/dist/components/admin/fields/SlugField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/StringField.svelte.d.ts +14 -0
- package/dist/components/admin/fields/StringField.svelte.d.ts.map +1 -0
- package/dist/components/admin/fields/TextareaField.svelte.d.ts +14 -0
- package/dist/components/admin/fields/TextareaField.svelte.d.ts.map +1 -0
- package/dist/components/fields/index.d.ts +9 -0
- package/dist/components/fields/index.d.ts.map +1 -0
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.d.ts.map +1 -0
- package/{src/components/index.ts → dist/components/index.js} +0 -4
- package/dist/components/layout/OrganizationSwitcher.svelte.d.ts +11 -0
- package/dist/components/layout/OrganizationSwitcher.svelte.d.ts.map +1 -0
- package/{src → dist}/components/layout/Sidebar.svelte +7 -7
- package/dist/components/layout/Sidebar.svelte.d.ts +14 -0
- package/dist/components/layout/Sidebar.svelte.d.ts.map +1 -0
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts +11 -0
- package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -0
- package/dist/components/layout/sidebar/NavMain.svelte.d.ts +19 -0
- package/dist/components/layout/sidebar/NavMain.svelte.d.ts.map +1 -0
- package/dist/components/layout/sidebar/NavSecondary.svelte.d.ts +9 -0
- package/dist/components/layout/sidebar/NavSecondary.svelte.d.ts.map +1 -0
- package/dist/components/layout/sidebar/NavUser.svelte.d.ts +9 -0
- package/dist/components/layout/sidebar/NavUser.svelte.d.ts.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +15 -0
- package/dist/db/adapters/index.d.ts +1 -0
- package/dist/db/adapters/index.d.ts.map +1 -0
- package/{src/db/adapters/index.ts → dist/db/adapters/index.js} +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -0
- package/{src/db/index.ts → dist/db/index.js} +0 -1
- package/dist/db/interfaces/asset.d.ts +51 -0
- package/dist/db/interfaces/asset.d.ts.map +1 -0
- package/dist/db/interfaces/asset.js +1 -0
- package/dist/db/interfaces/document.d.ts +36 -0
- package/dist/db/interfaces/document.d.ts.map +1 -0
- package/dist/db/interfaces/document.js +1 -0
- package/dist/db/interfaces/index.d.ts +73 -0
- package/dist/db/interfaces/index.d.ts.map +1 -0
- package/dist/db/interfaces/index.js +1 -0
- package/dist/db/interfaces/organization.d.ts +27 -0
- package/dist/db/interfaces/organization.d.ts.map +1 -0
- package/dist/db/interfaces/organization.js +1 -0
- package/dist/db/interfaces/schema.d.ts +21 -0
- package/dist/db/interfaces/schema.d.ts.map +1 -0
- package/dist/db/interfaces/schema.js +1 -0
- package/dist/db/interfaces/user.d.ts +15 -0
- package/dist/db/interfaces/user.d.ts.map +1 -0
- package/dist/db/interfaces/user.js +1 -0
- package/dist/db/utils/reference-resolver.d.ts +18 -0
- package/dist/db/utils/reference-resolver.d.ts.map +1 -0
- package/dist/db/utils/reference-resolver.js +80 -0
- package/dist/define.d.ts +3 -0
- package/dist/define.d.ts.map +1 -0
- package/dist/define.js +4 -0
- package/dist/email/index.d.ts +2 -0
- package/dist/email/index.d.ts.map +1 -0
- package/{src/email/index.ts → dist/email/index.js} +0 -1
- package/dist/email/interfaces/email.d.ts +42 -0
- package/dist/email/interfaces/email.d.ts.map +1 -0
- package/dist/email/interfaces/email.js +1 -0
- package/dist/engine.d.ts +26 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +66 -0
- package/dist/field-validation/rule.d.ts +51 -0
- package/dist/field-validation/rule.d.ts.map +1 -0
- package/dist/field-validation/rule.js +221 -0
- package/dist/field-validation/utils.d.ts +21 -0
- package/dist/field-validation/utils.d.ts.map +1 -0
- package/dist/field-validation/utils.js +66 -0
- package/dist/hooks.d.ts +23 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +96 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/{src/index.ts → dist/index.js} +0 -1
- package/dist/routes/assets-by-id.d.ts +5 -0
- package/dist/routes/assets-by-id.d.ts.map +1 -0
- package/dist/routes/assets-by-id.js +138 -0
- package/dist/routes/assets-cdn.d.ts +3 -0
- package/dist/routes/assets-cdn.d.ts.map +1 -0
- package/dist/routes/assets-cdn.js +155 -0
- package/dist/routes/assets.d.ts +4 -0
- package/dist/routes/assets.d.ts.map +1 -0
- package/dist/routes/assets.js +94 -0
- package/dist/routes/documents-by-id.d.ts +5 -0
- package/dist/routes/documents-by-id.d.ts.map +1 -0
- package/dist/routes/documents-by-id.js +142 -0
- package/dist/routes/documents-publish.d.ts +4 -0
- package/dist/routes/documents-publish.d.ts.map +1 -0
- package/dist/routes/documents-publish.js +151 -0
- package/dist/routes/documents.d.ts +4 -0
- package/dist/routes/documents.d.ts.map +1 -0
- package/dist/routes/documents.js +131 -0
- package/dist/routes/index.d.ts +6 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/{src/routes/index.ts → dist/routes/index.js} +0 -3
- package/dist/routes/organizations-by-id.d.ts +5 -0
- package/dist/routes/organizations-by-id.d.ts.map +1 -0
- package/dist/routes/organizations-by-id.js +187 -0
- package/dist/routes/organizations-invitations.d.ts +4 -0
- package/dist/routes/organizations-invitations.d.ts.map +1 -0
- package/dist/routes/organizations-invitations.js +125 -0
- package/dist/routes/organizations-members.d.ts +5 -0
- package/dist/routes/organizations-members.d.ts.map +1 -0
- package/dist/routes/organizations-members.js +206 -0
- package/dist/routes/organizations-switch.d.ts +3 -0
- package/dist/routes/organizations-switch.d.ts.map +1 -0
- package/dist/routes/organizations-switch.js +53 -0
- package/dist/routes/organizations.d.ts +4 -0
- package/dist/routes/organizations.d.ts.map +1 -0
- package/dist/routes/organizations.js +108 -0
- package/dist/routes/schemas-by-type.d.ts +3 -0
- package/dist/routes/schemas-by-type.d.ts.map +1 -0
- package/dist/routes/schemas-by-type.js +25 -0
- package/dist/routes/schemas.d.ts +3 -0
- package/dist/routes/schemas.d.ts.map +1 -0
- package/dist/routes/schemas.js +11 -0
- package/dist/routes-exports.d.ts +14 -0
- package/dist/routes-exports.d.ts.map +1 -0
- package/{src/routes-exports.ts → dist/routes-exports.js} +5 -28
- package/dist/schema-context.svelte.d.ts +10 -0
- package/dist/schema-context.svelte.d.ts.map +1 -0
- package/dist/schema-context.svelte.js +18 -0
- package/dist/schema-utils/cleanup.d.ts +21 -0
- package/dist/schema-utils/cleanup.d.ts.map +1 -0
- package/dist/schema-utils/cleanup.js +80 -0
- package/dist/schema-utils/index.d.ts +4 -0
- package/dist/schema-utils/index.d.ts.map +1 -0
- package/dist/schema-utils/utils.d.ts +30 -0
- package/dist/schema-utils/utils.d.ts.map +1 -0
- package/dist/schema-utils/utils.js +37 -0
- package/dist/schema-utils/validator.d.ts +6 -0
- package/dist/schema-utils/validator.d.ts.map +1 -0
- package/dist/schema-utils/validator.js +45 -0
- package/dist/server/index.d.ts +16 -0
- package/dist/server/index.d.ts.map +1 -0
- package/{src/server/index.ts → dist/server/index.js} +2 -14
- package/dist/services/asset-service.d.ts +86 -0
- package/dist/services/asset-service.d.ts.map +1 -0
- package/dist/services/asset-service.js +187 -0
- package/{src/services/index.ts → dist/services/index.d.ts} +1 -4
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +4 -0
- package/dist/storage/adapters/index.d.ts +2 -0
- package/dist/storage/adapters/index.d.ts.map +1 -0
- package/dist/storage/adapters/local-storage-adapter.d.ts +54 -0
- package/dist/storage/adapters/local-storage-adapter.d.ts.map +1 -0
- package/dist/storage/adapters/local-storage-adapter.js +187 -0
- package/dist/storage/index.d.ts +3 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/{src/storage/index.ts → dist/storage/index.js} +0 -2
- package/dist/storage/interfaces/index.d.ts +2 -0
- package/dist/storage/interfaces/index.d.ts.map +1 -0
- package/dist/storage/interfaces/storage.d.ts +91 -0
- package/dist/storage/interfaces/storage.d.ts.map +1 -0
- package/dist/storage/interfaces/storage.js +1 -0
- package/dist/storage/providers/storage.d.ts +43 -0
- package/dist/storage/providers/storage.d.ts.map +1 -0
- package/dist/storage/providers/storage.js +64 -0
- package/dist/types/asset.d.ts +73 -0
- package/dist/types/asset.d.ts.map +1 -0
- package/dist/types/asset.js +2 -0
- package/dist/types/auth.d.ts +50 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +41 -0
- package/dist/types/config.d.ts +47 -0
- package/dist/types/config.d.ts.map +1 -0
- package/dist/types/config.js +1 -0
- package/dist/types/document.d.ts +34 -0
- package/dist/types/document.d.ts.map +1 -0
- package/dist/types/document.js +1 -0
- package/dist/types/index.d.ts +9 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/organization.d.ts +105 -0
- package/dist/types/organization.d.ts.map +1 -0
- package/dist/types/organization.js +3 -0
- package/dist/types/schemas.d.ts +114 -0
- package/dist/types/schemas.d.ts.map +1 -0
- package/dist/types/schemas.js +1 -0
- package/dist/types/sidebar.d.ts +33 -0
- package/dist/types/sidebar.d.ts.map +1 -0
- package/dist/types/sidebar.js +1 -0
- package/dist/types/user.d.ts +14 -0
- package/dist/types/user.d.ts.map +1 -0
- package/dist/types/user.js +1 -0
- package/dist/utils/content-hash.d.ts +22 -0
- package/dist/utils/content-hash.d.ts.map +1 -0
- package/dist/utils/content-hash.js +67 -0
- package/dist/utils/image-url.d.ts +88 -0
- package/dist/utils/image-url.d.ts.map +1 -0
- package/dist/utils/image-url.js +165 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/{src/utils/index.ts → dist/utils/index.js} +0 -3
- package/dist/utils/slug.d.ts +13 -0
- package/dist/utils/slug.d.ts.map +1 -0
- package/dist/utils/slug.js +30 -0
- package/package.json +12 -25
- package/src/api/assets.ts +0 -75
- package/src/api/client.ts +0 -150
- package/src/api/documents.ts +0 -102
- package/src/api/organizations.ts +0 -154
- package/src/api/types.ts +0 -34
- package/src/auth/auth-errors.ts +0 -23
- package/src/auth/auth-hooks.ts +0 -132
- package/src/auth/provider.ts +0 -25
- package/src/config.ts +0 -18
- package/src/db/interfaces/asset.ts +0 -61
- package/src/db/interfaces/document.ts +0 -53
- package/src/db/interfaces/index.ts +0 -98
- package/src/db/interfaces/organization.ts +0 -51
- package/src/db/interfaces/schema.ts +0 -13
- package/src/db/interfaces/user.ts +0 -16
- package/src/db/utils/reference-resolver.ts +0 -119
- package/src/define.ts +0 -7
- package/src/email/interfaces/email.ts +0 -45
- package/src/engine.ts +0 -85
- package/src/field-validation/rule.ts +0 -287
- package/src/field-validation/utils.ts +0 -91
- package/src/hooks.ts +0 -142
- package/src/lib/is-mobile.svelte.ts +0 -9
- package/src/lib/utils.ts +0 -13
- package/src/routes/assets-by-id.ts +0 -161
- package/src/routes/assets-cdn.ts +0 -185
- package/src/routes/assets.ts +0 -116
- package/src/routes/documents-by-id.ts +0 -188
- package/src/routes/documents-publish.ts +0 -211
- package/src/routes/documents.ts +0 -172
- package/src/routes/organizations-by-id.ts +0 -258
- package/src/routes/organizations-invitations.ts +0 -183
- package/src/routes/organizations-members.ts +0 -301
- package/src/routes/organizations-switch.ts +0 -74
- package/src/routes/organizations.ts +0 -146
- package/src/routes/schemas-by-type.ts +0 -35
- package/src/routes/schemas.ts +0 -19
- package/src/schema-context.svelte.ts +0 -24
- package/src/schema-utils/cleanup.ts +0 -116
- package/src/schema-utils/utils.ts +0 -47
- package/src/schema-utils/validator.ts +0 -58
- package/src/services/asset-service.ts +0 -256
- package/src/storage/adapters/local-storage-adapter.ts +0 -215
- package/src/storage/interfaces/storage.ts +0 -114
- package/src/storage/providers/storage.ts +0 -83
- package/src/types/asset.ts +0 -81
- package/src/types/auth.ts +0 -80
- package/src/types/config.ts +0 -45
- package/src/types/document.ts +0 -38
- package/src/types/organization.ts +0 -119
- package/src/types/schemas.ts +0 -151
- package/src/types/sidebar.ts +0 -37
- package/src/types/user.ts +0 -17
- package/src/utils/content-hash.ts +0 -75
- package/src/utils/image-url.ts +0 -204
- package/src/utils/slug.ts +0 -33
- /package/{src → dist}/app.d.ts +0 -0
- /package/{src → dist}/auth/MULTI_TENANCY_PLAN.md +0 -0
- /package/{src → dist}/components/admin/AdminLayout.svelte +0 -0
- /package/{src → dist}/components/admin/DocumentEditor.svelte +0 -0
- /package/{src → dist}/components/admin/DocumentTypesList.svelte +0 -0
- /package/{src → dist}/components/admin/ObjectModal.svelte +0 -0
- /package/{src → dist}/components/admin/SchemaField.svelte +0 -0
- /package/{src → dist}/components/admin/fields/ArrayField.svelte +0 -0
- /package/{src → dist}/components/admin/fields/BooleanField.svelte +0 -0
- /package/{src → dist}/components/admin/fields/ImageField.svelte +0 -0
- /package/{src → dist}/components/admin/fields/NumberField.svelte +0 -0
- /package/{src → dist}/components/admin/fields/ReferenceField.svelte +0 -0
- /package/{src → dist}/components/admin/fields/SlugField.svelte +0 -0
- /package/{src → dist}/components/admin/fields/StringField.svelte +0 -0
- /package/{src → dist}/components/admin/fields/TextareaField.svelte +0 -0
- /package/{src/components/fields/index.ts → dist/components/fields/index.js} +0 -0
- /package/{src → dist}/components/layout/OrganizationSwitcher.svelte +0 -0
- /package/{src → dist}/components/layout/sidebar/AppSidebar.svelte +0 -0
- /package/{src → dist}/components/layout/sidebar/NavMain.svelte +0 -0
- /package/{src → dist}/components/layout/sidebar/NavSecondary.svelte +0 -0
- /package/{src → dist}/components/layout/sidebar/NavUser.svelte +0 -0
- /package/{src → dist}/plugins/README.md +0 -0
- /package/{src/schema-utils/index.ts → dist/schema-utils/index.js} +0 -0
- /package/{src/storage/adapters/index.ts → dist/storage/adapters/index.js} +0 -0
- /package/{src/storage/interfaces/index.ts → dist/storage/interfaces/index.js} +0 -0
- /package/{src/types/index.ts → dist/types/index.js} +0 -0
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import type { SchemaType } from '../types/schemas.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Schema utility functions that work with a schema registry
|
|
5
|
-
* These functions accept schemas as parameters to avoid package-level dependencies
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Get a schema type by name from a collection of schemas
|
|
10
|
-
*/
|
|
11
|
-
export function getSchemaByName(schemas: SchemaType[], name: string): SchemaType | null {
|
|
12
|
-
return schemas.find((schema) => schema.name === name) || null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Get all available object types (for array field dropdowns)
|
|
17
|
-
*/
|
|
18
|
-
export function getObjectTypes(schemas: SchemaType[]): SchemaType[] {
|
|
19
|
-
return schemas.filter((schema) => schema.type === 'object');
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Get all available document types
|
|
24
|
-
*/
|
|
25
|
-
export function getDocumentTypes(schemas: SchemaType[]): SchemaType[] {
|
|
26
|
-
return schemas.filter((schema) => schema.type === 'document');
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Check if a schema type exists
|
|
31
|
-
*/
|
|
32
|
-
export function schemaExists(schemas: SchemaType[], name: string): boolean {
|
|
33
|
-
return schemas.some((schema) => schema.name === name);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Get the available types for an array field
|
|
38
|
-
*/
|
|
39
|
-
export function getArrayTypes(
|
|
40
|
-
schemas: SchemaType[],
|
|
41
|
-
arrayField: { of?: Array<{ type: string }> }
|
|
42
|
-
): SchemaType[] {
|
|
43
|
-
if (!arrayField.of) return [];
|
|
44
|
-
|
|
45
|
-
const typeNames = arrayField.of.map((item) => item.type);
|
|
46
|
-
return schemas.filter((schema) => typeNames.includes(schema.name));
|
|
47
|
-
}
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import type { SchemaType, Field } from '../types/schemas.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Validate all schema references to ensure they exist
|
|
5
|
-
*/
|
|
6
|
-
export function validateSchemaReferences(schemas: SchemaType[]): void {
|
|
7
|
-
const schemaNames = new Set(schemas.map((schema) => schema.name));
|
|
8
|
-
const errors: string[] = [];
|
|
9
|
-
|
|
10
|
-
function validateField(field: Field, parentSchema: string): void {
|
|
11
|
-
// Check array field references
|
|
12
|
-
if (field.type === 'array' && field.of) {
|
|
13
|
-
for (const arrayType of field.of) {
|
|
14
|
-
if (!schemaNames.has(arrayType.type)) {
|
|
15
|
-
errors.push(
|
|
16
|
-
`Schema "${parentSchema}" field "${field.name}" references unknown type "${arrayType.type}"`
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
// Check object field references (if they reference external types)
|
|
23
|
-
if (field.type === 'object' && typeof field.fields === 'string') {
|
|
24
|
-
if (!schemaNames.has(field.fields)) {
|
|
25
|
-
errors.push(
|
|
26
|
-
`Schema "${parentSchema}" field "${field.name}" references unknown object type "${field.fields}"`
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
// Recursively check nested fields -- cast type later or something. cba
|
|
32
|
-
if ('fields' in field && Array.isArray(field.fields)) {
|
|
33
|
-
for (const nestedField of field.fields) {
|
|
34
|
-
validateField(nestedField, parentSchema);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Validate each schema
|
|
40
|
-
for (const schema of schemas) {
|
|
41
|
-
if (schema.fields) {
|
|
42
|
-
for (const field of schema.fields) {
|
|
43
|
-
validateField(field, schema.name);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Throw error if any validation issues found
|
|
49
|
-
if (errors.length > 0) {
|
|
50
|
-
console.error('\nSchema Validation Errors:');
|
|
51
|
-
errors.forEach((error) => console.error(error));
|
|
52
|
-
|
|
53
|
-
// Just throw the errors directly
|
|
54
|
-
throw new Error(errors.join('\n'));
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
console.log('✅ Schema validation passed - all references are valid');
|
|
58
|
-
}
|
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
// Asset service - orchestrates storage and database operations
|
|
2
|
-
import sharp from 'sharp';
|
|
3
|
-
import type { StorageAdapter } from '../storage/interfaces/storage.js';
|
|
4
|
-
import type { DatabaseAdapter } from '../db/interfaces/index.js';
|
|
5
|
-
import type { Asset } from '../types/index.js';
|
|
6
|
-
|
|
7
|
-
export interface AssetUploadData {
|
|
8
|
-
buffer: Buffer;
|
|
9
|
-
originalFilename: string;
|
|
10
|
-
mimeType: string;
|
|
11
|
-
size: number;
|
|
12
|
-
title?: string;
|
|
13
|
-
description?: string;
|
|
14
|
-
alt?: string;
|
|
15
|
-
creditLine?: string;
|
|
16
|
-
createdBy?: string; // User ID who uploaded this asset
|
|
17
|
-
metadata?: {
|
|
18
|
-
schemaType?: string; // e.g., 'newsletterLanding'
|
|
19
|
-
fieldPath?: string; // e.g., 'logo' or 'seo.metaImage'
|
|
20
|
-
[key: string]: any; // Allow additional metadata
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface AssetFilters {
|
|
25
|
-
assetType?: 'image' | 'file';
|
|
26
|
-
mimeType?: string;
|
|
27
|
-
search?: string;
|
|
28
|
-
limit?: number;
|
|
29
|
-
offset?: number;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Asset service - coordinates storage and database operations
|
|
34
|
-
* Maintains separation of concerns while providing unified asset management
|
|
35
|
-
*/
|
|
36
|
-
export class AssetService {
|
|
37
|
-
constructor(
|
|
38
|
-
private storage: StorageAdapter,
|
|
39
|
-
private database: DatabaseAdapter
|
|
40
|
-
) {}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Upload and store an asset
|
|
44
|
-
*/
|
|
45
|
-
async uploadAsset(organizationId: string, data: AssetUploadData): Promise<Asset> {
|
|
46
|
-
// Determine asset type
|
|
47
|
-
const assetType = data.mimeType.startsWith('image/') ? 'image' : 'file';
|
|
48
|
-
|
|
49
|
-
// Extract image metadata if it's an image
|
|
50
|
-
let width: number | undefined;
|
|
51
|
-
let height: number | undefined;
|
|
52
|
-
let metadata: any = {
|
|
53
|
-
// Include field metadata for privacy checking
|
|
54
|
-
...data.metadata
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
if (assetType === 'image') {
|
|
58
|
-
try {
|
|
59
|
-
const imageMetadata = await sharp(data.buffer).metadata();
|
|
60
|
-
width = imageMetadata.width;
|
|
61
|
-
height = imageMetadata.height;
|
|
62
|
-
|
|
63
|
-
// Merge image metadata with field metadata
|
|
64
|
-
metadata = {
|
|
65
|
-
...metadata, // Keep schemaType and fieldPath
|
|
66
|
-
format: imageMetadata.format,
|
|
67
|
-
space: imageMetadata.space,
|
|
68
|
-
channels: imageMetadata.channels,
|
|
69
|
-
density: imageMetadata.density,
|
|
70
|
-
hasProfile: imageMetadata.hasProfile,
|
|
71
|
-
hasAlpha: imageMetadata.hasAlpha
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// Add dominant color
|
|
75
|
-
const stats = await sharp(data.buffer).stats();
|
|
76
|
-
metadata.dominantColor = stats.dominant;
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.warn('Could not extract image metadata:', error);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 1. Store file using storage adapter
|
|
83
|
-
const storageFile = await this.storage.store({
|
|
84
|
-
buffer: data.buffer,
|
|
85
|
-
filename: data.originalFilename,
|
|
86
|
-
mimeType: data.mimeType,
|
|
87
|
-
size: data.size
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// 2. Save asset metadata using database adapter
|
|
91
|
-
try {
|
|
92
|
-
const asset = await this.database.createAsset({
|
|
93
|
-
assetType,
|
|
94
|
-
filename: storageFile.path.split('/').pop() || data.originalFilename,
|
|
95
|
-
originalFilename: data.originalFilename,
|
|
96
|
-
mimeType: data.mimeType,
|
|
97
|
-
size: data.size,
|
|
98
|
-
url: storageFile.url || '', // Empty for local storage initially
|
|
99
|
-
path: storageFile.path,
|
|
100
|
-
storageAdapter: this.storage.name,
|
|
101
|
-
organizationId,
|
|
102
|
-
width,
|
|
103
|
-
height,
|
|
104
|
-
metadata,
|
|
105
|
-
title: data.title || undefined,
|
|
106
|
-
description: data.description || undefined,
|
|
107
|
-
alt: data.alt || undefined,
|
|
108
|
-
creditLine: data.creditLine || undefined,
|
|
109
|
-
createdBy: data.createdBy
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// If using local storage, generate and store the CDN URL with the real asset ID
|
|
113
|
-
if (!storageFile.url) {
|
|
114
|
-
const cdnUrl = `/media/${asset.id}/${encodeURIComponent(asset.originalFilename)}`;
|
|
115
|
-
|
|
116
|
-
// Update both the object and the database
|
|
117
|
-
asset.url = cdnUrl;
|
|
118
|
-
await this.database.updateAsset(organizationId, asset.id, { url: cdnUrl });
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return asset;
|
|
122
|
-
} catch (error) {
|
|
123
|
-
// If database save fails, clean up the stored file
|
|
124
|
-
await this.storage.delete(storageFile.path);
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Find asset by ID
|
|
131
|
-
*/
|
|
132
|
-
async findAssetById(organizationId: string, id: string): Promise<Asset | null> {
|
|
133
|
-
return await this.database.findAssetById(organizationId, id);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/**
|
|
137
|
-
* Find asset by ID globally (bypasses organization filter for public asset access)
|
|
138
|
-
* Only available on PostgreSQL adapter with RLS bypass
|
|
139
|
-
*/
|
|
140
|
-
async findAssetByIdGlobal(id: string): Promise<Asset | null> {
|
|
141
|
-
// Check if the adapter has the global method
|
|
142
|
-
if (
|
|
143
|
-
'findAssetByIdGlobal' in this.database &&
|
|
144
|
-
typeof this.database.findAssetByIdGlobal === 'function'
|
|
145
|
-
) {
|
|
146
|
-
console.log('[AssetService] Using findAssetByIdGlobal from adapter');
|
|
147
|
-
return await this.database.findAssetByIdGlobal(id);
|
|
148
|
-
}
|
|
149
|
-
// Fallback: not supported
|
|
150
|
-
console.warn('[AssetService] findAssetByIdGlobal not supported by this database adapter');
|
|
151
|
-
console.warn('[AssetService] Database adapter type:', this.database.constructor.name);
|
|
152
|
-
console.warn(
|
|
153
|
-
'[AssetService] Available methods:',
|
|
154
|
-
Object.getOwnPropertyNames(Object.getPrototypeOf(this.database))
|
|
155
|
-
);
|
|
156
|
-
return null;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Find multiple assets with filtering
|
|
161
|
-
*/
|
|
162
|
-
async findAssets(organizationId: string, filters: AssetFilters = {}): Promise<Asset[]> {
|
|
163
|
-
return await this.database.findAssets(organizationId, filters);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Delete asset (both file and database record)
|
|
168
|
-
*
|
|
169
|
-
* Note: If the asset was stored by a different adapter (e.g., switching from R2 to local),
|
|
170
|
-
* file deletion may fail. The database record will still be removed for a clean state.
|
|
171
|
-
*/
|
|
172
|
-
async deleteAsset(organizationId: string, id: string): Promise<boolean> {
|
|
173
|
-
const asset = await this.database.findAssetById(organizationId, id);
|
|
174
|
-
if (!asset) {
|
|
175
|
-
return false;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Try to delete file from storage
|
|
179
|
-
// If the asset was stored by a different adapter, this may fail
|
|
180
|
-
if (asset.storageAdapter === this.storage.name) {
|
|
181
|
-
// Same adapter - delete should work
|
|
182
|
-
try {
|
|
183
|
-
await this.storage.delete(asset.path);
|
|
184
|
-
} catch (error) {
|
|
185
|
-
console.warn(`Failed to delete file from storage: ${asset.path}`, error);
|
|
186
|
-
}
|
|
187
|
-
} else {
|
|
188
|
-
// Different adapter - log warning but continue with database cleanup
|
|
189
|
-
console.warn(
|
|
190
|
-
`Asset ${id} was stored by '${asset.storageAdapter}' but current adapter is '${this.storage.name}'. ` +
|
|
191
|
-
`File at ${asset.path} may need manual cleanup.`
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Always delete database record for clean state
|
|
196
|
-
return await this.database.deleteAsset(organizationId, id);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Update asset metadata
|
|
201
|
-
*/
|
|
202
|
-
async updateAssetMetadata(
|
|
203
|
-
organizationId: string,
|
|
204
|
-
id: string,
|
|
205
|
-
metadata: {
|
|
206
|
-
title?: string;
|
|
207
|
-
description?: string;
|
|
208
|
-
alt?: string;
|
|
209
|
-
creditLine?: string;
|
|
210
|
-
updatedBy?: string; // User ID who updated this asset
|
|
211
|
-
}
|
|
212
|
-
): Promise<Asset | null> {
|
|
213
|
-
return await this.database.updateAsset(organizationId, id, metadata);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Get asset statistics
|
|
218
|
-
*/
|
|
219
|
-
async getAssetStats(organizationId: string): Promise<{
|
|
220
|
-
totalAssets: number;
|
|
221
|
-
totalImages: number;
|
|
222
|
-
totalFiles: number;
|
|
223
|
-
totalSize: number;
|
|
224
|
-
}> {
|
|
225
|
-
const [totalAssets, assetsByType, totalSize] = await Promise.all([
|
|
226
|
-
this.database.countAssets(organizationId),
|
|
227
|
-
this.database.countAssetsByType(organizationId),
|
|
228
|
-
this.database.getTotalAssetsSize(organizationId)
|
|
229
|
-
]);
|
|
230
|
-
|
|
231
|
-
return {
|
|
232
|
-
totalAssets,
|
|
233
|
-
totalImages: assetsByType.image || 0,
|
|
234
|
-
totalFiles: assetsByType.file || 0,
|
|
235
|
-
totalSize
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Get health status of both storage and database
|
|
241
|
-
*/
|
|
242
|
-
async getHealthStatus(): Promise<{
|
|
243
|
-
storage: boolean;
|
|
244
|
-
database: boolean;
|
|
245
|
-
}> {
|
|
246
|
-
const [storageHealthy, databaseHealthy] = await Promise.all([
|
|
247
|
-
this.storage.isHealthy(),
|
|
248
|
-
this.database.isHealthy()
|
|
249
|
-
]);
|
|
250
|
-
|
|
251
|
-
return {
|
|
252
|
-
storage: storageHealthy,
|
|
253
|
-
database: databaseHealthy
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
}
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
// Pure local file system storage adapter - no database operations
|
|
2
|
-
import { writeFile, mkdir, unlink, stat, readdir } from 'fs/promises';
|
|
3
|
-
import { join, dirname } from 'path';
|
|
4
|
-
import type {
|
|
5
|
-
StorageAdapter,
|
|
6
|
-
StorageConfig,
|
|
7
|
-
UploadFileData,
|
|
8
|
-
StorageFile
|
|
9
|
-
} from '../interfaces/storage.js';
|
|
10
|
-
|
|
11
|
-
const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
|
12
|
-
const DEFAULT_ALLOWED_TYPES = [
|
|
13
|
-
'image/jpeg',
|
|
14
|
-
'image/png',
|
|
15
|
-
'image/webp',
|
|
16
|
-
'image/gif',
|
|
17
|
-
'image/avif',
|
|
18
|
-
'application/pdf',
|
|
19
|
-
'text/plain'
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Pure local file system storage adapter - only handles files
|
|
24
|
-
*/
|
|
25
|
-
export class LocalStorageAdapter implements StorageAdapter {
|
|
26
|
-
readonly name = 'local';
|
|
27
|
-
private config: Required<StorageConfig>;
|
|
28
|
-
|
|
29
|
-
constructor(config: StorageConfig) {
|
|
30
|
-
this.config = {
|
|
31
|
-
basePath: config.basePath,
|
|
32
|
-
baseUrl: config.baseUrl || '',
|
|
33
|
-
maxFileSize: config.maxFileSize || DEFAULT_MAX_FILE_SIZE,
|
|
34
|
-
allowedTypes: config.allowedTypes || DEFAULT_ALLOWED_TYPES,
|
|
35
|
-
options: config.options || {}
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Generate unique filename preserving original name
|
|
41
|
-
*/
|
|
42
|
-
private async generateUniqueFilename(originalFilename: string): Promise<string> {
|
|
43
|
-
const { name, ext } = this.parseFilename(originalFilename);
|
|
44
|
-
let filename = originalFilename;
|
|
45
|
-
let counter = 1;
|
|
46
|
-
|
|
47
|
-
// Check if file exists on disk
|
|
48
|
-
while (await this.fileExistsOnDisk(filename)) {
|
|
49
|
-
filename = `${name} (${counter})${ext}`;
|
|
50
|
-
counter++;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return filename;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Parse filename into name and extension
|
|
58
|
-
*/
|
|
59
|
-
private parseFilename(filename: string): { name: string; ext: string } {
|
|
60
|
-
const lastDotIndex = filename.lastIndexOf('.');
|
|
61
|
-
if (lastDotIndex === -1) {
|
|
62
|
-
return { name: filename, ext: '' };
|
|
63
|
-
}
|
|
64
|
-
return {
|
|
65
|
-
name: filename.substring(0, lastDotIndex),
|
|
66
|
-
ext: filename.substring(lastDotIndex)
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Check if file exists on disk
|
|
72
|
-
*/
|
|
73
|
-
private async fileExistsOnDisk(filename: string): Promise<boolean> {
|
|
74
|
-
try {
|
|
75
|
-
const filePath = join(this.config.basePath, filename);
|
|
76
|
-
await stat(filePath);
|
|
77
|
-
return true;
|
|
78
|
-
} catch {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Store a file and return storage info
|
|
85
|
-
*/
|
|
86
|
-
async store(data: UploadFileData): Promise<StorageFile> {
|
|
87
|
-
// Validate file type
|
|
88
|
-
if (!this.config.allowedTypes.includes(data.mimeType)) {
|
|
89
|
-
throw new Error(
|
|
90
|
-
`Invalid file type: ${data.mimeType}. Allowed types: ${this.config.allowedTypes.join(', ')}`
|
|
91
|
-
);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Validate file size
|
|
95
|
-
if (data.size > this.config.maxFileSize) {
|
|
96
|
-
throw new Error(
|
|
97
|
-
`File too large: ${data.size} bytes. Maximum size: ${this.config.maxFileSize} bytes`
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Generate unique filename
|
|
102
|
-
const filename = await this.generateUniqueFilename(data.filename);
|
|
103
|
-
const filePath = join(this.config.basePath, filename);
|
|
104
|
-
|
|
105
|
-
// Store internal path only - URL will be generated by API with asset ID
|
|
106
|
-
// This forces all access through /assets/{id} for proper authorization
|
|
107
|
-
const url = ''; // Will be populated as /assets/{assetId}/{filename} by the API
|
|
108
|
-
|
|
109
|
-
console.log('[LocalStorageAdapter] Storing file:', {
|
|
110
|
-
filename,
|
|
111
|
-
filePath,
|
|
112
|
-
note: 'URL will be generated as /assets/{assetId}/{filename}',
|
|
113
|
-
basePath: this.config.basePath
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// Ensure storage directory exists
|
|
117
|
-
await mkdir(dirname(filePath), { recursive: true });
|
|
118
|
-
|
|
119
|
-
// Save file to disk
|
|
120
|
-
await writeFile(filePath, data.buffer);
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
path: filePath,
|
|
124
|
-
url, // Empty - API will generate clean URL with asset ID
|
|
125
|
-
size: data.size
|
|
126
|
-
};
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Read a file from storage
|
|
131
|
-
* Used by API endpoint to serve files
|
|
132
|
-
*/
|
|
133
|
-
async getObject(path: string): Promise<Buffer> {
|
|
134
|
-
const { readFile } = await import('fs/promises');
|
|
135
|
-
return await readFile(path);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Delete a file from storage
|
|
140
|
-
*/
|
|
141
|
-
async delete(path: string): Promise<boolean> {
|
|
142
|
-
try {
|
|
143
|
-
await unlink(path);
|
|
144
|
-
return true;
|
|
145
|
-
} catch (error) {
|
|
146
|
-
console.warn('Could not delete file from disk:', error);
|
|
147
|
-
return false;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Check if file exists
|
|
153
|
-
*/
|
|
154
|
-
async exists(path: string): Promise<boolean> {
|
|
155
|
-
try {
|
|
156
|
-
await stat(path);
|
|
157
|
-
return true;
|
|
158
|
-
} catch {
|
|
159
|
-
return false;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Get public URL for a file path
|
|
165
|
-
*/
|
|
166
|
-
getUrl(path: string): string {
|
|
167
|
-
// Extract filename from path and construct URL
|
|
168
|
-
const filename = path.split('/').pop() || '';
|
|
169
|
-
return `${this.config.baseUrl}/${encodeURIComponent(filename)}`;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Get storage information
|
|
174
|
-
*/
|
|
175
|
-
async getStorageInfo(): Promise<{ totalSize: number; availableSpace?: number }> {
|
|
176
|
-
try {
|
|
177
|
-
// Calculate total size of files in storage directory
|
|
178
|
-
const files = await readdir(this.config.basePath);
|
|
179
|
-
let totalSize = 0;
|
|
180
|
-
|
|
181
|
-
for (const file of files) {
|
|
182
|
-
try {
|
|
183
|
-
const filePath = join(this.config.basePath, file);
|
|
184
|
-
const stats = await stat(filePath);
|
|
185
|
-
if (stats.isFile()) {
|
|
186
|
-
totalSize += stats.size;
|
|
187
|
-
}
|
|
188
|
-
} catch {
|
|
189
|
-
// Skip files that can't be read
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return { totalSize };
|
|
194
|
-
} catch (error) {
|
|
195
|
-
console.error('Error getting storage info:', error);
|
|
196
|
-
return { totalSize: 0 };
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Health check - test if we can write to storage
|
|
202
|
-
*/
|
|
203
|
-
async isHealthy(): Promise<boolean> {
|
|
204
|
-
try {
|
|
205
|
-
const testFile = join(this.config.basePath, `health-check-${Date.now()}.tmp`);
|
|
206
|
-
await mkdir(dirname(testFile), { recursive: true });
|
|
207
|
-
await writeFile(testFile, 'health check');
|
|
208
|
-
await unlink(testFile);
|
|
209
|
-
return true;
|
|
210
|
-
} catch (error) {
|
|
211
|
-
console.error('Storage health check failed:', error);
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
// Pure storage interface for file operations only
|
|
2
|
-
export interface StorageFile {
|
|
3
|
-
path: string;
|
|
4
|
-
url: string;
|
|
5
|
-
size: number;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface UploadFileData {
|
|
9
|
-
buffer: Buffer;
|
|
10
|
-
filename: string;
|
|
11
|
-
mimeType: string;
|
|
12
|
-
size: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface StorageConfig {
|
|
16
|
-
basePath: string;
|
|
17
|
-
baseUrl?: string;
|
|
18
|
-
maxFileSize?: number;
|
|
19
|
-
allowedTypes?: string[];
|
|
20
|
-
options?: {
|
|
21
|
-
[key: string]: any;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface StorageObjectMetadata {
|
|
26
|
-
key: string;
|
|
27
|
-
size: number;
|
|
28
|
-
lastModified: Date;
|
|
29
|
-
contentType?: string;
|
|
30
|
-
etag?: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface ListObjectsOptions {
|
|
34
|
-
prefix?: string;
|
|
35
|
-
maxKeys?: number;
|
|
36
|
-
continuationToken?: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface ListObjectsResult {
|
|
40
|
-
objects: StorageObjectMetadata[];
|
|
41
|
-
isTruncated: boolean;
|
|
42
|
-
continuationToken?: string;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Pure storage interface - only handles file operations
|
|
47
|
-
* No database operations - completely agnostic
|
|
48
|
-
*/
|
|
49
|
-
export interface StorageAdapter {
|
|
50
|
-
// Adapter identifier (used to track which adapter stored each file)
|
|
51
|
-
readonly name: string;
|
|
52
|
-
|
|
53
|
-
// Core file operations (required)
|
|
54
|
-
store(data: UploadFileData): Promise<StorageFile>;
|
|
55
|
-
delete(path: string): Promise<boolean>;
|
|
56
|
-
exists(path: string): Promise<boolean>;
|
|
57
|
-
getUrl(path: string): string;
|
|
58
|
-
|
|
59
|
-
// Storage info
|
|
60
|
-
getStorageInfo(): Promise<{
|
|
61
|
-
totalSize: number;
|
|
62
|
-
availableSpace?: number;
|
|
63
|
-
}>;
|
|
64
|
-
|
|
65
|
-
// Health check
|
|
66
|
-
isHealthy(): Promise<boolean>;
|
|
67
|
-
|
|
68
|
-
// Connection management (optional)
|
|
69
|
-
connect?(): Promise<void>;
|
|
70
|
-
disconnect?(): Promise<void>;
|
|
71
|
-
|
|
72
|
-
// Extended file operations (optional - for advanced storage features)
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Retrieve file contents as Buffer
|
|
76
|
-
* Useful for: image processing, transformations, backups, migrations
|
|
77
|
-
*/
|
|
78
|
-
getObject?(path: string): Promise<Buffer>;
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* List objects in storage with optional filtering
|
|
82
|
-
* Useful for: admin UI file browser, asset management, cleanup operations
|
|
83
|
-
*/
|
|
84
|
-
listObjects?(options?: ListObjectsOptions): Promise<ListObjectsResult>;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Copy/duplicate an object within storage
|
|
88
|
-
* Useful for: versioning, backups, image variant generation
|
|
89
|
-
*/
|
|
90
|
-
copyObject?(sourcePath: string, destPath: string): Promise<boolean>;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Get object metadata without downloading the file
|
|
94
|
-
* Useful for: verifying file integrity, checking size/type
|
|
95
|
-
*/
|
|
96
|
-
getObjectMetadata?(path: string): Promise<StorageObjectMetadata>;
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Generate a signed URL for temporary access to a file
|
|
100
|
-
* Useful for: access control, private files, secure downloads
|
|
101
|
-
* @param path - File path
|
|
102
|
-
* @param expiresIn - Expiration time in seconds (default: 3600)
|
|
103
|
-
* @returns Signed URL that expires after the specified time
|
|
104
|
-
*/
|
|
105
|
-
getSignedUrl?(path: string, expiresIn?: number): Promise<string>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Storage provider factory interface
|
|
110
|
-
*/
|
|
111
|
-
export interface StorageProvider {
|
|
112
|
-
name: string;
|
|
113
|
-
createAdapter(config: StorageConfig): StorageAdapter;
|
|
114
|
-
}
|