diff --git a/.eslintrc.json b/.eslintrc.json index ea29278..0ad70c2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,7 +2,9 @@ "extends": ["next/core-web-vitals", "airbnb-base", "airbnb-typescript", "prettier"], "rules": { "arrow-body-style": "off", - "import/extensions": "off" + "import/extensions": "warn", + "import/order": "warn", + "import/no-extraneous-dependencies": ["error", { "devDependencies": true }] }, "parserOptions": { "project": ["./tsconfig.json"] diff --git a/package-lock.json b/package-lock.json index 65f5dbc..754092f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,10 @@ "@mapbox/search-js-core": "^1.0.0-beta.17", "@mapbox/search-js-react": "^1.0.0-beta.17", "@next/bundle-analyzer": "^14.0.3", - "@prisma/client": "^5.6.0", + "@prisma/client": "^5.7.0", "@react-email/components": "^0.0.11", "@react-email/render": "^0.0.9", "@react-email/tailwind": "^0.0.12", - "@types/express": "^4.17.21", "@vercel/analytics": "^1.1.0", "argon2": "^0.31.1", "cloudinary": "^1.41.0", @@ -54,6 +53,7 @@ "devDependencies": { "@faker-js/faker": "^8.3.1", "@types/cookie": "^0.5.1", + "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.2", "@types/lodash": "^4.14.195", "@types/mapbox__mapbox-sdk": "^0.13.4", @@ -79,7 +79,7 @@ "prettier": "^3.0.0", "prettier-plugin-jsdoc": "^1.0.2", "prettier-plugin-tailwindcss": "^0.5.7", - "prisma": "^5.6.0", + "prisma": "^5.7.0", "tailwindcss": "^3.3.3", "tailwindcss-animate": "^1.0.6", "ts-node": "^10.9.1", @@ -1633,13 +1633,10 @@ "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==" }, "node_modules/@prisma/client": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.6.0.tgz", - "integrity": "sha512-mUDefQFa1wWqk4+JhKPYq8BdVoFk9NFMBXUI8jAkBfQTtgx8WPx02U2HB/XbAz3GSUJpeJOKJQtNvaAIDs6sug==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.7.0.tgz", + "integrity": "sha512-cZmglCrfNbYpzUtz7HscVHl38e9CrUs31nrVoGUK1nIPXGgt8hT4jj2s657UXcNdQ/jBUxDgGmHyu2Nyrq1txg==", "hasInstallScript": true, - "dependencies": { - "@prisma/engines-version": "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee" - }, "engines": { "node": ">=16.13" }, @@ -1652,17 +1649,50 @@ } } }, + "node_modules/@prisma/debug": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.7.0.tgz", + "integrity": "sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA==", + "devOptional": true + }, "node_modules/@prisma/engines": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.6.0.tgz", - "integrity": "sha512-Mt2q+GNJpU2vFn6kif24oRSBQv1KOkYaterQsi0k2/lA+dLvhRX6Lm26gon6PYHwUM8/h8KRgXIUMU0PCLB6bw==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.7.0.tgz", + "integrity": "sha512-TkOMgMm60n5YgEKPn9erIvFX2/QuWnl3GBo6yTRyZKk5O5KQertXiNnrYgSLy0SpsKmhovEPQb+D4l0SzyE7XA==", "devOptional": true, - "hasInstallScript": true + "hasInstallScript": true, + "dependencies": { + "@prisma/debug": "5.7.0", + "@prisma/engines-version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9", + "@prisma/fetch-engine": "5.7.0", + "@prisma/get-platform": "5.7.0" + } }, "node_modules/@prisma/engines-version": { - "version": "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee.tgz", - "integrity": "sha512-UoFgbV1awGL/3wXuUK3GDaX2SolqczeeJ5b4FVec9tzeGbSWJboPSbT0psSrmgYAKiKnkOPFSLlH6+b+IyOwAw==" + "version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9.tgz", + "integrity": "sha512-V6tgRVi62jRwTm0Hglky3Scwjr/AKFBFtS+MdbsBr7UOuiu1TKLPc6xfPiyEN1+bYqjEtjxwGsHgahcJsd1rNg==", + "devOptional": true + }, + "node_modules/@prisma/fetch-engine": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.7.0.tgz", + "integrity": "sha512-zIn/qmO+N/3FYe7/L9o+yZseIU8ivh4NdPKSkQRIHfg2QVTVMnbhGoTcecbxfVubeTp+DjcbjS0H9fCuM4W04w==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "5.7.0", + "@prisma/engines-version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9", + "@prisma/get-platform": "5.7.0" + } + }, + "node_modules/@prisma/get-platform": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.7.0.tgz", + "integrity": "sha512-ZeV/Op4bZsWXuw5Tg05WwRI8BlKiRFhsixPcAM+5BKYSiUZiMKIi713tfT3drBq8+T0E1arNZgYSA9QYcglWNA==", + "devOptional": true, + "dependencies": { + "@prisma/debug": "5.7.0" + } }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.1", @@ -2061,6 +2091,7 @@ "version": "1.19.3", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==", + "dev": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -2087,6 +2118,7 @@ "version": "3.4.36", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "dev": true, "dependencies": { "@types/node": "*" } @@ -2110,6 +2142,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -2121,6 +2154,7 @@ "version": "4.17.37", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz", "integrity": "sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==", + "dev": true, "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -2141,7 +2175,8 @@ "node_modules/@types/http-errors": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz", - "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==" + "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==", + "dev": true }, "node_modules/@types/json-schema": { "version": "7.0.13", @@ -2216,7 +2251,8 @@ "node_modules/@types/mime": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", - "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==" + "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==", + "dev": true }, "node_modules/@types/minimist": { "version": "1.2.3", @@ -2310,12 +2346,14 @@ "node_modules/@types/qs": { "version": "6.9.8", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", - "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==" + "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==", + "dev": true }, "node_modules/@types/range-parser": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz", - "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==" + "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==", + "dev": true }, "node_modules/@types/react": { "version": "18.2.25", @@ -2393,6 +2431,7 @@ "version": "0.17.2", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.2.tgz", "integrity": "sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==", + "dev": true, "dependencies": { "@types/mime": "^1", "@types/node": "*" @@ -2402,6 +2441,7 @@ "version": "1.15.3", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.3.tgz", "integrity": "sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==", + "dev": true, "dependencies": { "@types/http-errors": "*", "@types/mime": "*", @@ -8522,13 +8562,13 @@ } }, "node_modules/prisma": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.6.0.tgz", - "integrity": "sha512-EEaccku4ZGshdr2cthYHhf7iyvCcXqwJDvnoQRAJg5ge2Tzpv0e2BaMCp+CbbDUwoVTzwgOap9Zp+d4jFa2O9A==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.7.0.tgz", + "integrity": "sha512-0rcfXO2ErmGAtxnuTNHQT9ztL0zZheQjOI/VNJzdq87C3TlGPQtMqtM+KCwU6XtmkoEr7vbCQqA7HF9IY0ST+Q==", "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "5.6.0" + "@prisma/engines": "5.7.0" }, "bin": { "prisma": "build/index.js" @@ -11843,23 +11883,54 @@ "integrity": "sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==" }, "@prisma/client": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.6.0.tgz", - "integrity": "sha512-mUDefQFa1wWqk4+JhKPYq8BdVoFk9NFMBXUI8jAkBfQTtgx8WPx02U2HB/XbAz3GSUJpeJOKJQtNvaAIDs6sug==", - "requires": { - "@prisma/engines-version": "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee" - } + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.7.0.tgz", + "integrity": "sha512-cZmglCrfNbYpzUtz7HscVHl38e9CrUs31nrVoGUK1nIPXGgt8hT4jj2s657UXcNdQ/jBUxDgGmHyu2Nyrq1txg==", + "requires": {} }, - "@prisma/engines": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.6.0.tgz", - "integrity": "sha512-Mt2q+GNJpU2vFn6kif24oRSBQv1KOkYaterQsi0k2/lA+dLvhRX6Lm26gon6PYHwUM8/h8KRgXIUMU0PCLB6bw==", + "@prisma/debug": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.7.0.tgz", + "integrity": "sha512-tZ+MOjWlVvz1kOEhNYMa4QUGURY+kgOUBqLHYIV8jmCsMuvA1tWcn7qtIMLzYWCbDcQT4ZS8xDgK0R2gl6/0wA==", "devOptional": true }, + "@prisma/engines": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.7.0.tgz", + "integrity": "sha512-TkOMgMm60n5YgEKPn9erIvFX2/QuWnl3GBo6yTRyZKk5O5KQertXiNnrYgSLy0SpsKmhovEPQb+D4l0SzyE7XA==", + "devOptional": true, + "requires": { + "@prisma/debug": "5.7.0", + "@prisma/engines-version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9", + "@prisma/fetch-engine": "5.7.0", + "@prisma/get-platform": "5.7.0" + } + }, "@prisma/engines-version": { - "version": "5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.6.0-32.e95e739751f42d8ca026f6b910f5a2dc5adeaeee.tgz", - "integrity": "sha512-UoFgbV1awGL/3wXuUK3GDaX2SolqczeeJ5b4FVec9tzeGbSWJboPSbT0psSrmgYAKiKnkOPFSLlH6+b+IyOwAw==" + "version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9.tgz", + "integrity": "sha512-V6tgRVi62jRwTm0Hglky3Scwjr/AKFBFtS+MdbsBr7UOuiu1TKLPc6xfPiyEN1+bYqjEtjxwGsHgahcJsd1rNg==", + "devOptional": true + }, + "@prisma/fetch-engine": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.7.0.tgz", + "integrity": "sha512-zIn/qmO+N/3FYe7/L9o+yZseIU8ivh4NdPKSkQRIHfg2QVTVMnbhGoTcecbxfVubeTp+DjcbjS0H9fCuM4W04w==", + "devOptional": true, + "requires": { + "@prisma/debug": "5.7.0", + "@prisma/engines-version": "5.7.0-41.79fb5193cf0a8fdbef536e4b4a159cad677ab1b9", + "@prisma/get-platform": "5.7.0" + } + }, + "@prisma/get-platform": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.7.0.tgz", + "integrity": "sha512-ZeV/Op4bZsWXuw5Tg05WwRI8BlKiRFhsixPcAM+5BKYSiUZiMKIi713tfT3drBq8+T0E1arNZgYSA9QYcglWNA==", + "devOptional": true, + "requires": { + "@prisma/debug": "5.7.0" + } }, "@radix-ui/react-compose-refs": { "version": "1.0.1", @@ -12133,6 +12204,7 @@ "version": "1.19.3", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.3.tgz", "integrity": "sha512-oyl4jvAfTGX9Bt6Or4H9ni1Z447/tQuxnZsytsCaExKlmJiU8sFgnIBRzJUpKwB5eWn9HuBYlUlVA74q/yN0eQ==", + "dev": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -12159,6 +12231,7 @@ "version": "3.4.36", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.36.tgz", "integrity": "sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w==", + "dev": true, "requires": { "@types/node": "*" } @@ -12182,6 +12255,7 @@ "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", @@ -12193,6 +12267,7 @@ "version": "4.17.37", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.37.tgz", "integrity": "sha512-ZohaCYTgGFcOP7u6aJOhY9uIZQgZ2vxC2yWoArY+FeDXlqeH66ZVBjgvg+RLVAS/DWNq4Ap9ZXu1+SUQiiWYMg==", + "dev": true, "requires": { "@types/node": "*", "@types/qs": "*", @@ -12213,7 +12288,8 @@ "@types/http-errors": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.2.tgz", - "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==" + "integrity": "sha512-lPG6KlZs88gef6aD85z3HNkztpj7w2R7HmR3gygjfXCQmsLloWNARFkMuzKiiY8FGdh1XDpgBdrSf4aKDiA7Kg==", + "dev": true }, "@types/json-schema": { "version": "7.0.13", @@ -12288,7 +12364,8 @@ "@types/mime": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.3.tgz", - "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==" + "integrity": "sha512-Ys+/St+2VF4+xuY6+kDIXGxbNRO0mesVg0bbxEfB97Od1Vjpjx9KD1qxs64Gcb3CWPirk9Xe+PT4YiiHQ9T+eg==", + "dev": true }, "@types/minimist": { "version": "1.2.3", @@ -12381,12 +12458,14 @@ "@types/qs": { "version": "6.9.8", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", - "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==" + "integrity": "sha512-u95svzDlTysU5xecFNTgfFG5RUWu1A9P0VzgpcIiGZA9iraHOdSzcxMxQ55DyeRaGCSxQi7LxXDI4rzq/MYfdg==", + "dev": true }, "@types/range-parser": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.5.tgz", - "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==" + "integrity": "sha512-xrO9OoVPqFuYyR/loIHjnbvvyRZREYKLjxV4+dY6v3FQR3stQ9ZxIGkaclF7YhI9hfjpuTbu14hZEy94qKLtOA==", + "dev": true }, "@types/react": { "version": "18.2.25", @@ -12463,6 +12542,7 @@ "version": "0.17.2", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.2.tgz", "integrity": "sha512-aAG6yRf6r0wQ29bkS+x97BIs64ZLxeE/ARwyS6wrldMm3C1MdKwCcnnEwMC1slI8wuxJOpiUH9MioC0A0i+GJw==", + "dev": true, "requires": { "@types/mime": "^1", "@types/node": "*" @@ -12472,6 +12552,7 @@ "version": "1.15.3", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.3.tgz", "integrity": "sha512-yVRvFsEMrv7s0lGhzrggJjNOSmZCdgCjw9xWrPr/kNNLp6FaDfMC1KaYl3TSJ0c58bECwNBMoQrZJ8hA8E1eFg==", + "dev": true, "requires": { "@types/http-errors": "*", "@types/mime": "*", @@ -16745,12 +16826,12 @@ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==" }, "prisma": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.6.0.tgz", - "integrity": "sha512-EEaccku4ZGshdr2cthYHhf7iyvCcXqwJDvnoQRAJg5ge2Tzpv0e2BaMCp+CbbDUwoVTzwgOap9Zp+d4jFa2O9A==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.7.0.tgz", + "integrity": "sha512-0rcfXO2ErmGAtxnuTNHQT9ztL0zZheQjOI/VNJzdq87C3TlGPQtMqtM+KCwU6XtmkoEr7vbCQqA7HF9IY0ST+Q==", "devOptional": true, "requires": { - "@prisma/engines": "5.6.0" + "@prisma/engines": "5.7.0" } }, "process": { diff --git a/package.json b/package.json index 25ef716..15272cc 100644 --- a/package.json +++ b/package.json @@ -5,10 +5,11 @@ "scripts": { "dev": "next dev", "build": "next build", + "prestart": "npm run build", "start": "next start", "lint": "next lint", "clear-db": "npx ts-node ./src/prisma/seed/clear/index.ts", - "format": "npx prettier . --write", + "format": "npx prettier . --write; npx prisma format;", "format-watch": "npx onchange \"**/*\" -- prettier --write --ignore-unknown {{changed}}", "seed": "npx --max-old-space-size=4096 ts-node ./src/prisma/seed/index.ts" }, @@ -21,11 +22,10 @@ "@mapbox/search-js-core": "^1.0.0-beta.17", "@mapbox/search-js-react": "^1.0.0-beta.17", "@next/bundle-analyzer": "^14.0.3", - "@prisma/client": "^5.6.0", + "@prisma/client": "^5.7.0", "@react-email/components": "^0.0.11", "@react-email/render": "^0.0.9", "@react-email/tailwind": "^0.0.12", - "@types/express": "^4.17.21", "@vercel/analytics": "^1.1.0", "argon2": "^0.31.1", "cloudinary": "^1.41.0", @@ -36,13 +36,12 @@ "lodash": "^4.17.21", "mapbox-gl": "^2.15.0", "multer": "^1.4.5-lts.1", - "next": "^14.0.3", "next-cloudinary": "^5.10.0", "next-connect": "^1.0.0-next.3", - "passport": "^0.6.0", + "next": "^14.0.3", "passport-local": "^1.0.0", + "passport": "^0.6.0", "pino": "^8.14.1", - "react": "^18.2.0", "react-daisyui": "^4.1.2", "react-dom": "^18.2.0", "react-email": "^1.9.5", @@ -52,6 +51,7 @@ "react-intersection-observer": "^9.5.2", "react-map-gl": "^7.1.2", "react-responsive-carousel": "^3.2.23", + "react": "^18.2.0", "swr": "^2.2.0", "theme-change": "^2.5.0", "zod": "^3.21.4" @@ -59,34 +59,35 @@ "devDependencies": { "@faker-js/faker": "^8.3.1", "@types/cookie": "^0.5.1", + "@types/express": "^4.17.21", "@types/jsonwebtoken": "^9.0.2", "@types/lodash": "^4.14.195", "@types/mapbox__mapbox-sdk": "^0.13.4", "@types/multer": "^1.4.7", "@types/node": "^20.4.2", "@types/passport-local": "^1.0.35", - "@types/react": "^18.2.15", "@types/react-dom": "^18.2.7", + "@types/react": "^18.2.15", "@types/sparkpost": "^2.1.5", "@vercel/fetch": "^7.0.0", "autoprefixer": "^10.4.14", "daisyui": "^3.9.2", "dotenv-cli": "^7.2.1", - "eslint": "^8.51.0", "eslint-config-airbnb-base": "15.0.0", "eslint-config-airbnb-typescript": "17.1.0", "eslint-config-next": "^13.5.4", "eslint-config-prettier": "^9.0.0", "eslint-plugin-react": "^7.33.2", + "eslint": "^8.51.0", "generate-password": "^1.7.1", "onchange": "^7.1.0", "postcss": "^8.4.26", - "prettier": "^3.0.0", "prettier-plugin-jsdoc": "^1.0.2", "prettier-plugin-tailwindcss": "^0.5.7", - "prisma": "^5.6.0", - "tailwindcss": "^3.3.3", + "prettier": "^3.0.0", + "prisma": "^5.7.0", "tailwindcss-animate": "^1.0.6", + "tailwindcss": "^3.3.3", "ts-node": "^10.9.1", "typescript": "^5.3.2" }, diff --git a/public/robots.txt b/public/robots.txt index e80fa28..064ec2f 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -3,4 +3,3 @@ Disallow: /api/ Disallow: /login/ Disallow: /register/ Disallow: /users/ -Disallow: /account/ diff --git a/src/components/Account/AccountInfo.tsx b/src/components/Account/AccountInfo.tsx index 0652075..63cf7c5 100644 --- a/src/components/Account/AccountInfo.tsx +++ b/src/components/Account/AccountInfo.tsx @@ -1,6 +1,6 @@ import validateEmailRequest from '@/requests/User/validateEmailRequest'; import validateUsernameRequest from '@/requests/validateUsernameRequest'; -import { BaseCreateUserSchema } from '@/services/User/schema/CreateUserValidationSchemas'; +import { BaseCreateUserSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import { Switch } from '@headlessui/react'; import { zodResolver } from '@hookform/resolvers/zod'; import { Dispatch, FC, useContext } from 'react'; diff --git a/src/components/Account/Security.tsx b/src/components/Account/Security.tsx index 4be98ce..f9e0b79 100644 --- a/src/components/Account/Security.tsx +++ b/src/components/Account/Security.tsx @@ -3,7 +3,7 @@ import { Dispatch, FunctionComponent } from 'react'; import { SubmitHandler, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; -import { UpdatePasswordSchema } from '@/services/User/schema/CreateUserValidationSchemas'; +import { UpdatePasswordSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import sendUpdatePasswordRequest from '@/requests/User/sendUpdatePasswordRequest'; import { AccountPageState, AccountPageAction } from '@/reducers/accountPageReducer'; import toast from 'react-hot-toast'; diff --git a/src/components/Account/UpdateProfileForm.tsx b/src/components/Account/UpdateProfileForm.tsx index 87ea529..039626a 100644 --- a/src/components/Account/UpdateProfileForm.tsx +++ b/src/components/Account/UpdateProfileForm.tsx @@ -5,7 +5,7 @@ import FormSegment from '@/components/ui/forms/FormSegment'; import Link from 'next/link'; import FormTextArea from '@/components/ui/forms/FormTextArea'; import { FC } from 'react'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import type { UseFormHandleSubmit, SubmitHandler, @@ -13,7 +13,7 @@ import type { UseFormRegister, } from 'react-hook-form'; import { z } from 'zod'; -import UpdateProfileSchema from '../../services/User/schema/UpdateProfileSchema'; +import UpdateProfileSchema from '@/services/users/auth/schema/UpdateProfileSchema'; type UpdateProfileSchemaT = z.infer; diff --git a/src/components/Account/UserAvatar.tsx b/src/components/Account/UserAvatar.tsx index 9c4df15..470d8eb 100644 --- a/src/components/Account/UserAvatar.tsx +++ b/src/components/Account/UserAvatar.tsx @@ -1,7 +1,7 @@ import { FC } from 'react'; import { CldImage } from 'next-cloudinary'; import { z } from 'zod'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import { FaUser } from 'react-icons/fa'; interface UserAvatarProps { diff --git a/src/components/BeerById/BeerCommentForm.tsx b/src/components/BeerById/BeerCommentForm.tsx index 9f58b50..ce7d752 100644 --- a/src/components/BeerById/BeerCommentForm.tsx +++ b/src/components/BeerById/BeerCommentForm.tsx @@ -1,6 +1,6 @@ import sendCreateBeerCommentRequest from '@/requests/BeerComment/sendCreateBeerCommentRequest'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import { zodResolver } from '@hookform/resolvers/zod'; import { FunctionComponent } from 'react'; diff --git a/src/components/BeerById/BeerInfoHeader.tsx b/src/components/BeerById/BeerInfoHeader.tsx index fae2384..c44d027 100644 --- a/src/components/BeerById/BeerInfoHeader.tsx +++ b/src/components/BeerById/BeerInfoHeader.tsx @@ -4,7 +4,7 @@ import { FC, useContext } from 'react'; import UserContext from '@/contexts/UserContext'; import { FaRegEdit } from 'react-icons/fa'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import { z } from 'zod'; import useGetBeerPostLikeCount from '@/hooks/data-fetching/beer-likes/useBeerPostLikeCount'; import useTimeDistance from '@/hooks/utilities/useTimeDistance'; diff --git a/src/components/BeerById/BeerPostCommentsSection.tsx b/src/components/BeerById/BeerPostCommentsSection.tsx index 8143923..5f83c91 100644 --- a/src/components/BeerById/BeerPostCommentsSection.tsx +++ b/src/components/BeerById/BeerPostCommentsSection.tsx @@ -1,6 +1,6 @@ import UserContext from '@/contexts/UserContext'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import { FC, MutableRefObject, useContext, useRef } from 'react'; import { z } from 'zod'; diff --git a/src/components/BeerById/BeerRecommendations.tsx b/src/components/BeerById/BeerRecommendations.tsx index edb81c4..96d0887 100644 --- a/src/components/BeerById/BeerRecommendations.tsx +++ b/src/components/BeerById/BeerRecommendations.tsx @@ -3,7 +3,7 @@ import { FC, MutableRefObject, useRef } from 'react'; import { useInView } from 'react-intersection-observer'; import { z } from 'zod'; import useBeerRecommendations from '@/hooks/data-fetching/beer-posts/useBeerRecommendations'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import debounce from 'lodash/debounce'; import BeerRecommendationLoadingComponent from './BeerRecommendationLoadingComponent'; diff --git a/src/components/BeerIndex/BeerCard.tsx b/src/components/BeerIndex/BeerCard.tsx index b79413c..3f27613 100644 --- a/src/components/BeerIndex/BeerCard.tsx +++ b/src/components/BeerIndex/BeerCard.tsx @@ -1,7 +1,7 @@ import Link from 'next/link'; import { FC, useContext } from 'react'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import { z } from 'zod'; import UserContext from '@/contexts/UserContext'; import useGetBeerPostLikeCount from '@/hooks/data-fetching/beer-likes/useBeerPostLikeCount'; diff --git a/src/components/BeerStyleById/BeerStyleBeerSection.tsx b/src/components/BeerStyleById/BeerStyleBeerSection.tsx index c0c4d45..bed62c4 100644 --- a/src/components/BeerStyleById/BeerStyleBeerSection.tsx +++ b/src/components/BeerStyleById/BeerStyleBeerSection.tsx @@ -3,7 +3,7 @@ import { FC, MutableRefObject, useRef } from 'react'; import { useInView } from 'react-intersection-observer'; import { z } from 'zod'; -import BeerStyleQueryResult from '@/services/BeerStyles/schema/BeerStyleQueryResult'; +import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import useBeerPostsByBeerStyle from '@/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyles'; import BeerRecommendationLoadingComponent from '../BeerById/BeerRecommendationLoadingComponent'; diff --git a/src/components/BeerStyleById/BeerStyleCommentForm.tsx b/src/components/BeerStyleById/BeerStyleCommentForm.tsx index 1ffe005..7e9f0e4 100644 --- a/src/components/BeerStyleById/BeerStyleCommentForm.tsx +++ b/src/components/BeerStyleById/BeerStyleCommentForm.tsx @@ -8,7 +8,7 @@ import CreateCommentValidationSchema from '@/services/schema/CommentSchema/Creat import toast from 'react-hot-toast'; import createErrorToast from '@/util/createErrorToast'; -import BeerStyleQueryResult from '@/services/BeerStyles/schema/BeerStyleQueryResult'; +import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import useBeerStyleComments from '@/hooks/data-fetching/beer-style-comments/useBeerStyleComments'; import sendCreateBeerStyleCommentRequest from '@/requests/BeerStyleComment/sendCreateBeerStyleCommentRequest'; import CommentForm from '../ui/CommentForm'; diff --git a/src/components/BeerStyleById/BeerStyleCommentSection.tsx b/src/components/BeerStyleById/BeerStyleCommentSection.tsx index 96f8554..5716327 100644 --- a/src/components/BeerStyleById/BeerStyleCommentSection.tsx +++ b/src/components/BeerStyleById/BeerStyleCommentSection.tsx @@ -5,7 +5,7 @@ import { z } from 'zod'; import { useRouter } from 'next/router'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; -import BeerStyleQueryResult from '@/services/BeerStyles/schema/BeerStyleQueryResult'; +import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import useBeerStyleComments from '@/hooks/data-fetching/beer-style-comments/useBeerStyleComments'; import LoadingComponent from '../BeerById/LoadingComponent'; import CommentsComponent from '../ui/CommentsComponent'; diff --git a/src/components/BeerStyleById/BeerStyleHeader.tsx b/src/components/BeerStyleById/BeerStyleHeader.tsx index e479b52..7285ae8 100644 --- a/src/components/BeerStyleById/BeerStyleHeader.tsx +++ b/src/components/BeerStyleById/BeerStyleHeader.tsx @@ -7,7 +7,7 @@ import { FaRegEdit } from 'react-icons/fa'; import { z } from 'zod'; import useTimeDistance from '@/hooks/utilities/useTimeDistance'; -import BeerStyleQueryResult from '@/services/BeerStyles/schema/BeerStyleQueryResult'; +import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import useBeerStyleLikeCount from '@/hooks/data-fetching/beer-style-likes/useBeerStyleLikeCount'; import BeerStyleLikeButton from './BeerStyleLikeButton'; diff --git a/src/components/BeerStyleIndex/BeerStyleCard.tsx b/src/components/BeerStyleIndex/BeerStyleCard.tsx index 331c68b..6ce2fe2 100644 --- a/src/components/BeerStyleIndex/BeerStyleCard.tsx +++ b/src/components/BeerStyleIndex/BeerStyleCard.tsx @@ -1,4 +1,4 @@ -import BeerStyleQueryResult from '@/services/BeerStyles/schema/BeerStyleQueryResult'; +import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import Link from 'next/link'; import { FC } from 'react'; diff --git a/src/components/BreweryById/BreweryBeerSection.tsx b/src/components/BreweryById/BreweryBeerSection.tsx index 9abfaf9..843efb5 100644 --- a/src/components/BreweryById/BreweryBeerSection.tsx +++ b/src/components/BreweryById/BreweryBeerSection.tsx @@ -1,5 +1,5 @@ import UseBeerPostsByBrewery from '@/hooks/data-fetching/beer-posts/useBeerPostsByBrewery'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import Link from 'next/link'; import { FC, MutableRefObject, useContext, useRef } from 'react'; import { useInView } from 'react-intersection-observer'; diff --git a/src/components/BreweryById/BreweryCommentForm.tsx b/src/components/BreweryById/BreweryCommentForm.tsx index fcaf4ee..162302c 100644 --- a/src/components/BreweryById/BreweryCommentForm.tsx +++ b/src/components/BreweryById/BreweryCommentForm.tsx @@ -1,5 +1,5 @@ import useBreweryPostComments from '@/hooks/data-fetching/brewery-comments/useBreweryPostComments'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; import { zodResolver } from '@hookform/resolvers/zod'; import { FC } from 'react'; diff --git a/src/components/BreweryById/BreweryCommentsSection.tsx b/src/components/BreweryById/BreweryCommentsSection.tsx index 8afaae9..e7b6c2e 100644 --- a/src/components/BreweryById/BreweryCommentsSection.tsx +++ b/src/components/BreweryById/BreweryCommentsSection.tsx @@ -1,5 +1,5 @@ import UserContext from '@/contexts/UserContext'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import { FC, MutableRefObject, useContext, useRef } from 'react'; import { z } from 'zod'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; diff --git a/src/components/BreweryById/BreweryInfoHeader.tsx b/src/components/BreweryById/BreweryInfoHeader.tsx index 262fbe7..e5d3b81 100644 --- a/src/components/BreweryById/BreweryInfoHeader.tsx +++ b/src/components/BreweryById/BreweryInfoHeader.tsx @@ -1,7 +1,7 @@ import UserContext from '@/contexts/UserContext'; import useGetBreweryPostLikeCount from '@/hooks/data-fetching/brewery-likes/useGetBreweryPostLikeCount'; import useTimeDistance from '@/hooks/utilities/useTimeDistance'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import { format } from 'date-fns'; import { FC, useContext } from 'react'; diff --git a/src/components/BreweryIndex/BreweryCard.tsx b/src/components/BreweryIndex/BreweryCard.tsx index 357fda5..39ae8e2 100644 --- a/src/components/BreweryIndex/BreweryCard.tsx +++ b/src/components/BreweryIndex/BreweryCard.tsx @@ -1,6 +1,6 @@ import UserContext from '@/contexts/UserContext'; import useGetBreweryPostLikeCount from '@/hooks/data-fetching/brewery-likes/useGetBreweryPostLikeCount'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import { FC, useContext } from 'react'; import Link from 'next/link'; import { z } from 'zod'; diff --git a/src/components/BreweryPost/CreateBreweryPostForm.tsx b/src/components/BreweryPost/CreateBreweryPostForm.tsx index 72e0424..4b1d2b6 100644 --- a/src/components/BreweryPost/CreateBreweryPostForm.tsx +++ b/src/components/BreweryPost/CreateBreweryPostForm.tsx @@ -1,6 +1,6 @@ import sendUploadBreweryImagesRequest from '@/requests/BreweryImage/sendUploadBreweryImageRequest'; import sendCreateBreweryPostRequest from '@/requests/BreweryPost/sendCreateBreweryPostRequest'; -import CreateBreweryPostSchema from '@/services/BreweryPost/schema/CreateBreweryPostSchema'; +import CreateBreweryPostSchema from '@/services/posts/brewery-post/schema/CreateBreweryPostSchema'; import UploadImageValidationSchema from '@/services/schema/ImageSchema/UploadImageValidationSchema'; import createErrorToast from '@/util/createErrorToast'; import { Tab } from '@headlessui/react'; @@ -29,7 +29,7 @@ import FormTextInput from '../ui/forms/FormTextInput'; import Button from '../ui/forms/Button'; const AddressAutofill = dynamic( - // @ts-ignore + // @ts-expect-error () => import('@mapbox/search-js-react').then((mod) => mod.AddressAutofill), { ssr: false }, ); @@ -108,7 +108,8 @@ const LocationSection: FC<{ errors: FieldErrors>; isSubmitting: boolean; setValue: UseFormSetValue>; -}> = ({ register, errors, isSubmitting, setValue }) => { + mapboxAccessToken: string; +}> = ({ register, errors, isSubmitting, setValue, mapboxAccessToken }) => { const onAutoCompleteChange = (address: string) => { setValue('address', address); }; @@ -133,7 +134,7 @@ const LocationSection: FC<{ @@ -201,7 +202,9 @@ const LocationSection: FC<{ ); }; -const CreateBreweryPostForm: FC = () => { +const CreateBreweryPostForm: FC<{ + mapboxAccessToken: string; +}> = ({ mapboxAccessToken }) => { const { register, handleSubmit, @@ -268,6 +271,7 @@ const CreateBreweryPostForm: FC = () => { register={register} errors={errors} isSubmitting={isSubmitting} + mapboxAccessToken={mapboxAccessToken} /> diff --git a/src/components/CreateBeerPostForm.tsx b/src/components/CreateBeerPostForm.tsx index 3f2eea7..fdba62f 100644 --- a/src/components/CreateBeerPostForm.tsx +++ b/src/components/CreateBeerPostForm.tsx @@ -4,8 +4,8 @@ import router from 'next/router'; import { FunctionComponent } from 'react'; import { useForm, SubmitHandler, FieldError } from 'react-hook-form'; import { z } from 'zod'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; -import CreateBeerPostValidationSchema from '@/services/BeerPost/schema/CreateBeerPostValidationSchema'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; +import CreateBeerPostValidationSchema from '@/services/posts/beer-post/schema/CreateBeerPostValidationSchema'; import sendCreateBeerPostRequest from '@/requests/BeerPost/sendCreateBeerPostRequest'; import UploadImageValidationSchema from '@/services/schema/ImageSchema/UploadImageValidationSchema'; import sendUploadBeerImagesRequest from '@/requests/BeerImage/sendUploadBeerImageRequest'; diff --git a/src/components/EditBeerPostForm.tsx b/src/components/EditBeerPostForm.tsx index 8e60360..63727a8 100644 --- a/src/components/EditBeerPostForm.tsx +++ b/src/components/EditBeerPostForm.tsx @@ -7,7 +7,7 @@ import { useForm, SubmitHandler } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import deleteBeerPostRequest from '@/requests/BeerPost/deleteBeerPostRequest'; -import EditBeerPostValidationSchema from '@/services/BeerPost/schema/EditBeerPostValidationSchema'; +import EditBeerPostValidationSchema from '@/services/posts/beer-post/schema/EditBeerPostValidationSchema'; import sendEditBeerPostRequest from '@/requests/BeerPost/sendEditBeerPostRequest'; import createErrorToast from '@/util/createErrorToast'; import Button from './ui/forms/Button'; diff --git a/src/components/Login/LoginForm.tsx b/src/components/Login/LoginForm.tsx index a9d5443..1c7fec9 100644 --- a/src/components/Login/LoginForm.tsx +++ b/src/components/Login/LoginForm.tsx @@ -1,5 +1,5 @@ import sendLoginUserRequest from '@/requests/User/sendLoginUserRequest'; -import LoginValidationSchema from '@/services/User/schema/LoginValidationSchema'; +import LoginValidationSchema from '@/services/users/auth/schema/LoginValidationSchema'; import { zodResolver } from '@hookform/resolvers/zod'; import { useRouter } from 'next/router'; import { useContext } from 'react'; diff --git a/src/components/RegisterUserForm.tsx b/src/components/RegisterUserForm.tsx index 7102ee0..588338e 100644 --- a/src/components/RegisterUserForm.tsx +++ b/src/components/RegisterUserForm.tsx @@ -1,5 +1,5 @@ import sendRegisterUserRequest from '@/requests/User/sendRegisterUserRequest'; -import { CreateUserValidationSchemaWithUsernameAndEmailCheck } from '@/services/User/schema/CreateUserValidationSchemas'; +import { CreateUserValidationSchemaWithUsernameAndEmailCheck } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import { zodResolver } from '@hookform/resolvers/zod'; import { useRouter } from 'next/router'; import { FC } from 'react'; diff --git a/src/components/UserPage/UserFollowButton.tsx b/src/components/UserPage/UserFollowButton.tsx index 59c4050..1f1dc58 100644 --- a/src/components/UserPage/UserFollowButton.tsx +++ b/src/components/UserPage/UserFollowButton.tsx @@ -2,7 +2,7 @@ import useFollowStatus from '@/hooks/data-fetching/user-follows/useFollowStatus' import useGetUsersFollowedByUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowedByUser'; import useGetUsersFollowingUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowingUser'; import sendUserFollowRequest from '@/requests/UserFollow/sendUserFollowRequest'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import { FC, useState } from 'react'; import { FaUserCheck, FaUserPlus } from 'react-icons/fa'; import { z } from 'zod'; diff --git a/src/components/UserPage/UserHeader.tsx b/src/components/UserPage/UserHeader.tsx index 7715b41..bf17d5a 100644 --- a/src/components/UserPage/UserHeader.tsx +++ b/src/components/UserPage/UserHeader.tsx @@ -3,7 +3,7 @@ import useTimeDistance from '@/hooks/utilities/useTimeDistance'; import { FC, useContext } from 'react'; import { z } from 'zod'; import { format } from 'date-fns'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import useGetUsersFollowedByUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowedByUser'; import useGetUsersFollowingUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowingUser'; import UserContext from '@/contexts/UserContext'; diff --git a/src/config/auth/localStrat.ts b/src/config/auth/localStrat.ts index 4417f51..9d8681d 100644 --- a/src/config/auth/localStrat.ts +++ b/src/config/auth/localStrat.ts @@ -1,11 +1,11 @@ -import findUserByUsername from '@/services/User/findUserByUsername'; import Local from 'passport-local'; +import { findUserByUsernameService } from '@/services/users/auth'; import ServerError from '../util/ServerError'; import { validatePassword } from './passwordFns'; const localStrat = new Local.Strategy(async (username, password, done) => { try { - const user = await findUserByUsername(username); + const user = await findUserByUsernameService({ username }); if (!user) { throw new ServerError('Username or password is incorrect.', 401); } diff --git a/src/config/auth/types.ts b/src/config/auth/types.ts index c02fb94..0a28928 100644 --- a/src/config/auth/types.ts +++ b/src/config/auth/types.ts @@ -1,4 +1,4 @@ -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import { IncomingMessage } from 'http'; import { NextApiRequest } from 'next'; import { z } from 'zod'; diff --git a/src/config/cloudinary/helpers/deleteImage.ts b/src/config/cloudinary/helpers/deleteImage.ts new file mode 100644 index 0000000..ba54f22 --- /dev/null +++ b/src/config/cloudinary/helpers/deleteImage.ts @@ -0,0 +1,11 @@ +import { cloudinary } from '..'; + +/** + * Deletes an image from Cloudinary. + * + * @param path - The cloudinary path of the image to be deleted. + * @returns A promise that resolves when the image is successfully deleted. + */ +const deleteImage = (path: string) => cloudinary.uploader.destroy(path); + +export default deleteImage; diff --git a/src/config/cloudinary/index.ts b/src/config/cloudinary/index.ts index 625b176..e624940 100644 --- a/src/config/cloudinary/index.ts +++ b/src/config/cloudinary/index.ts @@ -4,6 +4,7 @@ import { NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, CLOUDINARY_KEY, CLOUDINARY_SECRET, + NODE_ENV, } from '../env'; import CloudinaryStorage from './CloudinaryStorage'; @@ -14,6 +15,9 @@ cloudinary.config({ }); /** Cloudinary storage instance. */ -const storage = new CloudinaryStorage({ cloudinary, params: { folder: 'biergarten' } }); +const storage = new CloudinaryStorage({ + cloudinary, + params: { folder: NODE_ENV === 'production' ? 'biergarten' : 'biergarten-dev' }, +}); export { cloudinary, storage }; diff --git a/src/config/jwt/index.ts b/src/config/jwt/index.ts index d7d7aad..ba60a70 100644 --- a/src/config/jwt/index.ts +++ b/src/config/jwt/index.ts @@ -15,7 +15,7 @@ export const verifyConfirmationToken = async (token: string) => { const parsed = BasicUserInfoSchema.safeParse(decoded); if (!parsed.success) { - throw new Error('Invalid token'); + throw new ServerError('Invalid token.', 401); } return parsed.data; diff --git a/src/config/nextConnect/middleware/getCurrentUser.ts b/src/config/nextConnect/middleware/getCurrentUser.ts index 10bae13..4f2c3ba 100644 --- a/src/config/nextConnect/middleware/getCurrentUser.ts +++ b/src/config/nextConnect/middleware/getCurrentUser.ts @@ -1,9 +1,10 @@ import { NextApiResponse } from 'next'; import { NextHandler } from 'next-connect'; -import findUserById from '@/services/User/findUserById'; + import ServerError from '@/config/util/ServerError'; -import { getLoginSession } from '../../auth/session'; -import { UserExtendedNextApiRequest } from '../../auth/types'; +import { findUserByIdService } from '@/services/users/auth'; +import { getLoginSession } from '@/config/auth/session'; +import { UserExtendedNextApiRequest } from '@/config/auth/types'; /** Get the current user from the session. Adds the user to the request object. */ const getCurrentUser = async ( @@ -12,7 +13,7 @@ const getCurrentUser = async ( next: NextHandler, ) => { const session = await getLoginSession(req); - const user = await findUserById(session?.id); + const user = await findUserByIdService({ userId: session?.id }); if (!user) { throw new ServerError('User is not logged in.', 401); diff --git a/src/contexts/UserContext.tsx b/src/contexts/UserContext.tsx index 2cf7213..00e1506 100644 --- a/src/contexts/UserContext.tsx +++ b/src/contexts/UserContext.tsx @@ -1,5 +1,5 @@ import useUser from '@/hooks/auth/useUser'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import { ReactNode, createContext } from 'react'; import { z } from 'zod'; diff --git a/src/controllers/comments/beer-comments/index.ts b/src/controllers/comments/beer-comments/index.ts new file mode 100644 index 0000000..7ca8dbc --- /dev/null +++ b/src/controllers/comments/beer-comments/index.ts @@ -0,0 +1,114 @@ +import { NextApiResponse } from 'next'; +import { NextHandler } from 'next-connect'; +import { z } from 'zod'; + +import ServerError from '@/config/util/ServerError'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; + +import { + getBeerPostCommentByIdService, + editBeerPostCommentByIdService, + createBeerPostCommentService, + getAllBeerCommentsService, + deleteBeerCommentByIdService, +} from '@/services/comments/beer-comment'; + +import { + CommentRequest, + EditAndCreateCommentRequest, + GetAllCommentsRequest, +} from '../types'; + +export const checkIfBeerCommentOwner = async ( + req: T, + res: NextApiResponse>, + next: NextHandler, +) => { + const { id } = req.query; + const user = req.user!; + const comment = await getBeerPostCommentByIdService({ beerPostCommentId: id }); + + if (!comment) { + throw new ServerError('Comment not found', 404); + } + + if (comment.postedBy.id !== user.id) { + throw new ServerError('You are not authorized to modify this comment', 403); + } + + return next(); +}; + +export const editBeerPostComment = async ( + req: EditAndCreateCommentRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + + await editBeerPostCommentByIdService({ body: req.body, beerPostCommentId: id }); + + res.status(200).json({ + success: true, + message: 'Comment updated successfully', + statusCode: 200, + }); +}; + +export const deleteBeerPostComment = async ( + req: CommentRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + + await deleteBeerCommentByIdService({ beerPostCommentId: id }); + + res.status(200).json({ + success: true, + message: 'Comment deleted successfully', + statusCode: 200, + }); +}; + +export const createBeerPostComment = async ( + req: EditAndCreateCommentRequest, + res: NextApiResponse>, +) => { + const beerPostId = req.query.id; + + const newBeerComment = await createBeerPostCommentService({ + body: req.body, + userId: req.user!.id, + beerPostId, + }); + + res.status(201).json({ + message: 'Beer comment created successfully', + statusCode: 201, + payload: newBeerComment, + success: true, + }); +}; + +export const getAllBeerPostComments = async ( + req: GetAllCommentsRequest, + res: NextApiResponse>, +) => { + const beerPostId = req.query.id; + // eslint-disable-next-line @typescript-eslint/naming-convention + const { page_size, page_num } = req.query; + + const { comments, count } = await getAllBeerCommentsService({ + beerPostId, + pageNum: parseInt(page_num, 10), + pageSize: parseInt(page_size, 10), + }); + + res.setHeader('X-Total-Count', count); + + res.status(200).json({ + message: 'Beer comments fetched successfully', + statusCode: 200, + payload: comments, + success: true, + }); +}; diff --git a/src/controllers/comments/beer-style-comments/index.ts b/src/controllers/comments/beer-style-comments/index.ts new file mode 100644 index 0000000..9365c0c --- /dev/null +++ b/src/controllers/comments/beer-style-comments/index.ts @@ -0,0 +1,118 @@ +import ServerError from '@/config/util/ServerError'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse } from 'next'; +import { NextHandler } from 'next-connect'; +import { z } from 'zod'; + +import { + updateBeerStyleCommentById, + createNewBeerStyleComment, + getAllBeerStyleComments, + findBeerStyleCommentById, + deleteBeerStyleCommentById, +} from '@/services/comments/beer-style-comment'; + +import { + CommentRequest, + EditAndCreateCommentRequest, + GetAllCommentsRequest, +} from '../types'; + +export const checkIfBeerStyleCommentOwner = async < + CommentRequestType extends CommentRequest, +>( + req: CommentRequestType, + res: NextApiResponse>, + next: NextHandler, +) => { + const { id } = req.query; + const user = req.user!; + + const beerStyleComment = await findBeerStyleCommentById({ beerStyleCommentId: id }); + + if (!beerStyleComment) { + throw new ServerError('Beer style comment not found.', 404); + } + + if (beerStyleComment.postedBy.id !== user.id) { + throw new ServerError( + 'You are not authorized to modify this beer style comment.', + 403, + ); + } + + return next(); +}; + +export const editBeerStyleComment = async ( + req: EditAndCreateCommentRequest, + res: NextApiResponse>, +) => { + await updateBeerStyleCommentById({ + beerStyleCommentId: req.query.id, + body: req.body, + }); + + return res.status(200).json({ + success: true, + message: 'Comment updated successfully', + statusCode: 200, + }); +}; + +export const deleteBeerStyleComment = async ( + req: CommentRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + + await deleteBeerStyleCommentById({ beerStyleCommentId: id }); + + res.status(200).json({ + success: true, + message: 'Comment deleted successfully', + statusCode: 200, + }); +}; + +export const createComment = async ( + req: EditAndCreateCommentRequest, + res: NextApiResponse>, +) => { + const newBeerStyleComment = await createNewBeerStyleComment({ + body: req.body, + beerStyleId: req.query.id, + userId: req.user!.id, + }); + + res.status(201).json({ + message: 'Beer comment created successfully', + statusCode: 201, + payload: newBeerStyleComment, + success: true, + }); +}; + +export const getAll = async ( + req: GetAllCommentsRequest, + res: NextApiResponse>, +) => { + const beerStyleId = req.query.id; + // eslint-disable-next-line @typescript-eslint/naming-convention + const { page_size, page_num } = req.query; + + const { comments, count } = await getAllBeerStyleComments({ + beerStyleId, + pageNum: parseInt(page_num, 10), + pageSize: parseInt(page_size, 10), + }); + + res.setHeader('X-Total-Count', count); + + res.status(200).json({ + message: 'Beer comments fetched successfully', + statusCode: 200, + payload: comments, + success: true, + }); +}; diff --git a/src/controllers/comments/brewery-comments/index.ts b/src/controllers/comments/brewery-comments/index.ts new file mode 100644 index 0000000..103dca9 --- /dev/null +++ b/src/controllers/comments/brewery-comments/index.ts @@ -0,0 +1,121 @@ +import ServerError from '@/config/util/ServerError'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse } from 'next'; +import { NextHandler } from 'next-connect'; +import { z } from 'zod'; + +import { + getBreweryCommentById, + createNewBreweryComment, + getAllBreweryComments, + deleteBreweryCommentByIdService, + updateBreweryCommentById, +} from '@/services/comments/brewery-comment'; + +import { + CommentRequest, + EditAndCreateCommentRequest, + GetAllCommentsRequest, +} from '../types'; + +export const checkIfBreweryCommentOwner = async < + CommentRequestType extends CommentRequest, +>( + req: CommentRequestType, + res: NextApiResponse>, + next: NextHandler, +) => { + const { id } = req.query; + const user = req.user!; + const comment = await getBreweryCommentById({ breweryCommentId: id }); + + if (!comment) { + throw new ServerError('Comment not found', 404); + } + + if (comment.postedBy.id !== user.id) { + throw new ServerError('You are not authorized to modify this comment', 403); + } + + return next(); +}; + +export const editBreweryPostComment = async ( + req: EditAndCreateCommentRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + + const updated = updateBreweryCommentById({ + breweryCommentId: id, + body: req.body, + }); + + return res.status(200).json({ + success: true, + message: 'Comment updated successfully', + statusCode: 200, + payload: updated, + }); +}; + +export const deleteBreweryPostComment = async ( + req: CommentRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + + await deleteBreweryCommentByIdService({ breweryCommentId: id }); + + res.status(200).json({ + success: true, + message: 'Brewery comment deleted successfully', + statusCode: 200, + }); +}; + +export const createComment = async ( + req: EditAndCreateCommentRequest, + res: NextApiResponse>, +) => { + const breweryPostId = req.query.id; + + const user = req.user!; + + const newBreweryComment = await createNewBreweryComment({ + body: req.body, + breweryPostId, + userId: user.id, + }); + + res.status(201).json({ + message: 'Brewery comment created successfully', + statusCode: 201, + payload: newBreweryComment, + success: true, + }); +}; + +export const getAll = async ( + req: GetAllCommentsRequest, + res: NextApiResponse>, +) => { + const breweryPostId = req.query.id; + // eslint-disable-next-line @typescript-eslint/naming-convention + const { page_size, page_num } = req.query; + + const { comments, count } = await getAllBreweryComments({ + id: breweryPostId, + pageNum: parseInt(page_num, 10), + pageSize: parseInt(page_size, 10), + }); + + res.setHeader('X-Total-Count', count); + + res.status(200).json({ + message: 'Brewery comments fetched successfully', + statusCode: 200, + payload: comments, + success: true, + }); +}; diff --git a/src/controllers/comments/types/index.ts b/src/controllers/comments/types/index.ts new file mode 100644 index 0000000..f56f153 --- /dev/null +++ b/src/controllers/comments/types/index.ts @@ -0,0 +1,15 @@ +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; +import { z } from 'zod'; + +export interface CommentRequest extends UserExtendedNextApiRequest { + query: { id: string }; +} + +export interface EditAndCreateCommentRequest extends CommentRequest { + body: z.infer; +} + +export interface GetAllCommentsRequest extends UserExtendedNextApiRequest { + query: { id: string; page_size: string; page_num: string }; +} diff --git a/src/controllers/images/beer-images/index.ts b/src/controllers/images/beer-images/index.ts new file mode 100644 index 0000000..0a1d5c1 --- /dev/null +++ b/src/controllers/images/beer-images/index.ts @@ -0,0 +1,51 @@ +import ServerError from '@/config/util/ServerError'; +import { + addBeerImagesService, + deleteBeerImageService, +} from '@/services/images/beer-image'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse } from 'next'; +import { z } from 'zod'; +import { DeleteImageRequest, UploadImagesRequest } from '../types'; + +// eslint-disable-next-line import/prefer-default-export +export const processBeerImageData = async ( + req: UploadImagesRequest, + res: NextApiResponse>, +) => { + const { files, user, body } = req; + + if (!files || !files.length) { + throw new ServerError('No images uploaded', 400); + } + + const beerImages = await addBeerImagesService({ + beerPostId: req.query.id, + userId: user!.id, + body, + files, + }); + + res.status(200).json({ + success: true, + message: `Successfully uploaded ${beerImages.length} image${ + beerImages.length > 1 ? 's' : '' + }`, + statusCode: 200, + }); +}; + +export const deleteBeerImageData = async ( + req: DeleteImageRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + + await deleteBeerImageService({ beerImageId: id }); + + res.status(200).json({ + success: true, + message: `Successfully deleted image with id ${id}`, + statusCode: 200, + }); +}; diff --git a/src/controllers/images/brewery-images/index.ts b/src/controllers/images/brewery-images/index.ts new file mode 100644 index 0000000..a7a4a3c --- /dev/null +++ b/src/controllers/images/brewery-images/index.ts @@ -0,0 +1,34 @@ +import ServerError from '@/config/util/ServerError'; + +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse } from 'next'; +import { z } from 'zod'; +import { addBreweryImagesService } from '@/services/images/brewery-image'; +import { UploadImagesRequest } from '../types'; + +// eslint-disable-next-line import/prefer-default-export +export const processBreweryImageData = async ( + req: UploadImagesRequest, + res: NextApiResponse>, +) => { + const { files, user, body } = req; + + if (!files || !files.length) { + throw new ServerError('No images uploaded', 400); + } + + const breweryImages = await addBreweryImagesService({ + breweryPostId: req.query.id, + userId: user!.id, + body, + files, + }); + + res.status(200).json({ + success: true, + message: `Successfully uploaded ${breweryImages.length} image${ + breweryImages.length > 1 ? 's' : '' + }`, + statusCode: 200, + }); +}; diff --git a/src/controllers/images/types/index.ts b/src/controllers/images/types/index.ts new file mode 100644 index 0000000..8d4b308 --- /dev/null +++ b/src/controllers/images/types/index.ts @@ -0,0 +1,13 @@ +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import ImageMetadataValidationSchema from '@/services/schema/ImageSchema/ImageMetadataValidationSchema'; +import { z } from 'zod'; + +export interface UploadImagesRequest extends UserExtendedNextApiRequest { + files?: Express.Multer.File[]; + query: { id: string }; + body: z.infer; +} + +export interface DeleteImageRequest extends UserExtendedNextApiRequest { + query: { id: string }; +} diff --git a/src/controllers/likes/beer-posts-likes/index.ts b/src/controllers/likes/beer-posts-likes/index.ts new file mode 100644 index 0000000..0211164 --- /dev/null +++ b/src/controllers/likes/beer-posts-likes/index.ts @@ -0,0 +1,89 @@ +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import ServerError from '@/config/util/ServerError'; + +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse, NextApiRequest } from 'next'; +import { z } from 'zod'; + +import { getBeerPostById } from '@/services/posts/beer-post'; +import { + findBeerPostLikeByIdService, + createBeerPostLikeService, + removeBeerPostLikeService, + getBeerPostLikeCountService, +} from '@/services/likes/beer-post-like'; +import { LikeRequest } from '../types'; + +export const sendBeerPostLikeRequest = async ( + req: LikeRequest, + res: NextApiResponse>, +) => { + const user = req.user!; + const id = req.query.id as string; + + const beer = await getBeerPostById({ beerPostId: id }); + if (!beer) { + throw new ServerError('Could not find a beer post with that id.', 404); + } + + const liked = await findBeerPostLikeByIdService({ + beerPostId: beer.id, + likedById: user.id, + }); + + if (liked) { + await removeBeerPostLikeService({ beerPostLikeId: liked.id }); + res.status(200).json({ + success: true, + message: 'Successfully unliked beer post.', + statusCode: 200, + payload: { liked: false }, + }); + + return; + } + + await createBeerPostLikeService({ beerPostId: beer.id, likedById: user.id }); + res.status(200).json({ + success: true, + message: 'Successfully liked beer post.', + statusCode: 200, + payload: { liked: true }, + }); +}; + +export const getBeerPostLikeCount = async ( + req: NextApiRequest, + res: NextApiResponse>, +) => { + const id = req.query.id as string; + + const likeCount = await getBeerPostLikeCountService({ beerPostId: id }); + + res.status(200).json({ + success: true, + message: 'Successfully retrieved like count.', + statusCode: 200, + payload: { likeCount }, + }); +}; + +export const checkIfBeerPostIsLiked = async ( + req: UserExtendedNextApiRequest, + res: NextApiResponse>, +) => { + const user = req.user!; + const beerPostId = req.query.id as string; + + const alreadyLiked = await findBeerPostLikeByIdService({ + beerPostId, + likedById: user.id, + }); + + res.status(200).json({ + success: true, + message: alreadyLiked ? 'Beer post is liked.' : 'Beer post is not liked.', + statusCode: 200, + payload: { isLiked: !!alreadyLiked }, + }); +}; diff --git a/src/controllers/likes/beer-style-likes/index.ts b/src/controllers/likes/beer-style-likes/index.ts new file mode 100644 index 0000000..fd2499d --- /dev/null +++ b/src/controllers/likes/beer-style-likes/index.ts @@ -0,0 +1,82 @@ +import ServerError from '@/config/util/ServerError'; + +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse, NextApiRequest } from 'next'; +import { z } from 'zod'; +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import { + createBeerStyleLikeService, + findBeerStyleLikeService, + getBeerStyleLikeCountService, + removeBeerStyleLikeService, +} from '@/services/likes/beer-style-like'; +import { getBeerStyleByIdService } from '@/services/posts/beer-style-post'; +import { LikeRequest } from '../types'; + +export const sendBeerStyleLikeRequest = async ( + req: LikeRequest, + res: NextApiResponse>, +) => { + const user = req.user!; + const { id } = req.query; + + const beerStyle = await getBeerStyleByIdService({ beerStyleId: id }); + if (!beerStyle) { + throw new ServerError('Could not find a beer style with that id.', 404); + } + + const beerStyleLike = await findBeerStyleLikeService({ + beerStyleId: beerStyle.id, + likedById: user.id, + }); + + if (beerStyleLike) { + await removeBeerStyleLikeService({ beerStyleLikeId: beerStyleLike.id }); + res.status(200).json({ + message: 'Successfully unliked beer style.', + success: true, + statusCode: 200, + }); + } else { + await createBeerStyleLikeService({ beerStyleId: beerStyle.id, likedById: user.id }); + res.status(200).json({ + message: 'Successfully liked beer style.', + success: true, + statusCode: 200, + }); + } +}; + +export const getBeerStyleLikeCountRequest = async ( + req: NextApiRequest, + res: NextApiResponse>, +) => { + const id = req.query.id as string; + const likeCount = await getBeerStyleLikeCountService({ beerStyleId: id }); + + res.status(200).json({ + success: true, + message: 'Successfully retrieved like count.', + statusCode: 200, + payload: { likeCount }, + }); +}; + +export const checkIfBeerStyleIsLiked = async ( + req: UserExtendedNextApiRequest, + res: NextApiResponse>, +) => { + const user = req.user!; + const beerStyleId = req.query.id as string; + + const alreadyLiked = await findBeerStyleLikeService({ + beerStyleId, + likedById: user.id, + }); + res.status(200).json({ + success: true, + message: alreadyLiked ? 'Beer style is liked.' : 'Beer style is not liked.', + statusCode: 200, + payload: { isLiked: !!alreadyLiked }, + }); +}; diff --git a/src/controllers/likes/brewery-post-likes/index.ts b/src/controllers/likes/brewery-post-likes/index.ts new file mode 100644 index 0000000..6eeb790 --- /dev/null +++ b/src/controllers/likes/brewery-post-likes/index.ts @@ -0,0 +1,96 @@ +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import ServerError from '@/config/util/ServerError'; + +import { + createBreweryPostLikeService, + findBreweryPostLikeService, + getBreweryPostLikeCountService, + removeBreweryPostLikeService, +} from '@/services/likes/brewery-post-like'; +import { getBreweryPostByIdService } from '@/services/posts/brewery-post'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse, NextApiRequest } from 'next'; +import { z } from 'zod'; + +export const sendBreweryPostLikeRequest = async ( + req: UserExtendedNextApiRequest, + res: NextApiResponse>, +) => { + const id = req.query.id! as string; + const user = req.user!; + + const breweryPost = await getBreweryPostByIdService({ breweryPostId: id }); + + if (!breweryPost) { + throw new ServerError('Could not find a brewery post with that id', 404); + } + + const like = await findBreweryPostLikeService({ + breweryPostId: breweryPost.id, + likedById: user.id, + }); + + if (like) { + await removeBreweryPostLikeService({ breweryPostLikeId: like.id }); + res.status(200).json({ + success: true, + message: 'Successfully removed like from brewery post.', + statusCode: 200, + }); + + return; + } + + await createBreweryPostLikeService({ + breweryPostId: breweryPost.id, + likedById: user.id, + }); + res.status(200).json({ + success: true, + message: 'Successfully liked brewery post.', + statusCode: 200, + }); +}; + +export const getBreweryPostLikeCount = async ( + req: NextApiRequest, + res: NextApiResponse>, +) => { + const id = req.query.id! as string; + + const breweryPost = await getBreweryPostByIdService({ breweryPostId: id }); + if (!breweryPost) { + throw new ServerError('Could not find a brewery post with that id', 404); + } + + const likeCount = await getBreweryPostLikeCountService({ + breweryPostId: breweryPost.id, + }); + + res.status(200).json({ + success: true, + message: 'Successfully retrieved like count', + statusCode: 200, + payload: { likeCount }, + }); +}; + +export const getBreweryPostLikeStatus = async ( + req: UserExtendedNextApiRequest, + res: NextApiResponse>, +) => { + const user = req.user!; + const id = req.query.id as string; + + const liked = await findBreweryPostLikeService({ + breweryPostId: id, + likedById: user.id, + }); + + res.status(200).json({ + success: true, + message: liked ? 'Brewery post is liked.' : 'Brewery post is not liked.', + statusCode: 200, + payload: { isLiked: !!liked }, + }); +}; diff --git a/src/controllers/likes/types/index.ts b/src/controllers/likes/types/index.ts new file mode 100644 index 0000000..424a4e4 --- /dev/null +++ b/src/controllers/likes/types/index.ts @@ -0,0 +1,5 @@ +import { UserExtendedNextApiRequest } from '@/config/auth/types'; + +export interface LikeRequest extends UserExtendedNextApiRequest { + query: { id: string }; +} diff --git a/src/controllers/posts/beer-posts/index.ts b/src/controllers/posts/beer-posts/index.ts new file mode 100644 index 0000000..76cacf7 --- /dev/null +++ b/src/controllers/posts/beer-posts/index.ts @@ -0,0 +1,170 @@ +import ServerError from '@/config/util/ServerError'; + +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse } from 'next'; +import { NextHandler } from 'next-connect'; +import { z } from 'zod'; +import { GetAllPostsByConnectedPostId } from '@/controllers/posts/types'; +import { + BeerPostRequest, + CreateBeerPostRequest, + EditBeerPostRequest, + GetAllBeerPostsRequest, + GetBeerRecommendationsRequest, +} from '@/controllers/posts/beer-posts/types'; +import { + getBeerPostById, + editBeerPostByIdService, + deleteBeerPostByIdService, + getBeerRecommendationsService, + getAllBeerPostsService, + createNewBeerPost, + getBeerPostsByPostedByIdService, +} from '@/services/posts/beer-post'; + +export const checkIfBeerPostOwner = async ( + req: BeerPostRequestType, + res: NextApiResponse, + next: NextHandler, +) => { + const { user, query } = req; + const { id } = query; + + const beerPost = await getBeerPostById({ beerPostId: id }); + + if (!beerPost) { + throw new ServerError('Beer post not found', 404); + } + + if (beerPost.postedBy.id !== user!.id) { + throw new ServerError('You cannot edit that beer post.', 403); + } + + return next(); +}; + +export const editBeerPost = async ( + req: EditBeerPostRequest, + res: NextApiResponse>, +) => { + await editBeerPostByIdService({ beerPostId: req.query.id, body: req.body }); + + res.status(200).json({ + message: 'Beer post updated successfully', + success: true, + statusCode: 200, + }); +}; + +export const deleteBeerPost = async (req: BeerPostRequest, res: NextApiResponse) => { + const { id } = req.query; + + const deleted = await deleteBeerPostByIdService({ beerPostId: id }); + if (!deleted) { + throw new ServerError('Beer post not found', 404); + } + + res.status(200).json({ + message: 'Beer post deleted successfully', + success: true, + statusCode: 200, + }); +}; + +export const getBeerPostRecommendations = async ( + req: GetBeerRecommendationsRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + + const beerPost = await getBeerPostById({ beerPostId: id }); + + if (!beerPost) { + throw new ServerError('Beer post not found', 404); + } + + const pageNum = parseInt(req.query.page_num as string, 10); + const pageSize = parseInt(req.query.page_size as string, 10); + + const { beerRecommendations, count } = await getBeerRecommendationsService({ + beerPost, + pageNum, + pageSize, + }); + + res.setHeader('X-Total-Count', count); + res.status(200).json({ + success: true, + message: 'Recommendations fetched successfully', + statusCode: 200, + payload: beerRecommendations, + }); +}; + +export const getBeerPosts = async ( + req: GetAllBeerPostsRequest, + res: NextApiResponse>, +) => { + const pageNum = parseInt(req.query.page_num, 10); + const pageSize = parseInt(req.query.page_size, 10); + + const { beerPosts, count } = await getAllBeerPostsService({ pageNum, pageSize }); + + res.setHeader('X-Total-Count', count); + + res.status(200).json({ + message: 'Beer posts retrieved successfully', + statusCode: 200, + payload: beerPosts, + success: true, + }); +}; + +export const createBeerPost = async ( + req: CreateBeerPostRequest, + res: NextApiResponse>, +) => { + const { name, description, styleId, abv, ibu, breweryId } = req.body; + + const newBeerPost = await createNewBeerPost({ + name, + description, + abv, + ibu, + styleId, + breweryId, + userId: req.user!.id, + }); + + res.status(201).json({ + message: 'Beer post created successfully', + statusCode: 201, + payload: newBeerPost, + success: true, + }); +}; + +export const getBeerPostsByUserId = async ( + req: GetAllPostsByConnectedPostId, + res: NextApiResponse>, +) => { + const pageNum = parseInt(req.query.page_num, 10); + const pageSize = parseInt(req.query.page_size, 10); + + const { id } = req.query; + + const { beerPosts, count } = await getBeerPostsByPostedByIdService({ + pageNum, + pageSize, + postedById: id, + }); + + res.setHeader('X-Total-Count', count); + + res.status(200).json({ + message: `Beer posts by user ${id} fetched successfully`, + statusCode: 200, + payload: beerPosts, + success: true, + }); +}; diff --git a/src/controllers/posts/beer-posts/types/index.ts b/src/controllers/posts/beer-posts/types/index.ts new file mode 100644 index 0000000..a1b5977 --- /dev/null +++ b/src/controllers/posts/beer-posts/types/index.ts @@ -0,0 +1,25 @@ +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import CreateBeerPostValidationSchema from '@/services/posts/beer-post/schema/CreateBeerPostValidationSchema'; +import EditBeerPostValidationSchema from '@/services/posts/beer-post/schema/EditBeerPostValidationSchema'; +import { NextApiRequest } from 'next'; +import { z } from 'zod'; + +export interface BeerPostRequest extends UserExtendedNextApiRequest { + query: { id: string }; +} + +export interface EditBeerPostRequest extends BeerPostRequest { + body: z.infer; +} + +export interface GetAllBeerPostsRequest extends NextApiRequest { + query: { page_num: string; page_size: string }; +} + +export interface GetBeerRecommendationsRequest extends BeerPostRequest { + query: { id: string; page_num: string; page_size: string }; +} + +export interface CreateBeerPostRequest extends UserExtendedNextApiRequest { + body: z.infer; +} diff --git a/src/controllers/posts/beer-styles/index.ts b/src/controllers/posts/beer-styles/index.ts new file mode 100644 index 0000000..e5676a1 --- /dev/null +++ b/src/controllers/posts/beer-styles/index.ts @@ -0,0 +1,105 @@ +import { NextApiResponse } from 'next'; + +import { z } from 'zod'; + +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; + +import { getBeerPostsByBeerStyleIdService } from '@/services/posts/beer-post'; +import { + createBeerStyleService, + getAllBeerStylesService, + getBeerStyleByIdService, +} from '@/services/posts/beer-style-post'; + +import { CreateBeerStyleRequest, GetBeerStyleByIdRequest } from './types'; +import { GetAllPostsByConnectedPostId, GetAllPostsRequest } from '../types'; + +export const getBeerStyle = async ( + req: GetBeerStyleByIdRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + + const beerStyle = await getBeerStyleByIdService({ + beerStyleId: id, + }); + + res.status(200).json({ + message: 'Beer style retrieved successfully.', + statusCode: 200, + payload: beerStyle, + success: true, + }); +}; + +export const getAllBeersByBeerStyle = async ( + req: GetAllPostsByConnectedPostId, + res: NextApiResponse>, +) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { page_size, page_num, id } = req.query; + + const { beerPosts, count } = await getBeerPostsByBeerStyleIdService({ + pageNum: parseInt(page_num, 10), + pageSize: parseInt(page_size, 10), + styleId: id, + }); + + res.setHeader('X-Total-Count', count); + + res.status(200).json({ + message: `Beers with style id ${id} retrieved successfully.`, + statusCode: 200, + payload: beerPosts, + success: true, + }); +}; + +export const getBeerStyles = async ( + req: GetAllPostsRequest, + res: NextApiResponse>, +) => { + const pageNum = parseInt(req.query.page_num, 10); + const pageSize = parseInt(req.query.page_size, 10); + + const { beerStyles, beerStyleCount } = await getAllBeerStylesService({ + pageNum, + pageSize, + }); + + res.setHeader('X-Total-Count', beerStyleCount); + + res.status(200).json({ + message: 'Beer styles retrieved successfully.', + statusCode: 200, + payload: beerStyles, + success: true, + }); +}; + +export const createBeerStyle = async ( + req: CreateBeerStyleRequest, + res: NextApiResponse>, +) => { + const { abvRange, description, glasswareId, ibuRange, name } = req.body; + + const user = req.user!; + + const beerStyle = await createBeerStyleService({ + glasswareId, + postedById: user.id, + body: { + abvRange, + description, + ibuRange, + name, + }, + }); + + res.json({ + message: 'Beer style created successfully.', + statusCode: 200, + payload: beerStyle, + success: true, + }); +}; diff --git a/src/controllers/posts/beer-styles/types/index.ts b/src/controllers/posts/beer-styles/types/index.ts new file mode 100644 index 0000000..1b848d6 --- /dev/null +++ b/src/controllers/posts/beer-styles/types/index.ts @@ -0,0 +1,13 @@ +import { NextApiRequest } from 'next'; + +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import { z } from 'zod'; +import CreateBeerStyleValidationSchema from '@/services/posts/beer-style-post/schema/CreateBeerStyleValidationSchema'; + +export interface GetBeerStyleByIdRequest extends NextApiRequest { + query: { id: string }; +} + +export interface CreateBeerStyleRequest extends UserExtendedNextApiRequest { + body: z.infer; +} diff --git a/src/controllers/posts/breweries/index.ts b/src/controllers/posts/breweries/index.ts new file mode 100644 index 0000000..4e5cdfd --- /dev/null +++ b/src/controllers/posts/breweries/index.ts @@ -0,0 +1,218 @@ +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse } from 'next'; +import { z } from 'zod'; + +import geocode from '@/config/mapbox/geocoder'; +import ServerError from '@/config/util/ServerError'; + +import { + getAllBreweryPostsByPostedByIdService, + getAllBreweryPostsService, + createNewBreweryPostService, + createBreweryPostLocationService, + getMapBreweryPostsService, + getBreweryPostByIdService, + updateBreweryPostService, + deleteBreweryPostService, +} from '@/services/posts/brewery-post'; +import { getBeerPostsByBreweryIdService } from '@/services/posts/beer-post'; +import { NextHandler } from 'next-connect'; + +import { + BreweryPostRequest, + CreateBreweryPostRequest, + EditBreweryPostRequest, + GetBreweryPostsRequest, +} from './types'; +import { GetAllPostsByConnectedPostId } from '../types'; + +export const getBreweryPostsByUserId = async ( + req: GetAllPostsByConnectedPostId, + res: NextApiResponse>, +) => { + const pageNum = parseInt(req.query.page_num, 10); + const pageSize = parseInt(req.query.page_size, 10); + + const { id } = req.query; + + const { breweryPosts, count } = await getAllBreweryPostsByPostedByIdService({ + pageNum, + pageSize, + postedById: id, + }); + + res.setHeader('X-Total-Count', count); + + res.status(200).json({ + message: `Brewery posts by user ${id} fetched successfully`, + statusCode: 200, + payload: breweryPosts, + success: true, + }); +}; + +export const getBreweryPosts = async ( + req: GetBreweryPostsRequest, + res: NextApiResponse>, +) => { + const pageNum = parseInt(req.query.page_num, 10); + const pageSize = parseInt(req.query.page_size, 10); + + const { breweryPosts, count } = await getAllBreweryPostsService({ pageNum, pageSize }); + + res.setHeader('X-Total-Count', count); + res.status(200).json({ + message: 'Brewery posts retrieved successfully', + statusCode: 200, + payload: breweryPosts, + success: true, + }); +}; + +export const createBreweryPost = async ( + req: CreateBreweryPostRequest, + res: NextApiResponse>, +) => { + const { name, description, dateEstablished, address, city, country, region } = req.body; + const userId = req.user!.id; + + const fullAddress = `${address}, ${city}, ${region}, ${country}`; + + const geocoded = await geocode(fullAddress); + + if (!geocoded) { + throw new ServerError('Address is not valid', 400); + } + + const [latitude, longitude] = geocoded.center; + + const location = await createBreweryPostLocationService({ + body: { + address, + city, + country, + stateOrProvince: region, + coordinates: [latitude, longitude], + }, + postedById: userId, + }); + + const newBreweryPost = await createNewBreweryPostService({ + name, + description, + locationId: location.id, + dateEstablished, + userId, + }); + + res.status(201).json({ + message: 'Brewery post created successfully', + statusCode: 201, + payload: newBreweryPost, + success: true, + }); +}; + +export const getMapBreweryPosts = async ( + req: GetBreweryPostsRequest, + res: NextApiResponse>, +) => { + const pageNum = parseInt(req.query.page_num, 10); + const pageSize = parseInt(req.query.page_size, 10); + + const { breweryPosts, count } = await getMapBreweryPostsService({ + pageNum, + pageSize, + }); + + res.setHeader('X-Total-Count', count); + + res.status(200).json({ + message: 'Brewery posts retrieved successfully', + statusCode: 200, + payload: breweryPosts, + success: true, + }); +}; + +export const getAllBeersByBrewery = async ( + req: GetAllPostsByConnectedPostId, + res: NextApiResponse>, +) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { page_size, page_num, id } = req.query; + + const pageNum = parseInt(page_num, 10); + const pageSize = parseInt(page_size, 10); + + const { beerPosts, count } = await getBeerPostsByBreweryIdService({ + pageNum, + pageSize, + breweryId: id, + }); + + res.setHeader('X-Total-Count', count); + + res.status(200).json({ + message: 'Beers fetched successfully', + statusCode: 200, + payload: beerPosts, + success: true, + }); +}; + +export const checkIfBreweryPostOwner = async ( + req: BreweryPostRequest, + res: NextApiResponse, + next: NextHandler, +) => { + const user = req.user!; + const { id } = req.query; + + const breweryPost = await getBreweryPostByIdService({ breweryPostId: id }); + if (!breweryPost) { + throw new ServerError('Brewery post not found', 404); + } + + if (breweryPost.postedBy.id !== user.id) { + throw new ServerError('You are not the owner of this brewery post', 403); + } + + return next(); +}; + +export const editBreweryPost = async ( + req: EditBreweryPostRequest, + res: NextApiResponse>, +) => { + const { + body, + query: { id }, + } = req; + + await updateBreweryPostService({ breweryPostId: id, body }); + + res.status(200).json({ + message: 'Brewery post updated successfully', + success: true, + statusCode: 200, + }); +}; + +export const deleteBreweryPost = async ( + req: BreweryPostRequest, + res: NextApiResponse, +) => { + const { id } = req.query; + const deleted = await deleteBreweryPostService({ breweryPostId: id }); + + if (!deleted) { + throw new ServerError('Brewery post not found', 404); + } + + res.status(200).json({ + message: 'Brewery post deleted successfully', + success: true, + statusCode: 200, + }); +}; diff --git a/src/controllers/posts/breweries/types/index.ts b/src/controllers/posts/breweries/types/index.ts new file mode 100644 index 0000000..85dd30a --- /dev/null +++ b/src/controllers/posts/breweries/types/index.ts @@ -0,0 +1,22 @@ +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import CreateBreweryPostSchema from '@/services/posts/brewery-post/schema/CreateBreweryPostSchema'; +import EditBreweryPostValidationSchema from '@/services/posts/brewery-post/schema/EditBreweryPostValidationSchema'; +import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; +import { NextApiRequest } from 'next'; +import { z } from 'zod'; + +export interface GetBreweryPostsRequest extends NextApiRequest { + query: z.infer; +} + +export interface CreateBreweryPostRequest extends UserExtendedNextApiRequest { + body: z.infer; +} + +export interface BreweryPostRequest extends UserExtendedNextApiRequest { + query: { id: string }; +} + +export interface EditBreweryPostRequest extends BreweryPostRequest { + body: z.infer; +} diff --git a/src/controllers/posts/types/index.ts b/src/controllers/posts/types/index.ts new file mode 100644 index 0000000..e98c03f --- /dev/null +++ b/src/controllers/posts/types/index.ts @@ -0,0 +1,38 @@ +import { NextApiRequest } from 'next'; + +/** Represents the request object for getting all posts. */ +export interface GetAllPostsRequest extends NextApiRequest { + query: { page_size: string; page_num: string }; +} + +/** + * Represents the request object for getting all posts by a connected post ID. + * + * This may include: + * + * - All beers by a brewery ID + * - All beers by a beer style ID + * - All beer styles by a user ID + * - And more... + * + * @example + * const getAllBeersByBeerStyle = async ( + * req: GetAllPostsByConnectedPostId, + * res: NextApiResponse>, + * ) => { + * const { page_size, page_num, id } = req.query; + * // ... + * }; + * + * @example + * const getAllBeersByUserId = async ( + * req: GetAllPostsByConnectedPostId, + * res: NextApiResponse>, + * ) => { + * const { page_size, page_num, id } = req.query; + * // ... + * }; + */ +export interface GetAllPostsByConnectedPostId extends NextApiRequest { + query: { id: string; page_size: string; page_num: string }; +} diff --git a/src/controllers/users/auth/index.ts b/src/controllers/users/auth/index.ts new file mode 100644 index 0000000..e2109d1 --- /dev/null +++ b/src/controllers/users/auth/index.ts @@ -0,0 +1,302 @@ +import { removeTokenCookie } from '@/config/auth/cookie'; +import localStrat from '@/config/auth/localStrat'; +import { getLoginSession, setLoginSession } from '@/config/auth/session'; +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import ServerError from '@/config/util/ServerError'; + +import { NextApiRequest, NextApiResponse } from 'next'; +import { expressWrapper } from 'next-connect'; +import passport from 'passport'; +import { z } from 'zod'; + +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; + +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import type { NextFunction } from 'express'; +import { verifyConfirmationToken } from '@/config/jwt'; + +import { hashPassword } from '@/config/auth/passwordFns'; + +import { + createNewUserService, + deleteUserService, + findUserByEmailService, + findUserByUsernameService, + sendConfirmationEmailService, + sendResetPasswordEmailService, + updateUserService, + updateUserPasswordService, + confirmUserService, +} from '@/services/users/auth'; + +import { EditUserRequest, UserRouteRequest } from '@/controllers/users/profile/types'; +import { + CheckEmailRequest, + CheckUsernameRequest, + RegisterUserRequest, + ResetPasswordRequest, + TokenValidationRequest, + UpdatePasswordRequest, +} from './types'; + +export const authenticateUser = expressWrapper( + async ( + req: UserExtendedNextApiRequest, + res: NextApiResponse>, + next: NextFunction, + ) => { + passport.initialize(); + passport.use(localStrat); + passport.authenticate( + 'local', + { session: false }, + (error: unknown, token: z.infer) => { + if (error) { + next(error); + return; + } + req.user = token; + next(); + }, + )(req, res, next); + }, +); + +export const loginUser = async ( + req: UserExtendedNextApiRequest, + res: NextApiResponse>, +) => { + const user = req.user!; + await setLoginSession(res, user); + + res.status(200).json({ + message: 'Login successful.', + payload: user, + statusCode: 200, + success: true, + }); +}; + +export const logoutUser = async ( + req: NextApiRequest, + res: NextApiResponse>, +) => { + const session = await getLoginSession(req); + + if (!session) { + throw new ServerError('You are not logged in.', 400); + } + + removeTokenCookie(res); + + res.redirect('/'); +}; + +export const registerUser = async ( + req: RegisterUserRequest, + res: NextApiResponse>, +) => { + const [usernameTaken, emailTaken] = ( + await Promise.all([ + findUserByUsernameService({ username: req.body.username }), + findUserByEmailService({ email: req.body.email }), + ]) + ).map((user) => !!user); + + if (usernameTaken) { + throw new ServerError( + 'Could not register a user with that username as it is already taken.', + 409, + ); + } + + if (emailTaken) { + throw new ServerError( + 'Could not register a user with that email as it is already taken.', + 409, + ); + } + + const user = await createNewUserService(req.body); + + await setLoginSession(res, { + id: user.id, + username: user.username, + }); + + await sendConfirmationEmailService({ + email: user.email, + username: user.username, + userId: user.id, + }); + + res.status(201).json({ + success: true, + statusCode: 201, + message: 'User registered successfully.', + payload: user, + }); +}; + +export const confirmUser = async ( + req: TokenValidationRequest, + res: NextApiResponse>, +) => { + const { token } = req.query; + + const user = req.user!; + const { id } = await verifyConfirmationToken(token); + + if (user.accountIsVerified) { + throw new ServerError('Your account is already verified.', 400); + } + + if (user.id !== id) { + throw new ServerError('Could not confirm user.', 401); + } + + await confirmUserService({ userId: id }); + + res.status(200).json({ + message: 'User confirmed successfully.', + statusCode: 200, + success: true, + }); +}; + +export const resetPassword = async ( + req: ResetPasswordRequest, + res: NextApiResponse>, +) => { + const { email } = req.body; + + const user = await findUserByEmailService({ email }); + + if (user) { + await sendResetPasswordEmailService({ + email: user.email, + username: user.username, + userId: user.id, + }); + } + + res.status(200).json({ + statusCode: 200, + success: true, + message: + 'If an account with that email exists, we have sent you an email to reset your password.', + }); +}; + +export const sendCurrentUser = async ( + req: UserExtendedNextApiRequest, + res: NextApiResponse, +) => { + const { user } = req; + res.status(200).json({ + message: `Currently logged in as ${user!.username}`, + statusCode: 200, + success: true, + payload: user, + }); +}; + +export const checkEmail = async (req: CheckEmailRequest, res: NextApiResponse) => { + const { email: emailToCheck } = req.query; + + const email = await findUserByEmailService({ email: emailToCheck }); + + res.json({ + success: true, + payload: { emailIsTaken: !!email }, + statusCode: 200, + message: 'Getting email availability.', + }); +}; + +export const checkUsername = async (req: CheckUsernameRequest, res: NextApiResponse) => { + const { username: usernameToCheck } = req.query; + + const username = await findUserByUsernameService({ username: usernameToCheck }); + + res.json({ + success: true, + payload: { usernameIsTaken: !!username }, + statusCode: 200, + message: username ? 'Username is taken.' : 'Username is available.', + }); +}; + +export const updatePassword = async ( + req: UpdatePasswordRequest, + res: NextApiResponse>, +) => { + const user = req.user!; + const { password } = req.body; + + await updateUserPasswordService({ + userId: user.id, + password: await hashPassword(password), + }); + + res.json({ + message: 'Updated user password.', + statusCode: 200, + success: true, + }); +}; + +export const resendConfirmation = async ( + req: UserExtendedNextApiRequest, + res: NextApiResponse, +) => { + const user = req.user!; + + await sendConfirmationEmailService({ + userId: user.id, + username: user.username, + email: user.email, + }); + res.status(200).json({ + message: `Resent the confirmation email for ${user.username}.`, + statusCode: 200, + success: true, + }); +}; + +export const editUserInfo = async ( + req: EditUserRequest, + res: NextApiResponse>, +) => { + const { email, firstName, lastName, username } = req.body; + + const updatedUser = await updateUserService({ + userId: req.user!.id, + data: { email, firstName, lastName, username }, + }); + + res.json({ + message: 'User edited successfully', + payload: updatedUser, + success: true, + statusCode: 200, + }); +}; + +export const deleteAccount = async ( + req: UserRouteRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + const deletedUser = await deleteUserService({ userId: id }); + + if (!deletedUser) { + throw new ServerError('Could not find a user with that id.', 400); + } + + res.send({ + message: 'Successfully deleted user.', + statusCode: 200, + success: true, + }); +}; diff --git a/src/controllers/users/auth/types/index.ts b/src/controllers/users/auth/types/index.ts new file mode 100644 index 0000000..a8292e6 --- /dev/null +++ b/src/controllers/users/auth/types/index.ts @@ -0,0 +1,31 @@ +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import { + CreateUserValidationSchema, + UpdatePasswordSchema, +} from '@/services/users/auth/schema/CreateUserValidationSchemas'; +import TokenValidationSchema from '@/services/users/auth/schema/TokenValidationSchema'; +import { NextApiRequest } from 'next'; +import { z } from 'zod'; + +export interface RegisterUserRequest extends NextApiRequest { + body: z.infer; +} + +export interface TokenValidationRequest extends UserExtendedNextApiRequest { + query: z.infer; +} + +export interface ResetPasswordRequest extends NextApiRequest { + body: { email: string }; +} + +export interface UpdatePasswordRequest extends UserExtendedNextApiRequest { + body: z.infer; +} +export interface CheckEmailRequest extends NextApiRequest { + query: { email: string }; +} + +export interface CheckUsernameRequest extends NextApiRequest { + query: { username: string }; +} diff --git a/src/controllers/users/profile/index.ts b/src/controllers/users/profile/index.ts new file mode 100644 index 0000000..e84fabd --- /dev/null +++ b/src/controllers/users/profile/index.ts @@ -0,0 +1,216 @@ +import ServerError from '@/config/util/ServerError'; + +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse } from 'next'; +import { z } from 'zod'; +import { NextHandler } from 'next-connect'; + +import { UserExtendedNextApiRequest } from '@/config/auth/types'; + +import { findUserByIdService } from '@/services/users/auth'; + +import { + createUserFollow, + deleteUserFollow, + findUserFollow, + getUsersFollowedByUser, + getUsersFollowingUser, + updateUserAvatar, + updateUserProfileById, +} from '@/services/users/profile'; +import { + UserRouteRequest, + GetUserFollowInfoRequest, + EditUserRequest, + UpdateAvatarRequest, + UpdateProfileRequest, +} from './types'; + +export const followUser = async ( + req: UserRouteRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + + const user = await findUserByIdService({ userId: id }); + if (!user) { + throw new ServerError('User not found', 404); + } + + const currentUser = req.user!; + const userIsFollowedBySessionUser = await findUserFollow({ + followerId: currentUser.id, + followingId: id, + }); + + if (!userIsFollowedBySessionUser) { + await createUserFollow({ followerId: currentUser.id, followingId: id }); + res.status(200).json({ + message: 'Now following user.', + success: true, + statusCode: 200, + }); + + return; + } + + await deleteUserFollow({ followerId: currentUser.id, followingId: id }); + + res.status(200).json({ + message: 'No longer following user.', + success: true, + statusCode: 200, + }); +}; + +export const getUserFollowers = async ( + req: GetUserFollowInfoRequest, + res: NextApiResponse>, +) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { id, page_num, page_size } = req.query; + + const user = await findUserByIdService({ userId: id }); + if (!user) { + throw new ServerError('User not found', 404); + } + + const pageNum = parseInt(page_num, 10); + const pageSize = parseInt(page_size, 10); + + const { follows, count } = await getUsersFollowingUser({ + userId: id, + pageNum, + pageSize, + }); + + res.setHeader('X-Total-Count', count); + + res.json({ + message: 'Retrieved users that are followed by queried user', + payload: follows, + success: true, + statusCode: 200, + }); +}; + +export const getUsersFollowed = async ( + req: GetUserFollowInfoRequest, + res: NextApiResponse>, +) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { id, page_num, page_size } = req.query; + + const user = await findUserByIdService({ userId: id }); + if (!user) { + throw new ServerError('User not found', 404); + } + + const pageNum = parseInt(page_num, 10); + const pageSize = parseInt(page_size, 10); + + const { follows, count } = await getUsersFollowedByUser({ + userId: id, + pageNum, + pageSize, + }); + + res.setHeader('X-Total-Count', count); + + res.json({ + message: 'Retrieved users that are followed by queried user', + payload: follows, + success: true, + statusCode: 200, + }); +}; + +export const checkIfUserIsFollowedBySessionUser = async ( + req: GetUserFollowInfoRequest, + res: NextApiResponse>, +) => { + const { id } = req.query; + + const user = await findUserByIdService({ userId: id }); + if (!user) { + throw new ServerError('User not found', 404); + } + + const currentUser = req.user!; + + const userFollow = await findUserFollow({ + followerId: currentUser.id, + followingId: id, + }); + + const isFollowed = !!userFollow; + + res.status(200).json({ + message: isFollowed + ? 'User is followed by the session user.' + : 'User is not followed by the session user.', + success: true, + statusCode: 200, + payload: { isFollowed }, + }); +}; + +export const checkIfUserCanEditUser = async ( + req: EditUserRequest, + res: NextApiResponse, + next: NextHandler, +) => { + const authenticatedUser = req.user!; + + const userToUpdate = await findUserByIdService({ userId: req.query.id }); + if (!userToUpdate) { + throw new ServerError('User not found', 404); + } + + if (authenticatedUser.id !== userToUpdate.id) { + throw new ServerError('You are not permitted to modify this user', 403); + } + + return next(); +}; + +export const checkIfUserCanUpdateProfile = async ( + req: T, + res: NextApiResponse, + next: NextHandler, +) => { + const user = req.user!; + + if (user.id !== req.query.id) { + throw new ServerError('You can only update your own profile.', 403); + } + + await next(); +}; + +export const updateAvatar = async (req: UpdateAvatarRequest, res: NextApiResponse) => { + const { file, user } = req; + + await updateUserAvatar({ + userId: user!.id, + data: { alt: file.originalname, path: file.path, caption: '' }, + }); + res.status(200).json({ + message: 'User avatar updated successfully.', + statusCode: 200, + success: true, + }); +}; + +export const updateProfile = async (req: UpdateProfileRequest, res: NextApiResponse) => { + const user = req.user!; + const { body } = req; + + await updateUserProfileById({ userId: user!.id, data: { bio: body.bio } }); + + res.status(200).json({ + message: 'Profile updated successfully.', + statusCode: 200, + success: true, + }); +}; diff --git a/src/controllers/users/profile/types/index.ts b/src/controllers/users/profile/types/index.ts new file mode 100644 index 0000000..5c4f600 --- /dev/null +++ b/src/controllers/users/profile/types/index.ts @@ -0,0 +1,23 @@ +import { UserExtendedNextApiRequest } from '@/config/auth/types'; +import EditUserSchema from '@/services/users/auth/schema/EditUserSchema'; +import { z } from 'zod'; + +export interface UserRouteRequest extends UserExtendedNextApiRequest { + query: { id: string }; +} + +export interface GetUserFollowInfoRequest extends UserExtendedNextApiRequest { + query: { id: string; page_size: string; page_num: string }; +} + +export interface EditUserRequest extends UserRouteRequest { + body: z.infer; +} + +export interface UpdateAvatarRequest extends UserExtendedNextApiRequest { + file: Express.Multer.File; +} + +export interface UpdateProfileRequest extends UserExtendedNextApiRequest { + body: { bio: string }; +} diff --git a/src/emails/ForgotEmail.tsx b/src/emails/ForgotEmail.tsx index 5819674..8a2bad7 100644 --- a/src/emails/ForgotEmail.tsx +++ b/src/emails/ForgotEmail.tsx @@ -3,12 +3,12 @@ import { Tailwind } from '@react-email/tailwind'; import { FC } from 'react'; -interface ForgotEmailProps { +interface ResetPasswordEmailProps { name?: string; url?: string; } -const ForgotEmail: FC = ({ name, url }) => { +const ResetPasswordEmail: FC = ({ name, url }) => { return ( @@ -36,4 +36,4 @@ const ForgotEmail: FC = ({ name, url }) => { ); }; -export default ForgotEmail; +export default ResetPasswordEmail; diff --git a/src/emails/Welcome.tsx b/src/emails/WelcomeEmail.tsx similarity index 91% rename from src/emails/Welcome.tsx rename to src/emails/WelcomeEmail.tsx index 9380079..92e9513 100644 --- a/src/emails/Welcome.tsx +++ b/src/emails/WelcomeEmail.tsx @@ -3,13 +3,13 @@ import { Tailwind } from '@react-email/tailwind'; import { FC } from 'react'; -interface WelcomeEmail { +interface WelcomeEmailProps { subject?: string; name?: string; url?: string; } -const Welcome: FC = ({ name, url }) => ( +const WelcomeEmail: FC = ({ name, url }) => (
@@ -43,4 +43,4 @@ const Welcome: FC = ({ name, url }) => ( ); -export default Welcome; +export default WelcomeEmail; diff --git a/src/hooks/auth/useConfirmUser.ts b/src/hooks/auth/useConfirmUser.ts index 5da709c..32c148a 100644 --- a/src/hooks/auth/useConfirmUser.ts +++ b/src/hooks/auth/useConfirmUser.ts @@ -5,6 +5,14 @@ import { useState, useContext, useEffect } from 'react'; import toast from 'react-hot-toast'; import useSWR from 'swr'; +/** + * A custom hook to confirm a user's account. + * + * @returns An object with the following properties: + * + * - `needsToLogin`: A boolean indicating whether the user needs to log in. + * - `tokenInvalid`: A boolean indicating whether the token is invalid. + */ const useConfirmUser = () => { const router = useRouter(); const { user, mutate } = useContext(UserContext); diff --git a/src/hooks/auth/useRedirectIfLoggedIn.ts b/src/hooks/auth/useRedirectIfLoggedIn.ts index 50e8ac0..56de0e3 100644 --- a/src/hooks/auth/useRedirectIfLoggedIn.ts +++ b/src/hooks/auth/useRedirectIfLoggedIn.ts @@ -3,14 +3,10 @@ import { useRouter } from 'next/router'; import { useContext } from 'react'; /** - * Custom React hook that redirects the user to the home page if they are logged in. + * A custom hook to redirect the user to the home page if they are logged in. * * This hook is used to prevent logged in users from accessing the login and signup pages - * by redirecting them to the home page. - * - * This hook should only be used in a component that is under the UserContext provider. - * - * @returns {void} + * and should only be used in a component that is under the UserContext provider. */ const useRedirectWhenLoggedIn = (): void => { const { user } = useContext(UserContext); diff --git a/src/hooks/auth/useUser.ts b/src/hooks/auth/useUser.ts index 39a98d7..66e4ef4 100644 --- a/src/hooks/auth/useUser.ts +++ b/src/hooks/auth/useUser.ts @@ -1,9 +1,9 @@ -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWR from 'swr'; /** - * A custom React hook that fetches the current user's data from the server. + * A custom hook to fetch the current user's data. * * @returns An object with the following properties: * diff --git a/src/hooks/data-fetching/beer-comments/useBeerPostComments.ts b/src/hooks/data-fetching/beer-comments/useBeerPostComments.ts index 320f167..6ab2bdf 100644 --- a/src/hooks/data-fetching/beer-comments/useBeerPostComments.ts +++ b/src/hooks/data-fetching/beer-comments/useBeerPostComments.ts @@ -10,7 +10,7 @@ interface UseBeerPostCommentsProps { } /** - * A custom React hook that fetches comments for a specific beer post. + * A custom hook to fetch comments for a specific beer post. * * @param props - The props object. * @param props.pageNum - The page number of the comments to fetch. diff --git a/src/hooks/data-fetching/beer-likes/useBeerPostLikeCount.ts b/src/hooks/data-fetching/beer-likes/useBeerPostLikeCount.ts index 2300ef2..5b450bc 100644 --- a/src/hooks/data-fetching/beer-likes/useBeerPostLikeCount.ts +++ b/src/hooks/data-fetching/beer-likes/useBeerPostLikeCount.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; import useSWR from 'swr'; /** - * Custom hook to fetch the like count for a beer post from the server. + * A custom hook to fetch the like count for a beer post from the server. * * @param beerPostId - The ID of the beer post to fetch the like count for. * @returns An object with the following properties: diff --git a/src/hooks/data-fetching/beer-likes/useCheckIfUserLikesBeerPost.ts b/src/hooks/data-fetching/beer-likes/useCheckIfUserLikesBeerPost.ts index f95dd5b..9d71518 100644 --- a/src/hooks/data-fetching/beer-likes/useCheckIfUserLikesBeerPost.ts +++ b/src/hooks/data-fetching/beer-likes/useCheckIfUserLikesBeerPost.ts @@ -5,10 +5,9 @@ import useSWR from 'swr'; import { z } from 'zod'; /** - * A custom React hook that checks if the current user has liked a beer post by fetching - * data from the server. + * A custom hook to check if the current user has liked a beer post. * - * @param beerPostId The ID of the beer post to check for likes. + * @param beerPostId The ID of the beer post. * @returns An object with the following properties: * * - `error`: The error that occurred while fetching the data. diff --git a/src/hooks/data-fetching/beer-posts/useBeerPostSearch.ts b/src/hooks/data-fetching/beer-posts/useBeerPostSearch.ts index 85caf98..89be302 100644 --- a/src/hooks/data-fetching/beer-posts/useBeerPostSearch.ts +++ b/src/hooks/data-fetching/beer-posts/useBeerPostSearch.ts @@ -1,8 +1,9 @@ -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import useSWR from 'swr'; import { z } from 'zod'; + /** - * A custom React hook that searches for beer posts that match a given query string. + * A custom hook to search for beer posts that match a given query string. * * @param query The search query string to match beer posts against. * @returns An object with the following properties: diff --git a/src/hooks/data-fetching/beer-posts/useBeerPosts.ts b/src/hooks/data-fetching/beer-posts/useBeerPosts.ts index 10c45f6..ab62440 100644 --- a/src/hooks/data-fetching/beer-posts/useBeerPosts.ts +++ b/src/hooks/data-fetching/beer-posts/useBeerPosts.ts @@ -1,10 +1,10 @@ -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; /** - * A custom hook using SWR to fetch beer posts from the API. + * A custom hook to fetch beer posts from the API. * * @param options The options to use when fetching beer posts. * @param options.pageSize The number of beer posts to fetch per page. diff --git a/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyle.ts b/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyle.ts deleted file mode 100644 index 05879fc..0000000 --- a/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyle.ts +++ /dev/null @@ -1,66 +0,0 @@ -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import useSWRInfinite from 'swr/infinite'; -import { z } from 'zod'; - -interface UseBeerPostsByBeerStyleParams { - pageSize: number; - beerStyleId: string; -} - -const useBeerPostsByBeerStyle = ({ - pageSize, - beerStyleId, -}: UseBeerPostsByBeerStyleParams) => { - const fetcher = async (url: string) => { - const response = await fetch(url); - if (!response.ok) { - throw new Error(response.statusText); - } - - const json = await response.json(); - const count = response.headers.get('X-Total-Count'); - - const parsed = APIResponseValidationSchema.safeParse(json); - if (!parsed.success) { - throw new Error('API response validation failed'); - } - - const parsedPayload = z.array(BeerPostQueryResult).safeParse(parsed.data.payload); - if (!parsedPayload.success) { - throw new Error('API response validation failed'); - } - - const pageCount = Math.ceil(parseInt(count as string, 10) / pageSize); - return { - beerPosts: parsedPayload.data, - pageCount, - }; - }; - - const { data, error, isLoading, setSize, size } = useSWRInfinite( - (index) => - `/api/beers/styles/${beerStyleId}/beers?page_num=${ - index + 1 - }&page_size=${pageSize}`, - fetcher, - ); - - const beerPosts = data?.flatMap((d) => d.beerPosts) ?? []; - const pageCount = data?.[0].pageCount ?? 0; - const isLoadingMore = size > 0 && data && typeof data[size - 1] === 'undefined'; - const isAtEnd = !(size < data?.[0].pageCount!); - - return { - beerPosts, - pageCount, - size, - setSize, - isLoading, - isLoadingMore, - isAtEnd, - error: error as unknown, - }; -}; - -export default useBeerPostsByBeerStyle; diff --git a/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyles.ts b/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyles.ts index 05879fc..7e1a3b0 100644 --- a/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyles.ts +++ b/src/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyles.ts @@ -1,4 +1,4 @@ -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; @@ -8,6 +8,23 @@ interface UseBeerPostsByBeerStyleParams { beerStyleId: string; } +/** + * A custom hook to fetch beer posts by beer style. + * + * @param options The options for fetching beer posts. + * @param options.pageSize The number of beer posts to fetch per page. + * @param options.beerStyleId The ID of the beer style to fetch beer posts for. + * @returns An object with the following properties: + * + * - `beerPosts`: The beer posts fetched from the API. + * - `error`: The error that occurred while fetching the data. + * - `isAtEnd`: A boolean indicating whether all data has been fetched. + * - `isLoading`: A boolean indicating whether the data is being fetched. + * - `isLoadingMore`: A boolean indicating whether more data is being fetched. + * - `pageCount`: The total number of pages of data. + * - `setSize`: A function to set the size of the data. + * - `size`: The size of the data.` + */ const useBeerPostsByBeerStyle = ({ pageSize, beerStyleId, diff --git a/src/hooks/data-fetching/beer-posts/useBeerPostsByBrewery.ts b/src/hooks/data-fetching/beer-posts/useBeerPostsByBrewery.ts index 628f022..c58616a 100644 --- a/src/hooks/data-fetching/beer-posts/useBeerPostsByBrewery.ts +++ b/src/hooks/data-fetching/beer-posts/useBeerPostsByBrewery.ts @@ -1,4 +1,4 @@ -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; @@ -9,7 +9,7 @@ interface UseBeerPostsByBreweryParams { } /** - * A custom hook using SWR to fetch beer posts from the API. + * A custom hook to fetch beer posts by brewery. * * @param options The options to use when fetching beer posts. * @param options.pageSize The number of beer posts to fetch per page. diff --git a/src/hooks/data-fetching/beer-posts/useBeerPostsByUser.ts b/src/hooks/data-fetching/beer-posts/useBeerPostsByUser.ts index 90a099c..b5d25f3 100644 --- a/src/hooks/data-fetching/beer-posts/useBeerPostsByUser.ts +++ b/src/hooks/data-fetching/beer-posts/useBeerPostsByUser.ts @@ -1,4 +1,4 @@ -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; @@ -8,6 +8,23 @@ interface UseBeerPostsByUserParams { userId: string; } +/** + * A custom hook to fetch beer posts by user. + * + * @param options The options for fetching beer posts. + * @param options.pageSize The number of beer posts to fetch per page. + * @param options.userId The ID of the user to fetch beer posts for. + * @returns An object with the following properties: + * + * - `beerPosts`: The beer posts fetched from the API. + * - `error`: The error that occurred while fetching the data. + * - `isAtEnd`: A boolean indicating whether all data has been fetched. + * - `isLoading`: A boolean indicating whether the data is being fetched. + * - `isLoadingMore`: A boolean indicating whether more data is being fetched. + * - `pageCount`: The total number of pages of data. + * - `setSize`: A function to set the size of the data. + * - `size`: The size of the data.` + */ const useBeerPostsByUser = ({ pageSize, userId }: UseBeerPostsByUserParams) => { const fetcher = async (url: string) => { const response = await fetch(url); diff --git a/src/hooks/data-fetching/beer-posts/useBeerRecommendations.ts b/src/hooks/data-fetching/beer-posts/useBeerRecommendations.ts index 181c1bc..2157058 100644 --- a/src/hooks/data-fetching/beer-posts/useBeerRecommendations.ts +++ b/src/hooks/data-fetching/beer-posts/useBeerRecommendations.ts @@ -1,4 +1,4 @@ -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; @@ -9,7 +9,7 @@ interface UseBeerRecommendationsParams { } /** - * A custom hook using SWR to fetch beer recommendations from the API. + * A custom hook to fetch beer recommendations from the API. * * @param options The options to use when fetching beer recommendations. * @param options.pageSize The number of beer recommendations to fetch per page. diff --git a/src/hooks/data-fetching/beer-style-comments/useBeerStyleComments.ts b/src/hooks/data-fetching/beer-style-comments/useBeerStyleComments.ts index 8e4f4f0..054a031 100644 --- a/src/hooks/data-fetching/beer-style-comments/useBeerStyleComments.ts +++ b/src/hooks/data-fetching/beer-style-comments/useBeerStyleComments.ts @@ -3,13 +3,31 @@ import APIResponseValidationSchema from '@/validation/APIResponseValidationSchem import { z } from 'zod'; import useSWRInfinite from 'swr/infinite'; -interface UseBeerStyleCommentsProps { +interface UseBeerStyleCommentsOptions { id: string; pageSize: number; pageNum: number; } - -const useBeerStyleComments = ({ id, pageSize }: UseBeerStyleCommentsProps) => { +/** + * A custom hook to fetch comments for a beer style post. + * + * @param options The options for fetching comments. + * @param options.id The ID of the beer style to fetch comments for. + * @param options.pageSize The number of comments to fetch per page. + * @param options.pageNum The page number to fetch. + * @returns An object with the following properties: + * + * - `comments`: The comments fetched from the API. + * - `error`: The error that occurred while fetching the data. + * - `isLoading`: A boolean indicating whether the data is being fetched. + * - `isLoadingMore`: A boolean indicating whether more data is being fetched. + * - `isAtEnd`: A boolean indicating whether all data has been fetched. + * - `mutate`: A function to mutate the data. + * - `pageCount`: The total number of pages of data. + * - `setSize`: A function to set the size of the data. + * - `size`: The size of the data. + */ +const useBeerStyleComments = ({ id, pageSize }: UseBeerStyleCommentsOptions) => { const fetcher = async (url: string) => { const response = await fetch(url); diff --git a/src/hooks/data-fetching/beer-style-likes/useBeerStyleLikeCount.ts b/src/hooks/data-fetching/beer-style-likes/useBeerStyleLikeCount.ts index 908fa8f..51c764e 100644 --- a/src/hooks/data-fetching/beer-style-likes/useBeerStyleLikeCount.ts +++ b/src/hooks/data-fetching/beer-style-likes/useBeerStyleLikeCount.ts @@ -3,7 +3,7 @@ import { z } from 'zod'; import useSWR from 'swr'; /** - * Custom hook to fetch the like count for a beer style from the server. + * A custom hook to fetch the like count for a beer style post. * * @param beerStyleId - The ID of the beer style to fetch the like count for. * @returns An object with the following properties: diff --git a/src/hooks/data-fetching/beer-style-likes/useCheckIfUserLikesBeerPost.ts b/src/hooks/data-fetching/beer-style-likes/useCheckIfUserLikesBeerPost.ts index 3f2bd1e..d8f03fc 100644 --- a/src/hooks/data-fetching/beer-style-likes/useCheckIfUserLikesBeerPost.ts +++ b/src/hooks/data-fetching/beer-style-likes/useCheckIfUserLikesBeerPost.ts @@ -5,8 +5,7 @@ import useSWR from 'swr'; import { z } from 'zod'; /** - * A custom React hook that checks if the current user has liked a beer style by fetching - * data from the server. + * A custom hook to check if the current user has liked a beer style. * * @param beerStyleId The ID of the beer style to check for likes. * @returns An object with the following properties: diff --git a/src/hooks/data-fetching/beer-styles/useBeerStyles.ts b/src/hooks/data-fetching/beer-styles/useBeerStyles.ts index 7c6bf03..9d17cc8 100644 --- a/src/hooks/data-fetching/beer-styles/useBeerStyles.ts +++ b/src/hooks/data-fetching/beer-styles/useBeerStyles.ts @@ -1,10 +1,10 @@ -import BeerStyleQueryResult from '@/services/BeerStyles/schema/BeerStyleQueryResult'; +import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; /** - * A custom hook using SWR to fetch beer types from the API. + * A custom hook to fetch beer styles posts. * * @param options The options to use when fetching beer types. * @param options.pageSize The number of beer types to fetch per page. diff --git a/src/hooks/data-fetching/brewery-comments/useBreweryPostComments.ts b/src/hooks/data-fetching/brewery-comments/useBreweryPostComments.ts index 59e7803..4ac90b1 100644 --- a/src/hooks/data-fetching/brewery-comments/useBreweryPostComments.ts +++ b/src/hooks/data-fetching/brewery-comments/useBreweryPostComments.ts @@ -9,7 +9,7 @@ interface UseBreweryPostCommentsProps { } /** - * A custom React hook that fetches comments for a specific brewery post. + * A custom hook to fetch comments for a specific brewery post. * * @param props - The props object. * @param props.pageNum - The page number of the comments to fetch. diff --git a/src/hooks/data-fetching/brewery-likes/useCheckIfUserLikesBreweryPost.ts b/src/hooks/data-fetching/brewery-likes/useCheckIfUserLikesBreweryPost.ts index 0cb5b22..29618f8 100644 --- a/src/hooks/data-fetching/brewery-likes/useCheckIfUserLikesBreweryPost.ts +++ b/src/hooks/data-fetching/brewery-likes/useCheckIfUserLikesBreweryPost.ts @@ -5,7 +5,7 @@ import useSWR from 'swr'; import { z } from 'zod'; /** - * A custom React hook that checks if the current user likes a given brewery post. + * A custom hook to check if the current user likes a given brewery post. * * @param breweryPostId - The ID of the brewery post to check. * @returns An object with the following properties: diff --git a/src/hooks/data-fetching/brewery-posts/useBreweryMapPagePosts.ts b/src/hooks/data-fetching/brewery-posts/useBreweryMapPagePosts.ts index 544fd10..cc4d259 100644 --- a/src/hooks/data-fetching/brewery-posts/useBreweryMapPagePosts.ts +++ b/src/hooks/data-fetching/brewery-posts/useBreweryMapPagePosts.ts @@ -1,8 +1,24 @@ -import BreweryPostMapQueryResult from '@/services/BreweryPost/schema/BreweryPostMapQueryResult'; +import BreweryPostMapQueryResult from '@/services/posts/brewery-post/schema/BreweryPostMapQueryResult'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; +/** + * A custom hook to fetch brewery posts for the map. + * + * @param options The options to use when fetching brewery posts. + * @param options.pageSize The number of brewery posts to fetch per page. + * @returns An object with the following properties: + * + * - `breweryPosts`: The brewery posts fetched from the API. + * - `error`: The error that occurred while fetching the data. + * - `isAtEnd`: A boolean indicating whether all data has been fetched. + * - `isLoading`: A boolean indicating whether the data is being fetched. + * - `isLoadingMore`: A boolean indicating whether more data is being fetched. + * - `pageCount`: The total number of pages of data. + * - `setSize`: A function to set the size of the data. + * - `size`: The size of the data. + */ const useBreweryMapPagePosts = ({ pageSize }: { pageSize: number }) => { const fetcher = async (url: string) => { const response = await fetch(url); diff --git a/src/hooks/data-fetching/brewery-posts/useBreweryPosts.ts b/src/hooks/data-fetching/brewery-posts/useBreweryPosts.ts index 7ce0517..a319afd 100644 --- a/src/hooks/data-fetching/brewery-posts/useBreweryPosts.ts +++ b/src/hooks/data-fetching/brewery-posts/useBreweryPosts.ts @@ -1,4 +1,4 @@ -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; diff --git a/src/hooks/data-fetching/brewery-posts/useBreweryPostsByUser.ts b/src/hooks/data-fetching/brewery-posts/useBreweryPostsByUser.ts index 656cebd..f986d34 100644 --- a/src/hooks/data-fetching/brewery-posts/useBreweryPostsByUser.ts +++ b/src/hooks/data-fetching/brewery-posts/useBreweryPostsByUser.ts @@ -1,4 +1,4 @@ -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; @@ -8,6 +8,23 @@ interface UseBreweryPostsByUserParams { userId: string; } +/** + * A custom hook to fetch brewery posts by a specific user. + * + * @param options The options to use when fetching brewery posts. + * @param options.pageSize The number of brewery posts to fetch per page. + * @param options.userId The ID of the user to fetch brewery posts for. + * @returns An object with the following properties: + * + * - `breweryPosts`: The brewery posts fetched from the API. + * - `error`: The error that occurred while fetching the data. + * - `isAtEnd`: A boolean indicating whether all data has been fetched. + * - `isLoading`: A boolean indicating whether the data is being fetched. + * - `isLoadingMore`: A boolean indicating whether more data is being fetched. + * - `pageCount`: The total number of pages of data. + * - `setSize`: A function to set the size of the data. + * - `size`: The size of the data. + */ const useBreweryPostsByUser = ({ pageSize, userId }: UseBreweryPostsByUserParams) => { const fetcher = async (url: string) => { const response = await fetch(url); diff --git a/src/hooks/data-fetching/user-follows/useFollowStatus.ts b/src/hooks/data-fetching/user-follows/useFollowStatus.ts index c750ecf..cee43d7 100644 --- a/src/hooks/data-fetching/user-follows/useFollowStatus.ts +++ b/src/hooks/data-fetching/user-follows/useFollowStatus.ts @@ -2,6 +2,17 @@ import APIResponseValidationSchema from '@/validation/APIResponseValidationSchem import useSWR from 'swr'; import { z } from 'zod'; +/** + * A custom hook to check if the current user follows a given user. + * + * @param userFollowedId - The ID of the user to check. + * @returns An object with the following properties: + * + * - `isFollowed`: A boolean indicating whether the current user follows the user. + * - `error`: The error that occurred while fetching the data. + * - `isLoading`: A boolean indicating whether the data is being fetched. + * - `mutate`: A function to mutate the data. + */ const useFollowStatus = (userFollowedId: string) => { const { data, error, isLoading, mutate } = useSWR( `/api/users/${userFollowedId}/is-followed`, diff --git a/src/hooks/data-fetching/user-follows/useGetUsersFollowedByUser.ts b/src/hooks/data-fetching/user-follows/useGetUsersFollowedByUser.ts index dce2bfa..be56aae 100644 --- a/src/hooks/data-fetching/user-follows/useGetUsersFollowedByUser.ts +++ b/src/hooks/data-fetching/user-follows/useGetUsersFollowedByUser.ts @@ -1,5 +1,5 @@ /** - * Custom hook using SWR for fetching users followed by a specific user. + * A custom hook to fetch the users followed by a given user. * * @param options - The options for fetching users. * @param [options.pageSize=5] - The number of users to fetch per page. Default is `5` @@ -17,7 +17,7 @@ * - `mutate` A function to mutate the data. * - `error` The error object, if any. */ -import FollowInfoSchema from '@/services/UserFollows/schema/FollowInfoSchema'; +import FollowInfoSchema from '@/services/users/profile/schema/FollowInfoSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; diff --git a/src/hooks/data-fetching/user-follows/useGetUsersFollowingUser.ts b/src/hooks/data-fetching/user-follows/useGetUsersFollowingUser.ts index fb3eb3d..7555995 100644 --- a/src/hooks/data-fetching/user-follows/useGetUsersFollowingUser.ts +++ b/src/hooks/data-fetching/user-follows/useGetUsersFollowingUser.ts @@ -1,4 +1,4 @@ -import FollowInfoSchema from '@/services/UserFollows/schema/FollowInfoSchema'; +import FollowInfoSchema from '@/services/users/profile/schema/FollowInfoSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import useSWRInfinite from 'swr/infinite'; import { z } from 'zod'; @@ -9,10 +9,7 @@ interface UseGetUsersFollowingUser { } /** - * Custom hook using SWR for fetching users followed by a specific user. - * - * @example - * const { followers, followerCount } = useGetUsersFollowingUser({ userId: '123' }); + * A custom hook to fetch users following a user. * * @param options - The options for fetching users. * @param [options.pageSize=5] - The number of users to fetch per page. Default is `5` diff --git a/src/pages/api/beer-comments/[id].ts b/src/pages/api/beer-comments/[id].ts index 9c87b6d..8acf718 100644 --- a/src/pages/api/beer-comments/[id].ts +++ b/src/pages/api/beer-comments/[id].ts @@ -1,85 +1,22 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; -import findBeerCommentById from '@/services/BeerComment/findBeerCommentById'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; -import editBeerCommentById from '@/services/BeerComment/editBeerCommentById'; + import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; -import { createRouter, NextHandler } from 'next-connect'; +import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface DeleteCommentRequest extends UserExtendedNextApiRequest { - query: { id: string }; -} - -interface EditCommentRequest extends UserExtendedNextApiRequest { - query: { id: string }; - body: z.infer; -} - -const checkIfCommentOwner = async ( - req: DeleteCommentRequest | EditCommentRequest, - res: NextApiResponse>, - next: NextHandler, -) => { - const { id } = req.query; - const user = req.user!; - const comment = await findBeerCommentById({ beerCommentId: id }); - - if (!comment) { - throw new ServerError('Comment not found', 404); - } - - if (comment.postedBy.id !== user.id) { - throw new ServerError('You are not authorized to modify this comment', 403); - } - - return next(); -}; - -const editComment = async ( - req: EditCommentRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - - const updated = await editBeerCommentById({ - content: req.body.content, - rating: req.body.rating, - id, - }); - - res.status(200).json({ - success: true, - message: 'Comment updated successfully', - statusCode: 200, - payload: updated, - }); -}; - -const deleteComment = async ( - req: DeleteCommentRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - - await DBClient.instance.beerComment.delete({ - where: { id }, - }); - - res.status(200).json({ - success: true, - message: 'Comment deleted successfully', - statusCode: 200, - }); -}; +import { CommentRequest } from '@/controllers/comments/types'; +import { + checkIfBeerCommentOwner, + deleteBeerPostComment, + editBeerPostComment, +} from '@/controllers/comments/beer-comments'; const router = createRouter< - DeleteCommentRequest, + CommentRequest, NextApiResponse> >(); @@ -87,8 +24,8 @@ router .delete( validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), getCurrentUser, - checkIfCommentOwner, - deleteComment, + checkIfBeerCommentOwner, + deleteBeerPostComment, ) .put( validateRequest({ @@ -96,8 +33,8 @@ router bodySchema: CreateCommentValidationSchema, }), getCurrentUser, - checkIfCommentOwner, - editComment, + checkIfBeerCommentOwner, + editBeerPostComment, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/beer-style-comments/[id].ts b/src/pages/api/beer-style-comments/[id].ts index 6e0af5b..ec6dc8f 100644 --- a/src/pages/api/beer-style-comments/[id].ts +++ b/src/pages/api/beer-style-comments/[id].ts @@ -1,85 +1,21 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; +import { + checkIfBeerStyleCommentOwner, + deleteBeerStyleComment, + editBeerStyleComment, +} from '@/controllers/comments/beer-style-comments'; +import { CommentRequest } from '@/controllers/comments/types'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; -import { createRouter, NextHandler } from 'next-connect'; +import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface DeleteCommentRequest extends UserExtendedNextApiRequest { - query: { id: string }; -} - -interface EditCommentRequest extends UserExtendedNextApiRequest { - query: { id: string }; - body: z.infer; -} - -const checkIfCommentOwner = async ( - req: DeleteCommentRequest | EditCommentRequest, - res: NextApiResponse>, - next: NextHandler, -) => { - const { id } = req.query; - const user = req.user!; - const comment = await DBClient.instance.beerStyleComment.findFirst({ where: { id } }); - - if (!comment) { - throw new ServerError('Comment not found', 404); - } - - if (comment.postedById !== user.id) { - throw new ServerError('You are not authorized to modify this comment', 403); - } - - return next(); -}; - -const editComment = async ( - req: EditCommentRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - - const updated = await DBClient.instance.beerStyleComment.update({ - where: { id }, - data: { - content: req.body.content, - rating: req.body.rating, - updatedAt: new Date(), - }, - }); - - return res.status(200).json({ - success: true, - message: 'Comment updated successfully', - statusCode: 200, - payload: updated, - }); -}; - -const deleteComment = async ( - req: DeleteCommentRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - - await DBClient.instance.beerStyleComment.delete({ where: { id } }); - - res.status(200).json({ - success: true, - message: 'Comment deleted successfully', - statusCode: 200, - }); -}; - const router = createRouter< - DeleteCommentRequest, + CommentRequest, NextApiResponse> >(); @@ -89,8 +25,8 @@ router querySchema: z.object({ id: z.string().cuid() }), }), getCurrentUser, - checkIfCommentOwner, - deleteComment, + checkIfBeerStyleCommentOwner, + deleteBeerStyleComment, ) .put( validateRequest({ @@ -98,8 +34,8 @@ router bodySchema: CreateCommentValidationSchema, }), getCurrentUser, - checkIfCommentOwner, - editComment, + checkIfBeerStyleCommentOwner, + editBeerStyleComment, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/beers/[id]/comments/index.ts b/src/pages/api/beers/[id]/comments/index.ts index 52cb298..f9bc088 100644 --- a/src/pages/api/beers/[id]/comments/index.ts +++ b/src/pages/api/beers/[id]/comments/index.ts @@ -1,78 +1,19 @@ -import DBClient from '@/prisma/DBClient'; -import getAllBeerComments from '@/services/BeerComment/getAllBeerComments'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import createNewBeerComment from '@/services/BeerComment/createNewBeerComment'; import { createRouter } from 'next-connect'; import { z } from 'zod'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import { NextApiResponse } from 'next'; -import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; - -interface CreateCommentRequest extends UserExtendedNextApiRequest { - body: z.infer; - query: { id: string }; -} - -interface GetAllCommentsRequest extends UserExtendedNextApiRequest { - query: { id: string; page_size: string; page_num: string }; -} - -const createComment = async ( - req: CreateCommentRequest, - res: NextApiResponse>, -) => { - const { content, rating } = req.body; - - const beerPostId = req.query.id; - - const newBeerComment: z.infer = await createNewBeerComment({ - content, - rating, - beerPostId, - userId: req.user!.id, - }); - - res.status(201).json({ - message: 'Beer comment created successfully', - statusCode: 201, - payload: newBeerComment, - success: true, - }); -}; - -const getAll = async ( - req: GetAllCommentsRequest, - res: NextApiResponse>, -) => { - const beerPostId = req.query.id; - // eslint-disable-next-line @typescript-eslint/naming-convention - const { page_size, page_num } = req.query; - - const comments = await getAllBeerComments({ - beerPostId, - pageNum: parseInt(page_num, 10), - pageSize: parseInt(page_size, 10), - }); - - const count = await DBClient.instance.beerComment.count({ where: { beerPostId } }); - - res.setHeader('X-Total-Count', count); - - res.status(200).json({ - message: 'Beer comments fetched successfully', - statusCode: 200, - payload: comments, - success: true, - }); -}; +import { + createBeerPostComment, + getAllBeerPostComments, +} from '@/controllers/comments/beer-comments'; const router = createRouter< - // I don't want to use any, but I can't figure out how to get the types to work + // @TODO: Fix this any type any, NextApiResponse> >(); @@ -83,7 +24,7 @@ router.post( querySchema: z.object({ id: z.string().cuid() }), }), getCurrentUser, - createComment, + createBeerPostComment, ); router.get( @@ -94,7 +35,7 @@ router.get( page_num: z.coerce.number().int().positive(), }), }), - getAll, + getAllBeerPostComments, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/beers/[id]/images/index.ts b/src/pages/api/beers/[id]/images/index.ts index 69a245f..a7170ee 100644 --- a/src/pages/api/beers/[id]/images/index.ts +++ b/src/pages/api/beers/[id]/images/index.ts @@ -1,53 +1,19 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import { createRouter } from 'next-connect'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import { NextApiResponse } from 'next'; import { z } from 'zod'; -import ServerError from '@/config/util/ServerError'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import addBeerImageToDB from '@/services/BeerImage/addBeerImageToDB'; import ImageMetadataValidationSchema from '@/services/schema/ImageSchema/ImageMetadataValidationSchema'; import { uploadMiddlewareMultiple } from '@/config/multer/uploadMiddleware'; - -interface UploadBeerPostImagesRequest extends UserExtendedNextApiRequest { - files?: Express.Multer.File[]; - query: { id: string }; - body: z.infer; -} - -const processImageData = async ( - req: UploadBeerPostImagesRequest, - res: NextApiResponse>, -) => { - const { files, user, body } = req; - - if (!files || !files.length) { - throw new ServerError('No images uploaded', 400); - } - - const beerImages = await addBeerImageToDB({ - alt: body.alt, - caption: body.caption, - beerPostId: req.query.id, - userId: user!.id, - files, - }); - - res.status(200).json({ - success: true, - message: `Successfully uploaded ${beerImages.length} image${ - beerImages.length > 1 ? 's' : '' - }`, - statusCode: 200, - }); -}; +import { UploadImagesRequest } from '@/controllers/images/types'; +import { processBeerImageData } from '@/controllers/images/beer-images'; const router = createRouter< - UploadBeerPostImagesRequest, + UploadImagesRequest, NextApiResponse> >(); @@ -56,7 +22,7 @@ router.post( // @ts-expect-error uploadMiddlewareMultiple, validateRequest({ bodySchema: ImageMetadataValidationSchema }), - processImageData, + processBeerImageData, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/beers/[id]/index.ts b/src/pages/api/beers/[id]/index.ts index aeb526d..8a67ec7 100644 --- a/src/pages/api/beers/[id]/index.ts +++ b/src/pages/api/beers/[id]/index.ts @@ -1,73 +1,21 @@ -import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; -import getBeerPostById from '@/services/BeerPost/getBeerPostById'; -import { UserExtendedNextApiRequest } from '@/config/auth/types'; -import editBeerPostById from '@/services/BeerPost/editBeerPostById'; -import EditBeerPostValidationSchema from '@/services/BeerPost/schema/EditBeerPostValidationSchema'; -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; -import { createRouter, NextHandler } from 'next-connect'; +import { createRouter } from 'next-connect'; import { z } from 'zod'; -import ServerError from '@/config/util/ServerError'; + +import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import deleteBeerPostById from '@/services/BeerPost/deleteBeerPostById'; -interface BeerPostRequest extends UserExtendedNextApiRequest { - query: { id: string }; -} +import { EditBeerPostRequest } from '@/controllers/posts/beer-posts/types'; +import { + checkIfBeerPostOwner, + editBeerPost, + deleteBeerPost, +} from '@/controllers/posts/beer-posts'; -interface EditBeerPostRequest extends BeerPostRequest { - body: z.infer; -} +import EditBeerPostValidationSchema from '@/services/posts/beer-post/schema/EditBeerPostValidationSchema'; -const checkIfBeerPostOwner = async ( - req: BeerPostRequest, - res: NextApiResponse, - next: NextHandler, -) => { - const { user, query } = req; - const { id } = query; - - const beerPost = await getBeerPostById(id); - - if (!beerPost) { - throw new ServerError('Beer post not found', 404); - } - - if (beerPost.postedBy.id !== user!.id) { - throw new ServerError('You cannot edit that beer post.', 403); - } - - return next(); -}; - -const editBeerPost = async ( - req: EditBeerPostRequest, - res: NextApiResponse>, -) => { - await editBeerPostById({ id: req.query.id, data: req.body }); - - res.status(200).json({ - message: 'Beer post updated successfully', - success: true, - statusCode: 200, - }); -}; - -const deleteBeerPost = async (req: BeerPostRequest, res: NextApiResponse) => { - const { id } = req.query; - - const deleted = await deleteBeerPostById({ beerPostId: id }); - if (!deleted) { - throw new ServerError('Beer post not found', 404); - } - - res.status(200).json({ - message: 'Beer post deleted successfully', - success: true, - statusCode: 200, - }); -}; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; const router = createRouter< EditBeerPostRequest, diff --git a/src/pages/api/beers/[id]/like/index.ts b/src/pages/api/beers/[id]/like/index.ts index b85dc25..a6e7eb8 100644 --- a/src/pages/api/beers/[id]/like/index.ts +++ b/src/pages/api/beers/[id]/like/index.ts @@ -1,82 +1,31 @@ import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import getBeerPostById from '@/services/BeerPost/getBeerPostById'; -import { UserExtendedNextApiRequest } from '@/config/auth/types'; + import { createRouter } from 'next-connect'; import { z } from 'zod'; -import { NextApiRequest, NextApiResponse } from 'next'; -import ServerError from '@/config/util/ServerError'; -import createBeerPostLike from '@/services/BeerPostLike/createBeerPostLike'; -import removeBeerPostLikeById from '@/services/BeerPostLike/removeBeerPostLikeById'; -import findBeerPostLikeById from '@/services/BeerPostLike/findBeerPostLikeById'; +import { NextApiResponse } from 'next'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import getBeerPostLikeCount from '@/services/BeerPostLike/getBeerPostLikeCount'; - -const sendLikeRequest = async ( - req: UserExtendedNextApiRequest, - res: NextApiResponse>, -) => { - const user = req.user!; - const id = req.query.id as string; - - const beer = await getBeerPostById(id); - if (!beer) { - throw new ServerError('Could not find a beer post with that id', 404); - } - - const alreadyLiked = await findBeerPostLikeById({ - beerPostId: beer.id, - likedById: user.id, - }); - - const jsonResponse = { - success: true as const, - message: '', - statusCode: 200 as const, - }; - - if (alreadyLiked) { - await removeBeerPostLikeById({ beerLikeId: alreadyLiked.id }); - jsonResponse.message = 'Successfully unliked beer post'; - } else { - await createBeerPostLike({ id, user }); - jsonResponse.message = 'Successfully liked beer post'; - } - - res.status(200).json(jsonResponse); -}; - -const getLikeCount = async ( - req: NextApiRequest, - res: NextApiResponse>, -) => { - const id = req.query.id as string; - - const likeCount = await getBeerPostLikeCount({ beerPostId: id }); - - res.status(200).json({ - success: true, - message: 'Successfully retrieved like count.', - statusCode: 200, - payload: { likeCount }, - }); -}; +import { + sendBeerPostLikeRequest, + getBeerPostLikeCount, +} from '@/controllers/likes/beer-posts-likes'; +import { LikeRequest } from '@/controllers/likes/types'; const router = createRouter< - UserExtendedNextApiRequest, + LikeRequest, NextApiResponse> >(); router.post( getCurrentUser, validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), - sendLikeRequest, + sendBeerPostLikeRequest, ); router.get( validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), - getLikeCount, + getBeerPostLikeCount, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/beers/[id]/like/is-liked.ts b/src/pages/api/beers/[id]/like/is-liked.ts index 811cd9e..aa0f9a3 100644 --- a/src/pages/api/beers/[id]/like/is-liked.ts +++ b/src/pages/api/beers/[id]/like/is-liked.ts @@ -6,24 +6,8 @@ import APIResponseValidationSchema from '@/validation/APIResponseValidationSchem import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -import findBeerPostLikeById from '@/services/BeerPostLike/findBeerPostLikeById'; -const checkIfLiked = async ( - req: UserExtendedNextApiRequest, - res: NextApiResponse>, -) => { - const user = req.user!; - const beerPostId = req.query.id as string; - - const alreadyLiked = await findBeerPostLikeById({ beerPostId, likedById: user.id }); - - res.status(200).json({ - success: true, - message: alreadyLiked ? 'Beer post is liked.' : 'Beer post is not liked.', - statusCode: 200, - payload: { isLiked: !!alreadyLiked }, - }); -}; +import { checkIfBeerPostIsLiked } from '@/controllers/likes/beer-posts-likes'; const router = createRouter< UserExtendedNextApiRequest, @@ -33,7 +17,7 @@ const router = createRouter< router.get( getCurrentUser, validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), - checkIfLiked, + checkIfBeerPostIsLiked, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/beers/[id]/recommendations.ts b/src/pages/api/beers/[id]/recommendations.ts index bdcef88..15b5d36 100644 --- a/src/pages/api/beers/[id]/recommendations.ts +++ b/src/pages/api/beers/[id]/recommendations.ts @@ -1,52 +1,17 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import ServerError from '@/config/util/ServerError'; -import getBeerPostById from '@/services/BeerPost/getBeerPostById'; -import getBeerRecommendations from '@/services/BeerPost/getBeerRecommendations'; +import { getBeerPostRecommendations } from '@/controllers/posts/beer-posts'; +import { GetBeerRecommendationsRequest } from '@/controllers/posts/beer-posts/types'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface BeerPostRequest extends NextApiRequest { - query: { id: string; page_num: string; page_size: string }; -} - const router = createRouter< - BeerPostRequest, + GetBeerRecommendationsRequest, NextApiResponse> >(); -const getBeerRecommendationsRequest = async ( - req: BeerPostRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - - const beerPost = await getBeerPostById(id); - - if (!beerPost) { - throw new ServerError('Beer post not found', 404); - } - - const pageNum = parseInt(req.query.page_num as string, 10); - const pageSize = parseInt(req.query.page_size as string, 10); - - const { count, beerRecommendations } = await getBeerRecommendations({ - beerPost, - pageNum, - pageSize, - }); - - res.setHeader('X-Total-Count', count); - res.status(200).json({ - success: true, - message: 'Recommendations fetched successfully', - statusCode: 200, - payload: beerRecommendations, - }); -}; - router.get( validateRequest({ querySchema: z.object({ @@ -55,7 +20,7 @@ router.get( page_size: z.string().regex(/^[0-9]+$/), }), }), - getBeerRecommendationsRequest, + getBeerPostRecommendations, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/beers/create.ts b/src/pages/api/beers/create.ts index bd9ea15..4c55237 100644 --- a/src/pages/api/beers/create.ts +++ b/src/pages/api/beers/create.ts @@ -1,41 +1,14 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import { createRouter } from 'next-connect'; -import createNewBeerPost from '@/services/BeerPost/createNewBeerPost'; -import CreateBeerPostValidationSchema from '@/services/BeerPost/schema/CreateBeerPostValidationSchema'; + +import CreateBeerPostValidationSchema from '@/services/posts/beer-post/schema/CreateBeerPostValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; import { z } from 'zod'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; - -interface CreateBeerPostRequest extends UserExtendedNextApiRequest { - body: z.infer; -} - -const createBeerPost = async ( - req: CreateBeerPostRequest, - res: NextApiResponse>, -) => { - const { name, description, styleId: typeId, abv, ibu, breweryId } = req.body; - - const newBeerPost = await createNewBeerPost({ - name, - description, - abv, - ibu, - styleId: typeId, - breweryId, - userId: req.user!.id, - }); - - res.status(201).json({ - message: 'Beer post created successfully', - statusCode: 201, - payload: newBeerPost, - success: true, - }); -}; +import { createBeerPost } from '@/controllers/posts/beer-posts'; +import { CreateBeerPostRequest } from '@/controllers/posts/beer-posts/types'; const router = createRouter< CreateBeerPostRequest, diff --git a/src/pages/api/beers/index.ts b/src/pages/api/beers/index.ts index 6a5aa0a..6616805 100644 --- a/src/pages/api/beers/index.ts +++ b/src/pages/api/beers/index.ts @@ -1,40 +1,15 @@ import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; -import getAllBeerPosts from '@/services/BeerPost/getAllBeerPosts'; +import { getBeerPosts } from '@/controllers/posts/beer-posts'; +import { GetAllBeerPostsRequest } from '@/controllers/posts/beer-posts/types'; import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface GetBeerPostsRequest extends NextApiRequest { - query: z.infer; -} - -const getBeerPosts = async ( - req: GetBeerPostsRequest, - res: NextApiResponse>, -) => { - const pageNum = parseInt(req.query.page_num, 10); - const pageSize = parseInt(req.query.page_size, 10); - - const beerPosts = await getAllBeerPosts({ pageNum, pageSize }); - - const beerPostCount = await DBClient.instance.beerPost.count(); - - res.setHeader('X-Total-Count', beerPostCount); - - res.status(200).json({ - message: 'Beer posts retrieved successfully', - statusCode: 200, - payload: beerPosts, - success: true, - }); -}; - const router = createRouter< - GetBeerPostsRequest, + GetAllBeerPostsRequest, NextApiResponse> >(); diff --git a/src/pages/api/beers/search.ts b/src/pages/api/beers/search.ts index e088143..780a583 100644 --- a/src/pages/api/beers/search.ts +++ b/src/pages/api/beers/search.ts @@ -5,7 +5,7 @@ import { NextApiRequest, NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; import DBClient from '@/prisma/DBClient'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; const SearchSchema = z.object({ search: z.string().min(1), diff --git a/src/pages/api/beers/styles/[id]/beers/index.ts b/src/pages/api/beers/styles/[id]/beers/index.ts index 18f82d8..5d89585 100644 --- a/src/pages/api/beers/styles/[id]/beers/index.ts +++ b/src/pages/api/beers/styles/[id]/beers/index.ts @@ -1,7 +1,6 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; -import getBeerPostsByBeerStyleId from '@/services/BeerPost/getBeerPostsByBeerStyleId'; +import { getAllBeersByBeerStyle } from '@/controllers/posts/beer-styles'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiRequest, NextApiResponse } from 'next'; @@ -12,31 +11,6 @@ interface GetAllBeersByBeerStyleRequest extends NextApiRequest { query: { page_size: string; page_num: string; id: string }; } -const getAllBeersByBeerStyle = async ( - req: GetAllBeersByBeerStyleRequest, - res: NextApiResponse>, -) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { page_size, page_num, id } = req.query; - - const beers = await getBeerPostsByBeerStyleId({ - pageNum: parseInt(page_num, 10), - pageSize: parseInt(page_size, 10), - styleId: id, - }); - - const count = await DBClient.instance.beerPost.count({ where: { styleId: id } }); - - res.setHeader('X-Total-Count', count); - - res.status(200).json({ - message: 'Beers fetched successfully', - statusCode: 200, - payload: beers, - success: true, - }); -}; - const router = createRouter< GetAllBeersByBeerStyleRequest, NextApiResponse> diff --git a/src/pages/api/beers/styles/[id]/comments/index.ts b/src/pages/api/beers/styles/[id]/comments/index.ts index 4e44ac2..174d413 100644 --- a/src/pages/api/beers/styles/[id]/comments/index.ts +++ b/src/pages/api/beers/styles/[id]/comments/index.ts @@ -1,77 +1,13 @@ -import DBClient from '@/prisma/DBClient'; - import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import { createRouter } from 'next-connect'; import { z } from 'zod'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import { NextApiResponse } from 'next'; -import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; -import createNewBeerStyleComment from '@/services/BeerStyleComment/createNewBeerStyleComment'; -import getAllBeerStyleComments from '@/services/BeerStyleComment/getAllBeerStyleComments'; - -interface CreateCommentRequest extends UserExtendedNextApiRequest { - body: z.infer; - query: { id: string }; -} - -interface GetAllCommentsRequest extends UserExtendedNextApiRequest { - query: { id: string; page_size: string; page_num: string }; -} - -const createComment = async ( - req: CreateCommentRequest, - res: NextApiResponse>, -) => { - const { content, rating } = req.body; - - const newBeerStyleComment: z.infer = - await createNewBeerStyleComment({ - content, - rating, - beerStyleId: req.query.id, - userId: req.user!.id, - }); - - res.status(201).json({ - message: 'Beer comment created successfully', - statusCode: 201, - payload: newBeerStyleComment, - success: true, - }); -}; - -const getAll = async ( - req: GetAllCommentsRequest, - res: NextApiResponse>, -) => { - const beerStyleId = req.query.id; - // eslint-disable-next-line @typescript-eslint/naming-convention - const { page_size, page_num } = req.query; - - const comments = await getAllBeerStyleComments({ - beerStyleId, - pageNum: parseInt(page_num, 10), - pageSize: parseInt(page_size, 10), - }); - - const count = await DBClient.instance.beerStyleComment.count({ - where: { beerStyleId }, - }); - - res.setHeader('X-Total-Count', count); - - res.status(200).json({ - message: 'Beer comments fetched successfully', - statusCode: 200, - payload: comments, - success: true, - }); -}; +import { createComment, getAll } from '@/controllers/comments/beer-style-comments'; const router = createRouter< // I don't want to use any, but I can't figure out how to get the types to work diff --git a/src/pages/api/beers/styles/[id]/index.ts b/src/pages/api/beers/styles/[id]/index.ts index e72e8c1..c392ab7 100644 --- a/src/pages/api/beers/styles/[id]/index.ts +++ b/src/pages/api/beers/styles/[id]/index.ts @@ -1,31 +1,12 @@ import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import getBeerStyleById from '@/services/BeerStyles/getBeerStyleById'; +import { getBeerStyle } from '@/controllers/posts/beer-styles'; +import { GetBeerStyleByIdRequest } from '@/controllers/posts/beer-styles/types'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface GetBeerStyleByIdRequest extends NextApiRequest { - query: { id: string }; -} - -const getBeerStyle = async ( - req: GetBeerStyleByIdRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - - const beerStyle = await getBeerStyleById(id); - - res.status(200).json({ - message: 'Beer types retrieved successfully', - statusCode: 200, - payload: beerStyle, - success: true, - }); -}; - const router = createRouter< GetBeerStyleByIdRequest, NextApiResponse> diff --git a/src/pages/api/beers/styles/[id]/like/index.ts b/src/pages/api/beers/styles/[id]/like/index.ts index 7562d42..87ff024 100644 --- a/src/pages/api/beers/styles/[id]/like/index.ts +++ b/src/pages/api/beers/styles/[id]/like/index.ts @@ -1,84 +1,32 @@ import { createRouter } from 'next-connect'; import { z } from 'zod'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import { UserExtendedNextApiRequest } from '@/config/auth/types'; -import ServerError from '@/config/util/ServerError'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; - -import getBeerStyleById from '@/services/BeerStyles/getBeerStyleById'; -import findBeerStyleLikeById from '@/services/BeerStyleLike/findBeerStyleLikeById'; -import getBeerStyleLikeCount from '@/services/BeerStyleLike/getBeerStyleLikeCount'; -import createBeerStyleLike from '@/services/BeerStyleLike/createBeerStyleLike'; -import removeBeerStyleLikeById from '@/services/BeerStyleLike/removeBeerStyleLikeById'; - -const sendLikeRequest = async ( - req: UserExtendedNextApiRequest, - res: NextApiResponse>, -) => { - const user = req.user!; - const id = req.query.id as string; - - const beerStyle = await getBeerStyleById(id); - if (!beerStyle) { - throw new ServerError('Could not find a beer style with that id', 404); - } - - const alreadyLiked = await findBeerStyleLikeById({ - beerStyleId: beerStyle.id, - likedById: user.id, - }); - - const jsonResponse = { - success: true as const, - message: '', - statusCode: 200 as const, - }; - - if (alreadyLiked) { - await removeBeerStyleLikeById({ beerStyleLikeId: alreadyLiked.id }); - jsonResponse.message = 'Successfully unliked beer style.'; - } else { - await createBeerStyleLike({ beerStyleId: beerStyle.id, user }); - jsonResponse.message = 'Successfully liked beer style.'; - } - - res.status(200).json(jsonResponse); -}; - -const getLikeCount = async ( - req: NextApiRequest, - res: NextApiResponse>, -) => { - const id = req.query.id as string; - - const likeCount = await getBeerStyleLikeCount({ beerStyleId: id }); - res.status(200).json({ - success: true, - message: 'Successfully retrieved like count.', - statusCode: 200, - payload: { likeCount }, - }); -}; +import { + getBeerStyleLikeCountRequest, + sendBeerStyleLikeRequest, +} from '@/controllers/likes/beer-style-likes'; +import { LikeRequest } from '@/controllers/likes/types'; const router = createRouter< - UserExtendedNextApiRequest, + LikeRequest, NextApiResponse> >(); router.post( getCurrentUser, validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), - sendLikeRequest, + sendBeerStyleLikeRequest, ); router.get( validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), - getLikeCount, + getBeerStyleLikeCountRequest, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/beers/styles/[id]/like/is-liked.ts b/src/pages/api/beers/styles/[id]/like/is-liked.ts index ad7679a..3289cb6 100644 --- a/src/pages/api/beers/styles/[id]/like/is-liked.ts +++ b/src/pages/api/beers/styles/[id]/like/is-liked.ts @@ -6,37 +6,7 @@ import APIResponseValidationSchema from '@/validation/APIResponseValidationSchem import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -import DBClient from '@/prisma/DBClient'; - -interface FindBeerStyleLikeByIdArgs { - beerStyleId: string; - likedById: string; -} - -const findBeerStyleLikeById = async ({ - beerStyleId, - likedById, -}: FindBeerStyleLikeByIdArgs) => { - return DBClient.instance.beerStyleLike.findFirst({ - where: { beerStyleId, likedById }, - }); -}; - -const checkIfLiked = async ( - req: UserExtendedNextApiRequest, - res: NextApiResponse>, -) => { - const user = req.user!; - const beerStyleId = req.query.id as string; - - const alreadyLiked = await findBeerStyleLikeById({ beerStyleId, likedById: user.id }); - res.status(200).json({ - success: true, - message: alreadyLiked ? 'Beer style is liked.' : 'Beer style is not liked.', - statusCode: 200, - payload: { isLiked: !!alreadyLiked }, - }); -}; +import { checkIfBeerStyleIsLiked } from '@/controllers/likes/beer-style-likes'; const router = createRouter< UserExtendedNextApiRequest, @@ -46,7 +16,7 @@ const router = createRouter< router.get( getCurrentUser, validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), - checkIfLiked, + checkIfBeerStyleIsLiked, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/beers/styles/create.ts b/src/pages/api/beers/styles/create.ts index 8ec7fff..5b956e3 100644 --- a/src/pages/api/beers/styles/create.ts +++ b/src/pages/api/beers/styles/create.ts @@ -1,97 +1,19 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; - +import { createBeerStyle } from '@/controllers/posts/beer-styles'; +import { CreateBeerStyleRequest } from '@/controllers/posts/beer-styles/types'; +import CreateBeerStyleValidationSchema from '@/services/posts/beer-style-post/schema/CreateBeerStyleValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -const BeerStyleValidationSchema = z.object({ - id: z.string().cuid(), - name: z.string(), - postedBy: z.object({ - id: z.string().cuid(), - username: z.string(), - }), - glassware: z.object({ - id: z.string().cuid(), - name: z.string(), - description: z.string(), - }), - description: z.string(), - createdAt: z.date(), - updatedAt: z.date().nullable(), -}); - -const CreateBeerStyleValidationSchema = BeerStyleValidationSchema.omit({ - id: true, - postedBy: true, - createdAt: true, - updatedAt: true, - glassware: true, -}).extend({ - glasswareId: z.string().cuid(), -}); - -interface CreateBeerStyleRequest extends UserExtendedNextApiRequest { - body: z.infer; -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -interface GetBeerStyleRequest extends NextApiRequest { - query: { - id: string; - }; -} - -const createBeerStyle = async ( - req: CreateBeerStyleRequest, - res: NextApiResponse>, -) => { - const user = req.user!; - const { name, description, glasswareId } = req.body; - - const glassware = await DBClient.instance.glassware.findUnique({ - where: { id: glasswareId }, - }); - - if (!glassware) { - throw new ServerError('Glassware not found', 404); - } - - const newBeerStyle = await DBClient.instance.beerStyle.create({ - data: { - description, - name, - postedBy: { connect: { id: user.id } }, - glassware: { connect: { id: glassware.id } }, - }, - select: { - id: true, - name: true, - postedBy: { select: { id: true, username: true } }, - createdAt: true, - updatedAt: true, - }, - }); - - res.status(200).json({ - message: 'Beer posts retrieved successfully', - statusCode: 200, - payload: newBeerStyle, - success: true, - }); -}; - const router = createRouter< CreateBeerStyleRequest, NextApiResponse> >(); -router.get( +router.post( validateRequest({ bodySchema: CreateBeerStyleValidationSchema }), getCurrentUser, createBeerStyle, diff --git a/src/pages/api/beers/styles/index.ts b/src/pages/api/beers/styles/index.ts index 20901bb..cc518a6 100644 --- a/src/pages/api/beers/styles/index.ts +++ b/src/pages/api/beers/styles/index.ts @@ -1,39 +1,16 @@ import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; -import getAllBeerStyles from '@/services/BeerStyles/getAllBeerStyles'; +import { getBeerStyles } from '@/controllers/posts/beer-styles'; +import { GetAllPostsRequest } from '@/controllers/posts/types'; + import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface GetBeerStylesRequest extends NextApiRequest { - query: z.infer; -} - -const getBeerStyles = async ( - req: GetBeerStylesRequest, - res: NextApiResponse>, -) => { - const pageNum = parseInt(req.query.page_num, 10); - const pageSize = parseInt(req.query.page_size, 10); - - const beerStyles = await getAllBeerStyles({ pageNum, pageSize }); - const beerStyleCount = await DBClient.instance.beerStyle.count(); - - res.setHeader('X-Total-Count', beerStyleCount); - - res.status(200).json({ - message: 'Beer types retrieved successfully', - statusCode: 200, - payload: beerStyles, - success: true, - }); -}; - const router = createRouter< - GetBeerStylesRequest, + GetAllPostsRequest, NextApiResponse> >(); diff --git a/src/pages/api/breweries/[id]/beers/index.ts b/src/pages/api/breweries/[id]/beers/index.ts index b2bcb59..c50ebe4 100644 --- a/src/pages/api/breweries/[id]/beers/index.ts +++ b/src/pages/api/breweries/[id]/beers/index.ts @@ -1,71 +1,14 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import { getAllBeersByBrewery } from '@/controllers/posts/breweries'; +import { GetAllPostsByConnectedPostId } from '@/controllers/posts/types'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface GetAllBeersByBreweryRequest extends NextApiRequest { - query: { page_size: string; page_num: string; id: string }; -} - -const getAllBeersByBrewery = async ( - req: GetAllBeersByBreweryRequest, - res: NextApiResponse>, -) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { page_size, page_num, id } = req.query; - - const pageNum = parseInt(page_num, 10); - const pageSize = parseInt(page_size, 10); - - const beers: z.infer[] = - await DBClient.instance.beerPost.findMany({ - where: { breweryId: id }, - skip: (pageNum - 1) * pageSize, - take: pageSize, - select: { - id: true, - name: true, - ibu: true, - abv: true, - createdAt: true, - updatedAt: true, - description: true, - postedBy: { select: { username: true, id: true } }, - brewery: { select: { name: true, id: true } }, - style: { select: { name: true, id: true, description: true } }, - beerImages: { - select: { - alt: true, - path: true, - caption: true, - id: true, - createdAt: true, - updatedAt: true, - }, - }, - }, - }); - - const count = await DBClient.instance.beerPost.count({ - where: { breweryId: id }, - }); - - res.setHeader('X-Total-Count', count); - - res.status(200).json({ - message: 'Beers fetched successfully', - statusCode: 200, - payload: beers, - success: true, - }); -}; - const router = createRouter< - GetAllBeersByBreweryRequest, + GetAllPostsByConnectedPostId, NextApiResponse> >(); diff --git a/src/pages/api/breweries/[id]/comments/index.ts b/src/pages/api/breweries/[id]/comments/index.ts index 00f4e12..2e0175d 100644 --- a/src/pages/api/breweries/[id]/comments/index.ts +++ b/src/pages/api/breweries/[id]/comments/index.ts @@ -1,11 +1,5 @@ -/* eslint-disable @typescript-eslint/no-unused-vars */ -import DBClient from '@/prisma/DBClient'; - -import createNewBeerComment from '@/services/BeerComment/createNewBeerComment'; - import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import { createRouter } from 'next-connect'; @@ -13,73 +7,8 @@ import { z } from 'zod'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import { NextApiResponse } from 'next'; -import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; -import getAllBreweryComments from '@/services/BreweryComment/getAllBreweryComments'; import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; -import createNewBreweryComment from '@/services/BreweryComment/createNewBreweryComment'; - -interface CreateCommentRequest extends UserExtendedNextApiRequest { - body: z.infer; - query: { id: string }; -} - -interface GetAllCommentsRequest extends UserExtendedNextApiRequest { - query: { id: string; page_size: string; page_num: string }; -} - -const createComment = async ( - req: CreateCommentRequest, - res: NextApiResponse>, -) => { - const { content, rating } = req.body; - - const breweryPostId = req.query.id; - - const user = req.user!; - - const newBreweryComment: z.infer = - await createNewBreweryComment({ - content, - rating, - breweryPostId, - userId: user.id, - }); - - res.status(201).json({ - message: 'Beer comment created successfully', - statusCode: 201, - payload: newBreweryComment, - success: true, - }); -}; - -const getAll = async ( - req: GetAllCommentsRequest, - res: NextApiResponse>, -) => { - const breweryPostId = req.query.id; - // eslint-disable-next-line @typescript-eslint/naming-convention - const { page_size, page_num } = req.query; - - const comments = await getAllBreweryComments({ - id: breweryPostId, - pageNum: parseInt(page_num, 10), - pageSize: parseInt(page_size, 10), - }); - - const count = await DBClient.instance.breweryComment.count({ - where: { breweryPostId }, - }); - - res.setHeader('X-Total-Count', count); - - res.status(200).json({ - message: 'Beer comments fetched successfully', - statusCode: 200, - payload: comments, - success: true, - }); -}; +import { createComment, getAll } from '@/controllers/comments/brewery-comments'; const router = createRouter< // I don't want to use any, but I can't figure out how to get the types to work diff --git a/src/pages/api/breweries/[id]/images/index.ts b/src/pages/api/breweries/[id]/images/index.ts index ad056ef..66c3317 100644 --- a/src/pages/api/breweries/[id]/images/index.ts +++ b/src/pages/api/breweries/[id]/images/index.ts @@ -1,53 +1,19 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import { createRouter } from 'next-connect'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import { NextApiResponse } from 'next'; import { z } from 'zod'; -import ServerError from '@/config/util/ServerError'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import ImageMetadataValidationSchema from '@/services/schema/ImageSchema/ImageMetadataValidationSchema'; -import addBreweryImageToDB from '@/services/BreweryImage/addBreweryImageToDB'; import { uploadMiddlewareMultiple } from '@/config/multer/uploadMiddleware'; - -interface UploadBreweryPostImagesRequest extends UserExtendedNextApiRequest { - files?: Express.Multer.File[]; - query: { id: string }; - body: z.infer; -} - -const processImageData = async ( - req: UploadBreweryPostImagesRequest, - res: NextApiResponse>, -) => { - const { files, user, body } = req; - - if (!files || !files.length) { - throw new ServerError('No images uploaded', 400); - } - - const breweryImages = await addBreweryImageToDB({ - alt: body.alt, - caption: body.caption, - breweryPostId: req.query.id, - userId: user!.id, - files, - }); - - res.status(200).json({ - success: true, - message: `Successfully uploaded ${breweryImages.length} image${ - breweryImages.length > 1 ? 's' : '' - }`, - statusCode: 200, - }); -}; +import { UploadImagesRequest } from '@/controllers/images/types'; +import { processBreweryImageData } from '@/controllers/images/brewery-images'; const router = createRouter< - UploadBreweryPostImagesRequest, + UploadImagesRequest, NextApiResponse> >(); @@ -56,7 +22,7 @@ router.post( // @ts-expect-error uploadMiddlewareMultiple, validateRequest({ bodySchema: ImageMetadataValidationSchema }), - processImageData, + processBreweryImageData, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/breweries/[id]/index.ts b/src/pages/api/breweries/[id]/index.ts index f491ea6..f71a7a5 100644 --- a/src/pages/api/breweries/[id]/index.ts +++ b/src/pages/api/breweries/[id]/index.ts @@ -1,81 +1,17 @@ import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; -import { createRouter, NextHandler } from 'next-connect'; +import { createRouter } from 'next-connect'; import { z } from 'zod'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; -import getBreweryPostById from '@/services/BreweryPost/getBreweryPostById'; -import EditBreweryPostValidationSchema from '@/services/BreweryPost/schema/EditBreweryPostValidationSchema'; -interface BreweryPostRequest extends UserExtendedNextApiRequest { - query: { id: string }; -} +import { + checkIfBreweryPostOwner, + editBreweryPost, + deleteBreweryPost, +} from '@/controllers/posts/breweries'; +import { EditBreweryPostRequest } from '@/controllers/posts/breweries/types'; -interface EditBreweryPostRequest extends BreweryPostRequest { - body: z.infer; -} - -const checkIfBreweryPostOwner = async ( - req: BreweryPostRequest, - res: NextApiResponse, - next: NextHandler, -) => { - const user = req.user!; - const { id } = req.query; - - const breweryPost = await getBreweryPostById(id); - if (!breweryPost) { - throw new ServerError('Brewery post not found', 404); - } - - if (breweryPost.postedBy.id !== user.id) { - throw new ServerError('You are not the owner of this brewery post', 403); - } - - return next(); -}; - -const editBreweryPost = async ( - req: EditBreweryPostRequest, - res: NextApiResponse>, -) => { - const { - body, - query: { id }, - } = req; - - await DBClient.instance.breweryPost.update({ - where: { id }, - data: body, - }); - - res.status(200).json({ - message: 'Brewery post updated successfully', - success: true, - statusCode: 200, - }); -}; - -const deleteBreweryPost = async (req: BreweryPostRequest, res: NextApiResponse) => { - const { - query: { id }, - } = req; - - const deleted = await DBClient.instance.breweryPost.delete({ where: { id } }); - - if (!deleted) { - throw new ServerError('Brewery post not found', 404); - } - - res.status(200).json({ - message: 'Brewery post deleted successfully', - success: true, - statusCode: 200, - }); -}; const router = createRouter< EditBreweryPostRequest, NextApiResponse> diff --git a/src/pages/api/breweries/[id]/like/index.ts b/src/pages/api/breweries/[id]/like/index.ts index d567dd6..40b6c14 100644 --- a/src/pages/api/breweries/[id]/like/index.ts +++ b/src/pages/api/breweries/[id]/like/index.ts @@ -2,80 +2,16 @@ import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; +import { + sendBreweryPostLikeRequest, + getBreweryPostLikeCount, +} from '@/controllers/likes/brewery-post-likes'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -const sendLikeRequest = async ( - req: UserExtendedNextApiRequest, - res: NextApiResponse>, -) => { - const id = req.query.id! as string; - const user = req.user!; - - const breweryPost = await DBClient.instance.breweryPost.findUnique({ - where: { id }, - }); - - if (!breweryPost) { - throw new ServerError('Could not find a brewery post with that id', 404); - } - - const alreadyLiked = await DBClient.instance.breweryPostLike.findFirst({ - where: { breweryPostId: breweryPost.id, likedById: user.id }, - }); - - const jsonResponse = { - success: true as const, - message: '', - statusCode: 200 as const, - }; - - if (alreadyLiked) { - await DBClient.instance.breweryPostLike.delete({ - where: { id: alreadyLiked.id }, - }); - jsonResponse.message = 'Successfully unliked brewery post'; - } else { - await DBClient.instance.breweryPostLike.create({ - data: { breweryPostId: breweryPost.id, likedById: user.id }, - }); - jsonResponse.message = 'Successfully liked brewery post'; - } - - res.status(200).json(jsonResponse); -}; - -const getLikeCount = async ( - req: NextApiRequest, - res: NextApiResponse>, -) => { - const id = req.query.id! as string; - - const breweryPost = await DBClient.instance.breweryPost.findUnique({ - where: { id }, - }); - - if (!breweryPost) { - throw new ServerError('Could not find a brewery post with that id', 404); - } - - const likeCount = await DBClient.instance.breweryPostLike.count({ - where: { breweryPostId: breweryPost.id }, - }); - - res.status(200).json({ - success: true, - message: 'Successfully retrieved like count', - statusCode: 200, - payload: { likeCount }, - }); -}; - const router = createRouter< UserExtendedNextApiRequest, NextApiResponse> @@ -84,12 +20,12 @@ const router = createRouter< router.post( getCurrentUser, validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), - sendLikeRequest, + sendBreweryPostLikeRequest, ); router.get( validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), - getLikeCount, + getBreweryPostLikeCount, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/breweries/[id]/like/is-liked.ts b/src/pages/api/breweries/[id]/like/is-liked.ts index 00dc7eb..4a41897 100644 --- a/src/pages/api/breweries/[id]/like/is-liked.ts +++ b/src/pages/api/breweries/[id]/like/is-liked.ts @@ -2,34 +2,13 @@ import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; +import { getBreweryPostLikeStatus } from '@/controllers/likes/brewery-post-likes'; + import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -const checkIfLiked = async ( - req: UserExtendedNextApiRequest, - res: NextApiResponse>, -) => { - const user = req.user!; - const id = req.query.id as string; - - const alreadyLiked = await DBClient.instance.breweryPostLike.findFirst({ - where: { - breweryPostId: id, - likedById: user.id, - }, - }); - - res.status(200).json({ - success: true, - message: alreadyLiked ? 'Brewery post is liked.' : 'Brewery post is not liked.', - statusCode: 200, - payload: { isLiked: !!alreadyLiked }, - }); -}; - const router = createRouter< UserExtendedNextApiRequest, NextApiResponse> @@ -42,7 +21,7 @@ router.get( id: z.string().cuid(), }), }), - checkIfLiked, + getBreweryPostLikeStatus, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/breweries/create.ts b/src/pages/api/breweries/create.ts index 8cf3e04..6c935ef 100644 --- a/src/pages/api/breweries/create.ts +++ b/src/pages/api/breweries/create.ts @@ -1,4 +1,3 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import { createRouter } from 'next-connect'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; @@ -6,60 +5,10 @@ import { NextApiResponse } from 'next'; import { z } from 'zod'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; -import CreateBreweryPostSchema from '@/services/BreweryPost/schema/CreateBreweryPostSchema'; -import createNewBreweryPost from '@/services/BreweryPost/createNewBreweryPost'; -import geocode from '@/config/mapbox/geocoder'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; +import CreateBreweryPostSchema from '@/services/posts/brewery-post/schema/CreateBreweryPostSchema'; -interface CreateBreweryPostRequest extends UserExtendedNextApiRequest { - body: z.infer; -} - -const createBreweryPost = async ( - req: CreateBreweryPostRequest, - res: NextApiResponse>, -) => { - const { name, description, dateEstablished, address, city, country, region } = req.body; - const userId = req.user!.id; - - const fullAddress = `${address}, ${city}, ${region}, ${country}`; - - const geocoded = await geocode(fullAddress); - - if (!geocoded) { - throw new ServerError('Address is not valid', 400); - } - - const [latitude, longitude] = geocoded.center; - - const location = await DBClient.instance.breweryLocation.create({ - data: { - address, - city, - country, - stateOrProvince: region, - coordinates: [latitude, longitude], - postedBy: { connect: { id: userId } }, - }, - select: { id: true }, - }); - - const newBreweryPost = await createNewBreweryPost({ - name, - description, - locationId: location.id, - dateEstablished, - userId, - }); - - res.status(201).json({ - message: 'Brewery post created successfully', - statusCode: 201, - payload: newBreweryPost, - success: true, - }); -}; +import { CreateBreweryPostRequest } from '@/controllers/posts/breweries/types'; +import { createBreweryPost } from '@/controllers/posts/breweries'; const router = createRouter< CreateBreweryPostRequest, diff --git a/src/pages/api/breweries/index.ts b/src/pages/api/breweries/index.ts index d8814fa..1bffcd2 100644 --- a/src/pages/api/breweries/index.ts +++ b/src/pages/api/breweries/index.ts @@ -1,36 +1,13 @@ import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; -import getAllBreweryPosts from '@/services/BreweryPost/getAllBreweryPosts'; +import { getBreweryPosts } from '@/controllers/posts/breweries'; +import { GetBreweryPostsRequest } from '@/controllers/posts/breweries/types'; import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface GetBreweryPostsRequest extends NextApiRequest { - query: z.infer; -} - -const getBreweryPosts = async ( - req: GetBreweryPostsRequest, - res: NextApiResponse>, -) => { - const pageNum = parseInt(req.query.page_num, 10); - const pageSize = parseInt(req.query.page_size, 10); - - const breweryPosts = await getAllBreweryPosts({ pageNum, pageSize }); - const breweryPostCount = await DBClient.instance.breweryPost.count(); - - res.setHeader('X-Total-Count', breweryPostCount); - res.status(200).json({ - message: 'Brewery posts retrieved successfully', - statusCode: 200, - payload: breweryPosts, - success: true, - }); -}; - const router = createRouter< GetBreweryPostsRequest, NextApiResponse> diff --git a/src/pages/api/breweries/map/index.ts b/src/pages/api/breweries/map/index.ts index 1696faf..185cd3f 100644 --- a/src/pages/api/breweries/map/index.ts +++ b/src/pages/api/breweries/map/index.ts @@ -1,51 +1,15 @@ import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; -import BreweryPostMapQueryResult from '@/services/BreweryPost/schema/BreweryPostMapQueryResult'; +import { getMapBreweryPosts } from '@/controllers/posts/breweries'; +import { GetBreweryPostsRequest } from '@/controllers/posts/breweries/types'; + import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; + import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface GetBreweryPostsRequest extends NextApiRequest { - query: z.infer; -} - -const getBreweryPosts = async ( - req: GetBreweryPostsRequest, - res: NextApiResponse>, -) => { - const pageNum = parseInt(req.query.page_num, 10); - const pageSize = parseInt(req.query.page_size, 10); - - const skip = (pageNum - 1) * pageSize; - const take = pageSize; - - const breweryPosts: z.infer[] = - await DBClient.instance.breweryPost.findMany({ - select: { - location: { - select: { coordinates: true, city: true, country: true, stateOrProvince: true }, - }, - id: true, - name: true, - }, - skip, - take, - }); - const breweryPostCount = await DBClient.instance.breweryPost.count(); - - res.setHeader('X-Total-Count', breweryPostCount); - - res.status(200).json({ - message: 'Brewery posts retrieved successfully', - statusCode: 200, - payload: breweryPosts, - success: true, - }); -}; - const router = createRouter< GetBreweryPostsRequest, NextApiResponse> @@ -53,7 +17,7 @@ const router = createRouter< router.get( validateRequest({ querySchema: PaginatedQueryResponseSchema }), - getBreweryPosts, + getMapBreweryPosts, ); const handler = router.handler(); diff --git a/src/pages/api/brewery-comments/[id].ts b/src/pages/api/brewery-comments/[id].ts index 4d96022..57e13c4 100644 --- a/src/pages/api/brewery-comments/[id].ts +++ b/src/pages/api/brewery-comments/[id].ts @@ -1,86 +1,23 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; -import getBreweryCommentById from '@/services/BreweryComment/getBreweryCommentById'; + +import { + checkIfBreweryCommentOwner, + deleteBreweryPostComment, + editBreweryPostComment, +} from '@/controllers/comments/brewery-comments'; +import { CommentRequest } from '@/controllers/comments/types'; + import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; -import { createRouter, NextHandler } from 'next-connect'; +import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface DeleteCommentRequest extends UserExtendedNextApiRequest { - query: { id: string }; -} - -interface EditCommentRequest extends UserExtendedNextApiRequest { - query: { id: string }; - body: z.infer; -} - -const checkIfCommentOwner = async ( - req: DeleteCommentRequest | EditCommentRequest, - res: NextApiResponse>, - next: NextHandler, -) => { - const { id } = req.query; - const user = req.user!; - const comment = await getBreweryCommentById(id); - - if (!comment) { - throw new ServerError('Comment not found', 404); - } - - if (comment.postedById !== user.id) { - throw new ServerError('You are not authorized to modify this comment', 403); - } - - return next(); -}; - -const editComment = async ( - req: EditCommentRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - - const updated = await DBClient.instance.breweryComment.update({ - where: { id }, - data: { - content: req.body.content, - rating: req.body.rating, - updatedAt: new Date(), - }, - }); - - return res.status(200).json({ - success: true, - message: 'Comment updated successfully', - statusCode: 200, - payload: updated, - }); -}; - -const deleteComment = async ( - req: DeleteCommentRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - - await DBClient.instance.breweryComment.delete({ where: { id } }); - - res.status(200).json({ - success: true, - message: 'Comment deleted successfully', - statusCode: 200, - }); -}; - const router = createRouter< - DeleteCommentRequest, + CommentRequest, NextApiResponse> >(); @@ -90,8 +27,8 @@ router querySchema: z.object({ id: z.string().cuid() }), }), getCurrentUser, - checkIfCommentOwner, - deleteComment, + checkIfBreweryCommentOwner, + deleteBreweryPostComment, ) .put( validateRequest({ @@ -99,8 +36,8 @@ router bodySchema: CreateCommentValidationSchema, }), getCurrentUser, - checkIfCommentOwner, - editComment, + checkIfBreweryCommentOwner, + editBreweryPostComment, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/users/[id]/follow-user.ts b/src/pages/api/users/[id]/follow-user.ts index 0bb438e..866f40e 100644 --- a/src/pages/api/users/[id]/follow-user.ts +++ b/src/pages/api/users/[id]/follow-user.ts @@ -2,9 +2,7 @@ import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; -import findUserById from '@/services/User/findUserById'; +import { followUser } from '@/controllers/users/profile'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; @@ -21,55 +19,6 @@ const router = createRouter< NextApiResponse> >(); -const followUser = async ( - req: GetUserFollowInfoRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - - const user = await findUserById(id); - if (!user) { - throw new ServerError('User not found', 404); - } - - const currentUser = req.user!; - const userIsFollowedBySessionUser = await DBClient.instance.userFollow.findFirst({ - where: { - followerId: currentUser.id, - followingId: id, - }, - }); - - if (!userIsFollowedBySessionUser) { - await DBClient.instance.userFollow.create({ - data: { followerId: currentUser.id, followingId: id }, - }); - - res.status(200).json({ - message: 'Now following user.', - success: true, - statusCode: 200, - }); - - return; - } - - await DBClient.instance.userFollow.delete({ - where: { - followerId_followingId: { - followerId: currentUser.id, - followingId: id, - }, - }, - }); - - res.status(200).json({ - message: 'No longer following user.', - success: true, - statusCode: 200, - }); -}; - router.post( validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), getCurrentUser, diff --git a/src/pages/api/users/[id]/followers.ts b/src/pages/api/users/[id]/followers.ts index 2917802..761e1a3 100644 --- a/src/pages/api/users/[id]/followers.ts +++ b/src/pages/api/users/[id]/followers.ts @@ -1,59 +1,18 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; -import findUserById from '@/services/User/findUserById'; -import getUsersFollowingUser from '@/services/UserFollows/getUsersFollowingUser'; +import { getUserFollowers } from '@/controllers/users/profile'; +import { GetUserFollowInfoRequest } from '@/controllers/users/profile/types'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface GetUserFollowInfoRequest extends UserExtendedNextApiRequest { - query: { id: string; page_size: string; page_num: string }; -} - const router = createRouter< GetUserFollowInfoRequest, NextApiResponse> >(); -const getFollowingInfo = async ( - req: GetUserFollowInfoRequest, - res: NextApiResponse>, -) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { id, page_num, page_size } = req.query; - - const user = await findUserById(id); - if (!user) { - throw new ServerError('User not found', 404); - } - - const pageNum = parseInt(page_num, 10); - const pageSize = parseInt(page_size, 10); - - const following = await getUsersFollowingUser({ - userId: id, - pageNum, - pageSize, - }); - const followingCount = await DBClient.instance.userFollow.count({ - where: { following: { id } }, - }); - - res.setHeader('X-Total-Count', followingCount); - - res.json({ - message: 'Retrieved users that are followed by queried user', - payload: following, - success: true, - statusCode: 200, - }); -}; - router.get( validateRequest({ querySchema: z.object({ @@ -62,7 +21,7 @@ router.get( page_num: z.string().regex(/^\d+$/), }), }), - getFollowingInfo, + getUserFollowers, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/users/[id]/following.ts b/src/pages/api/users/[id]/following.ts index 1c3fa61..9e99111 100644 --- a/src/pages/api/users/[id]/following.ts +++ b/src/pages/api/users/[id]/following.ts @@ -1,10 +1,7 @@ import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; -import findUserById from '@/services/User/findUserById'; -import getUsersFollowedByUser from '@/services/UserFollows/getUsersFollowedByUser'; +import { getUsersFollowed } from '@/controllers/users/profile'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; @@ -20,40 +17,6 @@ const router = createRouter< NextApiResponse> >(); -const getFollowingInfo = async ( - req: GetUserFollowInfoRequest, - res: NextApiResponse>, -) => { - // eslint-disable-next-line @typescript-eslint/naming-convention - const { id, page_num, page_size } = req.query; - - const user = await findUserById(id); - if (!user) { - throw new ServerError('User not found', 404); - } - - const pageNum = parseInt(page_num, 10); - const pageSize = parseInt(page_size, 10); - - const following = await getUsersFollowedByUser({ - userId: id, - pageNum, - pageSize, - }); - const followingCount = await DBClient.instance.userFollow.count({ - where: { follower: { id } }, - }); - - res.setHeader('X-Total-Count', followingCount); - - res.json({ - message: 'Retrieved users that are followed by queried user', - payload: following, - success: true, - statusCode: 200, - }); -}; - router.get( validateRequest({ querySchema: z.object({ @@ -62,7 +25,7 @@ router.get( page_num: z.string().regex(/^\d+$/), }), }), - getFollowingInfo, + getUsersFollowed, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/users/[id]/index.ts b/src/pages/api/users/[id]/index.ts index d0da141..cc62439 100644 --- a/src/pages/api/users/[id]/index.ts +++ b/src/pages/api/users/[id]/index.ts @@ -1,113 +1,17 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; -import deleteUserById from '@/services/User/deleteUserById'; -import findUserByEmail from '@/services/User/findUserByEmail'; -import findUserById from '@/services/User/findUserById'; -import findUserByUsername from '@/services/User/findUserByUsername'; -import { BaseCreateUserSchema } from '@/services/User/schema/CreateUserValidationSchemas'; +import { editUserInfo, deleteAccount } from '@/controllers/users/auth'; +import { checkIfUserCanEditUser } from '@/controllers/users/profile'; +import { EditUserRequest } from '@/controllers/users/profile/types'; +import EditUserSchema from '@/services/users/auth/schema/EditUserSchema'; + import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; -import { NextHandler, createRouter } from 'next-connect'; +import { createRouter } from 'next-connect'; import { z } from 'zod'; -const EditUserSchema = BaseCreateUserSchema.pick({ - username: true, - email: true, - firstName: true, - lastName: true, -}); - -interface UserRouteRequest extends UserExtendedNextApiRequest { - query: { id: string }; -} - -interface EditUserRequest extends UserRouteRequest { - body: z.infer; -} - -const checkIfUserCanEditUser = async ( - req: EditUserRequest, - res: NextApiResponse, - next: NextHandler, -) => { - const authenticatedUser = req.user!; - - const userToUpdate = await findUserById(req.query.id); - if (!userToUpdate) { - throw new ServerError('User not found', 404); - } - - if (authenticatedUser.id !== userToUpdate.id) { - throw new ServerError('You are not permitted to modify this user', 403); - } - - return next(); -}; - -const editUser = async ( - req: EditUserRequest, - res: NextApiResponse>, -) => { - const { email, firstName, lastName, username } = req.body; - - const [usernameIsTaken, emailIsTaken] = await Promise.all([ - findUserByUsername(username), - findUserByEmail(email), - ]); - - const emailChanged = req.user!.email !== email; - const usernameChanged = req.user!.username !== username; - - if (emailIsTaken && emailChanged) { - throw new ServerError('Email is already taken', 400); - } - - if (usernameIsTaken && usernameChanged) { - throw new ServerError('Username is already taken', 400); - } - - const updatedUser = await DBClient.instance.user.update({ - where: { id: req.user!.id }, - data: { - email, - firstName, - lastName, - username, - accountIsVerified: emailChanged ? false : undefined, - }, - }); - - res.json({ - message: 'User edited successfully', - payload: updatedUser, - success: true, - statusCode: 200, - }); -}; - -const deleteUser = async ( - req: UserRouteRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - const deletedUser = await deleteUserById(id); - - if (!deletedUser) { - throw new ServerError('Could not find a user with that id.', 400); - } - - res.send({ - message: 'Successfully deleted user.', - statusCode: 200, - success: true, - }); -}; - const router = createRouter< EditUserRequest, NextApiResponse> @@ -120,7 +24,7 @@ router.put( querySchema: z.object({ id: z.string().cuid() }), }), checkIfUserCanEditUser, - editUser, + editUserInfo, ); router.delete( @@ -129,7 +33,7 @@ router.delete( querySchema: z.object({ id: z.string().cuid() }), }), checkIfUserCanEditUser, - deleteUser, + deleteAccount, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/users/[id]/is-followed.ts b/src/pages/api/users/[id]/is-followed.ts index 682b15d..a53ed42 100644 --- a/src/pages/api/users/[id]/is-followed.ts +++ b/src/pages/api/users/[id]/is-followed.ts @@ -1,65 +1,21 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; -import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; -import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; -import findUserById from '@/services/User/findUserById'; - -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; - import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface GetUserFollowInfoRequest extends UserExtendedNextApiRequest { - query: { id: string }; -} +import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; +import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; +import validateRequest from '@/config/nextConnect/middleware/validateRequest'; + +import { checkIfUserIsFollowedBySessionUser } from '@/controllers/users/profile'; +import { GetUserFollowInfoRequest } from '@/controllers/users/profile/types'; + +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; const router = createRouter< GetUserFollowInfoRequest, NextApiResponse> >(); -const checkIfUserIsFollowedBySessionUser = async ( - req: GetUserFollowInfoRequest, - res: NextApiResponse>, -) => { - const { id } = req.query; - - const user = await findUserById(id); - if (!user) { - throw new ServerError('User not found', 404); - } - - const currentUser = req.user!; - - const userIsFollowedBySessionUser = await DBClient.instance.userFollow.findFirst({ - where: { - followerId: currentUser.id, - followingId: id, - }, - }); - - if (!userIsFollowedBySessionUser) { - res.status(200).json({ - message: 'User is not followed by the current user.', - success: true, - statusCode: 200, - payload: { isFollowed: false }, - }); - - return; - } - - res.status(200).json({ - message: 'User is followed by the current user.', - success: true, - statusCode: 200, - payload: { isFollowed: true }, - }); -}; - router.get( validateRequest({ querySchema: z.object({ id: z.string().cuid() }) }), getCurrentUser, diff --git a/src/pages/api/users/[id]/posts/beers.ts b/src/pages/api/users/[id]/posts/beers.ts index 040e64b..ced48d3 100644 --- a/src/pages/api/users/[id]/posts/beers.ts +++ b/src/pages/api/users/[id]/posts/beers.ts @@ -1,48 +1,15 @@ -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; -import getBeerPostsByPostedById from '@/services/BeerPost/getBeerPostsByPostedById'; import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; - -interface GetBeerPostsRequest extends NextApiRequest { - query: { - page_num: string; - page_size: string; - id: string; - }; -} - -const getBeerPostsByUserId = async ( - req: GetBeerPostsRequest, - res: NextApiResponse>, -) => { - const pageNum = parseInt(req.query.page_num, 10); - const pageSize = parseInt(req.query.page_size, 10); - - const { id } = req.query; - - const beerPosts = await getBeerPostsByPostedById({ pageNum, pageSize, postedById: id }); - - const beerPostCount = await DBClient.instance.beerPost.count({ - where: { postedBy: { id } }, - }); - - res.setHeader('X-Total-Count', beerPostCount); - - res.status(200).json({ - message: `Beer posts by user ${id} fetched successfully`, - statusCode: 200, - payload: beerPosts, - success: true, - }); -}; +import { GetAllPostsByConnectedPostId } from '@/controllers/posts/types'; +import { getBeerPostsByUserId } from '@/controllers/posts/beer-posts'; const router = createRouter< - GetBeerPostsRequest, + GetAllPostsByConnectedPostId, NextApiResponse> >(); diff --git a/src/pages/api/users/[id]/posts/breweries.ts b/src/pages/api/users/[id]/posts/breweries.ts index 1b8975f..5ff73da 100644 --- a/src/pages/api/users/[id]/posts/breweries.ts +++ b/src/pages/api/users/[id]/posts/breweries.ts @@ -1,52 +1,15 @@ -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; -import getAllBreweryPostsByPostedById from '@/services/BreweryPost/getAllBreweryPostsByPostedById'; import PaginatedQueryResponseSchema from '@/services/schema/PaginatedQueryResponseSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; - -interface GetBreweryPostsRequest extends NextApiRequest { - query: { - page_num: string; - page_size: string; - id: string; - }; -} - -const getBreweryPostsByUserId = async ( - req: GetBreweryPostsRequest, - res: NextApiResponse>, -) => { - const pageNum = parseInt(req.query.page_num, 10); - const pageSize = parseInt(req.query.page_size, 10); - - const { id } = req.query; - - const breweryPosts = await getAllBreweryPostsByPostedById({ - pageNum, - pageSize, - postedById: id, - }); - - const breweryPostCount = await DBClient.instance.breweryPost.count({ - where: { postedBy: { id } }, - }); - - res.setHeader('X-Total-Count', breweryPostCount); - - res.status(200).json({ - message: `Brewery posts by user ${id} fetched successfully`, - statusCode: 200, - payload: breweryPosts, - success: true, - }); -}; +import { getBreweryPostsByUserId } from '@/controllers/posts/breweries'; +import { GetAllPostsByConnectedPostId } from '@/controllers/posts/types'; const router = createRouter< - GetBreweryPostsRequest, + GetAllPostsByConnectedPostId, NextApiResponse> >(); diff --git a/src/pages/api/users/[id]/profile/index.ts b/src/pages/api/users/[id]/profile/index.ts deleted file mode 100644 index 9c39a18..0000000 --- a/src/pages/api/users/[id]/profile/index.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; -import { singleUploadMiddleware } from '@/config/multer/uploadMiddleware'; -import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; -import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; - -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiResponse } from 'next'; -import { createRouter } from 'next-connect'; -import { z } from 'zod'; - -interface UpdateProfileRequest extends UserExtendedNextApiRequest { - file: Express.Multer.File; - body: { - bio: string; - }; -} - -interface UpdateUserProfileByIdParams { - id: string; - data: { - bio: string; - avatar: { - alt: string; - path: string; - caption: string; - }; - }; -} - -const updateUserProfileById = async ({ id, data }: UpdateUserProfileByIdParams) => { - const user: z.infer = await DBClient.instance.user.update({ - where: { id }, - data: { - bio: data.bio, - userAvatar: data.avatar - ? { - upsert: { - create: { - alt: data.avatar.alt, - path: data.avatar.path, - caption: data.avatar.caption, - }, - update: { - alt: data.avatar.alt, - path: data.avatar.path, - caption: data.avatar.caption, - }, - }, - } - : undefined, - }, - select: { - id: true, - username: true, - email: true, - bio: true, - userAvatar: true, - accountIsVerified: true, - createdAt: true, - firstName: true, - lastName: true, - updatedAt: true, - dateOfBirth: true, - role: true, - }, - }); - - return user; -}; - -const updateProfile = async (req: UpdateProfileRequest, res: NextApiResponse) => { - const { file, body, user } = req; - - await updateUserProfileById({ - id: user!.id, - data: { - bio: body.bio, - avatar: { alt: file.originalname, path: file.path, caption: '' }, - }, - }); - res.status(200).json({ - message: 'User confirmed successfully.', - statusCode: 200, - success: true, - }); -}; - -const router = createRouter< - UpdateProfileRequest, - NextApiResponse> ->(); - -router.put( - getCurrentUser, - - // @ts-expect-error - singleUploadMiddleware, - validateRequest({ bodySchema: z.object({ bio: z.string().max(1000) }) }), - - updateProfile, -); - -const handler = router.handler(); - -export default handler; -export const config = { api: { bodyParser: false } }; diff --git a/src/pages/api/users/[id]/profile/update-avatar.ts b/src/pages/api/users/[id]/profile/update-avatar.ts index be6846b..7cc918e 100644 --- a/src/pages/api/users/[id]/profile/update-avatar.ts +++ b/src/pages/api/users/[id]/profile/update-avatar.ts @@ -1,104 +1,13 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import { singleUploadMiddleware } from '@/config/multer/uploadMiddleware'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; - -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import { checkIfUserCanUpdateProfile, updateAvatar } from '@/controllers/users/profile'; +import { UpdateProfileRequest } from '@/controllers/users/profile/types'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; -import { NextHandler, createRouter } from 'next-connect'; +import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface UpdateProfileRequest extends UserExtendedNextApiRequest { - file: Express.Multer.File; - body: { - bio: string; - }; -} - -interface UpdateUserProfileByIdParams { - id: string; - data: { - avatar: { - alt: string; - path: string; - caption: string; - }; - }; -} - -const updateUserAvatarById = async ({ id, data }: UpdateUserProfileByIdParams) => { - const user: z.infer = await DBClient.instance.user.update({ - where: { id }, - data: { - userAvatar: data.avatar - ? { - upsert: { - create: { - alt: data.avatar.alt, - path: data.avatar.path, - caption: data.avatar.caption, - }, - update: { - alt: data.avatar.alt, - path: data.avatar.path, - caption: data.avatar.caption, - }, - }, - } - : undefined, - }, - select: { - id: true, - username: true, - email: true, - bio: true, - userAvatar: true, - accountIsVerified: true, - createdAt: true, - firstName: true, - lastName: true, - updatedAt: true, - dateOfBirth: true, - role: true, - }, - }); - - return user; -}; - -const checkIfUserCanUpdateProfile = async ( - req: UpdateProfileRequest, - res: NextApiResponse, - next: NextHandler, -) => { - const user = req.user!; - - if (user.id !== req.query.id) { - throw new ServerError('You can only update your own profile.', 403); - } - - await next(); -}; - -const updateProfile = async (req: UpdateProfileRequest, res: NextApiResponse) => { - const { file, user } = req; - - await updateUserAvatarById({ - id: user!.id, - data: { - avatar: { alt: file.originalname, path: file.path, caption: '' }, - }, - }); - res.status(200).json({ - message: 'User avatar updated successfully.', - statusCode: 200, - success: true, - }); -}; - const router = createRouter< UpdateProfileRequest, NextApiResponse> @@ -109,7 +18,7 @@ router.put( checkIfUserCanUpdateProfile, // @ts-expect-error singleUploadMiddleware, - updateProfile, + updateAvatar, ); const handler = router.handler(); diff --git a/src/pages/api/users/[id]/profile/update-bio.ts b/src/pages/api/users/[id]/profile/update-bio.ts deleted file mode 100644 index c75b91d..0000000 --- a/src/pages/api/users/[id]/profile/update-bio.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; - -import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; -import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import ServerError from '@/config/util/ServerError'; -import DBClient from '@/prisma/DBClient'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; - -import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiResponse } from 'next'; -import { NextHandler, createRouter } from 'next-connect'; -import { z } from 'zod'; - -interface UpdateProfileRequest extends UserExtendedNextApiRequest { - body: { - bio: string; - }; -} - -interface UpdateUserProfileByIdParams { - id: string; - data: { bio: string }; -} - -const updateUserProfileById = async ({ id, data }: UpdateUserProfileByIdParams) => { - const user: z.infer = await DBClient.instance.user.update({ - where: { id }, - data: { bio: data.bio }, - select: { - id: true, - username: true, - email: true, - bio: true, - userAvatar: true, - accountIsVerified: true, - createdAt: true, - firstName: true, - lastName: true, - updatedAt: true, - dateOfBirth: true, - role: true, - }, - }); - - return user; -}; - -const updateProfile = async (req: UpdateProfileRequest, res: NextApiResponse) => { - const user = req.user!; - const { body } = req; - - await updateUserProfileById({ id: user!.id, data: { bio: body.bio } }); - - res.status(200).json({ - message: 'Profile updated successfully.', - statusCode: 200, - success: true, - }); -}; - -const checkIfUserCanUpdateProfile = async ( - req: UpdateProfileRequest, - res: NextApiResponse, - next: NextHandler, -) => { - const user = req.user!; - - if (user.id !== req.query.id) { - throw new ServerError('You can only update your own profile.', 403); - } - - await next(); -}; - -const router = createRouter< - UpdateProfileRequest, - NextApiResponse> ->(); - -router.put( - getCurrentUser, - checkIfUserCanUpdateProfile, - validateRequest({ bodySchema: z.object({ bio: z.string().max(1000) }) }), - updateProfile, -); - -const handler = router.handler(); - -export default handler; diff --git a/src/pages/api/users/[id]/profile/update-profile.ts b/src/pages/api/users/[id]/profile/update-profile.ts new file mode 100644 index 0000000..65c0b68 --- /dev/null +++ b/src/pages/api/users/[id]/profile/update-profile.ts @@ -0,0 +1,25 @@ +import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; +import validateRequest from '@/config/nextConnect/middleware/validateRequest'; +import { checkIfUserCanUpdateProfile, updateProfile } from '@/controllers/users/profile'; +import { UpdateProfileRequest } from '@/controllers/users/profile/types'; + +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiResponse } from 'next'; +import { createRouter } from 'next-connect'; +import { z } from 'zod'; + +const router = createRouter< + UpdateProfileRequest, + NextApiResponse> +>(); + +router.put( + getCurrentUser, + checkIfUserCanUpdateProfile, + validateRequest({ bodySchema: z.object({ bio: z.string().max(1000) }) }), + updateProfile, +); + +const handler = router.handler(); + +export default handler; diff --git a/src/pages/api/users/check-email.ts b/src/pages/api/users/check-email.ts index 4d03c37..b48a3b7 100644 --- a/src/pages/api/users/check-email.ts +++ b/src/pages/api/users/check-email.ts @@ -1,38 +1,18 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import findUserByEmail from '@/services/User/findUserByEmail'; - -const CheckEmailRequestQuerySchema = z.object({ - email: z.string(), -}); - -interface CheckEmailRequestSchema extends NextApiRequest { - query: z.infer; -} +import { CheckEmailRequest } from '@/controllers/users/auth/types'; +import { checkEmail } from '@/controllers/users/auth'; const router = createRouter< - CheckEmailRequestSchema, + CheckEmailRequest, NextApiResponse> >(); -const checkEmail = async (req: NextApiRequest, res: NextApiResponse) => { - const { email: emailToCheck } = req.query; - - const email = await findUserByEmail(emailToCheck as string); - - res.json({ - success: true, - payload: { emailIsTaken: !!email }, - statusCode: 200, - message: 'Getting email availability.', - }); -}; - router.get( validateRequest({ querySchema: z.object({ email: z.string().email() }) }), checkEmail, diff --git a/src/pages/api/users/check-username.ts b/src/pages/api/users/check-username.ts index ebf4f60..5c2e79b 100644 --- a/src/pages/api/users/check-username.ts +++ b/src/pages/api/users/check-username.ts @@ -1,38 +1,19 @@ import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import findUserByUsername from '@/services/User/findUserByUsername'; -const CheckUsernameRequestQuerySchema = z.object({ - username: z.string(), -}); - -interface CheckUsernameRequestSchema extends NextApiRequest { - query: z.infer; -} +import { CheckUsernameRequest } from '@/controllers/users/auth/types'; +import { checkUsername } from '@/controllers/users/auth'; const router = createRouter< - CheckUsernameRequestSchema, + CheckUsernameRequest, NextApiResponse> >(); -const checkUsername = async (req: NextApiRequest, res: NextApiResponse) => { - const { username: usernameToCheck } = req.query; - - const user = await findUserByUsername(usernameToCheck as string); - - res.json({ - success: true, - payload: { usernameIsTaken: !!user }, - statusCode: 200, - message: 'Getting username availability.', - }); -}; - router.get( validateRequest({ querySchema: z.object({ username: z.string() }) }), checkUsername, diff --git a/src/pages/api/users/confirm.ts b/src/pages/api/users/confirm.ts index 9186631..1c81926 100644 --- a/src/pages/api/users/confirm.ts +++ b/src/pages/api/users/confirm.ts @@ -1,53 +1,24 @@ -import { UserExtendedNextApiRequest } from '@/config/auth/types'; -import { verifyConfirmationToken } from '@/config/jwt'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import ServerError from '@/config/util/ServerError'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import updateUserToBeConfirmedById from '@/services/User/updateUserToBeConfirmedById'; -const ConfirmUserValidationSchema = z.object({ token: z.string() }); - -interface ConfirmUserRequest extends UserExtendedNextApiRequest { - query: z.infer; -} - -const confirmUser = async (req: ConfirmUserRequest, res: NextApiResponse) => { - const { token } = req.query; - - const user = req.user!; - const { id } = await verifyConfirmationToken(token); - - if (user.accountIsVerified) { - throw new ServerError('Your account is already verified.', 400); - } - - if (user.id !== id) { - throw new ServerError('Could not confirm user.', 401); - } - - await updateUserToBeConfirmedById(id); - - res.status(200).json({ - message: 'User confirmed successfully.', - statusCode: 200, - success: true, - }); -}; +import { TokenValidationRequest } from '@/controllers/users/auth/types'; +import { confirmUser } from '@/controllers/users/auth'; +import TokenValidationSchema from '@/services/users/auth/schema/TokenValidationSchema'; const router = createRouter< - ConfirmUserRequest, + TokenValidationRequest, NextApiResponse> >(); router.get( getCurrentUser, - validateRequest({ querySchema: ConfirmUserValidationSchema }), + validateRequest({ querySchema: TokenValidationSchema }), confirmUser, ); diff --git a/src/pages/api/users/current.ts b/src/pages/api/users/current.ts index aff2a35..ba7fd7c 100644 --- a/src/pages/api/users/current.ts +++ b/src/pages/api/users/current.ts @@ -5,16 +5,7 @@ import { NextApiResponse } from 'next'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import { createRouter } from 'next-connect'; import { z } from 'zod'; - -const sendCurrentUser = async (req: UserExtendedNextApiRequest, res: NextApiResponse) => { - const { user } = req; - res.status(200).json({ - message: `Currently logged in as ${user!.username}`, - statusCode: 200, - success: true, - payload: user, - }); -}; +import { sendCurrentUser } from '@/controllers/users/auth'; const router = createRouter< UserExtendedNextApiRequest, diff --git a/src/pages/api/users/edit-password.ts b/src/pages/api/users/edit-password.ts index d62f21c..8a651d8 100644 --- a/src/pages/api/users/edit-password.ts +++ b/src/pages/api/users/edit-password.ts @@ -1,38 +1,15 @@ -import { hashPassword } from '@/config/auth/passwordFns'; -import { UserExtendedNextApiRequest } from '@/config/auth/types'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import DBClient from '@/prisma/DBClient'; -import { UpdatePasswordSchema } from '@/services/User/schema/CreateUserValidationSchemas'; +import { updatePassword } from '@/controllers/users/auth'; +import { UpdatePasswordRequest } from '@/controllers/users/auth/types'; +import { UpdatePasswordSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; + import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -interface UpdatePasswordRequest extends UserExtendedNextApiRequest { - body: z.infer; -} - -const updatePassword = async ( - req: UpdatePasswordRequest, - res: NextApiResponse>, -) => { - const { password } = req.body; - const hash = await hashPassword(password); - - const user = req.user!; - await DBClient.instance.user.update({ - data: { hash }, - where: { id: user.id }, - }); - - res.json({ - message: 'Updated user password.', - statusCode: 200, - success: true, - }); -}; const router = createRouter< UpdatePasswordRequest, NextApiResponse> diff --git a/src/pages/api/users/forgot-password.ts b/src/pages/api/users/forgot-password.ts index 156a5ec..a1789aa 100644 --- a/src/pages/api/users/forgot-password.ts +++ b/src/pages/api/users/forgot-password.ts @@ -1,65 +1,12 @@ -import { generateResetPasswordToken } from '@/config/jwt'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import sendEmail from '@/config/sparkpost/sendEmail'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -import DBClient from '@/prisma/DBClient'; -import { render } from '@react-email/render'; -import ForgotEmail from '@/emails/ForgotEmail'; -import { ReactElement } from 'react'; -import { User } from '@prisma/client'; -import { BASE_URL } from '@/config/env'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; - -interface ResetPasswordRequest extends NextApiRequest { - body: { email: string }; -} - -const sendResetPasswordEmail = async (user: User) => { - const token = generateResetPasswordToken({ id: user.id, username: user.username }); - - const url = `${BASE_URL}/users/reset-password?token=${token}`; - - const component = ForgotEmail({ name: user.username, url })! as ReactElement< - unknown, - string - >; - - const html = render(component); - const text = render(component, { plainText: true }); - - await sendEmail({ - address: user.email, - subject: 'Reset Password', - html, - text, - }); -}; - -const forgetPassword = async ( - req: ResetPasswordRequest, - res: NextApiResponse>, -) => { - const { email } = req.body; - - const user = await DBClient.instance.user.findUnique({ - where: { email }, - }); - - if (user) { - await sendResetPasswordEmail(user); - } - - res.status(200).json({ - statusCode: 200, - success: true, - message: - 'If an account with that email exists, we have sent you an email to reset your password.', - }); -}; +import { resetPassword } from '@/controllers/users/auth'; +import { ResetPasswordRequest } from '@/controllers/users/auth/types'; const router = createRouter< ResetPasswordRequest, @@ -68,7 +15,7 @@ const router = createRouter< router.post( validateRequest({ bodySchema: z.object({ email: z.string().email() }) }), - forgetPassword, + resetPassword, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/users/login.ts b/src/pages/api/users/login.ts index 525e337..c001094 100644 --- a/src/pages/api/users/login.ts +++ b/src/pages/api/users/login.ts @@ -1,15 +1,13 @@ import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import passport from 'passport'; -import { createRouter, expressWrapper } from 'next-connect'; -import localStrat from '@/config/auth/localStrat'; -import { setLoginSession } from '@/config/auth/session'; +import { createRouter } from 'next-connect'; import { NextApiResponse } from 'next'; import { z } from 'zod'; -import LoginValidationSchema from '@/services/User/schema/LoginValidationSchema'; + import { UserExtendedNextApiRequest } from '@/config/auth/types'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import { authenticateUser, loginUser } from '@/controllers/users/auth'; +import LoginValidationSchema from '@/services/users/auth/schema/LoginValidationSchema'; const router = createRouter< UserExtendedNextApiRequest, @@ -18,33 +16,8 @@ const router = createRouter< router.post( validateRequest({ bodySchema: LoginValidationSchema }), - expressWrapper(async (req, res, next) => { - passport.initialize(); - passport.use(localStrat); - passport.authenticate( - 'local', - { session: false }, - (error: unknown, token: z.infer) => { - if (error) { - next(error); - return; - } - req.user = token; - next(); - }, - )(req, res, next); - }), - async (req, res) => { - const user = req.user!; - await setLoginSession(res, user); - - res.status(200).json({ - message: 'Login successful.', - payload: user, - statusCode: 200, - success: true, - }); - }, + authenticateUser, + loginUser, ); const handler = router.handler(NextConnectOptions); diff --git a/src/pages/api/users/logout.ts b/src/pages/api/users/logout.ts index 3b33d6e..0e23889 100644 --- a/src/pages/api/users/logout.ts +++ b/src/pages/api/users/logout.ts @@ -1,28 +1,16 @@ -import { getLoginSession } from '@/config/auth/session'; -import { removeTokenCookie } from '@/config/auth/cookie'; import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { NextApiRequest, NextApiResponse } from 'next'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -import ServerError from '@/config/util/ServerError'; +import { logoutUser } from '@/controllers/users/auth'; const router = createRouter< NextApiRequest, NextApiResponse> >(); -router.all(async (req, res) => { - const session = await getLoginSession(req); - - if (!session) { - throw new ServerError('You are not logged in.', 400); - } - - removeTokenCookie(res); - - res.redirect('/'); -}); +router.all(logoutUser); const handler = router.handler(NextConnectOptions); export default handler; diff --git a/src/pages/api/users/register.ts b/src/pages/api/users/register.ts index 51b93a6..78e1c79 100644 --- a/src/pages/api/users/register.ts +++ b/src/pages/api/users/register.ts @@ -1,65 +1,25 @@ -import { setLoginSession } from '@/config/auth/session'; -import { NextApiRequest, NextApiResponse } from 'next'; +import { NextApiResponse } from 'next'; import { z } from 'zod'; -import ServerError from '@/config/util/ServerError'; import { createRouter } from 'next-connect'; -import createNewUser from '@/services/User/createNewUser'; -import { CreateUserValidationSchema } from '@/services/User/schema/CreateUserValidationSchemas'; + import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; -import findUserByUsername from '@/services/User/findUserByUsername'; -import findUserByEmail from '@/services/User/findUserByEmail'; import validateRequest from '@/config/nextConnect/middleware/validateRequest'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; - -import sendConfirmationEmail from '@/services/User/sendConfirmationEmail'; - -interface RegisterUserRequest extends NextApiRequest { - body: z.infer; -} - -const registerUser = async (req: RegisterUserRequest, res: NextApiResponse) => { - const [usernameTaken, emailTaken] = await Promise.all([ - findUserByUsername(req.body.username), - findUserByEmail(req.body.email), - ]); - - if (usernameTaken) { - throw new ServerError( - 'Could not register a user with that username as it is already taken.', - 409, - ); - } - - if (emailTaken) { - throw new ServerError( - 'Could not register a user with that email as it is already taken.', - 409, - ); - } - - const user = await createNewUser(req.body); - - await setLoginSession(res, { - id: user.id, - username: user.username, - }); - - await sendConfirmationEmail(user); - - res.status(201).json({ - success: true, - statusCode: 201, - message: 'User registered successfully.', - payload: user, - }); -}; +import { registerUser } from '@/controllers/users/auth'; +import { RegisterUserRequest } from '@/controllers/users/auth/types'; +import { CreateUserValidationSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; const router = createRouter< RegisterUserRequest, NextApiResponse> >(); -router.post(validateRequest({ bodySchema: CreateUserValidationSchema }), registerUser); +router.post( + validateRequest({ + bodySchema: CreateUserValidationSchema, + }), + registerUser, +); const handler = router.handler(NextConnectOptions); export default handler; diff --git a/src/pages/api/users/resend-confirmation.ts b/src/pages/api/users/resend-confirmation.ts index d422c29..56c92ab 100644 --- a/src/pages/api/users/resend-confirmation.ts +++ b/src/pages/api/users/resend-confirmation.ts @@ -5,21 +5,7 @@ import { NextApiResponse } from 'next'; import getCurrentUser from '@/config/nextConnect/middleware/getCurrentUser'; import { createRouter } from 'next-connect'; import { z } from 'zod'; -import sendConfirmationEmail from '@/services/User/sendConfirmationEmail'; - -const resendConfirmation = async ( - req: UserExtendedNextApiRequest, - res: NextApiResponse, -) => { - const user = req.user!; - - await sendConfirmationEmail(user); - res.status(200).json({ - message: `Resent the confirmation email for ${user.username}.`, - statusCode: 200, - success: true, - }); -}; +import { resendConfirmation } from '@/controllers/users/auth'; const router = createRouter< UserExtendedNextApiRequest, diff --git a/src/pages/beers/[id]/edit.tsx b/src/pages/beers/[id]/edit.tsx index c153afa..58b73d6 100644 --- a/src/pages/beers/[id]/edit.tsx +++ b/src/pages/beers/[id]/edit.tsx @@ -3,12 +3,12 @@ import Head from 'next/head'; import React from 'react'; import withPageAuthRequired from '@/util/withPageAuthRequired'; -import getBeerPostById from '@/services/BeerPost/getBeerPostById'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import EditBeerPostForm from '@/components/EditBeerPostForm'; import FormPageLayout from '@/components/ui/forms/FormPageLayout'; import { BiBeer } from 'react-icons/bi'; import { z } from 'zod'; +import { getBeerPostById } from '@/services/posts/beer-post'; interface EditPageProps { beerPost: z.infer; @@ -37,7 +37,6 @@ const EditBeerPostPage: NextPage = ({ beerPost }) => { ibu: beerPost.ibu, description: beerPost.description, id: beerPost.id, - styleId: beerPost.style.id, }} /> @@ -50,7 +49,7 @@ export default EditBeerPostPage; export const getServerSideProps = withPageAuthRequired( async (context, session) => { const beerPostId = context.params?.id as string; - const beerPost = await getBeerPostById(beerPostId); + const beerPost = await getBeerPostById({ beerPostId }); const { id: userId } = session; if (!beerPost) { diff --git a/src/pages/beers/[id]/index.tsx b/src/pages/beers/[id]/index.tsx index 3bf7bb3..51b03b0 100644 --- a/src/pages/beers/[id]/index.tsx +++ b/src/pages/beers/[id]/index.tsx @@ -1,8 +1,7 @@ import { NextPage, GetServerSideProps } from 'next'; import Head from 'next/head'; -import getBeerPostById from '@/services/BeerPost/getBeerPostById'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import { z } from 'zod'; @@ -12,6 +11,7 @@ import useMediaQuery from '@/hooks/utilities/useMediaQuery'; import { Tab } from '@headlessui/react'; import dynamic from 'next/dynamic'; import { CldImage } from 'next-cloudinary'; +import { getBeerPostById } from '@/services/posts/beer-post'; const [BeerInfoHeader, BeerPostCommentsSection, BeerRecommendations] = [ dynamic(() => import('@/components/BeerById/BeerInfoHeader')), @@ -100,7 +100,9 @@ const BeerByIdPage: NextPage = ({ beerPost }) => { }; export const getServerSideProps: GetServerSideProps = async (context) => { - const beerPost = await getBeerPostById(context.params!.id! as string); + const beerPost = await getBeerPostById({ + beerPostId: context.params?.id as string, + }); if (!beerPost) { return { notFound: true }; diff --git a/src/pages/beers/styles/[id]/index.tsx b/src/pages/beers/styles/[id]/index.tsx index ea81bb0..58d96b4 100644 --- a/src/pages/beers/styles/[id]/index.tsx +++ b/src/pages/beers/styles/[id]/index.tsx @@ -5,11 +5,12 @@ import { z } from 'zod'; import useMediaQuery from '@/hooks/utilities/useMediaQuery'; import { Tab } from '@headlessui/react'; -import getBeerStyleById from '@/services/BeerStyles/getBeerStyleById'; + import BeerStyleHeader from '@/components/BeerStyleById/BeerStyleHeader'; -import BeerStyleQueryResult from '@/services/BeerStyles/schema/BeerStyleQueryResult'; +import BeerStyleQueryResult from '@/services/posts/beer-style-post/schema/BeerStyleQueryResult'; import BeerStyleCommentSection from '@/components/BeerStyleById/BeerStyleCommentSection'; import BeerStyleBeerSection from '@/components/BeerStyleById/BeerStyleBeerSection'; +import { getBeerStyleByIdService } from '@/services/posts/beer-style-post'; interface BeerStylePageProps { beerStyle: z.infer; @@ -69,7 +70,7 @@ export default BeerStyleByIdPage; export const getServerSideProps: GetServerSideProps = async ({ params }) => { const id = params!.id as string; - const beerStyle = await getBeerStyleById(id); + const beerStyle = await getBeerStyleByIdService({ beerStyleId: id }); if (!beerStyle) { return { notFound: true }; } diff --git a/src/pages/breweries/[id]/beers/create.tsx b/src/pages/breweries/[id]/beers/create.tsx index 4374e24..e1f7d73 100644 --- a/src/pages/breweries/[id]/beers/create.tsx +++ b/src/pages/breweries/[id]/beers/create.tsx @@ -3,12 +3,12 @@ import FormPageLayout from '@/components/ui/forms/FormPageLayout'; import withPageAuthRequired from '@/util/withPageAuthRequired'; import DBClient from '@/prisma/DBClient'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import { BeerStyle } from '@prisma/client'; import { NextPage } from 'next'; import { BiBeer } from 'react-icons/bi'; import { z } from 'zod'; -import getBreweryPostById from '@/services/BreweryPost/getBreweryPostById'; +import { getBreweryPostByIdService } from '@/services/posts/brewery-post'; interface CreateBeerPageProps { brewery: z.infer; @@ -32,7 +32,7 @@ export const getServerSideProps = withPageAuthRequired( async (context) => { const id = context.params?.id as string; - const breweryPost = await getBreweryPostById(id); + const breweryPost = await getBreweryPostByIdService({ breweryPostId: id }); const beerStyles = await DBClient.instance.beerStyle.findMany(); return { diff --git a/src/pages/breweries/[id]/edit.tsx b/src/pages/breweries/[id]/edit.tsx index 171d980..5f2c493 100644 --- a/src/pages/breweries/[id]/edit.tsx +++ b/src/pages/breweries/[id]/edit.tsx @@ -5,9 +5,10 @@ import FormPageLayout from '@/components/ui/forms/FormPageLayout'; import FormSegment from '@/components/ui/forms/FormSegment'; import FormTextArea from '@/components/ui/forms/FormTextArea'; import FormTextInput from '@/components/ui/forms/FormTextInput'; -import getBreweryPostById from '@/services/BreweryPost/getBreweryPostById'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; -import EditBreweryPostValidationSchema from '@/services/BreweryPost/schema/EditBreweryPostValidationSchema'; +import { getBreweryPostByIdService } from '@/services/posts/brewery-post'; + +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; +import EditBreweryPostValidationSchema from '@/services/posts/brewery-post/schema/EditBreweryPostValidationSchema'; import withPageAuthRequired from '@/util/withPageAuthRequired'; import { zodResolver } from '@hookform/resolvers/zod'; import { NextPage } from 'next'; @@ -143,7 +144,7 @@ export default EditBreweryPostPage; export const getServerSideProps = withPageAuthRequired( async (context, session) => { const breweryPostId = context.params?.id as string; - const breweryPost = await getBreweryPostById(breweryPostId); + const breweryPost = await getBreweryPostByIdService({ breweryPostId }); const { id: userId } = session; diff --git a/src/pages/breweries/[id]/index.tsx b/src/pages/breweries/[id]/index.tsx index 4665c2b..a081fe4 100644 --- a/src/pages/breweries/[id]/index.tsx +++ b/src/pages/breweries/[id]/index.tsx @@ -1,5 +1,4 @@ -import getBreweryPostById from '@/services/BreweryPost/getBreweryPostById'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import { GetServerSideProps, NextPage } from 'next'; import { z } from 'zod'; @@ -13,6 +12,7 @@ import { Tab } from '@headlessui/react'; import dynamic from 'next/dynamic'; import { MAPBOX_ACCESS_TOKEN } from '@/config/env'; import { CldImage } from 'next-cloudinary'; +import { getBreweryPostByIdService } from '@/services/posts/brewery-post'; const [BreweryInfoHeader, BreweryBeersSection, BreweryCommentsSection, BreweryPostMap] = [ dynamic(() => import('@/components/BreweryById/BreweryInfoHeader')), @@ -114,7 +114,9 @@ const BreweryByIdPage: NextPage = ({ breweryPost, mapboxToken export const getServerSideProps: GetServerSideProps = async ( context, ) => { - const breweryPost = await getBreweryPostById(context.params!.id! as string); + const breweryPost = await getBreweryPostByIdService({ + breweryPostId: context.params?.id as string, + }); const mapboxToken = MAPBOX_ACCESS_TOKEN; return !breweryPost diff --git a/src/pages/breweries/create.tsx b/src/pages/breweries/create.tsx index d8cd0ec..6b7a988 100644 --- a/src/pages/breweries/create.tsx +++ b/src/pages/breweries/create.tsx @@ -1,12 +1,17 @@ import FormPageLayout from '@/components/ui/forms/FormPageLayout'; import withPageAuthRequired from '@/util/withPageAuthRequired'; -import { GetServerSideProps, NextPage } from 'next'; +import { NextPage } from 'next'; import Head from 'next/head'; import { FaBeer } from 'react-icons/fa'; import CreateBreweryPostForm from '@/components/BreweryPost/CreateBreweryPostForm'; +import { MAPBOX_ACCESS_TOKEN } from '@/config/env'; -const CreateBreweryPage: NextPage = () => { +interface CreateBreweryPageProps { + mapboxAccessToken: string; +} + +const CreateBreweryPage: NextPage = ({ mapboxAccessToken }) => { return ( <> @@ -20,7 +25,7 @@ const CreateBreweryPage: NextPage = () => { headingText="Create Brewery" headingIcon={FaBeer} > - + @@ -30,4 +35,6 @@ const CreateBreweryPage: NextPage = () => { export default CreateBreweryPage; -export const getServerSideProps: GetServerSideProps = withPageAuthRequired(); +export const getServerSideProps = withPageAuthRequired( + async () => ({ props: { mapboxAccessToken: MAPBOX_ACCESS_TOKEN } }), +); diff --git a/src/pages/breweries/index.tsx b/src/pages/breweries/index.tsx index 2b6dae2..3accbaf 100644 --- a/src/pages/breweries/index.tsx +++ b/src/pages/breweries/index.tsx @@ -3,7 +3,7 @@ import LoadingCard from '@/components/ui/LoadingCard'; import Spinner from '@/components/ui/Spinner'; import UserContext from '@/contexts/UserContext'; import useBreweryPosts from '@/hooks/data-fetching/brewery-posts/useBreweryPosts'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import { NextPage } from 'next'; import Head from 'next/head'; import { useContext, MutableRefObject, useRef } from 'react'; diff --git a/src/pages/breweries/map.tsx b/src/pages/breweries/map.tsx index 61b0c20..f82b90f 100644 --- a/src/pages/breweries/map.tsx +++ b/src/pages/breweries/map.tsx @@ -7,7 +7,7 @@ import LocationMarker from '@/components/ui/LocationMarker'; import Link from 'next/link'; import Head from 'next/head'; import useGeolocation from '@/hooks/utilities/useGeolocation'; -import BreweryPostMapQueryResult from '@/services/BreweryPost/schema/BreweryPostMapQueryResult'; +import BreweryPostMapQueryResult from '@/services/posts/brewery-post/schema/BreweryPostMapQueryResult'; import { z } from 'zod'; import useBreweryMapPagePosts from '@/hooks/data-fetching/brewery-posts/useBreweryMapPagePosts'; import ControlPanel from '@/components/ui/maps/ControlPanel'; diff --git a/src/pages/users/[id].tsx b/src/pages/users/[id].tsx index 66c2d5d..170a8ff 100644 --- a/src/pages/users/[id].tsx +++ b/src/pages/users/[id].tsx @@ -1,12 +1,13 @@ import useMediaQuery from '@/hooks/utilities/useMediaQuery'; -import findUserById from '@/services/User/findUserById'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; + +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import Head from 'next/head'; import { FC } from 'react'; import { z } from 'zod'; import withPageAuthRequired from '@/util/withPageAuthRequired'; import UserHeader from '@/components/UserPage/UserHeader'; +import { findUserByIdService } from '@/services/users/auth'; interface UserInfoPageProps { user: z.infer; @@ -39,7 +40,7 @@ export default UserInfoPage; export const getServerSideProps = withPageAuthRequired( async (context) => { const { id } = context.params!; - const user = await findUserById(id as string); + const user = await findUserByIdService({ userId: id as string }); return user ? { props: { user: JSON.parse(JSON.stringify(user)) } } : { notFound: true }; diff --git a/src/pages/users/account/edit-profile.tsx b/src/pages/users/account/edit-profile.tsx index bb04dad..70703c5 100644 --- a/src/pages/users/account/edit-profile.tsx +++ b/src/pages/users/account/edit-profile.tsx @@ -21,9 +21,9 @@ import UpdateProfileForm from '@/components/Account/UpdateProfileForm'; import useGetUsersFollowedByUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowedByUser'; import useGetUsersFollowingUser from '@/hooks/data-fetching/user-follows/useGetUsersFollowingUser'; -import UpdateProfileSchema from '@/services/User/schema/UpdateProfileSchema'; +import UpdateProfileSchema from '@/services/users/auth/schema/UpdateProfileSchema'; import sendUpdateUserAvatarRequest from '@/requests/Account/sendUpdateUserAvatarRequest'; -import sendUpdateUserProfileRequest from '@/requests/Account/sendUpdateUserProfileRequest.ts'; +import sendUpdateUserProfileRequest from '@/requests/Account/sendUpdateUserProfileRequest'; import Spinner from '@/components/ui/Spinner'; const ProfilePage: NextPage = () => { diff --git a/src/pages/users/forgot-password.tsx b/src/pages/users/forgot-password.tsx index 1307b01..a68adc3 100644 --- a/src/pages/users/forgot-password.tsx +++ b/src/pages/users/forgot-password.tsx @@ -4,7 +4,7 @@ import FormInfo from '@/components/ui/forms/FormInfo'; import FormLabel from '@/components/ui/forms/FormLabel'; import FormSegment from '@/components/ui/forms/FormSegment'; import FormTextInput from '@/components/ui/forms/FormTextInput'; -import { BaseCreateUserSchema } from '@/services/User/schema/CreateUserValidationSchemas'; +import { BaseCreateUserSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import createErrorToast from '@/util/createErrorToast'; import { zodResolver } from '@hookform/resolvers/zod'; import { NextPage } from 'next'; diff --git a/src/pages/users/reset-password.tsx b/src/pages/users/reset-password.tsx index f51ae9f..ec33445 100644 --- a/src/pages/users/reset-password.tsx +++ b/src/pages/users/reset-password.tsx @@ -1,7 +1,7 @@ import { setLoginSession } from '@/config/auth/session'; import { verifyResetPasswordToken } from '@/config/jwt'; import ServerError from '@/config/util/ServerError'; -import findUserById from '@/services/User/findUserById'; +import { findUserByIdService } from '@/services/users/auth'; import { GetServerSideProps, NextApiResponse, NextPage } from 'next'; @@ -29,14 +29,14 @@ export const getServerSideProps: GetServerSideProps = async (context) => { const { id } = await verifyResetPasswordToken(token as string); - const user = await findUserById(id); + const user = await findUserByIdService({ userId: id as string }); if (!user) { throw new ServerError('User not found', 404); } await setLoginSession(context.res as NextApiResponse, user); - return { redirect: { destination: '/account', permanent: false } }; + return { redirect: { destination: '/users/account', permanent: false } }; } catch (error) { return { props: {} }; } diff --git a/src/prisma/seed/clear/clearCloudinaryStorage.ts b/src/prisma/seed/clear/clearCloudinaryStorage.ts new file mode 100644 index 0000000..e280644 --- /dev/null +++ b/src/prisma/seed/clear/clearCloudinaryStorage.ts @@ -0,0 +1,7 @@ +import { cloudinary } from '../../../config/cloudinary'; + +const clearCloudinaryStorage = async () => { + await cloudinary.api.delete_resources_by_prefix('biergarten-dev/'); +}; + +export default clearCloudinaryStorage; diff --git a/src/prisma/seed/clear/index.ts b/src/prisma/seed/clear/index.ts index 5410ec0..e9b0d50 100644 --- a/src/prisma/seed/clear/index.ts +++ b/src/prisma/seed/clear/index.ts @@ -1,7 +1,16 @@ import logger from '../../../config/pino/logger'; +import clearCloudinaryStorage from './clearCloudinaryStorage'; import clearDatabase from './clearDatabase'; -clearDatabase().then(() => { - logger.info('Database cleared'); - process.exit(0); -}); +(async () => { + await clearDatabase(); + await clearCloudinaryStorage(); +})() + .then(() => { + logger.info('Successfully cleared database and cloudinary storage.'); + process.exit(0); + }) + .catch((err) => { + logger.error(err); + process.exit(1); + }); diff --git a/src/prisma/seed/create/createAdminUser.ts b/src/prisma/seed/create/createAdminUser.ts index 07b3d96..803f5f7 100644 --- a/src/prisma/seed/create/createAdminUser.ts +++ b/src/prisma/seed/create/createAdminUser.ts @@ -4,7 +4,7 @@ import { hashPassword } from '../../../config/auth/passwordFns'; import { ADMIN_PASSWORD } from '../../../config/env'; import DBClient from '../../DBClient'; -import GetUserSchema from '../../../services/User/schema/GetUserSchema'; +import GetUserSchema from '../../../services/users/auth/schema/GetUserSchema'; import imageUrls from '../util/imageUrls'; const createAdminUser = async () => { diff --git a/src/prisma/seed/index.ts b/src/prisma/seed/index.ts index 059d2c6..3eeccfd 100644 --- a/src/prisma/seed/index.ts +++ b/src/prisma/seed/index.ts @@ -20,6 +20,7 @@ import createNewBeerStyleComments from './create/createNewBeerStyleComments'; import createNewBeerStyleLikes from './create/createNewBeerStyleLikes'; import createNewUserAvatars from './create/createNewUserAvatars'; import createNewUserFollows from './create/createNewUserFollows'; +import clearCloudinaryStorage from './clear/clearCloudinaryStorage'; (async () => { try { @@ -27,6 +28,7 @@ import createNewUserFollows from './create/createNewUserFollows'; logger.info('Clearing database.'); await clearDatabase(); + await clearCloudinaryStorage(); logger.info('Database cleared successfully, preparing to seed.'); await createAdminUser(); diff --git a/src/requests/Account/sendUpdateUserAvatarRequest.ts b/src/requests/Account/sendUpdateUserAvatarRequest.ts index 8ce2c6c..298fce0 100644 --- a/src/requests/Account/sendUpdateUserAvatarRequest.ts +++ b/src/requests/Account/sendUpdateUserAvatarRequest.ts @@ -8,9 +8,9 @@ const sendUpdateUserAvatarRequest = async ({ userId, }: UpdateProfileRequestParams) => { const formData = new FormData(); - formData.append('file', file); + formData.append('image', file); - const response = await fetch(`/api/users/${userId}/`, { + const response = await fetch(`/api/users/${userId}/profile/update-avatar`, { method: 'PUT', body: formData, }); diff --git a/src/requests/Account/sendUpdateUserProfileRequest.ts.ts b/src/requests/Account/sendUpdateUserProfileRequest.ts similarity index 85% rename from src/requests/Account/sendUpdateUserProfileRequest.ts.ts rename to src/requests/Account/sendUpdateUserProfileRequest.ts index 8d421d6..c38ed00 100644 --- a/src/requests/Account/sendUpdateUserProfileRequest.ts.ts +++ b/src/requests/Account/sendUpdateUserProfileRequest.ts @@ -1,4 +1,4 @@ -import UpdateProfileSchema from '@/services/User/schema/UpdateProfileSchema'; +import UpdateProfileSchema from '@/services/users/auth/schema/UpdateProfileSchema'; import { z } from 'zod'; interface UpdateProfileRequestParams { @@ -10,7 +10,7 @@ const sendUpdateUserProfileRequest = async ({ bio, userId, }: UpdateProfileRequestParams) => { - const response = await fetch(`/api/users/${userId}/profile/update-bio`, { + const response = await fetch(`/api/users/${userId}/profile/update-profile`, { method: 'PUT', body: JSON.stringify({ bio }), headers: { 'Content-Type': 'application/json' }, diff --git a/src/requests/BeerImage/sendUploadBeerImageRequest.ts b/src/requests/BeerImage/sendUploadBeerImageRequest.ts index de1906c..1cf8637 100644 --- a/src/requests/BeerImage/sendUploadBeerImageRequest.ts +++ b/src/requests/BeerImage/sendUploadBeerImageRequest.ts @@ -1,4 +1,4 @@ -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { z } from 'zod'; diff --git a/src/requests/BeerPost/sendCreateBeerPostRequest.ts b/src/requests/BeerPost/sendCreateBeerPostRequest.ts index 960e75a..7e36b1f 100644 --- a/src/requests/BeerPost/sendCreateBeerPostRequest.ts +++ b/src/requests/BeerPost/sendCreateBeerPostRequest.ts @@ -1,5 +1,5 @@ -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; -import CreateBeerPostValidationSchema from '@/services/BeerPost/schema/CreateBeerPostValidationSchema'; +import BeerPostQueryResult from '@/services/posts/beer-post/schema/BeerPostQueryResult'; +import CreateBeerPostValidationSchema from '@/services/posts/beer-post/schema/CreateBeerPostValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { z } from 'zod'; diff --git a/src/requests/BeerPost/sendEditBeerPostRequest.ts b/src/requests/BeerPost/sendEditBeerPostRequest.ts index f85d615..de58f1d 100644 --- a/src/requests/BeerPost/sendEditBeerPostRequest.ts +++ b/src/requests/BeerPost/sendEditBeerPostRequest.ts @@ -1,4 +1,4 @@ -import EditBeerPostValidationSchema from '@/services/BeerPost/schema/EditBeerPostValidationSchema'; +import EditBeerPostValidationSchema from '@/services/posts/beer-post/schema/EditBeerPostValidationSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { z } from 'zod'; @@ -20,12 +20,11 @@ const sendEditBeerPostRequest = async ({ ibu, id, name, - styleId, }: z.infer) => { const response = await fetch(`/api/beers/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ abv, description, ibu, name, styleId, id }), + body: JSON.stringify({ abv, description, ibu, name, id }), }); if (!response.ok) { diff --git a/src/requests/BreweryImage/sendUploadBreweryImageRequest.ts b/src/requests/BreweryImage/sendUploadBreweryImageRequest.ts index 7875d68..be6ec7a 100644 --- a/src/requests/BreweryImage/sendUploadBreweryImageRequest.ts +++ b/src/requests/BreweryImage/sendUploadBreweryImageRequest.ts @@ -1,4 +1,4 @@ -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; import { z } from 'zod'; interface SendUploadBeerImagesRequestArgs { diff --git a/src/requests/BreweryPost/sendCreateBreweryPostRequest.ts b/src/requests/BreweryPost/sendCreateBreweryPostRequest.ts index 2da3ed5..5477035 100644 --- a/src/requests/BreweryPost/sendCreateBreweryPostRequest.ts +++ b/src/requests/BreweryPost/sendCreateBreweryPostRequest.ts @@ -1,5 +1,5 @@ -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; -import CreateBreweryPostSchema from '@/services/BreweryPost/schema/CreateBreweryPostSchema'; +import BreweryPostQueryResult from '@/services/posts/brewery-post/schema/BreweryPostQueryResult'; +import CreateBreweryPostSchema from '@/services/posts/brewery-post/schema/CreateBreweryPostSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { z } from 'zod'; diff --git a/src/requests/User/sendEditUserRequest.ts b/src/requests/User/sendEditUserRequest.ts index 17eb453..1ff9feb 100644 --- a/src/requests/User/sendEditUserRequest.ts +++ b/src/requests/User/sendEditUserRequest.ts @@ -1,4 +1,4 @@ -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { z } from 'zod'; diff --git a/src/requests/User/sendRegisterUserRequest.ts b/src/requests/User/sendRegisterUserRequest.ts index b29b5e5..3cb6c11 100644 --- a/src/requests/User/sendRegisterUserRequest.ts +++ b/src/requests/User/sendRegisterUserRequest.ts @@ -1,5 +1,5 @@ -import { CreateUserValidationSchema } from '@/services/User/schema/CreateUserValidationSchemas'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import { CreateUserValidationSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { z } from 'zod'; diff --git a/src/requests/User/sendUpdatePasswordRequest.ts b/src/requests/User/sendUpdatePasswordRequest.ts index 01b55c3..d565c30 100644 --- a/src/requests/User/sendUpdatePasswordRequest.ts +++ b/src/requests/User/sendUpdatePasswordRequest.ts @@ -1,4 +1,4 @@ -import { UpdatePasswordSchema } from '@/services/User/schema/CreateUserValidationSchemas'; +import { UpdatePasswordSchema } from '@/services/users/auth/schema/CreateUserValidationSchemas'; import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; import { z } from 'zod'; diff --git a/src/services/BeerComment/createNewBeerComment.ts b/src/services/BeerComment/createNewBeerComment.ts deleted file mode 100644 index 9abba2b..0000000 --- a/src/services/BeerComment/createNewBeerComment.ts +++ /dev/null @@ -1,37 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import CreateCommentValidationSchema from '../schema/CommentSchema/CreateCommentValidationSchema'; -import CommentQueryResult from '../schema/CommentSchema/CommentQueryResult'; - -const CreateNewBeerCommentServiceSchema = CreateCommentValidationSchema.extend({ - userId: z.string().cuid(), - beerPostId: z.string().cuid(), -}); - -type CreateNewBeerCommentArgs = z.infer; - -const createNewBeerComment = async ({ - content, - rating, - beerPostId, - userId, -}: CreateNewBeerCommentArgs): Promise> => { - return DBClient.instance.beerComment.create({ - data: { - content, - rating, - beerPost: { connect: { id: beerPostId } }, - postedBy: { connect: { id: userId } }, - }, - select: { - id: true, - content: true, - rating: true, - postedBy: { select: { id: true, username: true, userAvatar: true } }, - createdAt: true, - updatedAt: true, - }, - }); -}; - -export default createNewBeerComment; diff --git a/src/services/BeerComment/editBeerCommentById.ts b/src/services/BeerComment/editBeerCommentById.ts deleted file mode 100644 index 2aa4e3c..0000000 --- a/src/services/BeerComment/editBeerCommentById.ts +++ /dev/null @@ -1,32 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import CommentQueryResult from '../schema/CommentSchema/CommentQueryResult'; - -interface EditBeerCommentByIdArgs { - id: string; - content: string; - rating: number; -} - -const editBeerCommentById = async ({ - id, - content, - rating, -}: EditBeerCommentByIdArgs): Promise> => { - return DBClient.instance.beerComment.update({ - where: { id }, - data: { content, rating, updatedAt: new Date() }, - select: { - id: true, - content: true, - rating: true, - createdAt: true, - updatedAt: true, - postedBy: { - select: { id: true, username: true, createdAt: true, userAvatar: true }, - }, - }, - }); -}; - -export default editBeerCommentById; diff --git a/src/services/BeerComment/findBeerCommentById.ts b/src/services/BeerComment/findBeerCommentById.ts deleted file mode 100644 index f985414..0000000 --- a/src/services/BeerComment/findBeerCommentById.ts +++ /dev/null @@ -1,27 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import CommentQueryResult from '../schema/CommentSchema/CommentQueryResult'; - -interface FindBeerCommentArgs { - beerCommentId: string; -} - -const findBeerCommentById = async ({ - beerCommentId, -}: FindBeerCommentArgs): Promise | null> => { - return DBClient.instance.beerComment.findUnique({ - where: { id: beerCommentId }, - select: { - id: true, - content: true, - rating: true, - createdAt: true, - updatedAt: true, - postedBy: { - select: { id: true, username: true, createdAt: true, userAvatar: true }, - }, - }, - }); -}; - -export default findBeerCommentById; diff --git a/src/services/BeerComment/getAllBeerComments.ts b/src/services/BeerComment/getAllBeerComments.ts deleted file mode 100644 index c7a30bd..0000000 --- a/src/services/BeerComment/getAllBeerComments.ts +++ /dev/null @@ -1,34 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import CommentQueryResult from '../schema/CommentSchema/CommentQueryResult'; - -interface GetAllBeerCommentsArgs { - beerPostId: string; - pageNum: number; - pageSize: number; -} - -const getAllBeerComments = async ({ - beerPostId, - pageNum, - pageSize, -}: GetAllBeerCommentsArgs): Promise[]> => { - return DBClient.instance.beerComment.findMany({ - skip: (pageNum - 1) * pageSize, - take: pageSize, - where: { beerPostId }, - orderBy: { createdAt: 'desc' }, - select: { - id: true, - content: true, - rating: true, - createdAt: true, - updatedAt: true, - postedBy: { - select: { id: true, username: true, createdAt: true, userAvatar: true }, - }, - }, - }); -}; - -export default getAllBeerComments; diff --git a/src/services/BeerComment/getBeerCommentCount.ts b/src/services/BeerComment/getBeerCommentCount.ts deleted file mode 100644 index 989e7cd..0000000 --- a/src/services/BeerComment/getBeerCommentCount.ts +++ /dev/null @@ -1,13 +0,0 @@ -import DBClient from '@/prisma/DBClient'; - -interface GetBeerCommentCountArgs { - beerPostId: string; -} - -const getBeerCommentCount = async ({ - beerPostId, -}: GetBeerCommentCountArgs): Promise => { - return DBClient.instance.beerComment.count({ where: { beerPostId } }); -}; - -export default getBeerCommentCount; diff --git a/src/services/BeerImage/addBeerImageToDB.ts b/src/services/BeerImage/addBeerImageToDB.ts deleted file mode 100644 index 0ca896b..0000000 --- a/src/services/BeerImage/addBeerImageToDB.ts +++ /dev/null @@ -1,40 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { BeerImage } from '@prisma/client'; -import { z } from 'zod'; -import ImageMetadataValidationSchema from '../schema/ImageSchema/ImageMetadataValidationSchema'; - -interface ProcessImageDataArgs { - files: Express.Multer.File[]; - alt: z.infer['alt']; - caption: z.infer['caption']; - beerPostId: string; - userId: string; -} - -const addBeerImageToDB = ({ - alt, - caption, - files, - beerPostId, - userId, -}: ProcessImageDataArgs) => { - const beerImagePromises: Promise[] = []; - - files.forEach((file) => { - beerImagePromises.push( - DBClient.instance.beerImage.create({ - data: { - alt, - caption, - postedBy: { connect: { id: userId } }, - beerPost: { connect: { id: beerPostId } }, - path: file.path, - }, - }), - ); - }); - - return Promise.all(beerImagePromises); -}; - -export default addBeerImageToDB; diff --git a/src/services/BeerPost/createNewBeerPost.ts b/src/services/BeerPost/createNewBeerPost.ts deleted file mode 100644 index 87d19ba..0000000 --- a/src/services/BeerPost/createNewBeerPost.ts +++ /dev/null @@ -1,56 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import BeerPostQueryResult from './schema/BeerPostQueryResult'; -import CreateBeerPostValidationSchema from './schema/CreateBeerPostValidationSchema'; - -const CreateBeerPostWithUserSchema = CreateBeerPostValidationSchema.extend({ - userId: z.string().cuid(), -}); - -const createNewBeerPost = ({ - name, - description, - abv, - ibu, - styleId, - breweryId, - userId, -}: z.infer): Promise< - z.infer -> => { - return DBClient.instance.beerPost.create({ - data: { - name, - description, - abv, - ibu, - style: { connect: { id: styleId } }, - postedBy: { connect: { id: userId } }, - brewery: { connect: { id: breweryId } }, - }, - select: { - id: true, - name: true, - description: true, - abv: true, - ibu: true, - createdAt: true, - updatedAt: true, - beerImages: { - select: { - alt: true, - path: true, - caption: true, - id: true, - createdAt: true, - updatedAt: true, - }, - }, - brewery: { select: { id: true, name: true } }, - style: { select: { id: true, name: true, description: true } }, - postedBy: { select: { id: true, username: true } }, - }, - }); -}; - -export default createNewBeerPost; diff --git a/src/services/BeerPost/deleteBeerPostById.ts b/src/services/BeerPost/deleteBeerPostById.ts deleted file mode 100644 index 634712a..0000000 --- a/src/services/BeerPost/deleteBeerPostById.ts +++ /dev/null @@ -1,39 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import BeerPostQueryResult from './schema/BeerPostQueryResult'; - -interface DeleteBeerPostByIdArgs { - beerPostId: string; -} - -const deleteBeerPostById = ({ - beerPostId, -}: DeleteBeerPostByIdArgs): Promise | null> => { - return DBClient.instance.beerPost.delete({ - where: { id: beerPostId }, - select: { - abv: true, - createdAt: true, - description: true, - ibu: true, - id: true, - name: true, - updatedAt: true, - beerImages: { - select: { - alt: true, - path: true, - caption: true, - id: true, - createdAt: true, - updatedAt: true, - }, - }, - style: { select: { id: true, name: true, description: true } }, - postedBy: { select: { id: true, username: true } }, - brewery: { select: { id: true, name: true } }, - }, - }); -}; - -export default deleteBeerPostById; diff --git a/src/services/BeerPost/editBeerPostById.ts b/src/services/BeerPost/editBeerPostById.ts deleted file mode 100644 index 7f4ae0a..0000000 --- a/src/services/BeerPost/editBeerPostById.ts +++ /dev/null @@ -1,45 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import EditBeerPostValidationSchema from './schema/EditBeerPostValidationSchema'; -import BeerPostQueryResult from './schema/BeerPostQueryResult'; - -const schema = EditBeerPostValidationSchema.omit({ id: true, styleId: true }); - -interface EditBeerPostByIdArgs { - id: string; - data: z.infer; -} - -const editBeerPostById = ({ - id, - data: { abv, ibu, name, description }, -}: EditBeerPostByIdArgs): Promise> => { - return DBClient.instance.beerPost.update({ - where: { id }, - data: { abv, ibu, name, description }, - select: { - id: true, - name: true, - description: true, - abv: true, - ibu: true, - createdAt: true, - updatedAt: true, - beerImages: { - select: { - alt: true, - path: true, - caption: true, - id: true, - createdAt: true, - updatedAt: true, - }, - }, - brewery: { select: { id: true, name: true } }, - style: { select: { id: true, name: true, description: true } }, - postedBy: { select: { id: true, username: true } }, - }, - }); -}; - -export default editBeerPostById; diff --git a/src/services/BeerPost/getAllBeerPosts.ts b/src/services/BeerPost/getAllBeerPosts.ts deleted file mode 100644 index 483f02c..0000000 --- a/src/services/BeerPost/getAllBeerPosts.ts +++ /dev/null @@ -1,45 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; -import { z } from 'zod'; - -const prisma = DBClient.instance; - -interface GetAllBeerPostsArgs { - pageNum: number; - pageSize: number; -} - -const getAllBeerPosts = ({ - pageNum, - pageSize, -}: GetAllBeerPostsArgs): Promise[]> => { - return prisma.beerPost.findMany({ - select: { - id: true, - name: true, - ibu: true, - abv: true, - description: true, - createdAt: true, - updatedAt: true, - style: { select: { name: true, id: true, description: true } }, - brewery: { select: { name: true, id: true } }, - postedBy: { select: { id: true, username: true } }, - beerImages: { - select: { - alt: true, - path: true, - caption: true, - id: true, - createdAt: true, - updatedAt: true, - }, - }, - }, - take: pageSize, - skip: (pageNum - 1) * pageSize, - orderBy: { createdAt: 'desc' }, - }); -}; - -export default getAllBeerPosts; diff --git a/src/services/BeerPost/getBeerPostById.ts b/src/services/BeerPost/getBeerPostById.ts deleted file mode 100644 index 407c143..0000000 --- a/src/services/BeerPost/getBeerPostById.ts +++ /dev/null @@ -1,37 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; -import { z } from 'zod'; - -const prisma = DBClient.instance; - -const getBeerPostById = async ( - id: string, -): Promise | null> => { - return prisma.beerPost.findFirst({ - select: { - id: true, - name: true, - ibu: true, - abv: true, - createdAt: true, - updatedAt: true, - description: true, - postedBy: { select: { username: true, id: true } }, - brewery: { select: { name: true, id: true } }, - style: { select: { name: true, id: true, description: true } }, - beerImages: { - select: { - alt: true, - path: true, - caption: true, - id: true, - createdAt: true, - updatedAt: true, - }, - }, - }, - where: { id }, - }); -}; - -export default getBeerPostById; diff --git a/src/services/BeerPost/getBeerPostsByBeerStyleId.ts b/src/services/BeerPost/getBeerPostsByBeerStyleId.ts deleted file mode 100644 index 6fabe91..0000000 --- a/src/services/BeerPost/getBeerPostsByBeerStyleId.ts +++ /dev/null @@ -1,47 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import BeerPostQueryResult from './schema/BeerPostQueryResult'; - -interface GetBeerPostsByBeerStyleIdArgs { - styleId: string; - pageSize: number; - pageNum: number; -} - -const getBeerPostsByBeerStyleId = async ({ - pageNum, - pageSize, - styleId, -}: GetBeerPostsByBeerStyleIdArgs): Promise[]> => { - const beers = await DBClient.instance.beerPost.findMany({ - where: { styleId }, - take: pageSize, - skip: pageNum * pageSize, - select: { - id: true, - name: true, - ibu: true, - abv: true, - createdAt: true, - updatedAt: true, - description: true, - postedBy: { select: { username: true, id: true } }, - brewery: { select: { name: true, id: true } }, - style: { select: { name: true, id: true, description: true } }, - beerImages: { - select: { - alt: true, - path: true, - caption: true, - id: true, - createdAt: true, - updatedAt: true, - }, - }, - }, - }); - - return beers; -}; - -export default getBeerPostsByBeerStyleId; diff --git a/src/services/BeerPost/getBeerPostsByBreweryId.ts b/src/services/BeerPost/getBeerPostsByBreweryId.ts deleted file mode 100644 index 2d69ecb..0000000 --- a/src/services/BeerPost/getBeerPostsByBreweryId.ts +++ /dev/null @@ -1,47 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import BeerPostQueryResult from './schema/BeerPostQueryResult'; - -interface GetBeerPostsByBeerStyleIdArgs { - breweryId: string; - pageSize: number; - pageNum: number; -} - -const getBeerPostsByBeerStyleId = async ({ - pageNum, - pageSize, - breweryId, -}: GetBeerPostsByBeerStyleIdArgs): Promise[]> => { - const beers = await DBClient.instance.beerPost.findMany({ - where: { breweryId }, - take: pageSize, - skip: pageNum * pageSize, - select: { - id: true, - name: true, - ibu: true, - abv: true, - createdAt: true, - updatedAt: true, - description: true, - postedBy: { select: { username: true, id: true } }, - brewery: { select: { name: true, id: true } }, - style: { select: { name: true, id: true, description: true } }, - beerImages: { - select: { - alt: true, - path: true, - caption: true, - id: true, - createdAt: true, - updatedAt: true, - }, - }, - }, - }); - - return beers; -}; - -export default getBeerPostsByBeerStyleId; diff --git a/src/services/BeerPost/getBeerPostsByPostedById.ts b/src/services/BeerPost/getBeerPostsByPostedById.ts deleted file mode 100644 index 31ce792..0000000 --- a/src/services/BeerPost/getBeerPostsByPostedById.ts +++ /dev/null @@ -1,47 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import BeerPostQueryResult from './schema/BeerPostQueryResult'; - -interface GetBeerPostsByBeerStyleIdArgs { - postedById: string; - pageSize: number; - pageNum: number; -} - -const getBeerPostsByPostedById = async ({ - pageNum, - pageSize, - postedById, -}: GetBeerPostsByBeerStyleIdArgs): Promise[]> => { - const beers = await DBClient.instance.beerPost.findMany({ - where: { postedBy: { id: postedById } }, - take: pageSize, - skip: pageNum * pageSize, - select: { - id: true, - name: true, - ibu: true, - abv: true, - createdAt: true, - updatedAt: true, - description: true, - postedBy: { select: { username: true, id: true } }, - brewery: { select: { name: true, id: true } }, - style: { select: { name: true, id: true, description: true } }, - beerImages: { - select: { - alt: true, - path: true, - caption: true, - id: true, - createdAt: true, - updatedAt: true, - }, - }, - }, - }); - - return beers; -}; - -export default getBeerPostsByPostedById; diff --git a/src/services/BeerPost/getBeerRecommendations.ts b/src/services/BeerPost/getBeerRecommendations.ts deleted file mode 100644 index 2fce906..0000000 --- a/src/services/BeerPost/getBeerRecommendations.ts +++ /dev/null @@ -1,65 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; - -import { z } from 'zod'; - -interface GetBeerRecommendationsArgs { - beerPost: z.infer; - pageNum: number; - pageSize: number; -} - -const getBeerRecommendations = async ({ - beerPost, - pageNum, - pageSize, -}: GetBeerRecommendationsArgs): Promise<{ - beerRecommendations: z.infer[]; - count: number; -}> => { - const skip = (pageNum - 1) * pageSize; - const take = pageSize; - - const beerRecommendations: z.infer[] = - await DBClient.instance.beerPost.findMany({ - where: { - OR: [{ styleId: beerPost.style.id }, { breweryId: beerPost.brewery.id }], - NOT: { id: beerPost.id }, - }, - select: { - id: true, - name: true, - ibu: true, - abv: true, - description: true, - createdAt: true, - updatedAt: true, - style: { select: { name: true, id: true, description: true } }, - brewery: { select: { name: true, id: true } }, - postedBy: { select: { id: true, username: true } }, - beerImages: { - select: { - alt: true, - path: true, - caption: true, - id: true, - createdAt: true, - updatedAt: true, - }, - }, - }, - take, - skip, - }); - - const count = await DBClient.instance.beerPost.count({ - where: { - OR: [{ styleId: beerPost.style.id }, { breweryId: beerPost.brewery.id }], - NOT: { id: beerPost.id }, - }, - }); - - return { beerRecommendations, count }; -}; - -export default getBeerRecommendations; diff --git a/src/services/BeerPostLike/createBeerPostLike.ts b/src/services/BeerPostLike/createBeerPostLike.ts deleted file mode 100644 index f086321..0000000 --- a/src/services/BeerPostLike/createBeerPostLike.ts +++ /dev/null @@ -1,15 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import GetUserSchema from '../User/schema/GetUserSchema'; - -interface CreateBeerPostLikeArgs { - id: string; - user: z.infer; -} - -const createBeerPostLike = async ({ id, user }: CreateBeerPostLikeArgs) => - DBClient.instance.beerPostLike.create({ - data: { beerPost: { connect: { id } }, likedBy: { connect: { id: user.id } } }, - }); - -export default createBeerPostLike; diff --git a/src/services/BeerPostLike/findBeerPostLikeById.ts b/src/services/BeerPostLike/findBeerPostLikeById.ts deleted file mode 100644 index 11e2789..0000000 --- a/src/services/BeerPostLike/findBeerPostLikeById.ts +++ /dev/null @@ -1,14 +0,0 @@ -import DBClient from '@/prisma/DBClient'; - -interface FindBeerPostLikeByIdArgs { - beerPostId: string; - likedById: string; -} - -const findBeerPostLikeById = async ({ - beerPostId, - likedById, -}: FindBeerPostLikeByIdArgs) => - DBClient.instance.beerPostLike.findFirst({ where: { beerPostId, likedById } }); - -export default findBeerPostLikeById; diff --git a/src/services/BeerPostLike/getBeerPostLikeCount.ts b/src/services/BeerPostLike/getBeerPostLikeCount.ts deleted file mode 100644 index 14389b9..0000000 --- a/src/services/BeerPostLike/getBeerPostLikeCount.ts +++ /dev/null @@ -1,6 +0,0 @@ -import DBClient from '@/prisma/DBClient'; - -const getBeerPostLikeCount = async ({ beerPostId }: { beerPostId: string }) => - DBClient.instance.beerPostLike.count({ where: { beerPostId } }); - -export default getBeerPostLikeCount; diff --git a/src/services/BeerPostLike/removeBeerPostLikeById.ts b/src/services/BeerPostLike/removeBeerPostLikeById.ts deleted file mode 100644 index 2a6e3e7..0000000 --- a/src/services/BeerPostLike/removeBeerPostLikeById.ts +++ /dev/null @@ -1,10 +0,0 @@ -import DBClient from '@/prisma/DBClient'; - -interface RemoveBeerPostLikeArgs { - beerLikeId: string; -} - -const removeBeerPostLikeById = async ({ beerLikeId }: RemoveBeerPostLikeArgs) => - DBClient.instance.beerPostLike.delete({ where: { id: beerLikeId } }); - -export default removeBeerPostLikeById; diff --git a/src/services/BeerStyleComment/createNewBeerStyleComment.ts b/src/services/BeerStyleComment/createNewBeerStyleComment.ts deleted file mode 100644 index 9d29ee2..0000000 --- a/src/services/BeerStyleComment/createNewBeerStyleComment.ts +++ /dev/null @@ -1,39 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import CreateCommentValidationSchema from '../schema/CommentSchema/CreateCommentValidationSchema'; -import CommentQueryResult from '../schema/CommentSchema/CommentQueryResult'; - -const CreateNewBeerStyleCommentServiceSchema = CreateCommentValidationSchema.extend({ - userId: z.string().cuid(), - beerStyleId: z.string().cuid(), -}); - -type CreateNewBeerCommentArgs = z.infer; - -const createNewBeerStyleComment = async ({ - content, - rating, - userId, - beerStyleId, -}: CreateNewBeerCommentArgs): Promise> => { - return DBClient.instance.beerStyleComment.create({ - data: { - content, - rating, - beerStyle: { connect: { id: beerStyleId } }, - postedBy: { connect: { id: userId } }, - }, - select: { - id: true, - content: true, - rating: true, - createdAt: true, - updatedAt: true, - postedBy: { - select: { id: true, username: true, createdAt: true, userAvatar: true }, - }, - }, - }); -}; - -export default createNewBeerStyleComment; diff --git a/src/services/BeerStyleComment/getAllBeerStyleComments.ts b/src/services/BeerStyleComment/getAllBeerStyleComments.ts deleted file mode 100644 index 77deced..0000000 --- a/src/services/BeerStyleComment/getAllBeerStyleComments.ts +++ /dev/null @@ -1,34 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import CommentQueryResult from '../schema/CommentSchema/CommentQueryResult'; - -interface GetAllBeerStyleCommentArgs { - beerStyleId: string; - pageNum: number; - pageSize: number; -} - -const getAllBeerStyleComments = async ({ - beerStyleId, - pageNum, - pageSize, -}: GetAllBeerStyleCommentArgs): Promise[]> => { - return DBClient.instance.beerStyleComment.findMany({ - skip: (pageNum - 1) * pageSize, - take: pageSize, - where: { beerStyleId }, - orderBy: { createdAt: 'desc' }, - select: { - id: true, - content: true, - rating: true, - createdAt: true, - updatedAt: true, - postedBy: { - select: { id: true, username: true, createdAt: true, userAvatar: true }, - }, - }, - }); -}; - -export default getAllBeerStyleComments; diff --git a/src/services/BeerStyleComment/getBeerStyleCommentCount.ts b/src/services/BeerStyleComment/getBeerStyleCommentCount.ts deleted file mode 100644 index c52185c..0000000 --- a/src/services/BeerStyleComment/getBeerStyleCommentCount.ts +++ /dev/null @@ -1,15 +0,0 @@ -import DBClient from '@/prisma/DBClient'; - -interface GetBeerStyleCommentCountArgs { - beerStyleId: string; -} - -const getBeerCommentCount = async ({ - beerStyleId, -}: GetBeerStyleCommentCountArgs): Promise => { - return DBClient.instance.beerStyleComment.count({ - where: { beerStyleId }, - }); -}; - -export default getBeerCommentCount; diff --git a/src/services/BeerStyleLike/createBeerStyleLike.ts b/src/services/BeerStyleLike/createBeerStyleLike.ts deleted file mode 100644 index 9d4652b..0000000 --- a/src/services/BeerStyleLike/createBeerStyleLike.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { z } from 'zod'; -import DBClient from '@/prisma/DBClient'; -import GetUserSchema from '@/services/User/schema/GetUserSchema'; - -interface CreateBeerStyleLikeArgs { - beerStyleId: string; - user: z.infer; -} -const createBeerStyleLike = async ({ beerStyleId, user }: CreateBeerStyleLikeArgs) => { - return DBClient.instance.beerStyleLike.create({ - data: { - beerStyleId, - likedById: user.id, - }, - }); -}; - -export default createBeerStyleLike; diff --git a/src/services/BeerStyleLike/findBeerStyleLikeById.ts b/src/services/BeerStyleLike/findBeerStyleLikeById.ts deleted file mode 100644 index b9ec621..0000000 --- a/src/services/BeerStyleLike/findBeerStyleLikeById.ts +++ /dev/null @@ -1,16 +0,0 @@ -import DBClient from '@/prisma/DBClient'; - -interface FindBeerStyleLikeByIdArgs { - beerStyleId: string; - likedById: string; -} -const findBeerStyleLikeById = async ({ - beerStyleId, - likedById, -}: FindBeerStyleLikeByIdArgs) => { - return DBClient.instance.beerStyleLike.findFirst({ - where: { beerStyleId, likedById }, - }); -}; - -export default findBeerStyleLikeById; diff --git a/src/services/BeerStyleLike/getBeerStyleLikeCount.ts b/src/services/BeerStyleLike/getBeerStyleLikeCount.ts deleted file mode 100644 index 7ba4061..0000000 --- a/src/services/BeerStyleLike/getBeerStyleLikeCount.ts +++ /dev/null @@ -1,10 +0,0 @@ -import DBClient from '@/prisma/DBClient'; - -interface GetBeerStyleLikeCountArgs { - beerStyleId: string; -} -const getBeerStyleLikeCount = async ({ beerStyleId }: GetBeerStyleLikeCountArgs) => { - return DBClient.instance.beerStyleLike.count({ where: { beerStyleId } }); -}; - -export default getBeerStyleLikeCount; diff --git a/src/services/BeerStyleLike/removeBeerStyleLikeById.ts b/src/services/BeerStyleLike/removeBeerStyleLikeById.ts deleted file mode 100644 index 164eee4..0000000 --- a/src/services/BeerStyleLike/removeBeerStyleLikeById.ts +++ /dev/null @@ -1,12 +0,0 @@ -import DBClient from '@/prisma/DBClient'; - -interface RemoveBeerStyleLikeByIdArgs { - beerStyleLikeId: string; -} -const removeBeerStyleLikeById = async ({ - beerStyleLikeId, -}: RemoveBeerStyleLikeByIdArgs) => { - return DBClient.instance.beerStyleLike.delete({ where: { id: beerStyleLikeId } }); -}; - -export default removeBeerStyleLikeById; diff --git a/src/services/BeerStyles/deleteBeerStyleById.ts b/src/services/BeerStyles/deleteBeerStyleById.ts deleted file mode 100644 index 5d169ae..0000000 --- a/src/services/BeerStyles/deleteBeerStyleById.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { z } from 'zod'; -import DBClient from '@/prisma/DBClient'; -import BeerStyleQueryResult from './schema/BeerStyleQueryResult'; - -interface DeleteBeerStyleByIdArgs { - beerStyleId: string; -} - -const deleteBeerStyleById = async ({ - beerStyleId, -}: DeleteBeerStyleByIdArgs): Promise | null> => { - const deleted = await DBClient.instance.beerStyle.delete({ - where: { id: beerStyleId }, - select: { - id: true, - name: true, - createdAt: true, - updatedAt: true, - abvRange: true, - ibuRange: true, - description: true, - postedBy: { select: { id: true, username: true } }, - glassware: { select: { id: true, name: true } }, - }, - }); - - /** - * Prisma does not support tuples, so we have to typecast the ibuRange and abvRange - * fields to [number, number] in order to satisfy the zod schema. - */ - return deleted as Awaited>; -}; - -export default deleteBeerStyleById; diff --git a/src/services/BeerStyles/editBeerStyleById.ts b/src/services/BeerStyles/editBeerStyleById.ts deleted file mode 100644 index 07adb3f..0000000 --- a/src/services/BeerStyles/editBeerStyleById.ts +++ /dev/null @@ -1,30 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import BeerStyleQueryResult from './schema/BeerStyleQueryResult'; - -const editBeerStyleById = async ( - id: string, -): Promise | null> => { - const beerStyle = await DBClient.instance.beerStyle.findUnique({ - where: { id }, - select: { - id: true, - name: true, - postedBy: { select: { id: true, username: true } }, - createdAt: true, - updatedAt: true, - abvRange: true, - ibuRange: true, - description: true, - glassware: { select: { id: true, name: true } }, - }, - }); - - /** - * Prisma does not support tuples, so we have to typecast the ibuRange and abvRange - * fields to [number, number] in order to satisfy the zod schema. - */ - return beerStyle as Awaited>; -}; - -export default editBeerStyleById; diff --git a/src/services/BeerStyles/getAllBeerStyles.ts b/src/services/BeerStyles/getAllBeerStyles.ts deleted file mode 100644 index 65df57b..0000000 --- a/src/services/BeerStyles/getAllBeerStyles.ts +++ /dev/null @@ -1,37 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import BeerStyleQueryResult from './schema/BeerStyleQueryResult'; - -interface GetAllBeerStylesArgs { - pageNum: number; - pageSize: number; -} - -const getAllBeerStyles = async ({ - pageNum, - pageSize, -}: GetAllBeerStylesArgs): Promise[]> => { - const beerStyles = await DBClient.instance.beerStyle.findMany({ - take: pageSize, - skip: (pageNum - 1) * pageSize, - select: { - id: true, - name: true, - postedBy: { select: { id: true, username: true } }, - createdAt: true, - updatedAt: true, - abvRange: true, - ibuRange: true, - description: true, - glassware: { select: { id: true, name: true } }, - }, - }); - - /** - * Prisma does not support tuples, so we have to typecast the ibuRange and abvRange - * fields to [number, number] in order to satisfy the zod schema. - */ - return beerStyles as Awaited>; -}; - -export default getAllBeerStyles; diff --git a/src/services/BeerStyles/getBeerStyleById.ts b/src/services/BeerStyles/getBeerStyleById.ts deleted file mode 100644 index 688fcda..0000000 --- a/src/services/BeerStyles/getBeerStyleById.ts +++ /dev/null @@ -1,30 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import BeerStyleQueryResult from './schema/BeerStyleQueryResult'; - -const getBeerStyleById = async ( - id: string, -): Promise | null> => { - const beerStyle = await DBClient.instance.beerStyle.findUnique({ - where: { id }, - select: { - id: true, - name: true, - postedBy: { select: { id: true, username: true } }, - createdAt: true, - updatedAt: true, - abvRange: true, - ibuRange: true, - description: true, - glassware: { select: { id: true, name: true } }, - }, - }); - - /** - * Prisma does not support tuples, so we have to typecast the ibuRange and abvRange - * fields to [number, number] in order to satisfy the zod schema. - */ - return beerStyle as Awaited>; -}; - -export default getBeerStyleById; diff --git a/src/services/BreweryComment/createNewBreweryComment.ts b/src/services/BreweryComment/createNewBreweryComment.ts deleted file mode 100644 index d161292..0000000 --- a/src/services/BreweryComment/createNewBreweryComment.ts +++ /dev/null @@ -1,39 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import CreateCommentValidationSchema from '../schema/CommentSchema/CreateCommentValidationSchema'; -import CommentQueryResult from '../schema/CommentSchema/CommentQueryResult'; - -const CreateNewBreweryCommentServiceSchema = CreateCommentValidationSchema.extend({ - userId: z.string().cuid(), - breweryPostId: z.string().cuid(), -}); - -const createNewBreweryComment = async ({ - content, - rating, - breweryPostId, - userId, -}: z.infer): Promise< - z.infer -> => { - return DBClient.instance.breweryComment.create({ - data: { - content, - rating, - breweryPost: { connect: { id: breweryPostId } }, - postedBy: { connect: { id: userId } }, - }, - select: { - id: true, - content: true, - rating: true, - createdAt: true, - updatedAt: true, - postedBy: { - select: { id: true, username: true, createdAt: true, userAvatar: true }, - }, - }, - }); -}; - -export default createNewBreweryComment; diff --git a/src/services/BreweryComment/getAllBreweryComments.ts b/src/services/BreweryComment/getAllBreweryComments.ts deleted file mode 100644 index 766f68d..0000000 --- a/src/services/BreweryComment/getAllBreweryComments.ts +++ /dev/null @@ -1,35 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import CommentQueryResult from '../schema/CommentSchema/CommentQueryResult'; - -const getAllBreweryComments = async ({ - id, - pageNum, - pageSize, -}: { - id: string; - pageNum: number; - pageSize: number; -}) => { - const skip = (pageNum - 1) * pageSize; - const breweryComments: z.infer[] = - await DBClient.instance.breweryComment.findMany({ - skip, - take: pageSize, - where: { breweryPostId: id }, - select: { - id: true, - content: true, - rating: true, - createdAt: true, - updatedAt: true, - postedBy: { - select: { id: true, username: true, createdAt: true, userAvatar: true }, - }, - }, - orderBy: { createdAt: 'desc' }, - }); - return breweryComments; -}; - -export default getAllBreweryComments; diff --git a/src/services/BreweryComment/getBreweryCommentById.ts b/src/services/BreweryComment/getBreweryCommentById.ts deleted file mode 100644 index c3420a0..0000000 --- a/src/services/BreweryComment/getBreweryCommentById.ts +++ /dev/null @@ -1,9 +0,0 @@ -import DBClient from '@/prisma/DBClient'; - -const getBreweryCommentById = async (id: string) => { - return DBClient.instance.breweryComment.findUnique({ - where: { id }, - }); -}; - -export default getBreweryCommentById; diff --git a/src/services/BreweryImage/addBreweryImageToDB.ts b/src/services/BreweryImage/addBreweryImageToDB.ts deleted file mode 100644 index e4b5499..0000000 --- a/src/services/BreweryImage/addBreweryImageToDB.ts +++ /dev/null @@ -1,39 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { BreweryImage } from '@prisma/client'; -import { z } from 'zod'; -import ImageMetadataValidationSchema from '../schema/ImageSchema/ImageMetadataValidationSchema'; - -interface ProcessImageDataArgs { - files: Express.Multer.File[]; - alt: z.infer['alt']; - caption: z.infer['caption']; - breweryPostId: string; - userId: string; -} - -const addBreweryImageToDB = ({ - alt, - caption, - files, - breweryPostId, - userId, -}: ProcessImageDataArgs) => { - const breweryImagePromises: Promise[] = []; - files.forEach((file) => { - breweryImagePromises.push( - DBClient.instance.breweryImage.create({ - data: { - alt, - caption, - postedBy: { connect: { id: userId } }, - breweryPost: { connect: { id: breweryPostId } }, - path: file.path, - }, - }), - ); - }); - - return Promise.all(breweryImagePromises); -}; - -export default addBreweryImageToDB; diff --git a/src/services/BreweryPost/createNewBreweryPost.ts b/src/services/BreweryPost/createNewBreweryPost.ts deleted file mode 100644 index cdcc66a..0000000 --- a/src/services/BreweryPost/createNewBreweryPost.ts +++ /dev/null @@ -1,64 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import CreateBreweryPostSchema from './schema/CreateBreweryPostSchema'; -import BreweryPostQueryResult from './schema/BreweryPostQueryResult'; - -const CreateNewBreweryPostWithUserAndLocationSchema = CreateBreweryPostSchema.omit({ - address: true, - city: true, - country: true, - stateOrProvince: true, -}).extend({ - userId: z.string().cuid(), - locationId: z.string().cuid(), -}); - -const createNewBreweryPost = async ({ - dateEstablished, - description, - locationId, - name, - userId, -}: z.infer): Promise< - z.infer -> => { - const post = (await DBClient.instance.breweryPost.create({ - data: { - name, - description, - dateEstablished, - location: { connect: { id: locationId } }, - postedBy: { connect: { id: userId } }, - }, - select: { - id: true, - name: true, - description: true, - createdAt: true, - dateEstablished: true, - postedBy: { select: { id: true, username: true } }, - breweryImages: { - select: { - path: true, - caption: true, - id: true, - alt: true, - createdAt: true, - updatedAt: true, - }, - }, - location: { - select: { - city: true, - address: true, - coordinates: true, - country: true, - stateOrProvince: true, - }, - }, - }, - })) as Awaited>; - - return post; -}; -export default createNewBreweryPost; diff --git a/src/services/BreweryPost/getAllBreweryPosts.ts b/src/services/BreweryPost/getAllBreweryPosts.ts deleted file mode 100644 index c83883c..0000000 --- a/src/services/BreweryPost/getAllBreweryPosts.ts +++ /dev/null @@ -1,55 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; - -import { z } from 'zod'; - -const prisma = DBClient.instance; - -const getAllBreweryPosts = async ({ - pageNum, - pageSize, -}: { - pageNum: number; - pageSize: number; -}): Promise[]> => { - const breweryPosts = await prisma.breweryPost.findMany({ - take: pageSize, - skip: (pageNum - 1) * pageSize, - select: { - id: true, - location: { - select: { - city: true, - address: true, - coordinates: true, - country: true, - stateOrProvince: true, - }, - }, - description: true, - name: true, - postedBy: { select: { username: true, id: true } }, - breweryImages: { - select: { - path: true, - caption: true, - id: true, - alt: true, - createdAt: true, - updatedAt: true, - }, - }, - createdAt: true, - dateEstablished: true, - }, - orderBy: { createdAt: 'desc' }, - }); - - /** - * Prisma does not support tuples, so we have to typecast the coordinates field to - * [number, number] in order to satisfy the zod schema. - */ - return breweryPosts as Awaited>; -}; - -export default getAllBreweryPosts; diff --git a/src/services/BreweryPost/getAllBreweryPostsByPostedById.ts b/src/services/BreweryPost/getAllBreweryPostsByPostedById.ts deleted file mode 100644 index 9b70cd8..0000000 --- a/src/services/BreweryPost/getAllBreweryPostsByPostedById.ts +++ /dev/null @@ -1,58 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; - -import { z } from 'zod'; - -const prisma = DBClient.instance; - -const getAllBreweryPostsByPostedById = async ({ - pageNum, - pageSize, - postedById, -}: { - pageNum: number; - pageSize: number; - postedById: string; -}): Promise[]> => { - const breweryPosts = await prisma.breweryPost.findMany({ - where: { postedBy: { id: postedById } }, - take: pageSize, - skip: (pageNum - 1) * pageSize, - select: { - id: true, - location: { - select: { - city: true, - address: true, - coordinates: true, - country: true, - stateOrProvince: true, - }, - }, - description: true, - name: true, - postedBy: { select: { username: true, id: true } }, - breweryImages: { - select: { - path: true, - caption: true, - id: true, - alt: true, - createdAt: true, - updatedAt: true, - }, - }, - createdAt: true, - dateEstablished: true, - }, - orderBy: { createdAt: 'desc' }, - }); - - /** - * Prisma does not support tuples, so we have to typecast the coordinates field to - * [number, number] in order to satisfy the zod schema. - */ - return breweryPosts as Awaited>; -}; - -export default getAllBreweryPostsByPostedById; diff --git a/src/services/BreweryPost/getBreweryPostById.ts b/src/services/BreweryPost/getBreweryPostById.ts deleted file mode 100644 index 51b5db9..0000000 --- a/src/services/BreweryPost/getBreweryPostById.ts +++ /dev/null @@ -1,46 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import BreweryPostQueryResult from '@/services/BreweryPost/schema/BreweryPostQueryResult'; -import { z } from 'zod'; - -const prisma = DBClient.instance; - -const getBreweryPostById = async (id: string) => { - const breweryPost = await prisma.breweryPost.findFirst({ - select: { - id: true, - location: { - select: { - city: true, - address: true, - coordinates: true, - country: true, - stateOrProvince: true, - }, - }, - description: true, - name: true, - breweryImages: { - select: { - path: true, - caption: true, - id: true, - alt: true, - createdAt: true, - updatedAt: true, - }, - }, - postedBy: { select: { username: true, id: true } }, - createdAt: true, - dateEstablished: true, - }, - where: { id }, - }); - - /** - * Prisma does not support tuples, so we have to typecast the coordinates field to - * [number, number] in order to satisfy the zod schema. - */ - return breweryPost as z.infer | null; -}; - -export default getBreweryPostById; diff --git a/src/services/User/createNewUser.ts b/src/services/User/createNewUser.ts deleted file mode 100644 index b34c61a..0000000 --- a/src/services/User/createNewUser.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { hashPassword } from '@/config/auth/passwordFns'; -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import { CreateUserValidationSchema } from './schema/CreateUserValidationSchemas'; -import GetUserSchema from './schema/GetUserSchema'; - -const createNewUser = async ({ - email, - password, - firstName, - lastName, - dateOfBirth, - username, -}: z.infer) => { - const hash = await hashPassword(password); - const user: z.infer = await DBClient.instance.user.create({ - data: { - username, - email, - hash, - firstName, - lastName, - dateOfBirth: new Date(dateOfBirth), - }, - select: { - id: true, - username: true, - email: true, - firstName: true, - lastName: true, - dateOfBirth: true, - createdAt: true, - accountIsVerified: true, - updatedAt: true, - role: true, - userAvatar: true, - bio: true, - }, - }); - - return user; -}; - -export default createNewUser; diff --git a/src/services/User/deleteUserById.ts b/src/services/User/deleteUserById.ts deleted file mode 100644 index dd62693..0000000 --- a/src/services/User/deleteUserById.ts +++ /dev/null @@ -1,28 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import GetUserSchema from './schema/GetUserSchema'; - -const deleteUserById = async (id: string) => { - const deletedUser: z.infer | null = - await DBClient.instance.user.delete({ - where: { id }, - select: { - id: true, - username: true, - email: true, - firstName: true, - lastName: true, - dateOfBirth: true, - createdAt: true, - accountIsVerified: true, - updatedAt: true, - role: true, - userAvatar: true, - bio: true, - }, - }); - - return deletedUser; -}; - -export default deleteUserById; diff --git a/src/services/User/findUserByEmail.ts b/src/services/User/findUserByEmail.ts deleted file mode 100644 index 1fa85d4..0000000 --- a/src/services/User/findUserByEmail.ts +++ /dev/null @@ -1,13 +0,0 @@ -import DBClient from '../../prisma/DBClient'; - -const findUserByEmail = async (email: string) => - DBClient.instance.user.findFirst({ - where: { email }, - select: { - id: true, - username: true, - hash: true, - }, - }); - -export default findUserByEmail; diff --git a/src/services/User/findUserById.ts b/src/services/User/findUserById.ts deleted file mode 100644 index 05b1182..0000000 --- a/src/services/User/findUserById.ts +++ /dev/null @@ -1,37 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import GetUserSchema from './schema/GetUserSchema'; - -const findUserById = async (id: string) => { - const user: z.infer | null = - await DBClient.instance.user.findUnique({ - where: { id }, - select: { - id: true, - username: true, - email: true, - firstName: true, - lastName: true, - dateOfBirth: true, - createdAt: true, - accountIsVerified: true, - updatedAt: true, - role: true, - userAvatar: { - select: { - path: true, - alt: true, - caption: true, - createdAt: true, - id: true, - updatedAt: true, - }, - }, - bio: true, - }, - }); - - return user; -}; - -export default findUserById; diff --git a/src/services/User/findUserByIdPublic.ts b/src/services/User/findUserByIdPublic.ts deleted file mode 100644 index f7509d5..0000000 --- a/src/services/User/findUserByIdPublic.ts +++ /dev/null @@ -1,23 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; - -import PublicUserSchema from './schema/PublicUserSchema'; - -const findUserByIdPublic = async (id: string) => { - const user: z.infer | null = - await DBClient.instance.user.findUnique({ - where: { id }, - select: { - id: true, - username: true, - firstName: true, - lastName: true, - createdAt: true, - role: true, - }, - }); - - return user; -}; - -export default findUserByIdPublic; diff --git a/src/services/User/findUserByUsername.ts b/src/services/User/findUserByUsername.ts deleted file mode 100644 index 1669e71..0000000 --- a/src/services/User/findUserByUsername.ts +++ /dev/null @@ -1,13 +0,0 @@ -import DBClient from '../../prisma/DBClient'; - -const findUserByUsername = async (username: string) => - DBClient.instance.user.findFirst({ - where: { username }, - select: { - id: true, - username: true, - hash: true, - }, - }); - -export default findUserByUsername; diff --git a/src/services/User/schema/PublicUserSchema.ts b/src/services/User/schema/PublicUserSchema.ts deleted file mode 100644 index 59e85aa..0000000 --- a/src/services/User/schema/PublicUserSchema.ts +++ /dev/null @@ -1,11 +0,0 @@ -import GetUserSchema from '@/services/User/schema/GetUserSchema'; - -const PublicUserSchema = GetUserSchema.pick({ - id: true, - name: true, - createdAt: true, - username: true, - role: true, -}); - -export default PublicUserSchema; diff --git a/src/services/User/sendConfirmationEmail.ts b/src/services/User/sendConfirmationEmail.ts deleted file mode 100644 index 4cf6e19..0000000 --- a/src/services/User/sendConfirmationEmail.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { generateConfirmationToken } from '@/config/jwt'; -import sendEmail from '@/config/sparkpost/sendEmail'; - -import Welcome from '@/emails/Welcome'; -import { render } from '@react-email/render'; -import { z } from 'zod'; -import { BASE_URL } from '@/config/env'; -import { ReactElement } from 'react'; -import GetUserSchema from './schema/GetUserSchema'; - -type UserSchema = z.infer; - -const sendConfirmationEmail = async ({ id, username, email }: UserSchema) => { - const confirmationToken = generateConfirmationToken({ id, username }); - - const subject = 'Confirm your email'; - const name = username; - const url = `${BASE_URL}/users/confirm?token=${confirmationToken}`; - const address = email; - - const component = Welcome({ name, url, subject })! as ReactElement; - - const html = render(component); - const text = render(component, { plainText: true }); - - await sendEmail({ address, subject, text, html }); -}; - -export default sendConfirmationEmail; diff --git a/src/services/User/updateUserToBeConfirmedById.ts b/src/services/User/updateUserToBeConfirmedById.ts deleted file mode 100644 index 4a8e7f3..0000000 --- a/src/services/User/updateUserToBeConfirmedById.ts +++ /dev/null @@ -1,37 +0,0 @@ -import GetUserSchema from '@/services/User/schema/GetUserSchema'; -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; - -const updateUserToBeConfirmedById = async (id: string) => { - const user: z.infer = await DBClient.instance.user.update({ - where: { id }, - data: { accountIsVerified: true, updatedAt: new Date() }, - select: { - id: true, - username: true, - email: true, - accountIsVerified: true, - createdAt: true, - firstName: true, - lastName: true, - updatedAt: true, - dateOfBirth: true, - role: true, - bio: true, - userAvatar: { - select: { - id: true, - path: true, - alt: true, - caption: true, - createdAt: true, - updatedAt: true, - }, - }, - }, - }); - - return user; -}; - -export default updateUserToBeConfirmedById; diff --git a/src/services/UserFollows/getUsersFollowedByUser.ts b/src/services/UserFollows/getUsersFollowedByUser.ts deleted file mode 100644 index 3f58d19..0000000 --- a/src/services/UserFollows/getUsersFollowedByUser.ts +++ /dev/null @@ -1,27 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import FollowInfoSchema from './schema/FollowInfoSchema'; - -interface GetFollowingInfoByUserIdArgs { - userId: string; - pageNum: number; - pageSize: number; -} -const getUsersFollowedByUser = async ({ - userId, - pageNum, - pageSize, -}: GetFollowingInfoByUserIdArgs): Promise[]> => { - const usersFollowedByQueriedUser = await DBClient.instance.userFollow.findMany({ - take: pageSize, - skip: (pageNum - 1) * pageSize, - where: { following: { id: userId } }, - select: { - follower: { select: { username: true, userAvatar: true, id: true } }, - }, - }); - - return usersFollowedByQueriedUser.map((u) => u.follower); -}; - -export default getUsersFollowedByUser; diff --git a/src/services/UserFollows/getUsersFollowingUser.ts b/src/services/UserFollows/getUsersFollowingUser.ts deleted file mode 100644 index 5379b7f..0000000 --- a/src/services/UserFollows/getUsersFollowingUser.ts +++ /dev/null @@ -1,27 +0,0 @@ -import DBClient from '@/prisma/DBClient'; -import { z } from 'zod'; -import FollowInfoSchema from './schema/FollowInfoSchema'; - -interface GetFollowingInfoByUserIdArgs { - userId: string; - pageNum: number; - pageSize: number; -} -const getUsersFollowingUser = async ({ - userId, - pageNum, - pageSize, -}: GetFollowingInfoByUserIdArgs): Promise[]> => { - const usersFollowingQueriedUser = await DBClient.instance.userFollow.findMany({ - take: pageSize, - skip: (pageNum - 1) * pageSize, - where: { follower: { id: userId } }, - select: { - following: { select: { username: true, userAvatar: true, id: true } }, - }, - }); - - return usersFollowingQueriedUser.map((u) => u.following); -}; - -export default getUsersFollowingUser; diff --git a/src/services/comments/beer-comment/index.ts b/src/services/comments/beer-comment/index.ts new file mode 100644 index 0000000..b884f5a --- /dev/null +++ b/src/services/comments/beer-comment/index.ts @@ -0,0 +1,136 @@ +import DBClient from '@/prisma/DBClient'; + +import { + CreateBeerPostComment, + EditBeerPostCommentById, + FindOrDeleteBeerPostCommentById, + GetAllBeerPostComments, +} from './types'; + +/** + * The select object for retrieving beer post comments. + * + * @example + * const beerPostComments = await DBClient.instance.beerComment.findMany({ + * select: beerPostCommentSelect, + * }); + */ +const beerPostCommentSelect = { + id: true, + content: true, + rating: true, + createdAt: true, + updatedAt: true, + postedBy: { + select: { id: true, username: true, createdAt: true, userAvatar: true }, + }, +} as const; + +/** + * Creates a new comment for a beer post. + * + * @param params - The options for creating the comment. + * @param params.body - The body of the comment. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the beer. + * @param params.beerPostId - The ID of the beer post. + * @param params.userId - The ID of the user creating the comment. + * @returns A promise that resolves to the created beer comment. + */ +export const createBeerPostCommentService: CreateBeerPostComment = ({ + body, + beerPostId, + userId, +}) => { + const { content, rating } = body; + return DBClient.instance.beerComment.create({ + data: { + content, + rating, + beerPost: { connect: { id: beerPostId } }, + postedBy: { connect: { id: userId } }, + }, + select: beerPostCommentSelect, + }); +}; + +/** + * Edits a comment for a beer post. + * + * @param params - The options for editing the comment. + * @param params.body - The body of the comment. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the beer. + * @param params.beerPostCommentId - The ID of the beer post comment. + * @returns A promise that resolves to the updated beer comment. + */ +export const editBeerPostCommentByIdService: EditBeerPostCommentById = ({ + beerPostCommentId, + body, +}) => { + const { content, rating } = body; + return DBClient.instance.beerComment.update({ + where: { id: beerPostCommentId }, + data: { content, rating, updatedAt: new Date() }, + select: beerPostCommentSelect, + }); +}; + +/** + * Finds a comment for a beer post by ID. + * + * @param params - The options for finding the comment. + * @param params.beerPostCommentId - The ID of the beer post comment. + * @returns A promise that resolves to the found beer comment. + */ +export const getBeerPostCommentByIdService: FindOrDeleteBeerPostCommentById = ({ + beerPostCommentId, +}) => { + return DBClient.instance.beerComment.findUnique({ + where: { id: beerPostCommentId }, + select: beerPostCommentSelect, + }); +}; + +/** + * Deletes a comment for a beer post by ID. + * + * @param params - The options for deleting the comment. + * @param params.beerPostCommentId - The ID of the beer post comment. + * @returns A promise that resolves to the deleted beer comment. + */ +export const deleteBeerCommentByIdService: FindOrDeleteBeerPostCommentById = ({ + beerPostCommentId, +}) => { + return DBClient.instance.beerComment.delete({ + where: { id: beerPostCommentId }, + select: beerPostCommentSelect, + }); +}; + +/** + * Gets all comments for a beer post. + * + * @param params - The options for getting the comments. + * @param params.beerPostId - The ID of the beer post. + * @param params.pageNum - The page number of the comments. + * @param params.pageSize - The number of comments per page. + * @returns A promise that resolves to the found beer comments. + */ +export const getAllBeerCommentsService: GetAllBeerPostComments = async ({ + beerPostId, + pageNum, + pageSize, +}) => { + const comments = await DBClient.instance.beerComment.findMany({ + skip: (pageNum - 1) * pageSize, + take: pageSize, + where: { beerPostId }, + orderBy: { createdAt: 'desc' }, + select: beerPostCommentSelect, + }); + + const count = await DBClient.instance.beerComment.count({ where: { beerPostId } }); + + return { comments, count }; +}; diff --git a/src/services/comments/beer-comment/types/index.ts b/src/services/comments/beer-comment/types/index.ts new file mode 100644 index 0000000..f9d37b9 --- /dev/null +++ b/src/services/comments/beer-comment/types/index.ts @@ -0,0 +1,29 @@ +import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; +import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; +import { z } from 'zod'; + +type BeerPostComment = z.infer; + +export type CreateBeerPostComment = (args: { + body: z.infer; + userId: string; + beerPostId: string; +}) => Promise; + +export type EditBeerPostCommentById = (args: { + body: z.infer; + beerPostCommentId: string; +}) => Promise; + +export type FindOrDeleteBeerPostCommentById = (args: { + beerPostCommentId: string; +}) => Promise; + +export type GetAllBeerPostComments = (args: { + beerPostId: string; + pageNum: number; + pageSize: number; +}) => Promise<{ + comments: BeerPostComment[]; + count: number; +}>; diff --git a/src/services/comments/beer-style-comment/index.ts b/src/services/comments/beer-style-comment/index.ts new file mode 100644 index 0000000..2e700c3 --- /dev/null +++ b/src/services/comments/beer-style-comment/index.ts @@ -0,0 +1,138 @@ +import DBClient from '@/prisma/DBClient'; +import { + CreateNewBeerStyleComment, + GetAllBeerStyleComments, + UpdateBeerStyleCommentById, + FindOrDeleteBeerStyleCommentById, +} from './types'; + +/** + * The select object for retrieving beer style comments. + * + * @example + * const beerStyleComments = await DBClient.instance.beerStyleComment.findMany({ + * select: beerStyleCommentSelect, + * }); + */ +const beerStyleCommentSelect = { + id: true, + content: true, + rating: true, + createdAt: true, + updatedAt: true, + postedBy: { + select: { id: true, username: true, createdAt: true, userAvatar: true }, + }, +} as const; + +/** + * Creates a new comment for a beer style. + * + * @param params - The options for creating the comment. + * @param params.body - The body of the comment. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the beer style. + * @param params.beerStyleId - The ID of the beer style. + * @param params.userId - The ID of the user creating the comment. + * @returns A promise that resolves to the created beer comment. + */ +export const createNewBeerStyleComment: CreateNewBeerStyleComment = ({ + body, + userId, + beerStyleId, +}) => { + const { content, rating } = body; + return DBClient.instance.beerStyleComment.create({ + data: { + content, + rating, + beerStyle: { connect: { id: beerStyleId } }, + postedBy: { connect: { id: userId } }, + }, + select: beerStyleCommentSelect, + }); +}; + +/** + * Gets all comments for a beer style. + * + * @param params - The options for getting the comments. + * @param params.beerStyleId - The ID of the beer style. + * @param params.pageNum - The page number of the comments. + * @param params.pageSize - The page size of the comments. + * @returns A promise that resolves to the beer style comments. + */ +export const getAllBeerStyleComments: GetAllBeerStyleComments = async ({ + beerStyleId, + pageNum, + pageSize, +}) => { + const comments = await DBClient.instance.beerStyleComment.findMany({ + skip: (pageNum - 1) * pageSize, + take: pageSize, + where: { beerStyleId }, + orderBy: { createdAt: 'desc' }, + select: beerStyleCommentSelect, + }); + + const count = await DBClient.instance.beerStyleComment.count({ + where: { beerStyleId }, + }); + + return { comments, count }; +}; + +/** + * Updates a beer style comment by ID. + * + * @param params - The options for updating the comment. + * @param params.body - The body of the comment. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the beer style. + * @param params.beerStyleCommentId - The ID of the beer style comment. + * @returns A promise that resolves to the updated beer comment. + */ +export const updateBeerStyleCommentById: UpdateBeerStyleCommentById = ({ + body, + beerStyleCommentId, +}) => { + const { content, rating } = body; + + return DBClient.instance.beerStyleComment.update({ + where: { id: beerStyleCommentId }, + data: { content, rating, updatedAt: new Date() }, + select: beerStyleCommentSelect, + }); +}; + +/** + * Finds a comment for a beer style by ID. + * + * @param params - The options for finding the comment. + * @param params.beerStyleCommentId - The ID of the beer style comment. + * @returns A promise that resolves to the found beer comment. + */ +export const findBeerStyleCommentById: FindOrDeleteBeerStyleCommentById = ({ + beerStyleCommentId, +}) => { + return DBClient.instance.beerStyleComment.findUnique({ + where: { id: beerStyleCommentId }, + select: beerStyleCommentSelect, + }); +}; + +/** + * Deletes a comment for a beer style by ID. + * + * @param params - The options for deleting the comment. + * @param params.beerStyleCommentId - The ID of the beer style comment. + * @returns A promise that resolves to the deleted beer comment. + */ +export const deleteBeerStyleCommentById: FindOrDeleteBeerStyleCommentById = ({ + beerStyleCommentId, +}) => { + return DBClient.instance.beerStyleComment.delete({ + where: { id: beerStyleCommentId }, + select: beerStyleCommentSelect, + }); +}; diff --git a/src/services/comments/beer-style-comment/types/index.ts b/src/services/comments/beer-style-comment/types/index.ts new file mode 100644 index 0000000..e3c3029 --- /dev/null +++ b/src/services/comments/beer-style-comment/types/index.ts @@ -0,0 +1,29 @@ +import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; +import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; +import { z } from 'zod'; + +type BeerStyleComment = z.infer; + +export type FindOrDeleteBeerStyleCommentById = (args: { + beerStyleCommentId: string; +}) => Promise; + +export type UpdateBeerStyleCommentById = (args: { + body: z.infer; + beerStyleCommentId: string; +}) => Promise; + +export type GetAllBeerStyleComments = (args: { + beerStyleId: string; + pageNum: number; + pageSize: number; +}) => Promise<{ + comments: BeerStyleComment[]; + count: number; +}>; + +export type CreateNewBeerStyleComment = (args: { + body: z.infer; + userId: string; + beerStyleId: string; +}) => Promise; diff --git a/src/services/comments/brewery-comment/index.ts b/src/services/comments/brewery-comment/index.ts new file mode 100644 index 0000000..19f994c --- /dev/null +++ b/src/services/comments/brewery-comment/index.ts @@ -0,0 +1,138 @@ +import DBClient from '@/prisma/DBClient'; +import { + CreateNewBreweryComment, + FindDeleteBreweryCommentById, + GetAllBreweryComments, + UpdateBreweryCommentById, +} from './types'; + +/** + * The select object for retrieving brewery comments. + * + * @example + * const breweryComments = await DBClient.instance.breweryComment.findMany({ + * select: breweryCommentSelect, + * }); + */ +const breweryCommentSelect = { + id: true, + content: true, + rating: true, + createdAt: true, + updatedAt: true, + postedBy: { + select: { id: true, username: true, createdAt: true, userAvatar: true }, + }, +} as const; + +/** + * Updates a brewery comment by ID. + * + * @param params - The options for updating the brewery comment. + * @param params.breweryCommentId - The ID of the brewery comment. + * @param params.body - The body of the comment. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the brewery. + * @returns A promise that resolves to the updated brewery comment. + */ +export const updateBreweryCommentById: UpdateBreweryCommentById = ({ + breweryCommentId, + body, +}) => { + const { content, rating } = body; + + return DBClient.instance.breweryComment.update({ + where: { id: breweryCommentId }, + data: { content, rating }, + select: breweryCommentSelect, + }); +}; + +/** + * Creates a new comment for a brewery. + * + * @param params - The options for creating the comment. + * @param params.body - The body of the comment. + * @param params.body.content - The content of the comment. + * @param params.body.rating - The rating of the brewery. + * @param params.breweryPostId - The ID of the brewery post. + * @param params.userId - The ID of the user creating the comment. + * @returns A promise that resolves to the created brewery comment. + */ +export const createNewBreweryComment: CreateNewBreweryComment = ({ + body, + breweryPostId, + userId, +}) => { + const { content, rating } = body; + return DBClient.instance.breweryComment.create({ + data: { + content, + rating, + breweryPost: { connect: { id: breweryPostId } }, + postedBy: { connect: { id: userId } }, + }, + select: breweryCommentSelect, + }); +}; + +/** + * Gets all comments for a brewery. + * + * @param params - The options for getting the comments. + * @param params.breweryPostId - The ID of the brewery post. + * @param params.pageNum - The page number of the comments. + * @param params.pageSize - The number of comments per page. + * @returns A promise that resolves to the retrieved brewery comments. + */ +export const getAllBreweryComments: GetAllBreweryComments = async ({ + id, + pageNum, + pageSize, +}) => { + const comments = await DBClient.instance.breweryComment.findMany({ + skip: (pageNum - 1) * pageSize, + take: pageSize, + where: { breweryPostId: id }, + select: breweryCommentSelect, + orderBy: { createdAt: 'desc' }, + }); + + const count = await DBClient.instance.breweryComment.count({ + where: { breweryPostId: id }, + }); + + return { comments, count }; +}; + +/** + * Finds a comment for a brewery post by ID. + * + * @param params - The options for finding the comment. + * @param params.breweryCommentId - The ID of the brewery post comment. + * @returns A promise that resolves to the found brewery comment. + */ +export const getBreweryCommentById: FindDeleteBreweryCommentById = ({ + breweryCommentId, +}) => { + return DBClient.instance.breweryComment.findUnique({ + where: { id: breweryCommentId }, + select: breweryCommentSelect, + }); +}; + +/** + * Deletes a comment for a brewery post by ID. + * + * @param params - The options for deleting the comment. + * @param params.breweryCommentId - The ID of the brewery post comment. + * @returns A promise that resolves to the deleted brewery comment. + */ +export const deleteBreweryCommentByIdService: FindDeleteBreweryCommentById = ({ + breweryCommentId, +}) => { + return DBClient.instance.breweryComment.delete({ + where: { id: breweryCommentId }, + select: breweryCommentSelect, + }); +}; diff --git a/src/services/comments/brewery-comment/types/index.ts b/src/services/comments/brewery-comment/types/index.ts new file mode 100644 index 0000000..8d1dda0 --- /dev/null +++ b/src/services/comments/brewery-comment/types/index.ts @@ -0,0 +1,27 @@ +import CommentQueryResult from '@/services/schema/CommentSchema/CommentQueryResult'; +import CreateCommentValidationSchema from '@/services/schema/CommentSchema/CreateCommentValidationSchema'; + +import { z } from 'zod'; + +type BreweryComment = z.infer; + +export type UpdateBreweryCommentById = (args: { + breweryCommentId: string; + body: z.infer; +}) => Promise; + +export type CreateNewBreweryComment = (args: { + body: z.infer; + breweryPostId: string; + userId: string; +}) => Promise; + +export type GetAllBreweryComments = (args: { + id: string; + pageNum: number; + pageSize: number; +}) => Promise<{ comments: BreweryComment[]; count: number }>; + +export type FindDeleteBreweryCommentById = (args: { + breweryCommentId: string; +}) => Promise; diff --git a/src/services/images/beer-image/index.ts b/src/services/images/beer-image/index.ts new file mode 100644 index 0000000..faf8956 --- /dev/null +++ b/src/services/images/beer-image/index.ts @@ -0,0 +1,87 @@ +import DBClient from '@/prisma/DBClient'; +import { BeerImage } from '@prisma/client'; +import { cloudinary } from '@/config/cloudinary'; +import { + AddBeerImagesToDB, + DeleteBeerImageFromDBAndStorage, + UpdateBeerImageMetadata, +} from './types'; + +/** + * Adds beer images to the database. + * + * @param options - The options for adding beer images. + * @param options.body - The body of the request. + * @param options.body.alt - The alt text for the beer image. + * @param options.body.caption - The caption for the beer image. + * @param options.files - The array of files to be uploaded as beer images. + * @param options.beerPostId - The ID of the beer post. + * @param options.userId - The ID of the user. + * @returns A promise that resolves to an array of created beer images. + */ +export const addBeerImagesService: AddBeerImagesToDB = ({ + body, + files, + beerPostId, + userId, +}) => { + const beerImagePromises: Promise[] = []; + + const { alt, caption } = body; + files.forEach((file) => { + beerImagePromises.push( + DBClient.instance.beerImage.create({ + data: { + alt, + caption, + postedBy: { connect: { id: userId } }, + beerPost: { connect: { id: beerPostId } }, + path: file.path, + }, + }), + ); + }); + + return Promise.all(beerImagePromises); +}; + +/** + * Deletes a beer image from the database and storage. + * + * @param options - The options for deleting a beer image. + * @param options.beerImageId - The ID of the beer image. + */ +export const deleteBeerImageService: DeleteBeerImageFromDBAndStorage = async ({ + beerImageId, +}) => { + const deleted = await DBClient.instance.beerImage.delete({ + where: { id: beerImageId }, + select: { path: true, id: true }, + }); + const { path } = deleted; + await cloudinary.uploader.destroy(path); +}; + +/** + * Updates the beer image metadata in the database. + * + * @param options - The options for updating the beer image metadata. + * @param options.beerImageId - The ID of the beer image. + * @param options.body - The body of the request containing the alt and caption. + * @param options.body.alt - The alt text for the beer image. + * @param options.body.caption - The caption for the beer image. + * @returns A promise that resolves to the updated beer image. + */ + +export const updateBeerImageService: UpdateBeerImageMetadata = async ({ + beerImageId, + body, +}) => { + const { alt, caption } = body; + const updated = await DBClient.instance.beerImage.update({ + where: { id: beerImageId }, + data: { alt, caption }, + }); + + return updated; +}; diff --git a/src/services/images/beer-image/types/index.ts b/src/services/images/beer-image/types/index.ts new file mode 100644 index 0000000..e392c47 --- /dev/null +++ b/src/services/images/beer-image/types/index.ts @@ -0,0 +1,19 @@ +import ImageMetadataValidationSchema from '@/services/schema/ImageSchema/ImageMetadataValidationSchema'; +import { BeerImage } from '@prisma/client'; +import { z } from 'zod'; + +export type AddBeerImagesToDB = (args: { + files: Express.Multer.File[]; + body: z.infer; + beerPostId: string; + userId: string; +}) => Promise; + +export type DeleteBeerImageFromDBAndStorage = (args: { + beerImageId: string; +}) => Promise; + +export type UpdateBeerImageMetadata = (args: { + beerImageId: string; + body: z.infer; +}) => Promise; diff --git a/src/services/images/brewery-image/index.ts b/src/services/images/brewery-image/index.ts new file mode 100644 index 0000000..ac91450 --- /dev/null +++ b/src/services/images/brewery-image/index.ts @@ -0,0 +1,87 @@ +import DBClient from '@/prisma/DBClient'; +import { BreweryImage } from '@prisma/client'; +import { cloudinary } from '@/config/cloudinary'; +import { + AddBreweryImagesToDB, + DeleteBreweryImageFromDBAndStorage, + UpdateBreweryImageMetadata, +} from './types'; + +/** + * Adds brewery images to the database. + * + * @param options - The options for adding brewery images. + * @param options.body - The body of the request containing the alt and caption. + * @param options.body.alt - The alt text for the brewery image. + * @param options.body.caption - The caption for the brewery image. + * @param options.files - The array of files to be uploaded as brewery images. + * @param options.breweryPostId - The ID of the brewery post. + * @param options.userId - The ID of the user adding the images. + * @returns A promise that resolves to an array of created brewery images. + */ + +export const addBreweryImagesService: AddBreweryImagesToDB = ({ + body, + files, + breweryPostId, + userId, +}) => { + const breweryImagePromises: Promise[] = []; + + const { alt, caption } = body; + files.forEach((file) => { + breweryImagePromises.push( + DBClient.instance.breweryImage.create({ + data: { + alt, + caption, + postedBy: { connect: { id: userId } }, + breweryPost: { connect: { id: breweryPostId } }, + path: file.path, + }, + }), + ); + }); + + return Promise.all(breweryImagePromises); +}; + +/** + * Deletes a brewery image from the database and storage. + * + * @param options - The options for deleting a brewery image. + * @param options.breweryImageId - The ID of the brewery image. + */ +export const deleteBreweryImageService: DeleteBreweryImageFromDBAndStorage = async ({ + breweryImageId, +}) => { + const deleted = await DBClient.instance.breweryImage.delete({ + where: { id: breweryImageId }, + select: { path: true, id: true }, + }); + const { path } = deleted; + await cloudinary.uploader.destroy(path); +}; + +/** + * Updates the brewery image metadata in the database. + * + * @param options - The options for updating the brewery image metadata. + * @param options.breweryImageId - The ID of the brewery image. + * @param options.body - The body of the request containing the alt and caption. + * @param options.body.alt - The alt text for the brewery image. + * @param options.body.caption - The caption for the brewery image. + * @returns A promise that resolves to the updated brewery image. + */ +export const updateBreweryImageService: UpdateBreweryImageMetadata = async ({ + breweryImageId, + body, +}) => { + const { alt, caption } = body; + const updated = await DBClient.instance.breweryImage.update({ + where: { id: breweryImageId }, + data: { alt, caption }, + }); + + return updated; +}; diff --git a/src/services/images/brewery-image/types/index.ts b/src/services/images/brewery-image/types/index.ts new file mode 100644 index 0000000..6da8221 --- /dev/null +++ b/src/services/images/brewery-image/types/index.ts @@ -0,0 +1,19 @@ +import ImageMetadataValidationSchema from '@/services/schema/ImageSchema/ImageMetadataValidationSchema'; +import { BreweryImage } from '@prisma/client'; +import { z } from 'zod'; + +export type AddBreweryImagesToDB = (args: { + files: Express.Multer.File[]; + body: z.infer; + breweryPostId: string; + userId: string; +}) => Promise; + +export type DeleteBreweryImageFromDBAndStorage = (args: { + breweryImageId: string; +}) => Promise; + +export type UpdateBreweryImageMetadata = (args: { + breweryImageId: string; + body: z.infer; +}) => Promise; diff --git a/src/services/likes/beer-post-like/index.ts b/src/services/likes/beer-post-like/index.ts new file mode 100644 index 0000000..bb0908c --- /dev/null +++ b/src/services/likes/beer-post-like/index.ts @@ -0,0 +1,60 @@ +import DBClient from '@/prisma/DBClient'; +import { + CreateBeerPostLike, + FindBeerPostLikeById, + GetBeerPostLikeCount, + RemoveBeerPostLike, +} from './types'; + +/** + * Creates a new beer post like. + * + * @param params - The parameters object for creating the beer post like. + * @param params.beerPostId - The ID of the beer post. + * @param params.likedById - The ID of the user who will like the beer post. + * @returns A promise that resolves to the newly created beer post like. + */ +export const createBeerPostLikeService: CreateBeerPostLike = async ({ + beerPostId, + likedById, +}) => + DBClient.instance.beerPostLike.create({ + data: { + beerPost: { connect: { id: beerPostId } }, + likedBy: { connect: { id: likedById } }, + }, + }); + +/** + * Retrieves a beer post like by ID. + * + * @param params - The parameters object for retrieving the beer post like. + * @param params.beerPostId - The ID of the beer post. + * @param params.likedById - The ID of the user who liked the beer post. + * @returns A promise that resolves to the beer post like. + */ +export const findBeerPostLikeByIdService: FindBeerPostLikeById = async ({ + beerPostId, + likedById, +}) => DBClient.instance.beerPostLike.findFirst({ where: { beerPostId, likedById } }); + +/** + * Removes a beer post like. + * + * @param params - The parameters object for removing the beer post like. + * @param params.beerPostLikeId - The ID of the beer post like to remove. + * @returns A promise that resolves to the removed beer post like. + */ +export const removeBeerPostLikeService: RemoveBeerPostLike = async ({ beerPostLikeId }) => + DBClient.instance.beerPostLike.delete({ where: { id: beerPostLikeId } }); + +/** + * Retrieves the number of likes for a beer post. + * + * @param params - The parameters object for retrieving the number of likes for a beer + * post. + * @param params.beerPostId - The ID of the beer post. + * @returns A promise that resolves to the number of likes for a beer post. + */ +export const getBeerPostLikeCountService: GetBeerPostLikeCount = async ({ beerPostId }) => + DBClient.instance.beerPostLike.count({ where: { beerPostId } }); diff --git a/src/services/likes/beer-post-like/schema/BeerPostLikeSchema.ts b/src/services/likes/beer-post-like/schema/BeerPostLikeSchema.ts new file mode 100644 index 0000000..e849f1c --- /dev/null +++ b/src/services/likes/beer-post-like/schema/BeerPostLikeSchema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +const BeerPostLikeSchema = z.object({ + id: z.string().cuid(), + beerPostId: z.string().cuid(), + likedById: z.string().cuid(), + createdAt: z.coerce.date(), + updatedAt: z.coerce.date().nullable(), +}); + +export default BeerPostLikeSchema; diff --git a/src/services/likes/beer-post-like/types/index.ts b/src/services/likes/beer-post-like/types/index.ts new file mode 100644 index 0000000..200fba5 --- /dev/null +++ b/src/services/likes/beer-post-like/types/index.ts @@ -0,0 +1,23 @@ +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; + +import { z } from 'zod'; +import BeerPostLikeSchema from '../schema/BeerPostLikeSchema'; + +type User = z.infer; +type ReturnSchema = z.infer; + +export type CreateBeerPostLike = (args: { + beerPostId: string; + likedById: User['id']; +}) => Promise; + +export type FindBeerPostLikeById = (args: { + beerPostId: string; + likedById: User['id']; +}) => Promise; + +export type RemoveBeerPostLike = (args: { + beerPostLikeId: string; +}) => Promise; + +export type GetBeerPostLikeCount = (args: { beerPostId: string }) => Promise; diff --git a/src/services/likes/beer-style-like/index.ts b/src/services/likes/beer-style-like/index.ts new file mode 100644 index 0000000..003953f --- /dev/null +++ b/src/services/likes/beer-style-like/index.ts @@ -0,0 +1,63 @@ +import DBClient from '@/prisma/DBClient'; + +import { + CreateBeerStyleLike, + FindBeerStyleLike, + GetBeerStyleLikeCount, + RemoveBeerStyleLike, +} from './types'; + +/** + * Creates a new beer style like. + * + * @param params - The parameters object for creating the beer style like. + * @param params.beerStyleId - The ID of the beer style. + * @param params.likedById - The ID of the user who will like the beer style. + * @returns A promise that resolves to the newly created beer style like. + */ +export const createBeerStyleLikeService: CreateBeerStyleLike = async ({ + beerStyleId, + likedById, +}) => + DBClient.instance.beerStyleLike.create({ + data: { + beerStyle: { connect: { id: beerStyleId } }, + likedBy: { connect: { id: likedById } }, + }, + }); + +/** + * Retrieves a beer style like by ID. + * + * @param params - The parameters object for retrieving the beer style like. + * @param params.beerStyleId - The ID of the beer style. + * @param params.likedById - The ID of the user who liked the beer style. + * @returns A promise that resolves to the beer style like. + */ +export const findBeerStyleLikeService: FindBeerStyleLike = async ({ + beerStyleId, + likedById, +}) => DBClient.instance.beerStyleLike.findFirst({ where: { beerStyleId, likedById } }); + +/** + * Removes a beer style like. + * + * @param params - The parameters object for removing the beer style like. + * @param params.beerStyleLikeId - The ID of the beer style like to remove. + * @returns A promise that resolves to the removed beer style like. + */ +export const removeBeerStyleLikeService: RemoveBeerStyleLike = async ({ + beerStyleLikeId, +}) => DBClient.instance.beerStyleLike.delete({ where: { id: beerStyleLikeId } }); + +/** + * Retrieves the number of likes for a beer style. + * + * @param params - The parameters object for retrieving the number of likes for a beer + * style. + * @param params.beerStyleId - The ID of the beer style. + * @returns A promise that resolves to the number of likes for a beer style. + */ +export const getBeerStyleLikeCountService: GetBeerStyleLikeCount = async ({ + beerStyleId, +}) => DBClient.instance.beerStyleLike.count({ where: { beerStyleId } }); diff --git a/src/services/likes/beer-style-like/schema/BeerStyleLikeSchema.ts b/src/services/likes/beer-style-like/schema/BeerStyleLikeSchema.ts new file mode 100644 index 0000000..93ce932 --- /dev/null +++ b/src/services/likes/beer-style-like/schema/BeerStyleLikeSchema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +const BeerStyleLikeSchema = z.object({ + id: z.string().cuid(), + beerStyleId: z.string().cuid(), + likedById: z.string().cuid(), + createdAt: z.coerce.date(), + updatedAt: z.coerce.date().nullable(), +}); + +export default BeerStyleLikeSchema; diff --git a/src/services/likes/beer-style-like/types/index.ts b/src/services/likes/beer-style-like/types/index.ts new file mode 100644 index 0000000..877a742 --- /dev/null +++ b/src/services/likes/beer-style-like/types/index.ts @@ -0,0 +1,23 @@ +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; +import { z } from 'zod'; + +import BeerStyleLikeSchema from '../schema/BeerStyleLikeSchema'; + +type User = z.infer; +type ReturnSchema = z.infer; + +export type CreateBeerStyleLike = (args: { + beerStyleId: string; + likedById: User['id']; +}) => Promise; + +export type FindBeerStyleLike = (args: { + beerStyleId: string; + likedById: User['id']; +}) => Promise; + +export type RemoveBeerStyleLike = (args: { + beerStyleLikeId: string; +}) => Promise; + +export type GetBeerStyleLikeCount = (args: { beerStyleId: string }) => Promise; diff --git a/src/services/likes/brewery-post-like/index.ts b/src/services/likes/brewery-post-like/index.ts new file mode 100644 index 0000000..123d61a --- /dev/null +++ b/src/services/likes/brewery-post-like/index.ts @@ -0,0 +1,63 @@ +import DBClient from '@/prisma/DBClient'; + +import { + CreateBreweryPostLike, + FindBreweryPostLike, + GetBreweryPostLikeCount, + RemoveBreweryPostLike, +} from './types'; + +/** + * Creates a new brewery post like. + * + * @param params - The parameters object for creating the brewery post like. + * @param params.breweryPostId - The ID of the brewery post. + * @param params.likedById - The ID of the user who will like the brewery post. + * @returns A promise that resolves to the newly created brewery post like. + */ +export const createBreweryPostLikeService: CreateBreweryPostLike = async ({ + breweryPostId, + likedById, +}) => + DBClient.instance.breweryPostLike.create({ + data: { + breweryPost: { connect: { id: breweryPostId } }, + likedBy: { connect: { id: likedById } }, + }, + }); + +/** + * Retrieves a brewery post like by ID. + * + * @param params - The parameters object for retrieving the brewery post like. + * @param params.breweryPostId - The ID of the brewery post. + * @param params.likedById - The ID of the user who liked the brewery post. + * @returns A promise that resolves to the brewery post like. + */ +export const findBreweryPostLikeService: FindBreweryPostLike = async ({ + breweryPostId, + likedById, +}) => + DBClient.instance.breweryPostLike.findFirst({ where: { breweryPostId, likedById } }); + +/** + * Removes a brewery post like. + * + * @param params - The parameters object for removing the brewery post like. + * @param params.breweryPostLikeId - The ID of the brewery post like to remove. + * @returns A promise that resolves to the removed brewery post like. + */ +export const removeBreweryPostLikeService: RemoveBreweryPostLike = async ({ + breweryPostLikeId, +}) => DBClient.instance.breweryPostLike.delete({ where: { id: breweryPostLikeId } }); + +/** + * Retrieves the number of likes for a brewery post. + * + * @param params - The parameters object for retrieving the number of likes for a brewery + * @param params.breweryPostId - The ID of the brewery post. + * @returns A promise that resolves to the number of likes for a brewery post. + */ +export const getBreweryPostLikeCountService: GetBreweryPostLikeCount = async ({ + breweryPostId, +}) => DBClient.instance.breweryPostLike.count({ where: { breweryPostId } }); diff --git a/src/services/likes/brewery-post-like/schema/BreweryPostLikeSchema.ts b/src/services/likes/brewery-post-like/schema/BreweryPostLikeSchema.ts new file mode 100644 index 0000000..754ff31 --- /dev/null +++ b/src/services/likes/brewery-post-like/schema/BreweryPostLikeSchema.ts @@ -0,0 +1,11 @@ +import { z } from 'zod'; + +const BreweryPostLikeSchema = z.object({ + id: z.string().cuid(), + breweryPostId: z.string().cuid(), + likedById: z.string().cuid(), + createdAt: z.coerce.date(), + updatedAt: z.coerce.date().nullable(), +}); + +export default BreweryPostLikeSchema; diff --git a/src/services/likes/brewery-post-like/types/index.ts b/src/services/likes/brewery-post-like/types/index.ts new file mode 100644 index 0000000..f3d0581 --- /dev/null +++ b/src/services/likes/brewery-post-like/types/index.ts @@ -0,0 +1,24 @@ +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; +import { z } from 'zod'; +import BreweryPostLikeSchema from '../schema/BreweryPostLikeSchema'; + +type User = z.infer; +type ReturnSchema = z.infer; + +export type CreateBreweryPostLike = (args: { + breweryPostId: string; + likedById: User['id']; +}) => Promise; + +export type FindBreweryPostLike = (args: { + breweryPostId: string; + likedById: User['id']; +}) => Promise; + +export type RemoveBreweryPostLike = (args: { + breweryPostLikeId: string; +}) => Promise; + +export type GetBreweryPostLikeCount = (args: { + breweryPostId: string; +}) => Promise; diff --git a/src/services/posts/beer-post/index.ts b/src/services/posts/beer-post/index.ts new file mode 100644 index 0000000..679b9f3 --- /dev/null +++ b/src/services/posts/beer-post/index.ts @@ -0,0 +1,276 @@ +import DBClient from '@/prisma/DBClient'; +import { + CreateNewBeerPost, + EditBeerPostById, + FindOrDeleteBeerPostById, + GetAllBeerPosts, + GetAllBeerPostsByBreweryId, + GetAllBeerPostsByPostedById, + GetAllBeerPostsByStyleId, + GetBeerRecommendations, +} from './types'; + +/** + * The select object for retrieving beer posts. + * + * Satisfies the BeerPostQueryResult zod schema. + * + * @example + * const beerPosts = await DBClient.instance.beerPost.findMany({ + * select: beerPostSelect, + * }); + */ +const beerPostSelect = { + id: true, + name: true, + description: true, + abv: true, + ibu: true, + createdAt: true, + updatedAt: true, + beerImages: { + select: { + alt: true, + path: true, + caption: true, + id: true, + createdAt: true, + updatedAt: true, + }, + }, + brewery: { select: { id: true, name: true } }, + style: { select: { id: true, name: true, description: true } }, + postedBy: { select: { id: true, username: true } }, +} as const; + +/** + * Creates a new beer post. + * + * @param params - The parameters object for creating the beer post. + * @param params.name - The name of the beer. + * @param params.description - The description of the beer. + * @param params.abv - The alcohol by volume of the beer. + * @param params.ibu - The International Bitterness Units of the beer. + * @param params.styleId - The ID of the beer style. + * @param params.breweryId - The ID of the brewery. + * @param params.userId - The ID of the user who posted the beer. + * @returns A promise that resolves to the newly created beer post. + */ +export const createNewBeerPost: CreateNewBeerPost = ({ + name, + description, + abv, + ibu, + styleId, + breweryId, + userId, +}) => { + return DBClient.instance.beerPost.create({ + data: { + name, + description, + abv, + ibu, + style: { connect: { id: styleId } }, + postedBy: { connect: { id: userId } }, + brewery: { connect: { id: breweryId } }, + }, + select: beerPostSelect, + }); +}; + +/** + * Retrieves a beer post by ID. + * + * @param params - The parameters object for retrieving the beer post. + * @param params.beerPostId - The ID of the beer post to retrieve. + * @returns A promise that resolves to the beer post. + */ +export const getBeerPostById: FindOrDeleteBeerPostById = async ({ beerPostId }) => { + return DBClient.instance.beerPost.findFirst({ + where: { id: beerPostId }, + select: beerPostSelect, + }); +}; + +/** + * Retrieves all beer posts with pagination. + * + * @param params - The parameters object for retrieving beer posts. + * @param params.pageNum The page number to retrieve. + * @param params.pageSize The number of beer posts per page. + * @returns An object containing the beer posts and the total count. + */ +export const getAllBeerPostsService: GetAllBeerPosts = async ({ pageNum, pageSize }) => { + const beerPosts = await DBClient.instance.beerPost.findMany({ + select: beerPostSelect, + take: pageSize, + skip: (pageNum - 1) * pageSize, + orderBy: { createdAt: 'desc' }, + }); + + const count = await DBClient.instance.beerPost.count(); + + return { beerPosts, count }; +}; + +/** + * Retrieves beer posts by beer style ID. + * + * @param params - The parameters object for retrieving beer posts. + * @param params.pageNum - The page number of the results. + * @param params.pageSize - The number of results per page. + * @param params.styleId - The ID of the beer style. + * @returns A promise that resolves to an object containing the beer posts and the total + * count. + */ +export const getBeerPostsByBeerStyleIdService: GetAllBeerPostsByStyleId = async ({ + pageNum, + pageSize, + styleId, +}) => { + const beerPosts = await DBClient.instance.beerPost.findMany({ + where: { styleId }, + take: pageSize, + skip: (pageNum - 1) * pageSize, + select: beerPostSelect, + }); + + const count = await DBClient.instance.beerPost.count({ + where: { styleId }, + }); + + return { beerPosts, count }; +}; + +/** + * Retrieves beer posts by brewery ID. + * + * @param params - The parameters object for retrieving beer posts. + * @param params.pageNum - The page number of the results. + * @param params.pageSize - The number of beer posts per page. + * @param params.breweryId - The ID of the brewery. + * @returns A promise that resolves to an object containing the beer posts and the total + * count. + */ +export const getBeerPostsByBreweryIdService: GetAllBeerPostsByBreweryId = async ({ + pageNum, + pageSize, + breweryId, +}) => { + const beerPosts = await DBClient.instance.beerPost.findMany({ + where: { breweryId }, + take: pageSize, + skip: (pageNum - 1) * pageSize, + select: beerPostSelect, + }); + + const count = await DBClient.instance.beerPost.count({ + where: { breweryId }, + }); + + return { beerPosts, count }; +}; + +/** + * Retrieves beer posts by the ID of the user who posted them. + * + * @param params - The parameters object for retrieving beer posts. + * @param params.pageNum The page number of the results. + * @param params.pageSize The number of results per page. + * @param params.postedById The ID of the user who posted the beer posts. + * @returns A promise that resolves to an object containing the beer posts and the total + * count. + */ +export const getBeerPostsByPostedByIdService: GetAllBeerPostsByPostedById = async ({ + pageNum, + pageSize, + postedById, +}) => { + const beerPosts = await DBClient.instance.beerPost.findMany({ + where: { postedById }, + take: pageSize, + skip: (pageNum - 1) * pageSize, + select: beerPostSelect, + }); + + const count = await DBClient.instance.beerPost.count({ + where: { postedById }, + }); + + return { beerPosts, count }; +}; + +/** + * Retrieves beer recommendations based on the given parameters. + * + * @param params - The parameters object for retrieving beer recommendations. + * @param params.beerPost - The beer post for which recommendations are requested. + * @param params.pageNum - The page number of the recommendations. + * @param params.pageSize - The number of recommendations per page. + * @returns A promise that resolves to an object containing the beer recommendations and + * the total count. + */ +export const getBeerRecommendationsService: GetBeerRecommendations = async ({ + beerPost, + pageNum, + pageSize, +}) => { + const beerRecommendations = await DBClient.instance.beerPost.findMany({ + where: { + OR: [{ styleId: beerPost.style.id }, { breweryId: beerPost.brewery.id }], + NOT: { id: beerPost.id }, + }, + take: pageSize, + skip: (pageNum - 1) * pageSize, + select: beerPostSelect, + }); + + const count = await DBClient.instance.beerPost.count({ + where: { + OR: [{ styleId: beerPost.style.id }, { breweryId: beerPost.brewery.id }], + NOT: { id: beerPost.id }, + }, + }); + + return { beerRecommendations, count }; +}; + +/** + * Service for editing a beer post by ID. + * + * @param params - The parameters object for editing the beer post. + * @param params.beerPostId - The ID of the beer post to edit. + * @param params.body - The updated data for the beer post. + * @param params.body.abv - The updated ABV (Alcohol By Volume) of the beer. + * @param params.body.description - The updated description of the beer. + * @param params.body.ibu - The updated IBU (International Bitterness Units) of the beer. + * @param params.body.name - The updated name of the beer. + * @returns - A promise that resolves to the updated beer post. + */ +export const editBeerPostByIdService: EditBeerPostById = async ({ + body: { abv, description, ibu, name }, + beerPostId, +}) => { + return DBClient.instance.beerPost.update({ + where: { id: beerPostId }, + data: { abv, description, ibu, name }, + select: beerPostSelect, + }); +}; + +/** + * Service for deleting a beer post by ID. + * + * @param params - The parameters object for deleting the beer post. + * @param params.beerPostId - The ID of the beer post to delete. + * @returns - A promise that resolves to the deleted beer post. + */ +export const deleteBeerPostByIdService: FindOrDeleteBeerPostById = async ({ + beerPostId, +}) => { + return DBClient.instance.beerPost.delete({ + where: { id: beerPostId }, + select: beerPostSelect, + }); +}; diff --git a/src/services/BeerPost/schema/BeerPostQueryResult.ts b/src/services/posts/beer-post/schema/BeerPostQueryResult.ts similarity index 100% rename from src/services/BeerPost/schema/BeerPostQueryResult.ts rename to src/services/posts/beer-post/schema/BeerPostQueryResult.ts diff --git a/src/services/BeerPost/schema/CreateBeerPostValidationSchema.ts b/src/services/posts/beer-post/schema/CreateBeerPostValidationSchema.ts similarity index 100% rename from src/services/BeerPost/schema/CreateBeerPostValidationSchema.ts rename to src/services/posts/beer-post/schema/CreateBeerPostValidationSchema.ts diff --git a/src/services/BeerPost/schema/EditBeerPostValidationSchema.ts b/src/services/posts/beer-post/schema/EditBeerPostValidationSchema.ts similarity index 94% rename from src/services/BeerPost/schema/EditBeerPostValidationSchema.ts rename to src/services/posts/beer-post/schema/EditBeerPostValidationSchema.ts index f9f7233..98adce8 100644 --- a/src/services/BeerPost/schema/EditBeerPostValidationSchema.ts +++ b/src/services/posts/beer-post/schema/EditBeerPostValidationSchema.ts @@ -3,7 +3,7 @@ import CreateBeerPostValidationSchema from './CreateBeerPostValidationSchema'; const EditBeerPostValidationSchema = CreateBeerPostValidationSchema.omit({ breweryId: true, - typeId: true, + styleId: true, }).extend({ id: z.string().cuid() }); export default EditBeerPostValidationSchema; diff --git a/src/services/posts/beer-post/types/index.ts b/src/services/posts/beer-post/types/index.ts new file mode 100644 index 0000000..2e8fd0b --- /dev/null +++ b/src/services/posts/beer-post/types/index.ts @@ -0,0 +1,60 @@ +import { z } from 'zod'; +import BeerPostQueryResult from '../schema/BeerPostQueryResult'; +import EditBeerPostValidationSchema from '../schema/EditBeerPostValidationSchema'; +import CreateBeerPostValidationSchema from '../schema/CreateBeerPostValidationSchema'; + +const CreateSchema = CreateBeerPostValidationSchema.extend({ + userId: z.string().cuid(), +}); +const EditSchema = EditBeerPostValidationSchema.omit({ id: true }); + +type BeerPost = z.infer; + +export type CreateNewBeerPost = (args: z.infer) => Promise; + +export type EditBeerPostById = (args: { + beerPostId: string; + body: z.infer; +}) => Promise; + +export type FindOrDeleteBeerPostById = (args: { + beerPostId: string; +}) => Promise; + +export type GetAllBeerPosts = (args: { pageNum: number; pageSize: number }) => Promise<{ + beerPosts: BeerPost[]; + count: number; +}>; + +export type GetAllBeerPostsByPostedById = (args: { + postedById: string; + pageSize: number; + pageNum: number; +}) => Promise<{ + beerPosts: BeerPost[]; + count: number; +}>; + +export type GetAllBeerPostsByStyleId = (args: { + styleId: string; + pageSize: number; + pageNum: number; +}) => Promise<{ + beerPosts: BeerPost[]; + count: number; +}>; + +export type GetAllBeerPostsByBreweryId = (args: { + breweryId: string; + pageSize: number; + pageNum: number; +}) => Promise<{ + beerPosts: BeerPost[]; + count: number; +}>; + +export type GetBeerRecommendations = (args: { + beerPost: BeerPost; + pageNum: number; + pageSize: number; +}) => Promise<{ beerRecommendations: BeerPost[]; count: number }>; diff --git a/src/services/posts/beer-style-post/index.ts b/src/services/posts/beer-style-post/index.ts new file mode 100644 index 0000000..0a18b28 --- /dev/null +++ b/src/services/posts/beer-style-post/index.ts @@ -0,0 +1,182 @@ +import DBClient from '@/prisma/DBClient'; +import ServerError from '@/config/util/ServerError'; +import type { + CreateBeerStyle, + DeleteBeerStyleById, + EditBeerStyleById, + GetAllBeerStyles, + GetBeerStyleById, +} from '@/services/posts/beer-style-post/types'; + +/** + * The select object for retrieving beer styles. + * + * Satisfies the BeerStyleQueryResult zod schema. + * + * @remarks + * Prisma does not support tuples, so we have to typecast the ibuRange and abvRange fields + * to satisfy the zod schema. + * @example + * const beerStyles = await DBClient.instance.beerStyle.findMany({ + * select: beerStyleSelect, + * }); + */ +const beerStyleSelect = { + id: true, + name: true, + createdAt: true, + updatedAt: true, + abvRange: true, + ibuRange: true, + description: true, + postedBy: { select: { id: true, username: true } }, + glassware: { select: { id: true, name: true } }, +} as const; + +/** + * Deletes a beer style by id. + * + * @param args - The arguments for the service. + * @param args.beerStyleId - The id of the beer style to delete. + * @returns The deleted beer style. + */ +export const deleteBeerStyleService: DeleteBeerStyleById = async ({ beerStyleId }) => { + const deleted = await DBClient.instance.beerStyle.delete({ + where: { id: beerStyleId }, + select: beerStyleSelect, + }); + + return deleted as Awaited>; +}; + +/** + * Edits a beer style by id. + * + * @param args - The arguments for the service. + * @param args.beerStyleId - The id of the beer style to edit. + * @param args.body - The data to update the beer style with. + * @param args.body.abvRange - The abv range of the beer style. + * @param args.body.description - The description of the beer style. + * @param args.body.glasswareId - The id of the glassware to connect to the beer style. + * @param args.body.ibuRange - The ibu range of the beer style. + * @param args.body.name - The name of the beer style. + * @returns The updated beer style. + */ +export const editBeerStyleService: EditBeerStyleById = async ({ beerStyleId, body }) => { + const { abvRange, description, glasswareId, ibuRange, name } = body; + + const glassware = await DBClient.instance.glassware.findUnique({ + where: { id: glasswareId }, + select: { id: true }, + }); + + if (!glassware) { + throw new ServerError( + 'A glassware with that id does not exist and cannot be connected.', + 404, + ); + } + + const updated = await DBClient.instance.beerStyle.update({ + where: { id: beerStyleId }, + data: { + abvRange, + description, + ibuRange, + name, + glassware: { connect: { id: glasswareId } }, + }, + select: beerStyleSelect, + }); + + return updated as Awaited>; +}; + +/** + * Gets all beer styles with pagination. + * + * @param args - The arguments for the service. + * @param args.pageNum - The page number of the results. + * @param args.pageSize - The page size of the results. + * @returns The beer styles and the total count of beer styles. + */ +export const getAllBeerStylesService: GetAllBeerStyles = async ({ + pageNum, + pageSize, +}) => { + const beerStyles = await DBClient.instance.beerStyle.findMany({ + take: pageSize, + skip: (pageNum - 1) * pageSize, + select: beerStyleSelect, + }); + + const beerStyleCount = await DBClient.instance.beerStyle.count(); + + return { + beerStyles: beerStyles as Awaited< + ReturnType + >['beerStyles'], + beerStyleCount, + }; +}; + +/** + * Gets a beer style by id. + * + * @param args - The arguments for the service. + * @param args.beerStyleId - The id of the beer style to get. + * @returns The beer style. + */ +export const getBeerStyleByIdService: GetBeerStyleById = async ({ beerStyleId }) => { + const beerStyle = await DBClient.instance.beerStyle.findUnique({ + where: { id: beerStyleId }, + select: beerStyleSelect, + }); + + return beerStyle as Awaited>; +}; + +/** + * Creates a beer style. + * + * @param args - The arguments for the service. + * @param args.body - The data to create the beer style with. + * @param args.body.abvRange - The abv range of the beer style. + * @param args.body.description - The description of the beer style. + * @param args.body.glasswareId - The id of the glassware to connect to the beer style. + * @param args.body.ibuRange - The ibu range of the beer style. + * @param args.body.name - The name of the beer style. + * @param args.glasswareId - The id of the glassware to connect to the beer style. + * @param args.postedById - The id of the user who posted the beer style. + */ +export const createBeerStyleService: CreateBeerStyle = async ({ + body: { abvRange, description, ibuRange, name }, + glasswareId, + postedById, +}) => { + const glassware = await DBClient.instance.glassware.findUnique({ + where: { id: glasswareId }, + select: { id: true }, + }); + + if (!glassware) { + throw new ServerError( + 'A glassware with that id does not exist and cannot be connected.', + 404, + ); + } + + const beerStyle = await DBClient.instance.beerStyle.create({ + data: { + name, + description, + abvRange, + ibuRange, + glassware: { connect: { id: glasswareId } }, + postedBy: { connect: { id: postedById } }, + }, + select: beerStyleSelect, + }); + + return beerStyle as Awaited>; +}; diff --git a/src/services/BeerStyles/schema/BeerStyleQueryResult.ts b/src/services/posts/beer-style-post/schema/BeerStyleQueryResult.ts similarity index 100% rename from src/services/BeerStyles/schema/BeerStyleQueryResult.ts rename to src/services/posts/beer-style-post/schema/BeerStyleQueryResult.ts diff --git a/src/services/BeerStyles/schema/CreateBeerStyleValidationSchema.ts b/src/services/posts/beer-style-post/schema/CreateBeerStyleValidationSchema.ts similarity index 100% rename from src/services/BeerStyles/schema/CreateBeerStyleValidationSchema.ts rename to src/services/posts/beer-style-post/schema/CreateBeerStyleValidationSchema.ts diff --git a/src/services/posts/beer-style-post/types/index.ts b/src/services/posts/beer-style-post/types/index.ts new file mode 100644 index 0000000..9790bf7 --- /dev/null +++ b/src/services/posts/beer-style-post/types/index.ts @@ -0,0 +1,39 @@ +import { z } from 'zod'; +import BeerStyleQueryResult from '../schema/BeerStyleQueryResult'; + +type BeerStyle = z.infer; + +export type GetBeerStyleById = (args: { + beerStyleId: string; +}) => Promise; + +export type DeleteBeerStyleById = (args: { + beerStyleId: string; +}) => Promise; + +export type EditBeerStyleById = (args: { + beerStyleId: string; + body: { + name: string; + description: string; + abvRange: [number, number]; + ibuRange: [number, number]; + glasswareId: string; + }; +}) => Promise; + +export type GetAllBeerStyles = (args: { pageNum: number; pageSize: number }) => Promise<{ + beerStyles: BeerStyle[]; + beerStyleCount: number; +}>; + +export type CreateBeerStyle = (args: { + body: { + name: string; + description: string; + abvRange: [number, number]; + ibuRange: [number, number]; + }; + glasswareId: string; + postedById: string; +}) => Promise; diff --git a/src/services/posts/brewery-post/index.ts b/src/services/posts/brewery-post/index.ts new file mode 100644 index 0000000..0c3875f --- /dev/null +++ b/src/services/posts/brewery-post/index.ts @@ -0,0 +1,241 @@ +import DBClient from '@/prisma/DBClient'; + +import { + CreateBreweryPostLocation, + CreateNewBreweryPost, + GetAllBreweryPosts, + GetAllBreweryPostsByPostedById, + GetBreweryPostById, + GetMapBreweryPosts, + UpdateBreweryPost, +} from './types'; + +/** + * The select object to use when querying for brewery posts. + * + * @remarks + * Prisma does not support tuples, so we have to typecast the coordinates field to + * [number, number] in order to satisfy the zod schema. + */ +const breweryPostSelect = { + id: true, + name: true, + description: true, + createdAt: true, + dateEstablished: true, + postedBy: { select: { id: true, username: true } }, + breweryImages: { + select: { + path: true, + caption: true, + id: true, + alt: true, + createdAt: true, + updatedAt: true, + }, + }, + location: { + select: { + city: true, + address: true, + coordinates: true, + country: true, + stateOrProvince: true, + }, + }, +} as const; + +/** + * Creates a new brewery post. + * + * @param args - The arguments to create a new brewery post. + * @param args.name - The name of the brewery. + * @param args.description - The description of the brewery. + * @param args.dateEstablished - The date the brewery was established. + * @param args.userId - The id of the user who created the brewery post. + * @param args.locationId - The id of the location of the brewery. + * @returns The newly created brewery post. + */ +export const createNewBreweryPostService: CreateNewBreweryPost = async ({ + dateEstablished, + description, + locationId, + name, + userId, +}) => { + const post = (await DBClient.instance.breweryPost.create({ + data: { + name, + description, + dateEstablished, + location: { connect: { id: locationId } }, + postedBy: { connect: { id: userId } }, + }, + select: breweryPostSelect, + })) as Awaited>; + + return post; +}; + +/** + * Retrieves all brewery posts paginated. + * + * @param args - The arguments to get all brewery posts. + * @param args.pageNum - The page number of the brewery posts to get. + * @param args.pageSize - The number of brewery posts to get per page. + * @returns All brewery posts. + */ +export const getAllBreweryPostsService: GetAllBreweryPosts = async ({ + pageNum, + pageSize, +}) => { + const breweryPosts = (await DBClient.instance.breweryPost.findMany({ + take: pageSize, + skip: (pageNum - 1) * pageSize, + select: breweryPostSelect, + orderBy: { createdAt: 'desc' }, + })) as Awaited>['breweryPosts']; + + const count = await DBClient.instance.breweryPost.count(); + return { breweryPosts, count }; +}; + +/** + * Retrieves a brewery post by ID. + * + * @param args - The arguments to get a brewery post by ID. + * @param args.breweryPostId - The ID of the brewery post to get. + * @returns The brewery post. + */ +export const getBreweryPostByIdService: GetBreweryPostById = async ({ + breweryPostId, +}) => { + const breweryPost = await DBClient.instance.breweryPost.findFirst({ + select: breweryPostSelect, + where: { id: breweryPostId }, + }); + + return breweryPost as Awaited>; +}; + +/** + * Retrieves all brewery posts by posted by ID. + * + * @param args - The arguments to get all brewery posts by posted by ID. + * @param args.pageNum - The page number of the brewery posts to get. + * @param args.pageSize - The number of brewery posts to get per page. + * @param args.postedById - The ID of the user who posted the brewery posts. + */ +export const getAllBreweryPostsByPostedByIdService: GetAllBreweryPostsByPostedById = + async ({ pageNum, pageSize, postedById }) => { + const breweryPosts = (await DBClient.instance.breweryPost.findMany({ + where: { postedBy: { id: postedById } }, + take: pageSize, + skip: (pageNum - 1) * pageSize, + select: breweryPostSelect, + orderBy: { createdAt: 'desc' }, + })) as Awaited< + ReturnType + >['breweryPosts']; + + const count = await DBClient.instance.breweryPost.count({ + where: { postedBy: { id: postedById } }, + }); + + return { breweryPosts, count }; + }; + +/** + * Creates a brewery post location. + * + * @param args - The arguments to create a brewery post location. + * @param args.body - The body of the request. + * @param args.body.address - The address of the brewery. + * @param args.body.city - The city of the brewery. + * @param args.body.country - The country of the brewery. + * @param args.body.stateOrProvince - The state or province of the brewery. + * @param args.body.coordinates - The coordinates of the brewery in an array of [latitude, + * longitude]. + * @param args.postedById - The ID of the user who posted the brewery post. + * @returns The newly created brewery post location. + */ +export const createBreweryPostLocationService: CreateBreweryPostLocation = async ({ + body: { address, city, country, stateOrProvince, coordinates }, + postedById, +}) => { + const [latitude, longitude] = coordinates; + + return DBClient.instance.breweryLocation.create({ + data: { + address, + city, + country, + stateOrProvince, + coordinates: [latitude, longitude], + postedBy: { connect: { id: postedById } }, + }, + select: { id: true }, + }); +}; + +/** + * Gets all brewery posts for the post map. + * + * @param args - The arguments to get all brewery posts for the post map. + * @param args.pageNum - The page number of the brewery posts to get. + * @param args.pageSize - The number of brewery posts to get per page. + * @returns All brewery posts for the post map. + */ +export const getMapBreweryPostsService: GetMapBreweryPosts = async ({ + pageNum, + pageSize, +}) => { + const breweryPosts = await DBClient.instance.breweryPost.findMany({ + take: pageSize, + skip: (pageNum - 1) * pageSize, + select: { + id: true, + name: true, + location: { + select: { coordinates: true, city: true, country: true, stateOrProvince: true }, + }, + }, + orderBy: { createdAt: 'desc' }, + }); + + const count = await DBClient.instance.breweryPost.count(); + return { breweryPosts, count }; +}; + +/** + * Updates a brewery post. + * + * @param args - The arguments to update a brewery post. + * @param args.breweryPostId - The ID of the brewery post to update. + * @param args.body - The body of the request. + * @param args.body.name - The name of the brewery. + * @param args.body.description - The description of the brewery. + * @param args.body.dateEstablished - The date the brewery was established. + * @returns The updated brewery post. + */ +export const updateBreweryPostService: UpdateBreweryPost = async ({ + breweryPostId, + body, +}) => { + const breweryPost = await DBClient.instance.breweryPost.update({ + where: { id: breweryPostId }, + data: body, + select: breweryPostSelect, + }); + + return breweryPost as Awaited>; +}; + +export const deleteBreweryPostService: GetBreweryPostById = async ({ breweryPostId }) => { + const breweryPost = await DBClient.instance.breweryPost.delete({ + where: { id: breweryPostId }, + select: breweryPostSelect, + }); + + return breweryPost as Awaited>; +}; diff --git a/src/services/BreweryPost/schema/BreweryPostMapQueryResult.ts b/src/services/posts/brewery-post/schema/BreweryPostMapQueryResult.ts similarity index 100% rename from src/services/BreweryPost/schema/BreweryPostMapQueryResult.ts rename to src/services/posts/brewery-post/schema/BreweryPostMapQueryResult.ts diff --git a/src/services/BreweryPost/schema/BreweryPostQueryResult.ts b/src/services/posts/brewery-post/schema/BreweryPostQueryResult.ts similarity index 100% rename from src/services/BreweryPost/schema/BreweryPostQueryResult.ts rename to src/services/posts/brewery-post/schema/BreweryPostQueryResult.ts diff --git a/src/services/BreweryPost/schema/CreateBreweryPostSchema.ts b/src/services/posts/brewery-post/schema/CreateBreweryPostSchema.ts similarity index 100% rename from src/services/BreweryPost/schema/CreateBreweryPostSchema.ts rename to src/services/posts/brewery-post/schema/CreateBreweryPostSchema.ts diff --git a/src/services/posts/brewery-post/schema/CreateNewBreweryPostWithoutLocationSchema.ts b/src/services/posts/brewery-post/schema/CreateNewBreweryPostWithoutLocationSchema.ts new file mode 100644 index 0000000..7eca3c7 --- /dev/null +++ b/src/services/posts/brewery-post/schema/CreateNewBreweryPostWithoutLocationSchema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; +import CreateBreweryPostSchema from './CreateBreweryPostSchema'; + +const CreateNewBreweryPostWithoutLocationSchema = CreateBreweryPostSchema.omit({ + address: true, + city: true, + country: true, + stateOrProvince: true, +}).extend({ + userId: z.string().cuid(), + locationId: z.string().cuid(), +}); + +export default CreateNewBreweryPostWithoutLocationSchema; diff --git a/src/services/BreweryPost/schema/EditBreweryPostValidationSchema.ts b/src/services/posts/brewery-post/schema/EditBreweryPostValidationSchema.ts similarity index 100% rename from src/services/BreweryPost/schema/EditBreweryPostValidationSchema.ts rename to src/services/posts/brewery-post/schema/EditBreweryPostValidationSchema.ts diff --git a/src/services/posts/brewery-post/schema/GetMapBreweryPostsSchema.ts b/src/services/posts/brewery-post/schema/GetMapBreweryPostsSchema.ts new file mode 100644 index 0000000..93ced18 --- /dev/null +++ b/src/services/posts/brewery-post/schema/GetMapBreweryPostsSchema.ts @@ -0,0 +1,14 @@ +import { z } from 'zod'; + +const GetMapBreweryPostsSchema = z.object({ + name: z.string(), + id: z.string().cuid(), + location: z.object({ + city: z.string(), + country: z.string().nullable(), + stateOrProvince: z.string().nullable(), + coordinates: z.array(z.number(), z.number()), + }), +}); + +export default GetMapBreweryPostsSchema; diff --git a/src/services/posts/brewery-post/types/index.ts b/src/services/posts/brewery-post/types/index.ts new file mode 100644 index 0000000..4fd2bd0 --- /dev/null +++ b/src/services/posts/brewery-post/types/index.ts @@ -0,0 +1,57 @@ +import { z } from 'zod'; +import BreweryPostQueryResult from '../schema/BreweryPostQueryResult'; +import CreateNewBreweryPostWithoutLocationSchema from '../schema/CreateNewBreweryPostWithoutLocationSchema'; +import BreweryPostMapQueryResult from '../schema/BreweryPostMapQueryResult'; + +export type CreateNewBreweryPost = ( + args: z.infer, +) => Promise>; + +export type GetAllBreweryPosts = (args: { + pageNum: number; + pageSize: number; +}) => Promise<{ + breweryPosts: z.infer[]; + count: number; +}>; + +export type GetBreweryPostById = (args: { + breweryPostId: string; +}) => Promise | null>; + +export type GetAllBreweryPostsByPostedById = (args: { + pageNum: number; + pageSize: number; + postedById: string; +}) => Promise<{ + breweryPosts: z.infer[]; + count: number; +}>; + +export type CreateBreweryPostLocation = (args: { + body: { + address: string; + city: string; + country?: string; + stateOrProvince?: string; + coordinates: [number, number]; + }; + postedById: string; +}) => Promise<{ id: string }>; + +export type GetMapBreweryPosts = (args: { + pageNum: number; + pageSize: number; +}) => Promise<{ + breweryPosts: z.infer[]; + count: number; +}>; + +export type UpdateBreweryPost = (args: { + breweryPostId: string; + body: { + name: string; + description: string; + dateEstablished: Date; + }; +}) => Promise>; diff --git a/src/services/users/auth/index.ts b/src/services/users/auth/index.ts new file mode 100644 index 0000000..69e8081 --- /dev/null +++ b/src/services/users/auth/index.ts @@ -0,0 +1,308 @@ +/* eslint-disable import/prefer-default-export */ +import { hashPassword } from '@/config/auth/passwordFns'; +import DBClient from '@/prisma/DBClient'; +import { BASE_URL } from '@/config/env'; +import { generateConfirmationToken, generateResetPasswordToken } from '@/config/jwt'; +import sendEmail from '@/config/sparkpost/sendEmail'; + +import { ReactElement } from 'react'; +import ServerError from '@/config/util/ServerError'; +import { render } from '@react-email/render'; +import WelcomeEmail from '@/emails/WelcomeEmail'; +import ResetPasswordEmail from '@/emails/ForgotEmail'; + +import { + CreateNewUser, + DeleteUserById, + FindUserByEmail, + FindUserByUsername, + FindUserById, + SendConfirmationEmail, + SendResetPasswordEmail, + UpdateUserToBeConfirmedById, + UpdateUserPassword, + UpdateUserById, +} from './types'; + +/** + * The select object for retrieving users. + * + * Satisfies the GetUserSchema zod schema. + * + * @example + * const users = await DBClient.instance.user.findMany({ + * select: userSelect, + * }); + */ +const userSelect = { + id: true, + username: true, + email: true, + firstName: true, + lastName: true, + dateOfBirth: true, + createdAt: true, + accountIsVerified: true, + updatedAt: true, + role: true, + userAvatar: true, + bio: true, +} as const; + +/** + * The select object for retrieving users without sensitive information. + * + * @example + * const user = await DBClient.instance.user.findUnique({ + * where: { id: userId }, + * select: AuthUserSelect, + * }); + */ +const authUserSelect = { + id: true, + username: true, + hash: true, +} as const; + +/** + * Creates a new user. + * + * @param args The arguments for service. + * @param args.email The email of the user to create. + * @param args.password The password of the user to create. + * @param args.firstName The first name of the user to create. + * @param args.lastName The last name of the user to create. + * @param args.dateOfBirth The date of birth of the user to create. + * @param args.username The username of the user to create. + * @returns The user. + */ +export const createNewUserService: CreateNewUser = async ({ + email, + password, + firstName, + lastName, + dateOfBirth, + username, +}) => { + const hash = await hashPassword(password); + + const user = await DBClient.instance.user.create({ + data: { + username, + email, + hash, + firstName, + lastName, + dateOfBirth: new Date(dateOfBirth), + }, + select: userSelect, + }); + + return user; +}; + +/** + * Deletes a user by id. + * + * @param args The arguments for service. + * @param args.userId The id of the user to delete. + * @returns The user that was deleted if found, otherwise null. + */ +export const deleteUserService: DeleteUserById = ({ userId }) => { + return DBClient.instance.user.delete({ where: { id: userId }, select: authUserSelect }); +}; + +/** + * Finds a user by username. + * + * @param args The arguments for service. + * @param args.username The username of the user to find. + * @returns The user if found, otherwise null. + */ + +export const findUserByUsernameService: FindUserByUsername = async ({ username }) => { + return DBClient.instance.user.findUnique({ + where: { username }, + select: authUserSelect, + }); +}; + +/** + * Finds a user by email. + * + * @param args The arguments for service. + * @param args.email The email of the user to find. + */ +export const findUserByEmailService: FindUserByEmail = async ({ email }) => { + return DBClient.instance.user.findUnique({ where: { email }, select: userSelect }); +}; + +/** + * Finds a user by id. + * + * @param args The arguments for service. + * @param args.userId The id of the user to find. + * @returns The user if found, otherwise null. + */ +export const findUserByIdService: FindUserById = ({ userId }) => { + return DBClient.instance.user.findUnique({ where: { id: userId }, select: userSelect }); +}; + +/** + * Sends a confirmation email to the user using React Email and SparkPost. + * + * @param args The arguments for service. + * @param args.userId The id of the user to send the confirmation email to. + * @param args.username The username of the user to send the confirmation email to. + * @param args.email The email of the user to send the confirmation email to. + * @returns The user if found, otherwise null. + */ +export const sendConfirmationEmailService: SendConfirmationEmail = async ({ + userId, + username, + email, +}) => { + const confirmationToken = generateConfirmationToken({ id: userId, username }); + const url = `${BASE_URL}/users/confirm?token=${confirmationToken}`; + + const name = username; + const address = email; + const subject = 'Confirm your email'; + + const component = WelcomeEmail({ name, url, subject })! as ReactElement< + unknown, + string + >; + + const html = render(component); + const text = render(component, { plainText: true }); + + await sendEmail({ address, subject, text, html }); +}; + +/** + * Sends a reset password email to the specified user. + * + * @param args The arguments for service. + * @param args.userId The id of the user to send the reset password email to. + * @param args.username The username of the user to send the reset password email to. + * @param args.email The email of the user to send the reset password email to. + * @returns A promise that resolves to void. + */ +export const sendResetPasswordEmailService: SendResetPasswordEmail = async ({ + userId, + username, + email, +}) => { + const token = generateResetPasswordToken({ id: userId, username }); + + const url = `${BASE_URL}/users/reset-password?token=${token}`; + + const component = ResetPasswordEmail({ name: username, url })! as ReactElement< + unknown, + string + >; + + const html = render(component); + const text = render(component, { plainText: true }); + + await sendEmail({ + address: email, + subject: 'Reset Password', + html, + text, + }); +}; + +/** + * Updates a user to be confirmed by id. + * + * @param args The arguments for service. + * @param args.userId The id of the user to update. + * @returns The user. + */ +export const confirmUserService: UpdateUserToBeConfirmedById = async ({ userId }) => { + return DBClient.instance.user.update({ + where: { id: userId }, + data: { accountIsVerified: true, updatedAt: new Date() }, + select: userSelect, + }); +}; + +export const updateUserPasswordService: UpdateUserPassword = async ({ + password, + userId, +}) => { + const hash = await hashPassword(password); + + const user = await DBClient.instance.user.update({ + where: { id: userId }, + data: { hash, updatedAt: new Date() }, + select: authUserSelect, + }); + + return user; +}; + +/** + * Updates a user by id. + * + * @param args The arguments for service. + * @param args.userId The id of the user to update. + * @param args.data The data to update the user with. + * @param args.data.email The email of the user to update. + * @param args.data.firstName The first name of the user to update. + * @param args.data.lastName The last name of the user to update. + * @param args.data.username The username of the user to update. + */ +export const updateUserService: UpdateUserById = async ({ userId, data }) => { + const user = await DBClient.instance.user.findUnique({ + where: { id: userId }, + select: userSelect, + }); + + if (!user) { + throw new ServerError('User not found', 404); + } + + const updatedFields = { + email: data.email !== user.email, + username: data.username !== user.username, + firstName: data.firstName !== user.firstName, + lastName: data.lastName !== user.lastName, + } as const; + + if (updatedFields.email) { + const emailIsTaken = await findUserByEmailService({ email: data.email }); + if (emailIsTaken) { + throw new ServerError('Email is already taken', 400); + } + + await sendConfirmationEmailService({ + userId, + username: data.username, + email: data.email, + }); + } + + if (updatedFields.username) { + const usernameIsTaken = await findUserByUsernameService({ username: data.username }); + if (usernameIsTaken) { + throw new ServerError('Username is already taken', 400); + } + } + + const updatedUser = await DBClient.instance.user.update({ + where: { id: userId }, + data: { + email: updatedFields.email ? data.email : undefined, + username: updatedFields.username ? data.username : undefined, + firstName: updatedFields.firstName ? data.firstName : undefined, + lastName: updatedFields.lastName ? data.lastName : undefined, + accountIsVerified: updatedFields.email ? false : undefined, + }, + select: userSelect, + }); + + return updatedUser; +}; diff --git a/src/services/User/schema/CreateUserValidationSchemas.ts b/src/services/users/auth/schema/CreateUserValidationSchemas.ts similarity index 100% rename from src/services/User/schema/CreateUserValidationSchemas.ts rename to src/services/users/auth/schema/CreateUserValidationSchemas.ts diff --git a/src/services/users/auth/schema/EditUserSchema.ts b/src/services/users/auth/schema/EditUserSchema.ts new file mode 100644 index 0000000..f9d4bad --- /dev/null +++ b/src/services/users/auth/schema/EditUserSchema.ts @@ -0,0 +1,10 @@ +import { BaseCreateUserSchema } from './CreateUserValidationSchemas'; + +const EditUserSchema = BaseCreateUserSchema.pick({ + username: true, + email: true, + firstName: true, + lastName: true, +}); + +export default EditUserSchema; diff --git a/src/services/User/schema/GetUserSchema.ts b/src/services/users/auth/schema/GetUserSchema.ts similarity index 100% rename from src/services/User/schema/GetUserSchema.ts rename to src/services/users/auth/schema/GetUserSchema.ts diff --git a/src/services/User/schema/LoginValidationSchema.ts b/src/services/users/auth/schema/LoginValidationSchema.ts similarity index 100% rename from src/services/User/schema/LoginValidationSchema.ts rename to src/services/users/auth/schema/LoginValidationSchema.ts diff --git a/src/services/users/auth/schema/TokenValidationSchema.ts b/src/services/users/auth/schema/TokenValidationSchema.ts new file mode 100644 index 0000000..3589cd9 --- /dev/null +++ b/src/services/users/auth/schema/TokenValidationSchema.ts @@ -0,0 +1,7 @@ +import z from 'zod'; + +const TokenValidationSchema = z.object({ + token: z.string(), +}); + +export default TokenValidationSchema; diff --git a/src/services/User/schema/UpdateProfileSchema.ts b/src/services/users/auth/schema/UpdateProfileSchema.ts similarity index 100% rename from src/services/User/schema/UpdateProfileSchema.ts rename to src/services/users/auth/schema/UpdateProfileSchema.ts diff --git a/src/services/users/auth/types/index.ts b/src/services/users/auth/types/index.ts new file mode 100644 index 0000000..6389667 --- /dev/null +++ b/src/services/users/auth/types/index.ts @@ -0,0 +1,47 @@ +import { z } from 'zod'; +import GetUserSchema from '../schema/GetUserSchema'; +import { CreateUserValidationSchema } from '../schema/CreateUserValidationSchemas'; + +type User = z.infer; +type AuthUser = { username: string; hash: string; id: string }; + +export type CreateNewUser = ( + args: z.infer, +) => Promise; + +export type DeleteUserById = (args: { userId: string }) => Promise; + +export type FindUserById = (args: { userId: string }) => Promise; + +export type FindUserByUsername = (args: { username: string }) => Promise; + +export type FindUserByEmail = (args: { email: string }) => Promise; + +export type UpdateUserPassword = (args: { + userId: string; + password: string; +}) => Promise; + +export type SendConfirmationEmail = (args: { + userId: string; + username: string; + email: string; +}) => Promise; + +export type SendResetPasswordEmail = (args: { + userId: string; + username: string; + email: string; +}) => Promise; + +export type UpdateUserToBeConfirmedById = (args: { userId: string }) => Promise; + +export type UpdateUserById = (args: { + userId: string; + data: { + email: string; + firstName: string; + lastName: string; + username: string; + }; +}) => Promise; diff --git a/src/services/users/profile/index.ts b/src/services/users/profile/index.ts new file mode 100644 index 0000000..b7734c9 --- /dev/null +++ b/src/services/users/profile/index.ts @@ -0,0 +1,193 @@ +import DBClient from '@/prisma/DBClient'; +import ServerError from '@/config/util/ServerError'; +import { + GetUsersFollowedByOrFollowingUser, + UpdateUserAvatar, + UpdateUserProfileById, + UserFollowService, +} from './types'; + +/** + * The select object for retrieving users. + * + * Satisfies the GetUserSchema zod schema. + * + * @example + * const users = await DBClient.instance.user.findMany({ + * select: userSelect, + * }); + */ +const userSelect = { + id: true, + username: true, + email: true, + firstName: true, + lastName: true, + dateOfBirth: true, + createdAt: true, + accountIsVerified: true, + updatedAt: true, + role: true, + userAvatar: true, + bio: true, +} as const; + +/** + * Finds a user follow by the followerId and followingId. + * + * @returns The user follow if found, otherwise null. + */ +export const findUserFollow: UserFollowService = ({ followerId, followingId }) => { + return DBClient.instance.userFollow.findFirst({ where: { followerId, followingId } }); +}; + +/** + * Creates a new user follow. + * + * @param args The arguments for service. + * @param args.followerId The follower id of the user follow to create. + * @param args.followingId The following id of the user follow to create. + * @returns The user follow. + */ +export const createUserFollow: UserFollowService = ({ followerId, followingId }) => { + return DBClient.instance.userFollow.create({ data: { followerId, followingId } }); +}; + +/** + * Deletes a user follow. + * + * @param args The arguments for service. + * @param args.followerId The follower id of the user follow to delete. + * @param args.followingId The following id of the user follow to delete. + * @returns The user follow. + */ +export const deleteUserFollow: UserFollowService = ({ followerId, followingId }) => { + return DBClient.instance.userFollow.delete({ + where: { followerId_followingId: { followerId, followingId } }, + }); +}; + +/** + * Gets the users followed by the session user. + * + * @param args The arguments for service. + * @param args.userId The id of the user to check if followed by the session user. + * @param args.pageNum The page number of the users to retrieve. + * @param args.pageSize The page size of the users to retrieve. + * @returns The users followed by the queried user and the count of users followed by the + * queried user. + */ +export const getUsersFollowedByUser: GetUsersFollowedByOrFollowingUser = async ({ + userId, + pageNum, + pageSize, +}) => { + const usersFollowedByQueriedUser = await DBClient.instance.userFollow.findMany({ + take: pageSize, + skip: (pageNum - 1) * pageSize, + where: { follower: { id: userId } }, + select: { + follower: { select: { username: true, userAvatar: true, id: true } }, + }, + }); + const count = await DBClient.instance.userFollow.count({ + where: { follower: { id: userId } }, + }); + const follows = usersFollowedByQueriedUser.map((u) => u.follower); + + return { follows, count }; +}; + +/** + * Gets the users following the session user. + * + * @param args The arguments for service. + * @param args.userId The id of the user to check if followed by the session user. + * @param args.pageNum The page number of the users to retrieve. + * @param args.pageSize The page size of the users to retrieve. + */ +export const getUsersFollowingUser: GetUsersFollowedByOrFollowingUser = async ({ + userId, + pageNum, + pageSize, +}) => { + const usersFollowingQueriedUser = await DBClient.instance.userFollow.findMany({ + take: pageSize, + skip: (pageNum - 1) * pageSize, + where: { following: { id: userId } }, + select: { + following: { select: { username: true, userAvatar: true, id: true } }, + }, + }); + + const count = await DBClient.instance.userFollow.count({ + where: { following: { id: userId } }, + }); + + const follows = usersFollowingQueriedUser.map((u) => u.following); + return { follows, count }; +}; + +/** + * Updates the user avatar of the user. + * + * @param args The arguments for service. + * @param args.userId The id of the user to update the avatar of. + * @param args.data The data to update the user avatar with. + * @param args.data.alt The alt text of the user avatar. + * @param args.data.path The path of the user avatar. + * @param args.data.caption The caption of the user avatar. + * @returns The updated user. + */ +export const updateUserAvatar: UpdateUserAvatar = async ({ userId, data }) => { + const user = await DBClient.instance.user.findUnique({ + where: { id: userId }, + select: userSelect, + }); + + if (!user) { + throw new ServerError('User not found', 404); + } + + const updatedUser = await DBClient.instance.user.update({ + where: { id: userId }, + data: { + userAvatar: { + upsert: { + create: { + alt: data.alt, + path: data.path, + caption: data.caption, + }, + update: { + alt: data.alt, + path: data.path, + caption: data.caption, + }, + }, + }, + }, + select: userSelect, + }); + + return updatedUser; +}; + +/** + * Updates a user's profile by id. + * + * @param args The arguments for service. + * @param args.userId The id of the user to update. + * @param args.data The data to update the user with. + * @param args.data.bio The bio of the user. + * @returns The user. + */ +export const updateUserProfileById: UpdateUserProfileById = async ({ userId, data }) => { + const user = await DBClient.instance.user.update({ + where: { id: userId }, + data: { bio: data.bio }, + select: userSelect, + }); + + return user; +}; diff --git a/src/services/UserFollows/schema/FollowInfoSchema.ts b/src/services/users/profile/schema/FollowInfoSchema.ts similarity index 65% rename from src/services/UserFollows/schema/FollowInfoSchema.ts rename to src/services/users/profile/schema/FollowInfoSchema.ts index 86ff7d4..a073006 100644 --- a/src/services/UserFollows/schema/FollowInfoSchema.ts +++ b/src/services/users/profile/schema/FollowInfoSchema.ts @@ -1,4 +1,4 @@ -import GetUserSchema from '@/services/User/schema/GetUserSchema'; +import GetUserSchema from '@/services/users/auth/schema/GetUserSchema'; const FollowInfoSchema = GetUserSchema.pick({ userAvatar: true, diff --git a/src/services/users/profile/types/index.ts b/src/services/users/profile/types/index.ts new file mode 100644 index 0000000..7a3663e --- /dev/null +++ b/src/services/users/profile/types/index.ts @@ -0,0 +1,38 @@ +import { UserFollow } from '@prisma/client'; +import { z } from 'zod'; + +import FollowInfoSchema from '../schema/FollowInfoSchema'; +import GetUserSchema from '../../auth/schema/GetUserSchema'; + +type FollowInfo = z.infer; +type User = z.infer; + +export type UserFollowService = (args: { + followerId: string; + followingId: string; +}) => Promise; + +export type UpdateUserProfileById = (args: { + userId: string; + data: { bio: string }; +}) => Promise; + +export type CheckIfUserIsFollowedBySessionUser = (args: { + followerId: string; + followingId: string; +}) => Promise; + +export type GetUsersFollowedByOrFollowingUser = (args: { + userId: string; + pageNum: number; + pageSize: number; +}) => Promise<{ follows: FollowInfo[]; count: number }>; + +export type UpdateUserAvatar = (args: { + userId: string; + data: { + alt: string; + path: string; + caption: string; + }; +}) => Promise;