TIL
Node.js 백오피스 팀 프로젝트 (1)
황민도
2024. 6. 19. 23:06
내일배움캠프 스파르타 코딩클럽
< 디렉터리 >
< README.md >
# 🍜 육개장 (pet-sitter-platform backend)
안녕하세요 팀 6조 육개장입니다.
## (●'◡'●) 팀 소개
- 팀장 : 🫅모전하
- 팀원 : 👨🎓조민수
- 팀원 : 👩🎓박서진
- 팀원 : 👨🎓황민도
- 팀원 : 👩🎓이연서
## 프로젝트 소개
- 프로젝트 명 : 백오피 프로젝트 - petching
- 소개
- 한 줄 정리 : 유저와 펫시터를 연결해주는 플랫폼
## 🚦 Project Rules
# 개발환경
- OS: Window / Mac
- Code editor: Visual Studio Code
- Client-Tool : Insomnia
- DB-Tool: DBeaver
- Database: AWS/RDS (MySQL)
- Server: AWS/EC2
# 개발언어
- Front-End : Html, CSS, Javascript
- Back-End : Javascript
- Node.js, Express.js
- Database: MySQL
- ORM: Prisma
## 역할
- 모전하 : 팀장!!!! 프로필 기능, 프로필 사지 업로드
- 조민수 : 발표!!!! 펫시터 기능, 북마크 기능
- 박서진 : 리뷰 기능
- 황민도 : 회원가입 및 로그인 기능
- 이연서 : 예약 기능, 펫시터 기능
## 와이어 프레임

## API 명세서



## ERD

## Github Rules

## Code Convention

