Skip to content

Commit 7846c9f

Browse files
committed
스프린트 미션7
1 parent 31aae9f commit 7846c9f

13 files changed

Lines changed: 4277 additions & 0 deletions

File tree

.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.env*
2+
/generated/prisma
3+
node_modules/
4+
prisma/migrations/
5+
prisma/dev.db
6+
prisma/generated/
7+
*.log
8+
.DS_Store
9+
Thumbs.db

app.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import express from "express";
2+
import cors from "cors";
3+
import dotenv from "dotenv";
4+
import productRoutes from "./routes/product.js";
5+
import articleRoutes from "./routes/article.js";
6+
import commentRoutes from "./routes/comment.js";
7+
8+
dotenv.config();
9+
const app = express();
10+
11+
app.use(cors());
12+
app.use(express.json());
13+
14+
app.use("/api/products", productRoutes);
15+
app.use("/api/articles", articleRoutes);
16+
app.use("/api/comments", commentRoutes);
17+
18+
const PORT = process.env.PORT || 4000;
19+
const startServer = async () => {
20+
try {
21+
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
22+
} catch (err) {
23+
console.error("Failed to connect to DB:", err.message);
24+
}
25+
};
26+
27+
startServer();

controllers/articleController.js

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { PrismaClient } from "@prisma/client";
2+
const prisma = new PrismaClient();
3+
4+
export const createArticle = async (req, res) => {
5+
try {
6+
const { title, content, author, likeCount } = req.body;
7+
const article = await prisma.article.create({
8+
data: {
9+
title,
10+
content,
11+
author: author || "익명",
12+
likeCount,
13+
},
14+
});
15+
res.status(201).json(article);
16+
} catch (err) {
17+
console.error("❌ 게시글 등록 실패:", err);
18+
res.status(500).json({ message: "게시글 등록 실패", error: err.message });
19+
}
20+
};
21+
22+
23+
export const getArticle = async (req, res) => {
24+
try {
25+
const article = await prisma.article.findUnique({
26+
where: { id: Number(req.params.id) },
27+
select: {
28+
id: true,
29+
title: true,
30+
content: true,
31+
author: true,
32+
likeCount: true,
33+
createdAt: true,
34+
updatedAt: true,
35+
},
36+
});
37+
if (!article) return res.status(404).json({ message: "게시글 없음" });
38+
res.status(200).json(article);
39+
} catch (err) {
40+
console.error("❌ 단건 조회 실패:", err);
41+
res.status(500).json({ message: "조회 실패", error: err.message });
42+
}
43+
};
44+
45+
46+
export const getArticles = async (req, res) => {
47+
try {
48+
const { page = 1, limit = 10, search = "", sort = "recent" } = req.query;
49+
const skip = (page - 1) * limit;
50+
51+
const orderBy =
52+
sort === "like" ? { likeCount: "desc" } : { createdAt: "desc" };
53+
54+
const where = search
55+
? {
56+
OR: [
57+
{ title: { contains: search, mode: "insensitive" } },
58+
{ content: { contains: search, mode: "insensitive" } },
59+
{ author: { contains: search, mode: "insensitive" } },
60+
],
61+
}
62+
: {};
63+
64+
const articles = await prisma.article.findMany({
65+
where,
66+
orderBy,
67+
skip: Number(skip),
68+
take: Number(limit),
69+
select: {
70+
id: true,
71+
title: true,
72+
content: true,
73+
author: true,
74+
likeCount: true,
75+
createdAt: true,
76+
},
77+
});
78+
79+
res.status(200).json(articles);
80+
} catch (err) {
81+
console.error("❌ 목록 조회 실패:", err);
82+
res.status(500).json({ message: "목록 조회 실패", error: err.message });
83+
}
84+
};
85+
86+
export const updateArticle = async (req, res) => {
87+
try {
88+
const { title, content, author, likeCount } = req.body;
89+
const updated = await prisma.article.update({
90+
where: { id: Number(req.params.id) },
91+
data: { title, content, author, likeCount },
92+
});
93+
res.status(200).json(updated);
94+
} catch (err) {
95+
console.error("❌ 수정 실패:", err);
96+
res.status(500).json({ message: "수정 실패", error: err.message });
97+
}
98+
};
99+
100+
101+
export const deleteArticle = async (req, res) => {
102+
try {
103+
await prisma.article.delete({ where: { id: Number(req.params.id) } });
104+
res.status(200).json({ message: "게시글 삭제 완료" });
105+
} catch (err) {
106+
console.error("❌ 삭제 실패:", err);
107+
res.status(500).json({ message: "삭제 실패", error: err.message });
108+
}
109+
};

controllers/commentController.js

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { PrismaClient } from "@prisma/client";
2+
const prisma = new PrismaClient();
3+
4+
export const createComment = async (req, res) => {
5+
try {
6+
const { articleId, content, parentId } = req.body;
7+
const comment = await prisma.comment.create({
8+
data: {
9+
articleId: Number(articleId),
10+
content,
11+
...(parentId && { parentId: Number(parentId) })
12+
}
13+
});
14+
res.status(201).json(comment);
15+
} catch (err) {
16+
res.status(500).json({ message: "댓글 등록 실패", error: err.message });
17+
}
18+
};
19+
20+
21+
22+
export const updateComment = async (req, res) => {
23+
try {
24+
const { content } = req.body;
25+
const updated = await prisma.comment.update({
26+
where: { id: Number(req.params.id) },
27+
data: { content },
28+
});
29+
res.status(200).json(updated);
30+
} catch (err) {
31+
res.status(500).json({ message: "댓글 수정 실패", error: err.message });
32+
}
33+
};
34+
35+
36+
export const deleteComment = async (req, res) => {
37+
try {
38+
await prisma.comment.delete({ where: { id: Number(req.params.id) } });
39+
res.status(200).json({ message: "댓글 삭제 완료" });
40+
} catch (err) {
41+
res.status(500).json({ message: "댓글 삭제 실패", error: err.message });
42+
}
43+
};
44+
45+
46+
export const getComments = async (req, res) => {
47+
try {
48+
const { articleId, limit = 10 } = req.query;
49+
50+
51+
const comments = await prisma.comment.findMany({
52+
where: { articleId: Number(articleId), parentId: null },
53+
include: { children: true },
54+
orderBy: { id: "asc" },
55+
take: Number(limit),
56+
});
57+
58+
res.status(200).json(comments);
59+
} catch (err) {
60+
res.status(500).json({ message: "댓글 조회 실패", error: err.message });
61+
}
62+
};

