npm package: @flusys/ng-storage
Description: File storage module for FLUSYS Angular applications
Latest version: 4.1.1

# @flusys/ng-storage

> File storage UI library for the FLUSYS Angular platform — file upload, folder management, file browser, presigned URL fetching, and provider adapter registration.

[![npm version](https://img.shields.io/npm/v/@flusys/ng-storage.svg)](https://www.npmjs.com/package/@flusys/ng-storage)
[![Angular](https://img.shields.io/badge/Angular-21-red.svg)](https://angular.io)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue.svg)](https://www.typescriptlang.org)
[![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)

---

## Table of Contents

- [Overview](#overview)
- [Features](#features)
- [Compatibility](#compatibility)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Module Registration](#module-registration)
- [File URL Rule](#file-url-rule)
- [Services](#services)
  - [FileManagerApiService](#filemanagerapiservice)
  - [FolderApiService](#folderapiservice)
  - [UploadService](#uploadservice)
- [Components](#components)
  - [FileManagerComponent](#filemanagercomponent)
  - [FileSelectorDialogComponent](#fileselectordialogcomponent)
  - [FileUploaderComponent](#fileuploadercomponent)
- [Storage Routes](#storage-routes)
- [File Location Enum](#file-location-enum)
- [API Endpoints](#api-endpoints)
- [Configuration Reference](#configuration-reference)
- [Troubleshooting](#troubleshooting)
- [License](#license)

---

## Overview

`@flusys/ng-storage` provides the complete file management UI for FLUSYS applications. It supports all four storage backends (Local, S3, Azure Blob, SFTP) transparently — the backend generates the correct presigned URLs for each provider.

**Critical rule:** Never construct file URLs manually. Always use `FileUrlService` (from `ng-shared`) or the `FILE_PROVIDER` token. The backend generates presigned URLs that are provider-agnostic and access-controlled.

---

## Features

- ✅ File browser with folder navigation
- ✅ Drag-and-drop upload with progress
- ✅ Multi-file selection dialog (`FileSelectorDialogComponent`)
- ✅ Image preview thumbnails
- ✅ Download and delete actions
- ✅ `UploadService` — typed multi-part file upload
- ✅ `provideStorageProviders()` — registers `FILE_PROVIDER` for shared components
- ✅ Presigned URL fetching via `FileUrlService`
- ✅ Support for `LOCAL`, `S3`, `AZURE`, `SFTP` backends

---

## Compatibility

| Package | Version |
|---------|---------|
| Angular | 21+ |
| @flusys/ng-core | 4.x |
| @flusys/ng-shared | 4.x |

---

## Installation

```bash
npm install @flusys/ng-storage @flusys/ng-core @flusys/ng-shared
```

---

## Quick Start

### 1. Enable Storage in Config

```typescript
// environments/environment.ts
export const environment = {
  services: {
    storage: { enabled: true },
  },
};
```

### 2. Register Storage Providers

```typescript
// app.config.ts
import { provideStorageProviders } from '@flusys/ng-storage';

export const appConfig: ApplicationConfig = {
  providers: [
    ...provideStorageProviders(),
    // Provides: FILE_PROVIDER (IFileProvider) — used by FileSelectorDialogComponent, FileUrlService
  ],
};
```

### 3. Add Storage Routes (Optional)

```typescript
// app.routes.ts
import { STORAGE_ROUTES } from '@flusys/ng-storage';

export const routes: Routes = [
  {
    path: 'storage',
    loadChildren: () => STORAGE_ROUTES,
  },
];
```

---

## Module Registration

### `provideStorageProviders()`

Registers the storage adapter against the `FILE_PROVIDER` token from `ng-shared`:

```typescript
import { provideStorageProviders } from '@flusys/ng-storage';

providers: [
  ...provideStorageProviders(),
]
```

This enables:
- `FileSelectorDialogComponent` (from `ng-shared`) to list/browse files
- `FileUrlService.fetchSingleFileUrl()` to work correctly
- `HasPermissionDirective` to show storage-gated content

---

## File URL Rule

**CRITICAL:** Never construct file URLs manually. The backend handles URL generation for all storage providers (Local, S3, Azure, SFTP) including presigned URL rotation.

```typescript
// ❌ WRONG — breaks with S3, Azure, SFTP
const url = `${apiBaseUrl}/uploads/${fileId}`;
const url = `https://my-bucket.s3.amazonaws.com/${path}`;

// ✅ CORRECT — always use FileUrlService
import { FileUrlService } from '@flusys/ng-shared';

const file = await this.fileUrlService.fetchSingleFileUrl(fileId);
const url = file?.url ?? null; // Presigned, access-controlled, provider-agnostic
```

**Why backend URLs:**
- S3/Azure require presigned URLs that expire
- URLs are access-controlled (user must have permission)
- Works transparently across all 4 storage providers
- Automatic URL refresh on expiry

---

## Services

### FileManagerApiService

Typed API client for file management operations:

```typescript
import { FileManagerApiService } from '@flusys/ng-storage';

@Injectable({ ... })
export class MyService {
  private fileApi = inject(FileManagerApiService);

  getFiles(folderId?: string): Observable<IListResponse<IFile>> {
    return this.fileApi.getAll({ filters: { folderId } });
  }

  getFileUrls(fileIds: string[]): Observable<ISingleResponse<IFileWithUrl[]>> {
    return this.fileApi.getFiles(fileIds);
  }

  deleteFile(id: string): Observable<IMessageResponse> {
    return this.fileApi.delete(id);
  }
}
```

**FileManagerApiService Methods:**

| Method | Endpoint | Description |
|--------|----------|-------------|
| `getAll(params)` | `POST /file-manager/get-all` | List files with pagination |
| `getById(id)` | `POST /file-manager/get/:id` | Get file metadata |
| `getFiles(ids[])` | `POST /file-manager/get-files` | Batch fetch with presigned URLs |
| `delete(id)` | `POST /file-manager/delete` | Delete file |
| `bulkDelete(ids[])` | `POST /file-manager/bulk-delete` | Delete multiple files |
| `move(id, folderId)` | `POST /file-manager/move` | Move file to folder |

### FolderApiService

Typed API client for folder management:

```typescript
import { FolderApiService } from '@flusys/ng-storage';

@Injectable({ ... })
export class MyService {
  private folderApi = inject(FolderApiService);

  getFolders(parentId?: string): Observable<IListResponse<IFolder>> {
    return this.folderApi.getAll({ filters: { parentId } });
  }

  createFolder(name: string, parentId?: string): Observable<ISingleResponse<IFolder>> {
    return this.folderApi.insert({ name, parentId });
  }
}
```

**FolderApiService Methods:**

| Method | Endpoint | Description |
|--------|----------|-------------|
| `getAll(params)` | `POST /folder/get-all` | List folders |
| `getById(id)` | `POST /folder/get/:id` | Get folder |
| `insert(dto)` | `POST /folder/insert` | Create folder |
| `update(dto)` | `POST /folder/update` | Rename folder |
| `delete(id)` | `POST /folder/delete` | Delete folder |

### UploadService

Handles multi-part file uploads with progress tracking:

```typescript
import { UploadService } from '@flusys/ng-storage';

@Component({ ... })
export class UploadComponent {
  private uploadService = inject(UploadService);

  uploadFile(file: File, folderId?: string): void {
    this.uploadService.upload(file, { folderId }).subscribe({
      next: (event) => {
        if (event.type === 'progress') {
          console.log(`Progress: ${event.percent}%`);
        }
        if (event.type === 'complete') {
          console.log('Uploaded:', event.file);
        }
      },
      error: (err) => console.error('Upload failed:', err),
    });
  }

  uploadMultiple(files: File[]): void {
    this.uploadService.uploadBatch(files).subscribe(results => {
      console.log(`Uploaded ${results.length} files`);
    });
  }
}
```

**UploadService API:**

| Method | Description |
|--------|-------------|
| `upload(file, options?)` | Single file upload with progress events |
| `uploadBatch(files, options?)` | Multiple file upload (sequential) |
| `cancel(uploadId)` | Cancel in-progress upload |

---

## Components

### FileManagerComponent

Full-page file browser with folder tree, grid/list view, upload, and actions.

```html
<!-- Full file manager -->
<flusys-file-manager />

<!-- With pre-selected folder -->
<flusys-file-manager [initialFolderId]="rootFolderId" />
```

Features:
- Folder tree (left panel)
- File grid with thumbnails (right panel)
- Drag-and-drop upload zone
- Right-click context menu (rename, move, delete)
- Breadcrumb navigation
- Search files

### FileSelectorDialogComponent

Dialog for selecting one or multiple files. Used by other packages (`ng-form-builder`, `ng-email`) to pick files.

```html
<flusys-file-selector-dialog
  [visible]="showPicker"
  [multiple]="true"
  [accept]="'image/*'"
  (fileSelected)="onFilesSelected($event)"
  (cancel)="showPicker = false"
/>
```

| Input | Type | Description |
|-------|------|-------------|
| `visible` | `boolean` | Show/hide dialog |
| `multiple` | `boolean` | Allow multi-select (default: false) |
| `accept` | `string` | MIME type filter |
| `folderId` | `string` | Pre-open specific folder |

| Output | Type | Description |
|--------|------|-------------|
| `fileSelected` | `IFile \| IFile[]` | Selected file(s) |
| `cancel` | `void` | Dialog dismissed |

### FileUploaderComponent

Lightweight standalone upload component (without the full browser):

```html
<flusys-file-uploader
  [multiple]="false"
  [accept]="'image/jpeg,image/png'"
  [maxSizeMb]="5"
  [folderId]="profilePicturesFolderId"
  (uploaded)="onAvatarUploaded($event)"
/>
```

---

## Storage Routes

Lazy-loaded pages for the full file manager:

```typescript
import { STORAGE_ROUTES } from '@flusys/ng-storage';

{ path: 'storage', loadChildren: () => STORAGE_ROUTES }
```

| Route | Component | Description |
|-------|-----------|-------------|
| `/storage` | `FileManagerPageComponent` | Full file manager page |
| `/storage/folder/:id` | `FileManagerPageComponent` | Open specific folder |

---

## File Location Enum

```typescript
import { FileLocationEnum } from '@flusys/ng-storage';

enum FileLocationEnum {
  LOCAL = 'LOCAL',
  S3 = 'S3',
  AZURE = 'AZURE',
  SFTP = 'SFTP',
}
```

Use `FileLocationEnum` when filtering or categorizing files by their storage backend.

---

## API Endpoints

| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/file-manager/get-all` | List files with pagination |
| POST | `/file-manager/get/:id` | Get file metadata |
| POST | `/file-manager/get-files` | Batch fetch with presigned URLs |
| POST | `/file-manager/upload` | Upload file (multipart) |
| POST | `/file-manager/delete` | Delete file |
| POST | `/file-manager/bulk-delete` | Delete multiple files |
| POST | `/file-manager/move` | Move file to folder |
| POST | `/folder/get-all` | List folders |
| POST | `/folder/get/:id` | Get folder metadata |
| POST | `/folder/insert` | Create folder |
| POST | `/folder/update` | Rename folder |
| POST | `/folder/delete` | Delete folder |

---

## Configuration Reference

| Config Key | Type | Default | Description |
|------------|------|---------|-------------|
| `services.storage.enabled` | `boolean` | `false` | Enable storage module |

The storage backend (LOCAL/S3/AZURE/SFTP) is configured server-side in `nestjs-storage`. The Angular client doesn't need to know which provider is active.

---

## Troubleshooting

**File thumbnails don't load**

Images are loaded via presigned URLs. Ensure `FileUrlService.fetchSingleFileUrl()` is used (not manual URL construction). Check the browser network tab — are the URLs returning 403?

**Upload fails with 413 Payload Too Large**

The backend has a file size limit. Configure `maxBodySize` in `nestjs-storage` on the server. On the Angular side, validate client-side before upload:

```html
<flusys-file-uploader [maxSizeMb]="10" />
```

**`No provider for FILE_PROVIDER`**

Add `provideStorageProviders()` to `app.config.ts` providers.

**FileSelectorDialog is empty (no files)**

Ensure the user has permission to list files (`file:read`) and the backend storage is configured. Check the network tab for errors on `POST /file-manager/get-all`.

**Files visible but download fails**

The presigned URL may have expired. Call `FileUrlService.clearCache(fileId)` to force a fresh URL fetch.

---

## License

MIT © FLUSYS