@venizia/ignis-docs 0.0.5 → 0.0.6-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/package.json +1 -1
  2. package/wiki/best-practices/architectural-patterns.md +0 -2
  3. package/wiki/best-practices/architecture-decisions.md +0 -8
  4. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  5. package/wiki/best-practices/code-style-standards/index.md +0 -1
  6. package/wiki/best-practices/code-style-standards/tooling.md +0 -3
  7. package/wiki/best-practices/contribution-workflow.md +12 -12
  8. package/wiki/best-practices/index.md +4 -14
  9. package/wiki/best-practices/performance-optimization.md +3 -3
  10. package/wiki/best-practices/security-guidelines.md +2 -2
  11. package/wiki/best-practices/troubleshooting-tips.md +1 -1
  12. package/wiki/guides/core-concepts/application/bootstrapping.md +6 -7
  13. package/wiki/guides/core-concepts/components-guide.md +1 -1
  14. package/wiki/guides/core-concepts/components.md +2 -2
  15. package/wiki/guides/core-concepts/dependency-injection.md +4 -5
  16. package/wiki/guides/core-concepts/persistent/datasources.md +4 -5
  17. package/wiki/guides/core-concepts/services.md +1 -1
  18. package/wiki/guides/get-started/5-minute-quickstart.md +4 -5
  19. package/wiki/guides/get-started/philosophy.md +12 -24
  20. package/wiki/guides/index.md +2 -9
  21. package/wiki/guides/reference/mcp-docs-server.md +13 -13
  22. package/wiki/guides/tutorials/building-a-crud-api.md +10 -10
  23. package/wiki/guides/tutorials/complete-installation.md +11 -12
  24. package/wiki/guides/tutorials/ecommerce-api.md +3 -3
  25. package/wiki/guides/tutorials/realtime-chat.md +6 -6
  26. package/wiki/guides/tutorials/testing.md +4 -5
  27. package/wiki/index.md +8 -14
  28. package/wiki/references/base/bootstrapping.md +0 -3
  29. package/wiki/references/base/components.md +2 -2
  30. package/wiki/references/base/controllers.md +0 -1
  31. package/wiki/references/base/datasources.md +1 -1
  32. package/wiki/references/base/dependency-injection.md +2 -2
  33. package/wiki/references/base/filter-system/default-filter.md +2 -3
  34. package/wiki/references/base/filter-system/index.md +1 -1
  35. package/wiki/references/base/filter-system/quick-reference.md +0 -14
  36. package/wiki/references/base/middlewares.md +0 -8
  37. package/wiki/references/base/providers.md +0 -9
  38. package/wiki/references/base/repositories/advanced.md +1 -1
  39. package/wiki/references/base/repositories/mixins.md +2 -3
  40. package/wiki/references/base/services.md +0 -1
  41. package/wiki/references/components/authentication/api.md +444 -0
  42. package/wiki/references/components/authentication/errors.md +177 -0
  43. package/wiki/references/components/authentication/index.md +571 -0
  44. package/wiki/references/components/authentication/usage.md +781 -0
  45. package/wiki/references/components/health-check.md +292 -103
  46. package/wiki/references/components/index.md +14 -12
  47. package/wiki/references/components/mail/api.md +505 -0
  48. package/wiki/references/components/mail/errors.md +176 -0
  49. package/wiki/references/components/mail/index.md +535 -0
  50. package/wiki/references/components/mail/usage.md +404 -0
  51. package/wiki/references/components/request-tracker.md +229 -25
  52. package/wiki/references/components/socket-io/api.md +1051 -0
  53. package/wiki/references/components/socket-io/errors.md +119 -0
  54. package/wiki/references/components/socket-io/index.md +410 -0
  55. package/wiki/references/components/socket-io/usage.md +322 -0
  56. package/wiki/references/components/static-asset/api.md +261 -0
  57. package/wiki/references/components/static-asset/errors.md +89 -0
  58. package/wiki/references/components/static-asset/index.md +617 -0
  59. package/wiki/references/components/static-asset/usage.md +364 -0
  60. package/wiki/references/components/swagger.md +390 -110
  61. package/wiki/references/components/template/api-page.md +125 -0
  62. package/wiki/references/components/template/errors-page.md +100 -0
  63. package/wiki/references/components/template/index.md +104 -0
  64. package/wiki/references/components/template/setup-page.md +134 -0
  65. package/wiki/references/components/template/single-page.md +132 -0
  66. package/wiki/references/components/template/usage-page.md +127 -0
  67. package/wiki/references/components/websocket/api.md +508 -0
  68. package/wiki/references/components/websocket/errors.md +123 -0
  69. package/wiki/references/components/websocket/index.md +453 -0
  70. package/wiki/references/components/websocket/usage.md +475 -0
  71. package/wiki/references/helpers/cron/index.md +224 -0
  72. package/wiki/references/helpers/crypto/index.md +537 -0
  73. package/wiki/references/helpers/env/index.md +214 -0
  74. package/wiki/references/helpers/error/index.md +232 -0
  75. package/wiki/references/helpers/index.md +16 -15
  76. package/wiki/references/helpers/inversion/index.md +608 -0
  77. package/wiki/references/helpers/logger/index.md +600 -0
  78. package/wiki/references/helpers/network/api.md +986 -0
  79. package/wiki/references/helpers/network/index.md +620 -0
  80. package/wiki/references/helpers/queue/index.md +589 -0
  81. package/wiki/references/helpers/redis/index.md +495 -0
  82. package/wiki/references/helpers/socket-io/api.md +497 -0
  83. package/wiki/references/helpers/socket-io/index.md +513 -0
  84. package/wiki/references/helpers/storage/api.md +705 -0
  85. package/wiki/references/helpers/storage/index.md +583 -0
  86. package/wiki/references/helpers/template/index.md +66 -0
  87. package/wiki/references/helpers/template/single-page.md +126 -0
  88. package/wiki/references/helpers/testing/index.md +510 -0
  89. package/wiki/references/helpers/types/index.md +512 -0
  90. package/wiki/references/helpers/uid/index.md +272 -0
  91. package/wiki/references/helpers/websocket/api.md +736 -0
  92. package/wiki/references/helpers/websocket/index.md +574 -0
  93. package/wiki/references/helpers/worker-thread/index.md +470 -0
  94. package/wiki/references/index.md +2 -9
  95. package/wiki/references/quick-reference.md +3 -18
  96. package/wiki/references/utilities/jsx.md +1 -8
  97. package/wiki/references/utilities/statuses.md +0 -7
  98. package/wiki/references/components/authentication.md +0 -476
  99. package/wiki/references/components/mail.md +0 -687
  100. package/wiki/references/components/socket-io.md +0 -562
  101. package/wiki/references/components/static-asset.md +0 -1277
  102. package/wiki/references/helpers/cron.md +0 -108
  103. package/wiki/references/helpers/crypto.md +0 -132
  104. package/wiki/references/helpers/env.md +0 -83
  105. package/wiki/references/helpers/error.md +0 -97
  106. package/wiki/references/helpers/inversion.md +0 -176
  107. package/wiki/references/helpers/logger.md +0 -296
  108. package/wiki/references/helpers/network.md +0 -396
  109. package/wiki/references/helpers/queue.md +0 -150
  110. package/wiki/references/helpers/redis.md +0 -142
  111. package/wiki/references/helpers/socket-io.md +0 -932
  112. package/wiki/references/helpers/storage.md +0 -665
  113. package/wiki/references/helpers/testing.md +0 -133
  114. package/wiki/references/helpers/types.md +0 -167
  115. package/wiki/references/helpers/uid.md +0 -167
  116. package/wiki/references/helpers/worker-thread.md +0 -178
  117. package/wiki/references/src-details/boot.md +0 -379
  118. package/wiki/references/src-details/core.md +0 -263
  119. package/wiki/references/src-details/dev-configs.md +0 -298
  120. package/wiki/references/src-details/docs.md +0 -71
  121. package/wiki/references/src-details/helpers.md +0 -211
  122. package/wiki/references/src-details/index.md +0 -86
  123. package/wiki/references/src-details/inversion.md +0 -340
