├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── nest-cli.json
├── package-lock.json
├── package.json
├── pnpm-lock.yaml
├── public
├── img
│ ├── logo
│ │ └── codefactory_logo.png
│ ├── ratings
│ │ ├── 21a821cb-2acf-422e-a847-e1e18763a062.jpg
│ │ ├── 2a473d36-e8ed-4aba-9364-33df3bce21f1.jpg
│ │ ├── 7b448e09-b9af-425f-bfa3-6c3a8aaaa64f.jpg
│ │ └── f207b172-f026-4f1e-bac2-35af821d1285.jpg
│ ├── 떡볶이
│ │ ├── 계란떡볶이.jpg
│ │ ├── 닭갈비떡볶이.jpg
│ │ ├── 떡볶이.jpg
│ │ ├── 라면떡볶이.jpg
│ │ ├── 야채떡볶이.jpg
│ │ ├── 즉석떡볶이.jpg
│ │ ├── 짜장떡볶이.jpg
│ │ ├── 치즈떡볶이.jpg
│ │ └── 피자떡볶이.jpg
│ ├── 멕시칸
│ │ ├── 나초.jpg
│ │ ├── 야채타코.jpg
│ │ ├── 지옥불고추.jpg
│ │ ├── 치킨피타.jpg
│ │ ├── 칠리스프.jpg
│ │ ├── 토마토타코.jpg
│ │ ├── 토마토필라프.jpg
│ │ └── 포솔레.jpg
│ ├── 볶음밥
│ │ ├── 간장볶음밥.jpg
│ │ ├── 계란볶음밥.jpg
│ │ ├── 김치볶음밥.jpg
│ │ ├── 새우볶음밥.jpg
│ │ ├── 새우카레볶음밥.jpg
│ │ ├── 야채볶음밥.jpg
│ │ └── 카레볶음밥.jpg
│ ├── 스시
│ │ ├── 셰프추천스시.jpg
│ │ ├── 스시도시락.jpg
│ │ ├── 스시롤.jpg
│ │ ├── 아기자기스시.jpg
│ │ ├── 작은모듬스시.jpg
│ │ ├── 종합스시.jpg
│ │ ├── 중간모듬스시.jpg
│ │ └── 큰모듬스시.jpg
│ ├── 스테이크
│ │ ├── 감자칩스테이크.jpg
│ │ ├── 고추스테이크.jpg
│ │ ├── 닭다리스테이크.jpg
│ │ ├── 등심스테이크.jpg
│ │ ├── 미디움레어스테이크.jpg
│ │ ├── 상남자레어스테이크.jpg
│ │ └── 안심스테이크.jpg
│ ├── 쌀국수
│ │ ├── 굴요리.jpg
│ │ ├── 모듬세트.jpg
│ │ ├── 보통쌀국수.jpg
│ │ ├── 스프링롤.jpg
│ │ ├── 치킨쌀국수.jpg
│ │ └── 해물쌀국수.jpg
│ ├── 치킨
│ │ ├── 매콤치킨.jpg
│ │ ├── 바삭치킨.jpg
│ │ ├── 순살치킨.jpg
│ │ ├── 오븐치킨.jpg
│ │ └── 후라이드치킨.jpg
│ └── 파스타
│ │ ├── 계란스파게티.jpg
│ │ ├── 까르보나라.jpg
│ │ ├── 다이어트파스타.jpg
│ │ ├── 덜익은파스타.jpg
│ │ ├── 미트볼스파게티.jpg
│ │ ├── 봉골레파스타.jpg
│ │ ├── 빵스파게티.jpg
│ │ ├── 소세지스파게티.jpg
│ │ └── 해물스파게티.jpg
└── uploads
│ ├── 046ad80a-fa20-44ca-972a-9831b3d4d3c2.octet-stream
│ ├── 12fa2356-2478-4b8a-99de-02bb6e35618d.octet-stream
│ ├── 1a7f74b5-f796-403a-a041-70897e839954.octet-stream
│ ├── 29446fd2-45d5-4644-812d-b2a68c345447.octet-stream
│ ├── 385c8d18-3595-4a17-b9b7-ce4a95dc58de.octet-stream
│ ├── 7165f780-9e41-4aff-b389-24f345fb0a13.octet-stream
│ ├── 805aff87-8f97-4a91-b99a-eb4cbd5015d7.octet-stream
│ ├── 858fcdda-45a4-44a5-ad23-5d5d7bf11599.octet-stream
│ ├── 91eac660-7e5f-452e-8b7c-9ad262e41fd1.octet-stream
│ ├── c0e1cfac-0d88-4867-b3c7-73e3b13ec6e4.octet-stream
│ ├── d8267665-2c27-4939-80b1-a75753fb523d.octet-stream
│ ├── eb415117-8501-4ca4-a8d6-006e49d067c9.octet-stream
│ ├── eff08904-963e-41eb-a808-be78464ae9a6.octet-stream
│ └── f2efe6bb-e5aa-49b4-914d-0fbd1a382f27.octet-stream
├── src
├── app.controller.ts
├── app.module.ts
├── app.service.ts
├── auth
│ ├── auth.controller.ts
│ ├── auth.module.ts
│ ├── auth.service.ts
│ ├── basic-token.guard.ts
│ ├── bearer-token.guard.ts
│ ├── dto
│ │ ├── create-auth.dto.ts
│ │ └── update-auth.dto.ts
│ ├── entities
│ │ └── auth.entity.ts
│ └── jwt.strategy.ts
├── cache
│ ├── cache.module.ts
│ ├── cache.service.ts
│ ├── product.data.interface.ts
│ ├── product.data.ts
│ ├── rating.data.interface.ts
│ ├── rating.data.ts
│ ├── restaurant.data.interface.ts
│ └── restaurant.data.ts
├── core
│ ├── core.controller.ts
│ ├── core.module.ts
│ ├── core.service.ts
│ ├── decorator
│ │ ├── api-bearer-token-header.ts
│ │ └── api-paginated-ok-response.decorator.ts
│ ├── dto
│ │ └── pagination.dto.ts
│ ├── entity
│ │ ├── base.entity.ts
│ │ └── pagination.entity.ts
│ └── interceptor
│ │ └── response-delay.interceptor.ts
├── main.ts
├── order
│ ├── dto
│ │ ├── create-order.dto.ts
│ │ └── update-order.dto.ts
│ ├── entities
│ │ ├── order-product-entity.ts
│ │ └── order.entity.ts
│ ├── order.controller.ts
│ ├── order.module.ts
│ └── order.service.ts
├── product
│ ├── dto
│ │ ├── create-product.dto.ts
│ │ └── update-product.dto.ts
│ ├── entities
│ │ └── product.entity.ts
│ ├── product.controller.ts
│ ├── product.module.ts
│ └── product.service.ts
├── rating
│ ├── dto
│ │ ├── create-rating.dto.ts
│ │ └── update-rating.dto.ts
│ ├── entities
│ │ └── rating.entity.ts
│ ├── rating.controller.ts
│ ├── rating.module.ts
│ └── rating.service.ts
├── restaurant
│ ├── dto
│ │ ├── create-restaurant-rating.dto.ts
│ │ ├── create-restaurant.dto.ts
│ │ └── update-restaurant.dto.ts
│ ├── entities
│ │ └── restaurant.entity.ts
│ ├── restaurant.controller.ts
│ ├── restaurant.module.ts
│ └── restaurant.service.ts
└── user
│ ├── dto
│ ├── basket-item.dto.ts
│ ├── create-user.dto.ts
│ ├── patch-me-basket.dto.ts
│ └── update-user.dto.ts
│ ├── entities
│ ├── user.entity.interface.ts
│ └── user.entity.ts
│ ├── user.controller.ts
│ ├── user.module.ts
│ └── user.service.ts
├── test
├── app.e2e-spec.ts
└── jest-e2e.json
├── tsconfig.build.json
├── tsconfig.json
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | tsconfigRootDir : __dirname,
6 | sourceType: 'module',
7 | },
8 | plugins: ['@typescript-eslint/eslint-plugin'],
9 | extends: [
10 | 'plugin:@typescript-eslint/recommended',
11 | 'plugin:prettier/recommended',
12 | ],
13 | root: true,
14 | env: {
15 | node: true,
16 | jest: true,
17 | },
18 | ignorePatterns: ['.eslintrc.js'],
19 | rules: {
20 | '@typescript-eslint/interface-name-prefix': 'off',
21 | '@typescript-eslint/explicit-function-return-type': 'off',
22 | '@typescript-eslint/explicit-module-boundary-types': 'off',
23 | '@typescript-eslint/no-explicit-any': 'off',
24 | },
25 | };
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # compiled output
2 | /dist
3 | /node_modules
4 |
5 | # Logs
6 | logs
7 | *.log
8 | npm-debug.log*
9 | pnpm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 |
14 | # OS
15 | .DS_Store
16 |
17 | # Tests
18 | /coverage
19 | /.nyc_output
20 |
21 | # IDEs and editors
22 | /.idea
23 | .project
24 | .classpath
25 | .c9/
26 | *.launch
27 | .settings/
28 | *.sublime-workspace
29 |
30 | # IDE - VSCode
31 | .vscode/*
32 | !.vscode/settings.json
33 | !.vscode/tasks.json
34 | !.vscode/launch.json
35 | !.vscode/extensions.json
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "all"
4 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
6 | [circleci-url]: https://circleci.com/gh/nestjs/nest
7 |
8 | A progressive Node.js framework for building efficient and scalable server-side applications.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 | ## Description
26 |
27 | [Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
28 |
29 | ## Installation
30 |
31 | ```bash
32 | $ npm install
33 | ```
34 |
35 | ## Running the app
36 |
37 | ```bash
38 | # development
39 | $ npm run start
40 |
41 | # watch mode
42 | $ npm run start:dev
43 |
44 | # production mode
45 | $ npm run start:prod
46 | ```
47 |
48 | ## Test
49 |
50 | ```bash
51 | # unit tests
52 | $ npm run test
53 |
54 | # e2e tests
55 | $ npm run test:e2e
56 |
57 | # test coverage
58 | $ npm run test:cov
59 | ```
60 |
61 | ## Support
62 |
63 | Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
64 |
65 | ## Stay in touch
66 |
67 | - Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
68 | - Website - [https://nestjs.com](https://nestjs.com/)
69 | - Twitter - [@nestframework](https://twitter.com/nestframework)
70 |
71 | ## License
72 |
73 | Nest is [MIT licensed](LICENSE).
74 |
--------------------------------------------------------------------------------
/nest-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/nest-cli",
3 | "collection": "@nestjs/schematics",
4 | "sourceRoot": "src"
5 | }
6 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "delivery-server",
3 | "version": "0.0.1",
4 | "description": "",
5 | "author": "",
6 | "private": true,
7 | "license": "UNLICENSED",
8 | "scripts": {
9 | "prebuild": "rimraf dist",
10 | "build": "nest build",
11 | "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
12 | "start": "nest start",
13 | "start:dev": "nest start --watch",
14 | "start:debug": "nest start --debug --watch",
15 | "start:prod": "node dist/main",
16 | "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
17 | "test": "jest",
18 | "test:watch": "jest --watch",
19 | "test:cov": "jest --coverage",
20 | "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
21 | "test:e2e": "jest --config ./test/jest-e2e.json"
22 | },
23 | "dependencies": {
24 | "@faker-js/faker": "^6.3.1",
25 | "@nestjs/common": "^10.0.5",
26 | "@nestjs/core": "^10.0.5",
27 | "@nestjs/jwt": "^10.1.0",
28 | "@nestjs/mapped-types": "^2.0.2",
29 | "@nestjs/passport": "^10.0.0",
30 | "@nestjs/platform-express": "^10.0.5",
31 | "@nestjs/serve-static": "^4.0.0",
32 | "@nestjs/swagger": "^7.1.1",
33 | "@types/uuid": "^8.3.4",
34 | "class-transformer": "^0.5.1",
35 | "class-validator": "^0.13.2",
36 | "passport": "^0.5.2",
37 | "passport-jwt": "^4.0.0",
38 | "passport-local": "^1.0.0",
39 | "reflect-metadata": "^0.1.13",
40 | "rimraf": "^3.0.2",
41 | "rxjs": "^7.2.0",
42 | "swagger-ui-express": "^4.4.0",
43 | "uuid": "^8.3.2"
44 | },
45 | "devDependencies": {
46 | "@nestjs/cli": "^10.1.8",
47 | "@nestjs/schematics": "^10.0.1",
48 | "@nestjs/testing": "^10.0.5",
49 | "@types/express": "^4.17.13",
50 | "@types/jest": "27.4.1",
51 | "@types/multer": "^1.4.7",
52 | "@types/node": "^16.0.0",
53 | "@types/passport-jwt": "^3.0.6",
54 | "@types/passport-local": "^1.0.34",
55 | "@types/supertest": "^2.0.11",
56 | "@typescript-eslint/eslint-plugin": "^5.0.0",
57 | "@typescript-eslint/parser": "^5.0.0",
58 | "eslint": "^8.0.1",
59 | "eslint-config-prettier": "^8.3.0",
60 | "eslint-plugin-prettier": "^4.0.0",
61 | "jest": "^27.2.5",
62 | "prettier": "2.6.2",
63 | "source-map-support": "^0.5.20",
64 | "supertest": "^6.1.3",
65 | "ts-jest": "^27.0.3",
66 | "ts-loader": "^9.2.3",
67 | "ts-node": "^10.0.0",
68 | "tsconfig-paths": "^3.10.1",
69 | "typescript": "^4.3.5"
70 | },
71 | "jest": {
72 | "moduleFileExtensions": [
73 | "js",
74 | "json",
75 | "ts"
76 | ],
77 | "rootDir": "src",
78 | "testRegex": ".*\\.spec\\.ts$",
79 | "transform": {
80 | "^.+\\.(t|j)s$": "ts-jest"
81 | },
82 | "collectCoverageFrom": [
83 | "**/*.(t|j)s"
84 | ],
85 | "coverageDirectory": "../coverage",
86 | "testEnvironment": "node"
87 | },
88 | "packageManager": "pnpm@9.1.1+sha1.09ada6cd05003e0ced25fb716f9fda4063ec2e3b"
89 | }
90 |
--------------------------------------------------------------------------------
/public/img/logo/codefactory_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/logo/codefactory_logo.png
--------------------------------------------------------------------------------
/public/img/ratings/21a821cb-2acf-422e-a847-e1e18763a062.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/ratings/21a821cb-2acf-422e-a847-e1e18763a062.jpg
--------------------------------------------------------------------------------
/public/img/ratings/2a473d36-e8ed-4aba-9364-33df3bce21f1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/ratings/2a473d36-e8ed-4aba-9364-33df3bce21f1.jpg
--------------------------------------------------------------------------------
/public/img/ratings/7b448e09-b9af-425f-bfa3-6c3a8aaaa64f.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/ratings/7b448e09-b9af-425f-bfa3-6c3a8aaaa64f.jpg
--------------------------------------------------------------------------------
/public/img/ratings/f207b172-f026-4f1e-bac2-35af821d1285.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/ratings/f207b172-f026-4f1e-bac2-35af821d1285.jpg
--------------------------------------------------------------------------------
/public/img/떡볶이/계란떡볶이.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/떡볶이/계란떡볶이.jpg
--------------------------------------------------------------------------------
/public/img/떡볶이/닭갈비떡볶이.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/떡볶이/닭갈비떡볶이.jpg
--------------------------------------------------------------------------------
/public/img/떡볶이/떡볶이.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/떡볶이/떡볶이.jpg
--------------------------------------------------------------------------------
/public/img/떡볶이/라면떡볶이.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/떡볶이/라면떡볶이.jpg
--------------------------------------------------------------------------------
/public/img/떡볶이/야채떡볶이.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/떡볶이/야채떡볶이.jpg
--------------------------------------------------------------------------------
/public/img/떡볶이/즉석떡볶이.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/떡볶이/즉석떡볶이.jpg
--------------------------------------------------------------------------------
/public/img/떡볶이/짜장떡볶이.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/떡볶이/짜장떡볶이.jpg
--------------------------------------------------------------------------------
/public/img/떡볶이/치즈떡볶이.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/떡볶이/치즈떡볶이.jpg
--------------------------------------------------------------------------------
/public/img/떡볶이/피자떡볶이.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/떡볶이/피자떡볶이.jpg
--------------------------------------------------------------------------------
/public/img/멕시칸/나초.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/멕시칸/나초.jpg
--------------------------------------------------------------------------------
/public/img/멕시칸/야채타코.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/멕시칸/야채타코.jpg
--------------------------------------------------------------------------------
/public/img/멕시칸/지옥불고추.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/멕시칸/지옥불고추.jpg
--------------------------------------------------------------------------------
/public/img/멕시칸/치킨피타.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/멕시칸/치킨피타.jpg
--------------------------------------------------------------------------------
/public/img/멕시칸/칠리스프.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/멕시칸/칠리스프.jpg
--------------------------------------------------------------------------------
/public/img/멕시칸/토마토타코.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/멕시칸/토마토타코.jpg
--------------------------------------------------------------------------------
/public/img/멕시칸/토마토필라프.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/멕시칸/토마토필라프.jpg
--------------------------------------------------------------------------------
/public/img/멕시칸/포솔레.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/멕시칸/포솔레.jpg
--------------------------------------------------------------------------------
/public/img/볶음밥/간장볶음밥.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/볶음밥/간장볶음밥.jpg
--------------------------------------------------------------------------------
/public/img/볶음밥/계란볶음밥.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/볶음밥/계란볶음밥.jpg
--------------------------------------------------------------------------------
/public/img/볶음밥/김치볶음밥.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/볶음밥/김치볶음밥.jpg
--------------------------------------------------------------------------------
/public/img/볶음밥/새우볶음밥.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/볶음밥/새우볶음밥.jpg
--------------------------------------------------------------------------------
/public/img/볶음밥/새우카레볶음밥.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/볶음밥/새우카레볶음밥.jpg
--------------------------------------------------------------------------------
/public/img/볶음밥/야채볶음밥.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/볶음밥/야채볶음밥.jpg
--------------------------------------------------------------------------------
/public/img/볶음밥/카레볶음밥.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/볶음밥/카레볶음밥.jpg
--------------------------------------------------------------------------------
/public/img/스시/셰프추천스시.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스시/셰프추천스시.jpg
--------------------------------------------------------------------------------
/public/img/스시/스시도시락.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스시/스시도시락.jpg
--------------------------------------------------------------------------------
/public/img/스시/스시롤.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스시/스시롤.jpg
--------------------------------------------------------------------------------
/public/img/스시/아기자기스시.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스시/아기자기스시.jpg
--------------------------------------------------------------------------------
/public/img/스시/작은모듬스시.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스시/작은모듬스시.jpg
--------------------------------------------------------------------------------
/public/img/스시/종합스시.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스시/종합스시.jpg
--------------------------------------------------------------------------------
/public/img/스시/중간모듬스시.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스시/중간모듬스시.jpg
--------------------------------------------------------------------------------
/public/img/스시/큰모듬스시.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스시/큰모듬스시.jpg
--------------------------------------------------------------------------------
/public/img/스테이크/감자칩스테이크.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스테이크/감자칩스테이크.jpg
--------------------------------------------------------------------------------
/public/img/스테이크/고추스테이크.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스테이크/고추스테이크.jpg
--------------------------------------------------------------------------------
/public/img/스테이크/닭다리스테이크.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스테이크/닭다리스테이크.jpg
--------------------------------------------------------------------------------
/public/img/스테이크/등심스테이크.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스테이크/등심스테이크.jpg
--------------------------------------------------------------------------------
/public/img/스테이크/미디움레어스테이크.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스테이크/미디움레어스테이크.jpg
--------------------------------------------------------------------------------
/public/img/스테이크/상남자레어스테이크.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스테이크/상남자레어스테이크.jpg
--------------------------------------------------------------------------------
/public/img/스테이크/안심스테이크.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/스테이크/안심스테이크.jpg
--------------------------------------------------------------------------------
/public/img/쌀국수/굴요리.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/쌀국수/굴요리.jpg
--------------------------------------------------------------------------------
/public/img/쌀국수/모듬세트.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/쌀국수/모듬세트.jpg
--------------------------------------------------------------------------------
/public/img/쌀국수/보통쌀국수.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/쌀국수/보통쌀국수.jpg
--------------------------------------------------------------------------------
/public/img/쌀국수/스프링롤.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/쌀국수/스프링롤.jpg
--------------------------------------------------------------------------------
/public/img/쌀국수/치킨쌀국수.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/쌀국수/치킨쌀국수.jpg
--------------------------------------------------------------------------------
/public/img/쌀국수/해물쌀국수.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/쌀국수/해물쌀국수.jpg
--------------------------------------------------------------------------------
/public/img/치킨/매콤치킨.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/치킨/매콤치킨.jpg
--------------------------------------------------------------------------------
/public/img/치킨/바삭치킨.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/치킨/바삭치킨.jpg
--------------------------------------------------------------------------------
/public/img/치킨/순살치킨.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/치킨/순살치킨.jpg
--------------------------------------------------------------------------------
/public/img/치킨/오븐치킨.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/치킨/오븐치킨.jpg
--------------------------------------------------------------------------------
/public/img/치킨/후라이드치킨.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/치킨/후라이드치킨.jpg
--------------------------------------------------------------------------------
/public/img/파스타/계란스파게티.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/파스타/계란스파게티.jpg
--------------------------------------------------------------------------------
/public/img/파스타/까르보나라.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/파스타/까르보나라.jpg
--------------------------------------------------------------------------------
/public/img/파스타/다이어트파스타.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/파스타/다이어트파스타.jpg
--------------------------------------------------------------------------------
/public/img/파스타/덜익은파스타.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/파스타/덜익은파스타.jpg
--------------------------------------------------------------------------------
/public/img/파스타/미트볼스파게티.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/파스타/미트볼스파게티.jpg
--------------------------------------------------------------------------------
/public/img/파스타/봉골레파스타.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/파스타/봉골레파스타.jpg
--------------------------------------------------------------------------------
/public/img/파스타/빵스파게티.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/파스타/빵스파게티.jpg
--------------------------------------------------------------------------------
/public/img/파스타/소세지스파게티.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/파스타/소세지스파게티.jpg
--------------------------------------------------------------------------------
/public/img/파스타/해물스파게티.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/img/파스타/해물스파게티.jpg
--------------------------------------------------------------------------------
/public/uploads/046ad80a-fa20-44ca-972a-9831b3d4d3c2.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/046ad80a-fa20-44ca-972a-9831b3d4d3c2.octet-stream
--------------------------------------------------------------------------------
/public/uploads/12fa2356-2478-4b8a-99de-02bb6e35618d.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/12fa2356-2478-4b8a-99de-02bb6e35618d.octet-stream
--------------------------------------------------------------------------------
/public/uploads/1a7f74b5-f796-403a-a041-70897e839954.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/1a7f74b5-f796-403a-a041-70897e839954.octet-stream
--------------------------------------------------------------------------------
/public/uploads/29446fd2-45d5-4644-812d-b2a68c345447.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/29446fd2-45d5-4644-812d-b2a68c345447.octet-stream
--------------------------------------------------------------------------------
/public/uploads/385c8d18-3595-4a17-b9b7-ce4a95dc58de.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/385c8d18-3595-4a17-b9b7-ce4a95dc58de.octet-stream
--------------------------------------------------------------------------------
/public/uploads/7165f780-9e41-4aff-b389-24f345fb0a13.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/7165f780-9e41-4aff-b389-24f345fb0a13.octet-stream
--------------------------------------------------------------------------------
/public/uploads/805aff87-8f97-4a91-b99a-eb4cbd5015d7.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/805aff87-8f97-4a91-b99a-eb4cbd5015d7.octet-stream
--------------------------------------------------------------------------------
/public/uploads/858fcdda-45a4-44a5-ad23-5d5d7bf11599.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/858fcdda-45a4-44a5-ad23-5d5d7bf11599.octet-stream
--------------------------------------------------------------------------------
/public/uploads/91eac660-7e5f-452e-8b7c-9ad262e41fd1.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/91eac660-7e5f-452e-8b7c-9ad262e41fd1.octet-stream
--------------------------------------------------------------------------------
/public/uploads/c0e1cfac-0d88-4867-b3c7-73e3b13ec6e4.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/c0e1cfac-0d88-4867-b3c7-73e3b13ec6e4.octet-stream
--------------------------------------------------------------------------------
/public/uploads/d8267665-2c27-4939-80b1-a75753fb523d.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/d8267665-2c27-4939-80b1-a75753fb523d.octet-stream
--------------------------------------------------------------------------------
/public/uploads/eb415117-8501-4ca4-a8d6-006e49d067c9.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/eb415117-8501-4ca4-a8d6-006e49d067c9.octet-stream
--------------------------------------------------------------------------------
/public/uploads/eff08904-963e-41eb-a808-be78464ae9a6.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/eff08904-963e-41eb-a808-be78464ae9a6.octet-stream
--------------------------------------------------------------------------------
/public/uploads/f2efe6bb-e5aa-49b4-914d-0fbd1a382f27.octet-stream:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/codefactory-co/flutter-lv2-server/7351665d4e9e642f7ff3a364483afcb0602f590b/public/uploads/f2efe6bb-e5aa-49b4-914d-0fbd1a382f27.octet-stream
--------------------------------------------------------------------------------
/src/app.controller.ts:
--------------------------------------------------------------------------------
1 | import { Controller, Get } from '@nestjs/common';
2 | import { AppService } from './app.service';
3 |
4 | @Controller()
5 | export class AppController {
6 | constructor(private readonly appService: AppService) {}
7 |
8 | @Get()
9 | getHello(): string {
10 | return this.appService.getHello();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/app.module.ts:
--------------------------------------------------------------------------------
1 | import { ClassSerializerInterceptor, Module } from '@nestjs/common';
2 | import { AppController } from './app.controller';
3 | import { AppService } from './app.service';
4 | import { AuthModule } from './auth/auth.module';
5 | import { UserModule } from './user/user.module';
6 | import { ProductModule } from './product/product.module';
7 | import { APP_INTERCEPTOR } from '@nestjs/core';
8 | import { CacheModule } from './cache/cache.module';
9 | import { RestaurantModule } from './restaurant/restaurant.module';
10 | import { CoreModule } from './core/core.module';
11 | import { RatingModule } from './rating/rating.module';
12 | import { ResponseDelayInterceptor } from './core/interceptor/response-delay.interceptor';
13 | import { OrderModule } from './order/order.module';
14 |
15 | @Module({
16 | imports: [
17 | AuthModule,
18 | UserModule,
19 | ProductModule,
20 | CacheModule,
21 | RestaurantModule,
22 | CoreModule,
23 | RatingModule,
24 | OrderModule,
25 | ],
26 | controllers: [AppController],
27 | providers: [
28 | AppService,
29 | {
30 | provide: APP_INTERCEPTOR,
31 | useClass: ClassSerializerInterceptor,
32 | },
33 | {
34 | provide: APP_INTERCEPTOR,
35 | useClass: ResponseDelayInterceptor,
36 | },
37 | ],
38 | })
39 | export class AppModule {}
40 |
--------------------------------------------------------------------------------
/src/app.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 |
3 | @Injectable()
4 | export class AppService {
5 | getHello(): string {
6 | return 'Hello World!';
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/auth/auth.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Get,
4 | Post,
5 | Body,
6 | Patch,
7 | Param,
8 | Delete,
9 | UseGuards,
10 | Request,
11 | } from '@nestjs/common';
12 | import { AuthService } from './auth.service';
13 | import { CreateAuthDto } from './dto/create-auth.dto';
14 | import { UpdateAuthDto } from './dto/update-auth.dto';
15 | import { AuthGuard } from '@nestjs/passport';
16 | import { BasicTokenGuard } from './basic-token.guard';
17 | import { BearerTokenGuard, RefreshTokenGuard } from './bearer-token.guard';
18 | import { CreateUserDto } from '../user/dto/create-user.dto';
19 | import { UserService } from '../user/user.service';
20 | import {
21 | ApiHeader,
22 | ApiOkResponse,
23 | ApiOperation,
24 | ApiTags,
25 | } from '@nestjs/swagger';
26 | import {
27 | ApiBasicTokenHeader,
28 | ApiBearerTokenHeader,
29 | } from '../core/decorator/api-bearer-token-header';
30 |
31 | @ApiTags('auth')
32 | @Controller('auth')
33 | export class AuthController {
34 | constructor(
35 | private readonly authService: AuthService,
36 | private readonly userService: UserService,
37 | ) {}
38 |
39 | @ApiOperation({
40 | summary: 'Login하기',
41 | })
42 | @ApiBasicTokenHeader()
43 | @ApiOkResponse({
44 | description: 'Access Token과 Refresh Token',
45 | schema: {
46 | properties: {
47 | accessToken: {
48 | type: 'string',
49 | example: 'asdiofjzxl;ckvjoiasjewr.asdfoiasjdflkajsdf.asdfivjiaosdjf',
50 | description: 'Access Token',
51 | },
52 | refreshToken: {
53 | type: 'string',
54 | example: 'asdiofjzxl;ckvjoiasjewr.asdfoiasjdflkajsdf.asdfivjiaosdjf',
55 | description: 'Refresh Token',
56 | },
57 | },
58 | },
59 | })
60 | @UseGuards(BasicTokenGuard)
61 | @Post('login')
62 | async login(@Request() req) {
63 | return this.authService.login(req.user);
64 | }
65 |
66 | // @Post('register')
67 | // register(@Body() createUserDto: CreateUserDto) {
68 | // return this.userService.createUser(createUserDto);
69 | // }
70 |
71 | @ApiOperation({
72 | summary: 'Token Refresh하기',
73 | })
74 | @ApiBearerTokenHeader()
75 | @ApiOkResponse({
76 | description: 'Access Token',
77 | schema: {
78 | properties: {
79 | accessToken: {
80 | type: 'string',
81 | example: 'asdiofjzxl;ckvjoiasjewr.asdfoiasjdflkajsdf.asdfivjiaosdjf',
82 | description: 'Access Token',
83 | },
84 | },
85 | },
86 | })
87 | @UseGuards(RefreshTokenGuard)
88 | @Post('token')
89 | async token(@Request() req) {
90 | return {
91 | accessToken: await this.authService.rotateAccessToken(req.token),
92 | };
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/auth/auth.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { AuthService } from './auth.service';
3 | import { AuthController } from './auth.controller';
4 | import { UserModule } from '../user/user.module';
5 | import { PassportModule } from '@nestjs/passport';
6 | import { JwtModule } from '@nestjs/jwt';
7 | import { JwtStrategy } from './jwt.strategy';
8 | import { CacheModule } from '../cache/cache.module';
9 |
10 | @Module({
11 | imports: [
12 | UserModule,
13 | PassportModule,
14 | CacheModule,
15 | JwtModule.register({
16 | secret: 'codefactory',
17 | }),
18 | ],
19 | controllers: [AuthController],
20 | providers: [AuthService, JwtStrategy],
21 | exports: [AuthService, UserModule],
22 | })
23 | export class AuthModule {}
24 |
--------------------------------------------------------------------------------
/src/auth/auth.service.ts:
--------------------------------------------------------------------------------
1 | import { BadRequestException, Injectable } from '@nestjs/common';
2 | import { User } from '../user/entities/user.entity';
3 | import { UserService } from '../user/user.service';
4 | import { JwtService } from '@nestjs/jwt';
5 | import { CacheService } from '../cache/cache.service';
6 |
7 | @Injectable()
8 | export class AuthService {
9 | constructor(
10 | private cacheService: CacheService,
11 | private userService: UserService,
12 | private jwtService: JwtService,
13 | ) {}
14 |
15 | verifyToken(token: string) {
16 | return this.jwtService.verify(token);
17 | }
18 |
19 | async rotateAccessToken(refreshToken: string): Promise {
20 | const decoded = this.jwtService.verify(refreshToken);
21 |
22 | return this.signToken(
23 | {
24 | username: decoded.username,
25 | id: decoded.sub,
26 | },
27 | false,
28 | );
29 | }
30 |
31 | signToken(user: any, isRefreshToken: boolean): string {
32 | const payload = {
33 | username: user.username,
34 | sub: user.id,
35 | type: isRefreshToken ? 'refresh' : 'access',
36 | };
37 |
38 | return this.jwtService.sign(payload, {
39 | expiresIn: isRefreshToken ? '1d' : '300s',
40 | });
41 | }
42 |
43 | async authenticate(username: string, password: string): Promise {
44 | const user = await this.userService.findByUsername(username);
45 |
46 | if (!user) {
47 | return null;
48 | }
49 |
50 | if (user.password !== password) {
51 | return null;
52 | }
53 |
54 | return user;
55 | }
56 |
57 | async login(user: User) {
58 | return {
59 | refreshToken: this.signToken(user, true),
60 | accessToken: this.signToken(user, false),
61 | };
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/auth/basic-token.guard.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BadRequestException,
3 | CanActivate,
4 | ExecutionContext,
5 | ForbiddenException,
6 | Injectable,
7 | } from '@nestjs/common';
8 | import { Observable } from 'rxjs';
9 | import { AuthService } from './auth.service';
10 |
11 | @Injectable()
12 | export class BasicTokenGuard implements CanActivate {
13 | constructor(private readonly authService: AuthService) {}
14 |
15 | async canActivate(context: ExecutionContext): Promise {
16 | const req = context.switchToHttp().getRequest();
17 |
18 | const rawToken = req.headers['authorization'];
19 |
20 | if (!rawToken) {
21 | throw new BadRequestException('토큰이 없습니다.');
22 | }
23 |
24 | const splitToken = rawToken.split(' ');
25 |
26 | if (splitToken.length !== 2 || splitToken[0] !== 'Basic') {
27 | throw new ForbiddenException('잘못된 토큰입니다.');
28 | }
29 |
30 | const token = splitToken[1];
31 |
32 | const decoded = Buffer.from(token, 'base64').toString('utf8');
33 |
34 | const split = decoded.split(':');
35 |
36 | if (split.length !== 2) {
37 | throw new BadRequestException('잘못된 토큰입니다.');
38 | }
39 |
40 | const username = split[0];
41 | const password = split[1];
42 |
43 | const user = await this.authService.authenticate(username, password);
44 |
45 | if (!user) {
46 | throw new ForbiddenException('비밀번호가 틀렸습니다.');
47 | }
48 |
49 | req.user = user;
50 |
51 | return true;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/auth/bearer-token.guard.ts:
--------------------------------------------------------------------------------
1 | import {
2 | BadRequestException,
3 | CanActivate,
4 | ExecutionContext,
5 | Injectable,
6 | UnauthorizedException,
7 | } from '@nestjs/common';
8 | import { Observable } from 'rxjs';
9 | import { AuthService } from './auth.service';
10 | import { UserService } from '../user/user.service';
11 |
12 | @Injectable()
13 | export class BearerTokenGuard implements CanActivate {
14 | constructor(
15 | private readonly authService: AuthService,
16 | private readonly userService: UserService,
17 | ) {}
18 |
19 | async canActivate(context: ExecutionContext): Promise {
20 | const request = context.switchToHttp().getRequest();
21 |
22 | const rawToken = request.headers['authorization'];
23 |
24 | if (!rawToken) {
25 | throw new UnauthorizedException('토큰이 없습니다.');
26 | }
27 |
28 | const splitToken = rawToken.split(' ');
29 |
30 | if (splitToken.length !== 2 || splitToken[0] !== 'Bearer') {
31 | throw new UnauthorizedException('잘못된 토큰입니다.');
32 | }
33 |
34 | const token = splitToken[1];
35 |
36 | let payload;
37 |
38 | try {
39 | payload = this.authService.verifyToken(token);
40 | } catch (e) {
41 | throw new UnauthorizedException('잘못된 토큰입니다.');
42 | }
43 |
44 | if (!payload.sub) {
45 | throw new UnauthorizedException('잘못된 토큰입니다.');
46 | }
47 |
48 | request.user = await this.userService.findByUsername(payload.username);
49 | request.token = token;
50 | request.tokenType = payload.type;
51 |
52 | return true;
53 | }
54 | }
55 |
56 | @Injectable()
57 | export class AccessTokenGuard extends BearerTokenGuard {
58 | async canActivate(context: ExecutionContext): Promise {
59 | await super.canActivate(context);
60 |
61 | const req = context.switchToHttp().getRequest();
62 |
63 | if (req.tokenType !== 'access') {
64 | throw new UnauthorizedException('Access Token이 아닙니다.');
65 | }
66 |
67 | return true;
68 | }
69 | }
70 |
71 | @Injectable()
72 | export class RefreshTokenGuard extends BearerTokenGuard {
73 | async canActivate(context: ExecutionContext): Promise {
74 | await super.canActivate(context);
75 |
76 | const req = context.switchToHttp().getRequest();
77 |
78 | if (req.tokenType !== 'refresh') {
79 | throw new UnauthorizedException('Refresh Token이 아닙니다.');
80 | }
81 |
82 | return true;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/auth/dto/create-auth.dto.ts:
--------------------------------------------------------------------------------
1 | export class CreateAuthDto {}
2 |
--------------------------------------------------------------------------------
/src/auth/dto/update-auth.dto.ts:
--------------------------------------------------------------------------------
1 | import { PartialType } from '@nestjs/mapped-types';
2 | import { CreateAuthDto } from './create-auth.dto';
3 |
4 | export class UpdateAuthDto extends PartialType(CreateAuthDto) {}
5 |
--------------------------------------------------------------------------------
/src/auth/entities/auth.entity.ts:
--------------------------------------------------------------------------------
1 | export class Auth {}
2 |
--------------------------------------------------------------------------------
/src/auth/jwt.strategy.ts:
--------------------------------------------------------------------------------
1 | import { ExtractJwt, Strategy } from 'passport-jwt';
2 | import { PassportStrategy } from '@nestjs/passport';
3 | import { Injectable } from '@nestjs/common';
4 |
5 | @Injectable()
6 | export class JwtStrategy extends PassportStrategy(Strategy) {
7 | constructor() {
8 | super({
9 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
10 | ignoreExpiration: false,
11 | secretOrKey: 'codefactory',
12 | });
13 | }
14 |
15 | async validate(payload: any) {
16 | return { userId: payload.sub, username: payload.username };
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/cache/cache.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { CacheService } from './cache.service';
3 |
4 | @Module({
5 | providers: [CacheService],
6 | exports: [CacheService],
7 | })
8 | export class CacheModule {}
9 |
--------------------------------------------------------------------------------
/src/cache/cache.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import {
3 | chicken,
4 | ddeokBokGi,
5 | friedRice,
6 | italian,
7 | mexican,
8 | noodles,
9 | steak,
10 | sushi,
11 | } from './product.data';
12 | import { Product } from '../product/entities/product.entity';
13 | import { v5 as uuidv5, v4 as uuidv4 } from 'uuid';
14 | import {
15 | Restaurant,
16 | RestaurantDetail,
17 | } from '../restaurant/entities/restaurant.entity';
18 | import { restaurants } from './restaurant.data';
19 | import { Rating } from '../rating/entities/rating.entity';
20 | import { User } from '../user/entities/user.entity';
21 | import { faker } from '@faker-js/faker';
22 | import { fiveStarRatings, fourStarRatings } from './rating.data';
23 | import { Order } from '../order/entities/order.entity';
24 |
25 | const uuidNamespace = '6dbe4c21-009b-4b22-a9be-1c3eca2bc9ea';
26 |
27 | @Injectable()
28 | export class CacheService {
29 | constructor() {
30 | this.createUsers();
31 | this.createRestaurants();
32 | this.createProducts();
33 | this.createRatings();
34 | }
35 |
36 | users: User[] = [
37 | new User({
38 | id: 'f55b32d2-4d68-4c1e-a3ca-da9d7d0d92e5',
39 | username: 'test@codefactory.ai',
40 | password: 'testtest',
41 | imageUrl: '/logo/codefactory_logo.png',
42 | basket: [],
43 | }),
44 | ];
45 |
46 | orders: Order[] = [];
47 |
48 | ratings: Rating[] = [];
49 |
50 | restaurants: RestaurantDetail[] = [];
51 |
52 | products: Product[] = [];
53 |
54 | createProducts() {
55 | for (const restaurant of this.restaurants) {
56 | let products = [];
57 |
58 | switch (restaurant.name) {
59 | case '불타는 떡볶이':
60 | products = ddeokBokGi;
61 | break;
62 |
63 | case '매콤 멕시칸':
64 | products = mexican;
65 | break;
66 |
67 | case '엄마손 볶음밥':
68 | products = friedRice;
69 | break;
70 |
71 | case '신선 코팩 스시':
72 | products = sushi;
73 | break;
74 |
75 | case '사나이 대왕 스테이크':
76 | products = steak;
77 | break;
78 |
79 | case '현지맛 미미 쌀국수':
80 | products = noodles;
81 | break;
82 |
83 | case '불타는 냠냠 치킨':
84 | products = chicken;
85 | break;
86 |
87 | case '크리미 꾸덕 파스타':
88 | products = italian;
89 | break;
90 |
91 | default:
92 | break;
93 | }
94 |
95 | products = products.map(
96 | (item) =>
97 | new Product({
98 | id: uuidv4(),
99 | restaurant: new Restaurant(restaurant),
100 | ...item,
101 | }),
102 | );
103 |
104 | restaurant.products = products;
105 |
106 | this.products = [...this.products, ...products];
107 | }
108 | }
109 |
110 | getImagesByRestaurantName(name: string) {
111 | let images = [];
112 |
113 | switch (name) {
114 | case '불타는 떡볶이':
115 | images = ddeokBokGi.map((item) => item.imgUrl);
116 | break;
117 |
118 | case '매콤 멕시칸':
119 | images = mexican.map((item) => item.imgUrl);
120 | break;
121 |
122 | case '엄마손 볶음밥':
123 | images = friedRice.map((item) => item.imgUrl);
124 | break;
125 |
126 | case '신선 코팩 스시':
127 | images = sushi.map((item) => item.imgUrl);
128 | break;
129 |
130 | case '사나이 대왕 스테이크':
131 | images = steak.map((item) => item.imgUrl);
132 | break;
133 |
134 | case '현지맛 미미 쌀국수':
135 | images = noodles.map((item) => item.imgUrl);
136 | break;
137 |
138 | case '불타는 냠냠 치킨':
139 | images = chicken.map((item) => item.imgUrl);
140 | break;
141 |
142 | case '크리미 꾸덕 파스타':
143 | images = italian.map((item) => item.imgUrl);
144 | break;
145 |
146 | default:
147 | break;
148 | }
149 |
150 | return images;
151 | }
152 |
153 | createRestaurants() {
154 | const raws = new Array(100).fill(0).map(
155 | (_, index) =>
156 | new RestaurantDetail({
157 | id: uuidv5(`restaurant_${index}`, uuidNamespace),
158 | ratings: 5,
159 | ratingsCount: 100,
160 | products: [],
161 | ...restaurants[index % restaurants.length],
162 | }),
163 | );
164 |
165 | this.restaurants = raws;
166 | }
167 |
168 | createRatings() {
169 | const allRatings = [...fiveStarRatings, ...fourStarRatings];
170 |
171 | for (const restaurant of this.restaurants) {
172 | const randomInt = this.getRandomInt(100, 200);
173 |
174 | const indexArr = new Array(randomInt).fill(0).map((_, index) => {
175 | const randomImages = this.getImagesByRestaurantName(restaurant.name);
176 | randomImages.sort(() => Math.random() - 0.5);
177 |
178 | return new Rating({
179 | id: uuidv4(),
180 | restaurant: restaurant,
181 | imgUrls: index % 2 === 1 ? randomImages.slice(0, 5) : [],
182 | user: this.users[index % this.users.length],
183 | ...allRatings[index % allRatings.length],
184 | });
185 | });
186 |
187 | this.ratings = [...this.ratings, ...indexArr];
188 | }
189 |
190 | this.restaurants = this.restaurants.map(
191 | (restaurant) =>
192 | new RestaurantDetail({
193 | ...restaurant,
194 | ratings: +this.ratings
195 | .filter((rating) => rating.restaurant.id === restaurant.id)
196 | .reduce(
197 | (average, value, index, { length }) =>
198 | index === length - 1
199 | ? (average + value.rating) / length
200 | : average + value.rating,
201 | 0,
202 | )
203 | .toFixed(2),
204 | }),
205 | );
206 | }
207 |
208 | createUsers() {
209 | for (let i = 0; i < 10000; i++) {
210 | this.users = [
211 | ...this.users,
212 | new User({
213 | id: uuidv4(),
214 | username: faker.internet.email(),
215 | password: faker.datatype.string(16),
216 | imageUrl: '/logo/codefactory_logo.png',
217 | basket: [],
218 | }),
219 | ];
220 | }
221 | }
222 |
223 | getRandomInt(min, max): number {
224 | min = Math.ceil(min);
225 | max = Math.floor(max);
226 | return Math.floor(Math.random() * (max - min) + min);
227 | }
228 | }
229 |
--------------------------------------------------------------------------------
/src/cache/product.data.interface.ts:
--------------------------------------------------------------------------------
1 | import { Product } from '../product/entities/product.entity';
2 |
3 | export type IProductRaw = Omit;
4 |
--------------------------------------------------------------------------------
/src/cache/product.data.ts:
--------------------------------------------------------------------------------
1 | import { IProductRaw } from './product.data.interface';
2 |
3 | export const ddeokBokGi: IProductRaw[] = [
4 | {
5 | name: '떡볶이',
6 | imgUrl: '떡볶이/떡볶이.jpg',
7 | detail:
8 | '전통 떡볶이의 정석! 원하는대로 맵기를 선택하고 추억의 떡볶이맛에 빠져보세요! 쫀득한 쌀떡과 말랑한 오뎅의 완벽한 조화! 잘익은 반숙 계란은 덤!',
9 | price: 10000,
10 | },
11 | {
12 | name: '피자 떡볶이',
13 | imgUrl: '떡볶이/피자떡볶이.jpg',
14 | detail:
15 | '떡볶이와 피자의 완벽한 조화! 이게 과연 피자인가 떡볶이인가! 말랑하고 부드러운 빵 위에 매콤하고 쫀득한 떡이 함께하는 아름다운 혼종! 칼로리 폭탄 치즈까지 올라갔지만 맛있으면 0칼로리~',
16 | price: 12000,
17 | },
18 | {
19 | name: '계란 떡볶이',
20 | imgUrl: '떡볶이/계란떡볶이.jpg',
21 | detail:
22 | '매콤한 떡볶이에 아름다운 계란이 별미인 떡볶이! 완벽히 조리된 상태로 배달되니 잠깐만 대피고 먹으면 너무 맛있어요!',
23 | price: 12000,
24 | },
25 | {
26 | name: '닭갈비 떡볶이',
27 | imgUrl: '떡볶이/닭갈비떡볶이.jpg',
28 | detail:
29 | '누가 근손실을 말하였는가? 더이상 탄수화물때문에 여자친구와 싸울필요가 없다! 탄단지 밸런스를 완벽하게 잡아주는 닭가슴살 떡볶이! 헬창을 위한 닭가슴살도 듬뿍 들어있으니 이제 여친과 싸우지망~',
30 | price: 12000,
31 | },
32 | {
33 | name: '라면 떡볶이',
34 | imgUrl: '떡볶이/라면떡볶이.jpg',
35 | detail:
36 | '역시 떡볶이에는 라면이 정석! 더이상 마지막 한숟갈을 아쉬워할 필요가 없다! 떡볶이에 기본으로 추가되는 탄탄한 라면! 매콤한 소스와 더해져서 너무너무 맛있어요~',
37 | price: 12000,
38 | },
39 | {
40 | name: '야채 떡볶이',
41 | imgUrl: '떡볶이/야채떡볶이.jpg',
42 | detail:
43 | '내가 사장이지만 솔찍히 추천하기 힘들다... 하지만 굳이 야채를 이렇게까지 먹어야겠다면 만들어 드릴게. 야채먹어도 떡은 칼로리 높아서 어차피 할찌는건 알지?',
44 | price: 12000,
45 | },
46 | {
47 | name: '즉석 떡볶이',
48 | imgUrl: '떡볶이/즉석떡볶이.jpg',
49 | detail:
50 | '떡볶이는 그래도 즉석떡볶이지~ 불판 켜고 살짝만 대펴주면 쫀득쫀득한 떡의 촉감을 느낄 수 있다! 소스부터 모든 재료는 우리가 준비해드리니 후라이팬만 준비하세요!',
51 | price: 12000,
52 | },
53 | {
54 | name: '짜장 떡볶이',
55 | imgUrl: '떡볶이/짜장떡볶이.jpg',
56 | detail:
57 | '옛날에 왕족께서 드셨다는 바로 그 짜장떡볶이! 매운맛이 없어서 내취향은 아니지만 그래도 매운걸 안좋아하거나 짜장을 많이 좋아하면 먹을만해요~ 매콤한게 좋다면 고추가로 좀만 뿌려주면 싹~',
58 | price: 12000,
59 | },
60 | {
61 | name: '치즈 떡볶이',
62 | imgUrl: '떡볶이/치즈떡볶이.jpg',
63 | detail:
64 | '떡볶이의 끝판왕! 역시 떡볶이에는 치즈가 정석이지! 맛은 있으나 칼로리가 높은게 단잠이긴 하다. 하지만 어쨋든 우유로 만들었으니 열심히 먹으면 키가 1센치라도 더 클수있지 않을까?',
65 | price: 12000,
66 | },
67 | ];
68 |
69 | export const mexican: IProductRaw[] = [
70 | {
71 | name: '나초',
72 | imgUrl: '멕시칸/나초.jpg',
73 | detail:
74 | '멕시칸의 기본기 나초! 다른곳의 나초와 비교하지말라. 바삭함과 고소함을 동시에 느낄 수 있는 아주 오묘한 맛의 나초! 거기에 추가되는 우리만의 비밀 레시피 소스! 상큼한걸 원하면 칠리소스에 찍어먹고 꾸덕한걸 원하면 치즈디핑에 찍어먹으삼',
75 | price: 10000,
76 | },
77 | {
78 | name: '야채 타코',
79 | imgUrl: '멕시칸/야채타코.jpg',
80 | detail:
81 | '타코는 타코인데 야채가 매우 많이 들어갔다. 솔찍히 떡볶이에 들어간 야채는 진짜 맛없긴한데 멕시칸에 들어간 야채는 그래도 아주 프레시해서 굿굿! 맛뿐만 아니라 알록달록한 색상으로 눈도 만족시켜주는 맛나는 타코!',
82 | price: 10000,
83 | },
84 | {
85 | name: '지옥불 고추',
86 | imgUrl: '멕시칸/지옥불고추.jpg',
87 | detail:
88 | '우라나라 고추와는 다르다... 서서히 스며드는 이 매운맛... 나는 이거 먹을때마다 우는것같다. 눈물 한방을 흘리지않고 먹을 수 있다면 인~정',
89 | price: 10000,
90 | },
91 | {
92 | name: '치킨 피타',
93 | imgUrl: '멕시칸/치킨피타.jpg',
94 | detail:
95 | '건강한 햄버거라 생각하면된다. 맛도 있지만 칼로리도 적고 빵이 또 매우 쫄깃해서 식감도 좋고! 햄버거라기엔 치즈가 없어서 조금 아쉽지만 치즈를 원하면 치즈 디핑소스 천원에 추가 고고~',
96 | price: 10000,
97 | },
98 | {
99 | name: '칠리 스프',
100 | imgUrl: '멕시칸/칠리스프.jpg',
101 | detail:
102 | '매콤함이 살짝 느껴지는 꿀맛 존맛탱 칠리스프! 나초를 찍어먹거나 치킨피타등 메인 디쉬를 찍어먹어도 맛있어요! 물론 그냥먹어도 JMT~',
103 | price: 10000,
104 | },
105 | {
106 | name: '토마토 타코',
107 | imgUrl: '멕시칸/토마토타코.jpg',
108 | detail:
109 | '타코에 토마토를 엄청많이 넣어버렸다! 역시 멕시칸은 토마토맛에 먹는거지! 별로 맵지는 않으니 매운걸 잘 못먹는 사람도 충분히 먹을 수 있는 토마토 타코!',
110 | price: 10000,
111 | },
112 | {
113 | name: '토마토 필라프',
114 | imgUrl: '멕시칸/토마토필라프.jpg',
115 | detail:
116 | '한국식 볶음밥을 생각하면 안된다. 맛이 없는건 아니지만 매우 다른 감촉의 쌀과 소스가 사용됐다! ',
117 | price: 10000,
118 | },
119 | {
120 | name: '포솔레',
121 | imgUrl: '멕시칸/포솔레.jpg',
122 | detail:
123 | '이건 진짜 솔찍히 나도 뭔지모르겠다... 하지만 진짜 맛있으니 한번 먹어보세요. 여러가지 고기가 들어가고 야채와 라임까지... 약간 멕시칸식 국밥같은 느낌입니다. 맛있어요.',
124 | price: 10000,
125 | },
126 | ];
127 |
128 | export const friedRice: IProductRaw[] = [
129 | {
130 | name: '간장 볶음밥',
131 | imgUrl: '볶음밥/간장볶음밥.jpg',
132 | detail:
133 | '옛날에 엄마가 해주던 간장볶음밥! 이거 하나만 있으면 없던 입맛도 한번에 살아나는 Magic! 볶음밥의 고소함과 간장의 짭짜름함이 합쳐진 완벽한 조화의 맛!',
134 | price: 10000,
135 | },
136 | {
137 | name: '계란 볶음밥',
138 | imgUrl: '볶음밥/계란볶음밥.jpg',
139 | detail:
140 | '역시 볶음밥에 계란이 빠지면 너무 아쉽지! 볶음밥에 계란을 듬뿍 넣어서 맛도 좋아지고 영양분도 좋아진 볶음밥! ',
141 | price: 10000,
142 | },
143 | {
144 | name: '김치 볶음밥',
145 | imgUrl: '볶음밥/김치볶음밥.jpg',
146 | detail:
147 | '김치볶음밥에 멕시칸 스타일을 섞었다! 화끈한 맛과 볶음밥의 전통적인 고소함을 동시에 느낄 수 있는 꿀맛 아이템. 가격도 싸고 맛도 좋고! 한번 드셔보세요!',
148 | price: 10000,
149 | },
150 | {
151 | name: '새우 볶음밥',
152 | imgUrl: '볶음밥/새우볶음밥.jpg',
153 | detail:
154 | '볶음밥에 이렇게 큰 새우라니 이리 사치로울수가... 가격도 착하고 맛도 좋은 새우볶음밥! 특히 매운걸 못먹는사람에겐 안성맞춤임 너무 맛있는 볶음밥이예요!',
155 | price: 10000,
156 | },
157 | {
158 | name: '새우 카레 볶음밥',
159 | imgUrl: '볶음밥/새우카레볶음밥.jpg',
160 | detail:
161 | '새우만 들어간 볶음밥은 이제 식상하다! 약간 아쉬운 밋밋한 맛에 카레를 더해서 더욱 풍미로운 맛을 구현한 카레 새우 볶음밥! 달콤하고 짭짤한 맛에한번 빠져보세요!',
162 | price: 10000,
163 | },
164 | {
165 | name: '야채 볶음밥',
166 | imgUrl: '볶음밥/야채볶음밥.jpg',
167 | detail:
168 | '야채는 역시나... 왜먹는지 모르겠지만 그래도 저칼로리로 배를 채우려면 야채만한게 없지! 흰 쌀밥을 먹는게 양심에 찔리는 헬창이 있다면 야채가 듬뿍 들어간 야채볶음밥을 먹어보삼',
169 | price: 10000,
170 | },
171 | {
172 | name: '카레 볶음밥',
173 | imgUrl: '볶음밥/카레볶음밥.jpg',
174 | detail:
175 | '그냥 볶음밥은 역시 조금 밋밋한 맛이 있다. 하지만 카레가 추가된다면!? 짭짤한 맛과 고소한 맛의 완벽한 조화로 더욱 맛있는 볶음밥의 완성! 특히 카레는 오뚜기 3분요리가 아니라 우리가 직접 제조한 특제소스로 만든 카레!',
176 | price: 10000,
177 | },
178 | ];
179 |
180 | export const sushi: IProductRaw[] = [
181 | {
182 | name: '셰프 추천 스시',
183 | imgUrl: '스시/셰프추천스시.jpg',
184 | detail:
185 | '셰프가 추천하는 스시요리세트! 가격도 싸고 맛도 있고! 우리 가게에서 이보다 가성비 스시가 나올수가 없다! 저희 스시집에서 처음 주문해보면 이것부터 한번 먹어보세요! 맛있는것들로 꼭꼭 채워드리겠습니다!',
186 | price: 10000,
187 | },
188 | {
189 | name: '스시 도시락',
190 | imgUrl: '스시/스시도시락.jpg',
191 | detail:
192 | '도시락에 이쁘게 패키징돼있는 스시 도시락! 피크닉에 김밥이 왠말이냐! 이제는 데이트 도시락도 스시를 싸가는 시대! 냉장고에 잠시 넣어두고 도시락 통째로 가져가면 연인한테 인기만점! 아 물론 혼자먹어도 맛있어요 ㅠ',
193 | price: 10000,
194 | },
195 | {
196 | name: '스시 롤',
197 | imgUrl: '스시/스시롤.jpg',
198 | detail:
199 | '스시의 끝은 역시 스시롤! 내부도 꽉꽉채우고 맛도 아주 튼실한 완벽한 스시롤! 스시만 먹어도 맛있지만 간장에 와사비를 싹 풀어서 한번 콕 찍어먹으면 정말 너무너무 맛있다! 저희 가게에서 항상 제일 잘팔리는 베스트셀러입니다!',
200 | price: 10000,
201 | },
202 | {
203 | name: '아기자기 스시',
204 | imgUrl: '스시/아기자기스시.jpg',
205 | detail:
206 | '생긴게 아기자기해서 아기자기스시! 크기는 작지만 무시하지말라! 한입에 넣으면 너무 맛있는 아기자기스시! 간이 너무 잘 베어있어서 간장도 필요없음~ 와사비도 이미 다 들어있음~ 입이 작은 친구들도 한입에 먹기 좋아요~',
207 | price: 10000,
208 | },
209 | {
210 | name: '작은 모듬 스시',
211 | imgUrl: '스시/작은모듬스시.jpg',
212 | detail:
213 | '우리 레스토랑의 모든 노하우를 담은 모든 스시! 여러가지를 모두 맛보고싶지만 많이 먹지는 못하는 당신을위해 작은 모듬 스시를 준비했다! 어떤 메뉴를 고를지 항상 고민하는 당신! 이제 그냥 모듬스시 드세요!',
214 | price: 10000,
215 | },
216 | {
217 | name: '종합 스시',
218 | imgUrl: '스시/종합스시.jpg',
219 | detail:
220 | '이젠 더이상 아이디어가 없어서 종합스시라 지었다! 양이 많지는 않지만 있을거 다 있고 없을건 없다. 그래도 양이 적은사람은 이거 한접시 먹으면 배부를 수 있으니 양이 적은사람들한테 강추~',
221 | price: 10000,
222 | },
223 | {
224 | name: '중간 모듬 스시',
225 | imgUrl: '스시/중간모듬스시.jpg',
226 | detail:
227 | '우리 레스토랑의 모든 노하우를 담은 모든 스시! 여러가지를 모두 맛보고싶지만 큰 모듬스시는 너무 큰 당신을 위해 준비했다! 어떤 메뉴를 고를지 항상 고민하는 당신! 이제 그냥 모듬스시 드세요!',
228 | price: 10000,
229 | },
230 | {
231 | name: '큰 모듬 스시',
232 | imgUrl: '스시/큰모듬스시.jpg',
233 | detail:
234 | '우리 레스토랑의 모든 노하우를 담은 모든 스시! 여러가지를 모두 맛보고싶고 중간 모듬스시조차 너무 적은 당신을 위해 준비했다! 어떤 메뉴를 고를지 항상 고민하는 당신! 이제 그냥 모듬스시 드세요!',
235 | price: 10000,
236 | },
237 | ];
238 |
239 | export const steak: IProductRaw[] = [
240 | {
241 | name: '감자칩 스테이크',
242 | imgUrl: '스테이크/감자칩스테이크.jpg',
243 | detail:
244 | '스테끼에는 역시 감자칩이 진리! 겉은 바삭바삭하게 구워지고 속은 촉촉하게 미디엄레어로 구워진 스테이크와함께 소금과 후추가 듬뿍 뿌려진 짭짤한 감자칩을 같이 먹어볼 수 있는 세트메뉴!',
245 | price: 10000,
246 | },
247 | {
248 | name: '고추 스테이크',
249 | imgUrl: '스테이크/고추스테이크.jpg',
250 | detail:
251 | '우리나라 사람이라면 매운맛은 못참지! 스테이크치고 과하게 고추를 올린 느낌이 있긴하지만 그래도 먹어보면 맛있다! 보통 고추도 아니고 아주 매운 청양고추라 화장실이 두렵긴하지만 부드러운 스테이크와 은근 잘 어울린다!',
252 | price: 10000,
253 | },
254 | {
255 | name: '닭다리 스테이크',
256 | imgUrl: '스테이크/닭다리스테이크.jpg',
257 | detail:
258 | '누가 스테이크는 꼭 소고기로 만들어야한다고 하였는가? 닭다리로도 충분히 맛있는 스테이크를 만들 수 있다! 비주얼은 그냥 브라질닭을 오븐에 구운것같은 느낌이지만 일단 먹어보면 매우 맛있다는걸 느낄것이다.',
259 | price: 10000,
260 | },
261 | {
262 | name: '등심 스테이크',
263 | imgUrl: '스테이크/등심스테이크.jpg',
264 | detail:
265 | '스테이크의 정석은 역시나 등심! 너무나도 바삭하게 구워진 표면과 육즙이 완벽하게 갖혀버린 속살... 맛있어도 황당할정도로 너무 맛있다. 스테이크는 역시 이렇게 해먹는거지! 소스까지 찍어먹으면 더욱 맛있어요!',
266 | price: 10000,
267 | },
268 | {
269 | name: '미디움 레어 스테이크',
270 | imgUrl: '스테이크/미디움레어스테이크.jpg',
271 | detail:
272 | '스테이크는 미디음 레어가 정석! 솔찍히 웰던 먹으려면 왜 소고기를 먹는가!? 그냥 돼지고기 드삼. 그 누구도 미워할 수 없는 그맛 미디엄레어 스테이크! 한번 먹어보면 잊을 수 없는 그맛!',
273 | price: 10000,
274 | },
275 | {
276 | name: '상남자 레어 스테이크',
277 | imgUrl: '스테이크/상남자레어스테이크.jpg',
278 | detail:
279 | '이것은... 상남자 레어 스테이크... 이게 과연 불에 올라간적이 있었을까? 놀랍게도 한번은 지진거라고 합니다. 야만인처럼 소고기의 살집을 그대로 뜯어드시고싶다면 이걸 드시면 되지만 제 취향은 아닌관계로 Pass~',
280 | price: 10000,
281 | },
282 | {
283 | name: '안심 스테이크',
284 | imgUrl: '스테이크/안심스테이크.jpg',
285 | detail:
286 | '돈만 있다면 역시 등심보단 안심이지! 부드러움이 정말 도를 넘어버렸다. 입에 넣는순간 사르르 녹아버리고 과연 내가 이빨이 필요한가하는 생각을 하게된다. 너무 빨리 녹아버려서 아쉬울정도인 안심 스테이크! 꼭 한번 먹어보세요!',
287 | price: 10000,
288 | },
289 | ];
290 |
291 | export const noodles: IProductRaw[] = [
292 | {
293 | name: '굴요리',
294 | imgUrl: '쌀국수/굴요리.jpg',
295 | detail:
296 | '베트남식 굴요리! 굴을 어떻게한건지는 잘 모르겠는데 어쨋든 굴과 여러가치 야채가 들어가는것같다! 알록달록 색깔도 이쁘고 굴도 아주 신선하고 맛있으니 굴을 좋아한다면 꼭 한번은 먹어봐야하는 메뉴!',
297 | price: 10000,
298 | },
299 | {
300 | name: '모듬 세트',
301 | imgUrl: '쌀국수/모듬세트.jpg',
302 | detail:
303 | '각종 사이드메뉴를 모두 다 모았다! 어떤 사이드가 먹고싶은지 결정을 못하겠다면 이 메뉴를 시키면 한번에 해결! 소스에 콕콕 찍어서 한입에 쏙 넣어 먹으면 너무너무 맛있어요!',
304 | price: 10000,
305 | },
306 | {
307 | name: '보통 쌀국수',
308 | imgUrl: '쌀국수/보통쌀국수.jpg',
309 | detail:
310 | '베트남 음식의 정석은 역시 쌀국수! 근본있는 레스토랑답게 가장 근본있는 음식을 제일 잘한다! 배달은 국수와 육수를 따로 포장하니 혹시 식으면 잠시 전자레인지에 육수만 데워서 먹으면 싹~',
311 | price: 10000,
312 | },
313 | {
314 | name: '스프링롤',
315 | imgUrl: '쌀국수/스프링롤.jpg',
316 | detail:
317 | '스프링롤... 정말 너무 맛있다. 스라라차 소스에 콕 찍어서 한입에 쏙 넣으면 너무 맛나는것! 야채도 듬뿍 넣어서 아주 빵빵하게 감싸봤습니다! 우리 레스토랑 스프링롤 먹으면 다른곳에선 못먹는다~',
318 | price: 10000,
319 | },
320 | {
321 | name: '치킨 쌀국수',
322 | imgUrl: '쌀국수/치킨쌀국수.jpg',
323 | detail:
324 | '일반 쌀국수가 조금 심심하다면 치킨을 조금 감미한 헬창 프로틴 쌀국수를 한번 먹어보세요! 과연 이게 쌀국수인지 치킨국수인지 알 수 없을만큼 꽉차있는 양의 치킨을 맛보고싶다면 치킨 쌀국수!',
325 | price: 10000,
326 | },
327 | {
328 | name: '해물 쌀국수',
329 | imgUrl: '쌀국수/해물쌀국수.jpg',
330 | detail: '해물을 좋아한다면 해물 쌀국수를 참을 수 없지!',
331 | price: 10000,
332 | },
333 | ];
334 |
335 | export const chicken: IProductRaw[] = [
336 | {
337 | name: '매콤 치킨',
338 | imgUrl: '치킨/매콤치킨.jpg',
339 | detail:
340 | '한국인이라면 일단 매워야 음식이지! 매콤한 소스가 발린 매콤 치킨! 맵기는 하지만 못참을정도의 매운맛은 아니기때문에 매운걸 잘 못먹는 사람들도 충분히 화장실 걱정없이 소화할 수 있는 치킨!',
341 | price: 10000,
342 | },
343 | {
344 | name: '바삭 치킨',
345 | imgUrl: '치킨/바삭치킨.jpg',
346 | detail:
347 | '바삭함의 끝판왕 바삭치킨! 더이상의 바삭함은 없다! 만약에 이 치킨보다 더욱 바삭한 치킨이 있다면 저희에게 신고해주세요! 바로 환불해드림~',
348 | price: 10000,
349 | },
350 | {
351 | name: '순살 치킨',
352 | imgUrl: '치킨/순살치킨.jpg',
353 | detail:
354 | '손에 기름과 소스가 뭍는게 싫어하는 당신을 위한 순살치킨! 살짝 뻑뻑해보일수도 있지만 역시나 치킨은 순살로 먹어야 먹는맛이 있지! 젖가락과 소스까지 모두 제공해준다! 맛있게 한번 먹어보세용~',
355 | price: 10000,
356 | },
357 | {
358 | name: '오븐 치킨',
359 | imgUrl: '치킨/오븐치킨.jpg',
360 | detail:
361 | '치킨은 역시 기름에 튀기는것보단 오븐에 굽는게 최고지. 바삭바삭하게 구워서 한입 뜯어먹으면 맛도좋지만 쾌감이 너무 좋은 치킨! 뼈까지 다 먹어버리고싶어~~',
362 | price: 10000,
363 | },
364 | {
365 | name: '후라이드 치킨',
366 | imgUrl: '치킨/후라이드치킨.jpg',
367 | detail:
368 | '후라이드 치킨은 언제나 맛있다! 튜닝의 끝판왕은 역시나 순정! 소스 아무리 맛있게 뿌려봤자 결국엔 그냥 후라이드가 맛있다. 바삭바삭하고 동시에 말랑말랑한 치킨의 끝판왕 순정 후라이드 치킨!',
369 | price: 10000,
370 | },
371 | ];
372 |
373 | export const italian: IProductRaw[] = [
374 | {
375 | name: '계란 스파게티',
376 | imgUrl: '파스타/계란스파게티.jpg',
377 | detail:
378 | '파스타에는 원래 계란이 들어간다는걸 알고 있었는가? 계란이 들어가서 고소하지만 여기는 더~욱 많은 계란을 퍼부었다! 고소해도 너무 고소하고 맛있어도 너무 맛있는 계란파스타! 너무좋아용~',
379 | price: 10000,
380 | },
381 | {
382 | name: '까르보나라',
383 | imgUrl: '파스타/까르보나라.jpg',
384 | detail:
385 | '우리 식당에서 제일 자신있어하는 까르보나라! 특제 소스를 사용해서 약간은 매콤한 느낌이 첨가되었다! 느끼한맛을 어느정도 매운맛으로 완화시키는 완벽한 조화를 이루는 맛! 크림파스타를 좋아한다면 꼭 한번은 먹어봐야할 파스타!',
386 | price: 10000,
387 | },
388 | {
389 | name: '다이어트 파스타',
390 | imgUrl: '파스타/다이어트파스타.jpg',
391 | detail:
392 | '누가 파스타는 탄수화물만 들었다고 하였는가? 탄수화물 조절한다고 연인과 파스타조차 먹어주지않는 헬창들을 위해서 준비했다! 파스타는 줄이고 치킨을 더 넣어서 이게 과연 파스타라고 부르는게 맞는가싶긴 하지만 먹고싶다면 먹거라.',
393 | price: 10000,
394 | },
395 | {
396 | name: '덜익은 파스타',
397 | imgUrl: '파스타/덜익은파스타.jpg',
398 | detail:
399 | '진정한 상남자만 먹을 수 있는 파스타! 덜익은 파스타! 과연 물에 들어간적은 있을가 의심이 되긴하지만 과자라고 생각하고 맥주 안주로 씹어먹으면 맛이있진 않지만 그래도 먹을 순 있다.',
400 | price: 10000,
401 | },
402 | {
403 | name: '미트볼 스파게티',
404 | imgUrl: '파스타/미트볼스파게티.jpg',
405 | detail:
406 | '스파게티의 정석중 하나! 미트볼 스파게티! 미트볼은 역시 어디에넣어도 잘 어울린다! 토마토 파스타에 미트볼을 넣어서 더욱 완벽해진 스파게티! 매일먹어도 질리지를 않아요~~',
407 | price: 10000,
408 | },
409 | {
410 | name: '봉골레 파스타',
411 | imgUrl: '파스타/봉골레파스타.jpg',
412 | detail:
413 | '파스타를 잘하는집이면 봉골레 파스타를 꼭 먹어봐야지~ 말도안되게 맛있고 말도안되게 익숙한 그맛의 봉골레 파스타! 정석대로 진짜 맛있게 만들어줄테니 한번 드셔봐~',
414 | price: 10000,
415 | },
416 | {
417 | name: '빵 스파게티',
418 | imgUrl: '파스타/빵스파게티.jpg',
419 | detail:
420 | '빵을 듬뿍넣은 토마토 스파게티! 빠네 스파게티를 하기에는 빵이 너무 많이 필요하니 빵의 맛을 조금이라도 느껴볼 수 있도록 작게 잘라서 그릇에 뿌려버렸다! 하지만 너무 맛있는걸~',
421 | price: 10000,
422 | },
423 | {
424 | name: '소세지 스파게티',
425 | imgUrl: '파스타/소세지스파게티.jpg',
426 | detail:
427 | '소세지 너무좋아요~ 어릴때 엄마가 해주던 소세지가 듬뿍 들어간 스파게티! 이건 어떻게 요리해도 절대로 실패할수가 없다! 토마토의 달콤한 맛과 소세지의 짭짜름한 맛이 완벽하게 조화되서 어린시절이 떠오르는 맛나는 스파게티!',
428 | price: 10000,
429 | },
430 | {
431 | name: '해물 스파게티',
432 | imgUrl: '파스타/해물스파게티.jpg',
433 | detail:
434 | '해물을 좋아하면 역시 스파게티도 해물 스파게티를 먹어야죠! 비린맛 하나 없이 맛있게 구워내서 완벽한 해물과 이탈리아에서 특별 공수해온 고소한 향의 오일로 만든 오일 파스타가 합쳐진 완벽한 요리!',
435 | price: 10000,
436 | },
437 | ];
438 |
--------------------------------------------------------------------------------
/src/cache/rating.data.interface.ts:
--------------------------------------------------------------------------------
1 | import { Rating } from '../rating/entities/rating.entity';
2 |
3 | export type IRatingRaw = Omit;
4 |
--------------------------------------------------------------------------------
/src/cache/rating.data.ts:
--------------------------------------------------------------------------------
1 | import { IRatingRaw } from './rating.data.interface';
2 |
3 | export const fiveStarRatings: IRatingRaw[] = [
4 | {
5 | content:
6 | '양도 많고 맛있어요~ 배달기사님도 이때까지 뵀던 분중에 가장 친절하셨습니다 ㅎㅎ',
7 | },
8 | {
9 | content: '감사합니다 배달 엄청 빨랐어요! 양도 많고 굿!',
10 | },
11 | {
12 | content:
13 | '처음 주문했는데 맛있어요!! 소스도 넉넉하게 주시고 국물도 맛있네요!! 집앞이라 종종 주문할게요~ 밥 빼고 요청드렸는데 만족합니다!!',
14 | },
15 | {
16 | content: '정말 맛있어요.',
17 | },
18 | {
19 | content: '아집은 김치도 맛있다. 반찬까지 다먹음...',
20 | },
21 | {
22 | content: '왜 평점 5.0인지 알거 같아요! 자극적이지 않고 맛있어요~~',
23 | },
24 | {
25 | content:
26 | '리뷰에 칭찬이 많아서 시켜봤는데 진짜 맛있네요 :-) 요청사항도 잘 들어주셔서 감사해요!',
27 | },
28 | {
29 | content:
30 | '친구가 집에 놀러와서 동네맛집이 궁금하다고 했는데 여기가 최고라고해서 냉큼 주문했어요!! ^^ 덕분에 친구도 저도 진짜 맛있게 잘 먹었어요~ 이힛 감사합니다!!',
31 | },
32 | {
33 | content: '적당히 맵고 맛있습니다. 잘 먹었습니다. 감사합니다!!',
34 | },
35 | {
36 | content:
37 | '간만에 시켰는데 메뉴도 살짝 바뀌었지만 더 업그레이드 된 것 같아요~ 맛있었습니다! 감사합니다!!',
38 | },
39 | ].map((item) => ({
40 | ...item,
41 | rating: 5,
42 | }));
43 |
44 | export const fourStarRatings: IRatingRaw[] = [
45 | {
46 | content:
47 | '너무너무 맛있었어요. 일중리 내내 먹는것도 가능할 것 같아요. 하지만 좀 식어서 왔네요.',
48 | },
49 | {
50 | content:
51 | '동생이 맛있다고 손 씻는 동안 다 먹었어요... 손 씻을동안 청소년 한명이 흡입해서 없애버릴 정도로 정말 맛있습니다. 하지만 배송 속도가 아쉬워요 ㅠ',
52 | },
53 | {
54 | content: '맛있게 잘먹었습니다. 다음에 또 시킬게요',
55 | },
56 | {
57 | content:
58 | '역시 넘나 맛있네요~ 쿠폰 모은것도 사용하고 리뷰이벤트에 카톡이벤트까지 했어요! 여러분 여기 맛집입니다! 체인점도 많아졌더라구요~~ 응원합니다!',
59 | },
60 | {
61 | content: '배달 빠르고 포장도 깔끔해요~ 배달비 없는것도 굿!',
62 | },
63 | {
64 | content:
65 | '맛있어요~~ 양도 기대한것보다 넉넉했어요! 배달도 빠르고 단골 될 것 같네요~ ㅎㅎ',
66 | },
67 | {
68 | content:
69 | '어버이날 기념으로 드시고싶으신게 뭐냐고 여쭤봤는데 여기 말씀하시더라구요 ㅎㅎ 저는 양이 좀 부족했는데 그래도 맛있었습니다~!',
70 | },
71 | {
72 | content:
73 | '정말 왜 다들 맛집이라고 하는지 알겠어요!! 정말 잘먹었는데 반찬 까먹고 안보내셔서 4점만 드릴게요~',
74 | },
75 | {
76 | content: '존맛탱~',
77 | },
78 | {
79 | content:
80 | '정말 맛있게 먹었습니다. 여친은 배부르다는데 성인 남성이 먹기에는 조금 부족할 수 있어요~',
81 | },
82 | ].map((item) => ({
83 | rating: 4,
84 | ...item,
85 | }));
86 |
--------------------------------------------------------------------------------
/src/cache/restaurant.data.interface.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Restaurant,
3 | RestaurantDetail,
4 | } from '../restaurant/entities/restaurant.entity';
5 |
6 | export type IRestaurantRaw = Omit<
7 | RestaurantDetail,
8 | 'id' | 'ratings' | 'ratingsCount' | 'products'
9 | >;
10 |
--------------------------------------------------------------------------------
/src/cache/restaurant.data.ts:
--------------------------------------------------------------------------------
1 | import { RestaurantPriceRange } from '../restaurant/entities/restaurant.entity';
2 | import { IRestaurantRaw } from './restaurant.data.interface';
3 |
4 | export const restaurants: IRestaurantRaw[] = [
5 | {
6 | name: '불타는 떡볶이',
7 | thumbUrl: '떡볶이/떡볶이.jpg',
8 | tags: ['떡볶이', '치즈', '매운맛'],
9 | detail: `!!!리뷰 EVENT & 비조리 EVENT 진행중!!!
10 |
11 | @ 기본적으로 매콤합니다 @
12 | @ 덜맵게 가능하니 요청사항에 적어주세요 @
13 | @ 1인분 배달 가능합니다 @`,
14 | priceRange: RestaurantPriceRange.medium,
15 | deliveryTime: 15,
16 | deliveryFee: 2000,
17 | },
18 | {
19 | name: '매콤 멕시칸',
20 | thumbUrl: '멕시칸/야채타코.jpg',
21 | tags: ['멕시칸', '매콤', '할라피뇨'],
22 | detail: `고객님의 많은 관심에 힘입어 저녁 12시까지 영업합니다! 앞으로도 많이 찾아주세요 :)
23 |
24 | 항상 고객님의 안전한 먹거리를 위해 위생에 각별히 신경쓰고 있습니다. 안심하시고 맛있는 요리를 즐겨주세요!
25 |
26 | 언제든지 요청사항에 원하시는 내용을 적어주시면 최대한 맞춰 조리해드리니 부담없이 요청사항에 기재해주세요!`,
27 | priceRange: RestaurantPriceRange.expensive,
28 | deliveryTime: 30,
29 | deliveryFee: 0,
30 | },
31 | {
32 | name: '엄마손 볶음밥',
33 | thumbUrl: '볶음밥/새우볶음밥.jpg',
34 | tags: ['분식', '볶음밥', '김치'],
35 | detail: `***리뷰 이벤트 진행중***
36 | 1) 엄마손 볶음밥 찜하기!
37 | 2) 음식 사진과 5개 리뷰 작성 악속하기!
38 | 3) 요청사항에 '리뷰이벤트 참여' 적기!`,
39 | priceRange: RestaurantPriceRange.cheap,
40 | deliveryTime: 20,
41 | deliveryFee: 3000,
42 | },
43 | {
44 | name: '신선 코팩 스시',
45 | thumbUrl: '스시/중간모듬스시.jpg',
46 | tags: ['스시', '일식', '연어'],
47 | detail: `영업시간 변경 오후 9시 ~ 익일 점심 1시까지!
48 |
49 | 사진리뷰 & 찜 해주시면 '콜라 500ml' 서비스!
50 | 1. 사진올리기 리뷰약속!
51 | 2. 찜하기 누르기!
52 | 3. 맛있게 드시고 사진과 리뷰 꼭 부탁드려요~~`,
53 | priceRange: RestaurantPriceRange.expensive,
54 | deliveryTime: 30,
55 | deliveryFee: 0,
56 | },
57 | {
58 | name: '사나이 대왕 스테이크',
59 | thumbUrl: '스테이크/등심스테이크.jpg',
60 | tags: ['스테이크', '양식', '빠른배송'],
61 | detail: `요청사항에 '리뷰작성' | '리뷰' 남겨주시면 따근한 치즈스틱을 보내 드립니다!!!
62 |
63 | 메뉴권과 디지털 금액권 선물 바등신 고객님은 아웃백 어플에서 이제 주문이 가능합니다. 딜리버리 주문하기에서 메뉴주문 하신 후 결제하실때 메뉴권, 디지털 금액권 체크하고 선물받으신 바코드 번호 입력하시면 결제가 됩니다~ 참고해주세요!!
64 |
65 | 사나이 대왕 스테이크는 모든 음식을 전자레인지에 1분 데워 드시면 더더더더더욱 맛있게 드실 수 있습니다!`,
66 | priceRange: RestaurantPriceRange.expensive,
67 | deliveryTime: 10,
68 | deliveryFee: 0,
69 | },
70 | {
71 | name: '현지맛 미미 쌀국수',
72 | thumbUrl: '쌀국수/보통쌀국수.jpg',
73 | tags: ['쌀국수', '베트남', '해장요리'],
74 | detail: `쌀국수소스 한통에 칠리소스2 해선장소스1 비율로 드려요~
75 |
76 | **쌀국수 주문시
77 | 칠리&해선장 2대1비율, 양파절임, 단무지, 레몬조각1개
78 |
79 | **솔직한 후기 event~ing
80 | 후기를 작성해주시면 콜라 500ml를 드려요~
81 |
82 | **19주류 병맥주 주문시 오프너증정, 신분증 필히지참
83 |
84 | **배달안내
85 | 예상시간은 최대예상으로 보내드립니다
86 | 비, 눈 오는날은 지연될 수 있습니다.
87 | 배달대행을 사용하고 있으므로, 주소 잘못 입력시 배달료가 추가됩니다.`,
88 | priceRange: RestaurantPriceRange.cheap,
89 | deliveryTime: 25,
90 | deliveryFee: 1000,
91 | },
92 | {
93 | name: '불타는 냠냠 치킨',
94 | thumbUrl: '치킨/오븐치킨.jpg',
95 | tags: ['치킨', '치맥', '무료배송'],
96 | detail: `1회용품 줄이기 일환으로 젓가락은 기본 제공되지 않습니다.
97 | 필요하신분은 요청사항에 요청해주세요~!
98 |
99 | 반마리 메뉴에는 소스, 콜라가 기본 제공되지 않습니다
100 | 리뷰 이벤트 #리뷰감자 #리뷰소스 ((택1))
101 |
102 | 1,000원 2,000원 4,000원 할인쿠폰
103 | 방문포장시 대폭할인!`,
104 | priceRange: RestaurantPriceRange.medium,
105 | deliveryTime: 30,
106 | deliveryFee: 0,
107 | },
108 | {
109 | name: '크리미 꾸덕 파스타',
110 | thumbUrl: '파스타/까르보나라.jpg',
111 | tags: ['파스타', '이탈리안', '데이트'],
112 | detail: `저희 매장은 크림 파스타의 경우 비밀 레시피로 만든 특유의 크림 가루를 사용하여 조리하고있습니다. 보통 크림파스타 색이 아니더라도 믿고 안심하고 드셔도 됩니다.
113 |
114 | 알리오 올리오 메뉴 같은 경우는 조리시 기본으로 페페론치노가 첨가되어 다소 매콤할 수 있습니다.`,
115 | priceRange: RestaurantPriceRange.medium,
116 | deliveryTime: 15,
117 | deliveryFee: 0,
118 | },
119 | ];
120 |
--------------------------------------------------------------------------------
/src/core/core.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Get,
4 | Post,
5 | UploadedFile,
6 | UploadedFiles,
7 | UseInterceptors,
8 | } from '@nestjs/common';
9 | import { CoreService } from './core.service';
10 | import { FileInterceptor } from '@nestjs/platform-express';
11 | import * as multer from 'multer';
12 | import { v4 as uuid } from 'uuid';
13 | import { ApiBody, ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
14 |
15 | @ApiTags('core')
16 | @Controller('core')
17 | export class CoreController {
18 | constructor(private readonly coreService: CoreService) {}
19 |
20 | @Post('upload')
21 | @UseInterceptors(
22 | FileInterceptor('file', {
23 | storage: multer.diskStorage({
24 | destination: function (req, file, cb) {
25 | cb(null, 'public/uploads');
26 | },
27 | filename: function (req, file, cb) {
28 | const ext = file.mimetype.split('/').reverse()[0];
29 | cb(null, req.body['fileName']);
30 | },
31 | }),
32 | }),
33 | )
34 | @ApiOperation({
35 | summary: '파일 업로드하기',
36 | })
37 | @ApiOkResponse({
38 | description: '업로드된 파일의 경로',
39 | schema: {
40 | properties: {
41 | fileName: {
42 | type: 'string',
43 | description: '파일 경로',
44 | example: '/img/123123.png',
45 | },
46 | },
47 | },
48 | })
49 | @ApiBody({
50 | schema: {
51 | properties: {
52 | file: {
53 | type: 'string',
54 | format: 'binary',
55 | description: '업로드할 파일',
56 | },
57 | },
58 | },
59 | })
60 | postUpload(@UploadedFile() file: Express.Multer.File) {
61 | return {
62 | fileName: file.filename,
63 | };
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/core/core.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { CoreService } from './core.service';
3 | import { CoreController } from './core.controller';
4 |
5 | @Module({
6 | controllers: [CoreController],
7 | providers: [CoreService],
8 | exports: [CoreService],
9 | })
10 | export class CoreModule {}
11 |
--------------------------------------------------------------------------------
/src/core/core.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { BaseEntity } from './entity/base.entity';
3 | import { PaginationDto } from './dto/pagination.dto';
4 | import { Pagination } from './entity/pagination.entity';
5 |
6 | @Injectable()
7 | export class CoreService {
8 | paginate(
9 | items: T[],
10 | paginationDto: PaginationDto,
11 | ): Pagination {
12 | const copyItems = [...items];
13 | const startIdx = paginationDto.after
14 | ? copyItems.findIndex((item) => item.id === paginationDto.after)
15 | : 0;
16 |
17 | const plusOneData = copyItems.splice(
18 | startIdx === 0 ? startIdx : startIdx + 1,
19 | paginationDto.count + 1,
20 | );
21 | const hasMore = plusOneData.length > paginationDto.count;
22 |
23 | const data = hasMore
24 | ? plusOneData.splice(0, paginationDto.count)
25 | : plusOneData;
26 |
27 | return {
28 | meta: {
29 | count: data.length,
30 | hasMore,
31 | },
32 | data,
33 | };
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/core/decorator/api-bearer-token-header.ts:
--------------------------------------------------------------------------------
1 | import { applyDecorators } from '@nestjs/common';
2 | import { ApiHeader } from '@nestjs/swagger';
3 |
4 | export const ApiBearerTokenHeader = () => {
5 | return applyDecorators(
6 | ApiHeader({
7 | name: 'authorization',
8 | required: true,
9 | description: 'Bearer 토큰',
10 | example: 'Bearer xjvjiwsijzkxcjvoiasdjf',
11 | }),
12 | );
13 | };
14 |
15 | export const ApiBasicTokenHeader = () => {
16 | return applyDecorators(
17 | ApiHeader({
18 | name: 'authorization',
19 | required: true,
20 | description: 'Basic 토큰',
21 | example: 'Basic xjvjiwsijzkxcjvoiasdjf',
22 | }),
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/src/core/decorator/api-paginated-ok-response.decorator.ts:
--------------------------------------------------------------------------------
1 | import { applyDecorators, Type } from '@nestjs/common';
2 | import {
3 | ApiOkResponse,
4 | ApiResponseOptions,
5 | getSchemaPath,
6 | } from '@nestjs/swagger';
7 | import { Pagination, PaginationMeta } from '../entity/pagination.entity';
8 | import { BaseEntity } from '../entity/base.entity';
9 |
10 | export const ApiPaginatedOkResponseDecorator = >(
11 | model: T,
12 | options: ApiResponseOptions = {},
13 | ) => {
14 | return applyDecorators(
15 | ApiOkResponse({
16 | ...options,
17 | schema: {
18 | properties: {
19 | meta: {
20 | $ref: getSchemaPath(PaginationMeta),
21 | },
22 | data: {
23 | type: 'array',
24 | items: {
25 | $ref: getSchemaPath(model),
26 | },
27 | },
28 | },
29 | },
30 | }),
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/core/dto/pagination.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsOptional } from 'class-validator';
2 | import { ApiProperty } from '@nestjs/swagger';
3 |
4 | export class PaginationDto {
5 | // id
6 | @IsOptional()
7 | @ApiProperty({
8 | name: 'after',
9 | nullable: true,
10 | required: false,
11 | description: '마지막 Pagination한 모델의 ID',
12 | })
13 | after?: string;
14 |
15 | @IsOptional()
16 | @ApiProperty({
17 | name: 'count',
18 | default: 20,
19 | required: false,
20 | description: '한번에 가져올 데이터 갯수',
21 | })
22 | count?: number = 20;
23 | }
24 |
--------------------------------------------------------------------------------
/src/core/entity/base.entity.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 |
3 | export class BaseEntity {
4 | constructor(params: BaseEntity) {
5 | this.id = params.id;
6 | }
7 |
8 | @ApiProperty({
9 | name: 'id',
10 | description: '객체 ID',
11 | example: '1952a209-7c26-4f50-bc65-086f6e64dbbd',
12 | })
13 | id: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/core/entity/pagination.entity.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 |
3 | export class PaginationMeta {
4 | @ApiProperty({
5 | name: 'count',
6 | description: '데이터 갯수',
7 | example: 20,
8 | })
9 | count: number;
10 |
11 | @ApiProperty({
12 | name: 'hasMore',
13 | description: '데이터가 더 있는지 여부',
14 | example: true,
15 | })
16 | hasMore: boolean;
17 | }
18 |
19 | export class Pagination {
20 | @ApiProperty({
21 | name: 'meta',
22 | description: 'Pagination 정보',
23 | type: PaginationMeta,
24 | })
25 | meta: PaginationMeta;
26 |
27 | data: T[];
28 | }
29 |
--------------------------------------------------------------------------------
/src/core/interceptor/response-delay.interceptor.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Injectable,
3 | NestInterceptor,
4 | ExecutionContext,
5 | CallHandler,
6 | RequestTimeoutException,
7 | } from '@nestjs/common';
8 | import { delay, Observable, throwError, TimeoutError } from 'rxjs';
9 | import { catchError, timeout } from 'rxjs/operators';
10 |
11 | @Injectable()
12 | export class ResponseDelayInterceptor implements NestInterceptor {
13 | intercept(context: ExecutionContext, next: CallHandler): Observable {
14 | return next.handle().pipe(delay(500));
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { NestFactory, Reflector } from '@nestjs/core';
2 | import { AppModule } from './app.module';
3 | import { NestExpressApplication } from '@nestjs/platform-express';
4 | import { join } from 'path';
5 | import { ClassSerializerInterceptor, ValidationPipe } from '@nestjs/common';
6 | import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
7 | import { ResponseDelayInterceptor } from './core/interceptor/response-delay.interceptor';
8 |
9 | async function bootstrap() {
10 | const app = await NestFactory.create(AppModule);
11 |
12 |
13 | const config = new DocumentBuilder()
14 | .setTitle('코드팩토리 API')
15 | .setDescription('코드팩토리 RiverPod 강의 API')
16 | .setVersion('1.0')
17 | .build();
18 |
19 | app.useGlobalPipes(new ValidationPipe({ transform: true }));
20 | app.useStaticAssets(join(__dirname, '..', 'public'));
21 | app.useGlobalInterceptors(new ClassSerializerInterceptor(app.get(Reflector)));
22 | app.useGlobalInterceptors(new ResponseDelayInterceptor());
23 |
24 | const document = SwaggerModule.createDocument(app, config);
25 |
26 | SwaggerModule.setup('api', app, document);
27 | await app.listen(3000);
28 | }
29 |
30 | bootstrap();
31 |
--------------------------------------------------------------------------------
/src/order/dto/create-order.dto.ts:
--------------------------------------------------------------------------------
1 | import { BasketItemDto } from '../../user/dto/basket-item.dto';
2 | import { ApiProperty } from '@nestjs/swagger';
3 | import { IsArray, IsNumber, IsString, ValidateNested } from 'class-validator';
4 |
5 | export class CreateOrderDto {
6 | @ApiProperty({
7 | name: 'id',
8 | description: '주문 ID',
9 | example: '1952a209-7c26-4f50-bc65-086f6e64dbbd',
10 | })
11 | id: string;
12 |
13 | @ApiProperty({
14 | name: 'products',
15 | description: '주문 상품들',
16 | type: BasketItemDto,
17 | isArray: true,
18 | })
19 | products: BasketItemDto[];
20 |
21 | @ApiProperty({
22 | name: 'totalPrice',
23 | description: '총액',
24 | example: 10000,
25 | })
26 | totalPrice: number;
27 |
28 | @ApiProperty({
29 | name: 'createdAt',
30 | description: '생성일자',
31 | })
32 | createdAt: string;
33 | }
34 |
--------------------------------------------------------------------------------
/src/order/dto/update-order.dto.ts:
--------------------------------------------------------------------------------
1 | import { PartialType } from '@nestjs/swagger';
2 | import { CreateOrderDto } from './create-order.dto';
3 |
4 | export class UpdateOrderDto extends PartialType(CreateOrderDto) {}
5 |
--------------------------------------------------------------------------------
/src/order/entities/order-product-entity.ts:
--------------------------------------------------------------------------------
1 | import { BaseEntity } from '../../core/entity/base.entity';
2 | import { Restaurant } from '../../restaurant/entities/restaurant.entity';
3 | import { Transform } from 'class-transformer';
4 | import { ApiProperty } from '@nestjs/swagger';
5 |
6 | export class OrderProduct extends BaseEntity {
7 | constructor(params: OrderProduct) {
8 | super(params);
9 |
10 | this.name = params.name;
11 | this.detail = params.detail;
12 | this.imgUrl = params.imgUrl;
13 | this.price = params.price;
14 | }
15 |
16 | @ApiProperty({
17 | name: 'name',
18 | description: '이름',
19 | example: '떡볶이',
20 | })
21 | name: string;
22 |
23 | @Transform(({ value }) => `/img/${value}`)
24 | @ApiProperty({
25 | name: 'imgUrl',
26 | description: '이미지 URL',
27 | example: '/img/img.png',
28 | })
29 | imgUrl: string;
30 |
31 | @ApiProperty({
32 | name: 'detail',
33 | description: '상품 상세설명',
34 | example: '맛있는 떡볶이',
35 | })
36 | detail: string;
37 |
38 | @ApiProperty({
39 | name: 'price',
40 | description: '가격',
41 | example: 8000,
42 | })
43 | price: number;
44 | }
45 |
--------------------------------------------------------------------------------
/src/order/entities/order.entity.ts:
--------------------------------------------------------------------------------
1 | import { BaseEntity } from '../../core/entity/base.entity';
2 | import { Product } from '../../product/entities/product.entity';
3 | import { IBasketItem } from '../../user/entities/user.entity.interface';
4 | import { User } from '../../user/entities/user.entity';
5 | import { Exclude } from 'class-transformer';
6 | import { Restaurant } from '../../restaurant/entities/restaurant.entity';
7 | import {
8 | BasketItemDto,
9 | BasketItemWithFullProductDto,
10 | } from '../../user/dto/basket-item.dto';
11 | import { ApiProperty } from '@nestjs/swagger';
12 |
13 | export class Order extends BaseEntity {
14 | constructor(params: Order) {
15 | super(params);
16 |
17 | Object.assign(this, params);
18 | }
19 |
20 | @Exclude()
21 | user: User;
22 |
23 | @ApiProperty({
24 | name: 'products',
25 | isArray: true,
26 | type: BasketItemWithFullProductDto,
27 | description: '장바구니 상품',
28 | })
29 | products: BasketItemWithFullProductDto[];
30 |
31 | @ApiProperty({
32 | name: 'restaurant',
33 | type: Restaurant,
34 | description: '레스토랑 정보',
35 | })
36 | restaurant: Restaurant;
37 |
38 | @ApiProperty({
39 | name: 'totalPrice',
40 | description: '총 금액',
41 | example: 10000,
42 | })
43 | totalPrice: number;
44 |
45 | @ApiProperty({
46 | name: 'createdAt',
47 | description: '생성일자',
48 | })
49 | createdAt: string;
50 | }
51 |
--------------------------------------------------------------------------------
/src/order/order.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Get,
4 | Post,
5 | Body,
6 | Patch,
7 | Param,
8 | Delete,
9 | UseGuards,
10 | Request,
11 | Query,
12 | } from '@nestjs/common';
13 | import { OrderService } from './order.service';
14 | import { AccessTokenGuard } from '../auth/bearer-token.guard';
15 | import { CreateOrderDto } from './dto/create-order.dto';
16 | import { PaginationDto } from '../core/dto/pagination.dto';
17 | import { Product } from '../product/entities/product.entity';
18 | import { Pagination } from '../core/entity/pagination.entity';
19 | import { Order } from './entities/order.entity';
20 | import {
21 | ApiBody,
22 | ApiExtraModels,
23 | ApiOkResponse,
24 | ApiOperation,
25 | ApiTags,
26 | } from '@nestjs/swagger';
27 | import { ApiPaginatedOkResponseDecorator } from '../core/decorator/api-paginated-ok-response.decorator';
28 |
29 | @ApiTags('order')
30 | @ApiExtraModels(PaginationDto, Order)
31 | @Controller('order')
32 | export class OrderController {
33 | constructor(private readonly orderService: OrderService) {}
34 |
35 | @UseGuards(AccessTokenGuard)
36 | @ApiOperation({
37 | summary: '주문 Pagination',
38 | })
39 | @ApiPaginatedOkResponseDecorator(Order, {
40 | description: 'Pagination 결과',
41 | })
42 | @Get()
43 | paginateOrder(
44 | @Request() req,
45 | @Query() paginationDto: PaginationDto,
46 | ): Pagination {
47 | return this.orderService.paginateOrders(req.user, paginationDto);
48 | }
49 |
50 | @UseGuards(AccessTokenGuard)
51 | @Post()
52 | @ApiOperation({
53 | summary: '주문 생성하기',
54 | })
55 | @ApiBody({
56 | type: CreateOrderDto,
57 | })
58 | @ApiOkResponse({
59 | status: 201,
60 | type: Order,
61 | })
62 | postOrder(@Request() req, @Body() body: CreateOrderDto): Order {
63 | return this.orderService.postOrder(req.user, body);
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/order/order.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { OrderService } from './order.service';
3 | import { OrderController } from './order.controller';
4 | import { CacheModule } from '../cache/cache.module';
5 | import { AuthModule } from '../auth/auth.module';
6 | import { CoreModule } from '../core/core.module';
7 |
8 | @Module({
9 | controllers: [OrderController],
10 | providers: [OrderService],
11 | imports: [CacheModule, AuthModule, CoreModule],
12 | })
13 | export class OrderModule {}
14 |
--------------------------------------------------------------------------------
/src/order/order.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { CreateOrderDto } from './dto/create-order.dto';
3 | import { CacheService } from '../cache/cache.service';
4 | import { User } from '../user/entities/user.entity';
5 | import { Order } from './entities/order.entity';
6 | import { CoreService } from '../core/core.service';
7 | import { PaginationDto } from '../core/dto/pagination.dto';
8 | import { Pagination } from '../core/entity/pagination.entity';
9 | import { OrderProduct } from './entities/order-product-entity';
10 |
11 | @Injectable()
12 | export class OrderService {
13 | constructor(
14 | private cacheService: CacheService,
15 | private coreService: CoreService,
16 | ) {}
17 |
18 | paginateOrders(user: User, paginationDto: PaginationDto): Pagination {
19 | const result = this.coreService.paginate(
20 | this.cacheService.orders,
21 | paginationDto,
22 | );
23 |
24 | return {
25 | ...result,
26 | data: result.data.map((item) => new Order(item)),
27 | };
28 | }
29 |
30 | postOrder(user: User, createOrderDto: CreateOrderDto): Order {
31 | const newOrder = new Order({
32 | id: createOrderDto.id,
33 | user,
34 | restaurant: this.cacheService.products.find(
35 | (x) => createOrderDto.products[0].productId === x.id,
36 | ).restaurant,
37 | products: createOrderDto.products.map((basketItem) => ({
38 | product: new OrderProduct(
39 | this.cacheService.products.find(
40 | (product) => basketItem.productId === product.id,
41 | ),
42 | ),
43 | count: basketItem.count,
44 | })),
45 | totalPrice: createOrderDto.totalPrice,
46 | createdAt: createOrderDto.createdAt,
47 | });
48 |
49 | this.cacheService.orders = [newOrder, ...this.cacheService.orders];
50 |
51 | return newOrder;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/product/dto/create-product.dto.ts:
--------------------------------------------------------------------------------
1 | export class CreateProductDto {}
2 |
--------------------------------------------------------------------------------
/src/product/dto/update-product.dto.ts:
--------------------------------------------------------------------------------
1 | import { PartialType } from '@nestjs/mapped-types';
2 | import { CreateProductDto } from './create-product.dto';
3 |
4 | export class UpdateProductDto extends PartialType(CreateProductDto) {}
5 |
--------------------------------------------------------------------------------
/src/product/entities/product.entity.ts:
--------------------------------------------------------------------------------
1 | import { Exclude, Transform } from 'class-transformer';
2 | import { BaseEntity } from '../../core/entity/base.entity';
3 | import { Restaurant } from '../../restaurant/entities/restaurant.entity';
4 | import { ValidateNested } from 'class-validator';
5 | import { ApiProperty } from '@nestjs/swagger';
6 |
7 | export class Product extends BaseEntity {
8 | constructor(params: Product) {
9 | super(params);
10 |
11 | this.restaurant = params.restaurant;
12 | this.name = params.name;
13 | this.detail = params.detail;
14 | this.imgUrl = params.imgUrl;
15 | this.price = params.price;
16 | }
17 |
18 | @ApiProperty({
19 | name: 'restaurant',
20 | description: '레스토랑',
21 | type: Restaurant,
22 | })
23 | restaurant: Restaurant;
24 |
25 | @ApiProperty({
26 | name: 'name',
27 | description: '이름',
28 | example: '마라맛 코팩 떡볶이',
29 | })
30 | name: string;
31 |
32 | @Transform(({ value }) => `/img/${value}`)
33 | @ApiProperty({
34 | name: 'imgUrl',
35 | description: '이미지 링크',
36 | example: '/img/img.png',
37 | })
38 | imgUrl: string;
39 |
40 | @ApiProperty({
41 | name: 'detail',
42 | description: '상품설명',
43 | example: '서울에서 두번째로 맛있는 떡볶이집! 리뷰 이벤트 진행중~',
44 | })
45 | detail: string;
46 |
47 | @ApiProperty({
48 | name: 'price',
49 | description: '가격',
50 | example: 8000,
51 | })
52 | price: number;
53 | }
54 |
--------------------------------------------------------------------------------
/src/product/product.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Get,
4 | Post,
5 | Body,
6 | Patch,
7 | Param,
8 | Delete,
9 | UseGuards,
10 | NotFoundException,
11 | Query,
12 | } from '@nestjs/common';
13 | import { ProductService } from './product.service';
14 | import { Product } from './entities/product.entity';
15 | import { AccessTokenGuard } from '../auth/bearer-token.guard';
16 | import { PaginationDto } from '../core/dto/pagination.dto';
17 | import { Pagination } from '../core/entity/pagination.entity';
18 | import {
19 | ApiBody,
20 | ApiOkResponse,
21 | ApiOperation,
22 | ApiParam,
23 | ApiTags,
24 | } from '@nestjs/swagger';
25 | import { ApiPaginatedOkResponseDecorator } from '../core/decorator/api-paginated-ok-response.decorator';
26 | import { ApiBearerTokenHeader } from '../core/decorator/api-bearer-token-header';
27 |
28 | @ApiTags('product')
29 | @Controller('product')
30 | export class ProductController {
31 | constructor(private readonly productService: ProductService) {}
32 |
33 | @ApiOperation({
34 | summary: '상품 Pagination',
35 | })
36 | @ApiPaginatedOkResponseDecorator(Product, {
37 | description: 'Pagination 결과',
38 | })
39 | @ApiBody({
40 | type: PaginationDto,
41 | })
42 | @UseGuards(AccessTokenGuard)
43 | @Get()
44 | paginateProducts(@Query() paginationDto: PaginationDto): Pagination {
45 | return this.productService.paginateProducts(paginationDto);
46 | }
47 |
48 | @UseGuards(AccessTokenGuard)
49 | @Get(':pid')
50 | @ApiOperation({
51 | summary: '개별 상품 가져오기',
52 | })
53 | @ApiParam({
54 | name: 'pid',
55 | description: '가져올 상품 ID',
56 | example: '1952a209-7c26-4f50-bc65-086f6e64dbbd',
57 | })
58 | @ApiOkResponse({
59 | description: '응답 성공',
60 | type: Product,
61 | })
62 | getProduct(@Param('pid') pid: string): Product {
63 | const product = this.productService.getProductById(pid);
64 |
65 | if (!product) {
66 | throw new NotFoundException('존재하지 않는 상품입니다.');
67 | }
68 |
69 | return product;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/src/product/product.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { ProductService } from './product.service';
3 | import { ProductController } from './product.controller';
4 | import { AuthModule } from '../auth/auth.module';
5 | import { UserModule } from '../user/user.module';
6 | import { CacheModule } from '../cache/cache.module';
7 | import { CoreModule } from '../core/core.module';
8 |
9 | @Module({
10 | imports: [AuthModule, UserModule, CacheModule, CoreModule],
11 | controllers: [ProductController],
12 | providers: [ProductService],
13 | })
14 | export class ProductModule {}
15 |
--------------------------------------------------------------------------------
/src/product/product.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { Product } from './entities/product.entity';
3 | import { CacheService } from '../cache/cache.service';
4 | import { CoreService } from '../core/core.service';
5 | import { Pagination } from '../core/entity/pagination.entity';
6 | import { PaginationDto } from '../core/dto/pagination.dto';
7 | import { ProductModule } from './product.module';
8 |
9 | @Injectable()
10 | export class ProductService {
11 | constructor(
12 | private cacheService: CacheService,
13 | private coreService: CoreService,
14 | ) {}
15 |
16 | getAllProducts(): Product[] {
17 | return this.cacheService.products;
18 | }
19 |
20 | getProductById(id: string): Product {
21 | return this.cacheService.products.find((item) => item.id === id);
22 | }
23 |
24 | paginateProducts(paginationDto: PaginationDto): Pagination {
25 | const result = this.coreService.paginate(
26 | this.cacheService.products,
27 | paginationDto,
28 | );
29 |
30 | return {
31 | ...result,
32 | data: result.data.map((item) => new Product(item)),
33 | };
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/rating/dto/create-rating.dto.ts:
--------------------------------------------------------------------------------
1 | import { ApiProperty } from '@nestjs/swagger';
2 |
3 | export class CreateRatingDto {
4 | @ApiProperty({
5 | name: 'restaurantId',
6 | description: '레스토랑 ID',
7 | example: '1952a209-7c26-4f50-bc65-086f6e64dbbd',
8 | })
9 | restaurantId: string;
10 |
11 | @ApiProperty({
12 | name: 'rating',
13 | description: '평점',
14 | example: 5,
15 | })
16 | rating: number;
17 |
18 | @ApiProperty({
19 | name: 'content',
20 | description: '평가 내용',
21 | example: '맛있어요~',
22 | })
23 | content: string;
24 |
25 | // image path
26 | @ApiProperty({
27 | isArray: true,
28 | name: 'imageNames',
29 | description: '이미지 이름들',
30 | example: ['img/img.png', 'img/img2.png'],
31 | })
32 | imageNames: string[];
33 | }
34 |
--------------------------------------------------------------------------------
/src/rating/dto/update-rating.dto.ts:
--------------------------------------------------------------------------------
1 | import { PartialType } from '@nestjs/mapped-types';
2 | import { CreateRatingDto } from './create-rating.dto';
3 |
4 | export class UpdateRatingDto extends PartialType(CreateRatingDto) {}
5 |
--------------------------------------------------------------------------------
/src/rating/entities/rating.entity.ts:
--------------------------------------------------------------------------------
1 | import { BaseEntity } from '../../core/entity/base.entity';
2 | import { Exclude, Transform } from 'class-transformer';
3 | import { Restaurant } from '../../restaurant/entities/restaurant.entity';
4 | import { User } from '../../user/entities/user.entity';
5 | import { ApiProperty } from '@nestjs/swagger';
6 |
7 | export class Rating extends BaseEntity {
8 | constructor(params: Rating) {
9 | super(params);
10 |
11 | Object.assign(this, params);
12 | }
13 |
14 | @ApiProperty({
15 | name: 'user',
16 | description: '평점 작성한 유저',
17 | type: User,
18 | })
19 | user: User;
20 |
21 | @Exclude()
22 | restaurant: Restaurant;
23 |
24 | @ApiProperty({
25 | name: 'rating',
26 | example: 5,
27 | description: '평점',
28 | })
29 | rating: number;
30 |
31 | @ApiProperty({
32 | name: 'content',
33 | description: '평가 내용',
34 | example: '너무 맛있어요~',
35 | })
36 | content: string;
37 |
38 | @Transform(({ value }) => value.map((item) => `/img/${item}`))
39 | @ApiProperty({
40 | isArray: true,
41 | type: String,
42 | name: 'imgUrls',
43 | description: '이미지 URL',
44 | example: ['/img/test.png'],
45 | })
46 | imgUrls: string[];
47 | }
48 |
--------------------------------------------------------------------------------
/src/rating/rating.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Get,
4 | Post,
5 | Body,
6 | Patch,
7 | Param,
8 | Delete,
9 | Request,
10 | UseGuards,
11 | } from '@nestjs/common';
12 | import { RatingService } from './rating.service';
13 |
14 | @Controller('rating')
15 | export class RatingController {
16 | constructor(private readonly ratingService: RatingService) {}
17 |
18 | // @UseGuards(AccessTokenGuard)
19 | // @Get()
20 | // paginateRatings(@Param() paginationDto: PaginationDto) {
21 | // return this.ratingService.paginateRatings(paginationDto);
22 | // }
23 | //
24 | // @UseGuards(AccessTokenGuard)
25 | // @Post()
26 | // postRating(@Request() req, @Body() body: CreateRatingDto) {
27 | // return this.ratingService.createRestaurantRating(req.user, body);
28 | // }
29 | }
30 |
--------------------------------------------------------------------------------
/src/rating/rating.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { RatingService } from './rating.service';
3 | import { RatingController } from './rating.controller';
4 | import { AuthModule } from '../auth/auth.module';
5 | import { CoreModule } from '../core/core.module';
6 | import { CacheModule } from '../cache/cache.module';
7 |
8 | @Module({
9 | imports: [AuthModule, CoreModule, CacheModule],
10 | controllers: [RatingController],
11 | providers: [RatingService],
12 | exports: [RatingService],
13 | })
14 | export class RatingModule {}
15 |
--------------------------------------------------------------------------------
/src/rating/rating.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { CreateRatingDto } from './dto/create-rating.dto';
3 | import { UpdateRatingDto } from './dto/update-rating.dto';
4 | import { CoreService } from '../core/core.service';
5 | import { PaginationDto } from '../core/dto/pagination.dto';
6 | import { Pagination } from '../core/entity/pagination.entity';
7 | import { Rating } from './entities/rating.entity';
8 | import { CacheService } from '../cache/cache.service';
9 | import { User } from '../user/entities/user.entity';
10 | import { v4 as uuid } from 'uuid';
11 | import * as fs from 'fs';
12 | import * as path from 'path';
13 | import { CreateRestaurantRatingDto } from '../restaurant/dto/create-restaurant-rating.dto';
14 |
15 | @Injectable()
16 | export class RatingService {
17 | constructor(
18 | private coreService: CoreService,
19 | private cacheService: CacheService,
20 | ) {}
21 |
22 | paginateRatings(paginationDto: PaginationDto): Pagination {
23 | return this.coreService.paginate(this.cacheService.ratings, paginationDto);
24 | }
25 |
26 | paginateRestaurantRatings(
27 | restaurantId: string,
28 | paginationDto: PaginationDto,
29 | ): Pagination {
30 | return this.coreService.paginate(
31 | this.cacheService.ratings.filter((x) => x.restaurant.id === restaurantId),
32 | paginationDto,
33 | );
34 | }
35 |
36 | createRestaurantRating(
37 | user: User,
38 | restaurantId: string,
39 | createRatingDto: CreateRestaurantRatingDto,
40 | ): Rating {
41 | const restaurant = this.cacheService.restaurants.find(
42 | (x) => x.id === restaurantId,
43 | );
44 |
45 | const imgUrls = [];
46 |
47 | for (const img of createRatingDto.imageNames) {
48 | fs.renameSync(
49 | path.join(process.cwd(), 'public', 'uploads', img),
50 | path.join(process.cwd(), 'public', 'img', 'ratings', img),
51 | );
52 |
53 | imgUrls.push(path.join('ratings', img));
54 | }
55 |
56 | const newRating = new Rating({
57 | id: uuid(),
58 | user,
59 | restaurant,
60 | rating: createRatingDto.rating,
61 | content: createRatingDto.content,
62 | imgUrls: imgUrls,
63 | });
64 |
65 | this.cacheService.ratings = [newRating, ...this.cacheService.ratings];
66 |
67 | return newRating;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/restaurant/dto/create-restaurant-rating.dto.ts:
--------------------------------------------------------------------------------
1 | export class CreateRestaurantRatingDto {
2 | rating: number;
3 |
4 | content: string;
5 |
6 | // image path
7 | imageNames: string[];
8 | }
9 |
--------------------------------------------------------------------------------
/src/restaurant/dto/create-restaurant.dto.ts:
--------------------------------------------------------------------------------
1 | export class CreateRestaurantDto {}
2 |
--------------------------------------------------------------------------------
/src/restaurant/dto/update-restaurant.dto.ts:
--------------------------------------------------------------------------------
1 | import { PartialType } from '@nestjs/mapped-types';
2 | import { CreateRestaurantDto } from './create-restaurant.dto';
3 |
4 | export class UpdateRestaurantDto extends PartialType(CreateRestaurantDto) {}
5 |
--------------------------------------------------------------------------------
/src/restaurant/entities/restaurant.entity.ts:
--------------------------------------------------------------------------------
1 | import { BaseEntity } from '../../core/entity/base.entity';
2 | import { Transform } from 'class-transformer';
3 | import { Product } from '../../product/entities/product.entity';
4 | import { ApiProperty } from '@nestjs/swagger';
5 |
6 | export enum RestaurantPriceRange {
7 | cheap = 'cheap',
8 | medium = 'medium',
9 | expensive = 'expensive',
10 | }
11 |
12 | export class RestaurantProduct extends BaseEntity {
13 | constructor(params: RestaurantProduct) {
14 | super(params);
15 |
16 | this.name = params.name;
17 | this.detail = params.detail;
18 | this.imgUrl = params.imgUrl;
19 | this.price = params.price;
20 | }
21 |
22 | @ApiProperty({
23 | name: 'name',
24 | description: '이름',
25 | example: '마라맛 코팩 떡볶이',
26 | })
27 | name: string;
28 |
29 | @Transform(({ value }) => `/img/${value}`)
30 | @ApiProperty({
31 | name: 'imgUrl',
32 | description: '이미지 링크',
33 | example: '/img/img.png',
34 | })
35 | imgUrl: string;
36 |
37 | @ApiProperty({
38 | name: 'detail',
39 | description: '상품설명',
40 | example: '서울에서 두번째로 맛있는 떡볶이집! 리뷰 이벤트 진행중~',
41 | })
42 | detail: string;
43 |
44 | @ApiProperty({
45 | name: 'price',
46 | description: '가격',
47 | example: 8000,
48 | })
49 | price: number;
50 | }
51 |
52 | export class Restaurant extends BaseEntity {
53 | constructor(params: Restaurant) {
54 | super(params);
55 |
56 | this.name = params.name;
57 | this.thumbUrl = params.thumbUrl;
58 | this.tags = params.tags;
59 | this.priceRange = params.priceRange;
60 | this.ratings = params.ratings;
61 | this.ratingsCount = params.ratingsCount;
62 | this.deliveryTime = params.deliveryTime;
63 | this.deliveryFee = params.deliveryFee;
64 | }
65 |
66 | @ApiProperty({
67 | name: 'name',
68 | description: '이름',
69 | example: '우라나라에서 가장 맛있는 짜장면집',
70 | })
71 | name: string;
72 |
73 | @Transform(({ value }) => `/img/${value}`)
74 | @ApiProperty({
75 | name: 'thumbUrl',
76 | description: '썸네일 URL',
77 | example: '/img/thumb.png',
78 | })
79 | thumbUrl: string;
80 |
81 | @ApiProperty({
82 | name: 'tags',
83 | description: '레스토랑 태그들',
84 | example: ['신규', '세일중'],
85 | type: String,
86 | isArray: true,
87 | })
88 | tags: string[];
89 |
90 | @ApiProperty({
91 | name: 'priceRange',
92 | description: '가격대',
93 | example: 'cheap',
94 | })
95 | priceRange: RestaurantPriceRange;
96 |
97 | @ApiProperty({
98 | name: 'ratings',
99 | description: '별점',
100 | example: 4.89,
101 | })
102 | ratings: number;
103 |
104 | @ApiProperty({
105 | name: 'ratingsCount',
106 | description: '별점 갯수',
107 | example: 200,
108 | })
109 | ratingsCount: number;
110 |
111 | @ApiProperty({
112 | name: 'deliveryTime',
113 | description: '배달시간 (분)',
114 | example: 20,
115 | })
116 | deliveryTime: number;
117 |
118 | @ApiProperty({
119 | name: 'deliveryFee',
120 | description: '배달료',
121 | example: 3000,
122 | })
123 | deliveryFee: number;
124 | }
125 |
126 | export class RestaurantDetail extends Restaurant {
127 | constructor(params: RestaurantDetail) {
128 | super(params);
129 |
130 | this.detail = params.detail;
131 | this.products = params.products;
132 | }
133 |
134 | // 매장 설명
135 | @ApiProperty({
136 | name: 'detail',
137 | description: '레스토랑 설명',
138 | example: '오늘 주문하면 배송비 3000원 할인!',
139 | })
140 | detail: string;
141 |
142 | @ApiProperty({
143 | name: 'products',
144 | description: '판매 상품들',
145 | isArray: true,
146 | type: RestaurantProduct,
147 | })
148 | products: RestaurantProduct[];
149 | }
150 |
--------------------------------------------------------------------------------
/src/restaurant/restaurant.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Get,
4 | Post,
5 | Body,
6 | Request,
7 | Patch,
8 | Param,
9 | Delete,
10 | UseGuards,
11 | NotFoundException,
12 | Query,
13 | } from '@nestjs/common';
14 | import { RestaurantService } from './restaurant.service';
15 | import { CreateRestaurantDto } from './dto/create-restaurant.dto';
16 | import { UpdateRestaurantDto } from './dto/update-restaurant.dto';
17 | import { AccessTokenGuard } from '../auth/bearer-token.guard';
18 | import { PaginationDto } from '../core/dto/pagination.dto';
19 | import { Pagination } from '../core/entity/pagination.entity';
20 | import { Restaurant, RestaurantDetail } from './entities/restaurant.entity';
21 | import { Rating } from '../rating/entities/rating.entity';
22 | import { RatingService } from '../rating/rating.service';
23 | import { CreateRatingDto } from '../rating/dto/create-rating.dto';
24 | import {
25 | ApiBody,
26 | ApiExtraModels,
27 | ApiOkResponse,
28 | ApiOperation,
29 | ApiParam,
30 | ApiQuery,
31 | ApiResponse,
32 | ApiTags,
33 | } from '@nestjs/swagger';
34 | import { ApiPaginatedOkResponseDecorator } from '../core/decorator/api-paginated-ok-response.decorator';
35 | import { ApiBearerTokenHeader } from '../core/decorator/api-bearer-token-header';
36 |
37 | @ApiTags('restaurant')
38 | @ApiExtraModels(Pagination, Restaurant, Rating)
39 | @Controller('restaurant')
40 | export class RestaurantController {
41 | constructor(
42 | private readonly restaurantService: RestaurantService,
43 | private readonly ratingService: RatingService,
44 | ) {}
45 |
46 | @UseGuards(AccessTokenGuard)
47 | @Get()
48 | @ApiOperation({
49 | summary: '레스토랑을 Pagination합니다.',
50 | })
51 | @ApiPaginatedOkResponseDecorator(Restaurant, {
52 | description: '레스토랑 Pagination 결과값',
53 | })
54 | @ApiBearerTokenHeader()
55 | paginateRestaurants(
56 | @Query() paginationDto: PaginationDto,
57 | ): Pagination {
58 | return this.restaurantService.paginateRestaurants(paginationDto);
59 | }
60 |
61 | @UseGuards(AccessTokenGuard)
62 | @Get(':rid')
63 | @ApiOperation({
64 | summary: '레스토랑 정보 가져오기',
65 | })
66 | @ApiOkResponse({
67 | description: '레스토랑 정보 가져오기 성공',
68 | type: RestaurantDetail,
69 | })
70 | @ApiParam({
71 | name: 'rid',
72 | description: '레스토랑 ID',
73 | example: '1952a209-7c26-4f50-bc65-086f6e64dbbd',
74 | })
75 | @ApiBearerTokenHeader()
76 | getRestaurant(@Param('rid') id: string): RestaurantDetail {
77 | const restaurant = this.restaurantService.getRestaurantById(id);
78 |
79 | if (!restaurant) {
80 | throw new NotFoundException('존재하지 않는 ID입니다.');
81 | }
82 |
83 | return restaurant;
84 | }
85 |
86 | @UseGuards(AccessTokenGuard)
87 | @Get(':rid/rating')
88 | @ApiOperation({
89 | summary: '레스토랑 평점 Pagination',
90 | })
91 | @ApiPaginatedOkResponseDecorator(Rating, {
92 | description: 'Pagination 결과',
93 | })
94 | @ApiParam({
95 | name: 'rid',
96 | description: '레스토랑 ID',
97 | example: '1952a209-7c26-4f50-bc65-086f6e64dbbd',
98 | })
99 | @ApiBearerTokenHeader()
100 | paginateRestaurantRatings(
101 | @Param('rid') id: string,
102 | @Query() paginationDto: PaginationDto,
103 | ): Pagination {
104 | return this.ratingService.paginateRestaurantRatings(id, paginationDto);
105 | }
106 |
107 | @UseGuards(AccessTokenGuard)
108 | @Post(':rid/rating')
109 | @ApiOperation({
110 | summary: '레스토랑 평점 생성하기',
111 | })
112 | @ApiOkResponse({
113 | status: 201,
114 | description: '생성된 평점',
115 | type: Rating,
116 | })
117 | @ApiBody({
118 | type: CreateRatingDto,
119 | })
120 | @ApiParam({
121 | name: 'rid',
122 | description: '레스토랑 ID',
123 | example: '1952a209-7c26-4f50-bc65-086f6e64dbbd',
124 | })
125 | @ApiBearerTokenHeader()
126 | postRestaurantRating(
127 | @Param('rid') id: string,
128 | @Request() req,
129 | @Body() body: CreateRatingDto,
130 | ): Rating {
131 | return this.ratingService.createRestaurantRating(req.user, id, body);
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/restaurant/restaurant.module.ts:
--------------------------------------------------------------------------------
1 | import { Module } from '@nestjs/common';
2 | import { RestaurantService } from './restaurant.service';
3 | import { RestaurantController } from './restaurant.controller';
4 | import { CoreModule } from '../core/core.module';
5 | import { CacheModule } from '../cache/cache.module';
6 | import { AuthModule } from '../auth/auth.module';
7 | import { RatingModule } from '../rating/rating.module';
8 |
9 | @Module({
10 | imports: [CoreModule, CacheModule, RatingModule, AuthModule],
11 | controllers: [RestaurantController],
12 | providers: [RestaurantService],
13 | })
14 | export class RestaurantModule {}
15 |
--------------------------------------------------------------------------------
/src/restaurant/restaurant.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@nestjs/common';
2 | import { CreateRestaurantDto } from './dto/create-restaurant.dto';
3 | import { UpdateRestaurantDto } from './dto/update-restaurant.dto';
4 | import { PaginationDto } from '../core/dto/pagination.dto';
5 | import { Pagination } from '../core/entity/pagination.entity';
6 | import { Restaurant, RestaurantDetail } from './entities/restaurant.entity';
7 | import { CoreService } from '../core/core.service';
8 | import { CacheService } from '../cache/cache.service';
9 |
10 | @Injectable()
11 | export class RestaurantService {
12 | constructor(
13 | private coreService: CoreService,
14 | private cacheService: CacheService,
15 | ) {}
16 |
17 | paginateRestaurants(paginationDto: PaginationDto): Pagination {
18 | const result = this.coreService.paginate(
19 | this.cacheService.restaurants,
20 | paginationDto,
21 | );
22 |
23 | return {
24 | ...result,
25 | data: result.data.map((item) => new Restaurant(item)),
26 | };
27 | }
28 |
29 | getRestaurantById(id: string): RestaurantDetail {
30 | return this.cacheService.restaurants.find((item) => item.id === id);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/user/dto/basket-item.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsInt, IsNumber, IsString, ValidateNested } from 'class-validator';
2 | import { Product } from '../../product/entities/product.entity';
3 | import { ApiProperty } from '@nestjs/swagger';
4 | import { OrderProduct } from '../../order/entities/order-product-entity';
5 |
6 | export class BasketItemDto {
7 | @ApiProperty({
8 | name: 'productId',
9 | description: '상품 ID',
10 | example: '1952a209-7c26-4f50-bc65-086f6e64dbbd',
11 | })
12 | productId: string;
13 |
14 | @IsInt()
15 | @ApiProperty({
16 | name: 'count',
17 | description: '갯수',
18 | example: 10,
19 | })
20 | count: number;
21 | }
22 |
23 | export class BasketItemWithFullProductDto {
24 | @ApiProperty({
25 | name: 'product',
26 | description: '상품',
27 | type: Product,
28 | })
29 | product: OrderProduct;
30 |
31 | @ApiProperty({
32 | name: 'count',
33 | example: 10,
34 | description: '갯수',
35 | })
36 | count: number;
37 | }
38 |
--------------------------------------------------------------------------------
/src/user/dto/create-user.dto.ts:
--------------------------------------------------------------------------------
1 | import { IsEmail, IsNotEmpty, IsString, Length } from 'class-validator';
2 |
3 | export class CreateUserDto {
4 | @IsEmail(
5 | {},
6 | {
7 | message: '정확한 이메일을 입력해주세요.',
8 | },
9 | )
10 | username: string;
11 |
12 | @Length(6, 20, {
13 | message: '비밀번호는 6에서 20자를 입력해주세요.',
14 | })
15 | password: string;
16 | }
17 |
--------------------------------------------------------------------------------
/src/user/dto/patch-me-basket.dto.ts:
--------------------------------------------------------------------------------
1 | import { BasketItemDto } from './basket-item.dto';
2 | import { ApiProperty } from '@nestjs/swagger';
3 | import {
4 | ArrayNotEmpty,
5 | IsArray,
6 | IsNotEmpty,
7 | IsNumber,
8 | IsString,
9 | ValidateNested,
10 | } from 'class-validator';
11 | import { Type } from 'class-transformer';
12 |
13 | export class PatchMeBasketDtoBasket {
14 | @ApiProperty({
15 | name: 'productId',
16 | description: '상품의 ID',
17 | example: '1952a209-7c26-4f50-bc65-086f6e64dbbd',
18 | })
19 | @IsString()
20 | productId: string;
21 | @ApiProperty({
22 | name: 'count',
23 | description: '장바구니에 넣을 갯수',
24 | example: 10,
25 | })
26 | @IsNumber()
27 | count: number;
28 | }
29 |
30 | export class PatchMeBasketDto {
31 | @ApiProperty({
32 | name: 'basket',
33 | type: [PatchMeBasketDtoBasket],
34 | })
35 | @IsArray()
36 | @ValidateNested({ each: true })
37 | @Type(()=> PatchMeBasketDtoBasket)
38 | basket: PatchMeBasketDtoBasket[];
39 | }
40 |
--------------------------------------------------------------------------------
/src/user/dto/update-user.dto.ts:
--------------------------------------------------------------------------------
1 | import { PartialType } from '@nestjs/mapped-types';
2 | import { CreateUserDto } from './create-user.dto';
3 |
4 | export class UpdateUserDto extends PartialType(CreateUserDto) {}
5 |
--------------------------------------------------------------------------------
/src/user/entities/user.entity.interface.ts:
--------------------------------------------------------------------------------
1 | import { Product } from '../../product/entities/product.entity';
2 | import { OrderProduct } from '../../order/entities/order-product-entity';
3 |
4 | export interface IBasketItem {
5 | product: OrderProduct;
6 |
7 | count: number;
8 | }
9 |
--------------------------------------------------------------------------------
/src/user/entities/user.entity.ts:
--------------------------------------------------------------------------------
1 | import { BaseEntity } from '../../core/entity/base.entity';
2 | import { Exclude, Transform } from 'class-transformer';
3 | import { Product } from '../../product/entities/product.entity';
4 | import {
5 | BasketItemDto,
6 | BasketItemWithFullProductDto,
7 | } from '../dto/basket-item.dto';
8 | import { IBasketItem } from './user.entity.interface';
9 | import { ApiProperty } from '@nestjs/swagger';
10 |
11 | export class User extends BaseEntity {
12 | constructor(params: User) {
13 | super(params);
14 |
15 | Object.assign(this, params);
16 | }
17 |
18 | @ApiProperty({
19 | name: 'username',
20 | description: '사용자 이메일',
21 | example: 'test@codefactory.ai',
22 | })
23 | username: string;
24 |
25 | @Transform(({ value }) => `/img/${value}`)
26 | @ApiProperty({
27 | name: 'imageUrl',
28 | description: '프로필 이미지 URL',
29 | example: '/img/logo.png',
30 | })
31 | imageUrl: string;
32 |
33 | @Exclude()
34 | basket: BasketItemWithFullProductDto[];
35 |
36 | @Exclude()
37 | password: string;
38 | }
39 |
--------------------------------------------------------------------------------
/src/user/user.controller.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Controller,
3 | Get,
4 | Post,
5 | Body,
6 | Patch,
7 | Param,
8 | Request,
9 | Delete,
10 | UseGuards,
11 | } from '@nestjs/common';
12 | import { UserService } from './user.service';
13 | import { CreateUserDto } from './dto/create-user.dto';
14 | import { UpdateUserDto } from './dto/update-user.dto';
15 | import { AccessTokenGuard, BearerTokenGuard } from '../auth/bearer-token.guard';
16 | import { User } from './entities/user.entity';
17 | import { Product } from '../product/entities/product.entity';
18 | import {
19 | BasketItemDto,
20 | BasketItemWithFullProductDto,
21 | } from './dto/basket-item.dto';
22 | import { IBasketItem } from './entities/user.entity.interface';
23 | import { PatchMeBasketDto } from './dto/patch-me-basket.dto';
24 | import {
25 | ApiBody,
26 | ApiHeader,
27 | ApiOkResponse,
28 | ApiOperation,
29 | ApiResponse,
30 | ApiTags,
31 | } from '@nestjs/swagger';
32 | import { ApiBearerTokenHeader } from '../core/decorator/api-bearer-token-header';
33 |
34 | @ApiTags('user')
35 | @Controller('user')
36 | export class UserController {
37 | constructor(private readonly userService: UserService) {}
38 |
39 | @UseGuards(BearerTokenGuard)
40 | @Get('me')
41 | @ApiOperation({
42 | summary: '토큰을 기준으로 현재 사용자 정보를 가져옵니다.',
43 | })
44 | @ApiOkResponse({
45 | description: '사용자 가져오기 성공',
46 | type: User,
47 | })
48 | @ApiBearerTokenHeader()
49 | async getMe(@Request() req): Promise {
50 | return this.userService.findById(req.user.id);
51 | }
52 |
53 | @UseGuards(AccessTokenGuard)
54 | @Get('me/basket')
55 | @ApiOperation({
56 | summary: '현재 사용자의 장바구니를 가져옵니다.',
57 | })
58 | @ApiOkResponse({
59 | description: '장바구니 가져오기 성공',
60 | type: BasketItemWithFullProductDto,
61 | isArray: true,
62 | })
63 | @ApiBearerTokenHeader()
64 | async getMeBasket(@Request() req): Promise {
65 | return this.userService.getBasket(req.user.id);
66 | }
67 |
68 | @UseGuards(AccessTokenGuard)
69 | @Patch('me/basket')
70 | @ApiOperation({
71 | summary: '현재 사용자의 장바구니를 업데이트합니다.',
72 | })
73 | @ApiOkResponse({
74 | description: '장바구니 업데이트 성공',
75 | type: BasketItemWithFullProductDto,
76 | isArray: true,
77 | })
78 | @ApiBody({
79 | type: PatchMeBasketDto,
80 | })
81 | async patchMeBasket(
82 | @Request() req,
83 | @Body() body: PatchMeBasketDto,
84 | ): Promise {
85 | return this.userService.addToBasket(req.user.id, body.basket);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/user/user.module.ts:
--------------------------------------------------------------------------------
1 | import { forwardRef, Module } from '@nestjs/common';
2 | import { UserService } from './user.service';
3 | import { UserController } from './user.controller';
4 | import { AuthModule } from '../auth/auth.module';
5 | import { CacheModule } from '../cache/cache.module';
6 |
7 | @Module({
8 | controllers: [UserController],
9 | providers: [UserService],
10 | exports: [UserService],
11 | imports: [forwardRef(() => AuthModule), CacheModule],
12 | })
13 | export class UserModule {}
14 |
--------------------------------------------------------------------------------
/src/user/user.service.ts:
--------------------------------------------------------------------------------
1 | import { BadRequestException, Injectable } from '@nestjs/common';
2 | import { CreateUserDto } from './dto/create-user.dto';
3 | import { UpdateUserDto } from './dto/update-user.dto';
4 | import { User } from './entities/user.entity';
5 | import { v4 as uuidv4 } from 'uuid';
6 | import { Product } from '../product/entities/product.entity';
7 | import { CacheService } from '../cache/cache.service';
8 | import {
9 | BasketItemDto,
10 | BasketItemWithFullProductDto,
11 | } from './dto/basket-item.dto';
12 | import { IBasketItem } from './entities/user.entity.interface';
13 |
14 | @Injectable()
15 | export class UserService {
16 | constructor(private cacheService: CacheService) {}
17 |
18 | async findById(id: string) {
19 | return this.cacheService.users.find((user) => user.id);
20 | }
21 |
22 | async findByUsername(username: string): Promise {
23 | return this.cacheService.users.find((user) => user.username === username);
24 | }
25 |
26 | async createUser({ username, password }: CreateUserDto) {
27 | const hasUser = this.cacheService.users.find(
28 | (user) => user.username === username,
29 | );
30 |
31 | if (hasUser) {
32 | throw new BadRequestException('이미 가입한 사용자입니다.');
33 | }
34 |
35 | if (password.length < 6 || password.length > 20) {
36 | throw new BadRequestException(
37 | '비밀번호는 6자 이상 20자 이하로 입력해주세요.',
38 | );
39 | }
40 |
41 | const newUser = new User({
42 | id: uuidv4(),
43 | username,
44 | password,
45 | imageUrl: '/logo/codefactory_logo.png',
46 | basket: [],
47 | });
48 |
49 | this.cacheService.users = [...this.cacheService.users, newUser];
50 |
51 | return {
52 | id: newUser.id,
53 | };
54 | }
55 |
56 | getBasket(userId: string): BasketItemWithFullProductDto[] {
57 | return this.cacheService.users.find((x) => x.id === userId).basket;
58 | }
59 |
60 | addToBasket(
61 | userId: string,
62 | products: BasketItemDto[],
63 | ): BasketItemWithFullProductDto[] {
64 | const user = this.cacheService.users.find((x) => x.id === userId);
65 |
66 | user.basket = this._mapBasketDtoToProduct(products);
67 |
68 | return user.basket;
69 | }
70 |
71 | _mapBasketDtoToProduct(products: BasketItemDto[]) {
72 | const allProducts = this.cacheService.products;
73 |
74 | return products.map((dto) => ({
75 | product: allProducts.find((product) => product.id === dto.productId),
76 | count: dto.count,
77 | })).filter(item => item.count > 0);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/test/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { Test, TestingModule } from '@nestjs/testing';
2 | import { INestApplication } from '@nestjs/common';
3 | import * as request from 'supertest';
4 | import { AppModule } from './../src/app.module';
5 |
6 | describe('AppController (e2e)', () => {
7 | let app: INestApplication;
8 |
9 | beforeEach(async () => {
10 | const moduleFixture: TestingModule = await Test.createTestingModule({
11 | imports: [AppModule],
12 | }).compile();
13 |
14 | app = moduleFixture.createNestApplication();
15 | await app.init();
16 | });
17 |
18 | it('/ (GET)', () => {
19 | return request(app.getHttpServer())
20 | .get('/')
21 | .expect(200)
22 | .expect('Hello World!');
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/jest-e2e.json:
--------------------------------------------------------------------------------
1 | {
2 | "moduleFileExtensions": ["js", "json", "ts"],
3 | "rootDir": ".",
4 | "testEnvironment": "node",
5 | "testRegex": ".e2e-spec.ts$",
6 | "transform": {
7 | "^.+\\.(t|j)s$": "ts-jest"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "emitDecoratorMetadata": true,
7 | "experimentalDecorators": true,
8 | "allowSyntheticDefaultImports": true,
9 | "target": "es2017",
10 | "sourceMap": true,
11 | "outDir": "./dist",
12 | "baseUrl": "./",
13 | "incremental": true,
14 | "skipLibCheck": true,
15 | "strictNullChecks": false,
16 | "noImplicitAny": false,
17 | "strictBindCallApply": false,
18 | "forceConsistentCasingInFileNames": false,
19 | "noFallthroughCasesInSwitch": false
20 | }
21 | }
22 |
--------------------------------------------------------------------------------