Skip to content

Commit

Permalink
[FEAT] 리뷰 추가 API 구현 #16 (#33)
Browse files Browse the repository at this point in the history
* [Feat(제리/김민정)] 테이블 생성 #21

* [Feat(제리/김민정)] 라우터 생성 #21

* [Feat(제리/김민정)] 컨트롤러 생성 #21

* [Feat(제리/김민정)] 서비스 생성 #21

* [Feat(제리/김민정)] DAO 생성 #21

* [Feat(제리/김민정)] DTO 생성 #21

* [Docs(제리/김민정)] Swagger 문서 작성 #21
  • Loading branch information
mzeong committed Dec 3, 2023
1 parent d766a66 commit a31ae82
Show file tree
Hide file tree
Showing 13 changed files with 230 additions and 4 deletions.
9 changes: 9 additions & 0 deletions 제리/controllers/reviews.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { response } from '../config/response';
import { status } from '../config/response.status';

import { createReview } from '../services/reviews.service';

export const addReview = async (req, res, next) => {
const userId = 1;
res.send(response(status.SUCCESS, await createReview(userId, req.body)));
};
11 changes: 11 additions & 0 deletions 제리/daos/reviews.dao.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Review } from '../models';

export const insertReview = async (userId, data) => {
return Review.create({
user_id: userId,
store_id: data.store_id,
mission_id: data.mission_id,
rating: data.rating,
content: data?.content,
});
};
5 changes: 3 additions & 2 deletions 제리/daos/users.dao.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ export const getUserFoodTypeNamesById = async (userId) => {

export const getUserName = async (userId) => {
return User.findOne({
raw: true,
where: { id: parseInt(userId, 10) },
where: {
id: userId,
},
attributes: ['name'],
});
};
4 changes: 2 additions & 2 deletions 제리/dtos/create-mission-response.dto.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
export const createMissionResponseDTO = (mission, storeName) => {
export const createMissionResponseDTO = (mission, store) => {
return {
target_amount: mission.target_amount,
reward: mission.reward,
deadline: mission.deadline,
store_name: storeName,
store_name: store.name,
};
};
9 changes: 9 additions & 0 deletions 제리/dtos/create-review-response.dto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const createReviewResponseDTO = (review, user, store) => {
return {
user_name: user.name,
store_name: store.name,
rating: review.rating,
content: review?.content,
created_at: review.created_at,
};
};
2 changes: 2 additions & 0 deletions 제리/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { BaseError } from './config/error.js';
import { usersRouter } from './routes/users.route.js';
import { storesRouter } from './routes/stores.route.js';
import { missionsRouter } from './routes/missions.route.js';
import { reviewsRouter } from './routes/reviews.route.js';

dotenv.config();

Expand All @@ -35,6 +36,7 @@ app.use('/api-docs', SwaggerUi.serve, SwaggerUi.setup(specs));
app.use('/users', usersRouter);
app.use('/stores', storesRouter);
app.use('/missions', missionsRouter);
app.use('/reviews', reviewsRouter);

app.use((req, res, next) => {
const err = new BaseError(status.NOT_FOUND);
Expand Down
1 change: 1 addition & 0 deletions 제리/models/mission.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Mission extends Sequelize.Model {
static associate(db) {
db.Mission.belongsToMany(db.User, { through: db.UserMission });
db.Mission.belongsTo(db.Store, { foreignKey: 'store_id' });
db.Mission.hasMany(db.Review, { foreignKey: 'mission_id' });
}
}

Expand Down
50 changes: 50 additions & 0 deletions 제리/models/review.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
const Sequelize = require('sequelize');

class Review extends Sequelize.Model {
static initiate(sequelize) {
Review.init(
{
user_id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
},
store_id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
},
mission_id: {
type: Sequelize.INTEGER,
allowNull: false,
primaryKey: true,
},
rating: {
type: Sequelize.INTEGER,
allowNull: false,
},
content: {
type: Sequelize.TEXT,
allowNull: true,
},
},
{
sequelize,
timestamps: true,
underscored: true,
modelName: 'Review',
tableName: 'review',
paranoid: false,
charset: 'utf8',
collate: 'utf8_general_ci',
}
);
}
static associate(db) {
db.Review.belongsTo(db.User, { foreignKey: 'user_id', primaryKey: true });
db.Review.belongsTo(db.Store, { foreignKey: 'store_id', primaryKey: true });
db.Review.belongsTo(db.Mission, { foreignKey: 'mission_id', primaryKey: true });
}
}

module.exports = Review;
1 change: 1 addition & 0 deletions 제리/models/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class Store extends Sequelize.Model {
static associate(db) {
db.Store.belongsTo(db.Region, { foreignKey: 'region_id' });
db.Store.hasMany(db.Mission, { foreignKey: 'store_id' });
db.Store.hasMany(db.Review, { foreignKey: 'store_id' });
}
}

Expand Down
1 change: 1 addition & 0 deletions 제리/models/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class User extends Sequelize.Model {
static associate(db) {
db.User.belongsToMany(db.FoodType, { through: 'UserFoodType' });
db.User.belongsToMany(db.Mission, { through: db.UserMission });
db.User.hasMany(db.Review, { foreignKey: 'user_id' });
}
}

Expand Down
8 changes: 8 additions & 0 deletions 제리/routes/reviews.route.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import express from 'express';
import asyncHandler from 'express-async-handler';

import { addReview } from '../controllers/reviews.controller.js';

export const reviewsRouter = express.Router();

reviewsRouter.post('/', asyncHandler(addReview));
24 changes: 24 additions & 0 deletions 제리/services/reviews.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { insertReview } from '../daos/reviews.dao';
import { getStoreName } from '../daos/stores.dao';
import { getUserName } from '../daos/users.dao';
import { BaseError } from '../config/error';
import { status } from '../config/response.status';
import { createReviewResponseDTO } from '../dtos/create-review-response.dto';

export const createReview = async (userId, body) => {
try {
const review = await insertReview(userId, body);
return createReviewResponseDTO(review, await getUserName(userId), await getStoreName(body.store_id));
} catch (err) {
console.log(err.name);
if (err.name === 'SequelizeValidationError' || err.name === 'SequelizeDatabaseError') {
throw new BaseError(status.BAD_REQUEST);
} else if (err.name === 'SequelizeUniqueConstraintError') {
throw new BaseError(status.DUPLICATE_ENTRY);
} else if (err.name === 'SequelizeForeignKeyConstraintError') {
throw new BaseError(status.NOT_FOUND);
} else {
throw new BaseError(status.INTERNAL_SERVER_ERROR);
}
}
};
109 changes: 109 additions & 0 deletions 제리/swagger/reviews.swagger.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
paths:
/reviews:
post:
summary: '리뷰 추가'
parameters:
- in: body
name: 'review'
required: true
schema:
$ref: '#/definitions/Review'
responses:
'200':
description: '리뷰 추가 성공!'
schema:
type: object
properties:
isSuccess:
type: boolean
example: true
code:
type: integer
example: 2000
message:
type: string
example: 'success!'
result:
type: object
example:
{
'user_name': '홍길동',
'store_name': '가게 이름',
'rating': 5,
'content': '맛있어요!',
}
'400':
description: '리뷰 정보 에러'
schema:
type: object
properties:
isSuccess:
type: boolean
example: false
code:
type: integer
example: 'COMMON001'
message:
type: string
example: '잘못된 요청입니다.'
'404':
description: '등록되지 않은 가게 또는 미션'
schema:
type: object
properties:
isSuccess:
type: boolean
example: false
code:
type: integer
example: 'COMMON005'
message:
type: string
example: '요청한 페이지를 찾을 수 없습니다. 관리자에게 문의 바랍니다.'
'409':
description: '이미 작성한 리뷰'
schema:
type: object
properties:
isSuccess:
type: boolean
example: false
code:
type: integer
example: 'COMMON006'
message:
type: string
example: '중복된 항목입니다.'
'500':
description: '서버 에러'
schema:
type: object
properties:
isSuccess:
type: boolean
example: false
code:
type: integer
example: 'COMMON000'
message:
type: string
example: '서버 에러, 관리자에게 문의 바랍니다.'
definitions:
Review:
properties:
store_id:
type: integer
example: 1
mission_id:
type: integer
example: 1
rating:
type: integer
example: 5
content:
type: string
example: '맛있어요!'
required:
- store_id
- mission_id
- rating

0 comments on commit a31ae82

Please sign in to comment.