## 실행 방법
- 필요한 패키지 설치
```sh
yarn
```
- 서버 실행 (개발용)
```sh
yarn dev
```
< package.json >
{
"name": "petching",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"type": "module",
"scripts": {
"start": "node src/app.js",
"dev": "nodemon src/app.js",
"seed": "node prisma/seed.js",
"format": "prettier --write *.js **/*.js",
"test": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest --forceExit",
"test:unit": "cross-env NODE_ENV=test NODE_OPTIONS=--experimental-vm-modules jest __tests__/unit --forceExit"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.600.0",
"@prisma/client": "^5.15.0",
"aws-sdk": "^2.1644.0",
"bcrypt": "^5.1.1",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"joi": "^17.13.1",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.1",
"multer-s3": "^3.0.1",
"prisma": "^5.15.0",
"winston": "^3.13.0"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"cross-env": "^7.0.3",
"jest": "^29.7.0",
"nodemon": "^3.1.3",
"prettier": "^3.3.2"
}
}
< .prettierrc.json >
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": true,
"arrowParens": "always"
}
< .gitignore >
node_modules
# Keep environment variables out of version control
.env
< .env.example >
DATABASE_URL =
SERVER_PORT =
ACCESS_TOKEN_SECRET =
REFRESH_TOKEN_SECRET =
AWS_ACCESS_KEY_ID =
AWS_SECRET_ACCESS_KEY =
AWS_REGION =
AWS_S3_BUCKET_NAME =
< jest.config.js >
export default {
// 해당 패턴에 일치하는 경로가 존재할 경우 테스트를 하지 않고 넘어갑니다.
testPathIgnorePatterns: ['/node_modules/'],
// 테스트 실행 시 각 TestCase에 대한 출력을 해줍니다.
verbose: true,
// *.test.js, *.spec.js 파일만 테스트 파일로 인식해서 실행합니다.
testRegex: '.*\\.(test|spec)\\.js$',
transform: {},
};
< schema.prisma >
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement()) @map("id")
email String @unique @map("email")
password String @map("password")
name String @map("name")
introduce String? @map("introduce") @db.Text
profileImage String? @map("profile_image")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
booking Booking[]
refreshToken RefreshToken?
bookmark Bookmark[]
review Review[]
@@map("users")
}
model Petsitter {
id Int @id @default(autoincrement()) @map("id")
name String @map("name")
experience Int @default(0) @map("experience")
email String @unique @map("email")
password String @map("password")
profileImage String? @map("profile_image")
introduce String @default("안녕하세요.") @map("introduce")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
booking Booking[]
petsitterService PetsitterService[]
petsitterLocation PetsitterLocation[]
review Review[]
bookmark Bookmark[]
@@map("petsitters")
}
model PetsitterService {
id Int @id @default(autoincrement()) @map("id")
petsitterId Int @map("petsitter_id")
animalType AnimalType @map("animal_type")
serviceType ServiceType @map("service_type")
price Int @map("price")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
petsitter Petsitter @relation(fields: [petsitterId], references: [id], onDelete: Cascade)
@@map("petsitter_services")
}
enum AnimalType {
DOG
CAT
ETC
}
enum ServiceType {
WALK
SHOWER
PICKUP
FEED
}
model PetsitterLocation {
id Int @id @default(autoincrement()) @map("id")
petsitterId Int @map("petsitter_id")
location String @map("location")
surcharge Int @default(0) @map("surcharge")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
petsitter Petsitter @relation(fields: [petsitterId], references: [id], onDelete: Cascade)
@@map("petsitter_locations")
}
model Booking {
id Int @id @default(autoincrement()) @map("id")
userId Int @map("user_id")
petsitterId Int @map("petsitter_id")
animalType AnimalType @map("animal_type")
serviceType ServiceType @map("service")
location String @map("location")
content String? @map("content") @db.Text //요구사항
date DateTime @map("date")
totalPrice Int @map("total_price")
status Status @default(PENDING) @map("status")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
petsitter Petsitter @relation(fields: [petsitterId], references: [id], onDelete: Cascade)
@@map("bookings")
}
enum Status {
REJECTED //거절
CANCELED //취소
PENDING //보류
APPROVED //승인
DONE //끝남
}
model Bookmark {
id Int @id @default(autoincrement()) @map("id")
userId Int @unique @map("user_id")
petsitterId Int @map("petsitter_id")
createdAt DateTime @default(now()) @map("create_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
petsitter Petsitter @relation(fields: [petsitterId], references: [id], onDelete: Cascade)
@@map("bookmarks")
}
model Review {
id Int @id @default(autoincrement()) @map("id")
userId Int @map("user_id")
petsitterId Int @map("petsitter_id")
rating Int @map("rating")
comment String @map("comment")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
petsitter Petsitter @relation(fields: [petsitterId], references: [id], onDelete: Cascade)
@@map("reviews")
}
model RefreshToken {
id Int @id @default(autoincrement()) @map("id")
userId Int @unique @map("user_id")
refreshToken String? @map("refresh_token")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@map("refresh_tokens")
}
< seed.js >
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
// 펫시터 데이터
const petsitter1 = await prisma.petsitter.create({
data: {
name: '펫시터1',
experience: 1,
email: 'petsitter1@example.com',
password: '$2b$10$PeRLSlDLgtIyHYm3xEWeOu7ttFUzwlA1X6ysqaFQmCiM.riFhlaIW', //123 해싱된 값
},
});
const petsitter2 = await prisma.petsitter.create({
data: {
name: '펫시터2',
experience: 2,
email: 'petsitter2@example.com',
password: '$2b$10$PeRLSlDLgtIyHYm3xEWeOu7ttFUzwlA1X6ysqaFQmCiM.riFhlaIW',
},
});
const petsitter3 = await prisma.petsitter.create({
data: {
name: '펫시터3',
experience: 3,
email: 'petsitter3@example.com',
password: '$2b$10$PeRLSlDLgtIyHYm3xEWeOu7ttFUzwlA1X6ysqaFQmCiM.riFhlaIW',
},
});
//펫시터 서비스 데이터
await prisma.petsitterService.create({
data: {
petsitterId: petsitter1.id,
animalType: 'DOG',
serviceType: 'FEED',
price: 0,
},
});
await prisma.petsitterService.create({
data: {
petsitterId: petsitter1.id,
animalType: 'ETC',
serviceType: 'WALK',
price: 0,
},
});
await prisma.petsitterService.create({
data: {
petsitterId: petsitter2.id,
animalType: 'ETC',
serviceType: 'WALK',
price: 0,
},
});
await prisma.petsitterService.create({
data: {
petsitterId: petsitter3.id,
animalType: 'CAT',
serviceType: 'SHOWER',
price: 0,
},
});
//펫시터 서비스 장소
await prisma.petsitterLocation.create({
data: {
petsitterId: petsitter1.id,
location: '서울',
},
});
await prisma.petsitterLocation.create({
data: {
petsitterId: petsitter1.id,
location: '대구',
surcharge: 40000,
},
});
await prisma.petsitterLocation.create({
data: {
petsitterId: petsitter2.id,
location: '경주',
},
});
await prisma.petsitterLocation.create({
data: {
petsitterId: petsitter3.id,
location: '인천',
},
});
}
main()
.then(() => {
console.log('데이터 베이스 삽입 성공');
prisma.$disconnect();
})
.catch((err) => {
console.log('데이터 베이스 삽입 실패');
prisma.$disconnect();
});
< app.js >
import express from 'express';
import 'dotenv/config';
import { SERVER_PORT } from './constants/env.constant.js';
import { apiRouter } from './routers/index.js';
import errorHandler from './middlewares/error-handler.middleware.js';
import { HTTP_STATUS } from './constants/http-status.constant.js';
import logMiddleware from './middlewares/log.middleware.js';
const app = express();
app.use(express.json());
app.use(logMiddleware);
app.get('/health-check', (req, res, next) => {
res.status(HTTP_STATUS.OK).json({ message: "I'm healthy" });
});
app.use('/', apiRouter);
app.use(errorHandler);
app.listen(SERVER_PORT, () => {
console.log(`${SERVER_PORT}번 포트로 서버가 열렸습니다.`);
});