@flusys/ng-storage 3.0.0-rc → 3.0.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.
- package/README.md +176 -414
- package/fesm2022/{flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs → flusys-ng-storage-file-manager-form.component-CXVB9G-f.mjs} +4 -4
- package/fesm2022/{flusys-ng-storage-file-manager-form.component-Bv8ER8J0.mjs.map → flusys-ng-storage-file-manager-form.component-CXVB9G-f.mjs.map} +1 -1
- package/fesm2022/{flusys-ng-storage-file-manager-list.component-McM1zSlE.mjs → flusys-ng-storage-file-manager-list.component-0O_b4EjN.mjs} +4 -4
- package/fesm2022/{flusys-ng-storage-file-manager-list.component-McM1zSlE.mjs.map → flusys-ng-storage-file-manager-list.component-0O_b4EjN.mjs.map} +1 -1
- package/fesm2022/{flusys-ng-storage-folder-form.component-fRFiMFev.mjs → flusys-ng-storage-folder-form.component-BTQGEqNu.mjs} +4 -4
- package/fesm2022/{flusys-ng-storage-folder-form.component-fRFiMFev.mjs.map → flusys-ng-storage-folder-form.component-BTQGEqNu.mjs.map} +1 -1
- package/fesm2022/{flusys-ng-storage-folder-list.component-C4xHI8SO.mjs → flusys-ng-storage-folder-list.component-B8GoIh6Z.mjs} +4 -4
- package/fesm2022/{flusys-ng-storage-folder-list.component-C4xHI8SO.mjs.map → flusys-ng-storage-folder-list.component-B8GoIh6Z.mjs.map} +1 -1
- package/fesm2022/{flusys-ng-storage-storage-config-form.component-DEMexjmg.mjs → flusys-ng-storage-storage-config-form.component-CorNWrmF.mjs} +4 -4
- package/fesm2022/{flusys-ng-storage-storage-config-form.component-DEMexjmg.mjs.map → flusys-ng-storage-storage-config-form.component-CorNWrmF.mjs.map} +1 -1
- package/fesm2022/{flusys-ng-storage-storage-config-list.component-iJez-rII.mjs → flusys-ng-storage-storage-config-list.component-YXzz3Tvo.mjs} +4 -4
- package/fesm2022/{flusys-ng-storage-storage-config-list.component-iJez-rII.mjs.map → flusys-ng-storage-storage-config-list.component-YXzz3Tvo.mjs.map} +1 -1
- package/fesm2022/flusys-ng-storage.mjs +36 -38
- package/fesm2022/flusys-ng-storage.mjs.map +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Storage Package Guide
|
|
2
2
|
|
|
3
3
|
> **Package:** `@flusys/ng-storage`
|
|
4
|
+
> **Version:** 3.0.1
|
|
4
5
|
> **Type:** File storage management with upload, folder organization, and multi-provider support
|
|
5
6
|
|
|
6
7
|
---
|
|
@@ -14,14 +15,15 @@
|
|
|
14
15
|
- **Folder Management** - Folder organization for files
|
|
15
16
|
- **Storage Configs** - Multi-provider configurations (AWS S3, Azure Blob, SFTP, Local)
|
|
16
17
|
- **Company Scoping** - Automatic company-scoped queries (when enabled)
|
|
18
|
+
- **Permission Guards** - All routes protected by permission guards
|
|
17
19
|
- **Lazy Loading** - All page components are lazy-loaded via routes
|
|
18
20
|
|
|
19
21
|
### Package Hierarchy
|
|
20
22
|
|
|
21
23
|
```
|
|
22
|
-
@flusys/ng-core <- Foundation (BaseApiService, APP_CONFIG)
|
|
24
|
+
@flusys/ng-core <- Foundation (BaseApiService, APP_CONFIG, getDefaultStorageConfigId)
|
|
23
25
|
|
|
|
24
|
-
@flusys/ng-shared <- Shared utilities (ApiResourceService, IBaseEntity)
|
|
26
|
+
@flusys/ng-shared <- Shared utilities (ApiResourceService, IBaseEntity, BaseListPage)
|
|
25
27
|
|
|
|
26
28
|
@flusys/ng-layout <- Layout (LAYOUT_AUTH_STATE for company context)
|
|
27
29
|
|
|
|
@@ -30,17 +32,7 @@
|
|
|
30
32
|
|
|
31
33
|
### Company Context
|
|
32
34
|
|
|
33
|
-
Storage components use `LAYOUT_AUTH_STATE` from `@flusys/ng-layout` for company context. This follows the Provider Interface Pattern - ng-storage depends on ng-layout's abstraction, not ng-auth directly.
|
|
34
|
-
|
|
35
|
-
```typescript
|
|
36
|
-
import { LAYOUT_AUTH_STATE } from '@flusys/ng-layout';
|
|
37
|
-
|
|
38
|
-
private readonly companyContext = inject(LAYOUT_AUTH_STATE);
|
|
39
|
-
|
|
40
|
-
readonly currentCompanyName = computed(
|
|
41
|
-
() => this.companyContext.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME,
|
|
42
|
-
);
|
|
43
|
-
```
|
|
35
|
+
Storage components use `LAYOUT_AUTH_STATE` from `@flusys/ng-layout` for company context. This follows the Provider Interface Pattern - ng-storage depends on ng-layout's abstraction, not ng-auth directly. See `BaseStoragePage` class for implementation.
|
|
44
36
|
|
|
45
37
|
---
|
|
46
38
|
|
|
@@ -106,20 +98,20 @@ export const routes: Routes = [
|
|
|
106
98
|
|
|
107
99
|
### Route Structure
|
|
108
100
|
|
|
109
|
-
All routes are wrapped by `StorageContainerComponent` which provides tab navigation (Files, Folders, Configs).
|
|
110
|
-
|
|
111
|
-
| Path | Component |
|
|
112
|
-
|
|
113
|
-
| `/storage` | StorageContainerComponent |
|
|
114
|
-
| `/storage/files` | FileManagerListComponent |
|
|
115
|
-
| `/storage/files/new` | FileManagerFormComponent |
|
|
116
|
-
| `/storage/files/:id` | FileManagerFormComponent |
|
|
117
|
-
| `/storage/folders` | FolderListComponent |
|
|
118
|
-
| `/storage/folders/new` | FolderFormComponent |
|
|
119
|
-
| `/storage/folders/:id` | FolderFormComponent |
|
|
120
|
-
| `/storage/configs` | StorageConfigListComponent |
|
|
121
|
-
| `/storage/configs/new` | StorageConfigFormComponent |
|
|
122
|
-
| `/storage/configs/:id` | StorageConfigFormComponent |
|
|
101
|
+
All routes are wrapped by `StorageContainerComponent` which provides tab navigation (Files, Folders, Configs). Routes are protected by permission guards.
|
|
102
|
+
|
|
103
|
+
| Path | Component | Permission |
|
|
104
|
+
|------|-----------|------------|
|
|
105
|
+
| `/storage` | StorageContainerComponent | - |
|
|
106
|
+
| `/storage/files` | FileManagerListComponent | `FILE_PERMISSIONS.READ` |
|
|
107
|
+
| `/storage/files/new` | FileManagerFormComponent | `FILE_PERMISSIONS.CREATE` |
|
|
108
|
+
| `/storage/files/:id` | FileManagerFormComponent | `FILE_PERMISSIONS.UPDATE` |
|
|
109
|
+
| `/storage/folders` | FolderListComponent | `FOLDER_PERMISSIONS.READ` |
|
|
110
|
+
| `/storage/folders/new` | FolderFormComponent | `FOLDER_PERMISSIONS.CREATE` |
|
|
111
|
+
| `/storage/folders/:id` | FolderFormComponent | `FOLDER_PERMISSIONS.UPDATE` |
|
|
112
|
+
| `/storage/configs` | StorageConfigListComponent | `STORAGE_CONFIG_PERMISSIONS.READ` |
|
|
113
|
+
| `/storage/configs/new` | StorageConfigFormComponent | `STORAGE_CONFIG_PERMISSIONS.CREATE` |
|
|
114
|
+
| `/storage/configs/:id` | StorageConfigFormComponent | `STORAGE_CONFIG_PERMISSIONS.UPDATE` |
|
|
123
115
|
|
|
124
116
|
Default redirect: `/storage` -> `/storage/files`
|
|
125
117
|
|
|
@@ -171,20 +163,19 @@ interface IUploadOptionsDto {
|
|
|
171
163
|
}
|
|
172
164
|
|
|
173
165
|
interface IUploadedFileInfo {
|
|
174
|
-
name: string;
|
|
175
|
-
key: string;
|
|
176
|
-
size: number;
|
|
177
|
-
contentType: string;
|
|
166
|
+
name: string; // Original filename
|
|
167
|
+
key: string; // Storage key/path
|
|
168
|
+
size: number; // File size in bytes
|
|
169
|
+
contentType: string; // MIME type
|
|
170
|
+
location?: string; // Storage location (optional)
|
|
171
|
+
storageConfigId?: string; // Config used for upload (optional)
|
|
178
172
|
}
|
|
179
173
|
```
|
|
180
174
|
|
|
181
175
|
### File Manager
|
|
182
176
|
|
|
183
177
|
```typescript
|
|
184
|
-
interface IFileManager {
|
|
185
|
-
id: string;
|
|
186
|
-
createdAt: Date;
|
|
187
|
-
updatedAt: Date;
|
|
178
|
+
interface IFileManager extends IBaseEntity {
|
|
188
179
|
name: string;
|
|
189
180
|
contentType: string;
|
|
190
181
|
size: string; // In KB (string)
|
|
@@ -232,7 +223,7 @@ interface IFolder extends IBaseEntity {
|
|
|
232
223
|
}
|
|
233
224
|
|
|
234
225
|
interface ICreateFolderDto { name: string; }
|
|
235
|
-
interface IUpdateFolderDto { id: string;
|
|
226
|
+
interface IUpdateFolderDto extends Partial<ICreateFolderDto> { id: string; }
|
|
236
227
|
```
|
|
237
228
|
|
|
238
229
|
### Storage Config
|
|
@@ -241,27 +232,22 @@ interface IUpdateFolderDto { id: string; name?: string; }
|
|
|
241
232
|
interface IStorageConfig extends IBaseEntity {
|
|
242
233
|
name: string;
|
|
243
234
|
storage: FileLocationEnum;
|
|
244
|
-
config: Record<string,
|
|
245
|
-
isActive: boolean;
|
|
246
|
-
isDefault: boolean;
|
|
235
|
+
config: Record<string, unknown>; // Provider-specific configuration
|
|
236
|
+
isActive: boolean; // Whether config is active and usable
|
|
237
|
+
isDefault: boolean; // Set as default configuration
|
|
247
238
|
companyId?: string | null;
|
|
248
239
|
}
|
|
249
240
|
|
|
250
241
|
interface ICreateStorageConfigDto {
|
|
251
242
|
name: string;
|
|
252
243
|
storage: FileLocationEnum;
|
|
253
|
-
config: Record<string,
|
|
244
|
+
config: Record<string, unknown>;
|
|
254
245
|
isActive?: boolean;
|
|
255
246
|
isDefault?: boolean;
|
|
256
247
|
}
|
|
257
248
|
|
|
258
|
-
interface IUpdateStorageConfigDto {
|
|
249
|
+
interface IUpdateStorageConfigDto extends Partial<ICreateStorageConfigDto> {
|
|
259
250
|
id: string;
|
|
260
|
-
name?: string;
|
|
261
|
-
storage?: FileLocationEnum;
|
|
262
|
-
config?: Record<string, any>;
|
|
263
|
-
isActive?: boolean;
|
|
264
|
-
isDefault?: boolean;
|
|
265
251
|
}
|
|
266
252
|
```
|
|
267
253
|
|
|
@@ -269,34 +255,12 @@ interface IUpdateStorageConfigDto {
|
|
|
269
255
|
|
|
270
256
|
These define the shape of `IStorageConfig.config` for each provider:
|
|
271
257
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
endpoint?: string; // Custom S3-compatible endpoint
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
interface IAzureBlobConfig {
|
|
282
|
-
accountName: string;
|
|
283
|
-
accountKey: string;
|
|
284
|
-
containerName: string;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
interface ISftpConfig {
|
|
288
|
-
host: string;
|
|
289
|
-
port: number;
|
|
290
|
-
username: string;
|
|
291
|
-
password?: string;
|
|
292
|
-
privateKey?: string;
|
|
293
|
-
basePath: string;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
interface ILocalStorageConfig {
|
|
297
|
-
basePath: string;
|
|
298
|
-
}
|
|
299
|
-
```
|
|
258
|
+
| Interface | Required Fields | Optional Fields |
|
|
259
|
+
|-----------|-----------------|-----------------|
|
|
260
|
+
| `IAwsS3Config` | `region`, `bucket`, `accessKeyId`, `secretAccessKey` | `endpoint` (custom S3-compatible) |
|
|
261
|
+
| `IAzureBlobConfig` | `accountName`, `accountKey`, `containerName` | - |
|
|
262
|
+
| `ISftpConfig` | `host`, `port`, `username`, `basePath` | `password`, `privateKey` |
|
|
263
|
+
| `ILocalStorageConfig` | `basePath` | - |
|
|
300
264
|
|
|
301
265
|
---
|
|
302
266
|
|
|
@@ -306,7 +270,9 @@ All services are `providedIn: 'root'`. Company scoping is automatic when the com
|
|
|
306
270
|
|
|
307
271
|
### UploadService
|
|
308
272
|
|
|
309
|
-
Handles file upload and deletion. Extends `BaseApiService` (from ng-core)
|
|
273
|
+
Handles file upload and deletion. Extends `BaseApiService` (from ng-core) with base path `/storage`.
|
|
274
|
+
|
|
275
|
+
**Methods:**
|
|
310
276
|
|
|
311
277
|
| Method | Endpoint | Description |
|
|
312
278
|
|--------|----------|-------------|
|
|
@@ -318,85 +284,52 @@ Handles file upload and deletion. Extends `BaseApiService` (from ng-core).
|
|
|
318
284
|
| `getFileUrl(key)` | `GET /storage/upload/file/:key` | Get direct file download URL |
|
|
319
285
|
| `getDefaultConfigId()` | - | Get default storage config ID from APP_CONFIG |
|
|
320
286
|
|
|
321
|
-
Upload options are sent as
|
|
322
|
-
|
|
323
|
-
```typescript
|
|
324
|
-
// POST /storage/upload/single-file?storageConfigId=abc&folderPath=docs&compress=true
|
|
325
|
-
this.uploadService.uploadSingleFile(file, {
|
|
326
|
-
storageConfigId: 'abc',
|
|
327
|
-
folderPath: 'docs',
|
|
328
|
-
compress: true,
|
|
329
|
-
quality: 85,
|
|
330
|
-
format: ImageFormat.WEBP,
|
|
331
|
-
});
|
|
332
|
-
```
|
|
287
|
+
**Upload options** are sent as query parameters. **Storage Config Resolution:** Uses `storageConfigId` from options if provided, otherwise falls back to `getDefaultConfigId()` from APP_CONFIG. See Usage Examples for code samples.
|
|
333
288
|
|
|
334
289
|
### FileManagerApiService
|
|
335
290
|
|
|
336
|
-
File metadata CRUD. Extends `ApiResourceService<IUpdateFileManagerDto, IFileManager
|
|
291
|
+
File metadata CRUD. Extends `ApiResourceService<IUpdateFileManagerDto, IFileManager>` with endpoint `/storage/file-manager`.
|
|
337
292
|
|
|
338
|
-
**Inherited CRUD
|
|
293
|
+
**Inherited CRUD:** `insert`, `update`, `findById`, `getAll`, `delete` (and `*Async` variants)
|
|
339
294
|
|
|
340
295
|
**Additional methods:**
|
|
341
296
|
|
|
342
297
|
| Method | Endpoint | Description |
|
|
343
298
|
|--------|----------|-------------|
|
|
344
|
-
| `getFiles(fileIds)` | `POST /storage/file-manager/get-files` | Get files with valid presigned URLs |
|
|
345
|
-
| `
|
|
346
|
-
| `
|
|
347
|
-
|
|
348
|
-
**Presigned URL pattern:** Always use `getFiles()` to get valid URLs. Backend regenerates expired presigned URLs for cloud storage.
|
|
299
|
+
| `getFiles(fileIds: string[])` | `POST /storage/file-manager/get-files` | Get files with valid presigned URLs |
|
|
300
|
+
| `createFile(dto: ICreateFileManagerDto)` | `POST /storage/file-manager/insert` | Create file record (Observable) |
|
|
301
|
+
| `createFileAsync(dto: ICreateFileManagerDto)` | `POST /storage/file-manager/insert` | Create file record (Promise) |
|
|
349
302
|
|
|
350
|
-
|
|
351
|
-
const response = await firstValueFrom(
|
|
352
|
-
this.fileService.getFiles(['file-id-1', 'file-id-2'])
|
|
353
|
-
);
|
|
354
|
-
// response.data → [{ id, name, contentType, url }]
|
|
355
|
-
```
|
|
303
|
+
**Presigned URL pattern:** Always use `getFiles()` to get valid URLs. Backend regenerates expired presigned URLs for cloud storage. See Usage Examples.
|
|
356
304
|
|
|
357
305
|
### FolderApiService
|
|
358
306
|
|
|
359
|
-
Folder CRUD. Extends `ApiResourceService<IUpdateFolderDto, IFolder
|
|
307
|
+
Folder CRUD. Extends `ApiResourceService<ICreateFolderDto | IUpdateFolderDto, IFolder>` with endpoint `/storage/folder`. Standard CRUD operations only.
|
|
360
308
|
|
|
361
309
|
### StorageConfigApiService
|
|
362
310
|
|
|
363
|
-
Storage
|
|
311
|
+
Storage config CRUD. Extends `ApiResourceService<ICreateStorageConfigDto | IUpdateStorageConfigDto, IStorageConfig>` with endpoint `/storage/storage-config`. Standard CRUD operations only.
|
|
364
312
|
|
|
365
313
|
---
|
|
366
314
|
|
|
367
|
-
##
|
|
315
|
+
## Base Classes
|
|
368
316
|
|
|
369
|
-
|
|
317
|
+
Components extend shared base classes from `utils/base-storage-page.ts`:
|
|
370
318
|
|
|
371
|
-
|
|
319
|
+
| Class | Extends | Purpose | Key Members |
|
|
320
|
+
|-------|---------|---------|-------------|
|
|
321
|
+
| `BaseStoragePage` | - | Form pages | `isLoading`, `showCompanyInfo`, `currentCompanyName`, `showSuccess()`, `showError()`, `navigateTo()` |
|
|
322
|
+
| `BaseStorageListPage<T>` | `BaseListPage<T>` | List pages | `pageSize=50`, `totalRecords`, `currentCompanyName`, `getDeleteConfirmMessage()` |
|
|
372
323
|
|
|
373
|
-
|
|
324
|
+
Both inject: `Router`, `MessageService`, `APP_CONFIG`, `LAYOUT_AUTH_STATE`
|
|
374
325
|
|
|
375
|
-
|
|
376
|
-
// BaseStoragePage - Common functionality for all storage pages
|
|
377
|
-
abstract class BaseStoragePage {
|
|
378
|
-
readonly isLoading = signal(false);
|
|
379
|
-
readonly showCompanyInfo = computed(() => this.appConfig.enableCompanyFeature);
|
|
380
|
-
readonly currentCompanyName = computed(() =>
|
|
381
|
-
this.companyContext.currentCompanyInfo()?.name ?? DEFAULT_APP_NAME
|
|
382
|
-
);
|
|
383
|
-
|
|
384
|
-
protected showSuccess(detail: string): void;
|
|
385
|
-
protected showError(detail: string): void;
|
|
386
|
-
protected navigateTo(path: string[]): void;
|
|
387
|
-
}
|
|
326
|
+
**BaseStorageListPage inherits from BaseListPage:** `items`, `total`, `isLoading`, `currentPage`, `onPageChange()`, `getPaginationParams()`, `onCreate()`, `onEdit()`, `onDeleteAsync()`, `loadData()` (abstract), `getResourceRoute()` (abstract)
|
|
388
327
|
|
|
389
|
-
|
|
390
|
-
abstract class BaseStorageListPage extends BaseStoragePage {
|
|
391
|
-
readonly totalRecords = signal(0);
|
|
392
|
-
readonly pageSize = signal(50);
|
|
393
|
-
readonly currentPage = signal(1);
|
|
328
|
+
---
|
|
394
329
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
}
|
|
399
|
-
```
|
|
330
|
+
## Components
|
|
331
|
+
|
|
332
|
+
All components are standalone, use Angular 21 signal forms, and lazy-load via routes.
|
|
400
333
|
|
|
401
334
|
### StorageContainerComponent
|
|
402
335
|
|
|
@@ -405,91 +338,65 @@ Main container with tab navigation between Files, Folders, and Configs sections.
|
|
|
405
338
|
**Selector:** `lib-storage-container`
|
|
406
339
|
|
|
407
340
|
**Features:**
|
|
408
|
-
- Tab navigation
|
|
341
|
+
- Tab navigation filtered by user permissions (via `PermissionValidatorService`)
|
|
342
|
+
- Horizontal scroll without visible scrollbar (`scrollbar-hide`)
|
|
343
|
+
- Dynamic subtitle based on visible tabs
|
|
409
344
|
- Responsive padding: `p-2 sm:p-4`
|
|
410
|
-
- Dark mode compatible
|
|
345
|
+
- Dark mode compatible (Tailwind theme classes)
|
|
411
346
|
|
|
412
347
|
### List Components
|
|
413
348
|
|
|
414
|
-
All list components
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
- **Responsive title:** `text-lg sm:text-xl`
|
|
422
|
-
- **Responsive buttons:** `w-full sm:w-auto`
|
|
423
|
-
|
|
424
|
-
**FileManagerListComponent** additionally supports:
|
|
349
|
+
All list components extend `BaseStorageListPage<T>` with: lazy pagination (`p-table`), responsive columns (`hidden sm:table-cell`), company info display, delete confirmation (`ConfirmationService`), signal-based state (`isLoading`, `totalRecords`, `pageSize`, `currentPage`), and permission-based actions (`*hasPermission`). Responsive styling: titles `text-lg sm:text-xl`, buttons `w-full sm:w-auto`.
|
|
350
|
+
|
|
351
|
+
#### FileManagerListComponent
|
|
352
|
+
|
|
353
|
+
**Selector:** `lib-file-manager-list`
|
|
354
|
+
|
|
355
|
+
**Features:**
|
|
425
356
|
- File content type icons (image, video, audio, PDF, Word, Excel, generic)
|
|
426
|
-
-
|
|
427
|
-
-
|
|
428
|
-
- **
|
|
357
|
+
- Provider location tags with severity colors
|
|
358
|
+
- View file: fetches presigned URL via `getFiles()`, opens in new tab (tracks loading with `viewingFileId` signal)
|
|
359
|
+
- **Soft delete** (`type: 'delete'`) - recoverable | **Permanent delete** (`type: 'permanent'`) - irreversible
|
|
360
|
+
|
|
361
|
+
#### FolderListComponent
|
|
362
|
+
|
|
363
|
+
**Selector:** `lib-folder-list`
|
|
364
|
+
|
|
365
|
+
Simple CRUD list with folder icon, name, slug, and created date columns.
|
|
366
|
+
|
|
367
|
+
#### StorageConfigListComponent
|
|
368
|
+
|
|
369
|
+
**Selector:** `lib-storage-config-list`
|
|
370
|
+
|
|
371
|
+
Lists storage configurations with provider icons and tags.
|
|
429
372
|
|
|
430
373
|
### Form Components
|
|
431
374
|
|
|
432
|
-
All form components share:
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
**FileManagerFormComponent** additionally supports:
|
|
443
|
-
- PrimeNG file upload with drag-drop
|
|
444
|
-
- Storage config selection dropdown
|
|
445
|
-
- Folder assignment
|
|
446
|
-
- Auto-filling filename from uploaded file
|
|
447
|
-
|
|
448
|
-
**StorageConfigFormComponent** has dynamic form fields based on selected provider:
|
|
449
|
-
- AWS S3: region, bucket, accessKeyId, secretAccessKey, endpoint
|
|
450
|
-
- Azure Blob: accountName, accountKey, containerName
|
|
451
|
-
- SFTP: host, port, username, password, basePath
|
|
452
|
-
- Local: basePath
|
|
453
|
-
- **Active toggle** - enables/disables the configuration
|
|
454
|
-
- **Set as Default toggle** - marks configuration as the default for uploads
|
|
455
|
-
|
|
456
|
-
**Default Configuration:**
|
|
457
|
-
When `isDefault: true`, this configuration will be used automatically when no `storageConfigId` is provided in upload requests.
|
|
375
|
+
All form components share: Signal Forms with `form()` + `required()` validators, create/edit mode detection via `:id` route param, `effect()` + `toSignal(route.paramMap)` initialization, navigation back on save/cancel, computed validation, and consistent button styling (`Cancel: secondary outlined`, `Submit: primary`, order: Cancel → Submit, container: `flex justify-end gap-2 mt-4`).
|
|
376
|
+
|
|
377
|
+
| Component | Selector | Form Fields | Notes |
|
|
378
|
+
|-----------|----------|-------------|-------|
|
|
379
|
+
| FileManagerForm | `lib-file-manager-form` | `id`, `name`, `isPrivate`, `folderId`, `storageConfigId` | PrimeNG file upload (50MB max), storage config required for upload, edit mode shows metadata only |
|
|
380
|
+
| FolderForm | `lib-folder-form` | `id`, `name` | Simple name-only form |
|
|
381
|
+
| StorageConfigForm | `lib-storage-config-form` | `id`, `name`, `storage`, `isActive`, `isDefault`, provider-specific fields | Dynamic fields based on provider; defaults: `storage: AWS`, `isActive: true`, `sftpPort: '22'` |
|
|
382
|
+
|
|
383
|
+
**FileManagerForm Upload Flow:** 1) Upload file via `uploadSingleFile()` 2) Create metadata via `createFileAsync()`. Edit mode only calls `updateAsync()`.
|
|
458
384
|
|
|
459
385
|
---
|
|
460
386
|
|
|
461
387
|
## Utility Functions
|
|
462
388
|
|
|
463
|
-
The `utils/storage-utils.ts` file provides helper functions
|
|
389
|
+
The `utils/storage-utils.ts` file provides helper functions:
|
|
464
390
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
getProviderLabel(storage
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
// Get severity for p-tag based on provider
|
|
475
|
-
getProviderSeverity(storage: FileLocationEnum): 'success' | 'info' | 'warn' | 'danger' | 'secondary' | 'contrast';
|
|
476
|
-
|
|
477
|
-
// Get file icon based on content type
|
|
478
|
-
getFileIcon(contentType: string): string;
|
|
479
|
-
// Returns: 'pi pi-image', 'pi pi-video', 'pi pi-file-pdf', etc.
|
|
480
|
-
|
|
481
|
-
// Format file size to human readable
|
|
482
|
-
formatFileSize(bytes: number): string;
|
|
483
|
-
// Returns: '1.5 MB', '256 KB', etc.
|
|
484
|
-
|
|
485
|
-
// Storage provider options for dropdowns
|
|
486
|
-
const STORAGE_PROVIDERS = [
|
|
487
|
-
{ label: 'AWS S3', value: FileLocationEnum.AWS },
|
|
488
|
-
{ label: 'Azure Blob Storage', value: FileLocationEnum.AZURE },
|
|
489
|
-
{ label: 'SFTP', value: FileLocationEnum.SFTP },
|
|
490
|
-
{ label: 'Local Storage', value: FileLocationEnum.LOCAL },
|
|
491
|
-
];
|
|
492
|
-
```
|
|
391
|
+
| Function | Returns | Description |
|
|
392
|
+
|----------|---------|-------------|
|
|
393
|
+
| `getProviderLabel(storage)` | `string` | Display label: 'AWS S3', 'Azure Blob', 'SFTP', 'Local' |
|
|
394
|
+
| `getProviderIcon(storage)` | `string` | Icon class: `pi pi-cloud`, `pi pi-server`, `pi pi-folder` |
|
|
395
|
+
| `getProviderSeverity(storage)` | severity | Tag severity: `warn` (AWS), `info` (Azure), `contrast` (SFTP), `secondary` (Local) |
|
|
396
|
+
| `getFileIcon(contentType)` | `string` | File icon based on MIME type (image, video, audio, pdf, word, excel, generic) |
|
|
397
|
+
| `formatFileSize(bytes)` | `string` | Human readable: '256 KB', '1.5 MB', '2.3 GB' |
|
|
398
|
+
|
|
399
|
+
**Constants:** `STORAGE_PROVIDERS` - Dropdown options array with `{ label, value }` for AWS S3, Azure Blob, SFTP, Local Storage.
|
|
493
400
|
|
|
494
401
|
---
|
|
495
402
|
|
|
@@ -498,24 +405,13 @@ const STORAGE_PROVIDERS = [
|
|
|
498
405
|
### Upload a File
|
|
499
406
|
|
|
500
407
|
```typescript
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
storageConfigId: 'my-config-id',
|
|
509
|
-
folderPath: 'images/avatars',
|
|
510
|
-
compress: true,
|
|
511
|
-
maxWidth: 800,
|
|
512
|
-
maxHeight: 800,
|
|
513
|
-
quality: 80,
|
|
514
|
-
format: ImageFormat.WEBP,
|
|
515
|
-
})
|
|
516
|
-
);
|
|
517
|
-
return response.success ? response.data : null;
|
|
518
|
-
}
|
|
408
|
+
const response = await firstValueFrom(
|
|
409
|
+
this.uploadService.uploadSingleFile(file, {
|
|
410
|
+
storageConfigId: 'my-config-id', folderPath: 'images/avatars',
|
|
411
|
+
compress: true, maxWidth: 800, maxHeight: 800, quality: 80, format: ImageFormat.WEBP,
|
|
412
|
+
})
|
|
413
|
+
);
|
|
414
|
+
return response.success ? response.data : null;
|
|
519
415
|
```
|
|
520
416
|
|
|
521
417
|
### Upload and Register (Combined)
|
|
@@ -523,100 +419,55 @@ async uploadImage(file: File): Promise<IUploadedFileInfo | null> {
|
|
|
523
419
|
Use `uploadAndRegister` for a single call that uploads and creates the file manager record:
|
|
524
420
|
|
|
525
421
|
```typescript
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
async uploadProfilePicture(file: File): Promise<IFileManager | null> {
|
|
531
|
-
const response = await firstValueFrom(
|
|
532
|
-
this.uploadService.uploadAndRegister(file, {
|
|
533
|
-
compress: true,
|
|
534
|
-
quality: 85,
|
|
535
|
-
isPrivate: false,
|
|
536
|
-
folderId: 'avatars-folder-id',
|
|
537
|
-
})
|
|
538
|
-
);
|
|
539
|
-
return response.success ? response.data : null;
|
|
540
|
-
}
|
|
422
|
+
const response = await firstValueFrom(
|
|
423
|
+
this.uploadService.uploadAndRegister(file, { compress: true, quality: 85, isPrivate: false, folderId: 'avatars-folder-id' })
|
|
424
|
+
);
|
|
425
|
+
return response.success ? response.data : null;
|
|
541
426
|
```
|
|
542
427
|
|
|
543
428
|
### Get Files with Presigned URLs
|
|
544
429
|
|
|
545
430
|
```typescript
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
private readonly fileService = inject(FileManagerApiService);
|
|
549
|
-
|
|
550
|
-
async getFileUrls(ids: string[]): Promise<IFilesResponseDto[]> {
|
|
551
|
-
const response = await firstValueFrom(this.fileService.getFiles(ids));
|
|
552
|
-
return response.data ?? [];
|
|
553
|
-
}
|
|
431
|
+
const response = await firstValueFrom(this.fileService.getFiles(['file-id-1', 'file-id-2']));
|
|
432
|
+
// response.data → [{ id, name, contentType, url }]
|
|
554
433
|
```
|
|
555
434
|
|
|
556
|
-
### Delete
|
|
435
|
+
### Delete Files
|
|
557
436
|
|
|
558
|
-
|
|
437
|
+
| Mode | Type | Description |
|
|
438
|
+
|------|------|-------------|
|
|
439
|
+
| Soft delete | `type: 'delete'` | Marks as deleted (recoverable) |
|
|
440
|
+
| Permanent delete | `type: 'permanent'` | Removes from storage provider (irreversible) |
|
|
559
441
|
|
|
560
442
|
```typescript
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
message: `Delete "${file.name}"?`,
|
|
565
|
-
header: 'Delete File',
|
|
566
|
-
acceptButtonStyleClass: 'p-button-danger',
|
|
567
|
-
accept: async () => {
|
|
568
|
-
await this.fileService.deleteAsync({ id: file.id, type: 'delete' });
|
|
569
|
-
this.loadFiles();
|
|
570
|
-
},
|
|
571
|
-
});
|
|
572
|
-
}
|
|
443
|
+
await this.fileService.deleteAsync({ id: file.id, type: 'delete' }); // Soft
|
|
444
|
+
await this.fileService.deleteAsync({ id: file.id, type: 'permanent' }); // Permanent
|
|
445
|
+
```
|
|
573
446
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
await this.fileService.deleteAsync({ id: file.id, type: 'permanent' });
|
|
585
|
-
this.loadFiles();
|
|
586
|
-
},
|
|
587
|
-
});
|
|
588
|
-
}
|
|
447
|
+
### Create Storage Configuration
|
|
448
|
+
|
|
449
|
+
```typescript
|
|
450
|
+
await this.configService.insertAsync({
|
|
451
|
+
name: 'Production AWS S3',
|
|
452
|
+
storage: FileLocationEnum.AWS,
|
|
453
|
+
config: { region: 'us-east-1', bucket: 'my-bucket', accessKeyId: 'AKIA...', secretAccessKey: '...' },
|
|
454
|
+
isActive: true,
|
|
455
|
+
isDefault: true,
|
|
456
|
+
});
|
|
589
457
|
```
|
|
590
458
|
|
|
591
459
|
---
|
|
592
460
|
|
|
593
461
|
## Backend Integration
|
|
594
462
|
|
|
595
|
-
All endpoints use **POST-only RPC** style (not REST).
|
|
596
|
-
|
|
597
|
-
| Service |
|
|
598
|
-
|
|
599
|
-
| Upload |
|
|
600
|
-
|
|
|
601
|
-
|
|
|
602
|
-
|
|
|
603
|
-
| Upload | `GET /storage/upload/file/:filePath` | Direct file download |
|
|
604
|
-
| FileManager | `POST /storage/file-manager/get-files` | Get files with presigned URLs |
|
|
605
|
-
| FileManager | `POST /storage/file-manager/insert` | Create file metadata |
|
|
606
|
-
| FileManager | `POST /storage/file-manager/get/:id` | Get file by ID |
|
|
607
|
-
| FileManager | `POST /storage/file-manager/get-all` | List files (paginated) |
|
|
608
|
-
| FileManager | `POST /storage/file-manager/update` | Update file metadata |
|
|
609
|
-
| FileManager | `POST /storage/file-manager/delete` | Delete file |
|
|
610
|
-
| Folder | `POST /storage/folder/insert` | Create folder |
|
|
611
|
-
| Folder | `POST /storage/folder/get/:id` | Get folder by ID |
|
|
612
|
-
| Folder | `POST /storage/folder/get-all` | List folders |
|
|
613
|
-
| Folder | `POST /storage/folder/update` | Update folder |
|
|
614
|
-
| Folder | `POST /storage/folder/delete` | Delete folder |
|
|
615
|
-
| StorageConfig | `POST /storage/storage-config/insert` | Create config |
|
|
616
|
-
| StorageConfig | `POST /storage/storage-config/get/:id` | Get config by ID |
|
|
617
|
-
| StorageConfig | `POST /storage/storage-config/get-all` | List configs |
|
|
618
|
-
| StorageConfig | `POST /storage/storage-config/update` | Update config |
|
|
619
|
-
| StorageConfig | `POST /storage/storage-config/delete` | Delete config |
|
|
463
|
+
All endpoints use **POST-only RPC** style (not REST). Base paths:
|
|
464
|
+
|
|
465
|
+
| Service | Base Path | Standard CRUD |
|
|
466
|
+
|---------|-----------|---------------|
|
|
467
|
+
| Upload | `/storage/upload` | Custom methods (see Services section) |
|
|
468
|
+
| FileManager | `/storage/file-manager` | `insert`, `get/:id`, `get-all`, `update`, `delete` + `get-files` |
|
|
469
|
+
| Folder | `/storage/folder` | `insert`, `get/:id`, `get-all`, `update`, `delete` |
|
|
470
|
+
| StorageConfig | `/storage/storage-config` | `insert`, `get/:id`, `get-all`, `update`, `delete` |
|
|
620
471
|
|
|
621
472
|
---
|
|
622
473
|
|
|
@@ -627,10 +478,8 @@ All endpoints use **POST-only RPC** style (not REST).
|
|
|
627
478
|
Never construct file URLs manually. Use `FileManagerApiService.getFiles()` or `FileUrlService` from ng-shared.
|
|
628
479
|
|
|
629
480
|
```typescript
|
|
630
|
-
// Wrong:
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
// Correct: backend-generated presigned URL
|
|
481
|
+
// Wrong: const url = `${apiBaseUrl}/storage/${fileId}`;
|
|
482
|
+
// Correct:
|
|
634
483
|
const files = await firstValueFrom(this.fileService.getFiles([fileId]));
|
|
635
484
|
const url = files.data?.[0]?.url ?? null;
|
|
636
485
|
```
|
|
@@ -638,70 +487,34 @@ const url = files.data?.[0]?.url ?? null;
|
|
|
638
487
|
### 2. Specify Storage Config for Multi-Provider Setups
|
|
639
488
|
|
|
640
489
|
```typescript
|
|
641
|
-
|
|
642
|
-
await firstValueFrom(
|
|
643
|
-
this.uploadService.uploadSingleFile(file, {
|
|
644
|
-
storageConfigId: 'aws-media-bucket',
|
|
645
|
-
folderPath: 'images',
|
|
646
|
-
})
|
|
647
|
-
);
|
|
490
|
+
this.uploadService.uploadSingleFile(file, { storageConfigId: 'aws-media-bucket', folderPath: 'images' });
|
|
648
491
|
```
|
|
649
492
|
|
|
650
493
|
### 3. Compress Images for Web
|
|
651
494
|
|
|
652
495
|
```typescript
|
|
653
|
-
|
|
654
|
-
compress: true,
|
|
655
|
-
maxWidth: 1920,
|
|
656
|
-
maxHeight: 1080,
|
|
657
|
-
quality: 85,
|
|
658
|
-
format: ImageFormat.WEBP,
|
|
659
|
-
};
|
|
496
|
+
{ compress: true, maxWidth: 1920, maxHeight: 1080, quality: 85, format: ImageFormat.WEBP }
|
|
660
497
|
```
|
|
661
498
|
|
|
662
|
-
### 4. Provide
|
|
499
|
+
### 4. Provide Required Services at App Level
|
|
663
500
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
```typescript
|
|
667
|
-
export const appConfig: ApplicationConfig = {
|
|
668
|
-
providers: [ConfirmationService, MessageService],
|
|
669
|
-
};
|
|
670
|
-
```
|
|
501
|
+
Provide `ConfirmationService` and `MessageService` in `app.config.ts` for dialogs and toasts.
|
|
671
502
|
|
|
672
503
|
### 5. Enable Storage in APP_CONFIG
|
|
673
504
|
|
|
674
505
|
```typescript
|
|
675
|
-
|
|
676
|
-
services: {
|
|
677
|
-
storage: { baseUrl: 'http://localhost:2002/storage', enabled: true },
|
|
678
|
-
}
|
|
506
|
+
services: { storage: { baseUrl: 'http://localhost:2002/storage', enabled: true } }
|
|
679
507
|
```
|
|
680
508
|
|
|
681
509
|
### 6. Responsive Design Patterns
|
|
682
510
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
<p-button label="New" styleClass="w-full sm:w-auto" />
|
|
691
|
-
|
|
692
|
-
<!-- Form grids: stack on mobile, 2 columns on md+ -->
|
|
693
|
-
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
694
|
-
|
|
695
|
-
<!-- Labels: smaller text on mobile -->
|
|
696
|
-
<label class="text-sm sm:text-base">Label</label>
|
|
697
|
-
|
|
698
|
-
<!-- Table columns: hide on smaller screens -->
|
|
699
|
-
<th class="hidden sm:table-cell">Column</th>
|
|
700
|
-
<th class="hidden md:table-cell">Column</th>
|
|
701
|
-
|
|
702
|
-
<!-- Tab navigation: horizontal scroll without visible scrollbar -->
|
|
703
|
-
<div class="scrollbar-hide flex overflow-x-auto">
|
|
704
|
-
```
|
|
511
|
+
| Pattern | Classes |
|
|
512
|
+
|---------|---------|
|
|
513
|
+
| Titles | `text-lg sm:text-xl` |
|
|
514
|
+
| Buttons | `w-full sm:w-auto` |
|
|
515
|
+
| Form grids | `grid grid-cols-1 md:grid-cols-2 gap-4` |
|
|
516
|
+
| Table columns | `hidden sm:table-cell`, `hidden md:table-cell`, etc. |
|
|
517
|
+
| Tab navigation | `scrollbar-hide flex overflow-x-auto` |
|
|
705
518
|
|
|
706
519
|
---
|
|
707
520
|
|
|
@@ -712,83 +525,32 @@ All components follow these responsive patterns:
|
|
|
712
525
|
Registers provider adapters that enable other packages (ng-auth) to access storage functionality without direct dependencies.
|
|
713
526
|
|
|
714
527
|
```typescript
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
// app.config.ts
|
|
718
|
-
export const appConfig: ApplicationConfig = {
|
|
719
|
-
providers: [
|
|
720
|
-
...provideStorageProviders(), // Register storage providers
|
|
721
|
-
],
|
|
722
|
-
};
|
|
528
|
+
// app.config.ts - add to providers array
|
|
529
|
+
...provideStorageProviders()
|
|
723
530
|
```
|
|
724
531
|
|
|
725
|
-
**Registers:**
|
|
726
|
-
|
|
727
|
-
| Token | Adapter | Purpose |
|
|
728
|
-
|-------|---------|---------|
|
|
729
|
-
| `PROFILE_UPLOAD_PROVIDER` | `ProfileUploadProviderAdapter` | Profile picture upload for ng-auth |
|
|
730
|
-
|
|
731
|
-
### ProfileUploadProviderAdapter
|
|
732
|
-
|
|
733
|
-
Implements `IProfileUploadProvider` from ng-shared. Enables ng-auth's profile page to upload profile pictures.
|
|
532
|
+
**Registers:** `PROFILE_UPLOAD_PROVIDER` → `ProfileUploadProviderAdapter` (implements `IProfileUploadProvider` from ng-shared)
|
|
734
533
|
|
|
735
|
-
|
|
736
|
-
interface IProfileUploadProvider {
|
|
737
|
-
uploadProfilePicture(
|
|
738
|
-
file: File,
|
|
739
|
-
options?: IProfileUploadOptions
|
|
740
|
-
): Observable<ISingleResponse<IProfileUploadResult>>;
|
|
741
|
-
}
|
|
742
|
-
```
|
|
534
|
+
**ProfileUploadProviderAdapter defaults:** `folderPath: 'profile-pictures'`, `compress: true`, `maxWidth: 400`, `maxHeight: 400`
|
|
743
535
|
|
|
744
|
-
**Usage
|
|
745
|
-
|
|
746
|
-
```typescript
|
|
747
|
-
// Optional injection - graceful degradation when storage disabled
|
|
748
|
-
private readonly uploadProvider = inject(PROFILE_UPLOAD_PROVIDER, { optional: true });
|
|
749
|
-
|
|
750
|
-
// Upload profile picture
|
|
751
|
-
if (this.uploadProvider) {
|
|
752
|
-
this.uploadProvider.uploadProfilePicture(file, { compress: true }).subscribe(response => {
|
|
753
|
-
// response.data.id contains the file manager ID
|
|
754
|
-
this.updateUserProfile({ profilePictureId: response.data.id });
|
|
755
|
-
});
|
|
756
|
-
}
|
|
757
|
-
```
|
|
536
|
+
**Usage:** `inject(PROFILE_UPLOAD_PROVIDER, { optional: true })?.uploadProfilePicture(file)`
|
|
758
537
|
|
|
759
538
|
---
|
|
760
539
|
|
|
761
540
|
## Public API Exports
|
|
762
541
|
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
IStorageConfig, ICreateStorageConfigDto, IUpdateStorageConfigDto,
|
|
773
|
-
IAwsS3Config, IAzureBlobConfig, ISftpConfig, ILocalStorageConfig,
|
|
774
|
-
IUploadOptionsDto, IUploadedFileInfo,
|
|
775
|
-
} from '@flusys/ng-storage';
|
|
776
|
-
|
|
777
|
-
// Services
|
|
778
|
-
export {
|
|
779
|
-
FileManagerApiService, FolderApiService,
|
|
780
|
-
StorageConfigApiService, UploadService,
|
|
781
|
-
} from '@flusys/ng-storage';
|
|
782
|
-
|
|
783
|
-
// Routes & Components
|
|
784
|
-
export { STORAGE_ROUTES } from '@flusys/ng-storage';
|
|
785
|
-
export { StorageContainerComponent } from '@flusys/ng-storage';
|
|
786
|
-
|
|
787
|
-
// Provider Functions
|
|
788
|
-
export { provideStorageProviders } from '@flusys/ng-storage';
|
|
789
|
-
```
|
|
542
|
+
| Category | Exports |
|
|
543
|
+
|----------|---------|
|
|
544
|
+
| Enums | `FileLocationEnum`, `ImageFormat` |
|
|
545
|
+
| Interfaces | `IFileManager`, `ICreateFileManagerDto`, `IUpdateFileManagerDto`, `IGetFilesRequestDto`, `IFilesResponseDto`, `IFolder`, `ICreateFolderDto`, `IUpdateFolderDto`, `IStorageConfig`, `ICreateStorageConfigDto`, `IUpdateStorageConfigDto`, `IAwsS3Config`, `IAzureBlobConfig`, `ISftpConfig`, `ILocalStorageConfig`, `IUploadOptionsDto`, `IUploadedFileInfo` |
|
|
546
|
+
| Utils | `getProviderLabel`, `getProviderIcon`, `getProviderSeverity`, `getFileIcon`, `formatFileSize`, `STORAGE_PROVIDERS`, `BaseStoragePage`, `BaseStorageListPage` |
|
|
547
|
+
| Adapters | `ProfileUploadProviderAdapter` |
|
|
548
|
+
| Services | `FileManagerApiService`, `FolderApiService`, `StorageConfigApiService`, `UploadService` |
|
|
549
|
+
| Routes | `STORAGE_ROUTES`, `StorageContainerComponent` |
|
|
550
|
+
| Providers | `provideStorageProviders` |
|
|
790
551
|
|
|
791
552
|
---
|
|
792
553
|
|
|
793
|
-
**Last Updated:** 2026-02-
|
|
554
|
+
**Last Updated:** 2026-02-25
|
|
555
|
+
**Version:** 3.0.1
|
|
794
556
|
**Angular Version:** 21
|