diff --git a/README.md b/README.md index 8e0e1b4..9377330 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,8 @@ docker-compose up -d ``` > Note: `-d flag` is used to run the container in background +- This will start two app container instances, one db instance and an Nginx container acting as a load balancer for the two NestJS app containers. The configuration maps port 3000 of the host machine to the ports 3001 and 3002 which are in turn connected to the same ports of each app instances' docker containers, so we can access the load balancer by navigating to http://: in your web browser. + > If the NestJS applications fail to connect with the mongodb service inside the containers, try disabling your system firewall using: ``` sudo ufw disable @@ -47,15 +49,8 @@ docker ps -a docker images ``` -## Running docker image for nginx-lb - -- This will start an Nginx container acting as a load balancer for the two NestJS app containers. The configuration maps port 3000 of the host machine to the ports 3001 and 3002 which are in turn connected to the same ports of each app instances' docker containers, so we can access the load balancer by navigating to http://: in your web browser. +## Configuring nginx -``` -docker compose up -docker compose up -d --> To run the container in background - -``` - To change the protocol for load-balancing between the server instances, add one of the methods in the `upstream` block of [nginx.conf](load-balancer/nginx.conf) file. ``` diff --git a/load-balancer/Dockerfile b/load-balancer/Dockerfile deleted file mode 100644 index 29a53fa..0000000 --- a/load-balancer/Dockerfile +++ /dev/null @@ -1,10 +0,0 @@ -FROM nginx:latest - -RUN rm /etc/nginx/conf.d/default.conf - -COPY nginx.conf /etc/nginx/conf.d/ - -EXPOSE 3000 - -CMD ["nginx", "-g", "daemon off;"] - diff --git a/load-balancer/docker-compose.yml b/load-balancer/docker-compose.yml deleted file mode 100644 index b979a8f..0000000 --- a/load-balancer/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -version: '3' -networks: - bcloud-net: - external: true -services: - nginx: - build: . - ports: - - "3000:3000" - networks: - - bcloud-net - volumes: - - ./nginx.conf:/etc/nginx/nginx.conf diff --git a/load-balancer/nginx.conf b/load-balancer/nginx.conf deleted file mode 100644 index e628114..0000000 --- a/load-balancer/nginx.conf +++ /dev/null @@ -1,19 +0,0 @@ -events { - worker_connections 1024; -} - -http { - upstream app_servers { - server app_1:3001; - server app_2:3002; - } - - server { - - listen 3000; - - location / { - proxy_pass http://app_servers; - } - } -} diff --git a/server/Dockerfile b/server/Dockerfile index dd092bf..5a83d8c 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -10,5 +10,8 @@ ADD . /app ENV NODE_ENV=dev +EXPOSE 3001 +EXPOSE 3002 + CMD ["npm", "run", "start"] diff --git a/server/schema.validations.js b/server/schema.validations.js new file mode 100644 index 0000000..00f15e1 --- /dev/null +++ b/server/schema.validations.js @@ -0,0 +1,70 @@ +// import Joi from 'joi'; + +// export function validateRegister(body) { +// const schema = Joi.object({ +// email: Joi.string().email().min(3).required(), +// password: Joi.string().min(6).max(20).required(), +// name: Joi.string().min(3).max(24).required(), +// language: Joi.string().valid('tr', 'en').required(), +// platform: Joi.string().valid('Android', 'IOS').required(), +// timezone: Joi.number().required(), +// deviceId: Joi.string().min(4).required() +// }); +// return schema.validate(body); +// } + +// export function validateLogin(body) { +// const schema = Joi.object({ +// email: Joi.string().email().min(3).required(), +// password: Joi.string().min(6).max(20).required() +// }); +// return schema.validate(body); +// } + +// export function validateSendVerificationCode(body) { +// const schema = Joi.object({ +// email: Joi.string().email().min(3).required() +// }); +// return schema.validate(body); +// } + +// export function validateVerifyEmail(body) { +// const schema = Joi.object({ +// token: Joi.string().min(10).required(), +// code: Joi.string().length(4).required() +// }); +// return schema.validate(body); +// } + +// export function validateRefreshToken(body) { +// const schema = Joi.object({ +// refreshToken: Joi.string().min(10).required() +// }); +// return schema.validate(body); +// } + +// export function validateForgotPassword(body) { +// const schema = Joi.object({ +// password: Joi.string().min(6).max(20).required() +// }); +// return schema.validate(body); +// } + +// export function validateChangePassword(body) { +// const schema = Joi.object({ +// oldPassword: Joi.string().min(6).max(20).required(), +// newPassword: Joi.string().min(6).max(20).required() +// }); +// return schema.validate(body); +// } + +// export function validateEditUser(body) { +// const schema = Joi.object({ +// name: Joi.string().min(3).max(24), +// username: Joi.string().min(3).max(15), +// language: Joi.string().valid('tr', 'en'), +// gender: Joi.string().valid('male', 'female', 'other'), +// birthDate: Joi.date() +// }); +// return schema.validate(body); +// } diff --git a/server/src/files/files.controller.ts b/server/src/files/files.controller.ts index 83780cd..fc7f98b 100644 --- a/server/src/files/files.controller.ts +++ b/server/src/files/files.controller.ts @@ -1,7 +1,6 @@ -import { Controller, Get, Post, Put, Delete, UseGuards, Res, HttpStatus, Query, UploadedFiles, Param, Body } from '@nestjs/common'; +import { Controller, Get, Post, Delete, UseGuards, Res, HttpStatus, Query, UploadedFiles, Param, Body } from '@nestjs/common'; import { AuthGuard } from '@nestjs/passport'; import { ValidateObjectId } from '../shared/validate-object-id.pipes'; -import { FileDTO, IRole } from 'src/interfaces'; import { FilesService } from './files.service'; import { UploadedFile, UseInterceptors } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; @@ -46,10 +45,9 @@ export class FilesController { async deleteFile( @Query('fileID', new ValidateObjectId()) fileID, @Res() res, - // @CheckUserRole({ userIDKey: 'userID', fileIDKey: 'fileID', requiredRole: IRole.OWNER }) userFile: { userID: string; fileID: string }, ) { try { - await this.filesService.deleteFolder(fileID, 'all'); + await this.filesService.delete(fileID, 'all'); return res.status(HttpStatus.OK).json({ message: 'File deleted successfully' }); } catch (error) { return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({ message: 'Error deleting file' }); diff --git a/server/src/files/files.service.ts b/server/src/files/files.service.ts index a42ec42..0a8f9d6 100644 --- a/server/src/files/files.service.ts +++ b/server/src/files/files.service.ts @@ -52,9 +52,7 @@ export class FilesService { async divideFile(savedFile){ const pythonScriptPath = 'src/files/scripts/divider.py'; - const gemsFolderPath = path.join(__dirname, '..', '..', 'uploads/',`${savedFile.originalname}-${uuidv4()}${savedFile.extension}`); - const fileSize = (path.join(__dirname, '..', '..', 'uploads/',`${savedFile.fileName}`)); - const { stdout, stderr } = await execPromise(`python3 ${pythonScriptPath} ${savedFile.fileName} ${savedFile.extension}`) as child_process.ChildProcessWithoutNullStreams; + const { stdout, stderr } = await execPromise(`python3 ${pythonScriptPath} ${savedFile.fileName}`) as child_process.ChildProcessWithoutNullStreams; if(stderr) { this.loggerService.error(`divider.py: ${stderr}`); return stderr; @@ -63,15 +61,14 @@ export class FilesService { return gems; } - async combineFiles(fileName, originalname) : Promise { - const folderPath = path.join(__dirname, '..', '..', 'uploads', fileName); + async combineFiles(url: string, originalname: string) : Promise { + const folderPath = path.join(__dirname, '..', '..', 'uploads', url); const fileGems = await fs.promises.readdir(folderPath); const outputFilePath = path.join(folderPath, `${originalname}`); - - fileGems.sort((a, b) => Number(a.split('-').shift()) - Number(b.split('-').shift())); - const outputStream = fs.createWriteStream(outputFilePath); + fileGems.sort((a, b) => Number(a.split('-').shift()) - Number(b.split('-').shift())); + for (const gem of fileGems) { const gemPath = path.join(folderPath, gem); const readStream = fs.createReadStream(gemPath); @@ -118,7 +115,7 @@ export class FilesService { }); // creating gems and deleting temp image gems = await this.divideFile(savedFile); - this.deleteFile(`uploads/${savedFile.fileName}`) + this.deleteFileUtil(`uploads/${savedFile.fileName}`) this.loggerService.info(`createFile: File ${savedFile.fileName} saved to server`); }catch(error){ @@ -129,12 +126,12 @@ export class FilesService { // Saving to db try{ const createFileDTO = { - originalname: savedFile.originalname, - url: `${savedFile.fileName}${savedFile.extension}`, + originalname: `${savedFile.originalname}${savedFile.extension}`, + url: `${savedFile.fileName}.dir`, ownerID: userID, gems: [{ index: 0, - url: savedFile.fileName, //to be changed + url: `${savedFile.fileName}.dir`, //to be changed enc: "none" }] } @@ -150,7 +147,7 @@ export class FilesService { await this.userService.addFileToUser(userID.toString(), userFileRecord); this.loggerService.info(`File ${savedFile.fileName} added to db and user profile.`); }catch(error){ - this.deleteFile(`uploads/${savedFile.fileName}`) + this.deleteFileUtil(`uploads/${savedFile.fileName}`) await deleteFolderUtil(`uploads/${savedFile.fileName}${savedFile.extension}`, { recursive: true }); this.loggerService.error(`Unable to add file : ${savedFile.fileName} to db. Deleted from server. `); return error; @@ -168,10 +165,15 @@ export class FilesService { this.loggerService.info('Original file already exists, returning from cache.'); return path.join(__dirname, '..', '..', 'uploads', file.url, file.originalname); } - return this.combineFiles(file.url, file.originalname); + try{ + this.combineFiles(file.url, file.originalname); + }catch(err){ + this.loggerService.error(`downloadFile: ${err}`); + } + } - async deleteFile(url: string): Promise { + async deleteFileUtil(url: string): Promise { return new Promise(() => { fs.unlink(url, (error) => { if (error) { @@ -183,7 +185,7 @@ export class FilesService { }); } - async deleteFolder(fileID: ObjectId, location: string): Promise { + async delete(fileID: ObjectId, location: string): Promise { const file = await this.fileModel.findById(fileID.toString()); if(!file) { this.loggerService.error(`File with ID ${fileID} does not exist`) diff --git a/server/src/files/scripts/divider.py b/server/src/files/scripts/divider.py index 7b04fbf..562f28f 100644 --- a/server/src/files/scripts/divider.py +++ b/server/src/files/scripts/divider.py @@ -3,18 +3,17 @@ import math import json -if len(sys.argv) < 3: - print('Usage: python divider.py ') +if len(sys.argv) < 2: + print('Usage: python divider.py ') sys.exit(1) file_name = sys.argv[1] -extension = sys.argv[2] file_size = os.path.getsize(os.path.join('uploads', os.path.basename(file_name))) file_path = os.path.join('uploads', file_name) gem_size = 1024 * 1024 num_gems = math.ceil(file_size / gem_size) -output_dir = os.path.join('uploads', os.path.basename(file_name + extension)) +output_dir = os.path.join('uploads', os.path.basename(file_name+".dir")) os.makedirs(output_dir, exist_ok=True) diff --git a/server/src/user/schema/user.schema.ts b/server/src/user/schema/user.schema.ts index 285411a..453f9e1 100644 --- a/server/src/user/schema/user.schema.ts +++ b/server/src/user/schema/user.schema.ts @@ -8,13 +8,14 @@ export const userFileRecord = new mongoose.Schema({ required:true, }, fileID:{ - type: mongoose.Schema.Types.ObjectId, - required: true, + type: mongoose.Schema.Types.ObjectId, + ref: 'Files', + required: true, }, role: [{ - type: String, - enum: [IRole.VIEWER, IRole.EDITOR, IRole.OWNER], - required: true, + type: String, + enum: [IRole.VIEWER, IRole.EDITOR, IRole.OWNER], + required: true, }] });