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
17 changes: 17 additions & 0 deletions feature2/config/db.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import mysql from 'mysql2/promise';
import dotenv from 'dotenv';

dotenv.config();

export const pool = mysql.createPool({
host: process.env.DB_HOST || 'umc-database-week7.cx64ak0w82c4.ap-northeast-2.rds.amazonaws.com', // mysql의 hostname
user: process.env.DB_USER || 'root', // user 이름
port: process.env.DB_PORT || 3306, // 포트 번호
database: process.env.DB_TABLE || 'umc-week9-liv', // 데이터베이스 이름
password: process.env.DB_PASSWORD || 'Lhiso329k!', // 비밀번호
waitForConnections: true,
// Pool에 획득할 수 있는 connection이 없을 때,
// true면 요청을 queue에 넣고 connection을 사용할 수 있게 되면 요청을 실행하며, false이면 즉시 오류를 내보내고 다시 요청
connectionLimit: 10, // 몇 개의 커넥션을 가지게끔 할 것인지
queueLimit: 0, // getConnection에서 오류가 발생하기 전에 Pool에 대기할 요청의 개수 한도
});
6 changes: 6 additions & 0 deletions feature2/config/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class BaseError extends Error {
constructor(data){
super(data.message);
this.data = data;
}
}
9 changes: 9 additions & 0 deletions feature2/config/response.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const response = ({isSuccess, code, message, status}, result) => {
return {
isSuccess: isSuccess,
code: code,
message: message,
status: status,
result: result
}
};
28 changes: 28 additions & 0 deletions feature2/config/response.status.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { StatusCodes } from "http-status-codes";

export const status = {
// success
SUCCESS: {status: StatusCodes.OK, "isSuccess": true, "code": 2000, "message": "success!"},

// error
// common err
INTERNAL_SERVER_ERROR: {status: StatusCodes.INTERNAL_SERVER_ERROR, "isSuccess": false, "code": "COMMON000", "message": "서버 에러, 관리자에게 문의 바랍니다." },
BAD_REQUEST: {status: StatusCodes.BAD_REQUEST, "isSuccess": false, "code": "COMMON001", "message": "잘못된 요청입니다." },
UNAUTHORIZED: {status: StatusCodes.UNAUTHORIZED, "isSuccess": false, "code": "COMMON002", "message": "권한이 잘못되었습니다." },
METHOD_NOT_ALLOWED: {status: StatusCodes.METHOD_NOT_ALLOWED, "isSuccess": false, "code": "COMMON003", "message": "지원하지 않는 Http Method 입니다." },
FORBIDDEN: {status: StatusCodes.FORBIDDEN, "isSuccess": false, "code": "COMMON004", "message": "금지된 요청입니다." },
NOT_FOUND: {status: StatusCodes.NOT_FOUND, "isSuccess": false, "code": "COMMON005", "message": "요청한 페이지를 찾을 수 없습니다. 관리자에게 문의 바랍니다." },
PARAMETER_IS_WRONG : {status : StatusCodes.PARAMETER_IS_WRONG, "isSuccess" : false, "code": "COMMON006", "message":"잘못된 파라미터가 전달되었습니다."},

// member err
MEMBER_NOT_FOUND: {status: StatusCodes.BAD_REQUEST, "isSuccess": false, "code": "MEMBER4001", "message": "사용자가 없습니다."},
NICKNAME_NOT_EXIST: {status: StatusCodes.BAD_REQUEST, "isSuccess": false, "code": "MEMBER4002", "message": "닉네임은 필수입니다."},
EMAIL_ALREADY_EXIST: {status: StatusCodes.BAD_REQUEST, "isSuccess": false, "code": "MEMBER4003", "message": "이미 가입된 이메일이 존재합니다."},

// store err
STORE_ALREADY_EXIST: {status: StatusCodes.BAD_REQUEST, "isSuccess": false, "code": "STORE4001", "message": "이미 추가된 가게입니다."},

// article err
ARTICLE_NOT_FOUND: {status: StatusCodes.NOT_FOUND, "isSuccess": false, "code": "ARTICLE4001", "message": "게시글이 없습니다."}

};
16 changes: 16 additions & 0 deletions feature2/config/swagger.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import SwaggerJsdoc from "swagger-jsdoc";

const options = {
definition: {
info: {
title: 'UMC Study API',
version: '1.0.0',
description: 'UMC Study API with express, API 설명'
},
host: 'localhost:3000',
basepath: '../'
},
apis: ['./src/routes/*.js', './swagger/*']
};

export const specs = SwaggerJsdoc(options);
56 changes: 56 additions & 0 deletions feature2/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import express from 'express';
import { response } from './config/response.js';
import { BaseError } from './config/error.js';
import { status } from './config/response.status.js';
import dotenv from 'dotenv';
import { specs } from './config/swagger.config.js';
import SwaggerUi from 'swagger-ui-express';
import cors from 'cors';

