@venizia/ignis-docs 0.0.5 → 0.0.6-0
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/package.json +1 -1
- package/wiki/best-practices/architecture-decisions.md +0 -8
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/guides/core-concepts/components-guide.md +1 -1
- package/wiki/guides/core-concepts/components.md +2 -2
- package/wiki/guides/core-concepts/dependency-injection.md +1 -1
- package/wiki/guides/core-concepts/services.md +1 -1
- package/wiki/guides/tutorials/building-a-crud-api.md +1 -1
- package/wiki/guides/tutorials/ecommerce-api.md +2 -2
- package/wiki/guides/tutorials/realtime-chat.md +6 -6
- package/wiki/guides/tutorials/testing.md +1 -1
- package/wiki/references/base/bootstrapping.md +0 -2
- package/wiki/references/base/components.md +2 -2
- package/wiki/references/base/controllers.md +0 -1
- package/wiki/references/base/datasources.md +1 -1
- package/wiki/references/base/dependency-injection.md +1 -1
- package/wiki/references/base/filter-system/quick-reference.md +0 -14
- package/wiki/references/base/middlewares.md +0 -8
- package/wiki/references/base/providers.md +0 -9
- package/wiki/references/base/services.md +0 -1
- package/wiki/references/components/authentication/api.md +444 -0
- package/wiki/references/components/authentication/errors.md +177 -0
- package/wiki/references/components/authentication/index.md +571 -0
- package/wiki/references/components/authentication/usage.md +781 -0
- package/wiki/references/components/health-check.md +292 -103
- package/wiki/references/components/index.md +14 -12
- package/wiki/references/components/mail/api.md +505 -0
- package/wiki/references/components/mail/errors.md +176 -0
- package/wiki/references/components/mail/index.md +535 -0
- package/wiki/references/components/mail/usage.md +404 -0
- package/wiki/references/components/request-tracker.md +229 -25
- package/wiki/references/components/socket-io/api.md +1051 -0
- package/wiki/references/components/socket-io/errors.md +119 -0
- package/wiki/references/components/socket-io/index.md +410 -0
- package/wiki/references/components/socket-io/usage.md +322 -0
- package/wiki/references/components/static-asset/api.md +261 -0
- package/wiki/references/components/static-asset/errors.md +89 -0
- package/wiki/references/components/static-asset/index.md +617 -0
- package/wiki/references/components/static-asset/usage.md +364 -0
- package/wiki/references/components/swagger.md +390 -110
- package/wiki/references/components/template/api-page.md +125 -0
- package/wiki/references/components/template/errors-page.md +100 -0
- package/wiki/references/components/template/index.md +104 -0
- package/wiki/references/components/template/setup-page.md +134 -0
- package/wiki/references/components/template/single-page.md +132 -0
- package/wiki/references/components/template/usage-page.md +127 -0
- package/wiki/references/components/websocket/api.md +508 -0
- package/wiki/references/components/websocket/errors.md +123 -0
- package/wiki/references/components/websocket/index.md +453 -0
- package/wiki/references/components/websocket/usage.md +475 -0
- package/wiki/references/helpers/cron/index.md +224 -0
- package/wiki/references/helpers/crypto/index.md +537 -0
- package/wiki/references/helpers/env/index.md +214 -0
- package/wiki/references/helpers/error/index.md +232 -0
- package/wiki/references/helpers/index.md +16 -15
- package/wiki/references/helpers/inversion/index.md +608 -0
- package/wiki/references/helpers/logger/index.md +600 -0
- package/wiki/references/helpers/network/api.md +986 -0
- package/wiki/references/helpers/network/index.md +620 -0
- package/wiki/references/helpers/queue/index.md +589 -0
- package/wiki/references/helpers/redis/index.md +495 -0
- package/wiki/references/helpers/socket-io/api.md +497 -0
- package/wiki/references/helpers/socket-io/index.md +513 -0
- package/wiki/references/helpers/storage/api.md +705 -0
- package/wiki/references/helpers/storage/index.md +583 -0
- package/wiki/references/helpers/template/index.md +66 -0
- package/wiki/references/helpers/template/single-page.md +126 -0
- package/wiki/references/helpers/testing/index.md +510 -0
- package/wiki/references/helpers/types/index.md +512 -0
- package/wiki/references/helpers/uid/index.md +272 -0
- package/wiki/references/helpers/websocket/api.md +736 -0
- package/wiki/references/helpers/websocket/index.md +574 -0
- package/wiki/references/helpers/worker-thread/index.md +470 -0
- package/wiki/references/quick-reference.md +3 -18
- package/wiki/references/utilities/jsx.md +1 -8
- package/wiki/references/utilities/statuses.md +0 -7
- package/wiki/references/components/authentication.md +0 -476
- package/wiki/references/components/mail.md +0 -687
- package/wiki/references/components/socket-io.md +0 -562
- package/wiki/references/components/static-asset.md +0 -1277
- package/wiki/references/helpers/cron.md +0 -108
- package/wiki/references/helpers/crypto.md +0 -132
- package/wiki/references/helpers/env.md +0 -83
- package/wiki/references/helpers/error.md +0 -97
- package/wiki/references/helpers/inversion.md +0 -176
- package/wiki/references/helpers/logger.md +0 -296
- package/wiki/references/helpers/network.md +0 -396
- package/wiki/references/helpers/queue.md +0 -150
- package/wiki/references/helpers/redis.md +0 -142
- package/wiki/references/helpers/socket-io.md +0 -932
- package/wiki/references/helpers/storage.md +0 -665
- package/wiki/references/helpers/testing.md +0 -133
- package/wiki/references/helpers/types.md +0 -167
- package/wiki/references/helpers/uid.md +0 -167
- package/wiki/references/helpers/worker-thread.md +0 -178
|
@@ -0,0 +1,617 @@
|
|
|
1
|
+
# Static Asset
|
|
2
|
+
|
|
3
|
+
> Flexible file management system with support for multiple storage backends (local disk, MinIO/S3-compatible) through a unified interface, featuring factory-based controller generation and optional database file tracking via MetaLink.
|
|
4
|
+
|
|
5
|
+
## Quick Reference
|
|
6
|
+
|
|
7
|
+
| Item | Value |
|
|
8
|
+
|------|-------|
|
|
9
|
+
| **Package** | `@venizia/ignis` |
|
|
10
|
+
| **Class** | `StaticAssetComponent` |
|
|
11
|
+
| **Helper** | [`DiskHelper`](/references/helpers/storage/), [`MinioHelper`](/references/helpers/storage/) |
|
|
12
|
+
| **Runtimes** | Both |
|
|
13
|
+
|
|
14
|
+
#### Import Paths
|
|
15
|
+
```typescript
|
|
16
|
+
import {
|
|
17
|
+
StaticAssetComponent,
|
|
18
|
+
StaticAssetComponentBindingKeys,
|
|
19
|
+
StaticAssetStorageTypes,
|
|
20
|
+
DiskHelper,
|
|
21
|
+
MinioHelper,
|
|
22
|
+
} from '@venizia/ignis';
|
|
23
|
+
import type {
|
|
24
|
+
TStaticAssetsComponentOptions,
|
|
25
|
+
TMetaLinkConfig,
|
|
26
|
+
TStaticAssetExtraOptions,
|
|
27
|
+
TStaticAssetStorageType,
|
|
28
|
+
} from '@venizia/ignis';
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Key Features
|
|
32
|
+
|
|
33
|
+
| Feature | Description |
|
|
34
|
+
|---------|-------------|
|
|
35
|
+
| **Unified Storage Interface** | Single API for all storage types |
|
|
36
|
+
| **Multiple Storage Instances** | Configure multiple storage backends simultaneously |
|
|
37
|
+
| **Factory Pattern** | Dynamic controller generation per storage backend |
|
|
38
|
+
| **Built-in Security** | Comprehensive name validation, path traversal protection, header sanitization |
|
|
39
|
+
| **Database Tracking (MetaLink)** | Optional database-backed file tracking with metadata, principal association, and sync status |
|
|
40
|
+
| **Flexible Configuration** | Environment-based, production-ready setup |
|
|
41
|
+
|
|
42
|
+
## Setup
|
|
43
|
+
|
|
44
|
+
### Step 1: Bind Configuration
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import {
|
|
48
|
+
BaseApplication,
|
|
49
|
+
DiskHelper,
|
|
50
|
+
MinioHelper,
|
|
51
|
+
StaticAssetComponentBindingKeys,
|
|
52
|
+
StaticAssetStorageTypes,
|
|
53
|
+
TStaticAssetsComponentOptions,
|
|
54
|
+
} from '@venizia/ignis';
|
|
55
|
+
|
|
56
|
+
export class Application extends BaseApplication {
|
|
57
|
+
preConfigure() {
|
|
58
|
+
this.bind<TStaticAssetsComponentOptions>({
|
|
59
|
+
key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
|
|
60
|
+
}).toValue({
|
|
61
|
+
// MinIO storage for user uploads
|
|
62
|
+
staticAsset: {
|
|
63
|
+
controller: {
|
|
64
|
+
name: 'AssetController',
|
|
65
|
+
basePath: '/assets',
|
|
66
|
+
isStrict: true,
|
|
67
|
+
},
|
|
68
|
+
storage: StaticAssetStorageTypes.MINIO,
|
|
69
|
+
helper: new MinioHelper({
|
|
70
|
+
endPoint: 'localhost',
|
|
71
|
+
port: 9000,
|
|
72
|
+
accessKey: 'minioadmin',
|
|
73
|
+
secretKey: 'minioadmin',
|
|
74
|
+
useSSL: false,
|
|
75
|
+
}),
|
|
76
|
+
extra: {
|
|
77
|
+
parseMultipartBody: { storage: 'memory' },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
// Local disk storage for temporary files
|
|
81
|
+
staticResource: {
|
|
82
|
+
controller: {
|
|
83
|
+
name: 'ResourceController',
|
|
84
|
+
basePath: '/resources',
|
|
85
|
+
isStrict: true,
|
|
86
|
+
},
|
|
87
|
+
storage: StaticAssetStorageTypes.DISK,
|
|
88
|
+
helper: new DiskHelper({
|
|
89
|
+
basePath: './app_data/resources',
|
|
90
|
+
}),
|
|
91
|
+
extra: {
|
|
92
|
+
parseMultipartBody: { storage: 'memory' },
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Each storage backend gets a unique key (`staticAsset`, `staticResource`), its own controller configuration, and a helper instance.
|
|
101
|
+
|
|
102
|
+
### Step 2: Register Component
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { StaticAssetComponent } from '@venizia/ignis';
|
|
106
|
+
|
|
107
|
+
export class Application extends BaseApplication {
|
|
108
|
+
preConfigure() {
|
|
109
|
+
// ... Step 1 binding ...
|
|
110
|
+
this.component(StaticAssetComponent);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Step 3: Use the Endpoints
|
|
116
|
+
|
|
117
|
+
The component auto-registers REST endpoints for each configured backend. No injection needed in downstream code.
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
GET /assets/buckets — List all buckets
|
|
121
|
+
GET /assets/buckets/:bucketName — Get bucket details (or null)
|
|
122
|
+
POST /assets/buckets/:bucketName — Create a bucket
|
|
123
|
+
DELETE /assets/buckets/:bucketName — Delete a bucket
|
|
124
|
+
POST /assets/buckets/:bucketName/upload — Upload files
|
|
125
|
+
GET /assets/buckets/:bucketName/objects — List objects in bucket
|
|
126
|
+
GET /assets/buckets/:bucketName/objects/:obj — Stream file inline
|
|
127
|
+
GET /assets/buckets/:bucketName/objects/:obj/download — Download file (attachment)
|
|
128
|
+
DELETE /assets/buckets/:bucketName/objects/:obj — Delete file
|
|
129
|
+
PUT /assets/buckets/:bucketName/objects/:obj/meta-links — Sync MetaLink (MetaLink only)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Each storage backend gets its own base path (`/assets`, `/resources`, etc.) with the same endpoint structure.
|
|
133
|
+
|
|
134
|
+
#### Environment Variables
|
|
135
|
+
|
|
136
|
+
Add these to your `.env` file for MinIO:
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
APP_ENV_MINIO_HOST=localhost
|
|
140
|
+
APP_ENV_MINIO_API_PORT=9000
|
|
141
|
+
APP_ENV_MINIO_ACCESS_KEY=minioadmin
|
|
142
|
+
APP_ENV_MINIO_SECRET_KEY=minioadmin
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
#### Environment Keys Configuration
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// src/common/environments.ts
|
|
149
|
+
import { EnvironmentKeys as BaseEnv } from '@venizia/ignis';
|
|
150
|
+
|
|
151
|
+
export class EnvironmentKeys extends BaseEnv {
|
|
152
|
+
static readonly APP_ENV_MINIO_HOST = 'APP_ENV_MINIO_HOST';
|
|
153
|
+
static readonly APP_ENV_MINIO_API_PORT = 'APP_ENV_MINIO_API_PORT';
|
|
154
|
+
static readonly APP_ENV_MINIO_ACCESS_KEY = 'APP_ENV_MINIO_ACCESS_KEY';
|
|
155
|
+
static readonly APP_ENV_MINIO_SECRET_KEY = 'APP_ENV_MINIO_SECRET_KEY';
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
#### Docker Compose for MinIO
|
|
160
|
+
|
|
161
|
+
```yaml
|
|
162
|
+
version: '3.8'
|
|
163
|
+
services:
|
|
164
|
+
minio:
|
|
165
|
+
image: minio/minio:latest
|
|
166
|
+
container_name: minio
|
|
167
|
+
ports:
|
|
168
|
+
- "9000:9000" # API port
|
|
169
|
+
- "9001:9001" # Console port
|
|
170
|
+
environment:
|
|
171
|
+
MINIO_ROOT_USER: minioadmin
|
|
172
|
+
MINIO_ROOT_PASSWORD: minioadmin
|
|
173
|
+
command: server /data --console-address ":9001"
|
|
174
|
+
volumes:
|
|
175
|
+
- minio_data:/data
|
|
176
|
+
|
|
177
|
+
volumes:
|
|
178
|
+
minio_data:
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Start with `docker-compose up -d` and access the console at `http://localhost:9001`.
|
|
182
|
+
|
|
183
|
+
## Configuration
|
|
184
|
+
|
|
185
|
+
### Storage Types
|
|
186
|
+
|
|
187
|
+
| Type | Constant | Helper | Description |
|
|
188
|
+
|------|----------|--------|-------------|
|
|
189
|
+
| `'disk'` | `StaticAssetStorageTypes.DISK` | `DiskHelper` | Local filesystem with bucket-based directory structure |
|
|
190
|
+
| `'minio'` | `StaticAssetStorageTypes.MINIO` | `MinioHelper` | S3-compatible object storage (MinIO, AWS S3, etc.) |
|
|
191
|
+
|
|
192
|
+
The `StaticAssetStorageTypes` class provides a `SCHEME_SET` (a `Set` of all valid storage type strings) and an `isValid(orgType)` method for runtime validation:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
StaticAssetStorageTypes.isValid('minio'); // true
|
|
196
|
+
StaticAssetStorageTypes.isValid('s3'); // false
|
|
197
|
+
StaticAssetStorageTypes.SCHEME_SET; // Set { 'disk', 'minio' }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### `TStaticAssetsComponentOptions`
|
|
201
|
+
|
|
202
|
+
Each key in the options object defines a separate storage backend with its own controller:
|
|
203
|
+
|
|
204
|
+
| Option | Type | Default | Description |
|
|
205
|
+
|--------|------|---------|-------------|
|
|
206
|
+
| `controller.name` | `string` | -- | Controller class name |
|
|
207
|
+
| `controller.basePath` | `string` | -- | Base URL path (e.g., `'/assets'`) |
|
|
208
|
+
| `controller.isStrict` | `boolean` | `true` | Strict routing mode |
|
|
209
|
+
| `storage` | `'disk' \| 'minio'` | -- | Storage type |
|
|
210
|
+
| `helper` | `DiskHelper \| MinioHelper` | -- | Storage helper instance |
|
|
211
|
+
| `extra` | `TStaticAssetExtraOptions` | `undefined` | Extra options (multipart parsing, name normalization) |
|
|
212
|
+
| `useMetaLink` | `boolean` | `false` | Enable database file tracking |
|
|
213
|
+
| `metaLink` | `TMetaLinkConfig` | -- | MetaLink configuration (required when `useMetaLink: true`) |
|
|
214
|
+
|
|
215
|
+
#### TStaticAssetsComponentOptions -- Full Reference
|
|
216
|
+
```typescript
|
|
217
|
+
type TStaticAssetsComponentOptions = {
|
|
218
|
+
[key: string]: {
|
|
219
|
+
controller: {
|
|
220
|
+
name: string;
|
|
221
|
+
basePath: string;
|
|
222
|
+
isStrict?: boolean;
|
|
223
|
+
};
|
|
224
|
+
extra?: TStaticAssetExtraOptions;
|
|
225
|
+
} & (
|
|
226
|
+
| { storage: typeof StaticAssetStorageTypes.DISK; helper: DiskHelper }
|
|
227
|
+
| { storage: typeof StaticAssetStorageTypes.MINIO; helper: MinioHelper }
|
|
228
|
+
) &
|
|
229
|
+
({ useMetaLink?: false | undefined } | { useMetaLink: true; metaLink: TMetaLinkConfig });
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
type TStaticAssetExtraOptions = {
|
|
233
|
+
parseMultipartBody?: {
|
|
234
|
+
storage?: 'memory' | 'disk';
|
|
235
|
+
uploadDir?: string;
|
|
236
|
+
};
|
|
237
|
+
normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
|
|
238
|
+
normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
|
|
239
|
+
[key: string]: any;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
type TMetaLinkConfig<Schema extends TMetaLinkSchema = TMetaLinkSchema> = {
|
|
243
|
+
model: typeof BaseEntity<Schema>;
|
|
244
|
+
repository: DefaultCRUDRepository<Schema>;
|
|
245
|
+
};
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### DiskHelper
|
|
249
|
+
|
|
250
|
+
Stores files on the local filesystem using a bucket-based directory structure.
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
new DiskHelper({
|
|
254
|
+
basePath: string; // Base directory for storage
|
|
255
|
+
scope?: string; // Logger scope
|
|
256
|
+
identifier?: string; // Helper identifier
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Example:**
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
const diskHelper = new DiskHelper({
|
|
264
|
+
basePath: './app_data/storage',
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Directory structure:**
|
|
269
|
+
```
|
|
270
|
+
app_data/storage/
|
|
271
|
+
├── bucket-1/
|
|
272
|
+
│ ├── file1.pdf
|
|
273
|
+
│ └── file2.jpg
|
|
274
|
+
├── bucket-2/
|
|
275
|
+
│ └── document.docx
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Features: automatic directory creation, built-in path validation, metadata from file stats, stream-based operations.
|
|
279
|
+
|
|
280
|
+
### MinioHelper
|
|
281
|
+
|
|
282
|
+
Connects to MinIO or any S3-compatible object storage.
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
new MinioHelper({
|
|
286
|
+
endPoint: string; // MinIO server hostname
|
|
287
|
+
port: number; // API port (default: 9000)
|
|
288
|
+
useSSL: boolean; // Use HTTPS
|
|
289
|
+
accessKey: string; // Access key
|
|
290
|
+
secretKey: string; // Secret key
|
|
291
|
+
})
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
**Example:**
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
const minioHelper = new MinioHelper({
|
|
298
|
+
endPoint: 'minio.example.com',
|
|
299
|
+
port: 9000,
|
|
300
|
+
useSSL: true,
|
|
301
|
+
accessKey: process.env.MINIO_ACCESS_KEY,
|
|
302
|
+
secretKey: process.env.MINIO_SECRET_KEY,
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### MetaLink Configuration
|
|
307
|
+
|
|
308
|
+
MetaLink is an optional feature that tracks uploaded files in a database, storing file location, metadata (mimetype, size, etag), storage type, principal association (`principalType`, `principalId`), timestamps, and custom metadata (JSONB).
|
|
309
|
+
|
|
310
|
+
#### Benefits
|
|
311
|
+
|
|
312
|
+
- Query uploaded files by bucket, name, mimetype, etc.
|
|
313
|
+
- Track file history and audit trails
|
|
314
|
+
- Store custom metadata about files
|
|
315
|
+
- Associate files with principals via `principalType` and `principalId` (passed as query parameters on the upload endpoint)
|
|
316
|
+
- Graceful errors -- upload succeeds even if MetaLink creation fails
|
|
317
|
+
|
|
318
|
+
#### Setup
|
|
319
|
+
|
|
320
|
+
**1. Create Model:**
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
import { BaseMetaLinkModel, model } from '@venizia/ignis';
|
|
324
|
+
|
|
325
|
+
@model({ type: 'entity' })
|
|
326
|
+
export class FileMetaLinkModel extends BaseMetaLinkModel {
|
|
327
|
+
// Inherits all fields from BaseMetaLinkModel
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
**2. Create Repository:**
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
import { BaseMetaLinkRepository, repository, inject, IDataSource } from '@venizia/ignis';
|
|
335
|
+
|
|
336
|
+
@repository({})
|
|
337
|
+
export class FileMetaLinkRepository extends BaseMetaLinkRepository {
|
|
338
|
+
constructor(@inject({ key: 'datasources.postgres' }) dataSource: IDataSource) {
|
|
339
|
+
super({
|
|
340
|
+
entityClass: FileMetaLinkModel,
|
|
341
|
+
relations: {},
|
|
342
|
+
dataSource,
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
**3. Create Database Table:**
|
|
349
|
+
|
|
350
|
+
The model has `skipMigrate: true`, so create the table manually:
|
|
351
|
+
|
|
352
|
+
```sql
|
|
353
|
+
CREATE TABLE "MetaLink" (
|
|
354
|
+
id TEXT PRIMARY KEY,
|
|
355
|
+
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
356
|
+
modified_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
357
|
+
bucket_name TEXT NOT NULL,
|
|
358
|
+
object_name TEXT NOT NULL,
|
|
359
|
+
link TEXT NOT NULL,
|
|
360
|
+
mimetype TEXT NOT NULL,
|
|
361
|
+
size INTEGER NOT NULL,
|
|
362
|
+
etag TEXT,
|
|
363
|
+
metadata JSONB,
|
|
364
|
+
storage_type TEXT NOT NULL,
|
|
365
|
+
is_synced BOOLEAN NOT NULL DEFAULT false,
|
|
366
|
+
principal_type TEXT,
|
|
367
|
+
principal_id TEXT
|
|
368
|
+
);
|
|
369
|
+
|
|
370
|
+
CREATE INDEX "IDX_MetaLink_bucketName" ON "MetaLink"(bucket_name);
|
|
371
|
+
CREATE INDEX "IDX_MetaLink_objectName" ON "MetaLink"(object_name);
|
|
372
|
+
CREATE INDEX "IDX_MetaLink_storageType" ON "MetaLink"(storage_type);
|
|
373
|
+
CREATE INDEX "IDX_MetaLink_isSynced" ON "MetaLink"(is_synced);
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**4. Configure Component:**
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
import { FileMetaLinkModel, FileMetaLinkRepository } from './your-models';
|
|
380
|
+
|
|
381
|
+
export class Application extends BaseApplication {
|
|
382
|
+
configureComponents(): void {
|
|
383
|
+
this.repository(FileMetaLinkRepository);
|
|
384
|
+
|
|
385
|
+
this.bind<TStaticAssetsComponentOptions>({
|
|
386
|
+
key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
|
|
387
|
+
}).toValue({
|
|
388
|
+
uploads: {
|
|
389
|
+
controller: {
|
|
390
|
+
name: 'UploadController',
|
|
391
|
+
basePath: '/uploads',
|
|
392
|
+
isStrict: true,
|
|
393
|
+
},
|
|
394
|
+
storage: StaticAssetStorageTypes.MINIO,
|
|
395
|
+
helper: new MinioHelper({ /* ... */ }),
|
|
396
|
+
useMetaLink: true,
|
|
397
|
+
metaLink: {
|
|
398
|
+
model: FileMetaLinkModel,
|
|
399
|
+
repository: this.getSync(FileMetaLinkRepository),
|
|
400
|
+
},
|
|
401
|
+
extra: {
|
|
402
|
+
parseMultipartBody: { storage: 'memory' },
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
this.component(StaticAssetComponent);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
**5. Upload with Principal Association:**
|
|
413
|
+
|
|
414
|
+
When MetaLink is enabled, you can associate uploaded files with a principal (user, service, etc.) by passing query parameters on the upload endpoint:
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
const formData = new FormData();
|
|
418
|
+
formData.append('file', fileBlob, 'document.pdf');
|
|
419
|
+
|
|
420
|
+
// Associate the upload with a user
|
|
421
|
+
const response = await fetch(
|
|
422
|
+
'/uploads/buckets/user-files/upload?principalType=user&principalId=42',
|
|
423
|
+
{ method: 'POST', body: formData },
|
|
424
|
+
);
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
The `principalId` value is always stored as a string regardless of input type (coerced via `String()`).
|
|
428
|
+
|
|
429
|
+
#### Querying MetaLinks
|
|
430
|
+
|
|
431
|
+
```typescript
|
|
432
|
+
// Get all files in a bucket
|
|
433
|
+
const files = await fileMetaLinkRepository.find({
|
|
434
|
+
where: { bucketName: 'user-uploads' },
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// Get files by mimetype
|
|
438
|
+
const pdfs = await fileMetaLinkRepository.find({
|
|
439
|
+
where: { mimetype: 'application/pdf' },
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// Get files by storage type
|
|
443
|
+
const minioFiles = await fileMetaLinkRepository.find({
|
|
444
|
+
where: { storageType: 'minio' },
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
// Get files by principal
|
|
448
|
+
const userFiles = await fileMetaLinkRepository.find({
|
|
449
|
+
where: { principalType: 'user', principalId: '42' },
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Get synced files only
|
|
453
|
+
const syncedFiles = await fileMetaLinkRepository.find({
|
|
454
|
+
where: { isSynced: true },
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Get unsynced files (for manual sync operations)
|
|
458
|
+
const unsyncedFiles = await fileMetaLinkRepository.find({
|
|
459
|
+
where: { isSynced: false },
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
// Count synced files
|
|
463
|
+
const syncedCount = await fileMetaLinkRepository.count({
|
|
464
|
+
where: { isSynced: true },
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// Get recent uploads
|
|
468
|
+
const recent = await fileMetaLinkRepository.find({
|
|
469
|
+
orderBy: { createdAt: 'desc' },
|
|
470
|
+
limit: 10,
|
|
471
|
+
});
|
|
472
|
+
```
|
|
473
|
+
|
|
474
|
+
### Quick Start Options
|
|
475
|
+
|
|
476
|
+
**Option 1: MinIO Only**
|
|
477
|
+
```typescript
|
|
478
|
+
this.bind({
|
|
479
|
+
key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
|
|
480
|
+
}).toValue({
|
|
481
|
+
cloudStorage: {
|
|
482
|
+
controller: { name: 'CloudController', basePath: '/cloud' },
|
|
483
|
+
storage: StaticAssetStorageTypes.MINIO,
|
|
484
|
+
helper: new MinioHelper({ /* ... */ }),
|
|
485
|
+
extra: { parseMultipartBody: { storage: 'memory' } },
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
this.component(StaticAssetComponent);
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
**Option 2: Local Disk Only**
|
|
492
|
+
```typescript
|
|
493
|
+
this.bind({
|
|
494
|
+
key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
|
|
495
|
+
}).toValue({
|
|
496
|
+
localStorage: {
|
|
497
|
+
controller: { name: 'LocalController', basePath: '/files' },
|
|
498
|
+
storage: StaticAssetStorageTypes.DISK,
|
|
499
|
+
helper: new DiskHelper({ basePath: './uploads' }),
|
|
500
|
+
extra: { parseMultipartBody: { storage: 'disk' } },
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
this.component(StaticAssetComponent);
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
**Option 3: Multiple Storage Backends (Recommended)**
|
|
507
|
+
```typescript
|
|
508
|
+
this.bind({
|
|
509
|
+
key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
|
|
510
|
+
}).toValue({
|
|
511
|
+
userUploads: {
|
|
512
|
+
controller: { name: 'UploadsController', basePath: '/uploads' },
|
|
513
|
+
storage: StaticAssetStorageTypes.MINIO,
|
|
514
|
+
helper: new MinioHelper({ /* ... */ }),
|
|
515
|
+
extra: {},
|
|
516
|
+
},
|
|
517
|
+
tempFiles: {
|
|
518
|
+
controller: { name: 'TempController', basePath: '/temp' },
|
|
519
|
+
storage: StaticAssetStorageTypes.DISK,
|
|
520
|
+
helper: new DiskHelper({ basePath: './temp' }),
|
|
521
|
+
extra: {},
|
|
522
|
+
},
|
|
523
|
+
publicAssets: {
|
|
524
|
+
controller: { name: 'PublicController', basePath: '/public' },
|
|
525
|
+
storage: StaticAssetStorageTypes.DISK,
|
|
526
|
+
helper: new DiskHelper({ basePath: './public' }),
|
|
527
|
+
extra: {},
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
this.component(StaticAssetComponent);
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
### Custom Filename Normalization
|
|
534
|
+
|
|
535
|
+
```typescript
|
|
536
|
+
{
|
|
537
|
+
uploads: {
|
|
538
|
+
controller: { name: 'UploadController', basePath: '/uploads' },
|
|
539
|
+
storage: StaticAssetStorageTypes.MINIO,
|
|
540
|
+
helper: new MinioHelper({ /* ... */ }),
|
|
541
|
+
extra: {
|
|
542
|
+
parseMultipartBody: { storage: 'memory' },
|
|
543
|
+
normalizeNameFn: ({ originalName, folderPath }) => {
|
|
544
|
+
const prefix = folderPath ? `${folderPath}/` : '';
|
|
545
|
+
return `${prefix}${Date.now()}_${originalName.toLowerCase().replace(/\s/g, '_')}`;
|
|
546
|
+
},
|
|
547
|
+
normalizeLinkFn: ({ bucketName, normalizeName }) => {
|
|
548
|
+
return `/api/files/${bucketName}/${encodeURIComponent(normalizeName)}`;
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
The `normalizeNameFn` receives both the `originalName` and an optional `folderPath` from the uploaded file. The `folderPath` is passed through from the `IUploadFile` object and can be used to organize files into subdirectories.
|
|
556
|
+
|
|
557
|
+
### Custom Storage Implementation
|
|
558
|
+
|
|
559
|
+
You can implement your own storage backend by extending `BaseStorageHelper`:
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
import { BaseStorageHelper, IUploadFile, IUploadResult } from '@venizia/ignis-helpers';
|
|
563
|
+
|
|
564
|
+
class S3Helper extends BaseStorageHelper {
|
|
565
|
+
constructor(config: S3Config) {
|
|
566
|
+
super({ scope: 'S3Helper', identifier: 'S3Helper' });
|
|
567
|
+
// Initialize S3 client
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async isBucketExists(opts: { name: string }): Promise<boolean> {
|
|
571
|
+
// Implementation
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
async upload(opts: {
|
|
575
|
+
bucket: string;
|
|
576
|
+
files: IUploadFile[];
|
|
577
|
+
normalizeNameFn?: (opts: { originalName: string; folderPath?: string }) => string;
|
|
578
|
+
normalizeLinkFn?: (opts: { bucketName: string; normalizeName: string }) => string;
|
|
579
|
+
}): Promise<IUploadResult[]> {
|
|
580
|
+
// Implementation
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Implement other IStorageHelper methods...
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// Usage
|
|
587
|
+
this.bind<TStaticAssetsComponentOptions>({
|
|
588
|
+
key: StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS,
|
|
589
|
+
}).toValue({
|
|
590
|
+
s3Storage: {
|
|
591
|
+
controller: { name: 'S3Controller', basePath: '/s3-assets' },
|
|
592
|
+
storage: 'custom-s3',
|
|
593
|
+
helper: new S3Helper({ /* ... */ }),
|
|
594
|
+
extra: {},
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
```
|
|
598
|
+
|
|
599
|
+
## Binding Keys
|
|
600
|
+
|
|
601
|
+
| Key | Constant | Type | Required | Default |
|
|
602
|
+
|-----|----------|------|----------|---------|
|
|
603
|
+
| `@app/static-asset-component/options` | `StaticAssetComponentBindingKeys.STATIC_ASSET_COMPONENT_OPTIONS` | `TStaticAssetsComponentOptions` | Yes | `{}` |
|
|
604
|
+
|
|
605
|
+
> [!NOTE]
|
|
606
|
+
> The component provides an empty default binding. You must bind this key with your storage configuration before registering the component.
|
|
607
|
+
|
|
608
|
+
## See Also
|
|
609
|
+
|
|
610
|
+
- [Usage & Examples](./usage) - API Endpoints and Frontend Integration
|
|
611
|
+
- [API Reference](./api) - Controller Factory, Storage Interface, MetaLink Schema
|
|
612
|
+
- [Error Reference](./errors) - Name Validation and Troubleshooting
|
|
613
|
+
- [Storage Helpers](/references/helpers/storage/) - DiskHelper, MinioHelper, BaseStorageHelper
|
|
614
|
+
- [Request Utilities](/references/utilities/request) - File upload utilities
|
|
615
|
+
- [Security Guidelines](/best-practices/security-guidelines) - File upload security
|
|
616
|
+
- [Components Overview](/guides/core-concepts/components) - Component system basics
|
|
617
|
+
- [Controllers](/guides/core-concepts/controllers) - File upload endpoints
|