bunsane 0.1.0 → 0.1.2
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/.github/workflows/deploy-docs.yml +57 -0
- package/LICENSE.md +1 -1
- package/README.md +2 -28
- package/TODO.md +8 -1
- package/bun.lock +3 -0
- package/config/upload.config.ts +135 -0
- package/core/App.ts +168 -4
- package/core/ArcheType.ts +122 -0
- package/core/BatchLoader.ts +100 -0
- package/core/ComponentRegistry.ts +4 -3
- package/core/Components.ts +2 -2
- package/core/Decorators.ts +15 -8
- package/core/Entity.ts +193 -14
- package/core/EntityCache.ts +15 -0
- package/core/EntityHookManager.ts +855 -0
- package/core/EntityManager.ts +12 -2
- package/core/ErrorHandler.ts +64 -7
- package/core/FileValidator.ts +284 -0
- package/core/Query.ts +503 -85
- package/core/RequestContext.ts +24 -0
- package/core/RequestLoaders.ts +89 -0
- package/core/SchedulerManager.ts +710 -0
- package/core/UploadManager.ts +261 -0
- package/core/components/UploadComponent.ts +206 -0
- package/core/decorators/EntityHooks.ts +190 -0
- package/core/decorators/ScheduledTask.ts +83 -0
- package/core/events/EntityLifecycleEvents.ts +177 -0
- package/core/processors/ImageProcessor.ts +423 -0
- package/core/storage/LocalStorageProvider.ts +290 -0
- package/core/storage/StorageProvider.ts +112 -0
- package/database/DatabaseHelper.ts +183 -58
- package/database/index.ts +5 -5
- package/database/sqlHelpers.ts +7 -0
- package/docs/README.md +149 -0
- package/docs/_coverpage.md +36 -0
- package/docs/_sidebar.md +23 -0
- package/docs/api/core.md +568 -0
- package/docs/api/hooks.md +554 -0
- package/docs/api/index.md +222 -0
- package/docs/api/query.md +678 -0
- package/docs/api/service.md +744 -0
- package/docs/core-concepts/archetypes.md +512 -0
- package/docs/core-concepts/components.md +498 -0
- package/docs/core-concepts/entity.md +314 -0
- package/docs/core-concepts/hooks.md +683 -0
- package/docs/core-concepts/query.md +588 -0
- package/docs/core-concepts/services.md +647 -0
- package/docs/examples/code-examples.md +425 -0
- package/docs/getting-started.md +337 -0
- package/docs/index.html +97 -0
- package/gql/Generator.ts +58 -35
- package/gql/decorators/Upload.ts +176 -0
- package/gql/helpers.ts +67 -0
- package/gql/index.ts +65 -31
- package/gql/types.ts +1 -1
- package/index.ts +79 -11
- package/package.json +19 -10
- package/rest/Generator.ts +3 -0
- package/rest/index.ts +22 -0
- package/service/Service.ts +1 -1
- package/service/ServiceRegistry.ts +10 -6
- package/service/index.ts +12 -1
- package/tests/bench/insert.bench.ts +59 -0
- package/tests/bench/relations.bench.ts +269 -0
- package/tests/bench/sorting.bench.ts +415 -0
- package/tests/component-hooks.test.ts +1409 -0
- package/tests/component.test.ts +338 -0
- package/tests/errorHandling.test.ts +155 -0
- package/tests/hooks.test.ts +666 -0
- package/tests/query-sorting.test.ts +101 -0
- package/tests/relations.test.ts +169 -0
- package/tests/scheduler.test.ts +724 -0
- package/tsconfig.json +35 -34
- package/types/graphql.types.ts +87 -0
- package/types/hooks.types.ts +141 -0
- package/types/scheduler.types.ts +165 -0
- package/types/upload.types.ts +184 -0
- package/upload/index.ts +140 -0
- package/utils/UploadHelper.ts +305 -0
- package/utils/cronParser.ts +366 -0
- package/utils/errorMessages.ts +151 -0
- package/core/Events.ts +0 -0
package/upload/index.ts
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bunsane Upload System
|
|
3
|
+
* Comprehensive file upload handling for the Bunsane framework
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Core Upload System
|
|
7
|
+
export { UploadManager } from "../core/UploadManager";
|
|
8
|
+
export { FileValidator } from "../core/FileValidator";
|
|
9
|
+
|
|
10
|
+
// Storage Providers
|
|
11
|
+
export { StorageProvider } from "../core/storage/StorageProvider";
|
|
12
|
+
export { LocalStorageProvider } from "../core/storage/LocalStorageProvider";
|
|
13
|
+
|
|
14
|
+
// Components
|
|
15
|
+
export { UploadComponent, ImageMetadataComponent } from "../core/components/UploadComponent";
|
|
16
|
+
|
|
17
|
+
// Processors
|
|
18
|
+
export { ImageProcessor } from "../core/processors/ImageProcessor";
|
|
19
|
+
|
|
20
|
+
// Utilities
|
|
21
|
+
export { UploadHelper } from "../utils/UploadHelper";
|
|
22
|
+
|
|
23
|
+
// GraphQL Decorators
|
|
24
|
+
export {
|
|
25
|
+
Upload,
|
|
26
|
+
UploadField,
|
|
27
|
+
BatchUpload,
|
|
28
|
+
RequiredUpload,
|
|
29
|
+
UploadDecorators,
|
|
30
|
+
getUploadConfiguration
|
|
31
|
+
} from "../gql/decorators/Upload";
|
|
32
|
+
|
|
33
|
+
// Configuration
|
|
34
|
+
export {
|
|
35
|
+
DEFAULT_UPLOAD_CONFIG,
|
|
36
|
+
IMAGE_UPLOAD_CONFIG,
|
|
37
|
+
DOCUMENT_UPLOAD_CONFIG,
|
|
38
|
+
AVATAR_UPLOAD_CONFIG,
|
|
39
|
+
SECURE_UPLOAD_CONFIG
|
|
40
|
+
} from "../config/upload.config";
|
|
41
|
+
|
|
42
|
+
// Types
|
|
43
|
+
export type {
|
|
44
|
+
UploadConfiguration,
|
|
45
|
+
ImageProcessingOptions,
|
|
46
|
+
ValidationOptions,
|
|
47
|
+
ValidationResult,
|
|
48
|
+
UploadResult,
|
|
49
|
+
UploadError,
|
|
50
|
+
UploadErrorCode,
|
|
51
|
+
FileMetadata,
|
|
52
|
+
StorageResult,
|
|
53
|
+
UploadProgress,
|
|
54
|
+
BatchUploadResult,
|
|
55
|
+
UploadComponentData,
|
|
56
|
+
UploadDecoratorConfig,
|
|
57
|
+
UploadGraphQLType
|
|
58
|
+
} from "../types/upload.types";
|
|
59
|
+
|
|
60
|
+
// Imports for internal use
|
|
61
|
+
import { UploadManager } from "../core/UploadManager";
|
|
62
|
+
import type { UploadConfiguration } from "../types/upload.types";
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Initialize the upload system with default configuration
|
|
66
|
+
*/
|
|
67
|
+
export async function initializeUploadSystem(config?: Partial<UploadConfiguration>): Promise<void> {
|
|
68
|
+
const uploadManager = UploadManager.getInstance();
|
|
69
|
+
|
|
70
|
+
if (config) {
|
|
71
|
+
uploadManager.updateConfiguration(config);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Initialize default storage provider
|
|
75
|
+
await uploadManager.getStorageProvider("local").initialize();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Quick setup functions for common use cases
|
|
80
|
+
*/
|
|
81
|
+
export class QuickSetup {
|
|
82
|
+
/**
|
|
83
|
+
* Setup for image uploads with thumbnails
|
|
84
|
+
*/
|
|
85
|
+
static async forImages(): Promise<void> {
|
|
86
|
+
const uploadManager = UploadManager.getInstance();
|
|
87
|
+
uploadManager.updateConfiguration({
|
|
88
|
+
maxFileSize: 5 * 1024 * 1024, // 5MB
|
|
89
|
+
allowedMimeTypes: ["image/jpeg", "image/png", "image/gif", "image/webp"],
|
|
90
|
+
allowedExtensions: [".jpg", ".jpeg", ".png", ".gif", ".webp"],
|
|
91
|
+
generateThumbnails: true,
|
|
92
|
+
imageProcessing: {
|
|
93
|
+
generateThumbnails: true,
|
|
94
|
+
thumbnailSizes: [
|
|
95
|
+
{ width: 150, height: 150, suffix: "_thumb" },
|
|
96
|
+
{ width: 300, height: 300, suffix: "_medium" }
|
|
97
|
+
],
|
|
98
|
+
compress: true,
|
|
99
|
+
quality: 85
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Setup for document uploads
|
|
106
|
+
*/
|
|
107
|
+
static async forDocuments(): Promise<void> {
|
|
108
|
+
const uploadManager = UploadManager.getInstance();
|
|
109
|
+
uploadManager.updateConfiguration({
|
|
110
|
+
maxFileSize: 25 * 1024 * 1024, // 25MB
|
|
111
|
+
allowedMimeTypes: [
|
|
112
|
+
"application/pdf",
|
|
113
|
+
"text/plain",
|
|
114
|
+
"application/msword",
|
|
115
|
+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
116
|
+
],
|
|
117
|
+
allowedExtensions: [".pdf", ".txt", ".doc", ".docx"],
|
|
118
|
+
validateFileSignature: true,
|
|
119
|
+
generateThumbnails: false
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Setup for secure uploads with strict validation
|
|
125
|
+
*/
|
|
126
|
+
static async forSecureUploads(): Promise<void> {
|
|
127
|
+
const uploadManager = UploadManager.getInstance();
|
|
128
|
+
uploadManager.updateConfiguration({
|
|
129
|
+
maxFileSize: 1 * 1024 * 1024, // 1MB
|
|
130
|
+
allowedMimeTypes: ["image/jpeg", "image/png"],
|
|
131
|
+
allowedExtensions: [".jpg", ".jpeg", ".png"],
|
|
132
|
+
validateFileSignature: true,
|
|
133
|
+
sanitizeFileName: true,
|
|
134
|
+
validation: {
|
|
135
|
+
scanForMalware: true,
|
|
136
|
+
strictMimeType: true
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { UploadManager } from "../core/UploadManager";
|
|
2
|
+
import { UploadComponent, ImageMetadataComponent } from "../core/components/UploadComponent";
|
|
3
|
+
import { Entity } from "../core/Entity";
|
|
4
|
+
import type { UploadConfiguration, UploadResult, BatchUploadResult } from "../types/upload.types";
|
|
5
|
+
import { logger as MainLogger } from "../core/Logger";
|
|
6
|
+
|
|
7
|
+
const logger = MainLogger.child({ scope: "UploadHelper" });
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* UploadHelper - Utility class for common upload operations
|
|
11
|
+
* Provides convenience methods for handling uploads in services
|
|
12
|
+
*/
|
|
13
|
+
export class UploadHelper {
|
|
14
|
+
private static uploadManager = UploadManager.getInstance();
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Process single file upload and attach to entity
|
|
18
|
+
*/
|
|
19
|
+
static async processUploadForEntity(
|
|
20
|
+
entity: Entity,
|
|
21
|
+
file: File,
|
|
22
|
+
config?: Partial<UploadConfiguration>
|
|
23
|
+
): Promise<UploadResult> {
|
|
24
|
+
try {
|
|
25
|
+
logger.info(`Processing upload for entity ${entity.id}`);
|
|
26
|
+
|
|
27
|
+
const result = await this.uploadManager.uploadFile(file, config);
|
|
28
|
+
|
|
29
|
+
if (result.success && result.uploadId) {
|
|
30
|
+
// Create and attach upload component
|
|
31
|
+
const uploadComponent = new UploadComponent();
|
|
32
|
+
uploadComponent.setUploadData({
|
|
33
|
+
uploadId: result.uploadId,
|
|
34
|
+
fileName: result.fileName!,
|
|
35
|
+
originalFileName: result.originalFileName!,
|
|
36
|
+
mimeType: result.mimeType!,
|
|
37
|
+
size: result.size!,
|
|
38
|
+
path: result.path!,
|
|
39
|
+
url: result.url!,
|
|
40
|
+
uploadedAt: new Date().toISOString(),
|
|
41
|
+
metadata: result.metadata || {}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
entity.add(UploadComponent, uploadComponent.data());
|
|
45
|
+
|
|
46
|
+
// Add image metadata if it's an image
|
|
47
|
+
if (result.mimeType?.startsWith('image/')) {
|
|
48
|
+
await this.addImageMetadata(entity, file, result);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
logger.info(`Upload component attached to entity ${entity.id}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return result;
|
|
55
|
+
|
|
56
|
+
} catch (error) {
|
|
57
|
+
logger.error(`Failed to process upload for entity ${entity.id}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Process multiple files for an entity
|
|
64
|
+
*/
|
|
65
|
+
static async processBatchUploadForEntity(
|
|
66
|
+
entity: Entity,
|
|
67
|
+
files: File[],
|
|
68
|
+
config?: Partial<UploadConfiguration>
|
|
69
|
+
): Promise<BatchUploadResult> {
|
|
70
|
+
logger.info(`Processing batch upload of ${files.length} files for entity ${entity.id}`);
|
|
71
|
+
|
|
72
|
+
const results = await this.uploadManager.uploadFiles(files, config);
|
|
73
|
+
|
|
74
|
+
let successfulUploads = 0;
|
|
75
|
+
let failedUploads = 0;
|
|
76
|
+
const errors: any[] = [];
|
|
77
|
+
|
|
78
|
+
for (const result of results) {
|
|
79
|
+
if (result.success) {
|
|
80
|
+
successfulUploads++;
|
|
81
|
+
|
|
82
|
+
// Attach upload component to entity
|
|
83
|
+
const uploadComponent = new UploadComponent();
|
|
84
|
+
uploadComponent.setUploadData({
|
|
85
|
+
uploadId: result.uploadId!,
|
|
86
|
+
fileName: result.fileName!,
|
|
87
|
+
originalFileName: result.originalFileName!,
|
|
88
|
+
mimeType: result.mimeType!,
|
|
89
|
+
size: result.size!,
|
|
90
|
+
path: result.path!,
|
|
91
|
+
url: result.url!,
|
|
92
|
+
uploadedAt: new Date().toISOString(),
|
|
93
|
+
metadata: result.metadata || {}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
entity.add(UploadComponent, uploadComponent.data());
|
|
97
|
+
|
|
98
|
+
} else {
|
|
99
|
+
failedUploads++;
|
|
100
|
+
if (result.error) {
|
|
101
|
+
errors.push(result.error);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
totalFiles: files.length,
|
|
108
|
+
successfulUploads,
|
|
109
|
+
failedUploads,
|
|
110
|
+
results,
|
|
111
|
+
errors
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Replace existing upload on entity
|
|
117
|
+
*/
|
|
118
|
+
static async replaceUploadForEntity(
|
|
119
|
+
entity: Entity,
|
|
120
|
+
file: File,
|
|
121
|
+
config?: Partial<UploadConfiguration>
|
|
122
|
+
): Promise<UploadResult> {
|
|
123
|
+
// Remove existing upload component if present
|
|
124
|
+
const existingUpload = await entity.get(UploadComponent);
|
|
125
|
+
if (existingUpload) {
|
|
126
|
+
// Delete old file
|
|
127
|
+
await this.uploadManager.deleteFile(existingUpload.path);
|
|
128
|
+
// Remove component data will be handled by the new upload
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return await this.processUploadForEntity(entity, file, config);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Get upload URLs for entity
|
|
136
|
+
*/
|
|
137
|
+
static async getUploadUrlsForEntity(entity: Entity): Promise<string[]> {
|
|
138
|
+
const upload = await entity.get(UploadComponent);
|
|
139
|
+
return upload ? [upload.url] : [];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Clean up orphaned uploads for entity
|
|
144
|
+
*/
|
|
145
|
+
static async cleanupOrphanedUploads(entity: Entity): Promise<number> {
|
|
146
|
+
let cleaned = 0;
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const upload = await entity.get(UploadComponent);
|
|
150
|
+
|
|
151
|
+
if (upload) {
|
|
152
|
+
const exists = await this.uploadManager.getStorageProvider().exists(upload.path);
|
|
153
|
+
if (!exists) {
|
|
154
|
+
// File doesn't exist, remove component
|
|
155
|
+
// This would require entity method to remove specific component instance
|
|
156
|
+
cleaned++;
|
|
157
|
+
logger.info(`Cleaned orphaned upload reference: ${upload.path}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
} catch (error) {
|
|
162
|
+
logger.error(`Failed to cleanup orphaned uploads for entity ${entity.id}: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return cleaned;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get total storage used by entity
|
|
170
|
+
*/
|
|
171
|
+
static async getEntityStorageUsage(entity: Entity): Promise<number> {
|
|
172
|
+
const upload = await entity.get(UploadComponent);
|
|
173
|
+
return upload ? upload.size : 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Validate file before upload
|
|
178
|
+
*/
|
|
179
|
+
static async validateFile(file: File, config: UploadConfiguration): Promise<boolean> {
|
|
180
|
+
const validator = (this.uploadManager as any).fileValidator;
|
|
181
|
+
const result = await validator.validate(file, config);
|
|
182
|
+
return result.valid;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Generate secure file URL with expiration
|
|
187
|
+
*/
|
|
188
|
+
static async getSecureFileUrl(
|
|
189
|
+
path: string,
|
|
190
|
+
expiresIn: number = 3600, // 1 hour default
|
|
191
|
+
storageProvider?: string
|
|
192
|
+
): Promise<string | null> {
|
|
193
|
+
// This would be implemented by storage providers that support signed URLs
|
|
194
|
+
const provider = this.uploadManager.getStorageProvider(storageProvider);
|
|
195
|
+
|
|
196
|
+
// For now, return regular URL
|
|
197
|
+
// TODO: Implement signed URL generation for cloud providers
|
|
198
|
+
return await provider.getUrl(path);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Copy upload from one entity to another
|
|
203
|
+
*/
|
|
204
|
+
static async copyUploadBetweenEntities(
|
|
205
|
+
sourceEntity: Entity,
|
|
206
|
+
targetEntity: Entity,
|
|
207
|
+
preserveOriginal: boolean = true
|
|
208
|
+
): Promise<boolean> {
|
|
209
|
+
try {
|
|
210
|
+
const sourceUpload = await sourceEntity.get(UploadComponent);
|
|
211
|
+
if (!sourceUpload) {
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const provider = this.uploadManager.getStorageProvider();
|
|
216
|
+
|
|
217
|
+
if (preserveOriginal) {
|
|
218
|
+
// Copy file to new location
|
|
219
|
+
const newPath = sourceUpload.path.replace(
|
|
220
|
+
sourceEntity.id,
|
|
221
|
+
targetEntity.id
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
const success = await provider.copy(sourceUpload.path, newPath);
|
|
225
|
+
if (success) {
|
|
226
|
+
// Create new upload component for target entity
|
|
227
|
+
const newUpload = new UploadComponent();
|
|
228
|
+
newUpload.setUploadData({
|
|
229
|
+
uploadId: targetEntity.id,
|
|
230
|
+
fileName: sourceUpload.fileName,
|
|
231
|
+
originalFileName: sourceUpload.originalFileName,
|
|
232
|
+
mimeType: sourceUpload.mimeType,
|
|
233
|
+
size: sourceUpload.size,
|
|
234
|
+
path: newPath,
|
|
235
|
+
url: await provider.getUrl(newPath),
|
|
236
|
+
uploadedAt: sourceUpload.uploadedAt,
|
|
237
|
+
metadata: JSON.parse(sourceUpload.metadata || '{}')
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
targetEntity.add(UploadComponent, newUpload.data());
|
|
241
|
+
return true;
|
|
242
|
+
}
|
|
243
|
+
} else {
|
|
244
|
+
// Move file
|
|
245
|
+
const newPath = sourceUpload.path.replace(
|
|
246
|
+
sourceEntity.id,
|
|
247
|
+
targetEntity.id
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const success = await provider.move(sourceUpload.path, newPath);
|
|
251
|
+
if (success) {
|
|
252
|
+
// Remove from source and add to target
|
|
253
|
+
const newUpload = new UploadComponent();
|
|
254
|
+
newUpload.setUploadData({
|
|
255
|
+
uploadId: targetEntity.id,
|
|
256
|
+
fileName: sourceUpload.fileName,
|
|
257
|
+
originalFileName: sourceUpload.originalFileName,
|
|
258
|
+
mimeType: sourceUpload.mimeType,
|
|
259
|
+
size: sourceUpload.size,
|
|
260
|
+
path: newPath,
|
|
261
|
+
url: await provider.getUrl(newPath),
|
|
262
|
+
uploadedAt: sourceUpload.uploadedAt,
|
|
263
|
+
metadata: JSON.parse(sourceUpload.metadata || '{}')
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
targetEntity.add(UploadComponent, newUpload.data());
|
|
267
|
+
// TODO: Remove from source entity
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return false;
|
|
273
|
+
|
|
274
|
+
} catch (error) {
|
|
275
|
+
logger.error(`Failed to copy upload between entities: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Add image metadata to entity
|
|
282
|
+
*/
|
|
283
|
+
private static async addImageMetadata(
|
|
284
|
+
entity: Entity,
|
|
285
|
+
file: File,
|
|
286
|
+
uploadResult: UploadResult
|
|
287
|
+
): Promise<void> {
|
|
288
|
+
try {
|
|
289
|
+
// For now, we'll add basic metadata
|
|
290
|
+
// In a full implementation, this would use an image processing library
|
|
291
|
+
const imageMetadata = new ImageMetadataComponent();
|
|
292
|
+
|
|
293
|
+
// Set basic metadata (would be extracted from actual image)
|
|
294
|
+
imageMetadata.width = 0; // Would be set from image analysis
|
|
295
|
+
imageMetadata.height = 0; // Would be set from image analysis
|
|
296
|
+
imageMetadata.hasAlpha = file.type === 'image/png';
|
|
297
|
+
imageMetadata.isAnimated = file.type === 'image/gif';
|
|
298
|
+
|
|
299
|
+
entity.add(ImageMetadataComponent, imageMetadata.data());
|
|
300
|
+
|
|
301
|
+
} catch (error) {
|
|
302
|
+
logger.warn(`Failed to add image metadata: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|