diff --git a/package-lock.json b/package-lock.json index ca97217a53b4a430c05e6918557ed0280dca603c..fc64af1fe3071376a651e579f20e24e463367e01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,10 @@ "license": "ISC", "dependencies": { "@prisma/client": "^5.4.2", + "@types/multer": "^1.4.10", "argon2": "^0.31.1", "axios": "^1.6.1", + "basic-ftp": "^5.0.3", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.3.1", @@ -21,6 +23,7 @@ "jsonwebtoken": "^9.0.2", "nodemon": "^3.0.1", "ts-node": "^10.9.1", + "uuid": "^9.0.1", "winston": "^3.11.0", "zod": "^3.22.4" }, @@ -30,7 +33,9 @@ "@types/express": "^4.17.1", "@types/jest": "^29.5.6", "@types/jsonwebtoken": "^9.0.4", + "@types/node": "^20.9.0", "@types/supertest": "^2.0.15", + "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.8.0", "@typescript-eslint/parser": "^6.8.0", "eslint": "^8.52.0", @@ -1591,7 +1596,6 @@ "version": "1.19.4", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.4.tgz", "integrity": "sha512-N7UDG0/xiPQa2D/XrVJXjkWbpqHCd2sBaB32ggRF2l83RhPfamgKGF8gwwqyksS95qUS5ZYF9aF+lLPRlwI2UA==", - "dev": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -1601,7 +1605,6 @@ "version": "3.4.37", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.37.tgz", "integrity": "sha512-zBUSRqkfZ59OcwXon4HVxhx5oWCJmc0OtBTK05M+p0dYjgN6iTwIL2T/WbsQZrEsdnwaF9cWQ+azOnpPvIqY3Q==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -1634,7 +1637,6 @@ "version": "4.17.1", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.1.tgz", "integrity": "sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w==", - "dev": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "*", @@ -1645,7 +1647,6 @@ "version": "4.17.39", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.39.tgz", "integrity": "sha512-BiEUfAiGCOllomsRAZOiMFP7LAnrifHpt56pc4Z7l9K6ACyN06Ns1JLMBxwkfLOjJRlSf06NwWsT7yzfpaVpyQ==", - "dev": true, "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -1665,8 +1666,7 @@ "node_modules/@types/http-errors": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.3.tgz", - "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==", - "dev": true + "integrity": "sha512-pP0P/9BnCj1OVvQR2lF41EkDG/lWWnDyA203b/4Fmi2eTyORnBtcDoKDwjWQthELrBvWkMOrvSOnZ8OVlW6tXA==" }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.5", @@ -1720,28 +1720,33 @@ "node_modules/@types/mime": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.4.tgz", - "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==", - "dev": true + "integrity": "sha512-1Gjee59G25MrQGk8bsNvC6fxNiRgUlGn2wlhGf95a59DrprnnHk80FIMMFG9XHMdrfsuA119ht06QPDXA1Z7tw==" + }, + "node_modules/@types/multer": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.10.tgz", + "integrity": "sha512-6l9mYMhUe8wbnz/67YIjc7ZJyQNZoKq7fRXVf7nMdgWgalD0KyzJ2ywI7hoATUSXSbTu9q2HBiEwzy0tNN1v2w==", + "dependencies": { + "@types/express": "*" + } }, "node_modules/@types/node": { - "version": "20.8.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.7.tgz", - "integrity": "sha512-21TKHHh3eUHIi2MloeptJWALuCu5H7HQTdTrWIFReA8ad+aggoX+lRes3ex7/FtpC+sVUpFMQ+QTfYr74mruiQ==", + "version": "20.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz", + "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==", "dependencies": { - "undici-types": "~5.25.1" + "undici-types": "~5.26.4" } }, "node_modules/@types/qs": { "version": "6.9.9", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.9.tgz", - "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==", - "dev": true + "integrity": "sha512-wYLxw35euwqGvTDx6zfY1vokBFnsK0HNrzc6xNHchxfO2hpuRg74GbkEW7e3sSmPvj0TjCDT1VCa6OtHXnubsg==" }, "node_modules/@types/range-parser": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.6.tgz", - "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==", - "dev": true + "integrity": "sha512-+0autS93xyXizIYiyL02FCY8N+KkKPhILhcUSA276HxzreZ16kl+cmwvV2qAM/PuCCwPXzOXOWhiPcw20uSFcA==" }, "node_modules/@types/semver": { "version": "7.5.4", @@ -1753,7 +1758,6 @@ "version": "0.17.3", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.3.tgz", "integrity": "sha512-/7fKxvKUoETxjFUsuFlPB9YndePpxxRAOfGC/yJdc9kTjTeP5kRCTzfnE8kPUKCeyiyIZu0YQ76s50hCedI1ug==", - "dev": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -1763,7 +1767,6 @@ "version": "1.15.4", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.4.tgz", "integrity": "sha512-aqqNfs1XTF0HDrFdlY//+SGUxmdSUbjeRXb5iaZc3x0/vMbYmdw9qvOgHWOyyLFxSSRnUuP5+724zBgfw8/WAw==", - "dev": true, "dependencies": { "@types/http-errors": "*", "@types/mime": "*", @@ -1800,6 +1803,12 @@ "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.4.tgz", "integrity": "sha512-HlJjF3wxV4R2VQkFpKe0YqJLilYNgtRtsqqZtby7RkVsSs+i+vbyzjtUwpFEdUCKcrGzCiEJE7F/0mKjh0sunA==" }, + "node_modules/@types/uuid": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.7.tgz", + "integrity": "sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.29", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.29.tgz", @@ -2460,6 +2469,14 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/basic-ftp": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.3.tgz", + "integrity": "sha512-QHX8HLlncOLpy54mh+k/sWIFd0ThmRqwe9ZjELybGZK+tZ8rUb9VO0saKJUROTbE+KhzDUT7xziGpGrW8Kmd+g==", + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -6834,9 +6851,9 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==" }, "node_modules/undici-types": { - "version": "5.25.3", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", - "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==" + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" }, "node_modules/unpipe": { "version": "1.0.0", @@ -6898,6 +6915,18 @@ "node": ">= 0.4.0" } }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index a3eb89ca03bf568974f9330ddf1fd24a9aa1bf95..bf68eb9f1cd37bf91556db3604d140e600b56006 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,9 @@ "@types/express": "^4.17.1", "@types/jest": "^29.5.6", "@types/jsonwebtoken": "^9.0.4", + "@types/node": "^20.9.0", "@types/supertest": "^2.0.15", + "@types/uuid": "^9.0.7", "@typescript-eslint/eslint-plugin": "^6.8.0", "@typescript-eslint/parser": "^6.8.0", "eslint": "^8.52.0", @@ -36,8 +38,10 @@ }, "dependencies": { "@prisma/client": "^5.4.2", + "@types/multer": "^1.4.10", "argon2": "^0.31.1", "axios": "^1.6.1", + "basic-ftp": "^5.0.3", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "dotenv": "^16.3.1", @@ -47,6 +51,7 @@ "jsonwebtoken": "^9.0.2", "nodemon": "^3.0.1", "ts-node": "^10.9.1", + "uuid": "^9.0.1", "winston": "^3.11.0", "zod": "^3.22.4" } diff --git a/src/controllers/premium-album-controller.ts b/src/controllers/premium-album-controller.ts index 3bd4b03cc97a4b913620c5b2feb3ba544f19fb24..caf1fb99e04ffb759fc6bf8cfa860dff8956641a 100644 --- a/src/controllers/premium-album-controller.ts +++ b/src/controllers/premium-album-controller.ts @@ -10,7 +10,8 @@ const createPremiumAlbum = async ( ): Promise<void> => { try { const data = req.body; - const responseData = await PremiumAlbumService.createPremiumAlbum(data); + const coverFile = req.file; + const responseData = await PremiumAlbumService.createPremiumAlbum(data, coverFile); generateResponse(res, StatusCodes.OK, responseData); } catch (err) { next(err); @@ -44,10 +45,12 @@ const updatePremiumAlbum = async ( try { const premiumAlbumId = Number(req.params.premiumAlbumId); const data = req.body; + const coverFile = req.file; const updatedPremiumAlbum = await PremiumAlbumService.updatePremiumAlbum( data, premiumAlbumId, + coverFile ); generateResponse(res, StatusCodes.OK, updatedPremiumAlbum); } catch (err) { diff --git a/src/errors/standard-error.ts b/src/errors/standard-error.ts index 96675457a6a070146b6379c0767707eca5b5248d..0cbbacd9aaee0574c69ef9efe906bd7d9fc7461c 100644 --- a/src/errors/standard-error.ts +++ b/src/errors/standard-error.ts @@ -17,6 +17,7 @@ enum ErrorType { SONG_NOT_FOUND, INPUT_DATA_NOT_VALID, INVALID_API_KEY, + FILE_NOT_VALID, } class StandardError { @@ -106,6 +107,11 @@ class StandardError { this.status = StatusCodes.UNAUTHORIZED; break; + case ErrorType.FILE_NOT_VALID: + this.title = "Your File is invalid." + this.status = StatusCodes.BAD_REQUEST; + break; + default: this.title = "Unknown error."; this.status = StatusCodes.INTERNAL_SERVER_ERROR; diff --git a/src/services/premium-album-service.ts b/src/services/premium-album-service.ts index 4f181bea6a6a4450b3116fe1eac0bfbfd8abc90c..1f2edc2cfac5b2c58d8ec166c7414c0fcc2a8ebe 100644 --- a/src/services/premium-album-service.ts +++ b/src/services/premium-album-service.ts @@ -3,19 +3,27 @@ import prismaClient from "../cores/db"; import { ErrorType, StandardError } from "../errors/standard-error"; import {validate} from "../validation/validation"; import {createPremiumAlbumSchema, searchPremiumAlbumSchema, deletePremiumAlbumSchema, updatePremiumAlbumSchema} from "../validation/premium-album-validation"; +import { saveFile } from "../utils/FileProcessing"; const createPremiumAlbum = async ( data: Prisma.PremiumAlbumCreateInput, + coverFile: Express.Multer.File | undefined, ): Promise<PremiumAlbum> => { validate(createPremiumAlbumSchema, data) + if (!coverFile) { + throw new StandardError(ErrorType.FILE_NOT_VALID); + } + + const coverFileName = await saveFile(coverFile); + return prismaClient.premiumAlbum.create({ data: { albumName: data.albumName, releaseDate: data.releaseDate, genre: data.genre, artist: data.artist, - coverFilename: data.coverFilename, + coverFilename: coverFileName, }, }); }; @@ -75,6 +83,7 @@ const searchPremiumAlbum = async (reqQuery: { const updatePremiumAlbum = async ( inputData: Prisma.PremiumAlbumUpdateInput, premiumAlbumId: number, + coverFile: Express.Multer.File | undefined, ): Promise<PremiumAlbum> => { validate(updatePremiumAlbumSchema, {premiumAlbumId, ...inputData}) @@ -88,6 +97,13 @@ const updatePremiumAlbum = async ( throw new StandardError(ErrorType.ALBUM_NOT_FOUND); } + if (!coverFile) { + throw new StandardError(ErrorType.INPUT_DATA_NOT_VALID); + } + + const coverFileName = await saveFile(coverFile); + inputData.coverFilename = coverFileName; + return prismaClient.premiumAlbum.update({ where: { albumId: premiumAlbumId, diff --git a/src/utils/FileProcessing.ts b/src/utils/FileProcessing.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7039abb0d7e345e51bbb4ff5f34e29e28cb4100 --- /dev/null +++ b/src/utils/FileProcessing.ts @@ -0,0 +1,20 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import { v4 as uuidv4 } from 'uuid'; + +export const saveFile = (file: Express.Multer.File): Promise<string> => { + return new Promise((resolve, reject) => { + const { originalname, buffer } = file; + const fileExtension = path.extname(originalname); + const fileName = uuidv4() + fileExtension; + const filePath = path.join('storage', fileName); + + fs.writeFile(filePath, buffer, (error) => { + if (error) { + reject(error); + } else { + resolve(filePath); + } + }); + }); +};