From 3e3f65bd88d01876ec74bb2e517c30904f77deab Mon Sep 17 00:00:00 2001 From: feserr Date: Tue, 3 Oct 2023 02:00:16 +0100 Subject: [PATCH] Add playlist service support routes and DB tables. --- .env | 3 +- .eslintrc.js | 13 +- .gitignore | 4 +- db/Like.js | 12 + db/Track.js | 32 +++ db/User.js | 16 ++ db/index.js | 15 + db/seedData.js | 17 ++ db/seedFn.js | 38 ++- index.js | 3 + package-lock.json | 434 +++++++++++++++++++++-------- package.json | 11 +- server.js | 1 - src/controllers/bankController.js | 4 +- src/controllers/likeController.js | 64 +++++ src/controllers/trackController.js | 143 ++++++++++ src/controllers/userController.js | 91 ++++++ src/routes/likeRoutes.js | 7 + src/routes/trackRoutes.js | 9 + src/routes/userRoutes.js | 8 + test/test.http | 55 ++++ 21 files changed, 835 insertions(+), 145 deletions(-) create mode 100644 db/Like.js create mode 100644 db/Track.js create mode 100644 db/User.js create mode 100644 db/seedData.js create mode 100644 src/controllers/likeController.js create mode 100644 src/controllers/trackController.js create mode 100644 src/controllers/userController.js create mode 100644 src/routes/likeRoutes.js create mode 100644 src/routes/trackRoutes.js create mode 100644 src/routes/userRoutes.js create mode 100644 test/test.http diff --git a/.env b/.env index 56b5102..8d6a307 100644 --- a/.env +++ b/.env @@ -1 +1,2 @@ -ORIGINS= \ No newline at end of file +ORIGINS= +MAX_SONGS= diff --git a/.eslintrc.js b/.eslintrc.js index a7f647e..98fa9e0 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,12 +6,21 @@ module.exports = { }, extends: 'airbnb-base', overrides: [ + { + env: { + node: true, + }, + files: [ + '.eslintrc.{js,cjs}', + ], + parserOptions: { + sourceType: 'script', + }, + }, ], parserOptions: { ecmaVersion: 'latest', }, rules: { - 'consistent-return': 'off', - 'import/no-extraneous-dependencies': 'off', }, }; diff --git a/.gitignore b/.gitignore index eeb5ee8..321704d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,7 @@ haters .env*.* .vscode .cache -seedData.js +seedDataDev.js # package managers npm-debug.log* @@ -29,4 +29,4 @@ yarn-error.log* *.sqlite # IDEs -.vscode/ \ No newline at end of file +.vscode/ diff --git a/db/Like.js b/db/Like.js new file mode 100644 index 0000000..c1e0bc2 --- /dev/null +++ b/db/Like.js @@ -0,0 +1,12 @@ +const { Sequelize, sequelize } = require('./db'); + +const Like = sequelize.define('Like', { + id: { + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + allowNull: false, + primaryKey: true, + }, +}); + +module.exports = { Like }; diff --git a/db/Track.js b/db/Track.js new file mode 100644 index 0000000..3666251 --- /dev/null +++ b/db/Track.js @@ -0,0 +1,32 @@ +const { Sequelize, sequelize } = require('./db'); + +const Track = sequelize.define('track', { + id: { + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + allowNull: false, + primaryKey: true, + }, + spotifyTrackId: { + type: Sequelize.STRING, + allowNull: false, + }, + uri: { + type: Sequelize.STRING, + allowNull: false, + }, + name: { + type: Sequelize.STRING, + allowNull: false, + }, + album: { + type: Sequelize.STRING, + allowNull: false, + }, + artist: { + type: Sequelize.STRING, + allowNull: false, + }, +}); + +module.exports = { Track }; diff --git a/db/User.js b/db/User.js new file mode 100644 index 0000000..a961a8d --- /dev/null +++ b/db/User.js @@ -0,0 +1,16 @@ +const { Sequelize, sequelize } = require('./db'); + +const User = sequelize.define('user', { + id: { + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + allowNull: false, + primaryKey: true, + }, + spotifyUserId: { + type: Sequelize.STRING, + allowNull: false, + }, +}); + +module.exports = { User }; diff --git a/db/index.js b/db/index.js index b71d1b2..659746a 100644 --- a/db/index.js +++ b/db/index.js @@ -2,8 +2,23 @@ require('../src/utils/logger'); const { Bank } = require('./Bank'); const { Key } = require('./Key'); +const { Track } = require('./Track'); +const { User } = require('./User'); +const { Like } = require('./Like'); + +User.hasMany(Track, { foreignKey: 'userId' }); +Track.belongsTo(User, { foreignKey: 'userId', onDelete: 'CASCADE' }); + +User.hasMany(Like, { foreignKey: 'userId' }); +Like.belongsTo(User, { foreignKey: 'userId', onDelete: 'CASCADE' }); + +Track.hasMany(Like, { foreignKey: 'trackId' }); +Like.belongsTo(Track, { foreignKey: 'trackId', onDelete: 'CASCADE' }); module.exports = { Bank, Key, + Track, + User, + Like, }; diff --git a/db/seedData.js b/db/seedData.js new file mode 100644 index 0000000..4f13c50 --- /dev/null +++ b/db/seedData.js @@ -0,0 +1,17 @@ +const banks = []; + +const keys = []; + +const tracks = []; + +const users = []; + +const likes = []; + +module.exports = { + banks, + keys, + tracks, + users, + likes, +}; diff --git a/db/seedFn.js b/db/seedFn.js index cf52909..56fb204 100644 --- a/db/seedFn.js +++ b/db/seedFn.js @@ -1,23 +1,41 @@ const { sequelize } = require('./db'); -const { Bank, Key } = require('.'); -const { banks, keys } = require('./seedData'); + +const { + Bank, Key, Track, User, Like, +} = require('.'); + +const { + banks, keys, tracks, users, likes, +} = require('./seedData'); const seed = async () => { try { await sequelize.sync({ force: true }); // recreate db await Promise.all(banks.map(async (bank) => { - await Bank.create({ - holder: bank.holder, - account: bank.account, - country: bank.country, - }); + await Bank.create(bank); })); await Promise.all(keys.map(async (key) => { - await Key.create({ - key: key.key, - }); + await Key.create(key); + })); + + const createdUsers = []; + await Promise.all(users.map(async (user) => { + createdUsers.push(await User.create(user)); + })); + + const createdTracks = []; + await Promise.all(tracks.map(async (track) => { + const createdTrack = await Track.create(track); + await createdUsers[0].addTrack(createdTrack); + createdTracks.push(createdTrack); + })); + + await Promise.all(likes.map(async (like) => { + const createdLike = await Like.create(like); + await createdUsers[0].addLike(createdLike); + await createdTracks[0].addLike(createdLike); })); } catch (error) { global.logger.error(error); diff --git a/index.js b/index.js index b1a46e0..1e1f5fe 100644 --- a/index.js +++ b/index.js @@ -13,5 +13,8 @@ app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use('/api/bank', require('./src/routes/bankRoutes')); +app.use('/api/track', require('./src/routes/trackRoutes')); +app.use('/api/like', require('./src/routes/likeRoutes')); +app.use('/api/user', require('./src/routes/userRoutes')); module.exports = app; diff --git a/package-lock.json b/package-lock.json index 29ed08f..1b35a6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,11 +18,21 @@ }, "devDependencies": { "dotenv-flow": "^3.2.0", - "eslint": "^8.39.0", + "eslint": "^8.50.0", "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import": "^2.28.1", "eslint-plugin-jest": "^27.2.1", - "nodemon": "^2.0.22" + "nodemon": "^2.0.22", + "prettier": "^2.8.8" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/@eslint-community/eslint-utils": { @@ -41,23 +51,23 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", - "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.3.tgz", - "integrity": "sha512-+5gy6OQfk+xx3q0d6jGZZC3f3KzAkXc/IanVxd1is/VIIziRqqt3ongQz0FiTUXqTk0c7aDB3OaFuKnuSoJicQ==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", "dev": true, "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.2", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -73,9 +83,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.43.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.43.0.tgz", - "integrity": "sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -88,9 +98,9 @@ "optional": true }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", @@ -447,9 +457,9 @@ } }, "node_modules/acorn": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.9.0.tgz", - "integrity": "sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==", + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -626,6 +636,25 @@ "node": ">=8" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", @@ -662,6 +691,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.2.tgz", + "integrity": "sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -997,6 +1047,20 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/define-data-property": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz", + "integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.1", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", @@ -1159,18 +1223,19 @@ "optional": true }, "node_modules/es-abstract": { - "version": "1.21.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.2.tgz", - "integrity": "sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==", + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.2.tgz", + "integrity": "sha512-YoxfFcDmhjOgWPWsV13+2RNjq1F6UQnfs+8TftwNqtzlmFzEXvlUwdrNrYeaizfjQzRMxkZ6ElWMOJIFKdVqwA==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.2", "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "es-set-tostringtag": "^2.0.1", "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.2.0", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.1", "get-symbol-description": "^1.0.0", "globalthis": "^1.0.3", "gopd": "^1.0.1", @@ -1185,19 +1250,23 @@ "is-regex": "^1.1.4", "is-shared-array-buffer": "^1.0.2", "is-string": "^1.0.7", - "is-typed-array": "^1.1.10", + "is-typed-array": "^1.1.12", "is-weakref": "^1.0.2", "object-inspect": "^1.12.3", "object-keys": "^1.1.1", "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.4.3", + "regexp.prototype.flags": "^1.5.1", + "safe-array-concat": "^1.0.1", "safe-regex-test": "^1.0.0", - "string.prototype.trim": "^1.2.7", - "string.prototype.trimend": "^1.0.6", - "string.prototype.trimstart": "^1.0.6", + "string.prototype.trim": "^1.2.8", + "string.prototype.trimend": "^1.0.7", + "string.prototype.trimstart": "^1.0.7", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", "typed-array-length": "^1.0.4", "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.9" + "which-typed-array": "^1.1.11" }, "engines": { "node": ">= 0.4" @@ -1264,27 +1333,27 @@ } }, "node_modules/eslint": { - "version": "8.43.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.43.0.tgz", - "integrity": "sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==", + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.3", - "@eslint/js": "8.43.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.0", - "eslint-visitor-keys": "^3.4.1", - "espree": "^9.5.2", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1294,7 +1363,6 @@ "globals": "^13.19.0", "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", @@ -1304,9 +1372,8 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -1385,26 +1452,28 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.27.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", - "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "version": "2.28.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.28.1.tgz", + "integrity": "sha512-9I9hFlITvOV55alzoKBI+K9q74kv0iKMeY6av5+umsNwayt59fz692daGyjR+oStBQgx6nwR9rXldDev3Clw+A==", "dev": true, "dependencies": { "array-includes": "^3.1.6", + "array.prototype.findlastindex": "^1.2.2", "array.prototype.flat": "^1.3.1", "array.prototype.flatmap": "^1.3.1", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.7", - "eslint-module-utils": "^2.7.4", + "eslint-module-utils": "^2.8.0", "has": "^1.0.3", - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "is-glob": "^4.0.3", "minimatch": "^3.1.2", + "object.fromentries": "^2.0.6", + "object.groupby": "^1.0.0", "object.values": "^1.1.6", - "resolve": "^1.22.1", - "semver": "^6.3.0", - "tsconfig-paths": "^3.14.1" + "semver": "^6.3.1", + "tsconfig-paths": "^3.14.2" }, "engines": { "node": ">=4" @@ -1460,9 +1529,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", - "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dev": true, "dependencies": { "esrecurse": "^4.3.0", @@ -1476,9 +1545,9 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", - "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1488,12 +1557,12 @@ } }, "node_modules/espree": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.2.tgz", - "integrity": "sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dev": true, "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" }, @@ -1813,15 +1882,15 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "node_modules/function.prototype.name": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", - "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", + "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "functions-have-names": "^1.2.3" }, "engines": { "node": ">= 0.4" @@ -1920,9 +1989,9 @@ } }, "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -2315,9 +2384,9 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", + "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", "dev": true, "dependencies": { "has": "^1.0.3" @@ -2480,16 +2549,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.10", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", - "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", "dev": true, "dependencies": { - "available-typed-arrays": "^1.0.5", - "call-bind": "^1.0.2", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0" + "which-typed-array": "^1.1.11" }, "engines": { "node": ">= 0.4" @@ -2510,6 +2575,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3146,6 +3217,35 @@ "node": ">= 0.4" } }, + "node_modules/object.fromentries": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, "node_modules/object.values": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", @@ -3183,17 +3283,17 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", "dev": true, "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "type-check": "^0.4.0" }, "engines": { "node": ">= 0.8.0" @@ -3336,6 +3436,21 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -3464,14 +3579,14 @@ } }, "node_modules/regexp.prototype.flags": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", - "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz", + "integrity": "sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", "define-properties": "^1.2.0", - "functions-have-names": "^1.2.3" + "set-function-name": "^2.0.0" }, "engines": { "node": ">= 0.4" @@ -3567,6 +3682,24 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-array-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.1.tgz", + "integrity": "sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3606,9 +3739,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "bin": { "semver": "bin/semver.js" } @@ -3756,6 +3889,20 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, + "node_modules/set-function-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", + "integrity": "sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==", + "dev": true, + "dependencies": { + "define-data-property": "^1.0.1", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -3932,14 +4079,14 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", - "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz", + "integrity": "sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -3949,28 +4096,28 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", - "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz", + "integrity": "sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/string.prototype.trimstart": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", - "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz", + "integrity": "sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -4188,6 +4335,57 @@ "node": ">= 0.6" } }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -4355,17 +4553,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", - "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.5", "call-bind": "^1.0.2", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-tostringtag": "^1.0.0", - "is-typed-array": "^1.1.10" + "has-tostringtag": "^1.0.0" }, "engines": { "node": ">= 0.4" @@ -4390,15 +4587,6 @@ "@types/node": "*" } }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index abf3180..75d8b41 100644 --- a/package.json +++ b/package.json @@ -18,17 +18,18 @@ "dependencies": { "cors": "^2.8.5", "dotenv": "^16.0.3", + "dotenv-flow": "^3.2.0", "express": "^4.18.2", "js-logger": "^1.6.1", "sequelize": "^6.31.1", "sqlite3": "^5.1.6" }, "devDependencies": { - "dotenv-flow": "^3.2.0", - "eslint": "^8.39.0", + "eslint": "^8.50.0", "eslint-config-airbnb-base": "^15.0.0", - "eslint-plugin-import": "^2.27.5", + "eslint-plugin-import": "^2.28.1", "eslint-plugin-jest": "^27.2.1", - "nodemon": "^2.0.22" + "nodemon": "^2.0.22", + "prettier": "^2.8.8" } -} \ No newline at end of file +} diff --git a/server.js b/server.js index 7a70145..52e29ca 100644 --- a/server.js +++ b/server.js @@ -1,5 +1,4 @@ const app = require('.'); -require('dotenv').config(); const { PORT = 3005 } = process.env; diff --git a/src/controllers/bankController.js b/src/controllers/bankController.js index f22d8bb..54f6ff3 100644 --- a/src/controllers/bankController.js +++ b/src/controllers/bankController.js @@ -13,11 +13,13 @@ const bankController = { const accounts = await Bank.findAll(); - return res.status(200).send({ accounts }); + res.send({ accounts }); } catch (error) { global.logger.error(error.message); res.status(500).send({ message: 'Failed to retrieve bank data' }); } + + return res.status(200); }, }; diff --git a/src/controllers/likeController.js b/src/controllers/likeController.js new file mode 100644 index 0000000..a33e4e9 --- /dev/null +++ b/src/controllers/likeController.js @@ -0,0 +1,64 @@ +const { Track, User, Like } = require('../../db'); + +const likeController = { + getTrackLikes: async (req, res) => { + try { + const { spotifyTrackId } = req.params; + + const track = await Track.findOne({ where: { spotifyTrackId } }); + if (!track) return res.status(404).send({ message: 'Track not exist' }); + + const trackLikes = await Like.findAll({ + where: { trackId: track.id }, + }); + + const likesBySpotifyIds = await Promise.all( + trackLikes.map(async (like) => { + const user = await User.findByPk(like.userId); + return user.spotifyUserId; + }), + ); + + res.send({ likes: likesBySpotifyIds }); + } catch (error) { + global.logger.error(error.message); + res.status(500).send({ message: 'Failed to like the track' }); + } + + return res.status(200); + }, + + likeTrack: async (req, res) => { + try { + const { spotifyTrackId } = req.body; + + const track = await Track.findOne({ where: { spotifyTrackId } }); + if (!track) return res.status(404).send({ message: 'Track not exist' }); + + const { spotifyUserId } = req.body; + const user = await User.findOne({ where: { spotifyUserId } }); + if (!user) return res.status(404).send({ message: 'User not exist' }); + + const trackLikes = await Like.findOne({ + where: { trackId: track.id, userId: user.id }, + }); + + if (trackLikes) { + trackLikes.destroy(); + res.send({ message: 'Disliked' }); + } else { + const like = await Like.create(); + user.addLike(like); + track.addLike(like); + res.send({ message: 'Liked' }); + } + } catch (error) { + global.logger.error(error.message); + res.status(500).send({ message: 'Failed to like the track' }); + } + + return res.status(200); + }, +}; + +module.exports = likeController; diff --git a/src/controllers/trackController.js b/src/controllers/trackController.js new file mode 100644 index 0000000..059e25e --- /dev/null +++ b/src/controllers/trackController.js @@ -0,0 +1,143 @@ +const { Track, User, Like } = require('../../db'); + +const { MAX_SONGS = 2 } = process.env; + +const trackController = { + getTracks: async (_, res) => { + try { + let tracks = await Track.findAll(); + tracks = await Promise.all( + tracks.map(async (track) => { + const user = await User.findByPk(track.userId); + const tracksLikes = await Like.findAll({ where: { trackId: track.id } }); + const userLikes = await Promise.all( + await tracksLikes.map( + async (element) => (await User.findByPk(element.userId)).spotifyUserId, + ), + ); + + return { + id: track.spotifyTrackId, + uri: track.uri, + name: track.name, + album: track.album, + artist: track.artist, + spotifyTrackId: track.spotifyTrackId, + spotifyUserId: user.spotifyUserId, + likes: userLikes, + }; + }), + ); + + res.status(200).send({ tracks }); + } catch (error) { + global.logger.error(error.message); + res.status(500).send({ message: 'Failed to retrieve tracks' }); + } + + return res; + }, + + getTrackInfo: async (req, res) => { + try { + const { spotifyTrackId } = req.params; + const trackExist = await Track.findOne({ where: { spotifyTrackId } }); + + if (!trackExist) { + return res.status(200).send({ + trackInfo: { + spotifyUserId: '', + likes: [], + }, + }); + } + + const user = await User.findByPk(trackExist.userId); + const tracksLikes = await Like.findAll({ where: { trackId: trackExist.id } }); + const userLikes = await Promise.all( + await tracksLikes.map( + async (element) => (await User.findByPk(element.userId)).spotifyUserId, + ), + ); + + res.send({ + trackInfo: { + spotifyUserId: user.spotifyUserId, + likes: userLikes, + }, + }); + } catch (error) { + global.logger.error(error.message); + res.status(500).send({ message: 'Failed to remove track' }); + } + + return res.status(200); + }, + + addTrack: async (req, res) => { + try { + const { spotifyTrackId } = req.params; + const trackExist = await Track.findOne({ where: { spotifyTrackId } }); + if (trackExist) return res.status(400).send({ message: 'Track already at track' }); + + const { spotifyUserId } = req.body; + const user = await User.findOne({ where: { spotifyUserId } }); + if (!user) return res.status(404).send({ message: 'User not exist' }); + + const userTracks = await Track.findAll({ where: { userId: user.id } }); + if (userTracks && userTracks.length >= MAX_SONGS) return res.status(400).send({ message: 'User reached cuota' }); + + const newTrack = await Track.create({ + uri: req.body.uri, + name: req.body.name, + album: req.body.album, + artist: req.body.artist, + spotifyTrackId, + }); + await user.addTrack(newTrack); + + const like = await Like.create(); + user.addLike(like); + newTrack.addLike(like); + + res.status(200).send({ message: 'Track added' }); + } catch (error) { + global.logger.error(error.message); + res.status(500).send({ message: 'Failed to add track' }); + } + + return res; + }, + + removeTrack: async (req, res) => { + try { + const { spotifyUserId } = req.params; + const user = await User.findOne({ where: { spotifyUserId } }); + if (!user) return res.status(404).send({ message: 'User not exist' }); + + const { spotifyTrackId } = req.params; + const trackExist = await Track.findOne({ where: { spotifyTrackId } }); + + if (trackExist && trackExist.userId === user.id) { + Promise.all( + (await Like.findAll({ where: { trackId: trackExist.id } })).map(async (element) => { + element.destroy(); + }), + ); + + await trackExist.destroy(); + + res.send({ message: 'Track removed' }); + } else { + res.send({ message: 'Track not exist or added by other use' }); + } + } catch (error) { + global.logger.error(error.message); + res.status(500).send({ message: 'Failed to remove track' }); + } + + return res.status(200); + }, +}; + +module.exports = trackController; diff --git a/src/controllers/userController.js b/src/controllers/userController.js new file mode 100644 index 0000000..e038954 --- /dev/null +++ b/src/controllers/userController.js @@ -0,0 +1,91 @@ +const { User, Track, Like } = require('../../db'); + +const userController = { + register: async (req, res) => { + try { + const { spotifyUserId } = req.params; + const user = await User.findOne({ where: { spotifyUserId } }); + if (user) return res.status(204).send({ message: 'User already exist' }); + + User.create({ spotifyUserId }); + res.send({ message: 'User created' }); + } catch (error) { + global.logger.error(error.message); + res.status(500).send({ message: 'Failed register the user' }); + } + + return res.status(200); + }, + + delete: async (req, res) => { + try { + const { spotifyUserId } = req.params; + const user = await User.findOne({ where: { spotifyUserId } }); + if (!user) return res.status(404).send({ message: 'User not exist' }); + + const userTracks = await Track.findAll({ where: { userId: user.id } }); + await Promise.all( + await userTracks.map(async (track) => { + await Promise.all( + ( + await Like.findAll({ where: { trackId: track.id } }) + ).map(async (element) => { + element.destroy(); + }), + ); + + await track.destroy(); + }), + ); + + user.destroy(); + + res.send({ message: 'User deleted' }); + } catch (error) { + global.logger.error(error.message); + res.status(500).send({ message: 'Failed to delete the user' }); + } + + return res.status(200); + }, + + info: async (req, res) => { + try { + const { spotifyUserId } = req.params; + const user = await User.findOne({ where: { spotifyUserId } }); + if (!user) return res.status(404).send({ message: 'User not exist' }); + + let tracks = await Track.findAll({ where: { userId: user.id } }); + tracks = await Promise.all( + tracks.map(async (track) => { + const tracksLikes = await Like.findAll({ where: { trackId: track.id } }); + const userLikes = await Promise.all( + await tracksLikes.map( + async (element) => (await User.findByPk(element.userId)).spotifyUserId, + ), + ); + + return { + id: track.spotifyTrackId, + uri: track.uri, + name: track.name, + album: track.album, + artist: track.artist, + spotifyTrackId: track.spotifyTrackId, + spotifyUserId: user.spotifyUserId, + likes: userLikes, + }; + }), + ); + + res.send({ tracks }); + } catch (error) { + global.logger.error(error.message); + res.status(500).send({ message: 'Failed to delete the user' }); + } + + return res.status(200); + }, +}; + +module.exports = userController; diff --git a/src/routes/likeRoutes.js b/src/routes/likeRoutes.js new file mode 100644 index 0000000..92cddc3 --- /dev/null +++ b/src/routes/likeRoutes.js @@ -0,0 +1,7 @@ +const router = require('express').Router(); +const trackController = require('../controllers/likeController'); + +router.get('/:spotifyTrackId', trackController.getTrackLikes); +router.post('/', trackController.likeTrack); + +module.exports = router; diff --git a/src/routes/trackRoutes.js b/src/routes/trackRoutes.js new file mode 100644 index 0000000..2f14d20 --- /dev/null +++ b/src/routes/trackRoutes.js @@ -0,0 +1,9 @@ +const router = require('express').Router(); +const trackController = require('../controllers/trackController'); + +router.get('/', trackController.getTracks); +router.get('/:spotifyTrackId', trackController.getTrackInfo); +router.post('/:spotifyTrackId', trackController.addTrack); +router.delete('/:spotifyTrackId&:spotifyUserId', trackController.removeTrack); + +module.exports = router; diff --git a/src/routes/userRoutes.js b/src/routes/userRoutes.js new file mode 100644 index 0000000..d256c93 --- /dev/null +++ b/src/routes/userRoutes.js @@ -0,0 +1,8 @@ +const router = require('express').Router(); +const userController = require('../controllers/userController'); + +router.get('/:spotifyUserId', userController.info); +router.put('/:spotifyUserId', userController.register); +router.delete('/:spotifyUserId', userController.delete); + +module.exports = router; diff --git a/test/test.http b/test/test.http new file mode 100644 index 0000000..d9085da --- /dev/null +++ b/test/test.http @@ -0,0 +1,55 @@ +@hostname = http://localhost:3005 + +GET {{hostname}}/api/track HTTP/1.1 + +### + +GET {{hostname}}/api/track/7LQLbChhh7aURz53yOTL HTTP/1.1 + +### + +POST {{hostname}}/api/track/7LQLbChhh7aURz53yO HTTP/1.1 +content-type: application/json + +{ + "uri": "spotify:track:7LQLbChhh7aURz53yOTL", + "name": "mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm", + "album": "Entre Poetas y Presos", + "artist": "La RaĆ­z", + "spotifyUserId": "feserr" +} + +### + +DELETE {{hostname}}/api/track/7LQLbChhh7aURz53yOTLAr&feserr HTTP/1.1 + +### + +GET {{hostname}}/api/like/7LQLbChhh7aURz53yOTL HTTP/1.1 + +### + +POST {{hostname}}/api/like HTTP/1.1 +content-type: application/json + +{ + "spotifyTrackId": "7LQLbChhh7aURz53yOTLAr", + "spotifyUserId": "feserr" +} + +### + +GET {{hostname}}/api/user/feserr HTTP/1.1 + +### + +POST {{hostname}}/api/user/ HTTP/1.1 +content-type: application/json + +{ + "spotifyUserId": "feserr" +} + +### + +DELETE {{hostname}}/api/user/feserr HTTP/1.1