Merge pull request #74 from aaronpo97/cleanup

Refactor controller logic out of api routes, refactor services, update documentation
This commit is contained in:
Aaron Po
2023-12-17 23:51:32 -05:00
committed by GitHub
258 changed files with 5074 additions and 4141 deletions

View File

@@ -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"]

171
package-lock.json generated
View File

@@ -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": {

View File

@@ -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"
},

View File

@@ -3,4 +3,3 @@ Disallow: /api/
Disallow: /login/
Disallow: /register/
Disallow: /users/
Disallow: /account/

View File

@@ -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';

View File

@@ -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';

View File

@@ -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<typeof UpdateProfileSchema>;

View File

@@ -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 {

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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<z.infer<typeof CreateBreweryPostWithImagesSchema>>;
isSubmitting: boolean;
setValue: UseFormSetValue<z.infer<typeof CreateBreweryPostWithImagesSchema>>;
}> = ({ 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<{
</FormInfo>
<FormSegment>
<AddressAutofill
accessToken={process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN!}
accessToken={mapboxAccessToken}
onRetrieve={onAutoCompleteRetrieve}
onChange={onAutoCompleteChange}
>
@@ -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}
/>
</Tab.Panel>
</Tab.Panels>

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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';

View File

@@ -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);
}

View File

@@ -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';

View File

@@ -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;

View File

@@ -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 };

View File

@@ -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;

View File

@@ -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);

View File

@@ -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';

View File

@@ -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 <T extends CommentRequest>(
req: T,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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,
});
};

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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,
});
};

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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,
});
};

View File

@@ -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<typeof CreateCommentValidationSchema>;
}
export interface GetAllCommentsRequest extends UserExtendedNextApiRequest {
query: { id: string; page_size: string; page_num: string };
}

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { id } = req.query;
await deleteBeerImageService({ beerImageId: id });
res.status(200).json({
success: true,
message: `Successfully deleted image with id ${id}`,
statusCode: 200,
});
};

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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,
});
};

View File

@@ -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<typeof ImageMetadataValidationSchema>;
}
export interface DeleteImageRequest extends UserExtendedNextApiRequest {
query: { id: string };
}

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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 },
});
};

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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 },
});
};

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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 },
});
};

View File

@@ -0,0 +1,5 @@
import { UserExtendedNextApiRequest } from '@/config/auth/types';
export interface LikeRequest extends UserExtendedNextApiRequest {
query: { id: string };
}

View File

@@ -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 <BeerPostRequestType extends BeerPostRequest>(
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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,
});
};

View File

@@ -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<typeof EditBeerPostValidationSchema>;
}
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<typeof CreateBeerPostValidationSchema>;
}

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
// 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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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,
});
};

View File

@@ -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<typeof CreateBeerStyleValidationSchema>;
}

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
// 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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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,
});
};

View File

@@ -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<typeof PaginatedQueryResponseSchema>;
}
export interface CreateBreweryPostRequest extends UserExtendedNextApiRequest {
body: z.infer<typeof CreateBreweryPostSchema>;
}
export interface BreweryPostRequest extends UserExtendedNextApiRequest {
query: { id: string };
}
export interface EditBreweryPostRequest extends BreweryPostRequest {
body: z.infer<typeof EditBreweryPostValidationSchema>;
}

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
* ) => {
* const { page_size, page_num, id } = req.query;
* // ...
* };
*
* @example
* const getAllBeersByUserId = async (
* req: GetAllPostsByConnectedPostId,
* res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
* ) => {
* const { page_size, page_num, id } = req.query;
* // ...
* };
*/
export interface GetAllPostsByConnectedPostId extends NextApiRequest {
query: { id: string; page_size: string; page_num: string };
}

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
next: NextFunction,
) => {
passport.initialize();
passport.use(localStrat);
passport.authenticate(
'local',
{ session: false },
(error: unknown, token: z.infer<typeof GetUserSchema>) => {
if (error) {
next(error);
return;
}
req.user = token;
next();
},
)(req, res, next);
},
);
export const loginUser = async (
req: UserExtendedNextApiRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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,
});
};

View File

@@ -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<typeof CreateUserValidationSchema>;
}
export interface TokenValidationRequest extends UserExtendedNextApiRequest {
query: z.infer<typeof TokenValidationSchema>;
}
export interface ResetPasswordRequest extends NextApiRequest {
body: { email: string };
}
export interface UpdatePasswordRequest extends UserExtendedNextApiRequest {
body: z.infer<typeof UpdatePasswordSchema>;
}
export interface CheckEmailRequest extends NextApiRequest {
query: { email: string };
}
export interface CheckUsernameRequest extends NextApiRequest {
query: { username: string };
}

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
// 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<z.infer<typeof APIResponseValidationSchema>>,
) => {
// 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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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 <T extends UserExtendedNextApiRequest>(
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,
});
};

