diff --git a/app.js b/app.js index a3b2383..8c78048 100644 --- a/app.js +++ b/app.js @@ -1,11 +1,15 @@ import express from "express"; +import cors from "cors"; import productRouter from "./routes/product/product.js"; import articleRouter from "./routes/article/article.js"; import pCommentRouter from "./routes/pComment/pComment.js"; import aCommentRouter from "./routes/aComment/aComment.js"; +import pHeartRouter from "./routes/PHeart/PHeart.js"; +import aHeartRouter from "./routes/AHeart/AHeart.js"; const app = express(); +app.use(cors()); app.use(express.json()); // 라우터 등록 @@ -17,8 +21,12 @@ app.use("/pComment", pCommentRouter); app.use("/aComment", aCommentRouter); +app.use("/pHeart", pHeartRouter); + +app.use("/aHeart", aHeartRouter); + // 서버 실행 -const PORT = 3000; +const PORT = 5000; app.listen(PORT, () => { console.log(`✅ 서버가 http://localhost:${PORT}에서 실행 중...🚀`); }); diff --git a/controllers/AHeart/AHeart.js b/controllers/AHeart/AHeart.js new file mode 100644 index 0000000..f509050 --- /dev/null +++ b/controllers/AHeart/AHeart.js @@ -0,0 +1,30 @@ +import { createAHeart, updateAHeart } from "../../services/aHeart/aHeart.js"; + +export const postAHeart = async (req, res) => { + const data = req.body; + try { + const hearts = await createAHeart(data); + if (!hearts) { + return res.status(404).json({ error: "Articles not found" }); + } + res.status(200).json(hearts); + } catch (error) { + console.error("❌ [postAHeart] error:", error); + res.status(500).json({ error: "Internal Server Error" }); + } +}; + +export const patchAHeart = async (req, res) => { + const { id } = req.params; + const { data } = req.body; + try { + const hearts = await updateAHeart(id, data); + if (!hearts) { + return res.status(404).json({ error: "Hearts not found" }); + } + res.status(200).json(hearts); + } catch (error) { + console.error("❌ [patchAHeart] error:", error); + res.status(500).json({ error: "Internal Server Error" }); + } +}; diff --git a/controllers/PHeart/PHeart.js b/controllers/PHeart/PHeart.js new file mode 100644 index 0000000..48bfc8d --- /dev/null +++ b/controllers/PHeart/PHeart.js @@ -0,0 +1,30 @@ +import { createPHeart, updatePHeart } from "../../services/pHeart/pHeart.js"; + +export const postPHeart = async (req, res) => { + const data = req.body; + try { + const hearts = await createPHeart(data); + if (!hearts) { + return res.status(404).json({ error: "Articles not found" }); + } + res.status(200).json(hearts); + } catch (error) { + console.error("❌ [postPHeart] error:", error); + res.status(500).json({ error: "Internal Server Error" }); + } +}; + +export const patchPHeart = async (req, res) => { + const { id } = req.params; + const { data } = req.body; + try { + const hearts = await updatePHeart(id, data); + if (!hearts) { + return res.status(404).json({ error: "Hearts not found" }); + } + res.status(200).json(hearts); + } catch (error) { + console.error("❌ [patchPHeart] error:", error); + res.status(500).json({ error: "Internal Server Error" }); + } +}; diff --git a/controllers/article/article.js b/controllers/article/article.js index de6d5fc..bdf7d2b 100644 --- a/controllers/article/article.js +++ b/controllers/article/article.js @@ -21,9 +21,10 @@ export const getAllArticles = async (req, res) => { }; export const getArticleById = async (req, res) => { - const id = req.query.id; + const id = req.params.id; + const userId = req.query.userId; try { - const articles = await fetchArticleById(id); + const articles = await fetchArticleById(id, userId); if (!articles) { return res.status(404).json({ error: "Articles not found" }); } @@ -35,7 +36,7 @@ export const getArticleById = async (req, res) => { }; export const postArticle = async (req, res) => { - const data = req.body; + const { data } = req.body; try { const articles = await createArticle(data); if (!articles) { @@ -49,7 +50,8 @@ export const postArticle = async (req, res) => { }; export const patchArticle = async (req, res) => { - const { id, data } = req.body; + const { data } = req.body; + const id = req.params.id; try { const articles = await updateArticle(id, data); if (!articles) { diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 2cff15c..916db03 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -99,6 +99,23 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -245,6 +262,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -288,6 +317,19 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -306,6 +348,15 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -396,6 +447,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -518,6 +584,42 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -629,6 +731,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -885,6 +1002,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -969,6 +1095,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/package-lock.json b/package-lock.json index ac1c429..69320f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,8 @@ "": { "dependencies": { "@prisma/client": "^5.4.2", + "axios": "^1.10.0", + "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.21.2", "is-email": "^1.0.2", @@ -113,6 +115,23 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz", + "integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -259,6 +278,18 @@ "fsevents": "~2.3.2" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -302,6 +333,19 @@ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/debug": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", @@ -320,6 +364,15 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -410,6 +463,21 @@ "node": ">= 0.4" } }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -532,6 +600,42 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz", + "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -657,6 +761,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -913,6 +1032,15 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -997,6 +1125,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", diff --git a/package.json b/package.json index 503a449..c08bcde 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,8 @@ { "dependencies": { "@prisma/client": "^5.4.2", + "axios": "^1.10.0", + "cors": "^2.8.5", "dotenv": "^16.3.1", "express": "^4.21.2", "is-email": "^1.0.2", diff --git a/prisma/migrations/20250714082416_article_adjust/migration.sql b/prisma/migrations/20250714082416_article_adjust/migration.sql new file mode 100644 index 0000000..873da6b --- /dev/null +++ b/prisma/migrations/20250714082416_article_adjust/migration.sql @@ -0,0 +1,33 @@ +-- CreateTable +CREATE TABLE "PHeart" ( + "id" TEXT NOT NULL, + "productId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "PHeart_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "AHeart" ( + "id" TEXT NOT NULL, + "articleId" TEXT NOT NULL, + "userId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "AHeart_pkey" PRIMARY KEY ("id") +); + +-- AddForeignKey +ALTER TABLE "PHeart" ADD CONSTRAINT "PHeart_productId_fkey" FOREIGN KEY ("productId") REFERENCES "Product"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "PHeart" ADD CONSTRAINT "PHeart_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AHeart" ADD CONSTRAINT "AHeart_articleId_fkey" FOREIGN KEY ("articleId") REFERENCES "Article"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "AHeart" ADD CONSTRAINT "AHeart_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/migrations/20250714153520_deleted/migration.sql b/prisma/migrations/20250714153520_deleted/migration.sql new file mode 100644 index 0000000..1fcecdc --- /dev/null +++ b/prisma/migrations/20250714153520_deleted/migration.sql @@ -0,0 +1,17 @@ +-- AlterTable +ALTER TABLE "AComment" ADD COLUMN "deleted" BOOLEAN NOT NULL DEFAULT false; + +-- AlterTable +ALTER TABLE "AHeart" ADD COLUMN "canceled" BOOLEAN NOT NULL DEFAULT false; + +-- AlterTable +ALTER TABLE "Article" ADD COLUMN "deleted" BOOLEAN NOT NULL DEFAULT false; + +-- AlterTable +ALTER TABLE "PComment" ADD COLUMN "deleted" BOOLEAN NOT NULL DEFAULT false; + +-- AlterTable +ALTER TABLE "PHeart" ADD COLUMN "canceled" BOOLEAN NOT NULL DEFAULT false; + +-- AlterTable +ALTER TABLE "Product" ADD COLUMN "deleted" BOOLEAN NOT NULL DEFAULT false; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 56f8b2d..9177243 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -23,6 +23,8 @@ model User { article Article[] pComment PComment[] AComment AComment[] + PHeart PHeart[] + AHeart AHeart[] } model Product { @@ -32,10 +34,12 @@ model Product { price Int user User @relation(fields: [userId], references: [id]) userId String + deleted Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt pComment PComment[] tag tag[] + PHeart PHeart[] } model Article { @@ -44,9 +48,11 @@ model Article { content String? user User @relation(fields: [userId], references: [id]) userId String + deleted Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt AComment AComment[] + AHeart AHeart[] } model PComment { @@ -56,6 +62,7 @@ model PComment { productId String user User @relation(fields: [userId], references: [id]) userId String + deleted Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } @@ -67,6 +74,7 @@ model AComment { articleId String user User @relation(fields: [userId], references: [id]) userId String + deleted Boolean @default(false) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } @@ -78,6 +86,28 @@ model tag { content String } +model PHeart { + id String @id @default(uuid()) + product Product @relation(fields: [productId], references: [id]) + productId String + user User @relation(fields: [userId], references: [id]) + userId String + canceled Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model AHeart { + id String @id @default(uuid()) + article Article @relation(fields: [articleId], references: [id]) + articleId String + user User @relation(fields: [userId], references: [id]) + userId String + canceled Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + // CREATE TABLE 상품게시물이미지 ( // 이미지ID VARCHAR(20) NOT NULL, // 게시글ID VARCHAR(20) NOT NULL, @@ -93,15 +123,3 @@ model tag { // PRIMARY KEY(이미지ID), // FOREIGN KEY(게시글ID) REFERENCES 자유게시물(자유게시물ID) // ) - -// CREATE TABLE 상품좋아요 ( -// 상품게시글ID VARCHAR(20) NOT NULL, -// 회원ID VARCHAR(20) NOT -// NULL, -// PRIMARY KEY ( 상품게시글ID, -// 회원ID ), -// FOREIGN KEY ( 상품게시글ID ) -// REFERENCES 상품게시물 ( 상품게시물ID ), -// FOREIGN KEY ( 회원ID ) -// REFERENCES 유저 ( 회원ID ) -// ) diff --git a/repositories/aComment/aComment.js b/repositories/aComment/aComment.js index b9ebc7a..b536fca 100644 --- a/repositories/aComment/aComment.js +++ b/repositories/aComment/aComment.js @@ -6,8 +6,13 @@ const prisma = new PrismaClient(); // 커서 기반 페이지네이션 export const getAllAComments = async (articleId, cursor, limit = 10) => { return await prisma.aComment.findMany({ - where: { articleId }, - select: { id: true, content: true, createdAt: true }, + where: { articleId, deleted: false }, + select: { + id: true, + content: true, + user: { select: { nickname: true, id: true, img: true } }, + updatedAt: true, + }, cursor, take: parseInt(limit), }); @@ -15,8 +20,9 @@ export const getAllAComments = async (articleId, cursor, limit = 10) => { // 상품 댓글 등록 post (입력값 data는 객체) export const postAComment = async (articleId, data) => { + const { userId, content } = data.data; return await prisma.aComment.create({ - data: { ...data, articleId }, + data: { userId, content, articleId }, }); }; diff --git a/repositories/aHeart/aHeart.js b/repositories/aHeart/aHeart.js new file mode 100644 index 0000000..259e812 --- /dev/null +++ b/repositories/aHeart/aHeart.js @@ -0,0 +1,18 @@ +import { PrismaClient } from "@prisma/client"; +import { skip } from "@prisma/client/runtime/library"; +const prisma = new PrismaClient(); + +// heart post (좋아요 생성. 유저id와 게시물 id가 필요) +export const postAHeart = async (data) => { + return await prisma.aHeart.create({ + data: data, + }); +}; + +// heart patch (입력값 data는 객체, id는 문자열) +export const patchAHeart = async (id, data) => { + return await prisma.aHeart.update({ + where: { id: id }, + data: data, + }); +}; diff --git a/repositories/article/article.js b/repositories/article/article.js index 11a41c5..91eff13 100644 --- a/repositories/article/article.js +++ b/repositories/article/article.js @@ -1,43 +1,89 @@ import { PrismaClient } from "@prisma/client"; +import { Prisma } from "@prisma/client"; import { skip } from "@prisma/client/runtime/library"; const prisma = new PrismaClient(); // 자유게시글 목록 조회 get // offset 페이지네이션, 최신순 정렬 // title, content에 포함된 단어로 검색 -export const getAllArticles = async ({ keyword, offset = 0, limit = 10 }) => { - const whereCondition = keyword - ? { - OR: [ - { - title: { contains: keyword, mode: "insensitive" }, - }, - { - content: { contains: keyword, mode: "insensitive" }, - }, - ], - } - : undefined; - - return await prisma.article.findMany({ - where: whereCondition, - select: { id: true, title: true, content: true, createdAt: true }, - orderBy: { updatedAt: "desc" }, - skip: parseInt(offset), - take: parseInt(limit), - }); +export const getAllArticles = async ({ + userId, + keyword, + offset = 0, + limit = 10, + orderBy, +}) => { + const search = keyword ? `%${keyword}%` : null; + + const whereClause = search + ? Prisma.sql`AND (a.title ILIKE ${search} OR a.content ILIKE ${search})` + : Prisma.empty; + + const orderClause = + orderBy === "hearts" + ? Prisma.sql`ORDER BY heart_count DESC` + : Prisma.sql`ORDER BY a."updatedAt" DESC`; + + const rawResult = await prisma.$queryRaw` + SELECT + a.id, + a.title, + a."updatedAt", + u.id AS "userId", + u.nickname, + u.img, + COUNT(DISTINCT CASE WHEN h.canceled = false THEN h.id END) AS heart_count, + EXISTS ( + SELECT 1 FROM "AHeart" h2 + WHERE h2."articleId" = a.id AND h2."userId" = ${userId} AND h2.canceled = false + ) AS "isHearted", + ( + SELECT h3.id + FROM "AHeart" h3 + WHERE h3."articleId" = a.id + AND h3."userId" = ${userId} + AND h3.canceled = false + LIMIT 1 + ) AS "heartId" + FROM "Article" a + JOIN "User" u ON a."userId" = u.id + LEFT JOIN "AHeart" h ON h."articleId" = a.id + WHERE a.deleted = false + ${whereClause} + GROUP BY a.id, u.id + ${orderClause} + LIMIT ${Number(limit)} OFFSET ${Number(offset)}; + `; + + const result = rawResult.map((row) => ({ + ...row, + heart_count: Number(row.heart_count), + comment_count: Number(row.comment_count), + })); + + return result; }; // 자유게시글 단일 조회 get -export const getArticleById = async (id) => { +export const getArticleById = async (id, userId) => { return await prisma.article.findUnique({ - where: { id }, + where: { id, deleted: false }, select: { id: true, title: true, content: true, - tag: true, - createdAt: true, + updatedAt: true, + user: { select: { id: true, nickname: true, img: true } }, + _count: { + select: { + AHeart: { where: { canceled: false } }, + AComment: { where: { deleted: false } }, + }, + }, + AHeart: { + where: { userId: userId || "noUser", canceled: false }, + select: { id: true }, + }, }, }); }; diff --git a/repositories/pHeart/pHeart.js b/repositories/pHeart/pHeart.js new file mode 100644 index 0000000..b7e6c54 --- /dev/null +++ b/repositories/pHeart/pHeart.js @@ -0,0 +1,18 @@ +import { PrismaClient } from "@prisma/client"; +import { skip } from "@prisma/client/runtime/library"; +const prisma = new PrismaClient(); + +// heart post (좋아요 생성. 유저id와 게시물 id가 필요) +export const postPHeart = async (data) => { + return await prisma.pHeart.create({ + data: data, + }); +}; + +// heart patch (입력값 data는 객체, id는 문자열) +export const patchPHeart = async (id, data) => { + return await prisma.pHeart.update({ + where: { id: id }, + data: data, + }); +}; diff --git a/routes/AHeart/AHeart.js b/routes/AHeart/AHeart.js new file mode 100644 index 0000000..d3c4fef --- /dev/null +++ b/routes/AHeart/AHeart.js @@ -0,0 +1,9 @@ +import express from "express"; +import { postAHeart, patchAHeart } from "../../controllers/AHeart/AHeart.js"; + +const router = express.Router(); + +router.post("/", postAHeart); +router.patch("/:id", patchAHeart); + +export default router; diff --git a/routes/PHeart/PHeart.js b/routes/PHeart/PHeart.js new file mode 100644 index 0000000..8214d2d --- /dev/null +++ b/routes/PHeart/PHeart.js @@ -0,0 +1,9 @@ +import express from "express"; +import { postPHeart, patchPHeart } from "../../controllers/PHeart/PHeart.js"; + +const router = express.Router(); + +router.post("/", postPHeart); +router.patch("/:id", patchPHeart); + +export default router; diff --git a/routes/article/article.js b/routes/article/article.js index ce933d7..3accb05 100644 --- a/routes/article/article.js +++ b/routes/article/article.js @@ -12,7 +12,7 @@ const router = express.Router(); router.get("/", getAllArticles); router.get("/:id", getArticleById); router.post("/", postArticle); -router.patch("/", patchArticle); +router.patch("/:id", patchArticle); router.delete("/:id", deleteArticle); export default router; diff --git a/services/aHeart/aHeart.js b/services/aHeart/aHeart.js new file mode 100644 index 0000000..bb77c7f --- /dev/null +++ b/services/aHeart/aHeart.js @@ -0,0 +1,9 @@ +import { postAHeart, patchAHeart } from "../../repositories/aHeart/aHeart.js"; + +export const createAHeart = async (data) => { + return await postAHeart(data); +}; + +export const updateAHeart = async (id, data) => { + return await patchAHeart(id, data); +}; diff --git a/services/article/article.js b/services/article/article.js index 1b6c050..134239a 100644 --- a/services/article/article.js +++ b/services/article/article.js @@ -10,8 +10,8 @@ export const fetchAllArticles = async (query) => { return await getAllArticles(query); }; -export const fetchArticleById = async (id) => { - return await getArticleById(id); +export const fetchArticleById = async (id, userId) => { + return await getArticleById(id, userId); }; export const createArticle = async (data) => { diff --git a/services/pHeart/pHeart.js b/services/pHeart/pHeart.js new file mode 100644 index 0000000..78d4883 --- /dev/null +++ b/services/pHeart/pHeart.js @@ -0,0 +1,9 @@ +import { postPHeart, patchPHeart } from "../../repositories/pHeart/pHeart.js"; + +export const createPHeart = async (data) => { + return await postPHeart(data); +}; + +export const updatePHeart = async (id, data) => { + return await patchPHeart(id, data); +};