NestJSでFTP

NestJSで nestjs-ftp を使用して、FTPによるファイル転送、取得、削除、ファイルリストの取得を行うサンプルプログラムです。
## 参考サイト
[Github - AbdessalemLetaief/nestjs-ftp](URL "https://github.com/AbdessalemLetaief/nestjs-ftp?tab=readme-ov-file#readmeURL")

https://github.com/AbdessalemLetaief/nestjs-ftp?tab=readme-ov-file#readmeURL

## 環境
AlmaLinux 8.8
NestJS 10.3.2
npm 10.8.0
vsftpd 3.0.3
```main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000, '0.0.0.0');
}
bootstrap();
```

## nestjs-ftpのインストール
```
$ npm install nestjs-ftp
```
自動的に```basic-ftp```もインストールされる

## AppModule
localhost(127.0.0.1)に対してFTPで、ユーザ：```hoge```、パスワード：```pass```で接続する。
```app.module.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { FtpModule } from 'nestjs-ftp';

@Module({
  imports: [
    FtpModule.forRootFtpAsync({
      useFactory: async ()=>{
        return {
          host:"localhost",
          password:"pass",
          port:21,
          user:"hoge",
          secure:false
        }
      }
    })
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```
## AppService
ユーザ```hoge```のホームディレクトリ(```/home/hoge/```)をローカルディレクトリとして、FTPによるファイルの転送、取得、削除、ファイルリストの取得を行う。
```app.service.ts
import { Injectable } from '@nestjs/common';
import { FtpService } from 'nestjs-ftp';
import { FileInfo } from 'basic-ftp';

@Injectable()
export class AppService {
  constructor(private readonly _ftpService: FtpService){}

  //ファイル転送
  async uploadFile(name: string): Promise<string> {
    try {
      await this._ftpService.upload(`/home/hoge/${name}`,name);
    } catch (error) {
      return "FTP-PUT Failed.";
    }
    return "FTP-PUT Completed.";
  }
  
  //ファイル取得
  async downloadFile(name: string): Promise<string> {
    try {
      await this._ftpService.downloadTo(`/home/hoge/${name}`,name);
    } catch (error) {
      return "FTP-GET Failed.";
    }
    return "FTP-GET Completed.";
  }

  //ファイル削除
  async deleteFile(name: string): Promise<string> {
    try {
      await this._ftpService.delete(name);
    } catch (error) {
      return "FTP-DEL Failed.";
    }
    return "FTP-DEL Completed.";
  }

  //ファイルリスト取得
  //ファイル名とサイズのリストを返す
  async getList(): Promise<object[]> {
    let fileInfoList: FileInfo[];
    let fileList: {name: string, size: string}[] = [];
    try {
      fileInfoList = await this._ftpService.list();
    } catch (error) {
      throw new Error(error);
    }
    fileInfoList.forEach(element => {
      fileList.push({name: element.name, size: String(element.size)});
    });
    return fileList;
  }
}
```
## AppController
### API仕様
- ```http://localhost:3000/ftp-put?name=testfile```
ローカルディレクトリにある```testfile```をFTPルートディレクトリに転送
- ```http://localhost:3000/ftp-get?name=testfile```
FTPルートディレクトリにある```testfile```をローカルディレクトリに転送(取得)
- ```http://localhost:3000/ftp-del?name=testfile```
FTPルートディレクトリにある```testfile```を削除
- ```http://localhost:3000/ftp-list```
FTPルートディレクトリにあるファイルのリスト(```{name:ファイル名, size:ファイルサイズ}```の配列)を応答(ブラウザに表示)
```app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { Query } from '@nestjs/common';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  getHello(): string {
    return this.appService.getHello();
  }

  @Get('ftp-put')
  uploadFile(@Query('name') name: string): Promise<string> {
    return this.appService.uploadFile(name);
  }

  @Get('ftp-get')
  downloadFile(@Query('name') name: string): Promise<string> {
    return this.appService.downloadFile(name);
  }

  @Get('ftp-del')
  deleteFile(@Query('name') name: string): Promise<string> {
    return this.appService.deleteFile(name);
  }

  @Get('ftp-list')
  getFileList(): Promise<object[]> {
    return this.appService.getList();
  }
}
```

## 確認方法
- FTPサーバ(vsftpd)は起動中
- ```$ npm run start```で実行
- ブラウザからAPI実行(アドレスバーにURLを入力)