# @loma-portal/em-file-storage

S3-compatible file storage. Works against AWS S3, MinIO (self-hosted, our docker-compose adds it in Phase 8), Cloudflare R2, Wasabi, or any provider that speaks the S3 API.

## Usage

```ts
import { EmFileStorageModule, FILE_STORAGE, type FileStorage } from '@loma-portal/em-file-storage';

@Module({
  imports: [
    EmFileStorageModule.forRoot({
      bucket: process.env.S3_BUCKET!,
      region: process.env.S3_REGION!,
      endpoint: process.env.S3_ENDPOINT,            // optional — set for MinIO/R2
      accessKeyId: process.env.S3_ACCESS_KEY,
      secretAccessKey: process.env.S3_SECRET_KEY,
    }),
  ],
})
export class AppModule {}

// in a service
constructor(@Inject(FILE_STORAGE) private readonly storage: FileStorage) {}

await this.storage.put({ key: 'docs/abc.pdf', body: buffer, contentType: 'application/pdf' });
const url = await this.storage.getSignedDownloadUrl('docs/abc.pdf', { expiresIn: 3600 });
```

## Surface

| Method                    | Purpose                                              |
| ------------------------- | ---------------------------------------------------- |
| `put(input)`              | Upload object                                        |
| `delete(key)`             | Hard delete                                          |
| `getSignedDownloadUrl(k)` | Time-limited GET URL (default 15 min)                |
| `getSignedUploadUrl(k)`   | Time-limited PUT URL — frontend uploads direct to S3 |

## Conventions

- Keys are tenant-prefixed: `<tenantId>/<entity>/<id>/<filename>`.
- ACLs are private by default; share via signed URLs.
- Use `getSignedUploadUrl` from the frontend when uploading large files — keeps the API host out of the data path.