@@ -1,1277 +0,0 @@
1
- # Static Asset Component
2
-
3
- The Static Asset Component provides a flexible, extensible file management system with support for multiple storage backends through a unified interface.
4
-
5
- ## Overview
6
-
7
- | Feature | Description |
8
- |---------|-------------|
9
- | **Component** | `StaticAssetComponent` |
10
- | **Architecture** | Factory-based controller generation with unified storage interface |
11
- | **Storage Types** | `DiskHelper` (local filesystem), `MinioHelper` (S3-compatible) |
12
- | **Extensibility** | Easy to add new storage backends (S3, Azure Blob, Google Cloud Storage) |
13
- | **Dependencies** | Node.js `fs`, `path`, `stream`; MinIO client (optional) |
14
-
15
- ## Key Features
16
-
17
- ✅ **Unified Storage Interface** - Single API for all storage types
18
- ✅ **Multiple Storage Instances** - Configure multiple storage backends simultaneously
19
- ✅ **Factory Pattern** - Dynamic controller generation
20
- ✅ **Built-in Security** - Comprehensive name validation, path traversal protection
21
- ✅ **Type-Safe** - Full TypeScript support with strict interfaces
22
- ✅ **Flexible Configuration** - Environment-based, production-ready setup
23
- ✅ **Database Tracking (MetaLink)** - Optional database-backed file tracking with metadata
24
-
25
- ---
26
-
27
- ## Architecture
28
-
29
- ### Storage Helper Hierarchy
30
-
31
- ```typescript
32
- IStorageHelper (interface)
33
-
34
- BaseStorageHelper (abstract class)
35
-
36
- ├── DiskHelper (local filesystem)
37
- └── MinioHelper (S3-compatible)
38
- ```
39
-
40
- ### Component Flow
41
-
42
- ```
43
- Application Configuration
44
-
45
- StaticAssetComponent
46
-
47
- AssetControllerFactory
48
-
49
- Dynamic Controller(s) ← uses → IStorageHelper
50
- ```
51
-
52
- ---
53
-
54
- ## Installation & Setup
55
-
56
- ### Complete Setup Example
57
-
58
- Here's a real-world example from the Vert application showing how to configure storage backends:
59
-
60
- ```typescript
61
- import {
62
- applicationEnvironment,
63
- BaseApplication,
64
- DiskHelper,
65
- int,
66
- MinioHelper,
67
- StaticAssetComponent,
68
- StaticAssetComponentBindingKeys,
69
- StaticAssetStorageTypes,
70
- TStaticAssetsComponentOptions,
71
- ValueOrPromise,
72
- } from '@venizia/ignis';
73
- import { EnvironmentKeys } from './common/environments';
74
-
75
- export class Application extends BaseApplication {
76
- configureComponents(): void {
77
- // Configure Static Asset Component
78
- this.bind<TStaticAssetsComponentOptions>({
79
- key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
80
- }).toValue({
81
- // MinIO storage for user uploads and media
82
- staticAsset: {
83
- controller: {
84
- name: 'AssetController',
85
- basePath: '/assets',
86
- isStrict: true,
87
- },
88
- storage: StaticAssetStorageTypes.MINIO,
89
- helper: new MinioHelper({
90
- endPoint: applicationEnvironment.get(EnvironmentKeys.APP_ENV_MINIO_HOST),
91
- port: int(applicationEnvironment.get(EnvironmentKeys.APP_ENV_MINIO_API_PORT)),
92
- accessKey: applicationEnvironment.get(EnvironmentKeys.APP_ENV_MINIO_ACCESS_KEY),
93
- secretKey: applicationEnvironment.get(EnvironmentKeys.APP_ENV_MINIO_SECRET_KEY),
94
- useSSL: false,
95
- }),
96
- extra: {
97
- parseMultipartBody: {
98
- storage: 'memory',
99
- },
100
- },
101
- },
102
- // Local disk storage for temporary files and cache
103
- staticResource: {
104
- controller: {
105
- name: 'ResourceController',
106
- basePath: '/resources',
107
- isStrict: true,
108
- },
109
- storage: StaticAssetStorageTypes.DISK,
110
- helper: new DiskHelper({
111
- basePath: './app_data/resources',
112
- }),
113
- extra: {
114
- parseMultipartBody: {
115
- storage: 'memory',
116
- },
117
- },
118
- },
119
- });
120
-
121
- // Register the component
122
- this.component(StaticAssetComponent);
123
- }
124
-
125
- preConfigure() {
126
- this.configureComponents();
127
- }
128
- }
129
- ```
130
-
131
- **Key Configuration Elements:**
132
- - Each storage backend gets a unique key (`staticAsset`, `staticResource`)
133
- - Each backend has its own controller configuration (name, basePath)
134
- - Storage type is explicitly set using `StaticAssetStorageTypes`
135
- - Helper instances are created with environment variables
136
- - Extra options configure multipart body parsing
137
-
138
- ### Environment Variables
139
-
140
- Add these to your `.env` file:
141
-
142
- ```bash
143
- # MinIO Configuration
144
- APP_ENV_MINIO_HOST=localhost
145
- APP_ENV_MINIO_API_PORT=9000
146
- APP_ENV_MINIO_ACCESS_KEY=minioadmin
147
- APP_ENV_MINIO_SECRET_KEY=minioadmin
148
- ```
149
-
150
- ### Environment Keys Configuration
151
-
152
- Define the environment keys in your application:
153
-
154
- ```typescript
155
- // src/common/environments.ts
156
- import { EnvironmentKeys as BaseEnv } from '@venizia/ignis';
157
-
158
- export class EnvironmentKeys extends BaseEnv {
159
- // MinIO Configuration Keys
160
- static readonly APP_ENV_MINIO_HOST = 'APP_ENV_MINIO_HOST';
161
- static readonly APP_ENV_MINIO_API_PORT = 'APP_ENV_MINIO_API_PORT';
162
- static readonly APP_ENV_MINIO_ACCESS_KEY = 'APP_ENV_MINIO_ACCESS_KEY';
163
- static readonly APP_ENV_MINIO_SECRET_KEY = 'APP_ENV_MINIO_SECRET_KEY';
164
- }
165
- ```
166
-
167
- ### Configuration Options
168
-
169
- #### `TStaticAssetsComponentOptions`
170
-
171
- ```typescript
172
- type TStaticAssetsComponentOptions = {
173
- [key: string]: {
174
- // Controller configuration
175
- controller: {
176
- name: string; // Controller class name
177
- basePath: string; // Base URL path (e.g., '/assets')
178
- isStrict?: boolean; // Strict routing mode (default: true)
179
- };
180
-
181
- // Storage configuration
182
- storage: 'disk' | 'minio'; // Storage type
183
- helper: IStorageHelper; // Storage helper instance
184
-
185
- // Extra options
186
- extra?: {
187
- parseMultipartBody?: {
188
- storage?: 'memory' | 'disk';
189
- uploadDir?: string;
190
- };
191
- normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
192
- normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
193
- };
194
- } & (
195
- // MetaLink configuration (optional)
196
- | { useMetaLink?: false }
197
- | { useMetaLink: true; metaLink: TMetaLinkConfig }
198
- );
199
- };
200
-
201
- type TMetaLinkConfig<Schema extends TMetaLinkSchema = TMetaLinkSchema> = {
202
- model: typeof BaseEntity<Schema>; // MetaLink model class
203
- repository: DefaultCRUDRepository<Schema>; // MetaLink repository instance
204
- };
205
- ```
206
-
207
- ### Quick Start Options
208
-
209
- **Option 1: MinIO Only**
210
- ```typescript
211
- this.bind({
212
- key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
213
- }).toValue({
214
- cloudStorage: {
215
- controller: { name: 'CloudController', basePath: '/cloud' },
216
- storage: StaticAssetStorageTypes.MINIO,
217
- helper: new MinioHelper({ /* ... */ }),
218
- extra: { parseMultipartBody: { storage: 'memory' } },
219
- },
220
- });
221
- this.component(StaticAssetComponent);
222
- ```
223
-
224
- **Option 2: Local Disk Only**
225
- ```typescript
226
- this.bind({
227
- key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
228
- }).toValue({
229
- localStorage: {
230
- controller: { name: 'LocalController', basePath: '/files' },
231
- storage: StaticAssetStorageTypes.DISK,
232
- helper: new DiskHelper({ basePath: './uploads' }),
233
- extra: { parseMultipartBody: { storage: 'disk' } },
234
- },
235
- });
236
- this.component(StaticAssetComponent);
237
- ```
238
-
239
- **Option 3: Multiple Storage Backends (Recommended)**
240
- ```typescript
241
- // Use different storage types for different purposes
242
- this.bind({
243
- key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
244
- }).toValue({
245
- userUploads: {
246
- controller: { name: 'UploadsController', basePath: '/uploads' },
247
- storage: StaticAssetStorageTypes.MINIO,
248
- helper: new MinioHelper({ /* ... */ }),
249
- extra: {},
250
- },
251
- tempFiles: {
252
- controller: { name: 'TempController', basePath: '/temp' },
253
- storage: StaticAssetStorageTypes.DISK,
254
- helper: new DiskHelper({ basePath: './temp' }),
255
- extra: {},
256
- },
257
- publicAssets: {
258
- controller: { name: 'PublicController', basePath: '/public' },
259
- storage: StaticAssetStorageTypes.DISK,
260
- helper: new DiskHelper({ basePath: './public' }),
261
- extra: {},
262
- },
263
- });
264
- this.component(StaticAssetComponent);
265
- ```
266
-
267
-
268
- ## MetaLink: Database File Tracking
269
-
270
- MetaLink is an optional feature that tracks uploaded files in a database, enabling advanced file management, querying, and metadata storage.
271
-
272
- ### What is MetaLink?
273
-
274
- MetaLink creates a database record for every uploaded file, storing:
275
- - File location (bucket, object name, access link)
276
- - File metadata (mimetype, size, etag)
277
- - Storage type (disk or minio)
278
- - Timestamps (created, modified)
279
- - Custom metadata (JSONB field)
280
-
281
- ### Benefits
282
-
283
- ✅ **Query uploaded files** - Find files by bucket, name, mimetype, etc.
284
- ✅ **Track file history** - Know when files were uploaded
285
- ✅ **Store metadata** - Keep custom information about files
286
- ✅ **Database integration** - Associate files with other entities
287
- ✅ **Audit trail** - Track what was uploaded and when
288
- ✅ **Graceful errors** - Upload succeeds even if MetaLink creation fails
289
-
290
- ### Database Schema
291
-
292
- **Table:** `MetaLink`
293
-
294
- ```sql
295
- CREATE TABLE "MetaLink" (
296
- id TEXT PRIMARY KEY,
297
- created_at TIMESTAMP NOT NULL DEFAULT NOW(),
298
- modified_at TIMESTAMP NOT NULL DEFAULT NOW(),
299
- bucket_name TEXT NOT NULL,
300
- object_name TEXT NOT NULL,
301
- link TEXT NOT NULL,
302
- mimetype TEXT NOT NULL,
303
- size INTEGER NOT NULL,
304
- etag TEXT,
305
- metadata JSONB,
306
- storage_type TEXT NOT NULL,
307
- is_synced BOOLEAN NOT NULL DEFAULT false
308
- );
309
-
310
- CREATE INDEX "IDX_MetaLink_bucketName" ON "MetaLink"(bucket_name);
311
- CREATE INDEX "IDX_MetaLink_objectName" ON "MetaLink"(object_name);
312
- CREATE INDEX "IDX_MetaLink_storageType" ON "MetaLink"(storage_type);
313
- CREATE INDEX "IDX_MetaLink_isSynced" ON "MetaLink"(is_synced);
314
- ```
315
-
316
- **Schema Fields:**
317
-
318
- | Field | Type | Description |
319
- |-------|------|-------------|
320
- | `id` | TEXT | Primary key (UUID) |
321
- | `created_at` | TIMESTAMP | When record was created |
322
- | `modified_at` | TIMESTAMP | When record was last updated |
323
- | `bucket_name` | TEXT | Storage bucket name |
324
- | `object_name` | TEXT | File object name |
325
- | `link` | TEXT | Access URL to the file |
326
- | `mimetype` | TEXT | File MIME type |
327
- | `size` | INTEGER | File size in bytes |
328
- | `etag` | TEXT | Entity tag for versioning |
329
- | `metadata` | JSONB | Additional file metadata |
330
- | `storage_type` | TEXT | Storage type ('disk' or 'minio') |
331
- | `is_synced` | BOOLEAN | Whether MetaLink is synchronized with storage (default: false) |
332
-
333
- ### Setup
334
-
335
- #### Step 1: Create Model
336
-
337
- ```typescript
338
- import { BaseMetaLinkModel } from '@venizia/ignis';
339
- import { model } from '@venizia/ignis';
340
-
341
- @model({ type: 'entity' })
342
- export class FileMetaLinkModel extends BaseMetaLinkModel {
343
- // Inherits all fields from BaseMetaLinkModel
344
- }
345
- ```
346
-
347
- #### Step 2: Create Repository
348
-
349
- ```typescript
350
- import { BaseMetaLinkRepository } from '@venizia/ignis';
351
- import { repository, inject } from '@venizia/ignis';
352
- import { IDataSource } from '@venizia/ignis';
353
-
354
- @repository({})
355
- export class FileMetaLinkRepository extends BaseMetaLinkRepository {
356
- constructor(@inject({ key: 'datasources.postgres' }) dataSource: IDataSource) {
357
- super({
358
- entityClass: FileMetaLinkModel,
359
- relations: {},
360
- dataSource,
361
- });
362
- }
363
- }
364
- ```
365
-
366
- #### Step 3: Create Database Table
367
-
368
- The model has `skipMigrate: true`, so you need to create the table manually:
369
-
370
- ```sql
371
- -- Run this in your database
372
- CREATE TABLE "MetaLink" (
373
- id TEXT PRIMARY KEY,
374
- created_at TIMESTAMP NOT NULL DEFAULT NOW(),
375
- modified_at TIMESTAMP NOT NULL DEFAULT NOW(),
376
- bucket_name TEXT NOT NULL,
377
- object_name TEXT NOT NULL,
378
- link TEXT NOT NULL,
379
- mimetype TEXT NOT NULL,
380
- size INTEGER NOT NULL,
381
- etag TEXT,
382
- metadata JSONB,
383
- storage_type TEXT NOT NULL,
384
- is_synced BOOLEAN NOT NULL DEFAULT false
385
- );
386
-
387
- CREATE INDEX "IDX_MetaLink_bucketName" ON "MetaLink"(bucket_name);
388
- CREATE INDEX "IDX_MetaLink_objectName" ON "MetaLink"(object_name);
389
- CREATE INDEX "IDX_MetaLink_storageType" ON "MetaLink"(storage_type);
390
- CREATE INDEX "IDX_MetaLink_isSynced" ON "MetaLink"(is_synced);
391
- ```
392
-
393
- #### Step 4: Configure Component
394
-
395
- ```typescript
396
- import { FileMetaLinkModel, FileMetaLinkRepository } from './your-models';
397
-
398
- export class Application extends BaseApplication {
399
- configureComponents(): void {
400
- // Register repository
401
- this.repository(FileMetaLinkRepository);
402
-
403
- // Configure Static Asset Component with MetaLink
404
- this.bind<TStaticAssetsComponentOptions>({
405
- key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
406
- }).toValue({
407
- uploads: {
408
- controller: {
409
- name: 'UploadController',
410
- basePath: '/uploads',
411
- isStrict: true,
412
- },
413
- storage: StaticAssetStorageTypes.MINIO,
414
- helper: new MinioHelper({ /* ... */ }),
415
- useMetaLink: true,
416
- metaLink: {
417
- model: FileMetaLinkModel,
418
- repository: this.getSync(FileMetaLinkRepository),
419
- },
420
- extra: {
421
- parseMultipartBody: { storage: 'memory' },
422
- },
423
- },
424
- });
425
-
426
- this.component(StaticAssetComponent);
427
- }
428
- }
429
- ```
430
-
431
- ### API Response with MetaLink
432
-
433
- When `useMetaLink: true`, upload responses include the database record:
434
-
435
- ```json
436
- [
437
- {
438
- "bucketName": "user-uploads",
439
- "objectName": "document.pdf",
440
- "link": "/uploads/buckets/user-uploads/objects/document.pdf",
441
- "metaLink": {
442
- "id": "550e8400-e29b-41d4-a716-446655440000",
443
- "bucketName": "user-uploads",
444
- "objectName": "document.pdf",
445
- "link": "/uploads/buckets/user-uploads/objects/document.pdf",
446
- "mimetype": "application/pdf",
447
- "size": 1048576,
448
- "etag": "abc123def456",
449
- "metadata": {
450
- "originalName": "My Document.pdf",
451
- "uploadedBy": "user123"
452
- },
453
- "storageType": "minio",
454
- "isSynced": true,
455
- "createdAt": "2025-12-15T03:00:00.000Z",
456
- "modifiedAt": "2025-12-15T03:00:00.000Z"
457
- }
458
- }
459
- ]
460
- ```
461
-
462
- **Note:** The `isSynced` field is automatically set to `true` when files are uploaded, indicating the MetaLink is synchronized with the actual file in storage.
463
-
464
- ### Error Handling
465
-
466
- If MetaLink creation fails, the upload still succeeds:
467
-
468
- ```json
469
- [
470
- {
471
- "bucketName": "user-uploads",
472
- "objectName": "document.pdf",
473
- "link": "/uploads/buckets/user-uploads/objects/document.pdf",
474
- "metaLink": null,
475
- "metaLinkError": "Database connection error"
476
- }
477
- ]
478
- ```
479
-
480
- ### Querying MetaLinks
481
-
482
- ```typescript
483
- // Get all files in a bucket
484
- const files = await fileMetaLinkRepository.find({
485
- where: { bucketName: 'user-uploads' },
486
- });
487
-
488
- // Get files by mimetype
489
- const pdfs = await fileMetaLinkRepository.find({
490
- where: { mimetype: 'application/pdf' },
491
- });
492
-
493
- // Get files by storage type
494
- const minioFiles = await fileMetaLinkRepository.find({
495
- where: { storageType: 'minio' },
496
- });
497
-
498
- // Get synced files only
499
- const syncedFiles = await fileMetaLinkRepository.find({
500
- where: { isSynced: true },
501
- });
502
-
503
- // Get unsynced files (for manual sync operations)
504
- const unsyncedFiles = await fileMetaLinkRepository.find({
505
- where: { isSynced: false },
506
- });
507
-
508
- // Count synced files
509
- const syncedCount = await fileMetaLinkRepository.count({
510
- where: { isSynced: true },
511
- });
512
-
513
- // Get recent uploads
514
- const recent = await fileMetaLinkRepository.find({
515
- orderBy: { createdAt: 'desc' },
516
- limit: 10,
517
- });
518
- ```
519
-
520
- ### Automatic Cleanup
521
-
522
- When you delete a file, MetaLink records are automatically deleted:
523
-
524
- ```http
525
- DELETE /uploads/buckets/user-uploads/objects/document.pdf
526
- ```
527
-
528
- - Deletes file from storage
529
- - Deletes MetaLink record from database
530
- - Returns `{ "success": true }`
531
-
532
-
533
- ## Storage Helpers
534
-
535
- ### DiskHelper (Local Filesystem)
536
-
537
- Stores files on the local filesystem using a bucket-based directory structure.
538
-
539
- #### Constructor
540
-
541
- ```typescript
542
- new DiskHelper({
543
- basePath: string; // Base directory for storage
544
- scope?: string; // Logger scope
545
- identifier?: string; // Helper identifier
546
- })
547
- ```
548
-
549
- #### Example
550
-
551
- ```typescript
552
- const diskHelper = new DiskHelper({
553
- basePath: './app_data/storage',
554
- });
555
- ```
556
-
557
- **Directory Structure:**
558
- ```
559
- app_data/storage/
560
- ├── bucket-1/
561
- │ ├── file1.pdf
562
- │ └── file2.jpg
563
- ├── bucket-2/
564
- │ └── document.docx
565
- ```
566
-
567
- #### Features
568
-
569
- - Automatic directory creation
570
- - Built-in path validation
571
- - Metadata stored in file stats
572
- - Stream-based file operations
573
-
574
-
575
- ### MinioHelper (S3-Compatible Storage)
576
-
577
- Connects to MinIO or any S3-compatible object storage.
578
-
579
- #### Constructor
580
-
581
- ```typescript
582
- new MinioHelper({
583
- endPoint: string; // MinIO server hostname
584
- port: number; // API port (default: 9000)
585
- useSSL: boolean; // Use HTTPS
586
- accessKey: string; // Access key
587
- secretKey: string; // Secret key
588
- })
589
- ```
590
-
591
- #### Example
592
-
593
- ```typescript
594
- const minioHelper = new MinioHelper({
595
- endPoint: 'minio.example.com',
596
- port: 9000,
597
- useSSL: true,
598
- accessKey: process.env.MINIO_ACCESS_KEY,
599
- secretKey: process.env.MINIO_SECRET_KEY,
600
- });
601
- ```
602
-
603
-
604
- ## IStorageHelper Interface
605
-
606
- All storage helpers implement this unified interface:
607
-
608
- ```typescript
609
- interface IStorageHelper {
610
- // Name validation
611
- isValidName(name: string): boolean;
612
-
613
- // Bucket operations
614
- isBucketExists(opts: { name: string }): Promise<boolean>;
615
- getBuckets(): Promise<IBucketInfo[]>;
616
- getBucket(opts: { name: string }): Promise<IBucketInfo | null>;
617
- createBucket(opts: { name: string }): Promise<IBucketInfo | null>;
618
- removeBucket(opts: { name: string }): Promise<boolean>;
619
-
620
- // File operations
621
- upload(opts: {
622
- bucket: string;
623
- files: IUploadFile[];
624
- normalizeNameFn?: (opts: { originalName: string }) => string;
625
- normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
626
- }): Promise<IUploadResult[]>;
627
-
628
- getFile(opts: { bucket: string; name: string; options?: any }): Promise<Readable>;
629
- getStat(opts: { bucket: string; name: string }): Promise<IFileStat>;
630
- removeObject(opts: { bucket: string; name: string }): Promise<void>;
631
- removeObjects(opts: { bucket: string; names: string[] }): Promise<void>;
632
- listObjects(opts: IListObjectsOptions): Promise<IObjectInfo[]>;
633
-
634
- // Utility
635
- getFileType(opts: { mimeType: string }): string;
636
- }
637
- ```
638
-
639
-
640
- ## API Endpoints
641
-
642
- The component dynamically generates REST endpoints for each configured storage backend.
643
-
644
- ### Common Endpoints
645
-
646
- All storage backends expose the same API structure:
647
-
648
- #### **Get All Buckets**
649
-
650
- ```http
651
- GET /{basePath}/buckets
652
- ```
653
-
654
- **Response:**
655
- ```json
656
- [
657
- { "name": "my-bucket", "creationDate": "2025-01-01T00:00:00.000Z" }
658
- ]
659
- ```
660
-
661
-
662
- #### **Get Bucket by Name**
663
-
664
- ```http
665
- GET /{basePath}/buckets/:bucketName
666
- ```
667
-
668
- **Parameters:**
669
- - `bucketName` (path): Bucket name
670
-
671
- **Validation:**
672
- - ✅ Bucket name validated with `isValidName()`
673
- - ❌ Returns 400 if invalid
674
-
675
- **Response:**
676
- ```json
677
- { "name": "my-bucket", "creationDate": "2025-01-01T00:00:00.000Z" }
678
- ```
679
-
680
-
681
- #### **Create Bucket**
682
-
683
- ```http
684
- POST /{basePath}/buckets/:bucketName
685
- ```
686
-
687
- **Parameters:**
688
- - `bucketName` (path): Name of the new bucket
689
-
690
- **Response:**
691
- ```json
692
- { "name": "my-bucket", "creationDate": "2025-12-13T00:00:00.000Z" }
693
- ```
694
-
695
-
696
- #### **Delete Bucket**
697
-
698
- ```http
699
- DELETE /{basePath}/buckets/:bucketName
700
- ```
701
-
702
- **Parameters:**
703
- - `bucketName` (path): Bucket to delete
704
-
705
- **Response:**
706
- ```json
707
- { "success": true }
708
- ```
709
-
710
-
711
- #### **Upload Files**
712
-
713
- ```http
714
- POST /{basePath}/buckets/:bucketName/upload
715
- ```
716
-
717
- **Request Body:**
718
- - `multipart/form-data` with file fields
719
- - Each file can optionally include `folderPath` for organization
720
-
721
- **Response (without MetaLink):**
722
- ```json
723
- [
724
- {
725
- "bucketName": "my-bucket",
726
- "objectName": "file.pdf",
727
- "link": "/assets/buckets/my-bucket/objects/file.pdf"
728
- }
729
- ]
730
- ```
731
-
732
- **Response (with MetaLink enabled):**
733
- ```json
734
- [
735
- {
736
- "bucketName": "my-bucket",
737
- "objectName": "file.pdf",
738
- "link": "/assets/buckets/my-bucket/objects/file.pdf",
739
- "metaLink": {
740
- "id": "uuid",
741
- "bucketName": "my-bucket",
742
- "objectName": "file.pdf",
743
- "link": "/assets/buckets/my-bucket/objects/file.pdf",
744
- "mimetype": "application/pdf",
745
- "size": 1024,
746
- "etag": "abc123",
747
- "metadata": {},
748
- "storageType": "minio",
749
- "isSynced": true,
750
- "createdAt": "2025-12-15T03:00:00.000Z",
751
- "modifiedAt": "2025-12-15T03:00:00.000Z"
752
- }
753
- }
754
- ]
755
- ```
756
-
757
- **Example:**
758
- ```typescript
759
- const formData = new FormData();
760
- formData.append('file', fileBlob, 'document.pdf');
761
-
762
- const response = await fetch('/assets/buckets/uploads/upload', {
763
- method: 'POST',
764
- body: formData,
765
- });
766
-
767
- const result = await response.json();
768
- console.log(result[0].metaLink); // Database record (if MetaLink enabled)
769
- ```
770
-
771
-
772
- #### **Get Object (Stream)**
773
-
774
- ```http
775
- GET /{basePath}/buckets/:bucketName/objects/:objectName
776
- ```
777
-
778
- **Parameters:**
779
- - `bucketName` (path): Bucket name
780
- - `objectName` (path): Object name (URL-encoded)
781
-
782
- **Validation:**
783
- - ✅ Both bucket and object names validated
784
- - ❌ Returns 400 if either is invalid
785
-
786
- **Response:**
787
- - Streams file content with appropriate headers
788
- - Content-Type: From metadata or `application/octet-stream`
789
- - Content-Length: File size in bytes
790
- - X-Content-Type-Options: `nosniff`
791
-
792
-
793
- #### **Download Object**
794
-
795
- ```http
796
- GET /{basePath}/buckets/:bucketName/objects/:objectName/download
797
- ```
798
-
799
- **Parameters:**
800
- - `bucketName` (path): Bucket name
801
- - `objectName` (path): Object name (URL-encoded)
802
-
803
- **Response:**
804
- - Streams file with download headers
805
- - Content-Disposition: `attachment; filename="..."`
806
- - Triggers browser download dialog
807
-
808
- **Example:**
809
- ```typescript
810
- const downloadUrl = `/assets/buckets/uploads/objects/${encodeURIComponent('document.pdf')}/download`;
811
- window.open(downloadUrl, '_blank');
812
- ```
813
-
814
-
815
- #### **Delete Object**
816
-
817
- ```http
818
- DELETE /{basePath}/buckets/:bucketName/objects/:objectName
819
- ```
820
-
821
- **Parameters:**
822
- - `bucketName` (path): Bucket name
823
- - `objectName` (path): Object to delete
824
-
825
- **Response:**
826
- ```json
827
- { "success": true }
828
- ```
829
-
830
-
831
- #### **List Objects**
832
-
833
- ```http
834
- GET /{basePath}/buckets/:bucketName/objects
835
- ```
836
-
837
- **Query Parameters:**
838
- - `prefix` (optional): Filter by prefix
839
- - `recursive` (optional, boolean): Recursive listing
840
- - `maxKeys` (optional, number): Maximum objects to return
841
-
842
- **Response:**
843
- ```json
844
- [
845
- {
846
- "name": "file1.pdf",
847
- "size": 1024,
848
- "lastModified": "2025-12-13T00:00:00.000Z",
849
- "etag": "abc123"
850
- }
851
- ]
852
- ```
853
-
854
-
855
- #### **Delete Object**
856
-
857
- ```http
858
- DELETE /{basePath}/buckets/:bucketName/objects/:objectName
859
- ```
860
-
861
- **Parameters:**
862
- - `bucketName` (path): Bucket name
863
- - `objectName` (path): Object to delete (URL-encoded)
864
-
865
- **Validation:**
866
- - ✅ Both bucket and object names validated
867
- - ❌ Returns 400 if either is invalid
868
-
869
- **Behavior:**
870
- - Deletes file from storage
871
- - If MetaLink enabled, also deletes database record
872
- - MetaLink deletion errors are logged but don't fail the request
873
-
874
- **Response:**
875
- ```json
876
- {
877
- "success": true
878
- }
879
- ```
880
-
881
- **Example:**
882
- ```typescript
883
- const bucketName = 'user-uploads';
884
- const objectName = 'document.pdf';
885
-
886
- await fetch(`/assets/buckets/${bucketName}/objects/${encodeURIComponent(objectName)}`, {
887
- method: 'DELETE',
888
- });
889
-
890
- // File deleted from storage
891
- // MetaLink record deleted from database (if enabled)
892
- ```
893
-
894
-
895
- #### **Sync MetaLink** (MetaLink only)
896
-
897
- ```http
898
- PUT /{basePath}/buckets/:bucketName/objects/:objectName/meta-links
899
- ```
900
-
901
- **Availability:** Only available when `useMetaLink: true`
902
-
903
- **Parameters:**
904
- - `bucketName` (path): Bucket name
905
- - `objectName` (path): Object name (URL-encoded)
906
-
907
- **Validation:**
908
- - ✅ Both bucket and object names validated
909
- - ❌ Returns 400 if either is invalid
910
-
911
- **Behavior:**
912
- - Fetches current file metadata from storage
913
- - If MetaLink exists: Updates with latest metadata
914
- - If MetaLink doesn't exist: Creates new MetaLink record
915
- - Sets `isSynced: true` to mark as synchronized
916
-
917
- **Use Cases:**
918
- - Manually sync files that exist in storage but not in database
919
- - Update MetaLink metadata after file changes
920
- - Rebuild MetaLink records after database restore
921
- - Bulk synchronization operations
922
-
923
- **Response (MetaLink created):**
924
- ```json
925
- {
926
- "id": "uuid",
927
- "bucketName": "user-uploads",
928
- "objectName": "document.pdf",
929
- "link": "/assets/buckets/user-uploads/objects/document.pdf",
930
- "mimetype": "application/pdf",
931
- "size": 1048576,
932
- "etag": "abc123",
933
- "metadata": {},
934
- "storageType": "minio",
935
- "isSynced": true,
936
- "createdAt": "2025-12-15T03:00:00.000Z",
937
- "modifiedAt": "2025-12-15T03:00:00.000Z"
938
- }
939
- ```
940
-
941
- **Response (MetaLink updated):**
942
- ```json
943
- {
944
- "id": "existing-uuid",
945
- "bucketName": "user-uploads",
946
- "objectName": "document.pdf",
947
- "link": "/assets/buckets/user-uploads/objects/document.pdf",
948
- "mimetype": "application/pdf",
949
- "size": 1048576,
950
- "etag": "abc123updated",
951
- "metadata": {},
952
- "storageType": "minio",
953
- "isSynced": true,
954
- "createdAt": "2025-12-15T02:00:00.000Z",
955
- "modifiedAt": "2025-12-15T03:00:00.000Z"
956
- }
957
- ```
958
-
959
- **Example:**
960
- ```typescript
961
- // Sync a single file
962
- const bucketName = 'user-uploads';
963
- const objectName = 'document.pdf';
964
-
965
- const response = await fetch(
966
- `/assets/buckets/${bucketName}/objects/${encodeURIComponent(objectName)}/meta-links`,
967
- { method: 'PUT' }
968
- );
969
-
970
- const metaLink = await response.json();
971
- console.log('Synced:', metaLink.isSynced); // true
972
-
973
- // Bulk sync example: sync all files in storage
974
- const objects = await fetch(`/assets/buckets/${bucketName}/objects`).then(r => r.json());
975
-
976
- for (const obj of objects) {
977
- await fetch(
978
- `/assets/buckets/${bucketName}/objects/${encodeURIComponent(obj.name)}/meta-links`,
979
- { method: 'PUT' }
980
- );
981
- }
982
- ```
983
-
984
-
985
- ## Security Features
986
-
987
- ### Built-in Name Validation
988
-
989
- All storage helpers implement comprehensive name validation:
990
-
991
- ```typescript
992
- isValidName(name: string): boolean {
993
- // ❌ Prevents path traversal
994
- if (name.includes('..') || name.includes('/') || name.includes('\\'))
995
- return false;
996
-
997
- // ❌ Prevents hidden files
998
- if (name.startsWith('.')) return false;
999
-
1000
- // ❌ Prevents shell injection
1001
- const dangerousChars = /[;|&$`<>(){}[\]!#]/;
1002
- if (dangerousChars.test(name)) return false;
1003
-
1004
- // ❌ Prevents header injection
1005
- if (name.includes('\n') || name.includes('\r') || name.includes('\0'))
1006
- return false;
1007
-
1008
- // ❌ Prevents DoS (long names)
1009
- if (name.length > 255) return false;
1010
-
1011
- // ❌ Prevents empty names
1012
- if (name.trim().length === 0) return false;
1013
-
1014
- return true;
1015
- }
1016
- ```
1017
-
1018
- **Blocked Patterns:**
1019
- ```
1020
- ../etc/passwd ❌ Path traversal
1021
- .hidden ❌ Hidden file
1022
- file;rm -rf / ❌ Shell injection
1023
- file\ninjected ❌ Header injection
1024
- very_long_name... ❌ > 255 characters
1025
- ```
1026
-
1027
- ### HTTP Security Headers
1028
-
1029
- All responses include security headers:
1030
-
1031
- ```http
1032
- X-Content-Type-Options: nosniff
1033
- Content-Disposition: attachment; filename="..."
1034
- ```
1035
-
1036
-
1037
- ## Usage Examples
1038
-
1039
- ### Example 1: Multiple Storage Backends
1040
-
1041
- ```typescript
1042
- this.bind<TStaticAssetsComponentOptions>({
1043
- key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
1044
- }).toValue({
1045
- // User uploads → MinIO
1046
- uploads: {
1047
- controller: { name: 'UploadController', basePath: '/uploads' },
1048
- storage: StaticAssetStorageTypes.MINIO,
1049
- helper: new MinioHelper({ /* ... */ }),
1050
- extra: { parseMultipartBody: { storage: 'memory' } },
1051
- },
1052
-
1053
- // Temporary files → Local disk
1054
- temp: {
1055
- controller: { name: 'TempController', basePath: '/temp' },
1056
- storage: StaticAssetStorageTypes.DISK,
1057
- helper: new DiskHelper({ basePath: './temp' }),
1058
- extra: { parseMultipartBody: { storage: 'disk' } },
1059
- },
1060
-
1061
- // Public assets → Local disk
1062
- public: {
1063
- controller: { name: 'PublicController', basePath: '/public' },
1064
- storage: StaticAssetStorageTypes.DISK,
1065
- helper: new DiskHelper({ basePath: './public' }),
1066
- extra: { parseMultipartBody: { storage: 'memory' } },
1067
- },
1068
- });
1069
- ```
1070
-
1071
- **Result:** 3 independent storage systems with different endpoints:
1072
- - `/uploads/buckets/...`
1073
- - `/temp/buckets/...`
1074
- - `/public/buckets/...`
1075
-
1076
- ### Example 2: Frontend Integration
1077
-
1078
- ```typescript
1079
- // Upload file to MinIO
1080
- async function uploadFile(file: File) {
1081
- const formData = new FormData();
1082
- formData.append('file', file);
1083
-
1084
- const response = await fetch('/assets/buckets/user-uploads/upload', {
1085
- method: 'POST',
1086
- body: formData,
1087
- });
1088
-
1089
- const result = await response.json();
1090
- return result[0].link;
1091
- }
1092
-
1093
- // Download file
1094
- function downloadFile(bucketName: string, objectName: string) {
1095
- const url = `/assets/buckets/${bucketName}/objects/${encodeURIComponent(objectName)}/download`;
1096
- window.open(url, '_blank');
1097
- }
1098
-
1099
- // List files in bucket
1100
- async function listFiles(bucketName: string, prefix?: string) {
1101
- const url = new URL(`/assets/buckets/${bucketName}/objects`, window.location.origin);
1102
- if (prefix) url.searchParams.append('prefix', prefix);
1103
-
1104
- const response = await fetch(url);
1105
- return await response.json();
1106
- }
1107
- ```
1108
-
1109
- ### Example 3: Custom Filename Normalization
1110
-
1111
- ```typescript
1112
- {
1113
- uploads: {
1114
- controller: { name: 'UploadController', basePath: '/uploads' },
1115
- storage: StaticAssetStorageTypes.MINIO,
1116
- helper: new MinioHelper({ /* ... */ }),
1117
- extra: {
1118
- parseMultipartBody: { storage: 'memory' },
1119
- normalizeNameFn: ({ originalName }) => {
1120
- // Add timestamp prefix
1121
- return `${Date.now()}_${originalName.toLowerCase().replace(/\s/g, '_')}`;
1122
- },
1123
- normalizeLinkFn: ({ bucketName, normalizeName }) => {
1124
- // Custom link format
1125
- return `/api/files/${bucketName}/${encodeURIComponent(normalizeName)}`;
1126
- },
1127
- },
1128
- },
1129
- }
1130
- ```
1131
-
1132
-
1133
- ## Custom Storage Implementation
1134
-
1135
- You can implement your own storage backend by extending `BaseStorageHelper`:
1136
-
1137
- ```typescript
1138
- import { BaseStorageHelper, IUploadFile, IUploadResult } from '@venizia/ignis-helpers';
1139
-
1140
- class S3Helper extends BaseStorageHelper {
1141
- constructor(config: S3Config) {
1142
- super({ scope: 'S3Helper', identifier: 'S3Helper' });
1143
- // Initialize S3 client
1144
- }
1145
-
1146
- async isBucketExists(opts: { name: string }): Promise<boolean> {
1147
- // Implementation
1148
- }
1149
-
1150
- async upload(opts: {
1151
- bucket: string;
1152
- files: IUploadFile[];
1153
- }): Promise<IUploadResult[]> {
1154
- // Implementation
1155
- }
1156
-
1157
- // Implement other IStorageHelper methods...
1158
- }
1159
-
1160
- // Usage
1161
- this.bind<TStaticAssetsComponentOptions>({
1162
- key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
1163
- }).toValue({
1164
- s3Storage: {
1165
- controller: { name: 'S3Controller', basePath: '/s3-assets' },
1166
- storage: 'custom-s3',
1167
- helper: new S3Helper({ /* ... */ }),
1168
- extra: {},
1169
- },
1170
- });
1171
- ```
1172
-
1173
-
1174
- ## Troubleshooting
1175
-
1176
- ### Issue: "Invalid bucket/object name" errors
1177
-
1178
- **Cause:** Name fails `isValidName()` validation
1179
-
1180
- **Solution:** Ensure names:
1181
- - Don't contain `..`, `/`, `\`
1182
- - Don't start with `.`
1183
- - Don't contain shell special characters
1184
- - Are <= 255 characters
1185
- - Are not empty or whitespace-only
1186
-
1187
- ### Issue: Controller not registering
1188
-
1189
- **Cause:** Configuration key might be invalid or missing required fields
1190
-
1191
- **Solution:** Ensure each storage configuration has all required fields:
1192
- ```typescript
1193
- {
1194
- [uniqueKey]: {
1195
- controller: { name, basePath, isStrict },
1196
- storage: 'disk' | 'minio',
1197
- helper: IStorageHelper,
1198
- extra: {}
1199
- }
1200
- }
1201
- ```
1202
-
1203
- ### Issue: Files not uploading
1204
-
1205
- **DiskHelper:**
1206
- - Ensure `basePath` directory exists or can be created
1207
- - Check filesystem permissions
1208
-
1209
- **MinioHelper:**
1210
- - Verify MinIO server is running
1211
- - Check credentials (accessKey, secretKey)
1212
- - Verify network connectivity (endPoint, port)
1213
-
1214
- ### Issue: Large file uploads failing
1215
-
1216
- **Solution:** Switch to disk-based multipart parsing:
1217
-
1218
- ```typescript
1219
- extra: {
1220
- parseMultipartBody: {
1221
- storage: 'disk', // Use disk instead of memory
1222
- uploadDir: './uploads', // Temporary upload directory
1223
- },
1224
- }
1225
- ```
1226
-
1227
-
1228
- ## Docker Setup for Development
1229
-
1230
- ### Docker Compose for MinIO
1231
-
1232
- ```yaml
1233
- version: '3.8'
1234
- services:
1235
- minio:
1236
- image: minio/minio:latest
1237
- container_name: minio
1238
- ports:
1239
- - "9000:9000" # API port
1240
- - "9001:9001" # Console port
1241
- environment:
1242
- MINIO_ROOT_USER: minioadmin
1243
- MINIO_ROOT_PASSWORD: minioadmin
1244
- command: server /data --console-address ":9001"
1245
- volumes:
1246
- - minio_data:/data
1247
-
1248
- volumes:
1249
- minio_data:
1250
- ```
1251
-
1252
- **Start MinIO:**
1253
- ```bash
1254
- docker-compose up -d
1255
- ```
1256
-
1257
- **Access MinIO Console:**
1258
- ```
1259
- http://localhost:9001
1260
- ```
1261
-
1262
-
1263
- ## See Also
1264
-
1265
- - **Related Concepts:**
1266
- - [Components Overview](/guides/core-concepts/components) - Component system basics
1267
- - [Controllers](/guides/core-concepts/controllers) - File upload endpoints
1268
-
1269
- - **Other Components:**
1270
- - [Components Index](./index) - All built-in components
1271
-
1272
- - **References:**
1273
- - [Storage Helpers](/references/helpers/storage) - DiskHelper, MinIOHelper, BaseStorageHelper
1274
- - [Request Utilities](/references/utilities/request) - File upload utilities
1275
-
1276
- - **Best Practices:**
1277
- - [Security Guidelines](/best-practices/security-guidelines) - File upload security