diff --git a/docker-compose.yml b/docker-compose.yml index 2c24df705..b54cf5170 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -271,6 +271,9 @@ services: ISSUE_PERMIT_LIMIT: ${ISSUE_PERMIT_LIMIT} DOC_GEN_LIMIT: ${DOC_GEN_LIMIT} PERMIT_SCHEDULE_POLLING_INTERVAL: ${PERMIT_SCHEDULE_POLLING_INTERVAL} + GARMS_HOST: ${GARMS_HOST} + GARMS_USER: ${GARMS_USER} + GARMS_PWD: ${GARMS_PWD} healthcheck: test: "curl --silent --fail http://localhost:5050/ > /dev/null || exit 1" interval: 1m30s diff --git a/scheduler/Dockerfile b/scheduler/Dockerfile index df3605d48..6ab38e5b5 100644 --- a/scheduler/Dockerfile +++ b/scheduler/Dockerfile @@ -1,5 +1,6 @@ # Build container FROM node:20.15.1-alpine AS builder +RUN apk update && apk add lftp # Set the working directory to /app inside the container WORKDIR /app @@ -19,11 +20,15 @@ RUN npm prune --production # Deployment container FROM node:20.15.1-alpine + +RUN apk update && apk add lftp + RUN npm cache clean --force # Create and Assign permissions to npm folder RUN mkdir /.npm && chmod 777 /.npm + # Set the working directory to /app inside the deployment container WORKDIR /app @@ -68,6 +73,10 @@ ENV ACCESS_API_URL ${ACCESS_API_URL} ENV ISSUE_PERMIT_LIMIT ${ISSUE_PERMIT_LIMIT} ENV DOC_GEN_LIMIT ${DOC_GEN_LIMIT} ENV PERMIT_SCHEDULE_POLLING_INTERVAL ${PERMIT_SCHEDULE_POLLING_INTERVAL} +ENV GARMS_HOST ${GARMS_HOST} +ENV GARMS_USER ${GARMS_USER} +ENV GARMS_PWD ${GARMS_PWD} + # Copy production files from build COPY --from=builder /app/package*.json ./ diff --git a/scheduler/package-lock.json b/scheduler/package-lock.json index 19f3851bb..af885de21 100644 --- a/scheduler/package-lock.json +++ b/scheduler/package-lock.json @@ -27,12 +27,15 @@ "@nestjs/testing": "^10.4.15", "@nestjs/typeorm": "^10.0.2", "@types/crypto-js": "^4.2.2", + "@types/ftps": "^1.2.2", + "basic-ftp": "^5.0.5", "cache-manager": "^5.7.6", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "crypto-js": "^4.2.0", "dayjs": "^1.11.13", "dotenv": "^16.4.7", + "ftps": "^1.2.0", "jwks-rsa": "^3.1.0", "mssql": "^10.0.4", "nest-winston": "^1.10.0", @@ -4023,6 +4026,11 @@ "@types/send": "*" } }, + "node_modules/@types/ftps": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/ftps/-/ftps-1.2.2.tgz", + "integrity": "sha512-Y8dYcfh7LTgCBjVsNPDUDelaewFmKUaIMqLfN5NQs106zURjxQ77qFbugC9C8jhlO6REPpxUt5+qrjmSgNNhFw==" + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -5092,6 +5100,14 @@ } ] }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -6252,6 +6268,11 @@ "node": ">=12" } }, + "node_modules/duplex-child-process": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/duplex-child-process/-/duplex-child-process-0.0.5.tgz", + "integrity": "sha512-3WVvFnyEYmFYXi2VB9z9XG8y4MbCMEPYrSGYROY3Pp7TT5qsyrdv+rZS6ydjQvTegHMc00pbrl4V/OOwrzo1KQ==" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -7697,6 +7718,18 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/ftps": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ftps/-/ftps-1.2.0.tgz", + "integrity": "sha512-0nPYjr8oafrJZF0XGVLRsWvKyl7kVpIb4E5IPVcMCTmRnzLfgA821daRZTjVB8+Gb8EZu1n4J+iphtKWWeKESA==", + "dependencies": { + "duplex-child-process": "0.0.5", + "lodash": "^4.4.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -16905,6 +16938,11 @@ "@types/send": "*" } }, + "@types/ftps": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@types/ftps/-/ftps-1.2.2.tgz", + "integrity": "sha512-Y8dYcfh7LTgCBjVsNPDUDelaewFmKUaIMqLfN5NQs106zURjxQ77qFbugC9C8jhlO6REPpxUt5+qrjmSgNNhFw==" + }, "@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -17737,6 +17775,11 @@ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, + "basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==" + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -18570,6 +18613,11 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==" }, + "duplex-child-process": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/duplex-child-process/-/duplex-child-process-0.0.5.tgz", + "integrity": "sha512-3WVvFnyEYmFYXi2VB9z9XG8y4MbCMEPYrSGYROY3Pp7TT5qsyrdv+rZS6ydjQvTegHMc00pbrl4V/OOwrzo1KQ==" + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -19636,6 +19684,15 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "optional": true }, + "ftps": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ftps/-/ftps-1.2.0.tgz", + "integrity": "sha512-0nPYjr8oafrJZF0XGVLRsWvKyl7kVpIb4E5IPVcMCTmRnzLfgA821daRZTjVB8+Gb8EZu1n4J+iphtKWWeKESA==", + "requires": { + "duplex-child-process": "0.0.5", + "lodash": "^4.4.0" + } + }, "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", diff --git a/scheduler/package.json b/scheduler/package.json index 92e636dd3..b11330549 100644 --- a/scheduler/package.json +++ b/scheduler/package.json @@ -59,12 +59,15 @@ "@nestjs/testing": "^10.4.15", "@nestjs/typeorm": "^10.0.2", "@types/crypto-js": "^4.2.2", + "@types/ftps": "^1.2.2", + "basic-ftp": "^5.0.5", "cache-manager": "^5.7.6", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "crypto-js": "^4.2.0", "dayjs": "^1.11.13", "dotenv": "^16.4.7", + "ftps": "^1.2.0", "jwks-rsa": "^3.1.0", "mssql": "^10.0.4", "nest-winston": "^1.10.0", diff --git a/scheduler/src/app.module.ts b/scheduler/src/app.module.ts index f16244365..82aaf6d05 100644 --- a/scheduler/src/app.module.ts +++ b/scheduler/src/app.module.ts @@ -12,6 +12,7 @@ import { getTypeormLogLevel } from './common/helper/logger.helper'; import { CacheModule } from '@nestjs/cache-manager'; import { CgiSftpModule } from './modules/cgi-sftp/cgi-sftp.module'; import { PermitModule } from './modules/permit/permit.module'; +import { GarmsModule } from './modules/garms/garms.module'; const envPath = path.resolve(process.cwd() + '/../'); @Module({ @@ -44,6 +45,7 @@ const envPath = path.resolve(process.cwd() + '/../'); FeatureFlagsModule, CgiSftpModule, PermitModule, + GarmsModule, ], controllers: [AppController], providers: [AppService], diff --git a/scheduler/src/modules/garms/garms.controller.ts b/scheduler/src/modules/garms/garms.controller.ts new file mode 100644 index 000000000..e84533006 --- /dev/null +++ b/scheduler/src/modules/garms/garms.controller.ts @@ -0,0 +1,22 @@ +import { Controller, Get } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { GarmsService } from './garms.service'; + +@ApiTags('Health Check') +@Controller('garms') +export class GarmsController { + constructor(private readonly garmsService: GarmsService) {} + + @Get() + async getHello(): Promise { + return await this.garmsService.ftpsFile(); + } + @Get('getHello2') + getHello2() { + this.garmsService.ftpsFile2(); + } + @Get('getHello3') + async getHello3() { + console.log(await this.garmsService.ftpsFile3()); + } +} diff --git a/scheduler/src/modules/garms/garms.module.ts b/scheduler/src/modules/garms/garms.module.ts new file mode 100644 index 000000000..e3243af38 --- /dev/null +++ b/scheduler/src/modules/garms/garms.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { GarmsController } from './garms.controller'; +import { GarmsService } from './garms.service'; + +@Module({ + controllers: [GarmsController], + providers: [GarmsService], +}) +export class GarmsModule {} diff --git a/scheduler/src/modules/garms/garms.service.ts b/scheduler/src/modules/garms/garms.service.ts new file mode 100644 index 000000000..de209b3ee --- /dev/null +++ b/scheduler/src/modules/garms/garms.service.ts @@ -0,0 +1,107 @@ +import { Injectable } from '@nestjs/common'; +import { Client } from 'basic-ftp'; +import * as FTPS from 'ftps'; +import { exec } from 'child_process'; + +import * as fs from 'fs'; +import { promisify } from 'util'; + +@Injectable() +export class GarmsService { + private client: Client; + + + constructor() { + this.client = new Client(); + this.client.ftp.verbose = true; + } + + async ftpsFile(): Promise { + try { + console.log('GARMS HOST : ', process.env.GARMS_HOST); + console.log('GARMS USER : ', process.env.GARMS_USER); + console.log('GARMS PWD : ', process.env.GARMS_PWD); + // Connect to FTP server + await this.connectToFtp(); + console.log('Current working directory', await this.client.pwd()); + await this.client.cd('GARMSD.GA4701.WS.BATCH'); + console.log('pwd: ', await this.client.pwd()); + // await this.client.uploadFrom('Hello world', 'GARMD.GA4701.WS.BATCH(+1)'); + + // Upload file + // await this.client.uploadFrom(filePath, remotePath); + // console.log(`File uploaded: ${filePath} -> ${remotePath}`); + } catch (error) { + console.error('Error uploading file', error); + throw error; + } finally { + // Close the connection after uploading + this.client.close(); + } + return 'Vehicles Healthcheck!'; + } + async connectToFtp() { + try { + await this.client.access({ + host: process.env.GARMS_HOST, // Replace with your FTPS server host + user: process.env.GARMS_USER, // Your username for the FTPS server + password: process.env.GARMS_PASSWROD, // Your password + secure: true, // Enabling FTPS (SSL/TLS encryption) + secureOptions: { + rejectUnauthorized: false, // Depending on your certificate, this can be true or false + }, + }); + console.log('Connected to FTPS server'); + } catch (error) { + console.error('Error connecting to FTPS server', error); + throw error; + } + } + ftpsFile2() { + console.log('file data from hello2: ', fs.readFileSync('./dist/orbctest.txt', 'utf-8')); + const ftps = new FTPS({ + host: process.env.GARMS_HOST, // required + username: process.env.GARMS_USER, // Optional. Use empty username for anonymous access. + password: process.env.GARMS_PWD, // Required if username is not empty, except when requiresPassword: false + // protocol is added on beginning of host, ex : sftp://domain.com in this case + additionalLftpCommands: + 'set cache:enable no;set ftp:passive-mode on;set ftp:use-size no;set ftp:ssl-protect-data yes;set ftp:ssl-force yes;set ftps:initial-prot "P";set net:connection-limit 1;set net:max-retries 1;debug 3;', // Additional commands to pass to lftp, splitted by ';' + }); + const remoteFilePath = 'GARMD.GA4701.WS.BATCH(+1)'; + const localFilePath = './dist/orbctest.txt' + console.log('executing quote'); + ftps.raw('quote SITE LRecl=140') + console.log('executed quote'); + ftps.pwd().exec(console.log); + const remoteFile = "'GARMD.GA4701.WS.BATCH(+1)'"; + console.log('Remote file is: ',remoteFile) + ftps.raw(`put -a ${localFilePath} -o "'${remoteFilePath}'"`); + ftps.raw('quit') + ftps.pwd().exec(console.log); + } + + async ftpsFile3() { + const execPromise = promisify(exec); + try { + const remoteFilePath = 'GARMD.GA4701.WS.BATCH(+1)'; + const localFilePath = './dist/orbctest.txt' + const additionalParams = 'set cache:enable no;set ftp:passive-mode on;set ftp:use-size no;set ftp:ssl-protect-data yes;set ftp:ssl-force yes;set ftps:initial-prot P;set net:connection-limit 1;set net:max-retries 1;debug 5'; + + // Construct the lftp command + const command = ` + lftp -u ${process.env.GARMS_USER},${process.env.GARMS_PWD} ${process.env.GARMS_HOST} -e ${additionalParams} put -a ${localFilePath} -o "'${remoteFilePath}'"; quit;` + + // Execute the command using child_process + const { stdout, stderr } = await execPromise(command); + + if (stderr) { + throw new Error(`Error transferring file: ${stderr}`); + } + + return stdout; + } catch (error) { + console.error('Error:', error.message); + throw new Error(`File transfer failed: ${error.message}`); + } + } +}