From 4e71293a4d1f939dc3cf724ae72a62036b161f9c Mon Sep 17 00:00:00 2001 From: dingding-21 Date: Fri, 10 Jun 2022 23:29:04 +0900 Subject: [PATCH] [ADD] Level1: Review API implementation related to: #7 --- 4th-seminar/level1/.gitignore | 146 ++++++++++++++++++ 4th-seminar/level1/nodemon.json | 11 ++ 4th-seminar/level1/package.json | 26 ++++ 4th-seminar/level1/src/config/index.ts | 23 +++ .../src/controllers/ReviewController.ts | 75 +++++++++ .../level1/src/controllers/UserController.ts | 127 +++++++++++++++ 4th-seminar/level1/src/controllers/index.ts | 2 + 4th-seminar/level1/src/index.ts | 43 ++++++ .../interfaces/comm/PostBaseResponseDto.ts | 5 + .../src/interfaces/movie/MovieCreateDto.ts | 0 .../level1/src/interfaces/movie/MovieInfo.ts | 7 + .../src/interfaces/review/ReviewCreateDto.ts | 7 + .../src/interfaces/review/ReviewInfo.ts | 8 + .../interfaces/review/ReviewResponseDto.ts | 8 + .../src/interfaces/school/SchoolInfo.ts | 4 + .../src/interfaces/user/UserCreateDto.ts | 9 ++ .../level1/src/interfaces/user/UserInfo.ts | 9 ++ .../src/interfaces/user/UserResponseDto.ts | 6 + .../src/interfaces/user/UserUpdateDto.ts | 9 ++ 4th-seminar/level1/src/loaders/db.ts | 27 ++++ 4th-seminar/level1/src/models/Movie.ts | 27 ++++ 4th-seminar/level1/src/models/Review.ts | 28 ++++ 4th-seminar/level1/src/models/User.ts | 27 ++++ .../level1/src/modules/responseMessage.ts | 26 ++++ 4th-seminar/level1/src/modules/statusCode.ts | 15 ++ 4th-seminar/level1/src/modules/util.ts | 19 +++ 4th-seminar/level1/src/routes/ReviewRouter.ts | 19 +++ 4th-seminar/level1/src/routes/UserRouter.ts | 11 ++ 4th-seminar/level1/src/routes/index.ts | 10 ++ .../level1/src/services/ReviewService.ts | 59 +++++++ .../level1/src/services/UserService.ts | 70 +++++++++ 4th-seminar/level1/src/services/index.ts | 3 + 4th-seminar/level1/tsconfig.json | 28 ++++ 33 files changed, 894 insertions(+) create mode 100644 4th-seminar/level1/.gitignore create mode 100644 4th-seminar/level1/nodemon.json create mode 100644 4th-seminar/level1/package.json create mode 100644 4th-seminar/level1/src/config/index.ts create mode 100644 4th-seminar/level1/src/controllers/ReviewController.ts create mode 100644 4th-seminar/level1/src/controllers/UserController.ts create mode 100644 4th-seminar/level1/src/controllers/index.ts create mode 100644 4th-seminar/level1/src/index.ts create mode 100644 4th-seminar/level1/src/interfaces/comm/PostBaseResponseDto.ts create mode 100644 4th-seminar/level1/src/interfaces/movie/MovieCreateDto.ts create mode 100644 4th-seminar/level1/src/interfaces/movie/MovieInfo.ts create mode 100644 4th-seminar/level1/src/interfaces/review/ReviewCreateDto.ts create mode 100644 4th-seminar/level1/src/interfaces/review/ReviewInfo.ts create mode 100644 4th-seminar/level1/src/interfaces/review/ReviewResponseDto.ts create mode 100644 4th-seminar/level1/src/interfaces/school/SchoolInfo.ts create mode 100644 4th-seminar/level1/src/interfaces/user/UserCreateDto.ts create mode 100644 4th-seminar/level1/src/interfaces/user/UserInfo.ts create mode 100644 4th-seminar/level1/src/interfaces/user/UserResponseDto.ts create mode 100644 4th-seminar/level1/src/interfaces/user/UserUpdateDto.ts create mode 100644 4th-seminar/level1/src/loaders/db.ts create mode 100644 4th-seminar/level1/src/models/Movie.ts create mode 100644 4th-seminar/level1/src/models/Review.ts create mode 100644 4th-seminar/level1/src/models/User.ts create mode 100644 4th-seminar/level1/src/modules/responseMessage.ts create mode 100644 4th-seminar/level1/src/modules/statusCode.ts create mode 100644 4th-seminar/level1/src/modules/util.ts create mode 100644 4th-seminar/level1/src/routes/ReviewRouter.ts create mode 100644 4th-seminar/level1/src/routes/UserRouter.ts create mode 100644 4th-seminar/level1/src/routes/index.ts create mode 100644 4th-seminar/level1/src/services/ReviewService.ts create mode 100644 4th-seminar/level1/src/services/UserService.ts create mode 100644 4th-seminar/level1/src/services/index.ts create mode 100644 4th-seminar/level1/tsconfig.json diff --git a/4th-seminar/level1/.gitignore b/4th-seminar/level1/.gitignore new file mode 100644 index 0000000..a9ba020 --- /dev/null +++ b/4th-seminar/level1/.gitignore @@ -0,0 +1,146 @@ + +# Created by https://www.toptal.com/developers/gitignore/api/node +# Edit at https://www.toptal.com/developers/gitignore?templates=node + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* +yarn.lock + +### Node Patch ### +# Serverless Webpack directories +.webpack/ + +# Optional stylelint cache + +# SvelteKit build / generate output +.svelte-kit + +# End of https://www.toptal.com/developers/gitignore/api/node \ No newline at end of file diff --git a/4th-seminar/level1/nodemon.json b/4th-seminar/level1/nodemon.json new file mode 100644 index 0000000..7e70d98 --- /dev/null +++ b/4th-seminar/level1/nodemon.json @@ -0,0 +1,11 @@ +{ + "watch": [ + "src", + ".env" + ], + "ext": "js,ts,json", + "ignore": [ + "src/**/*.spec.ts" + ], + "exec": "ts-node --transpile-only ./src/index.ts" +} \ No newline at end of file diff --git a/4th-seminar/level1/package.json b/4th-seminar/level1/package.json new file mode 100644 index 0000000..d132071 --- /dev/null +++ b/4th-seminar/level1/package.json @@ -0,0 +1,26 @@ +{ + "name": "node-typescript-init", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "dev": "nodemon", + "build": "tsc && node dist" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@types/express": "^4.17.13", + "@types/mongoose": "^5.11.97", + "@types/node": "^17.0.25", + "nodemon": "^2.0.15", + "ts-node": "^10.7.0", + "typescript": "^4.6.3" + }, + "dependencies": { + "dotenv": "^16.0.0", + "express": "^4.17.3", + "express-validator": "^6.14.0", + "mongoose": "^6.3.1" + } +} diff --git a/4th-seminar/level1/src/config/index.ts b/4th-seminar/level1/src/config/index.ts new file mode 100644 index 0000000..4449be4 --- /dev/null +++ b/4th-seminar/level1/src/config/index.ts @@ -0,0 +1,23 @@ +import dotenv from "dotenv"; + +// Set the NODE_ENV to 'development' by default +process.env.NODE_ENV = process.env.NODE_ENV || "development"; + +const envFound = dotenv.config(); +if (envFound.error) { + // This error should crash whole process + + throw new Error("⚠️ Couldn't find .env file ⚠️"); +} + +export default { + /** + * Your favorite port + */ + port: parseInt(process.env.PORT as string, 10) as number, + + /** + * MongoDB URI + */ + mongoURI: process.env.MONGODB_URI as string, +}; \ No newline at end of file diff --git a/4th-seminar/level1/src/controllers/ReviewController.ts b/4th-seminar/level1/src/controllers/ReviewController.ts new file mode 100644 index 0000000..e356d84 --- /dev/null +++ b/4th-seminar/level1/src/controllers/ReviewController.ts @@ -0,0 +1,75 @@ +import { Request, Response } from 'express'; +import statusCode from '../../../../3rd-seminar/level1/src/modules/statusCode'; +import util from '../../../../3rd-seminar/level1/src/modules/util'; +import message from '../../../../3rd-seminar/level1/src/modules/responseMessage'; +import { validationResult } from 'express-validator'; +import { ReviewCreateDto } from '../interfaces/review/ReviewCreateDto'; +import ReviewService from '../services/ReviewService'; + +/** + * @route POST /review/movies/:movieId + * @desc Create Review + * @access Public + */ + +const createReview = async (req: Request, res: Response) => { + const error = validationResult(req); + + if (!error.isEmpty()) { + return res + .status(statusCode.BAD_REQUEST) + .send(util.fail(statusCode.BAD_REQUEST, message.NULL_VALUE)); + } + + const reviewCreateDto: ReviewCreateDto = req.body; + const { movieId } = req.params; + + try { + const data = await ReviewService.createReview(movieId, reviewCreateDto); + + res + .status(statusCode.CREATED) + .send(util.success(statusCode.CREATED, message.CREATE_REVIEW_SUCCESS)); + } catch (error) { + console.log(error); + res + .status(statusCode.INTERNAL_SERVER_ERROR) + .send( + util.fail( + statusCode.INTERNAL_SERVER_ERROR, + message.INTERNAL_SERVER_ERROR + ) + ); + } +}; + +/** + * @route GET /review/movies/:movieId + * @desc Get Review + * @access Public + */ + +const getReviews = async (req: Request, res: Response): Promise => { + const { movieId } = req.params; + + try { + const data = await ReviewService.getReviews(movieId); + res + .status(statusCode.OK) + .send(util.success(statusCode.OK, message.READ_REVIEW_SUCCESS, data)); + } catch (error) { + res + .status(statusCode.INTERNAL_SERVER_ERROR) + .send( + util.fail( + statusCode.INTERNAL_SERVER_ERROR, + message.INTERNAL_SERVER_ERROR + ) + ); + } +}; + +export default { + createReview, + getReviews, +}; diff --git a/4th-seminar/level1/src/controllers/UserController.ts b/4th-seminar/level1/src/controllers/UserController.ts new file mode 100644 index 0000000..cee1842 --- /dev/null +++ b/4th-seminar/level1/src/controllers/UserController.ts @@ -0,0 +1,127 @@ +import { Request, Response } from 'express'; +import { UserCreateDto } from '../interfaces/user/UserCreateDto'; +import statusCode from '../modules/statusCode'; +import util from '../modules/util'; +import message from '../modules/responseMessage'; +import { UserService } from '../services'; +import { UserUpdateDto } from '../interfaces/user/UserUpdateDto'; + +/** + * @route POST /user + * @desc Create User + * @access Public + */ +const createUser = async (req: Request, res: Response) => { + const userCreateDto: UserCreateDto = req.body; //User Create Dto로 req.body 받아옴 + + try { + const data = await UserService.createUser(userCreateDto); + + res + .status(statusCode.CREATED) + .send( + util.success(statusCode.CREATED, message.CREATE_USER_SUCCESS, data) + ); + } catch (error) { + console.log(error); + // 서버 내부에서 오류 발생 + res + .status(statusCode.INTERNAL_SERVER_ERROR) + .json( + util.fail( + statusCode.INTERNAL_SERVER_ERROR, + message.INTERNAL_SERVER_ERROR + ) + ); + } +}; + +/** + * @route PUT /user/:userId + * @desc Update User + * @access Public + */ +const updateUser = async (req: Request, res: Response) => { + const userUpdateDto: UserUpdateDto = req.body; + const { userId } = req.params; + try { + await UserService.updateUser(userId, userUpdateDto); + + res.status(statusCode.NO_CONTENT).send(); + } catch (error) { + console.log(error); + // 서버 내부에서 오류 발생 + res + .status(statusCode.INTERNAL_SERVER_ERROR) + .json( + util.fail( + statusCode.INTERNAL_SERVER_ERROR, + message.INTERNAL_SERVER_ERROR + ) + ); + } +}; + +/** + * @route GET /user/:userId + * @desc Read User + * @access Public + */ +const findUserById = async (req: Request, res: Response) => { + const { userId } = req.params; + try { + const data = await UserService.findUserById(userId); + if (!data) { + return res + .status(statusCode.BAD_REQUEST) + .send(util.fail(statusCode.NOT_FOUND, message.NOT_FOUND)); + } + res + .status(statusCode.OK) + .send(util.success(statusCode.OK, message.READ_USER_SUCCESS, data)); + } catch (error) { + console.log(error); + // 서버 내부에서 오류 발생 + res + .status(statusCode.INTERNAL_SERVER_ERROR) + .json( + util.fail( + statusCode.INTERNAL_SERVER_ERROR, + message.INTERNAL_SERVER_ERROR + ) + ); + } +}; + +/** + * @route DELETE /user/:userId + * @desc Delete User + * @access Public + */ +const deleteUser = async (req: Request, res: Response) => { + const { userId } = req.params; + try { + const data = await UserService.findUserById(userId); + await UserService.deleteUser(userId); + + res.status(statusCode.NO_CONTENT).send(); + } catch (error) { + console.log(error); + // 서버 내부에서 오류 발생 + res + .status(statusCode.INTERNAL_SERVER_ERROR) + .json( + util.fail( + statusCode.INTERNAL_SERVER_ERROR, + message.INTERNAL_SERVER_ERROR + ) + ); + } +}; + +export default { + createUser, + updateUser, + findUserById, + deleteUser, +}; diff --git a/4th-seminar/level1/src/controllers/index.ts b/4th-seminar/level1/src/controllers/index.ts new file mode 100644 index 0000000..68886ac --- /dev/null +++ b/4th-seminar/level1/src/controllers/index.ts @@ -0,0 +1,2 @@ +import UserController from './UserController'; +export default { UserController }; diff --git a/4th-seminar/level1/src/index.ts b/4th-seminar/level1/src/index.ts new file mode 100644 index 0000000..3fc26c9 --- /dev/null +++ b/4th-seminar/level1/src/index.ts @@ -0,0 +1,43 @@ +import express, { Request, Response, NextFunction } from 'express'; +const app = express(); +import connectDB from './loaders/db'; +import routes from './routes'; +require('dotenv').config(); + +connectDB(); + +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); + +app.use(routes); +interface ErrorType { + message: string; + status: number; +} + +app.use(function ( + err: ErrorType, + req: Request, + res: Response, + next: NextFunction +) { + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'production' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); + +app + .listen(process.env.PORT, () => { + console.log(` + ################################################ + 🛡️ Server listening on port 🛡️ + ################################################ + `); + }) + .on('error', (err) => { + console.error(err); + process.exit(1); + }); diff --git a/4th-seminar/level1/src/interfaces/comm/PostBaseResponseDto.ts b/4th-seminar/level1/src/interfaces/comm/PostBaseResponseDto.ts new file mode 100644 index 0000000..fb2bb10 --- /dev/null +++ b/4th-seminar/level1/src/interfaces/comm/PostBaseResponseDto.ts @@ -0,0 +1,5 @@ +import mongoose from 'mongoose'; + +export interface PostBaseResponseDto { + _id: mongoose.Schema.Types.ObjectId; +} diff --git a/4th-seminar/level1/src/interfaces/movie/MovieCreateDto.ts b/4th-seminar/level1/src/interfaces/movie/MovieCreateDto.ts new file mode 100644 index 0000000..e69de29 diff --git a/4th-seminar/level1/src/interfaces/movie/MovieInfo.ts b/4th-seminar/level1/src/interfaces/movie/MovieInfo.ts new file mode 100644 index 0000000..a1e0244 --- /dev/null +++ b/4th-seminar/level1/src/interfaces/movie/MovieInfo.ts @@ -0,0 +1,7 @@ +export interface MovieInfo { + title: string; + director: string; + startDate: Date; + thumbnail: string; + story: string; +} diff --git a/4th-seminar/level1/src/interfaces/review/ReviewCreateDto.ts b/4th-seminar/level1/src/interfaces/review/ReviewCreateDto.ts new file mode 100644 index 0000000..7309351 --- /dev/null +++ b/4th-seminar/level1/src/interfaces/review/ReviewCreateDto.ts @@ -0,0 +1,7 @@ +import mongoose from 'mongoose'; + +export interface ReviewCreateDto { + writer: mongoose.Types.ObjectId; + title: string; + content: string; +} diff --git a/4th-seminar/level1/src/interfaces/review/ReviewInfo.ts b/4th-seminar/level1/src/interfaces/review/ReviewInfo.ts new file mode 100644 index 0000000..1f1e5c4 --- /dev/null +++ b/4th-seminar/level1/src/interfaces/review/ReviewInfo.ts @@ -0,0 +1,8 @@ +import mongoose from 'mongoose'; + +export interface ReviewInfo { + writer: mongoose.Types.ObjectId; + movie: mongoose.Types.ObjectId; + title: string; + content: string; +} diff --git a/4th-seminar/level1/src/interfaces/review/ReviewResponseDto.ts b/4th-seminar/level1/src/interfaces/review/ReviewResponseDto.ts new file mode 100644 index 0000000..e7cf0b3 --- /dev/null +++ b/4th-seminar/level1/src/interfaces/review/ReviewResponseDto.ts @@ -0,0 +1,8 @@ +import { MovieInfo } from '../movie/MovieInfo'; + +export interface ReviewResponseDto { + writer: string; + title: string; + content: string; + movie: MovieInfo; +} diff --git a/4th-seminar/level1/src/interfaces/school/SchoolInfo.ts b/4th-seminar/level1/src/interfaces/school/SchoolInfo.ts new file mode 100644 index 0000000..9a8ab13 --- /dev/null +++ b/4th-seminar/level1/src/interfaces/school/SchoolInfo.ts @@ -0,0 +1,4 @@ +export interface SchoolInfo { + name: string; + major: string; +} diff --git a/4th-seminar/level1/src/interfaces/user/UserCreateDto.ts b/4th-seminar/level1/src/interfaces/user/UserCreateDto.ts new file mode 100644 index 0000000..067f554 --- /dev/null +++ b/4th-seminar/level1/src/interfaces/user/UserCreateDto.ts @@ -0,0 +1,9 @@ +import { SchoolInfo } from '../school/SchoolInfo'; + +export interface UserCreateDto { + name: string; + phone: string; + email: string; + age?: number; + school?: SchoolInfo; +} diff --git a/4th-seminar/level1/src/interfaces/user/UserInfo.ts b/4th-seminar/level1/src/interfaces/user/UserInfo.ts new file mode 100644 index 0000000..7e782ca --- /dev/null +++ b/4th-seminar/level1/src/interfaces/user/UserInfo.ts @@ -0,0 +1,9 @@ +import { SchoolInfo } from '../school/SchoolInfo'; + +export interface UserInfo { + name: string; + phone: string; + email: string; + age: number; + school: SchoolInfo; +} diff --git a/4th-seminar/level1/src/interfaces/user/UserResponseDto.ts b/4th-seminar/level1/src/interfaces/user/UserResponseDto.ts new file mode 100644 index 0000000..0181aec --- /dev/null +++ b/4th-seminar/level1/src/interfaces/user/UserResponseDto.ts @@ -0,0 +1,6 @@ +import mongoose from 'mongoose'; +import { UserCreateDto } from './UserCreateDto'; + +export interface userResponseDto extends UserCreateDto { + _id: mongoose.Schema.Types.ObjectId; +} diff --git a/4th-seminar/level1/src/interfaces/user/UserUpdateDto.ts b/4th-seminar/level1/src/interfaces/user/UserUpdateDto.ts new file mode 100644 index 0000000..593751c --- /dev/null +++ b/4th-seminar/level1/src/interfaces/user/UserUpdateDto.ts @@ -0,0 +1,9 @@ +import { SchoolInfo } from '../school/SchoolInfo'; + +export interface UserUpdateDto { + name?: string; + phone?: string; + email?: string; + age?: number; + school?: SchoolInfo; +} diff --git a/4th-seminar/level1/src/loaders/db.ts b/4th-seminar/level1/src/loaders/db.ts new file mode 100644 index 0000000..23a7253 --- /dev/null +++ b/4th-seminar/level1/src/loaders/db.ts @@ -0,0 +1,27 @@ +import mongoose from 'mongoose'; +import config from '../config'; +import Movie from '../models/Movie'; +import Review from '../models/Review'; + +const connectDB = async () => { + try { + await mongoose.connect(config.mongoURI); + + mongoose.set('autoCreate', true); + + console.log('Mongoose Connected ...'); + + Movie.createCollection().then((collection) => { + console.log('Movie Collection Created'); + }); + + Review.createCollection().then((collection) => { + console.log('Review Collection Created'); + }); + } catch (err: any) { + console.error(err.message); + process.exit(1); + } +}; + +export default connectDB; diff --git a/4th-seminar/level1/src/models/Movie.ts b/4th-seminar/level1/src/models/Movie.ts new file mode 100644 index 0000000..ffa84f7 --- /dev/null +++ b/4th-seminar/level1/src/models/Movie.ts @@ -0,0 +1,27 @@ +import mongoose from 'mongoose'; +import { MovieInfo } from '../interfaces/movie/MovieInfo'; + +const MovieSchema = new mongoose.Schema({ + title: { + type: String, + required: true, + }, + director: { + type: String, + required: true, + }, + startDate: { + type: Date, + }, + thumbnail: { + type: String, + }, + story: { + type: String, + }, +}); + +export default mongoose.model( + 'Movie', + MovieSchema +); diff --git a/4th-seminar/level1/src/models/Review.ts b/4th-seminar/level1/src/models/Review.ts new file mode 100644 index 0000000..b0361d5 --- /dev/null +++ b/4th-seminar/level1/src/models/Review.ts @@ -0,0 +1,28 @@ +import mongoose from 'mongoose'; +import { ReviewInfo } from '../interfaces/review/ReviewInfo'; + +const ReviewSchema = new mongoose.Schema({ + writer: { + type: mongoose.Types.ObjectId, + required: true, + ref: 'User', + }, + movie: { + type: mongoose.Types.ObjectId, + required: true, + ref: 'Movie', + }, + title: { + type: String, + required: true, + }, + content: { + type: String, + required: true, + }, +}); + +export default mongoose.model( + 'Review', + ReviewSchema +); diff --git a/4th-seminar/level1/src/models/User.ts b/4th-seminar/level1/src/models/User.ts new file mode 100644 index 0000000..bf510da --- /dev/null +++ b/4th-seminar/level1/src/models/User.ts @@ -0,0 +1,27 @@ +import mongoose from 'mongoose'; +import { UserInfo } from '../interfaces/user/UserInfo'; + +const UserSchema = new mongoose.Schema({ + name: { + type: String, + required: true, + }, + phone: { + type: String, + required: true, + }, + email: { + type: String, + required: true, + unique: true, + }, + age: { + type: Number, + }, + school: { + name: { type: String }, + major: { type: String }, + }, +}); + +export default mongoose.model('User', UserSchema); diff --git a/4th-seminar/level1/src/modules/responseMessage.ts b/4th-seminar/level1/src/modules/responseMessage.ts new file mode 100644 index 0000000..053d2f1 --- /dev/null +++ b/4th-seminar/level1/src/modules/responseMessage.ts @@ -0,0 +1,26 @@ +const message = { + NULL_VALUE: '필요한 값이 없습니다.', + NOT_FOUND: '존재하지 않는 자원', + BAD_REQUEST: '잘못된 요청', + INTERNAL_SERVER_ERROR: '서버 내부 오류', + + // 유저 + READ_USER_SUCCESS: '유저 조회 성공', + CREATE_USER_SUCCESS: '유저 생성 성공', + DELETE_USER_SUCCESS: '유저 삭제 성공', + UPDATE_USER_SUCCESS: '유저 수정 성공', + + // 블로그 + READ_BLOG_SUCCESS: '블로그 조회 성공', + CREATE_BLOG_SUCCESS: '블로그 생성 성공', + DELETE_BLOG_SUCCESS: '블로그 삭제 성공', + UPDATE_BLOG_SUCCESS: '블로그 수정 성공', + + // 리뷰 + READ_REVIEW_SUCCESS: '리뷰 조회 성공', + CREATE_REVIEW_SUCCESS: '리뷰 생성 성공', + DELETE_REVIEW_SUCCESS: '리뷰 삭제 성공', + UPDATE_REVIEW_SUCCESS: '리뷰 수정 성공', +}; + +export default message; diff --git a/4th-seminar/level1/src/modules/statusCode.ts b/4th-seminar/level1/src/modules/statusCode.ts new file mode 100644 index 0000000..a8a29eb --- /dev/null +++ b/4th-seminar/level1/src/modules/statusCode.ts @@ -0,0 +1,15 @@ +const statusCode = { + OK: 200, + CREATED: 201, + NO_CONTENT: 204, + BAD_REQUEST: 400, + UNAUTHORIZED: 401, + FORBIDDEN: 403, + NOT_FOUND: 404, + CONFLICT: 409, + INTERNAL_SERVER_ERROR: 500, + SERVICE_UNAVAILABLE: 503, + DB_ERROR: 600, +}; + +export default statusCode; \ No newline at end of file diff --git a/4th-seminar/level1/src/modules/util.ts b/4th-seminar/level1/src/modules/util.ts new file mode 100644 index 0000000..406cc16 --- /dev/null +++ b/4th-seminar/level1/src/modules/util.ts @@ -0,0 +1,19 @@ +const util = { + success: (status: number, message: string, data?: any) => { + return { + status, + success: true, + message, + data, + }; + }, + fail: (status: number, message: string, data?: any) => { + return { + status, + success: false, + message, + }; + }, +}; + +export default util; \ No newline at end of file diff --git a/4th-seminar/level1/src/routes/ReviewRouter.ts b/4th-seminar/level1/src/routes/ReviewRouter.ts new file mode 100644 index 0000000..f187d7d --- /dev/null +++ b/4th-seminar/level1/src/routes/ReviewRouter.ts @@ -0,0 +1,19 @@ +import express, { Router } from 'express'; +import ReviewController from '../controllers/ReviewController'; +import { body } from 'express-validator/check'; + +const router: Router = express.Router(); + +router.post( + '/movies/:movieId', + [ + body('title').notEmpty(), + body('content').notEmpty(), + body('writer').notEmpty(), + ], + ReviewController.createReview +); + +router.get('/movies/:movieId', ReviewController.getReviews); + +export default router; diff --git a/4th-seminar/level1/src/routes/UserRouter.ts b/4th-seminar/level1/src/routes/UserRouter.ts new file mode 100644 index 0000000..6beccd2 --- /dev/null +++ b/4th-seminar/level1/src/routes/UserRouter.ts @@ -0,0 +1,11 @@ +import express, { Router } from 'express'; +import UserController from '../controllers/UserController'; + +const router: Router = express.Router(); + +router.post('/', UserController.createUser); +router.put('/:userId', UserController.updateUser); +router.get('/:userId', UserController.findUserById); +router.delete('/:userId', UserController.deleteUser); + +export default router; diff --git a/4th-seminar/level1/src/routes/index.ts b/4th-seminar/level1/src/routes/index.ts new file mode 100644 index 0000000..bf824cb --- /dev/null +++ b/4th-seminar/level1/src/routes/index.ts @@ -0,0 +1,10 @@ +import express, { Router } from 'express'; +import UserRouter from '../routes/UserRouter'; +import ReviewRouter from '../routes/ReviewRouter'; + +const router: Router = express.Router(); + +router.use('/user', UserRouter); +router.use('/review', ReviewRouter); + +export default router; diff --git a/4th-seminar/level1/src/services/ReviewService.ts b/4th-seminar/level1/src/services/ReviewService.ts new file mode 100644 index 0000000..94ed93a --- /dev/null +++ b/4th-seminar/level1/src/services/ReviewService.ts @@ -0,0 +1,59 @@ +import { PostBaseResponseDto } from '../interfaces/comm/PostBaseResponseDto'; +import { ReviewCreateDto } from '../interfaces/review/ReviewCreateDto'; +import { ReviewResponseDto } from '../interfaces/review/ReviewResponseDto'; +import Review from '../models/Review'; + +const createReview = async ( + movieId: string, + reviewCreateDto: ReviewCreateDto +): Promise => { + try { + const review = new Review({ + title: reviewCreateDto.title, + content: reviewCreateDto.content, + writer: reviewCreateDto.writer, + movie: movieId, + }); + await review.save(); + + const data = { + _id: review._id, + }; + + return data; + } catch (error) { + console.log(error); + throw error; + } +}; + +const getReviews = async (movieId: string): Promise => { + try { + const reviews = await Review.find({ + movie: movieId, + }) + .populate('writer', 'name') + .populate('movie'); + + const data = await Promise.all( + reviews.map((review: any) => { + const result = { + writer: review.writer.name, + movie: review.movie, + title: review.title, + content: review.content, + }; + return result; + }) + ); + return data; + } catch (error) { + console.log(error); + throw error; + } +}; + +export default { + createReview, + getReviews, +}; diff --git a/4th-seminar/level1/src/services/UserService.ts b/4th-seminar/level1/src/services/UserService.ts new file mode 100644 index 0000000..9f198ad --- /dev/null +++ b/4th-seminar/level1/src/services/UserService.ts @@ -0,0 +1,70 @@ +import { PostBaseResponseDto } from '../interfaces/comm/PostBaseResponseDto'; +import { UserCreateDto } from '../interfaces/user/UserCreateDto'; +import { userResponseDto } from '../interfaces/user/UserResponseDto'; +import { UserUpdateDto } from '../interfaces/user/UserUpdateDto'; +import User from '../models/User'; + +const createUser = async ( + userCreateDto: UserCreateDto +): Promise => { + try { + const user = new User({ + name: userCreateDto.name, + phone: userCreateDto.phone, + email: userCreateDto.email, + age: userCreateDto.age, + school: userCreateDto.school, + }); + + await user.save(); + + const data = { + _id: user._id, + }; + + return data; + } catch (error) { + console.log(error); + throw error; + } +}; + +const updateUser = async (userId: string, userUpdateDto: UserUpdateDto) => { + try { + await User.findByIdAndUpdate(userId, userUpdateDto); + } catch (error) { + console.log(error); + throw error; + } +}; + +const findUserById = async ( + userId: string +): Promise => { + try { + const user = await User.findById(userId); + + if (!user) { + return null; + } + return user; + } catch (error) { + console.log(error); + throw error; + } +}; + +const deleteUser = async (userId: string): Promise => { + try { + await User.findByIdAndDelete(userId); + } catch (error) { + console.log(error); + throw error; + } +}; +export default { + createUser, + updateUser, + findUserById, + deleteUser, +}; diff --git a/4th-seminar/level1/src/services/index.ts b/4th-seminar/level1/src/services/index.ts new file mode 100644 index 0000000..1c98f16 --- /dev/null +++ b/4th-seminar/level1/src/services/index.ts @@ -0,0 +1,3 @@ +import UserService from './UserService'; + +export { UserService }; diff --git a/4th-seminar/level1/tsconfig.json b/4th-seminar/level1/tsconfig.json new file mode 100644 index 0000000..34bc28e --- /dev/null +++ b/4th-seminar/level1/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es6", // 어떤 버전으로 컴파일 + "allowSyntheticDefaultImports": true, // default export가 없는 모듈에서 default imports를 허용 + "experimentalDecorators": true, // decorator 실험적 허용 + "emitDecoratorMetadata": true, // 데코레이터가 있는 선언에 대해 특정 타입의 메타 데이터를 내보내는 실험적인 지원 + "skipLibCheck": true, // 정의 파일 타입 체크 여부 + "moduleResolution": "node", // commonJS -> node 에서 동작 + "module": "commonjs", // import 문법 + "strict": true, // 타입 검사 엄격하게 + "pretty": true, // error 메시지 예쁘게 + "sourceMap": true, // 소스맵 파일 생성 -> .ts가 .js 파일로 트랜스 시 .js.map 생성 + "outDir": "./dist", // 트랜스 파일 (.js) 저장 경로 + "allowJs": true, // js 파일 ts에서 import 허용 + "esModuleInterop": true, // ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져올 수 있게 허용 + "typeRoots": [ + "./src/types/express.d.ts", // 타입(*.d.ts)파일을 가져올 디렉토리 설정 + "./node_modules/@types" // 설정 안할시 기본적으로 ./node_modules/@types + ] + }, + "include": [ + "./src/**/*" // build 시 포함 + ], + "exclude": [ + "node_modules", // build 시 제외 + "tests" + ] +}