Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,191 changes: 1,191 additions & 0 deletions 4-Sprint-Misson/package-lock.json

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions 4-Sprint-Misson/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "3-sprint-misson",
"version": "1.0.0",
"type": "module",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@prisma/client": "^6.13.0",
"bcrypt": "^6.0.0",
"dotenv": "^17.2.1",
"express": "^5.1.0",
"jsonwebtoken": "^9.0.2",
"multer": "^2.0.2"
},
"description": ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"nickname" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "Post" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"views" INTEGER NOT NULL DEFAULT 0,

CONSTRAINT "Post_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User_nickname_key" ON "User"("nickname");

-- CreateIndex
CREATE UNIQUE INDEX "Post_title_key" ON "Post"("title");
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
Warnings:

- You are about to drop the `Post` table. If the table is not empty, all the data it contains will be lost.
- You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost.

*/
-- DropTable
DROP TABLE "public"."Post";

-- DropTable
DROP TABLE "public"."User";

-- CreateTable
CREATE TABLE "public"."Product" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT NOT NULL,
"price" INTEGER NOT NULL,
"tags" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "Product_pkey" PRIMARY KEY ("id")
);

-- CreateTable
CREATE TABLE "public"."Article" (
"id" SERIAL NOT NULL,
"title" TEXT NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "Article_pkey" PRIMARY KEY ("id")
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- CreateTable
CREATE TABLE "public"."comment" (
"id" SERIAL NOT NULL,
"content" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"productId" INTEGER,
"articleId" INTEGER,

CONSTRAINT "comment_pkey" PRIMARY KEY ("id")
);
3 changes: 3 additions & 0 deletions 4-Sprint-Misson/prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
81 changes: 81 additions & 0 deletions 4-Sprint-Misson/prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

generator client {
provider = "prisma-client-js"
}

model User {
id Int @id @default(autoincrement())
email String @unique
nickname String
image String?
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

refreshToken String? //토큰
products Product[]
comments comment[]
articles Article[]
likes Like[]
}


model Product {
id Int @id @default(autoincrement())
name String
description String
price Float
tags String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

userId Int
user User @relation(fields: [userId], references: [id])
comments comment[]
likes Like[]
}

model Article {
id Int @id @default(autoincrement())
title String
content String
userId Int
createdAt DateTime @default(now())
updatedAt DateTime @default(now())

user User @relation(fields: [userId], references: [id])
comments comment[]
likes Like[]
}

model comment {
id Int @id @default(autoincrement())
content String
createdAt DateTime @default(now())
userId Int
user User @relation(fields: [userId], references: [id])
productId Int?
product Product? @relation(fields: [productId], references: [id])
articleId Int?
article Article? @relation(fields: [articleId], references: [id])
}

model Like {
id Int @id @default(autoincrement())
userId Int
productId Int?
articleId Int?
createdAt DateTime @default(now())

user User @relation(fields: [userId], references: [id])
product Product? @relation(fields: [productId], references: [id])
article Article? @relation(fields: [articleId], references: [id])

@@unique([userId, productId])
@@unique([userId, articleId])
}

148 changes: 148 additions & 0 deletions 4-Sprint-Misson/product.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import express from 'express';
import { PrismaClient } from '@prisma/client';

const router = express.Router();
const prisma = new PrismaClient();


// 상품 등록 API
router.post('/', async (req, res) => {
const { name, description, price, tags } = req.body;

try {
if (!name || !description || !price) {
return res.status(400).json({ error: '요소 누락 됨' });
}

const product = await prisma.product.create({
data: { name, description, price, tags },
});

res.status(201).json(product);
} catch (err) {
res.status(500).json({ error: '상품 등록 오류' });
}
});


// 상품 상세 조회 API
router.get('/:id', async (req, res) => {
const id = Number(req.params.id);
try {
const product = await prisma.product.findUnique({
where: { id },
select: {
id: true,
name: true,
description: true,
price: true,
tags: true,
createdAt: true,
},
});

if (!product) {
return res.status(404).json({ error: '상품을 찾을 수 없음.' });
}

res.status(200).json(product);
} catch (err) {
res.status(500).json({ error: '상품 조회 중 오류' });
}
});


// 상품 수정 API
router.patch('/:id', async (req, res) => {
const id = Number(req.params.id);
const { name, description, price, tags } = req.body;

try {
const exists = await prisma.product.findUnique({ where: { id } });
if (!exists) return res.status(404).json({ error: '상품 없음' });

const product = await prisma.product.update({
where: { id },
data: { name, description, price, tags },
});

res.status(200).json(product);
} catch (err) {
res.status(500).json({ error: '수정 중 오류' });
}
});


// 상품 삭제 API
router.delete('/:id', async (req, res) => {
const id = Number(req.params.id);
try {
const exists = await prisma.product.findUnique({ where: { id } });
if (!exists) return res.status(404).json({ error: '상품 없음' });

await prisma.product.delete({ where: { id } });
res.status(204).send();
} catch (err) {
res.status(500).json({ error: '삭제 중 오류' });
}
});


// 상품 목록 조회 API, offsset 방식 페이지네이션, 최신순 정렬
router.get('/', async (req, res) => {
const {
page = 1,
pageSize = 10,
sort = 'recent',
search = '',
} = req.query;

//page와 pageSize로 offset 기반 페이지 나눔진행
const skip = (Number(page) - 1) * Number(pageSize);
const take = Number(pageSize);

//검색, name 또는 description에 포함되면 해당 상품 검색
// insensitive: 대소문자 무시
try {
const where = {
OR: [
{ name: { contains: search, mode: 'insensitive' } },
{ description: { contains: search, mode: 'insensitive' } },
],
};

const products = await prisma.product.findMany({
where,
//최신순 정렬
orderBy: { createdAt: sort === 'recent' ? 'desc' : 'asc' },
skip,
take,
select: {
id: true,
name: true,
price: true,
createdAt: true,
},
});

//조건에 맞는 전체 데이터 수 산출
const total = await prisma.product.count({ where });

//상품 목록, pagination 내 정보 보이게
res.status(200).json({
data: products,
pagination: {
total,
page: Number(page),
pageSize: Number(pageSize),
totalPages: Math.ceil(total / pageSize),
},
});
} catch (err) {
res.status(500).json({ error: '조회 오류' });
}
});



export default router;
Loading