View File

@@ -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<typeof EditUserSchema>;
}
export interface UpdateAvatarRequest extends UserExtendedNextApiRequest {
file: Express.Multer.File;
}
export interface UpdateProfileRequest extends UserExtendedNextApiRequest {
body: { bio: string };
}

View File

@@ -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<ForgotEmailProps> = ({ name, url }) => {
const ResetPasswordEmail: FC<ResetPasswordEmailProps> = ({ name, url }) => {
return (
<Tailwind>
<Container className="mx-auto">
@@ -36,4 +36,4 @@ const ForgotEmail: FC<ForgotEmailProps> = ({ name, url }) => {
);
};
export default ForgotEmail;
export default ResetPasswordEmail;

View File

@@ -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<WelcomeEmail> = ({ name, url }) => (
const WelcomeEmail: FC<WelcomeEmailProps> = ({ name, url }) => (
<Tailwind>
<Container className="flex h-full w-full flex-col items-center justify-center">
<Section>
@@ -43,4 +43,4 @@ const Welcome: FC<WelcomeEmail> = ({ name, url }) => (
</Tailwind>
);
export default Welcome;
export default WelcomeEmail;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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:
*

View File

@@ -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.

View File

@@ -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:

View File

@@ -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.

View File

@@ -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:

View File

@@ -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.

View File

@@ -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;

View File

@@ -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,

View File

@@ -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.

View File

@@ -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);

View File

@@ -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.

View File

@@ -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);

View File

@@ -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:

View File

@@ -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:

View File

@@ -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.

View File

@@ -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.

View File

@@ -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:

View File

@@ -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);

View File

@@ -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';

View File

@@ -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);

View File

@@ -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`,

View File

@@ -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';

View File

@@ -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`

View File

@@ -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<typeof CreateCommentValidationSchema>;
}
const checkIfCommentOwner = async (
req: DeleteCommentRequest | EditCommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>
>();
@@ -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);

View File

@@ -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<typeof CreateCommentValidationSchema>;
}
const checkIfCommentOwner = async (
req: DeleteCommentRequest | EditCommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>
>();
@@ -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);

View File

@@ -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<typeof CreateCommentValidationSchema>;
query: { id: string };
}
interface GetAllCommentsRequest extends UserExtendedNextApiRequest {
query: { id: string; page_size: string; page_num: string };
}
const createComment = async (
req: CreateCommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { content, rating } = req.body;
const beerPostId = req.query.id;
const newBeerComment: z.infer<typeof CommentQueryResult> = 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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>
>();
@@ -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);

View File

@@ -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<typeof ImageMetadataValidationSchema>;
}
const processImageData = async (
req: UploadBeerPostImagesRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>
>();
@@ -56,7 +22,7 @@ router.post(
// @ts-expect-error
uploadMiddlewareMultiple,
validateRequest({ bodySchema: ImageMetadataValidationSchema }),
processImageData,
processBeerImageData,
);
const handler = router.handler(NextConnectOptions);

View File

@@ -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<typeof EditBeerPostValidationSchema>;
}
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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,

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>
>();
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);

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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);

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>
>();
const getBeerRecommendationsRequest = async (
req: BeerPostRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
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);

View File

@@ -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<typeof CreateBeerPostValidationSchema>;
}
const createBeerPost = async (
req: CreateBeerPostRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
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,

View File

@@ -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<typeof PaginatedQueryResponseSchema>;
}
const getBeerPosts = async (
req: GetBeerPostsRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>
>();

View File

@@ -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),

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
// 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<z.infer<typeof APIResponseValidationSchema>>

View File

@@ -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<typeof CreateCommentValidationSchema>;
query: { id: string };
}
interface GetAllCommentsRequest extends UserExtendedNextApiRequest {
query: { id: string; page_size: string; page_num: string };
}
const createComment = async (
req: CreateCommentRequest,
res: NextApiResponse<z.infer<typeof APIResponseValidationSchema>>,
) => {
const { content, rating } = req.body;
const newBeerStyleComment: z.infer<typeof CommentQueryResult> =
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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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

View File

@@ -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<z.infer<typeof APIResponseValidationSchema>>,
) => {
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<z.infer<typeof APIResponseValidationSchema>>

Some files were not shown because too many files have changed in this diff Show More