├── .all-contributorsrc
├── .github
└── ISSUE_TEMPLATE
│ ├── ---------.md
│ └── ------.md
├── .vscode
└── settings.json
├── README.md
└── WeathyServer
├── .eslintrc.json
├── .gitignore
├── .prettierrc.json
├── app.js
├── bin
└── www
├── controllers
├── authController.js
├── calendarController.js
├── clothesController.js
├── userController.js
├── weatherController.js
└── weathyController.js
├── models
├── category.js
├── climate.js
├── climateMessage.js
├── clothes.js
├── dailyWeather.js
├── hourlyWeather.js
├── index.js
├── location.js
├── token.js
├── user.js
├── weathy.js
└── weathyClothes.js
├── modules
├── exception.js
├── logger.js
├── statusCode.js
├── swagger.js
├── tokenMiddleware.js
└── uploadFile.js
├── package-lock.json
├── package.json
├── public
└── stylesheets
│ └── style.css
├── routes
├── auth.js
├── index.js
├── users.js
├── weather.js
└── weathy.js
├── services
├── calendarService.js
├── climateService.js
├── clothesService.js
├── index.js
├── locationService.js
├── tokenService.js
├── userService.js
├── weatherService.js
└── weathyService.js
├── tests
├── modules
│ ├── logger.spec.js
│ └── tokenMiddleware.spec.js
├── services
│ ├── calendarService.spec.js
│ ├── climateService.spec.js
│ ├── clothesService.spec.js
│ ├── locationService.spec.js
│ ├── tokenService.spec.js
│ ├── userService.spec.js
│ ├── weatherService.spec.js
│ └── weathyService.spec.js
└── utils
│ ├── dateUtils.spec.js
│ └── tokenUtils.spec.js
├── utils
├── dateUtils.js
└── tokenUtils.js
└── views
├── error.jade
├── index.jade
└── layout.jade
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "contributors": [
8 | {
9 | "login": "yxxshin",
10 | "name": "Yeon Sang Shin",
11 | "avatar_url": "https://avatars0.githubusercontent.com/u/63148508?v=4",
12 | "profile": "https://github.com/yxxshin",
13 | "contributions": [
14 | "code"
15 | ]
16 | },
17 | {
18 | "login": "seonuk",
19 | "name": "seonuk",
20 | "avatar_url": "https://avatars3.githubusercontent.com/u/22928068?v=4",
21 | "profile": "https://github.com/seonuk",
22 | "contributions": [
23 | "code"
24 | ]
25 | },{
26 | "login": "dshyun0226",
27 | "name": "Jahyun Kim",
28 | "avatar_url": "https://avatars3.githubusercontent.com/u/8098698?v=4",
29 | "profile": "https://github.com/dshyun0226",
30 | "contributions": [
31 | "code"
32 | ]
33 | }
34 | ],
35 | "contributorsPerLine": 7,
36 | "projectName": "WeathyServer",
37 | "projectOwner": "TeamWeathy",
38 | "repoType": "github",
39 | "repoHost": "https://github.com",
40 | "skipCi": true
41 | }
42 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/---------.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 새로운 기능 개발
3 | about: 신규 혹은 추가 기능을 개발합니다.
4 | title: ''
5 | labels: IMPLEMENTATION
6 | assignees: ''
7 |
8 | ---
9 |
10 | # 제목
11 |
12 | - 신규 기능의 개발 배경 및 효과에 대해서 설명해주세요.
13 | - e.g., 로그인 API를 신규 개발
14 | - e.g., 참고: [로그인 API 명세](link)
15 |
16 | ## 작업 브랜치
17 | - `master` <- `dev`
18 |
19 | ## 확인 사항
20 | - [ ] PR 생성 전 확인이 필요한 작업들을 작성해주세요.
21 | - [ ] e.g., [로그인 API 명세](link)의 "개발 여부" 업데이트
22 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/------.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 버그 리포트
3 | about: 올바르게 작동하지 않는 기능을 제보 및 수정합니다.
4 | title: ''
5 | labels: BUG
6 | assignees: ''
7 |
8 | ---
9 |
10 | # 제목
11 |
12 | ## 개요
13 | ### 발생 환경
14 | - 버그가 발생한 환경에 대해서 자세히 설명해주세요.
15 | - e.g., macOS Catalina, Postman 7.36.0
16 |
17 | ### 기대 내용
18 | - 재현 방법을 최대한 자세히 설명해주세요.
19 | - e.g., 헤더가 "Accept: Application/json" 를 포함합니다.
20 | - 정상적으로 작동했을 때의 상황을 작성해주세요.
21 | - e.g., /users/3으로 GET 요청을 보냈을 때, 반환되는 사용자의 정보에 id가 3인 사용자의 이름이 포함되어야 합니다.
22 |
23 | ### 현재 내용
24 | - 현재 상황을 작성해주세요.
25 | - e.g., 500에러가 발생합니다.
26 |
27 | ## 레퍼런스
28 | - 버그가 발생했을 때의 스크린샷, 로그 등을 첨부해주세요.
29 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | // Set the default
3 | "editor.formatOnSave": false,
4 | // Enable per-language
5 | "[javascript]": {
6 | "editor.formatOnSave": true
7 | },
8 | "editor.codeActionsOnSave": {
9 | // For ESLint
10 | "source.fixAll.eslint": true
11 | }
12 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WeathyServer
2 |
3 |
4 | [](#contributors-)
5 |
6 |
7 | ## 나에게 돌아오는 맞춤 서비스, Weathy 🌤
8 |
9 |
10 | ## Contributors ✨
11 |
12 |
13 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | ### dependencies module (package.json)
29 | #### Dev module:
30 | ```json
31 | "devDependencies": {
32 | "decache": "^4.6.0",
33 | "eslint": "^7.16.0",
34 | "eslint-config-prettier": "^7.1.0",
35 | "eslint-plugin-prettier": "^3.3.0",
36 | "mocha": "^8.2.1",
37 | "prettier": "2.2.1",
38 | "swagger-jsdoc": "^6.0.0",
39 | "swagger-ui-express": "^4.1.6"
40 | }
41 | ```
42 |
43 | #### module:
44 | ```json
45 | "dependencies": {
46 | "app-root-path": "^3.0.0",
47 | "cookie-parser": "~1.4.4",
48 | "crypto-random-string": "^3.3.0",
49 | "dayjs": "^1.10.2",
50 | "debug": "~2.6.9",
51 | "express": "~4.16.1",
52 | "http-errors": "~1.6.3",
53 | "jade": "~1.11.0",
54 | "morgan": "~1.9.1",
55 | "mysql2": "^2.2.5",
56 | "request": "^2.88.2",
57 | "request-promise": "^4.2.6",
58 | "sequelize": "^6.3.5",
59 | "sequelize-cli": "^6.2.0",
60 | "winston": "^3.3.3",
61 | "winston-daily-rotate-file": "^4.5.0"
62 | }
63 | ```
64 |
65 | ### ER Diagram
66 |
67 |
68 |
69 | ### 서버 아키텍쳐
70 |
71 |
72 |
73 | ### 핵심 기능 설명
74 | open weather api를 사용해서 날씨를 수집하고 해당 날씨에 대해 사용자가 자신의 옷차림과 상태를 기록한다.
75 | 기록된 데이터를 바탕으로 오늘 날씨와 비슷한 날씨의 기록 데이터를 가져와 날씨 판단에 있어 비교척도를 제공해준다.
76 |
77 | ### 팀별 역할 분담
78 | - 신연상 : API 위키 문서 관리, Login, User, Clothes API 개발 및 테스트코드 작성
79 | - 최선욱 : Open Weather Batch 프로그램 개발, Weathy API 개발 및 테스트코드 작성
80 | - 김자현 : 스키마 설계, DB 권한 및 계정 , Calendar, Weather API 개발 및 테스트코드 작성
81 | 이외의 API 설계, 코드 리뷰 등은 함께 하였음!
82 |
83 | ### API 명세서
84 | [API 명세서 링크](https://github.com/TeamWeathy/WeathyServer/wiki)
85 |
--------------------------------------------------------------------------------
/WeathyServer/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | // 코드 포맷을 prettier로 설정
3 | "plugins": [
4 | "prettier"
5 | ],
6 | "env": {
7 | // "express":true,
8 | "node": true,
9 | "jest": true,
10 | "mocha": true,
11 | "browser": false,
12 | "commonjs": true,
13 | "es2021": true
14 | },
15 | "extends": [
16 | "eslint:recommended",
17 | "plugin:prettier/recommended"
18 | ],
19 | "parserOptions": {
20 | "ecmaVersion": 12,
21 | "sourceType": "script"
22 | },
23 | "ignorePatterns": [
24 | "node_modules/"
25 | ],
26 | // ESLint 룰을 설정
27 | "rules": {
28 | // prettier에 맞게 룰을 설정
29 | "prettier/prettier": "error"
30 | },
31 | "globals": {
32 | "process": false,
33 | "__dirname": false
34 | }
35 | }
--------------------------------------------------------------------------------
/WeathyServer/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.toptal.com/developers/gitignore/api/macos,node,vscode
2 | # Edit at https://www.toptal.com/developers/gitignore?templates=macos,node,vscode
3 |
4 | ### macOS ###
5 | # General
6 | .DS_Store
7 | .AppleDouble
8 | .LSOverride
9 |
10 | # Icon must end with two \r
11 | Icon
12 |
13 |
14 | # Thumbnails
15 | ._*
16 |
17 | # Files that might appear in the root of a volume
18 | .DocumentRevisions-V100
19 | .fseventsd
20 | .Spotlight-V100
21 | .TemporaryItems
22 | .Trashes
23 | .VolumeIcon.icns
24 | .com.apple.timemachine.donotpresent
25 |
26 | # Directories potentially created on remote AFP share
27 | .AppleDB
28 | .AppleDesktop
29 | Network Trash Folder
30 | Temporary Items
31 | .apdisk
32 |
33 | ### Node ###
34 | # Logs
35 | logs
36 | *.log
37 | npm-debug.log*
38 | yarn-debug.log*
39 | yarn-error.log*
40 | lerna-debug.log*
41 |
42 | # Diagnostic reports (https://nodejs.org/api/report.html)
43 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
44 |
45 | # Runtime data
46 | pids
47 | *.pid
48 | *.seed
49 | *.pid.lock
50 |
51 | # Directory for instrumented libs generated by jscoverage/JSCover
52 | lib-cov
53 |
54 | # Coverage directory used by tools like istanbul
55 | coverage
56 | *.lcov
57 |
58 | # nyc test coverage
59 | .nyc_output
60 |
61 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
62 | .grunt
63 |
64 | # Bower dependency directory (https://bower.io/)
65 | bower_components
66 |
67 | # node-waf configuration
68 | .lock-wscript
69 |
70 | # Compiled binary addons (https://nodejs.org/api/addons.html)
71 | build/Release
72 |
73 | # Dependency directories
74 | node_modules/
75 | jspm_packages/
76 |
77 | # TypeScript v1 declaration files
78 | typings/
79 |
80 | # TypeScript cache
81 | *.tsbuildinfo
82 |
83 | # Optional npm cache directory
84 | .npm
85 |
86 | # Optional eslint cache
87 | .eslintcache
88 |
89 | # Microbundle cache
90 | .rpt2_cache/
91 | .rts2_cache_cjs/
92 | .rts2_cache_es/
93 | .rts2_cache_umd/
94 |
95 | # Optional REPL history
96 | .node_repl_history
97 |
98 | # Output of 'npm pack'
99 | *.tgz
100 |
101 | # Yarn Integrity file
102 | .yarn-integrity
103 |
104 | # dotenv environment variables file
105 | .env
106 | .env.test
107 | .env*.local
108 |
109 | # parcel-bundler cache (https://parceljs.org/)
110 | .cache
111 | .parcel-cache
112 |
113 | # Next.js build output
114 | .next
115 |
116 | # Nuxt.js build / generate output
117 | .nuxt
118 | dist
119 |
120 | # Gatsby files
121 | .cache/
122 | # Comment in the public line in if your project uses Gatsby and not Next.js
123 | # https://nextjs.org/blog/next-9-1#public-directory-support
124 | # public
125 |
126 | # vuepress build output
127 | .vuepress/dist
128 |
129 | # Serverless directories
130 | .serverless/
131 |
132 | # FuseBox cache
133 | .fusebox/
134 |
135 | # DynamoDB Local files
136 | .dynamodb/
137 |
138 | # TernJS port file
139 | .tern-port
140 |
141 | # Stores VSCode versions used for testing VSCode extensions
142 | .vscode-test
143 |
144 | ### vscode ###
145 | .vscode/*
146 |
147 | !.vscode/tasks.json
148 | !.vscode/launch.json
149 | !.vscode/extensions.json
150 | *.code-workspace
151 |
152 | # End of https://www.toptal.com/developers/gitignore/api/macos,node,vscode
153 |
154 | config/
--------------------------------------------------------------------------------
/WeathyServer/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "none",
3 | "tabWidth": 4,
4 | "semi": true,
5 | "singleQuote": true
6 | }
7 |
--------------------------------------------------------------------------------
/WeathyServer/app.js:
--------------------------------------------------------------------------------
1 | const createError = require('http-errors');
2 | const express = require('express');
3 | const path = require('path');
4 | const cookieParser = require('cookie-parser');
5 | const morgan = require('morgan');
6 | const sc = require('./modules/statusCode');
7 |
8 | const indexRouter = require('./routes/index');
9 | const authRouter = require('./routes/auth');
10 | const usersRouter = require('./routes/users');
11 | const weathyRouter = require('./routes/weathy');
12 | const weatherRouter = require('./routes/weather');
13 |
14 | const { swaggerUi, specs } = require('./modules/swagger');
15 | const exception = require('./modules/exception');
16 | const logger = require('winston');
17 |
18 | const app = express();
19 |
20 | // set etag false
21 | // 동적 요청에 대한 응답을 보낼 때 etag 생성을 하지 않도록 설정
22 | app.set("etag", false);
23 |
24 | // 정적 요청에 대한 응답을 보낼 때 etag 생성을 하지 않도록 설정
25 | const options = { etag: false };
26 | app.use(express.static("public", options));
27 |
28 | // view engine setup
29 | app.set('views', path.join(__dirname, 'views'));
30 | app.set('view engine', 'jade');
31 |
32 | const db = require('./models/index.js');
33 |
34 |
35 | app.use(morgan('dev'));
36 | app.use(express.json());
37 | app.use(express.urlencoded({ extended: false }));
38 | app.use(cookieParser());
39 | app.use(express.static(path.join(__dirname, 'public')));
40 |
41 | app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
42 | app.use('/', indexRouter);
43 | app.use('/auth', authRouter);
44 | app.use('/users', usersRouter);
45 | app.use('/weather', weatherRouter);
46 | app.use('/weathy', weathyRouter);
47 |
48 | // catch 404 and forward to error handler
49 | app.use(function (req, res, next) {
50 | next(createError(404));
51 | });
52 |
53 | // error handler
54 | app.use(function (err, req, res, next) {
55 | res.status(err.status || 500).json({
56 | message: err.message
57 | });
58 | });
59 |
60 | module.exports = app;
61 |
--------------------------------------------------------------------------------
/WeathyServer/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var app = require('../app');
8 | var debug = require('debug')('weathyserver:server');
9 | var http = require('http');
10 |
11 | /**
12 | * Get port from environment and store in Express.
13 | */
14 |
15 | var port = normalizePort(process.env.PORT || '3000');
16 | app.set('port', port);
17 |
18 | /**
19 | * Create HTTP server.
20 | */
21 |
22 | var server = http.createServer(app);
23 |
24 | /**
25 | * Listen on provided port, on all network interfaces.
26 | */
27 |
28 | server.listen(port);
29 | server.on('error', onError);
30 | server.on('listening', onListening);
31 |
32 | /**
33 | * Normalize a port into a number, string, or false.
34 | */
35 |
36 | function normalizePort(val) {
37 | var port = parseInt(val, 10);
38 |
39 | if (isNaN(port)) {
40 | // named pipe
41 | return val;
42 | }
43 |
44 | if (port >= 0) {
45 | // port number
46 | return port;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | /**
53 | * Event listener for HTTP server "error" event.
54 | */
55 |
56 | function onError(error) {
57 | if (error.syscall !== 'listen') {
58 | throw error;
59 | }
60 |
61 | var bind = typeof port === 'string' ? 'Pipe ' + port : 'Port ' + port;
62 |
63 | // handle specific listen errors with friendly messages
64 | switch (error.code) {
65 | case 'EACCES':
66 | console.error(bind + ' requires elevated privileges');
67 | process.exit(1);
68 | break;
69 | case 'EADDRINUSE':
70 | console.error(bind + ' is already in use');
71 | process.exit(1);
72 | break;
73 | default:
74 | throw error;
75 | }
76 | }
77 |
78 | /**
79 | * Event listener for HTTP server "listening" event.
80 | */
81 |
82 | function onListening() {
83 | var addr = server.address();
84 | var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port;
85 | debug('Listening on ' + bind);
86 | }
87 |
--------------------------------------------------------------------------------
/WeathyServer/controllers/authController.js:
--------------------------------------------------------------------------------
1 | const createError = require('http-errors');
2 | const { Token } = require('../models');
3 | const exception = require('../modules/exception');
4 | const statusCode = require('../modules/statusCode');
5 | const { userService } = require('../services');
6 |
7 | module.exports = {
8 | login: async (req, res, next) => {
9 | const { uuid } = req.body;
10 |
11 | if (!uuid) {
12 | next(createError(400));
13 | }
14 | try {
15 | const user = await userService.getUserByAccount(uuid);
16 | const userToken = await Token.findOne({
17 | where: { user_id: user.id }
18 | });
19 | const token = userToken.token;
20 | res.locals.tokenValue = token;
21 | res.status(statusCode.OK).json({
22 | user: {
23 | id: user.id,
24 | nickname: user.nickname
25 | },
26 | token: token,
27 | message: '로그인 성공'
28 | });
29 | next();
30 | } catch (error) {
31 | switch (error.message) {
32 | case exception.NO_USER:
33 | next(createError(401));
34 | break;
35 | default:
36 | next(createError(500));
37 | }
38 | }
39 | }
40 | };
41 |
--------------------------------------------------------------------------------
/WeathyServer/controllers/calendarController.js:
--------------------------------------------------------------------------------
1 | const createError = require('http-errors');
2 | const statusCode = require('../modules/statusCode');
3 | const dateUtils = require('../utils/dateUtils');
4 | const { calendarService } = require('../services');
5 |
6 | module.exports = {
7 | getCalendarOverviews: async (req, res, next) => {
8 | const { userId } = req.params;
9 | const { start, end } = req.query;
10 |
11 | if (!start || !end) {
12 | return next(createError(400));
13 | }
14 |
15 | try {
16 | const validCalendarOverviewList = await calendarService.getValidCalendarOverviewList(
17 | userId,
18 | start,
19 | end
20 | );
21 | let calendarOverviewList = [];
22 | let curDay = new Date(start);
23 | let endDay = new Date(end);
24 | let pos = 0;
25 | while (curDay <= endDay) {
26 | if (
27 | pos < validCalendarOverviewList.length &&
28 | validCalendarOverviewList[pos].date ==
29 | dateUtils.formatDate(curDay)
30 | ) {
31 | calendarOverviewList.push(validCalendarOverviewList[pos++]);
32 | } else {
33 | calendarOverviewList.push(null);
34 | }
35 | curDay.setDate(curDay.getDate() + 1);
36 | }
37 | res.status(statusCode.OK).json({
38 | calendarOverviewList,
39 | message: '캘린더 월 정보 조회 성공'
40 | });
41 | next();
42 | } catch (error) {
43 | switch (error.message) {
44 | default:
45 | return next(createError(500));
46 | }
47 | }
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/WeathyServer/controllers/clothesController.js:
--------------------------------------------------------------------------------
1 | const createError = require('http-errors');
2 | const exception = require('../modules/exception');
3 | const statusCode = require('../modules/statusCode');
4 | const { clothesService } = require('../services');
5 | const { unionTwoCloset } = require('../services/clothesService');
6 |
7 | module.exports = {
8 | getClothes: async (req, res, next) => {
9 | const { userId } = req.params;
10 | const { weathy_id: weathyId } = req.query;
11 |
12 | if (!userId) {
13 | next(createError(400));
14 | }
15 |
16 | try {
17 | let closet = await clothesService.getClothesByUserId(userId);
18 |
19 | if (weathyId) {
20 | const weathyCloset = await clothesService.getClothesByWeathyId(
21 | userId,
22 | weathyId
23 | );
24 | closet = unionTwoCloset(closet, weathyCloset);
25 | }
26 | res.status(statusCode.OK).json({
27 | closet: closet,
28 | message: '옷 정보 조회 성공'
29 | });
30 | next();
31 | } catch (error) {
32 | switch (error.message) {
33 | default:
34 | next(createError(500));
35 | }
36 | }
37 | },
38 | addClothes: async (req, res, next) => {
39 | const { userId } = req.params;
40 | const { weathy_id: weathyId } = req.query;
41 | const { category, name } = req.body;
42 |
43 | if (!userId || !category || !name) {
44 | next(createError(400));
45 | }
46 |
47 | try {
48 | await clothesService.addClothesByUserId(userId, category, name);
49 | let closet = await clothesService.getClothesByUserId(userId);
50 | if (weathyId) {
51 | const weathyCloset = await clothesService.getClothesByWeathyId(
52 | userId,
53 | weathyId
54 | );
55 | closet = unionTwoCloset(closet, weathyCloset);
56 | }
57 |
58 | res.status(statusCode.OK).json({
59 | closet,
60 | message: '옷 추가 성공'
61 | });
62 | next();
63 | } catch (error) {
64 | console.log(error.message);
65 | switch (error.message) {
66 | case exception.ALREADY_CLOTHES:
67 | next(createError(403));
68 | break;
69 | default:
70 | next(createError(500));
71 | }
72 | }
73 | },
74 | deleteClothes: async (req, res, next) => {
75 | const { userId } = req.params;
76 | const { clothes } = req.body;
77 |
78 | if (!userId || !clothes) {
79 | next(createError(400));
80 | }
81 |
82 | try {
83 | const closet = await clothesService.deleteClothesByUserId(
84 | userId,
85 | clothes
86 | );
87 |
88 | res.status(statusCode.OK).json({
89 | closet: closet,
90 | message: '옷 삭제 성공'
91 | });
92 | next();
93 | } catch (error) {
94 | console.log(error.message);
95 | switch (error.message) {
96 | case exception.NO_CLOTHES:
97 | next(createError(400));
98 | break;
99 | case exception.NOT_AUTHORIZED_CLOTHES:
100 | next(createError(403));
101 | break;
102 | default:
103 | console.log(error.message);
104 | next(createError(500));
105 | }
106 | }
107 | }
108 | };
109 |
--------------------------------------------------------------------------------
/WeathyServer/controllers/userController.js:
--------------------------------------------------------------------------------
1 | const createError = require('http-errors');
2 | const exception = require('../modules/exception');
3 | const statusCode = require('../modules/statusCode');
4 | const { userService, tokenService } = require('../services');
5 |
6 | module.exports = {
7 | createUser: async (req, res, next) => {
8 | const { uuid, nickname } = req.body;
9 |
10 | if (!uuid || !nickname) {
11 | next(createError(400));
12 | }
13 |
14 | try {
15 | const user = await userService.createUserByUuid(uuid, nickname);
16 | const token = await tokenService.createTokenOfUser(user.id);
17 |
18 | return res.status(statusCode.OK).json({
19 | user: {
20 | id: user.id,
21 | nickname: user.nickname
22 | },
23 | token: token,
24 | message: '유저 생성 성공'
25 | });
26 | } catch (error) {
27 | switch (error.message) {
28 | case exception.ALREADY_USER:
29 | next(createError(400));
30 | break;
31 | default:
32 | next(createError(500));
33 | }
34 | }
35 | },
36 | modifyUser: async (req, res, next) => {
37 | const token = res.locals.tokenValue;
38 | const { userId } = req.params;
39 | const { nickname } = req.body;
40 |
41 | if (!userId || !nickname) {
42 | next(createError(400));
43 | }
44 |
45 | try {
46 | const user = await userService.modifyUserById(userId, nickname);
47 |
48 | res.status(statusCode.OK).json({
49 | user: {
50 | id: user.id,
51 | nickname: user.nickname
52 | },
53 | token: token,
54 | message: '유저 닉네임 변경 성공'
55 | });
56 | next();
57 | } catch (error) {
58 | switch (error.message) {
59 | case exception.INVALID_TOKEN:
60 | case exception.EXPIRED_TOKEN:
61 | case exception.MISMATCH_TOKEN:
62 | next(createError(401));
63 | break;
64 | default:
65 | next(createError(500));
66 | }
67 | }
68 | }
69 | };
70 |
--------------------------------------------------------------------------------
/WeathyServer/controllers/weatherController.js:
--------------------------------------------------------------------------------
1 | const createError = require('http-errors');
2 | const exception = require('../modules/exception');
3 | const statusCode = require('../modules/statusCode');
4 | const dateUtils = require('../utils/dateUtils');
5 | const { locationService, weatherService } = require('../services');
6 |
7 | module.exports = {
8 | getWeatherByLocation: async (req, res, next) => {
9 | const { lat, lon } = req.query;
10 | let { code, date } = req.query;
11 |
12 | if (!date) {
13 | return next(createError(400));
14 | } else if (!code && (!lat || !lon)) {
15 | return next(createError(400));
16 | } else if (!code) {
17 | try {
18 | code = await locationService.getCode(lat, lon);
19 | } catch (error) {
20 | switch (error.message) {
21 | case exception.INVALID_LOCATION:
22 | return next(createError(204));
23 | default:
24 | return next(createError(500));
25 | }
26 | }
27 | }
28 |
29 | let time = date.split('T')[1] ? date.split('T')[1] : null;
30 | date = date.split('T')[0];
31 | if (!date) {
32 | return next(createError(400));
33 | }
34 |
35 | try {
36 | const overviewWeather = await weatherService.getOverviewWeather(
37 | code,
38 | date,
39 | time,
40 | dateUtils.format12
41 | );
42 | if (!overviewWeather) {
43 | throw Error(exception.NO_DATA);
44 | }
45 | return res.status(statusCode.OK).json({
46 | overviewWeather,
47 | message: '실시간 날씨 정보 반환 성공'
48 | });
49 | } catch (error) {
50 | switch (error.message) {
51 | case exception.NO_DATA:
52 | return next(createError(204));
53 | default:
54 | return next(createError(500));
55 | }
56 | }
57 | },
58 | getHourlyWeatherForecast: async (req, res, next) => {
59 | let { code, date } = req.query;
60 | if (!code || !date) {
61 | return next(createError(400));
62 | }
63 |
64 | let time = date.split('T')[1];
65 | date = date.split('T')[0];
66 | if (!date || !time) {
67 | return next(createError(400));
68 | }
69 |
70 | let hourlyWeatherList = [];
71 | for (let i = 0; i < 24; ++i) {
72 | hourlyWeatherList.push(
73 | await weatherService.getHourlyWeather(
74 | code,
75 | date,
76 | time,
77 | dateUtils.format24
78 | )
79 | );
80 | const { next_date, next_time } = dateUtils.getNextHour(date, time);
81 | date = next_date;
82 | time = next_time;
83 | }
84 | return res.status(statusCode.OK).json({
85 | hourlyWeatherList,
86 | message: '시간 별 날씨 조회 성공'
87 | });
88 | },
89 | getDailyWeatherForecast: async (req, res, next) => {
90 | let { code, date } = req.query;
91 | if (!code || !date) {
92 | return next(createError(400));
93 | }
94 |
95 | date = date.split('T')[0];
96 | if (!date) {
97 | return next(createError(400));
98 | }
99 |
100 | let dailyWeatherList = [];
101 | for (let i = 0; i < 7; ++i) {
102 | dailyWeatherList.push(
103 | await weatherService.getDailyWeatherWithClimateIconId(
104 | code,
105 | date
106 | )
107 | );
108 | date = dateUtils.getNextDay(date);
109 | }
110 | return res.status(statusCode.OK).json({
111 | dailyWeatherList,
112 | message: '일자 별 날씨 조회 성공'
113 | });
114 | },
115 | getExtraDailyWeather: async (req, res, next) => {
116 | let { code, date } = req.query;
117 | if (!code || !date) {
118 | return next(createError(400));
119 | }
120 |
121 | date = date.split('T')[0];
122 | if (!date) {
123 | return next(createError(400));
124 | }
125 |
126 | try {
127 | let extraWeather = await weatherService.getExtraDailyWeather(
128 | code,
129 | date
130 | );
131 | return res.status(statusCode.OK).json({
132 | extraWeather,
133 | message: '상세 날씨 조회 성공'
134 | });
135 | } catch (error) {
136 | switch (error.message) {
137 | case exception.NO_DATA:
138 | return res.status(statusCode.NO_CONTENT).json({
139 | extraWeather: null,
140 | message: '날씨 정보를 찾을 수 없음'
141 | });
142 | default:
143 | return next(createError(400));
144 | }
145 | }
146 | },
147 | getWeathersByKeyword: async (req, res, next) => {
148 | let { keyword, date } = req.query;
149 | if (!keyword || !date) {
150 | return next(createError(400));
151 | }
152 |
153 | let time = date.split('T')[1] || null;
154 | date = date.split('T')[0];
155 |
156 | if (!date) {
157 | return next(createError(400));
158 | }
159 |
160 | try {
161 | const overviewWeatherList = await weatherService.getOverviewWeathers(
162 | keyword,
163 | date,
164 | time,
165 | dateUtils.format12
166 | );
167 |
168 | return res.status(statusCode.OK).json({
169 | overviewWeatherList,
170 | message: '검색 성공'
171 | });
172 | } catch (error) {
173 | switch (error.message) {
174 | case exception.NO_DATA:
175 | return next(createError(204));
176 | default:
177 | return next(createError(500));
178 | }
179 | }
180 | }
181 | };
182 |
--------------------------------------------------------------------------------
/WeathyServer/controllers/weathyController.js:
--------------------------------------------------------------------------------
1 | const createError = require('http-errors');
2 | const sc = require('../modules/statusCode');
3 | const weathyService = require('../services/weathyService');
4 | const dayjs = require('dayjs');
5 |
6 | const weatherService = require('../services/weatherService');
7 | const exception = require('../modules/exception');
8 | const logger = require('winston');
9 |
10 | const { uploadS3 } = require('../modules/uploadFile');
11 |
12 | module.exports = {
13 | getRecommendedWeathy: async (req, res, next) => {
14 | const { code, date } = req.query;
15 | const { userId } = req.params;
16 | const dateRegex = /^(19|20)\d{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$/;
17 |
18 | const isExistWeather = await weatherService.getDailyWeather(code, date);
19 |
20 | if (!dateRegex.test(date) || !code || !isExistWeather) {
21 | return next(
22 | createError(
23 | sc.BAD_REQUEST,
24 | 'Parameter Error: 존재하지않는 날씨 데이터이거나 date, code 형식이 일치하지않습니다.'
25 | )
26 | );
27 | }
28 |
29 | try {
30 | const recommendedWeathy = await weathyService.getRecommendedWeathy(
31 | code,
32 | date,
33 | userId
34 | );
35 |
36 | if (!recommendedWeathy) {
37 | res.status(sc.NO_CONTENTS).json({});
38 | next();
39 | } else {
40 | res.status(sc.OK).json({
41 | ...recommendedWeathy,
42 | message: '추천 웨디 조회 성공'
43 | });
44 | next();
45 | }
46 | } catch (error) {
47 | switch (error.message) {
48 | default:
49 | return next(createError(sc.INTERNAL_SERVER_ERROR));
50 | }
51 | }
52 | },
53 |
54 | getWeathy: async (req, res, next) => {
55 | const { date } = req.query;
56 | const userId = res.locals.userId;
57 | const dateRegex = /^(19|20)\d{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$/;
58 | const isPast = dayjs().isAfter(dayjs(date));
59 |
60 | if (!dateRegex.test(date) || !isPast) {
61 | return next(
62 | createError(
63 | sc.BAD_REQUEST,
64 | 'Parameter Error: date 형식에 맞는 과거 날짜를 선택해주세요'
65 | )
66 | );
67 | }
68 |
69 | try {
70 | const weathy = await weathyService.getWeathy(date, userId);
71 |
72 | if (!weathy) {
73 | res.status(sc.NO_CONTENTS).json({});
74 | next();
75 | } else {
76 | res.status(sc.OK).json({
77 | ...weathy,
78 | message: '웨디 기록 조회 성공'
79 | });
80 | next();
81 | }
82 | } catch (error) {
83 | switch (error.message) {
84 | default:
85 | next(createError(sc.INTERNAL_SERVER_ERROR));
86 | }
87 | }
88 | },
89 |
90 | createWeathy: async (req, res, next) => {
91 | try {
92 | if (!req.body.weathy) throw Error(exception.BAD_REQUEST);
93 |
94 | const weathyParams = JSON.parse(req.body.weathy);
95 | const {
96 | date,
97 | code,
98 | clothes,
99 | stampId,
100 | feedback,
101 | userId
102 | } = weathyParams;
103 | const dateRegex = /^(19|20)\d{2}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[0-1])$/;
104 | const isExistFile = req.file ? true : false;
105 | const resUserId = res.locals.userId;
106 | let imgUrl;
107 |
108 | if (!dateRegex.test(date) || !code || !clothes || !stampId)
109 | throw Error(exception.BAD_REQUEST);
110 |
111 | if (resUserId !== userId) {
112 | return next(
113 | createError(
114 | sc.INVALID_ACCOUNT,
115 | 'Token userId and body userId mismatch'
116 | )
117 | );
118 | }
119 |
120 | const dailyWeatherId = await weatherService.getDailyWeatherId(
121 | code,
122 | date
123 | );
124 |
125 | if (!dailyWeatherId) {
126 | throw Error(exception.NO_DAILY_WEATHER);
127 | }
128 |
129 | const checkOwnerClothes = await weathyService.checkOwnerClothes(
130 | clothes,
131 | userId
132 | );
133 |
134 | if (!checkOwnerClothes) {
135 | return next(
136 | createError(
137 | sc.NO_AUTHORITY,
138 | '옷에 대한 접근 권한이 없습니다.'
139 | )
140 | );
141 | }
142 |
143 | if (await weathyService.isDuplicateWeathy(userId, dailyWeatherId))
144 | throw Error(exception.DUPLICATION_WEATHY);
145 |
146 | if (isExistFile) imgUrl = await uploadS3(userId, req.file.buffer);
147 |
148 | const weathyId = await weathyService.createWeathy(
149 | dailyWeatherId,
150 | clothes,
151 | stampId,
152 | userId,
153 | feedback || null,
154 | imgUrl || null
155 | );
156 |
157 | res.status(sc.OK).json({
158 | message: '웨디 기록 성공',
159 | weathyId
160 | });
161 | next();
162 | } catch (error) {
163 | logger.error(error.stack);
164 |
165 | switch (error.message) {
166 | case exception.NO_DAILY_WEATHER:
167 | return next(
168 | createError(
169 | sc.BAD_REQUEST,
170 | '해당 위치의 Daily Weather가 존재하지않음'
171 | )
172 | );
173 | case exception.DUPLICATION_WEATHY:
174 | return next(
175 | createError(
176 | sc.BAD_REQUEST,
177 | '잘못된 날짜에 Weathy 작성(중복된 웨디 작성)'
178 | )
179 | );
180 | case exception.NO_AUTHORITY:
181 | return next(
182 | createError(
183 | sc.BAD_REQUEST,
184 | 'Autority Error: Clothes 권한 없음'
185 | )
186 | );
187 | case exception.BAD_REQUEST:
188 | return next(createError(sc.BAD_REQUEST, 'Parameter Error'));
189 |
190 | default:
191 | return next(createError(sc.INTERNAL_SERVER_ERROR));
192 | }
193 | }
194 | },
195 |
196 | modifyWeathy: async (req, res, next) => {
197 | try {
198 | if (!req.body.weathy) throw Error(exception.BAD_REQUEST);
199 |
200 | const weathyParams = JSON.parse(req.body.weathy);
201 | const { weathyId } = req.params;
202 | const { code, clothes, stampId, feedback, isDelete } = weathyParams;
203 | const isExistFile = req.file ? true : false;
204 | const userId = res.locals.userId;
205 |
206 | const checkOwnerClothes = await weathyService.checkOwnerClothes(
207 | clothes,
208 | userId
209 | );
210 |
211 | if (!checkOwnerClothes) {
212 | return next(
213 | createError(
214 | sc.NO_AUTHORITY,
215 | '옷에 대한 접근 권한이 없습니다.'
216 | )
217 | );
218 | }
219 |
220 | const weathy = await weathyService.modifyWeathy(
221 | weathyId,
222 | userId,
223 | code,
224 | clothes,
225 | stampId,
226 | feedback || null
227 | );
228 |
229 | if (!weathy) throw Error(exception.NO_AUTHORITY);
230 |
231 | if (isExistFile && !isDelete) {
232 | //수정 시
233 | const imgUrl = await uploadS3(userId, req.file.buffer);
234 | await weathyService.modifyImgField(imgUrl, weathyId, userId);
235 | } else if (!isExistFile && isDelete) {
236 | //삭제 시
237 | await weathyService.modifyImgField(null, weathyId, userId);
238 | }
239 |
240 | res.status(sc.OK).json({
241 | message: '웨디 기록 수정 완료'
242 | });
243 | next();
244 | } catch (err) {
245 | logger.error(err.stack);
246 | switch (err.message) {
247 | case exception.NO_DAILY_WEATHER:
248 | return next(
249 | createError(
250 | sc.BAD_REQUEST,
251 | 'Daily weather 데이터가 존재하지 않습니다.'
252 | )
253 | );
254 | case exception.NO_AUTHORITY:
255 | return next(
256 | createError(
257 | sc.BAD_REQUEST,
258 | '웨디를 수정할 수 없습니다.'
259 | )
260 | );
261 | case exception.DUPLICATION_WEATHY:
262 | return next(
263 | createError(
264 | sc.BAD_REQUEST,
265 | '잘못된 날짜에 Weathy 작성(중복된 웨디 작성)'
266 | )
267 | );
268 | case exception.BAD_REQUEST:
269 | return next(createError(sc.BAD_REQUEST, 'Parameter Error'));
270 | case exception.CANNOT_UPLOAD_FILE:
271 | return next(
272 | createError(
273 | sc.INTERNAL_SERVER_ERROR,
274 | 'Cannot upload File'
275 | )
276 | );
277 | default:
278 | return next(createError(sc.INTERNAL_SERVER_ERROR));
279 | }
280 | }
281 | },
282 |
283 | deleteWeathy: async (req, res, next) => {
284 | const { weathyId } = req.params;
285 | const userId = res.locals.userId;
286 |
287 | try {
288 | const deletedWeathy = await weathyService.deleteWeathy(
289 | weathyId,
290 | userId
291 | );
292 |
293 | if (!deletedWeathy) {
294 | res.status(sc.NO_CONTENTS).json({});
295 | next();
296 | } else {
297 | res.status(sc.OK).json({
298 | message: '웨디 기록 삭제 성공'
299 | });
300 | next();
301 | }
302 | } catch (err) {
303 | switch (err) {
304 | default:
305 | return next(createError(sc.INTERNAL_SERVER_ERROR));
306 | }
307 | }
308 | }
309 | };
310 |
--------------------------------------------------------------------------------
/WeathyServer/models/category.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 | return sequelize.define(
3 | 'ClothesCategories',
4 | {
5 | name: {
6 | type: DataTypes.STRING(45),
7 | allowNull: false
8 | }
9 | },
10 | {
11 | underscored: true,
12 | freezeTableName: true,
13 | paranoid: true
14 | }
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/WeathyServer/models/climate.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 | return sequelize.define(
3 | 'Climates',
4 | {
5 | icon_id: {
6 | type: DataTypes.INTEGER,
7 | allowNull: false
8 | },
9 | description: {
10 | type: DataTypes.STRING(100),
11 | allowNull: false
12 | }
13 | },
14 | {
15 | underscored: true,
16 | freezeTableName: true,
17 | paranoid: true
18 | }
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/WeathyServer/models/climateMessage.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 | return sequelize.define(
3 | 'ClimateMessages',
4 | {
5 | weather_group: {
6 | type: DataTypes.INTEGER,
7 | allowNull: false
8 | },
9 | description: {
10 | type: DataTypes.STRING(100),
11 | allowNull: false
12 | }
13 | },
14 | {
15 | underscored: true,
16 | freezeTableName: true,
17 | paranoid: true,
18 | indexes: [
19 | {
20 | unique: true,
21 | fields: ['weather_group', 'description']
22 | }
23 | ]
24 | }
25 | );
26 | };
27 |
--------------------------------------------------------------------------------
/WeathyServer/models/clothes.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 | return sequelize.define(
3 | 'Clothes',
4 | {
5 | user_id: {
6 | type: DataTypes.INTEGER,
7 | allowNull: false
8 | },
9 | name: {
10 | type: DataTypes.STRING(45),
11 | allowNull: false
12 | },
13 | is_deleted: {
14 | type: DataTypes.TINYINT(1),
15 | allowNull: false
16 | }
17 | },
18 | {
19 | underscored: true,
20 | freezeTableName: true,
21 | paranoid: true
22 | }
23 | );
24 | };
25 |
--------------------------------------------------------------------------------
/WeathyServer/models/dailyWeather.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 | return sequelize.define(
3 | 'DailyWeathers',
4 | {
5 | date: {
6 | type: DataTypes.DATE,
7 | allowNull: false
8 | },
9 | temperature_max: {
10 | type: DataTypes.INTEGER,
11 | allowNull: false
12 | },
13 | temperature_min: {
14 | type: DataTypes.INTEGER,
15 | allowNull: false
16 | },
17 | humidity: {
18 | type: DataTypes.INTEGER,
19 | allowNull: false
20 | },
21 | precipitation: {
22 | type: DataTypes.INTEGER,
23 | allowNull: false
24 | },
25 | wind_speed: {
26 | type: DataTypes.FLOAT,
27 | allowNull: false
28 | },
29 | wind_direction: {
30 | type: DataTypes.INTEGER,
31 | allowNull: false
32 | }
33 | },
34 | {
35 | underscored: true,
36 | freezeTableName: true,
37 | paranoid: true,
38 | indexes: [
39 | {
40 | unique: true,
41 | fields: ['location_id', 'date']
42 | }
43 | ]
44 | }
45 | );
46 | };
47 |
--------------------------------------------------------------------------------
/WeathyServer/models/hourlyWeather.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 | return sequelize.define(
3 | 'HourlyWeathers',
4 | {
5 | date: {
6 | type: DataTypes.DATE,
7 | allowNull: false
8 | },
9 | hour: {
10 | type: DataTypes.INTEGER,
11 | allowNull: false
12 | },
13 | temperature: {
14 | type: DataTypes.INTEGER,
15 | allowNull: false
16 | },
17 | pop: {
18 | type: DataTypes.INTEGER,
19 | allowNull: false
20 | }
21 | },
22 | {
23 | underscored: true,
24 | freezeTableName: true,
25 | paranoid: true,
26 | indexes: [
27 | {
28 | unique: true,
29 | fields: ['date', 'hour', 'location_id']
30 | }
31 | ]
32 | }
33 | );
34 | };
35 |
--------------------------------------------------------------------------------
/WeathyServer/models/index.js:
--------------------------------------------------------------------------------
1 | const Sequelize = require('sequelize');
2 | const env = process.env.NODE_ENV || 'development';
3 | const config = require('../config/database.json')[env];
4 | const db = {};
5 |
6 | let sequelize;
7 |
8 | if (config.use_env_variable) {
9 | sequelize = new Sequelize(process.env[config.use_env_variable], config);
10 | } else {
11 | sequelize = new Sequelize(
12 | config.database,
13 | config.username,
14 | config.password,
15 | config
16 | );
17 | }
18 |
19 | db.sequelize = sequelize;
20 | db.Sequelize = Sequelize;
21 |
22 | db.HourlyWeather = require('./hourlyWeather')(sequelize, Sequelize);
23 | db.DailyWeather = require('./dailyWeather')(sequelize, Sequelize);
24 | db.Location = require('./location')(sequelize, Sequelize);
25 | db.Climate = require('./climate')(sequelize, Sequelize);
26 | db.ClimateMessage = require('./climateMessage')(sequelize, Sequelize);
27 | db.User = require('./user')(sequelize, Sequelize);
28 | db.ClothesCategory = require('./category')(sequelize, Sequelize);
29 | db.Clothes = require('./clothes')(sequelize, Sequelize);
30 | db.WeathyClothes = require('./weathyClothes')(sequelize, Sequelize);
31 | db.Weathy = require('./weathy')(sequelize, Sequelize);
32 | db.Token = require('./token')(sequelize, Sequelize);
33 |
34 | db.Location.hasMany(
35 | db.HourlyWeather,
36 | { foreignKey: 'location_id' },
37 | { onDelete: 'RESTRICT' }
38 | );
39 | db.HourlyWeather.belongsTo(db.Location, {
40 | foreignKey: 'location_id'
41 | });
42 |
43 | db.Location.hasMany(
44 | db.HourlyWeather,
45 | { foreignKey: 'location_id' },
46 | { onDelete: 'RESTRICT' }
47 | );
48 | db.DailyWeather.belongsTo(db.Location, {
49 | foreignKey: 'location_id'
50 | });
51 |
52 | db.Climate.hasMany(
53 | db.DailyWeather,
54 | { foreignKey: 'climate_id' },
55 | { onDelete: 'RESTRICT' }
56 | );
57 | db.DailyWeather.belongsTo(db.Climate, {
58 | foreignKey: 'climate_id'
59 | });
60 |
61 | db.Climate.hasMany(
62 | db.HourlyWeather,
63 | { foreignKey: 'climate_id' },
64 | { onDelete: 'RESTRICT' }
65 | );
66 | db.HourlyWeather.belongsTo(db.Climate, { foreignKey: 'climate_id' });
67 |
68 | db.User.hasMany(db.Weathy, { foreignKey: 'user_id' }, { onDelete: 'CASCADE' });
69 | db.Weathy.belongsTo(db.User, { foreignKey: 'user_id' });
70 |
71 | db.User.hasMany(db.Clothes, { foreignKey: 'user_id' }, { onDelete: 'CASCADE' });
72 | db.Clothes.belongsTo(db.User, { foreignKey: 'user_id' });
73 |
74 | db.ClothesCategory.hasMany(db.Clothes, {
75 | foreignKey: 'category_id'
76 | });
77 | db.Clothes.belongsTo(db.ClothesCategory, {
78 | foreignKey: 'category_id'
79 | });
80 |
81 | db.Clothes.hasMany(db.WeathyClothes, { foreignKey: 'clothes_id' });
82 | db.WeathyClothes.belongsTo(db.Clothes, { foreignKey: 'clothes_id' });
83 |
84 | db.Weathy.hasMany(
85 | db.WeathyClothes,
86 | { foreignKey: 'weathy_id', onDelete: 'CASCADE' }
87 | // { onDelete: 'CASCADE' }
88 | );
89 | db.WeathyClothes.belongsTo(db.Weathy, { foreignKey: 'weathy_id' });
90 |
91 | db.DailyWeather.hasMany(
92 | db.Weathy,
93 | { foreignKey: 'dailyweather_id' },
94 | { onDelete: 'cascade' }
95 | );
96 | db.Weathy.belongsTo(db.DailyWeather, { foreignKey: 'dailyweather_id' });
97 |
98 | db.User.hasOne(db.Token, { foreignKey: 'user_id' }, { onDelete: 'cascade' });
99 | db.Token.belongsTo(db.User, { foreignKey: 'user_id' });
100 |
101 | module.exports = db;
102 |
--------------------------------------------------------------------------------
/WeathyServer/models/location.js:
--------------------------------------------------------------------------------
1 | const Sequelize = require('sequelize');
2 |
3 | module.exports = (sequelize, DataTypes) => {
4 | return sequelize.define(
5 | 'Locations',
6 | {
7 | id: {
8 | type: Sequelize.BIGINT,
9 | autoIncrement: true,
10 | primaryKey: true
11 | },
12 | name: {
13 | type: DataTypes.STRING(45),
14 | allowNull: false
15 | },
16 | lat: {
17 | type: DataTypes.DOUBLE,
18 | allowNull: false
19 | },
20 | lng: {
21 | type: DataTypes.DOUBLE,
22 | allowNull: false
23 | }
24 | },
25 | {
26 | underscored: true,
27 | freezeTableName: true,
28 | paranoid: true
29 | }
30 | );
31 | };
32 |
--------------------------------------------------------------------------------
/WeathyServer/models/token.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 | return sequelize.define(
3 | 'Token',
4 | {
5 | token: {
6 | type: DataTypes.STRING(45),
7 | allowNull: false
8 | }
9 | },
10 | {
11 | underscored: true,
12 | freezeTableName: true
13 | }
14 | );
15 | };
16 |
--------------------------------------------------------------------------------
/WeathyServer/models/user.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 | return sequelize.define(
3 | 'Users',
4 | {
5 | nickname: {
6 | type: DataTypes.STRING(8),
7 | allowNull: false
8 | },
9 | uuid: {
10 | type: DataTypes.STRING(45),
11 | allowNull: false
12 | }
13 | },
14 | {
15 | underscored: true,
16 | freezeTableName: true,
17 | paranoid: true
18 | }
19 | );
20 | };
21 |
--------------------------------------------------------------------------------
/WeathyServer/models/weathy.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 | return sequelize.define(
3 | 'Weathies',
4 | {
5 | emoji_id: {
6 | type: DataTypes.INTEGER,
7 | allowNull: false
8 | },
9 | description: {
10 | type: DataTypes.STRING(500),
11 | allowNull: true
12 | },
13 | img_url: {
14 | type: DataTypes.STRING(500),
15 | allowNull: true
16 | }
17 | },
18 | {
19 | underscored: true,
20 | freezeTableName: true
21 | }
22 | );
23 | };
24 |
--------------------------------------------------------------------------------
/WeathyServer/models/weathyClothes.js:
--------------------------------------------------------------------------------
1 | module.exports = (sequelize, DataTypes) => {
2 | return sequelize.define(
3 | 'WeathyClothes',
4 | {},
5 | {
6 | underscored: true,
7 | freezeTableName: true
8 | }
9 | );
10 | };
11 |
--------------------------------------------------------------------------------
/WeathyServer/modules/exception.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | // COMMON
3 | NO_DATA: 'NO_DATA',
4 |
5 | // TOKEN
6 | EXPIRED_TOKEN: 'EXPIRED_TOKEN',
7 | INVALID_TOKEN: 'INVALID_TOKEN',
8 | MISMATCH_TOKEN: 'MISMATCH_TOKEN',
9 |
10 | NO_AUTHORITY: 'NO_AUTHORITY',
11 | // USER
12 | NO_USER: 'NO_USER',
13 | ALREADY_USER: 'ALREADY_USER',
14 |
15 | // LOCATION
16 | INVALID_LOCATION: 'INVALID_LOCATION',
17 |
18 | // WEAHTER:
19 | NO_DAILY_WEATHER: 'NO_DAILY_WEATHER_DATA',
20 |
21 | // WEATHY
22 | DUPLICATION_WEATHY: 'DUPLICATION_WEATHRY_ERROR',
23 |
24 | // CLOTHES
25 | ALREADY_CLOTHES: 'ALREADY_CLOTHES',
26 | NO_CLOTHES: 'NO_CLOTHES',
27 | NOT_AUTHORIZED_CLOTHES: 'NOT_AUTHORIZED_CLOTHES',
28 |
29 | // REQUEST
30 | BAD_REQUEST: 'BAD_REQUEST',
31 |
32 | CANNOT_UPLOAD_FILE: 'CANNOT_UPLOAD_FILE',
33 | // SERVER
34 | SERVER_ERROR: 'SERVER_ ERROR'
35 | };
36 |
--------------------------------------------------------------------------------
/WeathyServer/modules/logger.js:
--------------------------------------------------------------------------------
1 | const { createLogger, format, transports } = require('winston');
2 | const winstonDaily = require('winston-daily-rotate-file');
3 |
4 | const appRoot = require('app-root-path');
5 | const fs = require('fs');
6 |
7 | const env = process.env.NODE_ENV || 'development';
8 | const logDir = `${appRoot}/logs`;
9 |
10 | // Create the log directory if it does not exist
11 | if (!fs.existsSync(logDir)) {
12 | fs.mkdirSync(logDir);
13 | }
14 | const { combine, timestamp, label, printf } = format;
15 | const logFormat = printf(({ level, message, label, timestamp }) => {
16 | return `${timestamp} [${label}] ${level}: ${message}`;
17 | });
18 |
19 | const logger = createLogger({
20 | level: 'info',
21 | format: combine(
22 | label({
23 | label: 'Weathy'
24 | }),
25 | timestamp({
26 | format: 'YYYY-MM-DD HH:mm:ss'
27 | }),
28 | logFormat
29 | ),
30 | transports: [
31 | new winstonDaily({
32 | level: 'info',
33 | datePattern: 'YYYY-MM-DD',
34 | dirname: logDir,
35 | filename: `%DATE%.log`,
36 | maxFiles: 30,
37 | zippedArchive: true
38 | }),
39 | new winstonDaily({
40 | level: 'error',
41 | datePattern: 'YYYY-MM-DD',
42 | dirname: logDir,
43 | filename: `%DATE%.error.log`,
44 | maxFiles: 30,
45 | zippedArchive: true
46 | })
47 | ]
48 | });
49 |
50 | if (env !== 'production') {
51 | logger.add(
52 | new transports.Console({
53 | format: format.combine(format.colorize(), format.simple())
54 | })
55 | );
56 | }
57 | module.exports = logger;
58 |
--------------------------------------------------------------------------------
/WeathyServer/modules/statusCode.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | OK: 200,
3 |
4 | NO_CONTENTS: 204,
5 | INVALID_ACCOUNT: 401,
6 |
7 | // Error status code
8 | BAD_REQUEST: 400,
9 | NO_AUTHORITY: 403,
10 | INTERNAL_SERVER_ERROR: 500
11 | };
12 |
--------------------------------------------------------------------------------
/WeathyServer/modules/swagger.js:
--------------------------------------------------------------------------------
1 | const swaggerUi = require('swagger-ui-express');
2 | const swaggereJsdoc = require('swagger-jsdoc');
3 |
4 | const options = {
5 | swaggerDefinition: {
6 | info: {
7 | title: 'Weathy API',
8 | version: '1.0.0',
9 | description: 'Weathy API with express'
10 | },
11 | host: 'localhost:3000',
12 | basePath: '/'
13 | },
14 | apis: ['./routes/*.js', './swagger/*']
15 | };
16 |
17 | const specs = swaggereJsdoc(options);
18 |
19 | module.exports = {
20 | swaggerUi,
21 | specs
22 | };
23 |
--------------------------------------------------------------------------------
/WeathyServer/modules/tokenMiddleware.js:
--------------------------------------------------------------------------------
1 | const createError = require('http-errors');
2 | const dayjs = require('dayjs');
3 | const { Token, sequelize } = require('../models');
4 | const sc = require('./statusCode');
5 | const exception = require('./exception');
6 | const { generateToken, isUserOwnerOfToken } = require('../utils/tokenUtils');
7 |
8 | const TOKEN_EXPIRES_IN_DAYS = 15; // 토큰 유효 기간 (15일)
9 |
10 | const validateToken = async (req, res, next) => {
11 | // 토큰 검사
12 | // 1. header에 token이 있는지 확인
13 | const token = req.headers['x-access-token'];
14 | if (!token) {
15 | return next(createError(sc.BAD_REQUEST, 'No Token'));
16 | }
17 |
18 | // 2. 이 토큰이 유효한지 확인
19 | const userToken = await Token.findOne({ where: { token: token } });
20 | // 2-1. token 객체가 DB에 존재해야 한다
21 | if (userToken === null) {
22 | return next(createError(sc.INVALID_ACCOUNT, 'No Matching Token on DB'));
23 | } else if (!isUserOwnerOfToken(userToken.user_id, token)) {
24 | // 2-2. 객체의 userId와 토큰에 포함된 id가 같아야 한다
25 | return next(createError(sc.INVALID_ACCOUNT, 'Token user_id is wrong'));
26 | } else {
27 | // 2-3. param에 userId가 있다면, 토큰에 포함된 id와 일치하는지 확인. 없으면 그냥 넘어간다
28 | // 가정: param에 userId가 없다면, 그 API는 param에 userId를 요구하지 않는 것이다
29 | // param에 userId가 있어야 하는데 없는 경우는, service 단에서 error를 처리해야 함
30 | const { userId } = req.params;
31 | if (userId && !isUserOwnerOfToken(userId, token)) {
32 | return next(
33 | createError(
34 | sc.INVALID_ACCOUNT,
35 | 'Param userId is different with Token'
36 | )
37 | );
38 | }
39 |
40 | // 2-4. token의 만료 기한이 지나지 않았어야 한다
41 | const updatedTime = userToken.updatedAt;
42 | const updatedTimeDayjs = dayjs(updatedTime);
43 | const expirationTime = updatedTimeDayjs.add(
44 | TOKEN_EXPIRES_IN_DAYS,
45 | 'days'
46 | );
47 | const now = dayjs(new Date());
48 | if (!now.isBefore(expirationTime)) {
49 | return next(createError(sc.INVALID_ACCOUNT, 'Expired Token'));
50 | }
51 | }
52 |
53 | // 3. res.locals에 token 값과 userId 저장해 둠
54 | res.locals.tokenValue = token;
55 | res.locals.userId = userToken.user_id;
56 | next();
57 | };
58 |
59 | const updateToken = async (req, res) => {
60 | // 토큰 업데이트
61 | // 사용되면 return res.send() 에서 return을 뺄 것
62 |
63 | try {
64 | const token = res.locals.tokenValue;
65 |
66 | const userToken = await Token.findOne({ where: { token: token } });
67 |
68 | userToken.changed('updatedAt', true);
69 | await userToken.update({ updatedAt: new Date() });
70 |
71 | return res;
72 | } catch (err) {
73 | console.log(err);
74 | throw Error(exception.SERVER_ERROR);
75 | }
76 | };
77 |
78 | module.exports = {
79 | validateToken,
80 | updateToken
81 | };
82 |
--------------------------------------------------------------------------------
/WeathyServer/modules/uploadFile.js:
--------------------------------------------------------------------------------
1 | const aws = require('aws-sdk');
2 |
3 | const logger = require('winston');
4 |
5 | const exception = require('./exception');
6 |
7 | aws.config.loadFromPath(__dirname + '/../config/s3.json');
8 |
9 | const s3 = new aws.S3();
10 |
11 | const uploadS3 = (userId, file) => {
12 | const param = {
13 | Bucket: 'weathy',
14 | Key: `ootd/${userId}/${Date.now()}.jpeg`,
15 | ACL: 'public-read',
16 | Body: file,
17 | ContentType: 'image/jpeg'
18 | };
19 |
20 | return new Promise((res, rej) => {
21 | s3.upload(param, function (err, data) {
22 | if (err) {
23 | logger.error(err);
24 | rej(Error(exception.SERVER_ERROR));
25 | } else {
26 | res(data.Location);
27 | }
28 | });
29 | });
30 | };
31 |
32 | module.exports = {
33 | uploadS3
34 | };
35 |
--------------------------------------------------------------------------------
/WeathyServer/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "weathyserver",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "node ./bin/www",
7 | "test": "node_modules/.bin/mocha $(find tests/ -name '*.js') --recursive -w"
8 | },
9 | "dependencies": {
10 | "app-root-path": "^3.0.0",
11 | "aws-sdk": "^2.848.0",
12 | "cookie-parser": "~1.4.4",
13 | "crypto-random-string": "^3.3.0",
14 | "dayjs": "^1.10.2",
15 | "debug": "~2.6.9",
16 | "express": "~4.16.1",
17 | "http-errors": "~1.6.3",
18 | "jade": "~1.11.0",
19 | "morgan": "~1.9.1",
20 | "multer": "^1.4.2",
21 | "multer-s3": "^2.9.0",
22 | "mysql2": "^2.2.5",
23 | "node-mocks-http": "^1.10.1",
24 | "request": "^2.88.2",
25 | "request-promise": "^4.2.6",
26 | "sequelize": "^6.3.5",
27 | "sequelize-cli": "^6.2.0",
28 | "winston": "^3.3.3",
29 | "winston-daily-rotate-file": "^4.5.0"
30 | },
31 | "devDependencies": {
32 | "decache": "^4.6.0",
33 | "eslint": "^7.16.0",
34 | "eslint-config-prettier": "^7.1.0",
35 | "eslint-plugin-prettier": "^3.3.0",
36 | "mocha": "^8.2.1",
37 | "prettier": "2.2.1",
38 | "swagger-jsdoc": "^6.0.0",
39 | "swagger-ui-express": "^4.1.6"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/WeathyServer/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 50px;
3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
4 | }
5 |
6 | a {
7 | color: #00B7FF;
8 | }
9 |
--------------------------------------------------------------------------------
/WeathyServer/routes/auth.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const authController = require('../controllers/authController');
4 | const { updateToken } = require('../modules/tokenMiddleware');
5 |
6 | router.post('/login', authController.login, updateToken);
7 |
8 | module.exports = router;
9 |
--------------------------------------------------------------------------------
/WeathyServer/routes/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | /* GET home page. */
5 | router.get('/', function (req, res) {
6 | res.render('index', { title: 'Express' });
7 | });
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/WeathyServer/routes/users.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | const weathyController = require('../controllers/weathyController');
5 | const { validateToken, updateToken } = require('../modules/tokenMiddleware');
6 | const userController = require('../controllers/userController');
7 | const clothesController = require('../controllers/clothesController');
8 | const calendarController = require('../controllers/calendarController');
9 |
10 | router.post('/', userController.createUser);
11 | router.put('/:userId', validateToken, userController.modifyUser, updateToken);
12 | router.get(
13 | '/:userId/clothes',
14 | validateToken,
15 | clothesController.getClothes,
16 | updateToken
17 | );
18 | router.post(
19 | '/:userId/clothes',
20 | validateToken,
21 | clothesController.addClothes,
22 | updateToken
23 | );
24 | router.delete(
25 | '/:userId/clothes',
26 | validateToken,
27 | clothesController.deleteClothes,
28 | updateToken
29 | );
30 |
31 | router.get(
32 | '/:userId/weathy/recommend',
33 | validateToken,
34 | weathyController.getRecommendedWeathy,
35 | updateToken
36 | );
37 | router.get(
38 | '/:userId/calendar',
39 | validateToken,
40 | calendarController.getCalendarOverviews,
41 | updateToken
42 | );
43 |
44 | module.exports = router;
45 |
--------------------------------------------------------------------------------
/WeathyServer/routes/weather.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const weatherController = require('../controllers/weatherController');
4 |
5 | router.get('/overview', weatherController.getWeatherByLocation);
6 | router.get('/forecast/hourly', weatherController.getHourlyWeatherForecast);
7 | router.get('/forecast/daily', weatherController.getDailyWeatherForecast);
8 | router.get('/daily/extra', weatherController.getExtraDailyWeather);
9 | router.get('/overviews', weatherController.getWeathersByKeyword);
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/WeathyServer/routes/weathy.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | const weathyController = require('../controllers/weathyController');
5 | const { validateToken, updateToken } = require('../modules/tokenMiddleware');
6 | const multer = require('multer');
7 |
8 | router.get('/', validateToken, weathyController.getWeathy, updateToken);
9 | router.post(
10 | '/',
11 | validateToken,
12 | multer().single('img'),
13 | weathyController.createWeathy,
14 | updateToken
15 | );
16 | router.put(
17 | '/:weathyId',
18 | validateToken,
19 | multer().single('img'),
20 | weathyController.modifyWeathy,
21 | updateToken
22 | );
23 | router.delete(
24 | '/:weathyId',
25 | validateToken,
26 | weathyController.deleteWeathy,
27 | updateToken
28 | );
29 | module.exports = router;
30 |
--------------------------------------------------------------------------------
/WeathyServer/services/calendarService.js:
--------------------------------------------------------------------------------
1 | const { DailyWeather, Weathy } = require('../models');
2 | const Sequelize = require('sequelize');
3 | const { literal } = require('sequelize');
4 |
5 | module.exports = {
6 | getValidCalendarOverviewList: async (userId, startDate, endDate) => {
7 | const Op = Sequelize.Op;
8 | const weathies = await Weathy.findAll({
9 | include: [
10 | {
11 | model: DailyWeather,
12 | attributes: [
13 | 'date',
14 | 'temperature_max',
15 | 'temperature_min',
16 | 'climate_id'
17 | ],
18 | where: {
19 | date: {
20 | [Op.and]: {
21 | [Op.gte]: startDate,
22 | [Op.lte]: endDate
23 | }
24 | }
25 | }
26 | }
27 | ],
28 | where: {
29 | user_id: userId
30 | },
31 | order: literal('DailyWeather.date ASC')
32 | });
33 | let validCalendarOverviewList = [];
34 | for (let i = 0; i < weathies.length; ++i) {
35 | const weathy = weathies[i];
36 | validCalendarOverviewList.push({
37 | id: weathy.id,
38 | date: weathy.DailyWeather.date,
39 | climateIconId: weathy.DailyWeather.climate_id,
40 | stampId: weathy.emoji_id,
41 | temperature: {
42 | maxTemp: weathy.DailyWeather.temperature_max,
43 | minTemp: weathy.DailyWeather.temperature_min
44 | }
45 | });
46 | }
47 | return validCalendarOverviewList;
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/WeathyServer/services/climateService.js:
--------------------------------------------------------------------------------
1 | const { Climate, ClimateMessage } = require('../models');
2 | const exception = require('../modules/exception');
3 |
4 | const getIconId = async (climate_id) => {
5 | const climate = await Climate.findOne({ where: { id: climate_id } });
6 | if (!climate) {
7 | throw Error(exception.NO_DATA);
8 | }
9 | return climate.icon_id;
10 | };
11 |
12 | const getTemperatureLevel = (temperature) => {
13 | let idx = parseInt(temperature / 10) + 3;
14 | idx = Math.max(1, idx);
15 | idx = Math.min(6, idx);
16 | return idx;
17 | };
18 |
19 | const getWeatherGroup = (climate_id, temperature) => {
20 | climate_id %= 100;
21 | if (climate_id == 1) {
22 | return getTemperatureLevel(temperature);
23 | } else if (climate_id == 2 || climate_id == 3) {
24 | return 6 + getTemperatureLevel(temperature);
25 | } else if (climate_id == 4) {
26 | return 12 + getTemperatureLevel(temperature);
27 | } else if (climate_id == 9 || climate_id == 10) {
28 | return 19;
29 | } else if (climate_id == 11) {
30 | return 20;
31 | } else if (climate_id == 13) {
32 | return 21;
33 | } else if (climate_id == 50) {
34 | return 22;
35 | }
36 | throw Error(exception.NO_DATA);
37 | };
38 |
39 | const getDescription = async (climate_id, temperature) => {
40 | const weather_group = await getWeatherGroup(climate_id, temperature);
41 | const climateMessages = await ClimateMessage.findAll({
42 | where: { weather_group: weather_group }
43 | });
44 | if (climateMessages.length == 0) {
45 | throw Error(exception.NO_DATA);
46 | }
47 |
48 | const rand = Math.random();
49 | const idx = Math.floor(rand * climateMessages.length);
50 | return climateMessages[idx].description;
51 | };
52 |
53 | const getClimateByIconId = async (id) => {
54 | const climate = await Climate.findOne({
55 | where: {
56 | icon_id: id
57 | }
58 | });
59 | const iconId = climate.icon_id;
60 | const description = climate.description;
61 |
62 | return {
63 | iconId,
64 | description
65 | };
66 | };
67 | module.exports = {
68 | getClimate: async (id, temperature) => {
69 | const icon_id = await getIconId(id);
70 | const description = await getDescription(id, temperature);
71 | return {
72 | iconId: icon_id,
73 | description: description
74 | };
75 | },
76 | getIconId,
77 | getClimateByIconId
78 | };
79 |
--------------------------------------------------------------------------------
/WeathyServer/services/clothesService.js:
--------------------------------------------------------------------------------
1 | const { Clothes, ClothesCategory, WeathyClothes } = require('../models');
2 | const exception = require('../modules/exception');
3 | const { Op } = require('sequelize');
4 |
5 | const setClothesForm = async () => {
6 | const closet = {};
7 |
8 | const clothesCategories = await ClothesCategory.findAll({
9 | attributes: ['name', 'id']
10 | });
11 |
12 | for (const c of clothesCategories) {
13 | closet[c.name] = {
14 | categoryId: c.id,
15 | clothes: []
16 | };
17 | }
18 | return closet;
19 | };
20 |
21 | const unionTwoCloset = (closet1, closet2) => {
22 | const top1 = closet1.top.clothes;
23 | const top2 = closet2.top.clothes;
24 | const unionTop = top2.concat(top1).filter(function (cl) {
25 | return this.has(cl.id) ? false : this.add(cl.id);
26 | }, new Set());
27 | const bottom1 = closet1.bottom.clothes;
28 | const bottom2 = closet2.bottom.clothes;
29 | const unionBottom = bottom2.concat(bottom1).filter(function (cl) {
30 | return this.has(cl.id) ? false : this.add(cl.id);
31 | }, new Set());
32 | const outer1 = closet1.outer.clothes;
33 | const outer2 = closet2.outer.clothes;
34 | const unionOuter = outer2.concat(outer1).filter(function (cl) {
35 | return this.has(cl.id) ? false : this.add(cl.id);
36 | }, new Set());
37 | const etc1 = closet1.etc.clothes;
38 | const etc2 = closet2.etc.clothes;
39 | const unionEtc = etc2.concat(etc1).filter(function (cl) {
40 | return this.has(cl.id) ? false : this.add(cl.id);
41 | }, new Set());
42 |
43 | const unionCloset = {
44 | top: {
45 | categoryId: 1,
46 | clothesNum: top1.length,
47 | clothes: unionTop
48 | },
49 | bottom: {
50 | categoryId: 2,
51 | clothesNum: bottom1.length,
52 | clothes: unionBottom
53 | },
54 | outer: {
55 | categoryId: 3,
56 | clothesNum: outer1.length,
57 | clothes: unionOuter
58 | },
59 | etc: {
60 | categoryId: 4,
61 | clothesNum: etc1.length,
62 | clothes: unionEtc
63 | }
64 | };
65 | return unionCloset;
66 | };
67 |
68 | async function getClothesNumByUserId(userId) {
69 | const aliveClothes = await Clothes.findAndCountAll({
70 | where: {
71 | user_id: userId,
72 | is_deleted: 0
73 | }
74 | });
75 | return aliveClothes.count;
76 | }
77 |
78 | async function getClothesByUserId(userId) {
79 | const responseCloset = new Object();
80 | const clothesCategories = await ClothesCategory.findAll();
81 |
82 | for (const category of clothesCategories) {
83 | const categoryClothes = await Clothes.findAll({
84 | where: {
85 | user_id: userId,
86 | category_id: category.id,
87 | is_deleted: 0
88 | },
89 | order: [
90 | ['updated_at', 'DESC'],
91 | ['id', 'DESC']
92 | ]
93 | });
94 |
95 | const categoryCloset = new Object();
96 | categoryCloset.categoryId = category.id;
97 | const categoryClothesList = new Array();
98 | await categoryClothes.forEach((element) => {
99 | const clothes = new Object();
100 | clothes.id = element.id;
101 | clothes.name = element.name;
102 | categoryClothesList.push(clothes);
103 | });
104 | categoryCloset.clothes = categoryClothesList;
105 | categoryCloset.clothesNum = categoryClothesList.length;
106 | responseCloset[category.name] = categoryCloset;
107 | }
108 | return responseCloset;
109 | }
110 |
111 | async function getClothesByWeathyId(userId, weathyId) {
112 | const responseCloset = new Object();
113 | const clothesCategories = await ClothesCategory.findAll();
114 |
115 | const weathyClothes = await WeathyClothes.findAll({
116 | where: {
117 | weathy_id: weathyId
118 | },
119 | attributes: ['clothes_id']
120 | });
121 | const weathyClothesIdList = new Array();
122 | await weathyClothes.forEach((e) => {
123 | weathyClothesIdList.push(e.dataValues.clothes_id);
124 | });
125 |
126 | for (const category of clothesCategories) {
127 | const categoryClothes = await Clothes.findAll({
128 | where: {
129 | user_id: userId,
130 | category_id: category.id,
131 | id: {
132 | [Op.in]: weathyClothesIdList
133 | }
134 | },
135 | order: [
136 | ['updated_at', 'DESC'],
137 | ['id', 'DESC']
138 | ]
139 | });
140 |
141 | const categoryCloset = new Object();
142 | categoryCloset.categoryId = category.id;
143 | const categoryClothesList = new Array();
144 | await categoryClothes.forEach((element) => {
145 | const clothes = new Object();
146 | clothes.id = element.id;
147 | clothes.name = element.name;
148 | categoryClothesList.push(clothes);
149 | });
150 | categoryCloset.clothes = categoryClothesList;
151 | categoryCloset.clothesNum = categoryClothesList.length;
152 | responseCloset[category.name] = categoryCloset;
153 | }
154 | return responseCloset;
155 | }
156 |
157 | async function getWeathyCloset(weathyId) {
158 | try {
159 | const closet = await setClothesForm();
160 |
161 | const weathyClothes = await WeathyClothes.findAll({
162 | include: [
163 | {
164 | model: Clothes,
165 | required: true,
166 | paranoid: false,
167 | attributes: ['id', 'name'],
168 | include: [
169 | {
170 | model: ClothesCategory,
171 | required: true,
172 | paranoid: false,
173 | attributes: ['id', 'name']
174 | }
175 | ]
176 | }
177 | ],
178 | where: {
179 | weathy_id: weathyId
180 | }
181 | });
182 |
183 | for (let wc of weathyClothes) {
184 | const categoryName = wc.Clothe.ClothesCategory.name;
185 | const clothesId = wc.Clothe.id;
186 | const clothesName = wc.Clothe.name;
187 |
188 | closet[categoryName].clothes.push({
189 | id: clothesId,
190 | name: clothesName
191 | });
192 | }
193 |
194 | for (let category of Object.keys(closet)) {
195 | closet[category].clothesNum = closet[category].clothes.length;
196 | }
197 |
198 | return closet;
199 | } catch (err) {
200 | throw Error(exception.SERVER_ERROR);
201 | }
202 | }
203 |
204 | async function addClothesByUserId(userId, category, name) {
205 | // 이미 한 번 지워졌었던 값인지 확인하는 작업이 필요하다
206 | const alreadyClothes = await Clothes.findOne({
207 | where: { user_id: userId, category_id: category, name: name }
208 | });
209 |
210 | if (alreadyClothes === null) {
211 | await Clothes.create({
212 | user_id: userId,
213 | category_id: category,
214 | name: name,
215 | is_deleted: 0
216 | });
217 | } else if (alreadyClothes.is_deleted === 0) {
218 | throw Error(exception.ALREADY_CLOTHES);
219 | } else {
220 | await Clothes.update(
221 | { is_deleted: 0 },
222 | {
223 | where: {
224 | user_id: userId,
225 | category_id: category,
226 | name: name
227 | }
228 | }
229 | );
230 | }
231 |
232 | const responseCloset = new Object();
233 | const clothesCategories = await ClothesCategory.findAll();
234 |
235 | for (const ca of clothesCategories) {
236 | const categoryClothes = await Clothes.findAll({
237 | where: {
238 | user_id: userId,
239 | category_id: ca.id,
240 | is_deleted: 0
241 | },
242 | order: [
243 | ['updated_at', 'DESC'],
244 | ['id', 'DESC']
245 | ]
246 | });
247 | const categoryCloset = new Object();
248 | categoryCloset.categoryId = ca.id;
249 | const categoryClothesList = new Array();
250 | await categoryClothes.forEach((element) => {
251 | const clothes = new Object();
252 | clothes.id = element.id;
253 | clothes.name = element.name;
254 | categoryClothesList.push(clothes);
255 | });
256 | if (ca.id === category) {
257 | categoryCloset.clothes = categoryClothesList;
258 | } else {
259 | categoryCloset.clothes = [];
260 | }
261 | categoryCloset.clothesNum = categoryCloset.clothes.length;
262 | responseCloset[ca.name] = categoryCloset;
263 | }
264 | return responseCloset;
265 | }
266 |
267 | async function deleteClothesByUserId(userId, clothesList) {
268 | // If there are invalid clothes in clothesList, throw error
269 | for (let c in clothesList) {
270 | let cl = await Clothes.findOne({
271 | where: { user_id: userId, id: clothesList[c] }
272 | });
273 |
274 | if (cl === null || cl.is_deleted === 1) {
275 | throw Error(exception.NO_CLOTHES);
276 | }
277 | }
278 |
279 | await Clothes.update(
280 | {
281 | is_deleted: 1
282 | },
283 | {
284 | where: {
285 | id: {
286 | [Op.in]: clothesList
287 | }
288 | }
289 | }
290 | );
291 |
292 | const responseCloset = await getClothesByUserId(userId);
293 | return responseCloset;
294 | }
295 |
296 | async function createClothesByName(userId, category, name) {
297 | // name으로 userId의 clothes 만들기
298 | await Clothes.create({
299 | user_id: userId,
300 | category_id: category,
301 | name: name,
302 | is_deleted: 0
303 | });
304 | }
305 |
306 | async function createDefaultClothes(userId) {
307 | // 유저 생성될 때 기본으로 생성되는 clothes들 만들기
308 | await createClothesByName(userId, 1, '니트');
309 | await createClothesByName(userId, 1, '후드티');
310 | await createClothesByName(userId, 1, '티셔츠');
311 | await createClothesByName(userId, 1, '셔츠');
312 | await createClothesByName(userId, 1, '블라우스');
313 | await createClothesByName(userId, 1, '나시');
314 | await createClothesByName(userId, 2, '청바지');
315 | await createClothesByName(userId, 2, '슬랙스');
316 | await createClothesByName(userId, 2, '면바지');
317 | await createClothesByName(userId, 2, '트레이닝복');
318 | await createClothesByName(userId, 2, '스커트');
319 | await createClothesByName(userId, 2, '레깅스');
320 | await createClothesByName(userId, 3, '패딩');
321 | await createClothesByName(userId, 3, '코트');
322 | await createClothesByName(userId, 3, '재킷');
323 | await createClothesByName(userId, 3, '점퍼');
324 | await createClothesByName(userId, 3, '가디건');
325 | await createClothesByName(userId, 3, '경량패딩');
326 | await createClothesByName(userId, 4, '목도리');
327 | await createClothesByName(userId, 4, '장갑');
328 | await createClothesByName(userId, 4, '모자');
329 | await createClothesByName(userId, 4, '부츠');
330 | await createClothesByName(userId, 4, '스니커즈');
331 | await createClothesByName(userId, 4, '로퍼');
332 | }
333 |
334 | module.exports = {
335 | unionTwoCloset,
336 | getClothesNumByUserId,
337 | getClothesByUserId,
338 | getClothesByWeathyId,
339 | addClothesByUserId,
340 | deleteClothesByUserId,
341 | getWeathyCloset,
342 | createDefaultClothes
343 | };
344 |
--------------------------------------------------------------------------------
/WeathyServer/services/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | tokenService: require('./tokenService'),
3 | userService: require('./userService'),
4 | clothesService: require('./clothesService'),
5 | climateService: require('./climateService'),
6 | locationService: require('./locationService'),
7 | weatherService: require('./weatherService'),
8 | weathyService: require('./weathyService'),
9 | calendarService: require('./calendarService')
10 | };
11 |
--------------------------------------------------------------------------------
/WeathyServer/services/locationService.js:
--------------------------------------------------------------------------------
1 | const { Location } = require('../models');
2 | const Sequelize = require('sequelize');
3 | const exception = require('../modules/exception');
4 | const request = require('request-promise');
5 | const apiKey = require('../config/kakao.json')['apiKey'];
6 |
7 | module.exports = {
8 | getCode: async (lat, lng) => {
9 | let response;
10 | try {
11 | response = await request.get(
12 | 'https://dapi.kakao.com/v2/local/geo/coord2regioncode.json?x=' +
13 | lng +
14 | '&y=' +
15 | lat,
16 | {
17 | headers: {
18 | Authorization: apiKey
19 | },
20 | json: true
21 | }
22 | );
23 | } catch (err) {
24 | throw Error(exception.SERVER_ERROR);
25 | }
26 |
27 | for (let i = 0; i < response.documents.length; ++i) {
28 | if (response.documents[i].region_type == 'H') {
29 | return parseInt(response.documents[i].code / 100000) * 100000;
30 | }
31 | }
32 | },
33 | getLocationByCode: async (code) => {
34 | const location = await Location.findOne({ where: { id: code } });
35 | if (!location) {
36 | throw Error(exception.NO_DATA);
37 | }
38 | return {
39 | code: location.id,
40 | name: location.name
41 | };
42 | },
43 | getLocationsByKeyword: async (keyword) => {
44 | const Op = Sequelize.Op;
45 | const locations = await Location.findAll({
46 | where: {
47 | name: {
48 | [Op.like]: '%' + keyword + '%'
49 | }
50 | },
51 | attributes: [['id', 'code'], 'name']
52 | });
53 | return locations;
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/WeathyServer/services/tokenService.js:
--------------------------------------------------------------------------------
1 | const { Token } = require('../models');
2 | const { generateToken } = require('../utils/tokenUtils');
3 |
4 | module.exports = {
5 | createTokenOfUser: async (user_id) => {
6 | const token = generateToken(user_id);
7 | await Token.create({
8 | user_id: user_id,
9 | token: token
10 | });
11 | return token;
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/WeathyServer/services/userService.js:
--------------------------------------------------------------------------------
1 | const { User } = require('../models');
2 | const exception = require('../modules/exception');
3 | const { createDefaultClothes } = require('../services/clothesService');
4 |
5 | module.exports = {
6 | getUserByAccount: async (uuid) => {
7 | // uuid로 user 가져오기
8 | const user = await User.findOne({ where: { uuid: uuid } });
9 | if (user === null) {
10 | throw Error(exception.NO_USER);
11 | } else {
12 | return user;
13 | }
14 | },
15 | createUserByUuid: async (uuid, nickname) => {
16 | // uuid, nickname 으로 유저 생성
17 | // 이미 같은 uuid를 가진 유저가 있는지 확인
18 | const alreadyUser = await User.findOne({
19 | where: {
20 | uuid
21 | }
22 | });
23 | if (alreadyUser != null) {
24 | // 이미 같은 uuid를 가진 유저가 있음
25 | throw Error(exception.ALREADY_USER);
26 | }
27 |
28 | const user = await User.create({
29 | nickname,
30 | uuid
31 | });
32 |
33 | await createDefaultClothes(user.id);
34 | return user;
35 | },
36 | modifyUserById: async (userId, nickname) => {
37 | await User.update({ nickname: nickname }, { where: { id: userId } });
38 | const user = await User.findOne({ where: { id: userId } });
39 | return user;
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/WeathyServer/services/weatherService.js:
--------------------------------------------------------------------------------
1 | const dateUtils = require('../utils/dateUtils');
2 | const exception = require('../modules/exception');
3 | const { DailyWeather, HourlyWeather } = require('../models');
4 | const climateService = require('./climateService');
5 | const locationService = require('./locationService');
6 |
7 | const getRainRating = (value) => {
8 | if (value < 1) return 1;
9 | else if (value < 5) return 2;
10 | else if (value < 20) return 3;
11 | else if (value < 80) return 4;
12 | else if (value < 150) return 5;
13 | else return 6;
14 | };
15 |
16 | const getHumidityRating = (value) => {
17 | if (value <= 20) return 1;
18 | else if (value <= 40) return 2;
19 | else if (value <= 60) return 3;
20 | else if (value <= 80) return 4;
21 | else if (value <= 100) return 5;
22 | };
23 |
24 | const getWindRating = (value) => {
25 | if (value <= 1) return 1;
26 | else if (value <= 4) return 2;
27 | else if (value <= 8) return 3;
28 | else if (value <= 12) return 4;
29 | else if (value <= 17) return 5;
30 | else return 6;
31 | };
32 |
33 | const getDailyWeather = async (code, date) => {
34 | const dailyWeather = await DailyWeather.findOne({
35 | where: { location_id: code, date: date }
36 | });
37 |
38 | if (!dailyWeather) {
39 | return null;
40 | }
41 | return {
42 | date: {
43 | year: dateUtils.getYear(dailyWeather.date),
44 | month: dateUtils.getMonth(dailyWeather.date),
45 | day: dateUtils.getDay(dailyWeather.date),
46 | dayOfWeek: dateUtils.getYoil(dailyWeather.date)
47 | },
48 | temperature: {
49 | maxTemp: dailyWeather.temperature_max,
50 | minTemp: dailyWeather.temperature_min
51 | }
52 | };
53 | };
54 |
55 | const getHourlyWeather = async (code, date, hour, timeFormat) => {
56 | const hourlyWeather = await HourlyWeather.findOne({
57 | where: { location_id: code, date: date, hour: hour }
58 | });
59 | if (!hourlyWeather) {
60 | return null;
61 | }
62 | return {
63 | time: timeFormat(hourlyWeather.hour),
64 | temperature: hourlyWeather.temperature,
65 | climate: await climateService.getClimate(
66 | hourlyWeather.climate_id,
67 | hourlyWeather.temperature
68 | ),
69 | pop: hourlyWeather.pop
70 | };
71 | };
72 | const getDailyWeatherWithClimateIconId = async (code, date) => {
73 | const dailyWeather = await DailyWeather.findOne({
74 | where: { location_id: code, date: date }
75 | });
76 | if (!dailyWeather) {
77 | return null;
78 | }
79 | return {
80 | date: {
81 | month: dateUtils.getMonth(dailyWeather.date),
82 | day: dateUtils.getDay(dailyWeather.date),
83 | dayOfWeek: dateUtils.getYoil(dailyWeather.date)
84 | },
85 | temperature: {
86 | maxTemp: dailyWeather.temperature_max,
87 | minTemp: dailyWeather.temperature_min
88 | },
89 | climateIconId: dailyWeather.climate_id
90 | };
91 | };
92 |
93 | module.exports = {
94 | getHourlyWeather,
95 | getDailyWeather,
96 | getDailyWeatherWithClimateIconId,
97 | getOverviewWeather: async (code, date, hour, timeFormat) => {
98 | let dailyClimate;
99 | if (!hour) {
100 | hour = 12;
101 |
102 | const dailyWeatherWithClimate = await getDailyWeatherWithClimateIconId(
103 | code,
104 | date
105 | );
106 |
107 | if (!dailyWeatherWithClimate) return null;
108 |
109 | dailyClimate = await climateService.getClimateByIconId(
110 | dailyWeatherWithClimate.climateIconId
111 | );
112 | }
113 | const location = await locationService.getLocationByCode(code);
114 | const dailyWeather = await getDailyWeather(code, date);
115 | const hourlyWeather = await getHourlyWeather(
116 | code,
117 | date,
118 | hour,
119 | timeFormat
120 | );
121 |
122 | if (!dailyWeather || !hourlyWeather) {
123 | return null;
124 | }
125 |
126 | if (dailyClimate) hourlyWeather.climate = dailyClimate;
127 |
128 | return {
129 | region: location,
130 | dailyWeather,
131 | hourlyWeather
132 | };
133 | },
134 |
135 | getOverviewWeathers: async (keyword, date, hour, timeFormat) => {
136 | let defaultClimateFlag = false;
137 |
138 | if (!hour) {
139 | hour = 12;
140 | defaultClimateFlag = true;
141 | }
142 |
143 | const locations = await locationService.getLocationsByKeyword(keyword);
144 | let overviewWeatherList = [];
145 |
146 | for (let i = 0; i < locations.length; ++i) {
147 | const location = locations[i];
148 | const dailyWeather = await getDailyWeather(
149 | location.dataValues.code,
150 | date
151 | );
152 | const hourlyWeather = await getHourlyWeather(
153 | location.dataValues.code,
154 | date,
155 | hour,
156 | timeFormat
157 | );
158 | if (!dailyWeather || !hourlyWeather) {
159 | continue;
160 | }
161 | if (defaultClimateFlag) {
162 | const dailyWeatherWithClimate = await getDailyWeatherWithClimateIconId(
163 | location.dataValues.code,
164 | date
165 | );
166 |
167 | if (!dailyWeatherWithClimate) continue;
168 |
169 | hourlyWeather.climate = await climateService.getClimateByIconId(
170 | dailyWeatherWithClimate.climateIconId
171 | );
172 | }
173 |
174 | overviewWeatherList.push({
175 | region: location,
176 | dailyWeather,
177 | hourlyWeather
178 | });
179 | }
180 | return overviewWeatherList;
181 | },
182 |
183 | getExtraDailyWeather: async (code, date) => {
184 | const dailyWeather = await DailyWeather.findOne({
185 | where: { location_id: code, date: date }
186 | });
187 | if (!dailyWeather) {
188 | throw Error(exception.NO_DATA);
189 | }
190 | return {
191 | rain: {
192 | value: dailyWeather.precipitation,
193 | rating: getRainRating(dailyWeather.precipitation)
194 | },
195 | humidity: {
196 | value: dailyWeather.humidity,
197 | rating: getHumidityRating(dailyWeather.humidity)
198 | },
199 | wind: {
200 | value: dailyWeather.wind_speed,
201 | rating: getWindRating(dailyWeather.wind_speed)
202 | }
203 | };
204 | },
205 |
206 | getDailyClimateId: async (code, date) => {
207 | const dailyWeather = await DailyWeather.findOne({
208 | where: { location_id: code, date }
209 | });
210 | if (!dailyWeather) {
211 | return null;
212 | }
213 | return {
214 | climateId: dailyWeather.climate_id
215 | };
216 | },
217 |
218 | getDailyWeatherId: async (code, date) => {
219 | const dailyWeather = await DailyWeather.findOne({
220 | where: {
221 | location_id: code,
222 | date
223 | }
224 | });
225 |
226 | if (!dailyWeather) {
227 | return null;
228 | }
229 |
230 | return dailyWeather.id;
231 | }
232 | };
233 |
--------------------------------------------------------------------------------
/WeathyServer/services/weathyService.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const { Op, literal, UniqueConstraintError } = require('sequelize');
3 | const { format12 } = require('../utils/dateUtils');
4 | const {
5 | Weathy,
6 | DailyWeather,
7 | sequelize,
8 | WeathyClothes,
9 | Clothes
10 | } = require('../models');
11 | const locationService = require('./locationService');
12 | const weatherService = require('./weatherService');
13 | const clothesService = require('./clothesService');
14 | const climateService = require('./climateService');
15 | const exception = require('../modules/exception');
16 | const { getDailyClimateId } = require('./weatherService');
17 |
18 | const calculateConditionPoint = (candidate, todayWeather) => {
19 | const { todayTemp, todayClimateId } = todayWeather;
20 | const { minTemp: todayMinTemp, maxTemp: todayMaxTemp } = todayTemp;
21 | const {
22 | temperature_min: pastMinTemp,
23 | temperature_max: pastMaxTemp,
24 | climate_id: pastClimateId
25 | } = candidate.DailyWeather;
26 |
27 | const condition1 =
28 | (Math.abs(todayMaxTemp - pastMaxTemp) +
29 | Math.abs(todayMinTemp - pastMinTemp)) *
30 | 3;
31 | const condition2 =
32 | Math.abs(
33 | Math.abs(todayMaxTemp - todayMinTemp) -
34 | Math.abs(pastMaxTemp - pastMinTemp)
35 | ) * 2;
36 | const condition3 = todayClimateId % 100 === pastClimateId % 100 ? 1 : 0;
37 |
38 | return condition1 + condition2 + condition3;
39 | };
40 |
41 | const getWeathyOnDate = async (date, userId) => {
42 | const weathy = await Weathy.findOne({
43 | include: [
44 | {
45 | model: DailyWeather,
46 | required: true,
47 | attributes: [
48 | 'temperature_max',
49 | 'temperature_min',
50 | 'climate_id',
51 | 'location_id',
52 | 'date'
53 | ],
54 | where: {
55 | date
56 | }
57 | }
58 | ],
59 | where: {
60 | user_id: userId
61 | }
62 | });
63 |
64 | return weathy;
65 | };
66 |
67 | const selectBestDate = (todayWeather, candidates) => {
68 | const SUITABLE_STAMP_ID = 3;
69 |
70 | let weathy = {
71 | recommend: undefined,
72 | point: undefined
73 | };
74 |
75 | for (let candidate of candidates) {
76 | let point = calculateConditionPoint(candidate, todayWeather);
77 |
78 | //init
79 | if (!weathy.recommend) {
80 | weathy.recommend = candidate;
81 | weathy.point = point;
82 | }
83 |
84 | if (point < weathy.point) {
85 | weathy.recommend = candidate;
86 | weathy.point = point;
87 | } else if (
88 | weathy.point === point &&
89 | candidate.emoji_id === SUITABLE_STAMP_ID
90 | ) {
91 | weathy.recommend = candidate;
92 | }
93 | }
94 |
95 | if (!weathy.recommend) return null;
96 |
97 | return weathy.recommend.DailyWeather.date;
98 | };
99 |
100 | const getSuitableWeathers = (todayWeather, weathies) => {
101 | const { todayTemp } = todayWeather;
102 | const { minTemp: todayMinTemp, maxTemp: todayMaxTemp } = todayTemp;
103 | const weathyCase = {
104 | 1: [],
105 | 2: []
106 | };
107 |
108 | for (let w of weathies) {
109 | const {
110 | temperature_min: pastMinTemp,
111 | temperature_max: pastMaxTemp
112 | } = w.DailyWeather;
113 |
114 | const maxTempPoint = Math.abs(todayMaxTemp - pastMaxTemp);
115 | const minTempPoint = Math.abs(todayMinTemp - pastMinTemp);
116 |
117 | if (maxTempPoint <= 2 && minTempPoint <= 2) weathyCase[1].push(w);
118 | else if (maxTempPoint <= 2 || minTempPoint <= 2) weathyCase[2].push(w);
119 | }
120 |
121 | if (weathyCase[1].length !== 0) return weathyCase[1];
122 |
123 | return weathyCase[2];
124 | };
125 |
126 | const loadWeatherOnDate = async (code, date) => {
127 | const dailyWeather = await weatherService.getDailyWeather(code, date);
128 | const dailyClimateId = await weatherService.getDailyClimateId(code, date);
129 |
130 | if (!dailyWeather || !dailyClimateId) return null;
131 |
132 | const { temperature: todayTemp } = dailyWeather;
133 | const { climateId: todayClimateId } = dailyClimateId;
134 |
135 | if (!todayTemp || !todayClimateId) return null;
136 |
137 | return {
138 | todayTemp,
139 | todayClimateId
140 | };
141 | };
142 |
143 | const getMostSimilarDate = async (code, date, candidates) => {
144 | const todayWeather = await loadWeatherOnDate(code, date); //현재 지역의 날씨 로드
145 |
146 | if (!todayWeather) return null;
147 |
148 | const candidatesOfSuitableCase = getSuitableWeathers(
149 | todayWeather,
150 | candidates
151 | ); //적합한 case의 weathers 가져옴
152 |
153 | return selectBestDate(todayWeather, candidatesOfSuitableCase);
154 | };
155 |
156 | const upsertWeathyClothes = async (clothes, weathyId, transaction) => {
157 | const clothesDBForm = [];
158 |
159 | await WeathyClothes.destroy(
160 | {
161 | where: {
162 | weathy_id: weathyId
163 | }
164 | },
165 | { transaction }
166 | );
167 |
168 | for (let c of clothes) {
169 | clothesDBForm.push({
170 | weathy_id: weathyId,
171 |
172 | clothes_id: c
173 | });
174 | }
175 |
176 | await WeathyClothes.bulkCreate(clothesDBForm, { transaction });
177 | };
178 |
179 | const loadWeathiesInSixtyDays = async (date, userId) => {
180 | const sixtyAgo = dayjs(date).subtract(60, 'day').format('YYYY-MM-DD');
181 |
182 | const weathies = await Weathy.findAll({
183 | include: [
184 | {
185 | model: DailyWeather,
186 | required: true,
187 | attributes: [
188 | 'temperature_max',
189 | 'temperature_min',
190 | 'climate_id',
191 | 'location_id',
192 | 'date'
193 | ],
194 | where: {
195 | date: {
196 | [Op.lt]: date,
197 | [Op.gte]: sixtyAgo
198 | }
199 | }
200 | }
201 | ],
202 | where: {
203 | user_id: userId
204 | },
205 |
206 | order: literal('DailyWeather.date ASC')
207 | });
208 |
209 | return weathies;
210 | };
211 |
212 | const getRecommendedWeathy = async (code, date, userId) => {
213 | const candidates = await loadWeathiesInSixtyDays(date, userId);
214 | const mostSimilarDate = await getMostSimilarDate(code, date, candidates);
215 |
216 | if (!mostSimilarDate) return null;
217 |
218 | const recommendedWeathy = await getWeathy(mostSimilarDate, userId);
219 |
220 | return recommendedWeathy;
221 | };
222 |
223 | const checkOwnerClothes = async (clothes, userId) => {
224 | const clothesIdSet = new Set();
225 |
226 | const clothesList = await Clothes.findAll({
227 | where: {
228 | user_id: userId
229 | },
230 | attributes: ['id']
231 | });
232 |
233 | for (let c of clothesList) {
234 | clothesIdSet.add(c.id);
235 | }
236 | for (let c of clothes) {
237 | if (!clothesIdSet.has(c)) return false;
238 | }
239 |
240 | return true;
241 | };
242 |
243 | const findDailyWeatherByWeathy = async (
244 | weathyId,
245 | code,
246 | userId,
247 | transaction
248 | ) => {
249 | const target = await Weathy.findOne(
250 | {
251 | include: [
252 | {
253 | model: DailyWeather,
254 | required: true,
255 | attributes: ['id', 'date']
256 | }
257 | ],
258 | where: {
259 | id: weathyId,
260 | user_id: userId
261 | }
262 | },
263 | { transaction }
264 | );
265 |
266 | if (!target) return null;
267 |
268 | const dailyWeatherDate = target.DailyWeather.date;
269 | const dailyWeather = await DailyWeather.findOne(
270 | {
271 | where: {
272 | date: dailyWeatherDate,
273 | location_id: code
274 | }
275 | },
276 | { transaction }
277 | );
278 |
279 | return dailyWeather;
280 | };
281 |
282 | const getWeathy = async (date, userId) => {
283 | const weathy = await getWeathyOnDate(date, userId);
284 |
285 | if (!weathy) return null;
286 |
287 | const { location_id: code } = weathy.DailyWeather;
288 | const dailyWeather = await weatherService.getDailyWeather(code, date);
289 | const hourlyWeather = await weatherService.getHourlyWeather(
290 | code,
291 | date,
292 | 12,
293 | format12
294 | );
295 |
296 | if (!hourlyWeather) return null;
297 |
298 | // Hotfix. Should be updated in the future.
299 | const dailyClimate = await getDailyClimateId(code, date);
300 | hourlyWeather.climate.iconId = dailyClimate.climateId;
301 | hourlyWeather.climate = await climateService.getClimateByIconId(
302 | hourlyWeather.climate.iconId
303 | );
304 |
305 | const region = await locationService.getLocationByCode(code);
306 | const closet = await clothesService.getWeathyCloset(weathy.id);
307 |
308 | return {
309 | weathy: {
310 | region,
311 | dailyWeather,
312 | hourlyWeather,
313 | closet,
314 | weathyId: weathy.id,
315 | stampId: weathy.emoji_id,
316 | feedback: weathy.description || null,
317 | imgUrl: weathy.img_url || null
318 | }
319 | };
320 | };
321 |
322 | const createWeathy = async (
323 | dailyWeatherId,
324 | clothes,
325 | stampId,
326 | userId,
327 | feedback = null,
328 | imgUrl = null
329 | ) => {
330 | const transaction = await sequelize.transaction();
331 |
332 | try {
333 | const weathy = await Weathy.create(
334 | {
335 | user_id: userId,
336 | dailyweather_id: dailyWeatherId,
337 | emoji_id: stampId,
338 | description: feedback,
339 | img_url: imgUrl
340 | },
341 | { transaction }
342 | );
343 |
344 | await upsertWeathyClothes(clothes, weathy.id, transaction);
345 |
346 | await transaction.commit();
347 | return weathy.id;
348 | } catch (err) {
349 | await transaction.rollback();
350 |
351 | if (err instanceof UniqueConstraintError) {
352 | throw Error(exception.DUPLICATION_WEATHY);
353 | }
354 |
355 | throw Error(exception.SERVER_ERROR);
356 | }
357 | };
358 |
359 | const deleteWeathy = async (weathyId, userId) => {
360 | try {
361 | const deletedWeathy = await Weathy.destroy({
362 | where: {
363 | user_id: userId,
364 | id: weathyId
365 | }
366 | });
367 |
368 | return deletedWeathy;
369 | } catch (err) {
370 | throw Error(exception.SERVER_ERROR);
371 | }
372 | };
373 |
374 | const modifyWeathy = async (
375 | weathyId,
376 | userId,
377 | code,
378 | clothes,
379 | stampId,
380 | feedback = null
381 | ) => {
382 | const transaction = await sequelize.transaction();
383 |
384 | try {
385 | const dailyWeather = await findDailyWeatherByWeathy(
386 | weathyId,
387 | code,
388 | userId,
389 | transaction
390 | );
391 |
392 | if (!dailyWeather) throw Error(exception.NO_DAILY_WEATHER);
393 |
394 | const isUpdated = await Weathy.update(
395 | {
396 | dailyweather_id: dailyWeather.id,
397 | emoji_id: stampId,
398 | description: feedback
399 | },
400 | {
401 | where: {
402 | user_id: userId,
403 | id: weathyId
404 | }
405 | },
406 | { transaction }
407 | );
408 |
409 | await upsertWeathyClothes(clothes, weathyId, transaction);
410 |
411 | await transaction.commit();
412 |
413 | return isUpdated;
414 | } catch (err) {
415 | await transaction.rollback();
416 |
417 | if (err.message === exception.NO_DAILY_WEATHER) {
418 | throw Error(exception.NO_DAILY_WEATHER);
419 | }
420 | if (err instanceof UniqueConstraintError) {
421 | throw Error(exception.DUPLICATION_WEATHY);
422 | }
423 | throw Error(exception.SERVER_ERROR);
424 | }
425 | };
426 |
427 | const modifyImgField = async (imgUrl = null, weathyId, userId) => {
428 | try {
429 | await Weathy.update(
430 | {
431 | img_url: imgUrl
432 | },
433 | {
434 | where: {
435 | user_id: userId,
436 | id: weathyId
437 | }
438 | }
439 | );
440 | } catch (err) {
441 | throw Error(exception.SERVER_ERROR);
442 | }
443 | };
444 |
445 | const isDuplicateWeathy = async (dailyWeatherId, userId) => {
446 | try {
447 | const count = await Weathy.count({
448 | where: {
449 | user_id: userId,
450 | dailyweather_id: dailyWeatherId
451 | }
452 | });
453 | if (count) return true;
454 | return false;
455 | } catch (err) {
456 | throw Error(exception.SERVER_ERROR);
457 | }
458 | };
459 |
460 | module.exports = {
461 | getRecommendedWeathy,
462 | getWeathy,
463 | createWeathy,
464 | deleteWeathy,
465 | modifyWeathy,
466 | checkOwnerClothes,
467 | modifyImgField,
468 | isDuplicateWeathy
469 | };
470 |
--------------------------------------------------------------------------------
/WeathyServer/tests/modules/logger.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const decache = require('decache');
3 | const { transports } = require('winston');
4 | let logger = require('../../modules/logger');
5 |
6 | describe('logger test', function () {
7 | let prev_node_env = process.env.NODE_ENV;
8 | beforeEach('init cache', () => {
9 | decache('../../modules/logger');
10 | logger = require('../../modules/logger');
11 | });
12 | describe('production mode test', () => {
13 | before('set NODE_ENV to production', () => {
14 | process.env.NODE_ENV = 'production';
15 | });
16 | after('reset NODE_ENV', () => {
17 | if (!prev_node_env) {
18 | delete process.env.NODE_ENV;
19 | } else {
20 | process.env.NODE_ENV = prev_node_env;
21 | }
22 | });
23 | it('production mode not logs to console', () => {
24 | for (let i = 0; i < logger.transports.length; ++i) {
25 | assert.ok(
26 | !(logger.transports[i] instanceof transports.Console)
27 | );
28 | }
29 | });
30 | });
31 | describe('development mode test', () => {
32 | before('set NODE_ENV to development', () => {
33 | process.env.NODE_ENV = 'development';
34 | });
35 | after('reset NODE_ENV', () => {
36 | if (!prev_node_env) {
37 | delete process.env.NODE_ENV;
38 | } else {
39 | process.env.NODE_ENV = prev_node_env;
40 | }
41 | });
42 | it('development mode logs to console', () => {
43 | let cnt_console_logger = 0;
44 | for (let i = 0; i < logger.transports.length; ++i) {
45 | if (logger.transports[i] instanceof transports.Console) {
46 | cnt_console_logger += 1;
47 | }
48 | }
49 | assert.ok(cnt_console_logger > 0);
50 | });
51 | });
52 | });
53 |
--------------------------------------------------------------------------------
/WeathyServer/tests/modules/tokenMiddleware.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const httpMocks = require('node-mocks-http');
3 | const dayjs = require('dayjs');
4 | const { Token } = require('../../models');
5 | const { validateToken, updateToken } = require('../../modules/tokenMiddleware');
6 |
7 | describe('tokenMiddleware test', function () {
8 | describe('validateToken test', () => {
9 | // testing tokens::
10 | // userID 22 (Should be Updated)
11 | // userID 25 (Should not be Updated)
12 |
13 | const firstTestUserId = 22;
14 | const secondTestUserId = 25;
15 | let firstTestUserToken, secondTestUserToken;
16 |
17 | before('get values, and update tokens used for test', async () => {
18 | const firstTestUserTokenObj = await Token.findOne({
19 | where: { user_id: firstTestUserId }
20 | });
21 | firstTestUserToken = firstTestUserTokenObj.token;
22 | const secondTestUserTokenObj = await Token.findOne({
23 | where: { user_id: secondTestUserId }
24 | });
25 | secondTestUserToken = secondTestUserTokenObj.token;
26 |
27 | const req = httpMocks.createRequest();
28 | const res = httpMocks.createResponse({
29 | locals: {
30 | tokenValue: firstTestUserToken
31 | }
32 | });
33 | await updateToken(req, res);
34 | });
35 |
36 | it('If there is no token on req, throw error', async () => {
37 | const req = httpMocks.createRequest();
38 | const res = httpMocks.createResponse();
39 |
40 | await validateToken(req, res, (next) => {
41 | assert.ok(next instanceof Error);
42 | });
43 | });
44 |
45 | it('If there is no matching token on DB, throw error', async () => {
46 | const req = httpMocks.createRequest({
47 | headers: {
48 | 'x-access-token': 'THIS_IS_FAKE_TOKEN'
49 | }
50 | });
51 | const res = httpMocks.createResponse();
52 |
53 | await validateToken(req, res, (next) => {
54 | assert.ok(next instanceof Error);
55 | });
56 | });
57 |
58 | it("If param's userId and token's userId is different, throw error", async () => {
59 | const req = httpMocks.createRequest({
60 | headers: {
61 | 'x-access-token': firstTestUserToken
62 | },
63 | params: {
64 | userId: secondTestUserId
65 | }
66 | });
67 | const res = httpMocks.createResponse();
68 |
69 | await validateToken(req, res, (next) => {
70 | assert.ok(next instanceof Error);
71 | });
72 | });
73 |
74 | it('If token is expired, throw error', async () => {
75 | const req = httpMocks.createRequest({
76 | headers: {
77 | 'x-access-token': secondTestUserToken
78 | }
79 | });
80 | const res = httpMocks.createResponse();
81 |
82 | await validateToken(req, res, (next) => {
83 | assert.ok(next instanceof Error);
84 | });
85 | });
86 |
87 | it('If token is valid, does not throw error (No param)', async () => {
88 | const req = httpMocks.createRequest({
89 | headers: {
90 | 'x-access-token': firstTestUserToken
91 | }
92 | });
93 | const res = httpMocks.createResponse();
94 |
95 | await validateToken(req, res, (next) => {
96 | assert.ok(!(next instanceof Error));
97 | });
98 | });
99 |
100 | it('If token is valid, does not throw error (Has param)', async () => {
101 | const req = httpMocks.createRequest({
102 | headers: {
103 | 'x-access-token': firstTestUserToken
104 | },
105 | params: {
106 | userId: firstTestUserId
107 | }
108 | });
109 | const res = httpMocks.createResponse();
110 |
111 | await validateToken(req, res, (next) => {
112 | assert.ok(!(next instanceof Error));
113 | });
114 | });
115 | });
116 |
117 | describe('updateToken test', () => {
118 | // testing tokens::
119 | // userID 22 (Gets Updated)
120 | const testUserId = 22;
121 | let testUserToken;
122 | let beforeUpdatedTime;
123 |
124 | before('get values used for test', async () => {
125 | const testUserTokenObj = await Token.findOne({
126 | where: { user_id: testUserId }
127 | });
128 | testUserToken = testUserTokenObj.token;
129 | beforeUpdatedTime = dayjs(testUserTokenObj.updated_at);
130 | });
131 |
132 | it('Token should be updated', async () => {
133 | const req = httpMocks.createRequest();
134 | const res = httpMocks.createResponse({
135 | locals: {
136 | tokenValue: testUserToken
137 | }
138 | });
139 | await updateToken(req, res);
140 |
141 | const afterTestUserTokenObj = await Token.findOne({
142 | where: { user_id: testUserId }
143 | });
144 | const afterUpdatedTime = dayjs(afterTestUserTokenObj.updated_at);
145 |
146 | assert.ok(afterUpdatedTime.isAfter(beforeUpdatedTime));
147 | assert.ok(beforeUpdatedTime.isBefore(afterUpdatedTime));
148 | });
149 | });
150 | });
151 |
--------------------------------------------------------------------------------
/WeathyServer/tests/services/calendarService.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const { calendarService } = require('../../services');
3 |
4 | describe('calendarService test', function () {
5 | describe('getValidCalendarOverviewList test', function () {
6 | it('getValidCalendarOverviewList returns CalendarOverviewList', async function () {
7 | const validCalendarOverviews = calendarService.getValidCalendarOverviewList(
8 | 1,
9 | '2021-01-01',
10 | '2021-01-02'
11 | );
12 | assert.ok((await validCalendarOverviews).length, 2);
13 | });
14 |
15 | it('getValidCalendarOverviewList returns CalendarOverviewList', async function () {
16 | const validCalendarOverviews = calendarService.getValidCalendarOverviewList(
17 | 1,
18 | '2021-01-01',
19 | '2021-01-08'
20 | );
21 | assert.ok((await validCalendarOverviews).length, 2);
22 | });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/WeathyServer/tests/services/climateService.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const { climateService } = require('../../services');
3 |
4 | describe('climate service test', function () {
5 | describe('getClimate test', function () {
6 | it('getClimateBy returns Climate test', async function () {
7 | const climate = await climateService.getClimate(1, 0);
8 | assert.ok(climate);
9 | assert.strictEqual(climate.iconId, 1);
10 | });
11 | it('getClimateBy returns different description every time', async function () {
12 | const climate_first = await climateService.getClimate(1, 0);
13 | const climate_second = await climateService.getClimate(1, 0);
14 | assert.ok(climate_first.description != climate_second.description);
15 | });
16 | it('getClimateBy throws error if not exists', async function () {
17 | await assert.rejects(async () => {
18 | await climateService.getClimate(-2, 0);
19 | });
20 | });
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/WeathyServer/tests/services/clothesService.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const { Clothes, Weathy } = require('../../models');
3 | const {
4 | getClothesByUserId,
5 | addClothesByUserId,
6 | deleteClothesByUserId,
7 | getClothesNumByUserId,
8 | getClothesByWeathyId
9 | } = require('../../services/clothesService');
10 | const { createWeathy, deleteWeathy } = require('../../services/weathyService');
11 |
12 | describe('clothesService test', function () {
13 | describe('getClothesNumByUserId test', () => {
14 | // uses userId = 450 (Has only original clothes: 12 clothes)
15 | const userId = 450;
16 | it('getClothesNumByUserId returns clothesNum', async () => {
17 | const firstClothesNum = await getClothesNumByUserId(userId);
18 | assert.strictEqual(firstClothesNum, 12);
19 |
20 | // add one clothes for test
21 | const testClothesName = 'Test_Cl';
22 | await addClothesByUserId(userId, 1, testClothesName);
23 | const secondClothesNum = await getClothesNumByUserId(userId);
24 | assert.strictEqual(secondClothesNum, 13);
25 |
26 | // delete the clothes
27 | const testClothes = await Clothes.findOne({
28 | where: {
29 | user_id: userId,
30 | name: testClothesName
31 | }
32 | });
33 | const clothesList = new Array();
34 | clothesList.push(testClothes.id);
35 | await deleteClothesByUserId(userId, clothesList);
36 | const thirdClothesNum = await getClothesNumByUserId(userId);
37 | assert.strictEqual(thirdClothesNum, 12);
38 | });
39 | });
40 |
41 | describe('getClothesByUserId test', () => {
42 | let closet, userId;
43 | before('get closet', async () => {
44 | userId = 35;
45 | closet = await getClothesByUserId(userId);
46 | });
47 | it('getClothesByUserId returns closet', async () => {
48 | assert.strictEqual(closet.bottom.categoryId, 2);
49 | assert.strictEqual(closet.bottom.clothes[0].id, 16);
50 | assert.strictEqual(closet.bottom.clothes[0].name, '바지1');
51 | });
52 | });
53 |
54 | describe('getClothesByWeathyID test', () => {
55 | const userId = 450;
56 | const testClothesName = 'testC';
57 | let testWeathyId, testClothesId;
58 | before('Add Clothes -> Record Weathy -> Delete Clothes', async () => {
59 | await addClothesByUserId(userId, 1, testClothesName);
60 | const testClothes = await Clothes.findOne({
61 | where: {
62 | user_id: userId,
63 | name: testClothesName
64 | }
65 | });
66 | testClothesId = testClothes.id;
67 | const recordedClothesList = [5035, 5040, 5043];
68 | recordedClothesList.push(testClothesId);
69 |
70 | await createWeathy(1, recordedClothesList, 3, userId, 'TEST');
71 |
72 | const testWeathy = await Weathy.findOne({
73 | where: {
74 | user_id: userId,
75 | dailyWeather_id: 1
76 | }
77 | });
78 | testWeathyId = testWeathy.id;
79 |
80 | const testClothesList = new Array();
81 | testClothesList.push(testClothesId);
82 |
83 | await deleteClothesByUserId(userId, testClothesList);
84 | });
85 |
86 | after('Delete Clothes and Weathy', async () => {
87 | await deleteWeathy(testWeathyId, userId);
88 | });
89 |
90 | it('getClothesByWeathyId should store clothes deleted after record', async () => {
91 | const closet = await getClothesByWeathyId(userId, testWeathyId);
92 | assert.strictEqual(closet.top.clothes[0].id, testClothesId);
93 | assert.strictEqual(closet.top.clothes[0].name, testClothesName);
94 | });
95 | });
96 |
97 | let userId, category, number, name;
98 | userId = 35;
99 | category = 1;
100 |
101 | describe('addClothesByUserId test', () => {
102 | it('First addition of clothes', async () => {
103 | number = Math.floor(Math.random() * 100000);
104 | name = '옷' + number;
105 |
106 | await addClothesByUserId(userId, category, name);
107 | const addedClothes = await Clothes.findOne({
108 | where: { category_id: category, name: name }
109 | });
110 |
111 | assert.ok(addedClothes !== null);
112 | });
113 |
114 | it('Second addition makes exception ALREADY_CLOTHES', async () => {
115 | await assert.rejects(async () => {
116 | await addClothesByUserId(userId, category, name);
117 | });
118 | });
119 | });
120 |
121 | let clothes = new Array();
122 | describe('deleteClothesByUserId test', () => {
123 | it('First deletion of clothes', async () => {
124 | const deletedBeforeClothes = await Clothes.findOne({
125 | where: { user_id: userId, category_id: category, name: name }
126 | });
127 | const deletedId = deletedBeforeClothes.id;
128 | clothes.push(deletedId);
129 | await deleteClothesByUserId(userId, clothes);
130 |
131 | const deletedAfterClothes = await Clothes.findOne({
132 | where: {
133 | user_id: userId,
134 | category_id: category,
135 | name: name,
136 | is_deleted: 0
137 | }
138 | });
139 |
140 | assert.strictEqual(deletedAfterClothes, null);
141 | }).timeout(15000);
142 |
143 | it('Second deletion makes exception NO_CLOTHES', async () => {
144 | await assert.rejects(async () => {
145 | await deleteClothesByUserId(userId, clothes);
146 | });
147 | });
148 |
149 | it("When deleting other user's clothes, throws error", async () => {
150 | const wrongUserId = userId + 1;
151 |
152 | await assert.rejects(async () => {
153 | await deleteClothesByUserId(wrongUserId, clothes);
154 | });
155 | });
156 | });
157 | });
158 |
--------------------------------------------------------------------------------
/WeathyServer/tests/services/locationService.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const { locationService } = require('../../services');
3 | const exception = require('../../modules/exception');
4 |
5 | describe('location service test', function () {
6 | describe('getLocationByCode test', function () {
7 | it('getLocationById returns location', async function () {
8 | const location = await locationService.getLocationByCode(
9 | 1100000000
10 | );
11 | assert.strictEqual(location.code, 1100000000);
12 | assert.strictEqual(location.name, '서울특별시');
13 | });
14 | it('getLocationById throws error if not exists', async function () {
15 | await assert.rejects(async () => {
16 | await locationService.getById(0);
17 | }, exception.NO_DATA);
18 | });
19 | });
20 | describe('getLocationsByKeyword', function () {
21 | it('getLocationsByKeyword returns locations', async function () {
22 | const locations = await locationService.getLocationsByKeyword(
23 | '서울'
24 | );
25 | assert.strictEqual(locations.length, 26);
26 | });
27 | it('getLocationsByKeyword returns empty array if not exists', async function () {
28 | const locations = await locationService.getLocationsByKeyword(
29 | '김자현'
30 | );
31 | assert.strictEqual(locations.length, 0);
32 | });
33 | });
34 | describe('getCode test', function () {
35 | it('getCode returns code', async function () {
36 | const code = await locationService.getCode(
37 | 37.57037778,
38 | 126.98164166666668
39 | );
40 | assert.strictEqual(code, 1111000000);
41 | });
42 | it('getCode throws error if not exists', async function () {
43 | await assert.ok(async () => {
44 | await locationService.getCode(-1, -1);
45 | }, exception.SERVER_ERROR);
46 | });
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/WeathyServer/tests/services/tokenService.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const { Token } = require('../../models');
3 | const { createTokenOfUser } = require('../../services/tokenService');
4 |
5 | describe('tokenService test', function () {
6 | describe('createTokenOfUser test', () => {
7 | const testUserId = 1;
8 | it('token should be created by user_id', async () => {
9 | await createTokenOfUser(testUserId);
10 | const token = await Token.findOne({
11 | where: { user_id: testUserId }
12 | });
13 | assert.ok(token !== null);
14 | });
15 |
16 | after('Delete created token', async () => {
17 | await Token.destroy({
18 | where: { user_id: testUserId }
19 | });
20 | });
21 | });
22 | });
23 |
--------------------------------------------------------------------------------
/WeathyServer/tests/services/userService.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const { User, Clothes } = require('../../models');
3 | const {
4 | getUserByAccount,
5 | createUserByUuid,
6 | modifyUserById
7 | } = require('../../services/userService');
8 |
9 | describe('userService test', function () {
10 | describe('getUserByAccount Test', () => {
11 | it('verify that user is obtained by uuid correctly', async () => {
12 | const uuid = 'test';
13 | const firstUser = await getUserByAccount(uuid);
14 | const secondUser = await User.findOne({ where: { id: 1 } });
15 | assert.ok(firstUser.id === secondUser.id);
16 | assert.ok(firstUser.nickname === secondUser.nickname);
17 | });
18 |
19 | it('if there is no user, exception NO_USER', async () => {
20 | const uuid = 'thisisnotuser';
21 | await assert.rejects(async () => {
22 | await getUserByAccount(uuid);
23 | });
24 | });
25 | });
26 |
27 | describe('createUserByUuid Test', () => {
28 | it('user should be correctly created', async () => {
29 | const number = Math.floor(Math.random() * 100000);
30 | const uuid = 'Test' + number;
31 | const nickname = 'Test';
32 | const firstUser = await createUserByUuid(uuid, nickname);
33 | const secondUser = await User.findOne({ order: [['id', 'DESC']] });
34 | assert.ok(firstUser.id === secondUser.id);
35 | assert.ok(firstUser.nickname === secondUser.nickname);
36 | const clothes = await Clothes.findOne({
37 | where: { user_id: firstUser.id, name: '티셔츠' }
38 | });
39 | assert.ok(clothes !== null);
40 | });
41 |
42 | it('if already uuid exists, exception ALRLEADY_USER', async () => {
43 | const uuid = 'test';
44 | const nickname = 'usertest';
45 |
46 | await assert.rejects(async () => {
47 | await createUserByUuid(uuid, nickname);
48 | });
49 | });
50 | });
51 |
52 | let originalNickname;
53 | describe('modifyUserById Test', () => {
54 | before('save original nickname', async () => {
55 | const user = await User.findOne({ where: { id: 1 } });
56 | originalNickname = user.nickname;
57 | });
58 |
59 | after('put nickname to original', async () => {
60 | await User.update(
61 | { nickname: originalNickname },
62 | { where: { id: 1 } }
63 | );
64 | });
65 |
66 | it('user nickname should be changed', async () => {
67 | const userId = 1;
68 | const nickname = 'yeonsang';
69 | await modifyUserById(userId, nickname);
70 | const user = await User.findOne({ where: { id: 1 } });
71 |
72 | assert.ok(user.nickname === 'yeonsang');
73 | });
74 | });
75 | });
76 |
--------------------------------------------------------------------------------
/WeathyServer/tests/services/weatherService.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const dateUtils = require('../../utils/dateUtils');
3 | const { weatherService } = require('../../services');
4 |
5 | const assertDailyWeather = (dailyWeather) => {
6 | assert.strictEqual(dailyWeather.date.month, 1);
7 | assert.strictEqual(dailyWeather.date.day, 1);
8 | assert.strictEqual(dailyWeather.date.dayOfWeek, '금요일');
9 | assert.strictEqual(dailyWeather.temperature.maxTemp, -100);
10 | assert.strictEqual(dailyWeather.temperature.minTemp, 100);
11 | };
12 |
13 | const assertDailyWeatherWithClimateIconId = (dailyWeather) => {
14 | assert.strictEqual(dailyWeather.date.month, 1);
15 | assert.strictEqual(dailyWeather.date.day, 1);
16 | assert.strictEqual(dailyWeather.date.dayOfWeek, '금요일');
17 | assert.strictEqual(dailyWeather.temperature.maxTemp, -100);
18 | assert.strictEqual(dailyWeather.temperature.minTemp, 100);
19 | assert.strictEqual(dailyWeather.climateIconId, 1);
20 | };
21 |
22 | const assertHourlyWeather = (hourlyWeather) => {
23 | assert.strictEqual(hourlyWeather.time, '오후 12시');
24 | assert.strictEqual(hourlyWeather.temperature, 10);
25 | assert.strictEqual(hourlyWeather.climate.iconId, 2);
26 | assert.strictEqual(hourlyWeather.pop, 0);
27 | };
28 |
29 | describe('weather service test', function () {
30 | describe('getDailyWeather test', function () {
31 | it('getDailyWeather returns dailyWeather', async function () {
32 | const dailyWeather = await weatherService.getDailyWeather(
33 | 1100000000,
34 | '2021-01-01'
35 | );
36 | assertDailyWeather(dailyWeather);
37 | });
38 | it('getDailyWeather returns null if not exists', async function () {
39 | const dailyWeather = await weatherService.getDailyWeather(
40 | 1100000000,
41 | '2020-01-01'
42 | );
43 | assert(dailyWeather == null);
44 | });
45 | });
46 | describe('getDailyWeatherWithClimateIconId test', function () {
47 | it('getDailyWeatherWithClimateIconId returns dailyWeatherWithClimate', async function () {
48 | const dailyWeather = await weatherService.getDailyWeatherWithClimateIconId(
49 | 1100000000,
50 | '2021-01-01'
51 | );
52 | assertDailyWeatherWithClimateIconId(dailyWeather);
53 | });
54 | it('getDailyWeatherWithClimateIconId returns null if not exists', async function () {
55 | const dailyWeather = await weatherService.getDailyWeatherWithClimateIconId(
56 | 1100000000,
57 | '2020-01-01'
58 | );
59 | assert(dailyWeather == null);
60 | });
61 | });
62 | describe('getHourlyWeather test', function () {
63 | it('getHourlyWeather returns hourlyWeather', async function () {
64 | const hourlyWeather = await weatherService.getHourlyWeather(
65 | 1100000000,
66 | '2021-01-01',
67 | 12,
68 | dateUtils.format12
69 | );
70 | assertHourlyWeather(hourlyWeather);
71 | });
72 | it('getHourlyWeather returns null if not exists', async function () {
73 | const dailyWeather = await weatherService.getDailyWeather(
74 | 1100000000,
75 | '2020-01-01'
76 | );
77 | assert(dailyWeather == null);
78 | });
79 | });
80 | describe('getOverviewWeather test', function () {
81 | it('getOverviewWeather returns overviewWeather', async function () {
82 | const overviewWeather = await weatherService.getOverviewWeather(
83 | 1100000000,
84 | '2021-01-01',
85 | 12,
86 | dateUtils.format12
87 | );
88 | assert.strictEqual(overviewWeather.region.code, 1100000000);
89 | assert.strictEqual(overviewWeather.region.name, '서울특별시');
90 | assertDailyWeather(overviewWeather.dailyWeather);
91 | assertHourlyWeather(overviewWeather.hourlyWeather);
92 | });
93 | it('getOverviewWeather returns null if not exists', async function () {
94 | const overviewWeather = await weatherService.getOverviewWeather(
95 | 1100000000,
96 | '2020-01-01',
97 | 12,
98 | dateUtils.format12
99 | );
100 | assert(overviewWeather == null);
101 | });
102 | });
103 | describe('getOverviewWeathers test', function () {
104 | it('getOverviewWeathers returns overviewWeatherList', async function () {
105 | const overviewWeatherList = await weatherService.getOverviewWeathers(
106 | '서울특별시',
107 | '2021-01-01',
108 | 12,
109 | dateUtils.format12
110 | );
111 | assert.strictEqual(overviewWeatherList.length, 1);
112 | assertDailyWeather(overviewWeatherList[0].dailyWeather);
113 | assertHourlyWeather(overviewWeatherList[0].hourlyWeather);
114 | }).timeout(15000);
115 | it('getOverviewWeathers returns empty list if not exists', async function () {
116 | const overviewWeatherList = await weatherService.getOverviewWeathers(
117 | '김자현',
118 | '2021-01-01',
119 | 12,
120 | dateUtils.format12
121 | );
122 | assert.strictEqual(overviewWeatherList.length, 0);
123 | });
124 | });
125 | describe('getExtraDailyWeather test', function () {
126 | it('getExtraDailyWeather returns extraWeather', async function () {
127 | const extraWeather = await weatherService.getExtraDailyWeather(
128 | 1100000000,
129 | '2021-01-01'
130 | );
131 | assert(extraWeather.rain.value == 10);
132 | assert(extraWeather.humidity.value == 10);
133 | assert(extraWeather.wind.value == 91.9);
134 | });
135 | it('getExtraDailyWeather throw error if not exists', async function () {
136 | await assert.rejects(async () => {
137 | await weatherService.getExtraDailyWeather(-2, '2020-01-01');
138 | });
139 | });
140 | });
141 | });
142 |
--------------------------------------------------------------------------------
/WeathyServer/tests/services/weathyService.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const weathyService = require('../../services/weathyService');
3 | const exception = require('../../modules/exception');
4 | const { Weathy, WeathyClothes, DailyWeather } = require('../../models');
5 | const { sequelize } = require('../../models');
6 | const assertRegion = (region) => {
7 | assert.strictEqual(region.code, 1100000000);
8 | assert.strictEqual(region.name, '서울특별시');
9 | };
10 |
11 | const assertDailyWeather = (dailyWeather) => {
12 | assert.strictEqual(dailyWeather.date.month, 1);
13 | assert.strictEqual(dailyWeather.date.day, 1);
14 | assert.strictEqual(dailyWeather.date.dayOfWeek, '금요일');
15 |
16 | assert.strictEqual(dailyWeather.temperature.maxTemp, -100);
17 | assert.strictEqual(dailyWeather.temperature.minTemp, 100);
18 | };
19 |
20 | const assertHourlyWeather = (hourlyWeather) => {
21 | assert.strictEqual(hourlyWeather.time, '오후 12시');
22 | assert.strictEqual(typeof hourlyWeather.temperature, 'number');
23 | // assert.strictEqual(hourlyWeather.climate.iconId, 2); dailyClimateIconId
24 | assert.strictEqual(hourlyWeather.pop, 0);
25 | };
26 |
27 | const assertClosetWeather = (closet) => {
28 | assert.strictEqual(closet.top.categoryId, 1);
29 | assert.strictEqual(closet.bottom.categoryId, 2);
30 | assert.strictEqual(closet.outer.categoryId, 3);
31 | assert.strictEqual(closet.etc.categoryId, 4);
32 | };
33 |
34 | const assertWeathy = ({ weathy }) => {
35 | assertRegion(weathy.region);
36 | assertDailyWeather(weathy.dailyWeather);
37 | assertHourlyWeather(weathy.hourlyWeather);
38 | assertClosetWeather(weathy.closet);
39 |
40 | assert.strictEqual(weathy.weathyId, 32);
41 | assert.strictEqual(weathy.stampId, 1);
42 |
43 | assert.ok(weathy.feedback === null || typeof weathy.feedback === 'string');
44 | assert.ok(weathy.imgUrl === null || typeof weathy.imgUrl === 'string');
45 | };
46 |
47 | describe('weathy service test', function () {
48 | describe('getdWeathy test', function () {
49 | it('getWeathy returns Weathy', async function () {
50 | const weathy = await weathyService.getWeathy('2021-01-01', 1);
51 | assertWeathy(weathy);
52 | });
53 |
54 | it('getDailyWeather returns null', async function () {
55 | const weathy = await weathyService.getWeathy('3000-01-01', 1);
56 | assert(weathy === null);
57 | });
58 | });
59 | describe('getWeathy test', function () {
60 | it('getRecommendedWeathy returns recommened weathy', async function () {
61 | const weathy = await weathyService.getRecommendedWeathy(
62 | 2647000000,
63 | '2021-01-11',
64 | 1
65 | );
66 | const answerWeathy = await weathyService.getWeathy('2021-01-10', 1);
67 |
68 | assert.ok(weathy.id == answerWeathy.id);
69 | });
70 |
71 | it('getRecommendedWeathy returns null', async function () {
72 | const weathy = await weathyService.getRecommendedWeathy(
73 | 1100000000,
74 | '3000-01-01',
75 | 1
76 | );
77 |
78 | assert.ok(weathy === null);
79 | });
80 | });
81 | describe('createWeathy test', function () {
82 | it('createWeathy Success case test ', async function () {
83 | const weathyId = await weathyService.createWeathy(
84 | 200,
85 | [5, 6],
86 | 3,
87 | 1
88 | );
89 | const answerWeathy = await Weathy.findOne({
90 | where: {
91 | id: weathyId
92 | }
93 | });
94 |
95 | assert.ok(answerWeathy.id === weathyId);
96 |
97 | await Weathy.destroy({
98 | where: {
99 | id: weathyId
100 | },
101 | paranoid: false
102 | });
103 | });
104 | it('createWeathy Duplicate Fail case test ', async function () {
105 | let weathyId;
106 | try {
107 | weathyId = await weathyService.createWeathy(200, [5, 6], 3, 1);
108 | await weathyService.createWeathy(200, [5, 6], 3, 1);
109 | assert.fail();
110 | } catch (err) {
111 | assert.ok(err.message === exception.DUPLICATION_WEATHY);
112 | } finally {
113 | await Weathy.destroy({
114 | where: {
115 | id: weathyId
116 | },
117 | paranoid: false
118 | });
119 | }
120 | });
121 | });
122 |
123 | describe('deleteWeathy test', function () {
124 | it('deleteWeathy Success case test ', async function () {
125 | const userId = 1;
126 | const weathyId = await weathyService.createWeathy(
127 | 200,
128 | [5, 6],
129 | 3,
130 | userId
131 | );
132 |
133 | await weathyService.deleteWeathy(weathyId, userId);
134 |
135 | const weathy = await Weathy.findOne({
136 | where: {
137 | id: weathyId,
138 | user_id: userId
139 | }
140 | });
141 | const result = await WeathyClothes.findAndCountAll({
142 | where: {
143 | weathy_id: weathyId
144 | }
145 | });
146 |
147 | assert.ok(weathy === null);
148 | assert.ok(result.count === 0);
149 | });
150 | });
151 |
152 | describe('modifyWeathy test', function () {
153 | const userId = 1;
154 | const code = 1111000000;
155 | let weathyId;
156 |
157 | before('Create Weathy', async () => {
158 | weathyId = await weathyService.createWeathy(200, [5, 6], 3, userId);
159 | });
160 |
161 | after('Delete Weathy', async () => {
162 | await Weathy.destroy({
163 | where: {
164 | id: weathyId
165 | },
166 | paranoid: false
167 | });
168 | });
169 |
170 | it('modifyWeathy Success case test ', async function () {
171 | await weathyService.modifyWeathy(
172 | weathyId,
173 | userId,
174 | code,
175 | [6],
176 | 1,
177 | 'feedback was changed'
178 | );
179 |
180 | const modifedWeathy = await Weathy.findOne({
181 | include: [
182 | {
183 | model: DailyWeather,
184 | required: true,
185 | attributes: ['location_id']
186 | }
187 | ],
188 | where: {
189 | id: weathyId,
190 | user_id: userId
191 | }
192 | });
193 |
194 | const result = await WeathyClothes.findAndCountAll({
195 | where: {
196 | weathy_id: weathyId
197 | }
198 | });
199 |
200 | assert.ok(modifedWeathy.description === 'feedback was changed');
201 | assert.ok(modifedWeathy.emoji_id === 1);
202 | assert.ok(modifedWeathy.DailyWeather.location_id === code);
203 | assert.ok(result.count === 1);
204 | });
205 | });
206 |
207 | describe('modifyImgField Test', async function () {
208 | const userId = 1;
209 | let weathyId;
210 | before('Create Weathy', async function () {
211 | const weathy = await Weathy.create({
212 | user_id: 1,
213 | dailyweather_id: 108,
214 | emoji_id: 3,
215 | description: 'hello',
216 | img_url: null
217 | });
218 | weathyId = weathy.id;
219 | });
220 |
221 | after('Delete Weathy', async function () {
222 | await Weathy.destroy({
223 | where: {
224 | id: weathyId
225 | }
226 | });
227 | });
228 |
229 | it('Insert null into img_url', async function () {
230 | await weathyService.modifyImgField(null, weathyId, userId);
231 | const weathy = await Weathy.findOne({
232 | where: {
233 | id: weathyId
234 | }
235 | });
236 |
237 | assert.strictEqual(weathy.img_url, null);
238 | });
239 |
240 | it('Insert hello world into img_url', async function () {
241 | const helloWorld = 'hello world!';
242 | await weathyService.modifyImgField(helloWorld, weathyId, userId);
243 | const weathy = await Weathy.findOne({
244 | where: {
245 | id: weathyId
246 | }
247 | });
248 | assert.strictEqual(weathy.img_url, helloWorld);
249 | });
250 | });
251 | });
252 |
--------------------------------------------------------------------------------
/WeathyServer/tests/utils/dateUtils.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const dateUtils = require('../../utils/dateUtils');
3 |
4 | describe('dateUtils test', function () {
5 | describe('getYear test', function () {
6 | it('Possible to parse year from YYYY-MM-DD', function () {
7 | assert.strictEqual(dateUtils.getYear('1999-00-00'), 1999);
8 | });
9 | it('Possible to parse year from YYYY-MM', function () {
10 | assert.strictEqual(dateUtils.getYear('1999-00'), 1999);
11 | });
12 | });
13 |
14 | describe('getMonth test', function () {
15 | it('Possible to parse month from YYYY-MM-DD', function () {
16 | assert.strictEqual(dateUtils.getMonth('1999-04-00'), 4);
17 | });
18 | it('Possible to parse month from YYYY-MM', function () {
19 | assert.strictEqual(dateUtils.getMonth('1999-04'), 4);
20 | });
21 | });
22 |
23 | describe('format12 test', function () {
24 | it('Format12 returns at 12pm', function () {
25 | assert.strictEqual(dateUtils.format12(12), '오후 12시');
26 | });
27 | it('Format12 returns at 12pm', function () {
28 | assert.strictEqual(dateUtils.format12(0), '오전 12시');
29 | });
30 | it('Format12 returns at 1pm', function () {
31 | assert.strictEqual(dateUtils.format12(13), '오후 1시');
32 | });
33 | it('Format12 returns at 1am', function () {
34 | assert.strictEqual(dateUtils.format12(1), '오전 1시');
35 | });
36 | });
37 |
38 | describe('format24 test', function () {
39 | it('Format24 returns at 12pm', function () {
40 | assert.strictEqual(dateUtils.format24(12), '12시');
41 | });
42 | it('Format24 returns at 12pm', function () {
43 | assert.strictEqual(dateUtils.format24(0), '0시');
44 | });
45 | it('Format24 returns at 1pm', function () {
46 | assert.strictEqual(dateUtils.format24(13), '13시');
47 | });
48 | it('Format24 returns at 1am', function () {
49 | assert.strictEqual(dateUtils.format24(1), '1시');
50 | });
51 | });
52 |
53 | describe('formatDate test', function () {
54 | it('formatDate returns YYYY-MM-DD formated string', function () {
55 | const date = new Date('1999-01-01');
56 | assert.ok(dateUtils.formatDate(date) == '1999-01-01');
57 | });
58 | });
59 |
60 | describe('getNextHour test', function () {
61 | it('getNextHour increase time', function () {
62 | const { next_date, next_time } = dateUtils.getNextHour(
63 | '1999-01-01',
64 | 12
65 | );
66 | assert.strictEqual(next_date, '1999-01-01');
67 | assert.strictEqual(next_time, 13);
68 | });
69 | it('getNextHour increase date and reset time when date change', function () {
70 | const { next_date, next_time } = dateUtils.getNextHour(
71 | '1999-01-01',
72 | 23
73 | );
74 | assert.strictEqual(next_date, '1999-01-02');
75 | assert.strictEqual(next_time, 0);
76 | });
77 | });
78 |
79 | describe('getNextDay test', function () {
80 | it('getNextDay increase day', function () {
81 | const date = dateUtils.getNextDay('1999-01-01');
82 | assert.strictEqual(date, '1999-01-02');
83 | });
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/WeathyServer/tests/utils/tokenUtils.spec.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const tokenUtils = require('../../utils/tokenUtils');
3 |
4 | describe('tokenUtils test', function () {
5 | describe('generateToken test', function () {
6 | it('generateToken returns token contains userId', function () {
7 | const token = tokenUtils.generateToken(1);
8 | assert.ok(token.split(':')[0], 1);
9 | });
10 |
11 | it('generateToken returns token contains 30 length random string', function () {
12 | const token = tokenUtils.generateToken(1);
13 | assert.ok(token.split(':')[1].length, 30);
14 | });
15 | });
16 | describe('getUserIdFromToken test', function () {
17 | it('getUserIdFromToken returns userId', function () {
18 | const token = '1:token';
19 | assert.ok(tokenUtils.getUserIdFromToken(token), 1);
20 | });
21 | });
22 | describe('isUserOwnerOfToken test', function () {
23 | it('isUserOwnerOfToken returns true if userId matched', function () {
24 | const token = '1:token';
25 | assert.ok(tokenUtils.isUserOwnerOfToken(1, token));
26 | });
27 | it('isUserOwnerOfToken returns false if userId unmatched', function () {
28 | const token = '1:token';
29 | assert.ok(!tokenUtils.isUserOwnerOfToken(2, token));
30 | });
31 | });
32 | });
33 |
--------------------------------------------------------------------------------
/WeathyServer/utils/dateUtils.js:
--------------------------------------------------------------------------------
1 | const day_of_week = [
2 | '일요일',
3 | '월요일',
4 | '화요일',
5 | '수요일',
6 | '목요일',
7 | '금요일',
8 | '토요일'
9 | ];
10 |
11 | const formatDate = (d) => {
12 | let month = '' + (d.getMonth() + 1);
13 | let day = '' + d.getDate();
14 | const year = d.getFullYear();
15 | if (month.length < 2) month = '0' + month;
16 | if (day.length < 2) day = '0' + day;
17 | return [year, month, day].join('-');
18 | };
19 |
20 | module.exports = {
21 | getYear: (date) => {
22 | return parseInt(date.split('-')[0]);
23 | },
24 | getMonth: (date) => {
25 | return parseInt(date.split('-')[1]);
26 | },
27 | getDay: (date) => {
28 | return parseInt(date.split('-')[2]);
29 | },
30 | getYoil: (date) => {
31 | const today = new Date(date).getDay();
32 | return day_of_week[today];
33 | },
34 | format24: (hour) => {
35 | return hour + '시';
36 | },
37 | format12: (hour) => {
38 | if (hour == 0) {
39 | return '오전 12시';
40 | } else if (hour == 12) {
41 | return '오후 12시';
42 | } else if (hour > 12) {
43 | return '오후 ' + (hour - 12) + '시';
44 | } else {
45 | return '오전 ' + hour + '시';
46 | }
47 | },
48 | formatDate,
49 | getNextHour: (date, time) => {
50 | let day = new Date(date);
51 | ++time;
52 | if (time == 24) {
53 | day.setDate(day.getDate() + 1);
54 | time = 0;
55 | }
56 | return { next_date: formatDate(day), next_time: time };
57 | },
58 | getNextDay: (date) => {
59 | let day = new Date(date);
60 | day.setDate(day.getDate() + 1);
61 | return formatDate(day);
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/WeathyServer/utils/tokenUtils.js:
--------------------------------------------------------------------------------
1 | const cryptoRandomString = require('crypto-random-string');
2 |
3 | module.exports = {
4 | generateToken: (user_id) => {
5 | return (
6 | user_id +
7 | ':' +
8 | cryptoRandomString({ length: 30, type: 'alphanumeric' })
9 | );
10 | },
11 | getUserIdFromToken: (token) => {
12 | return token.split(':')[0];
13 | },
14 | isUserOwnerOfToken: (userId, token) => {
15 | return userId == token.split(':')[0];
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/WeathyServer/views/error.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= message
5 | h2= error.status
6 | pre #{error.stack}
7 |
--------------------------------------------------------------------------------
/WeathyServer/views/index.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= title
5 | p Welcome to #{title}
6 |
--------------------------------------------------------------------------------
/WeathyServer/views/layout.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | title= title
5 | link(rel='stylesheet', href='/stylesheets/style.css')
6 | body
7 | block content
8 |
--------------------------------------------------------------------------------