import { userRouter } from './src/routes/user.route.js';
import { storeRouter } from './src/routes/store.route.js';


dotenv.config(); // .env 파일 사용 (환경 변수 관리)

const app = express();
const port = 3000;

// server setting - veiw, static, body-parser etc..
app.set('port', process.env.PORT || 3000) // 서버 포트 지정
app.use(cors()); // cors 방식 허용
app.use(express.static('public')); // 정적 파일 접근
app.use(express.json()); // request의 본문을 json으로 해석할 수 있도록 함 (JSON 형태의 요청 body를 파싱하기 위함)
app.use(express.urlencoded({extended: false})); // 단순 객체 문자열 형태로 본문 데이터 해석
app.use(express.urlencoded({extended: false})); // 단순 객체 문자열 형태로 본문 데이터 해석

// swagger
app.use('/api-docs', SwaggerUi.serve, SwaggerUi.setup(specs));

// router setting
app.use('/user', userRouter);
app.use('/store', storeRouter);
app.use('/:storeId', storeRouter);
app.use('/:userId', userRouter);


// error handling
app.use((req, res, next) => {
const err = new BaseError(status.NOT_FOUND);
next(err);
});

app.use((err, req, res, next) => {
console.log(err.data.status);
console.log(err.data.message);
// 템플릿 엔진 변수 설정
res.locals.message = err.data.message;
// 개발환경이면 에러를 출력하고 아니면 출력하지 않기
res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
console.log("error", err);
res.status(err.data.status || status.INTERNAL_SERVER_ERROR).send(response(err.data));
});

app.listen(app.get('port'), () => {
console.log(`Example app listening on port ${app.get('port')}`);
});
24 changes: 24 additions & 0 deletions feature2/src/controllers/user.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { response } from "../../config/response.js";
import { status } from "../../config/response.status.js";
import { joinUser, addUserMission } from "../services/user.service.js";
import { getReview } from "../providers/user.provider.js";

export const userSignin = async (req, res, next) => {
console.log("회원가입을 요청하였습니다!");
console.log("body:", req.body); // 값이 잘 들어오나 찍어보기 위한 테스트용

res.send(response(status.SUCCESS, await joinUser(req.body)));
}

export const userMission = async (req, res, next) => {
console.log("미션 추가를 요청하였습니다!");
console.log("body:", req.body); // 값이 잘 들어오나 찍어보기 위한 테스트용

res.send(response(status.SUCCESS, await addUserMission(req.body)));
}

export const reviewPreview = async (req, res, next) => {
console.log("리뷰 조회를 요청하였습니다!");

return res.send(response(status.SUCCESS, await getReview(req.params.userId, req.query)));
}
37 changes: 37 additions & 0 deletions feature2/src/dtos/user.dto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// sign in response DTO
export const signinResponseDTO = (user, prefer) => {
const preferFood = [];
for (let i = 0; i < prefer[0].length; i++) {
preferFood.push(prefer[0][i].f_category_name);
}
return {"email": user[0].email, "name": user[0].user_name, "preferCategory": preferFood};
}

// mission response DTO
export const MissionResponseDTO = (mission, user_mission) => {
const userName = [];
const reward = [];
userName.push(user_mission[0][0].user_name);
reward.push(user_mission[0][0].reward);
return {"user": userName, "mission": mission[0].mission_id, "reward": reward, "status":mission[0].status};
}

// preview review response DTO
export const previewReviewResponseDTO = (data) => {

const reviews = [];

for (let i = 0; i < data.length; i++) {
reviews.push({
"store_name": data[i].store_name,
"score": data[i].score,
"review_content": data[i].content,
"create_date": formatDate(data[i].created_at)
})
}
return {"reviewData": reviews, "cursorId": data[data.length-1].review_id};
}

const formatDate = (date) => {
return new Intl.DateTimeFormat('kr').format(new Date(date)).replaceAll(" ", "").slice(0, -1);
}
148 changes: 148 additions & 0 deletions feature2/src/models/user.dao.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { pool } from "../../config/db.config.js";
import { BaseError } from "../../config/error.js";
import { status } from "../../config/response.status.js";
import { connectFoodCategory, confirmEmail, getUserID, insertUserSql, getPreferToUserID } from "./user.sql.js";
import { insertMissionSql, getMissionID, getUserToMissionID } from "./user.sql.js";
import { getReviewByReviewIdAtFirst, getReviewByReviewId } from "./user.sql.js";

// User 데이터 삽입
export const addUser = async (data) => {
try{
const conn = await pool.getConnection();

const [confirm] = await pool.query(confirmEmail, data.email);

if(confirm[0].isExistEmail){
conn.release();
return -1;
}

const result = await pool.query(insertUserSql, [data.email, data.name, data.gender, data.birth, data.addr, data.specAddr, data.phone]);

conn.release();
return result[0].insertId;

}catch (err) {
throw new BaseError(status.PARAMETER_IS_WRONG);
}
}