controllers/productController.js

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { PrismaClient } from "@prisma/client";
2+
const prisma = new PrismaClient();
3+
4+
5+
export const getProducts = async (req, res) => {
6+
try {
7+
const { page = 1, limit = 10, search = "", sort = "recent" } = req.query;
8+
const skip = (page - 1) * limit;
9+
10+
const products = await prisma.product.findMany({
11+
where: {
12+
OR: [
13+
{ name: { contains: search, mode: "insensitive" } },
14+
{ description: { contains: search, mode: "insensitive" } }
15+
]
16+
},
17+
orderBy: sort === "recent" ? { createdAt: "desc" } : { createdAt: "asc" },
18+
skip,
19+
take: Number(limit)
20+
});
21+
22+
res.status(200).json(products);
23+
} catch (err) {
24+
res.status(500).json({ message: "서버 오류", error: err.message });
25+
}
26+
};
27+
28+
29+
export const getProduct = async (req, res) => {
30+
try {
31+
const id = Number(req.params.id);
32+
const product = await prisma.product.findUnique({ where: { id } });
33+
34+
if (!product) return res.status(404).json({ message: "Product not found" });
35+
res.status(200).json(product);
36+
} catch (err) {
37+
res.status(500).json({ message: "서버 오류", error: err.message });
38+
}
39+
};
40+
41+
42+
export const createProduct = async (req, res) => {
43+
try {
44+
const { name, description, price, tags } = req.body;
45+
46+
const newProduct = await prisma.product.create({
47+
data: {
48+
name,
49+
description,
50+
price: Number(price),
51+
tags,
52+
likeCount: 0
53+
}
54+
});
55+
56+
res.status(201).json(newProduct);
57+
} catch (err) {
58+
res.status(500).json({ message: "등록 실패", error: err.message });
59+
}
60+
};
61+
62+
63+
export const updateProduct = async (req, res) => {
64+
try {
65+
const id = Number(req.params.id);
66+
67+
const updatedProduct = await prisma.product.update({
68+
where: { id },
69+
data: req.body
70+
});
71+
72+
res.status(200).json(updatedProduct);
73+
} catch (err) {
74+
if (err.code === "P2025") {
75+
return res.status(404).json({ message: "Product not found" });
76+
}
77+
res.status(500).json({ message: "수정 실패", error: err.message });
78+
}
79+
};
80+
81+
export const deleteProduct = async (req, res) => {
82+
try {
83+
const id = Number(req.params.id);
84+
85+
await prisma.product.delete({ where: { id } });
86+
87+
res.status(200).json({ message: "Product deleted" });
88+
} catch (err) {
89+
if (err.code === "P2025") {
90+
return res.status(404).json({ message: "Product not found" });
91+
}
92+
res.status(500).json({ message: "삭제 실패", error: err.message });
93+
}
94+
};

data/seed.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { PrismaClient } from '@prisma/client';
2+
const prisma = new PrismaClient();
3+
4+
async function main() {
5+
await prisma.product.createMany({
6+
data: [
7+
{ name: '중고 노트북', description: '상태 양호', price: 500000, tags: ['노트북','전자제품'], likeCount: 0 },
8+
{ name: '키보드 팝니다', description: '기계식, 축 청축', price: 30000, tags: ['키보드','컴퓨터'], likeCount: 0 },
9+
{ name: '아이폰 케이스', description: '깨끗해요', price: 8000, tags: ['악세사리'], likeCount: 0 }
10+
]
11+
});
12+
13+
14+
const article1 = await prisma.article.create({
15+
data: {
16+
title: '첫 번째 글',
17+
content: '안녕하세요 자유게시판!'
18+
}
19+
});
20+
21+
const article2 = await prisma.article.create({
22+
data: {
23+
title: '두 번째 글',
24+
content: '두 번째 글 내용입니다.'
25+
}
26+
});
27+
28+
await prisma.comment.createMany({
29+
data: [
30+
{ content: '첫 번째 글 댓글이에요!', articleId: article1.id },
31+
{ content: '첫 번째 글 추가 댓글', articleId: article1.id },
32+
{ content: '두 번째 글 댓글!', articleId: article2.id },
33+
{ content: '첫 번째 댓글', articleId: article1.id },
34+
{ content: '두 번째 댓글', articleId: article1.id },
35+
{ content: '세 번째 댓글', articleId: article1.id },
36+
{ content: '네 번째 댓글', articleId: article1.id },
37+
{ content: '다섯 번째 댓글', articleId: article1.id }
38+
]
39+
});
40+
41+
await prisma.comment.create({
42+
data: { content: "대댓글 내용", articleId: 1, parentId: 3 }
43+
});
44+
45+
console.log('Seed 완료');
46+
}
47+
48+
main()
49+
.catch(e => { console.error(e); process.exit(1); })
50+
.finally(async () => { await prisma.$disconnect(); });

0 commit comments

Comments
 (0)