// 사용자 정보 얻기
export const getUser = async (userId) => {
try {
const conn = await pool.getConnection();
const [user] = await pool.query(getUserID, userId);

console.log(user);

if(user.length == 0){
return -1;
}

conn.release();
return user;

} catch (err) {
throw new BaseError(status.PARAMETER_IS_WRONG);
}
}

// 음식 선호 카테고리 매핑
export const setPrefer = async (userId, foodCategoryId) => {
try {
const conn = await pool.getConnection();

await pool.query(connectFoodCategory, [foodCategoryId, userId]);

conn.release();

return;
} catch (err) {
throw new BaseError(status.PARAMETER_IS_WRONG);
}
}

// 사용자 선호 카테고리 반환
export const getUserPreferToUserID = async (userID) => {
try {
const conn = await pool.getConnection();
const prefer = await pool.query(getPreferToUserID, userID);

conn.release();

return prefer;
} catch (err) {
throw new BaseError(status.PARAMETER_IS_WRONG);
}
}

// mission 데이터 삽입
export const addMission = async (data) => {
try{
const conn = await pool.getConnection();

const result = await pool.query(insertMissionSql, [data.user, data.mission, data.status]);
conn.release();

return result[0].insertId;

}catch (err) {
throw new BaseError(status.PARAMETER_IS_WRONG);
}
}

// mission 정보 얻기
export const getMission = async (missionId) => {
try {
const conn = await pool.getConnection();
const [mission] = await pool.query(getMissionID, missionId);

console.log(mission);

if(mission.length == 0){
return -1;
}

conn.release();
return mission;

} catch (err) {
throw new BaseError(status.PARAMETER_IS_WRONG);
}
}

// user 반환
export const getUserNameToMissionID = async (missionId) => {
try {
const conn = await pool.getConnection();
const user_mission = await pool.query(getUserToMissionID, missionId);
console.log("\n 1 \n");

conn.release();

return user_mission;
} catch (err) {
throw new BaseError(status.PARAMETER_IS_WRONG);
}
}


// review 반환
export const getPreviewReview = async (cursorId, size, storeId) => {
try {
const conn = await pool.getConnection();

if(cursorId == "undefined" || typeof cursorId == "undefined" || cursorId == null){
const [reviews] = await pool.query(getReviewByReviewIdAtFirst, [parseInt(userId), parseInt(size)]);
conn.release();
return reviews;

}else{
const [reviews] = await pool.query(getReviewByReviewId, [parseInt(userId), parseInt(cursorId), parseInt(size)]);
conn.release();
return reviews;
}
} catch (err) {
throw new BaseError(status.PARAMETER_IS_WRONG);
}
}
35 changes: 35 additions & 0 deletions feature2/src/models/user.sql.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export const insertUserSql = "INSERT INTO user (email, user_name, gender, birth, user_address, user_spec_address, user_phone) VALUES (?, ?, ?, ?, ?, ?, ?);";

export const getUserID = "SELECT * FROM user WHERE user_id = ?";

export const connectFoodCategory = "INSERT INTO user_favor_category (f_category_id, user_id) VALUES (?, ?);";

export const confirmEmail = "SELECT EXISTS(SELECT 1 FROM user WHERE email = ?) as isExistEmail";

export const getPreferToUserID =
"SELECT ufc.uf_category_id, ufc.f_category_id, ufc.user_id, fcl.f_category_name "
+ "FROM user_favor_category ufc JOIN food_category_list fcl on ufc.f_category_id = fcl.f_category_id "
+ "WHERE ufc.user_id = ? ORDER BY ufc.f_category_id ASC;";

export const insertMissionSql = "INSERT INTO user_mission (user_id, mission_id, status) VALUES (?, ?, ?);";

export const getMissionID = "SELECT * FROM user_mission WHERE id = ?";

export const getUserToMissionID =
"SELECT user.user_name, mission.reward "
+ "FROM user_mission um JOIN user on um.user_id = user.user_id "
+ " JOIN mission on um.mission_id = mission.id "
+ "WHERE user_mission.id = ?;";


export const getReviewByReviewIdAtFirst =
"SELECT s.store_name, s.store_id, r.review_id, r.score, r.content, r.created_at "
+ "FROM review r JOIN store s on r.store_id = s.store_id "
+ "WHERE r.user_id = ? "
+ "ORDER BY r.review_id DESC LIMIT ? ;"

export const getReviewByReviewId =
"SELECT s.store_name, s.store_id, r.review_id, r.score, r.content, r.created_at "
+ "FROM review r JOIN store s on r.store_id = s.store_id "
+ "WHERE r.user_id = ? AND r.review_id < ? "
+ "ORDER BY r.review_id DESC LIMIT ? ;"
Loading