├── .firebaserc
├── .github
├── ISSUE_TEMPLATE.md
├── ISSUE_TEMPLATE
│ └── feature_request.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ └── test-deploy.yml
├── .gitignore
├── README.md
├── firebase.json
├── functions
├── .eslintrc.js
├── .gitignore
├── .prettierrc.js
├── api
│ ├── index.js
│ └── routes
│ │ ├── auth
│ │ ├── authDoorbellGET.js
│ │ ├── authSignOutPOST.js
│ │ ├── authSignUpPOST.js
│ │ ├── authTestGET.js
│ │ ├── authUserDELETE.js
│ │ └── index.js
│ │ ├── feed
│ │ ├── feedGET.js
│ │ ├── feedLikePOST.js
│ │ ├── feedReportPOST.js
│ │ └── index.js
│ │ ├── index.js
│ │ ├── myroom
│ │ ├── index.js
│ │ ├── myroomGET.js
│ │ ├── myroomRoomGET.js
│ │ ├── myroomThumbnailListGET.js
│ │ └── myroomThumbnailPATCH.js
│ │ ├── notice
│ │ ├── active
│ │ │ ├── activeGET.js
│ │ │ ├── activeReadPATCH.js
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── noticeDELETE.js
│ │ ├── noticeNewGET.js
│ │ ├── noticeSettingGET.js
│ │ ├── noticeSettingPATCH.js
│ │ └── service
│ │ │ ├── index.js
│ │ │ ├── serviceGET.js
│ │ │ └── serviceReadPATCH.js
│ │ ├── room
│ │ ├── index.js
│ │ ├── roomCodeGET.js
│ │ ├── roomDELETE.js
│ │ ├── roomDetailGET.js
│ │ ├── roomEnterPOST.js
│ │ ├── roomListGET.js
│ │ ├── roomOutDELETE.js
│ │ ├── roomPOST.js
│ │ ├── roomPurposePATCH.js
│ │ ├── roomReadPATCH.js
│ │ ├── roomRecordPOST.js
│ │ ├── roomStartPOST.js
│ │ ├── roomStatusPOST.js
│ │ ├── roomTimelineGET.js
│ │ ├── roomWaitingGET.js
│ │ ├── roomWaitingMemberGET.js
│ │ └── sparkPOST.js
│ │ ├── scheduling
│ │ ├── index.js
│ │ ├── inspectPOST.js
│ │ └── remindPOST.js
│ │ ├── user
│ │ ├── index.js
│ │ ├── userProfileGET.js
│ │ └── userProfilePATCH.js
│ │ └── version
│ │ ├── index.js
│ │ ├── versionGET.js
│ │ └── versionPATCH.js
├── config
│ ├── dbConfig.js
│ └── firebaseClient.js
├── constants
│ ├── alarmMessage.js
│ ├── day.js
│ ├── defaultProfileImg.js
│ ├── jwt.js
│ ├── lifeTimelineMessage.js
│ ├── responseMessage.js
│ ├── statusCode.js
│ └── termList.js
├── db
│ ├── db.js
│ ├── dialog.js
│ ├── index.js
│ ├── lifeTimeline.js
│ ├── like.js
│ ├── notice.js
│ ├── ownership.js
│ ├── record.js
│ ├── remind.js
│ ├── report.js
│ ├── room.js
│ ├── schedule.js
│ ├── spark.js
│ ├── user.js
│ └── version.js
├── index.js
├── lib
│ ├── convertSnakeToCamel.js
│ ├── deleteImage.js
│ ├── jwtHandlers.js
│ ├── passedDayToStr.js
│ ├── pushAlarm.js
│ └── util.js
├── middlewares
│ ├── auth.js
│ ├── slackAPI.js
│ └── uploadImage.js
├── package.json
└── scheduler
│ └── funcs.js
└── package.json
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "we-sopt-spark"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 | ## 📌 Issue
3 |
4 | - Issue !
5 |
6 | ## 📝 To-do
7 |
8 | - [ ] todo!
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## 📌 Issue
11 |
12 |
13 | ## 📝 To-do
14 |
15 | - [ ] todo!
16 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ### ✅ Default Checklist
2 |
3 | - [x] check branch
4 |
5 | - [x] set Labels
6 |
7 | - [x] set Reviewers
8 |
9 | ---
10 |
11 | ### 📕 Task
12 |
13 | - [x] Something you did..
14 |
15 | ---
16 |
17 | ### 😳 Issue
18 |
19 | - [ ] Help me..
--------------------------------------------------------------------------------
/.github/workflows/test-deploy.yml:
--------------------------------------------------------------------------------
1 | name: Spark Test Server Deploy
2 | on:
3 | push:
4 | branches: [test]
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 |
10 | steps:
11 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
12 | - uses: actions/checkout@v2
13 |
14 | # Create firebase-sdk file
15 | - name: Create Firebase SDK File
16 | id: create-json
17 | uses: jsdaniell/create-json@1.1.2
18 | with:
19 | name: "we-sopt-spark-firebase-adminsdk-emnjd-30d5170309.json"
20 | json: ${{ secrets.FIREBASE_SDK }}
21 | dir: "functions/"
22 |
23 | # Create .env file
24 | - name: Create .env
25 | run: |
26 | touch ./functions/.env
27 | echo "${{ secrets.DOT_ENV }}" > ./functions/.env
28 | shell: bash
29 |
30 | # Update .firebaserc file
31 | - name: Update .firebaserc
32 | run: |
33 | rm .firebaserc
34 | touch .firebaserc
35 | echo "${{ secrets.FIREBASE_RC }}" > .firebaserc
36 | shell: bash
37 |
38 | # Replace firebaseClient Config file
39 | - name: Replace FirebaseClient Config file
40 | run: |
41 | rm ./functions/config/firebaseClient.js
42 | touch ./functions/config/firebaseClient.js
43 | echo "${{ secrets.TEST_SERVER_FIREBASE_CLIENT }}" > ./functions/config/firebaseClient.js
44 | shell: bash
45 |
46 | - name: Install npm packages
47 | run: |
48 | cd functions
49 | npm install
50 |
51 | - name: Deploy to Firebase
52 | uses: w9jds/firebase-action@master
53 | with:
54 | args: deploy --only functions --project spark-test-server
55 | env:
56 | FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
57 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | firebase-debug.log*
8 | firebase-debug.*.log*
9 |
10 | # Firebase cache
11 | .firebase/
12 |
13 | # Firebase config
14 |
15 | # Uncomment this if you'd like others to create their own Firebase project.
16 | # For a team working on the same Firebase project(s), it is recommended to leave
17 | # it commented so all members can deploy to the same project(s) in .firebaserc.
18 | # .firebaserc
19 |
20 | # Runtime data
21 | pids
22 | *.pid
23 | *.seed
24 | *.pid.lock
25 |
26 | # Directory for instrumented libs generated by jscoverage/JSCover
27 | lib-cov
28 |
29 | # Coverage directory used by tools like istanbul
30 | coverage
31 |
32 | # nyc test coverage
33 | .nyc_output
34 |
35 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
36 | .grunt
37 |
38 | # Bower dependency directory (https://bower.io/)
39 | bower_components
40 |
41 | # node-waf configuration
42 | .lock-wscript
43 |
44 | # Compiled binary addons (http://nodejs.org/api/addons.html)
45 | build/Release
46 |
47 | # Dependency directories
48 | node_modules/
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Optional REPL history
57 | .node_repl_history
58 |
59 | # Output of 'npm pack'
60 | *.tgz
61 |
62 | # Yarn Integrity file
63 | .yarn-integrity
64 |
65 | # dotenv environment variables file
66 | .env
67 |
68 |
69 | # Created by https://www.toptal.com/developers/gitignore/api/node
70 | # Edit at https://www.toptal.com/developers/gitignore?templates=node
71 |
72 | ### Node ###
73 | # Logs
74 | logs
75 | *.log
76 | npm-debug.log*
77 | yarn-debug.log*
78 | yarn-error.log*
79 | lerna-debug.log*
80 | .pnpm-debug.log*
81 |
82 | # Diagnostic reports (https://nodejs.org/api/report.html)
83 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
84 |
85 | # Runtime data
86 | pids
87 | *.pid
88 | *.seed
89 | *.pid.lock
90 |
91 | # Directory for instrumented libs generated by jscoverage/JSCover
92 | lib-cov
93 |
94 | # Coverage directory used by tools like istanbul
95 | coverage
96 | *.lcov
97 |
98 | # nyc test coverage
99 | .nyc_output
100 |
101 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
102 | .grunt
103 |
104 | # Bower dependency directory (https://bower.io/)
105 | bower_components
106 |
107 | # node-waf configuration
108 | .lock-wscript
109 |
110 | # Compiled binary addons (https://nodejs.org/api/addons.html)
111 | build/Release
112 |
113 | # Dependency directories
114 | node_modules/
115 | jspm_packages/
116 |
117 | # Snowpack dependency directory (https://snowpack.dev/)
118 | web_modules/
119 |
120 | # TypeScript cache
121 | *.tsbuildinfo
122 |
123 | # Optional npm cache directory
124 | .npm
125 |
126 | # Optional eslint cache
127 | .eslintcache
128 |
129 | # Microbundle cache
130 | .rpt2_cache/
131 | .rts2_cache_cjs/
132 | .rts2_cache_es/
133 | .rts2_cache_umd/
134 |
135 | # Optional REPL history
136 | .node_repl_history
137 |
138 | # Output of 'npm pack'
139 | *.tgz
140 |
141 | # Yarn Integrity file
142 | .yarn-integrity
143 |
144 | # dotenv environment variables file
145 | .env
146 | .env.test
147 | .env.production
148 |
149 | # parcel-bundler cache (https://parceljs.org/)
150 | .cache
151 | .parcel-cache
152 |
153 | # Next.js build output
154 | .next
155 | out
156 |
157 | # Nuxt.js build / generate output
158 | .nuxt
159 | dist
160 |
161 | # Gatsby files
162 | .cache/
163 | # Comment in the public line in if your project uses Gatsby and not Next.js
164 | # https://nextjs.org/blog/next-9-1#public-directory-support
165 | # public
166 |
167 | # vuepress build output
168 | .vuepress/dist
169 |
170 | # Serverless directories
171 | .serverless/
172 |
173 | # FuseBox cache
174 | .fusebox/
175 |
176 | # DynamoDB Local files
177 | .dynamodb/
178 |
179 | # TernJS port file
180 | .tern-port
181 |
182 | # Stores VSCode versions used for testing VSCode extensions
183 | .vscode-test
184 |
185 | # yarn v2
186 | .yarn/cache
187 | .yarn/unplugged
188 | .yarn/build-state.yml
189 | .yarn/install-state.gz
190 | .pnp.*
191 |
192 | ### Node Patch ###
193 | # Serverless Webpack directories
194 | .webpack/
195 |
196 | # End of https://www.toptal.com/developers/gitignore/api/node
197 |
198 | .DS_Store
199 |
200 | # Firebase service account
201 | we-sopt-spark-firebase-adminsdk-emnjd-30d5170309.json
202 |
203 | package-lock.json
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 🎇 Spark, A large fire comes from small spark
2 |
3 | > 👑 WE SOPT 29th APPJAM 대상 👑
4 | > 친구와 함께하는 66일간의 습관 형성 서비스!
5 | > 프로젝트 기간 : 2022. 01. 03. ~ 2022. 01. 22.
6 | > 📲 [App Store](https://apps.apple.com/kr/app/spark-%EC%8A%A4%ED%8C%8C%ED%81%AC-%EC%B9%9C%EA%B5%AC%EC%99%80-%EC%8A%B5%EA%B4%80-%EA%B4%80%EB%A6%AC/id1605811861) | [Play Store](https://play.google.com/store/apps/details?id=com.teamsparker.android)
7 |
8 | ## 🔥 Branding
9 |
10 | 
11 |
12 | 
13 |
14 |
18 |
19 | ---
20 |
21 | ## 📚 API Document
22 |
23 | ### [Spark API Document ✨](https://www.notion.so/API-94b97e62a8b84769a784d86992287119)
24 |
25 | | Function | Description | Developer |
26 | | :---: | --- | --- |
27 | | Auth | Apple, Kakao 로그인 | 🦋 영권 |
28 | | | Apple, Kakao 회원가입 | 🐱 설희 |
29 | | | 회원 탈퇴 | 🐱 설희 |
30 | | Feed | 피드 모아보기 | 🐱 설희 |
31 | | | 피드 좋아요 및 좋아요 취소 | 🐻❄️ 정현 / 🦋 영권 |
32 | | | 피드 신고하기 | 🐱 설희 |
33 | | Room | 습관방 생성 | 🦋 영권 |
34 | | | 방 리스트 조회 | 🐱 설희 |
35 | | | 대기방 상세 조회 | 🦋 영권 |
36 | | | 습관방 상세 조회 | 🐱 설희 |
37 | | | 나의 목표 설정하기 | 🦋 영권 |
38 | | | 습관방 시작 | 🦋 영권 |
39 | | | 대기방 인원 조회 | 🦋 영권 |
40 | | | 쉴래요, 고민중 | 🦋 영권 |
41 | | | 습관 인증하기 | 🐱 설희 |
42 | | | 스파크 보내기 | 🐱 설희 |
43 | | | 참여 코드로 대기방 조회 | 🦋 영권 |
44 | | | 습관방 참여 | 🦋 영권 |
45 | | | 참여자 내보내기 | 🦋 영권 |
46 | | | 습관방 및 대기방 나가기 | 🦋 영권 |
47 | | | 대기방 삭제 | 🦋 영권 |
48 | | | 홈에서 성공 및 실패한 카드 | 🐱 설희 |
49 | | User | 유저 프로필 조회 | 🦋 영권 |
50 | | | 유저 프로필 수정 | 🦋 영권 |
51 | | Myroom | 보관함 리스트 불러오기 | 🐱 설희 |
52 | | | 특정 습관방 인증사진 모아보기 | 🐱 설희 |
53 | | | 보관함 대표 이미지 변경 | 🐱 설희 |
54 | | | 보관함 대표 이미지 목록 불러오기 | 🦋 영권 |
55 | | Notice | 서비스 알림 조회 | 🦋 영권 |
56 | | | 서비스 알림 읽음 처리 | 🦋 영권 |
57 | | | 활동 알림 조회 | 🦋 영권 |
58 | | | 활동 알림 읽음 처리 | 🦋 영권 |
59 | | | 알림 삭제 | 🦋 영권 |
60 | | | 새로운 알림 빨콩 조회 | 🦋 영권 |
61 | | Life Timeline | 생명 타임라인 조회 | 🦋 영권 |
62 | | FCM | 푸시알림 전송 기능 | 🦋 영권 |
63 | | | 푸시알림 ON / OFF Toggle | 🦋 영권 |
64 | | | 푸시알림 설정 조회 | 🦋 영권 |
65 | | Version | 최신 버전정보 불러오기 | 🦋 영권 |
66 | | | 최신 버전정보 갱신하기 | 🦋 영권 |
67 |
68 |
69 | ---
70 |
71 |
309 |
310 |
311 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "functions": {
3 | "predeploy": [
4 | "npm --prefix \"$RESOURCE_DIR\" run lint"
5 | ],
6 | "source": "functions"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/functions/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | node: true,
4 | commonjs: true,
5 | es2021: true,
6 | },
7 | extends: ["eslint:recommended", "eslint-config-prettier"],
8 | parserOptions: {
9 | ecmaVersion: 12,
10 | },
11 | rules: {
12 | "no-prototype-builtins": "off",
13 | "no-self-assign": "off",
14 | "no-empty": "off",
15 | "no-case-declarations": "off",
16 | "consistent-return": "off",
17 | "arrow-body-style": "off",
18 | camelcase: "off",
19 | quotes: "off",
20 | "no-unused-vars": "off",
21 | "comma-dangle": "off",
22 | "no-bitwise": "off",
23 | "no-use-before-define": "off",
24 | "no-extra-boolean-cast": "off",
25 | "no-empty-pattern": "off",
26 | curly: "off",
27 | "no-unreachable": "off",
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/functions/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
--------------------------------------------------------------------------------
/functions/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | bracketSpacing: true,
3 | jsxBracketSameLine: true,
4 | singleQuote: true,
5 | trailingComma: "all",
6 | arrowParens: "always",
7 | printWidth: 200,
8 | tabWidth: 2,
9 | };
10 |
--------------------------------------------------------------------------------
/functions/api/index.js:
--------------------------------------------------------------------------------
1 | // 각종 모듈들
2 | const functions = require('firebase-functions');
3 | const express = require('express');
4 | const cors = require('cors');
5 | const cookieParser = require('cookie-parser');
6 | const dotenv = require('dotenv');
7 | const hpp = require('hpp');
8 | const helmet = require('helmet');
9 |
10 | // 보안 상 깃허브에 올리면 안 되는 정보를 .env라는 파일로 관리하기 위해 사용하는 모듈
11 | dotenv.config();
12 |
13 | // initializing
14 | const app = express();
15 |
16 | // Cross-Origin Resource Sharing을 열어주는 미들웨어
17 | // https://evan-moon.github.io/2020/05/21/about-cors/ 에서 자세한 정보 확인
18 | app.use(cors());
19 |
20 | // 보안을 위한 미들웨어들
21 | // process.env.NODE_ENV는 배포된 서버에서는 'production'으로, 로컬에서 돌아가는 서버에서는 'development'로 고정됨.
22 | if (process.env.NODE_ENV === 'production') {
23 | app.use(hpp());
24 | app.use(helmet());
25 | }
26 |
27 | // request에 담긴 정보를 json 형태로 파싱하기 위한 미들웨어들
28 | app.use(express.json());
29 | app.use(express.urlencoded({ extended: true }));
30 | app.use(cookieParser());
31 |
32 | // 라우팅: routes 폴더로 관리
33 | app.use('/', require('./routes'));
34 |
35 | // route 폴더에 우리가 지정할 경로가 아닌 다른 경로로 요청이 올 경우,
36 | // 잘못된 경로로 요청이 들어왔다는 메시지를 클라이언트에 보냄
37 | app.use('*', (req, res) => {
38 | res.status(404).json({
39 | status: 404,
40 | success: false,
41 | message: '요청 경로가 올바르지 않습니다',
42 | });
43 | });
44 |
45 | // express를 firebase functions로 감싸주는 코드
46 | module.exports = functions
47 | .runWith({
48 | timeoutSeconds: 300, // 요청을 처리하는 과정이 300초를 초과하면 타임아웃 시키기
49 | memory: '512MB', // 서버에 할당되는 메모리
50 | })
51 | .region('asia-northeast3') // 서버가 돌아갈 region. asia-northeast3는 서울
52 | .https.onRequest(async (req, res) => {
53 | // 들어오는 요청에 대한 로그를 콘솔에 찍기. 디버깅 때 유용하게 쓰일 예정.
54 | // 콘솔에 찍고 싶은 내용을 원하는 대로 추가하면 됨. (req.headers, req.query 등)
55 | console.log('\n\n', '[api]', `[${req.method.toUpperCase()}]`, req.originalUrl, req.body);
56 |
57 | // 맨 위에 선언된 express app 객체를 리턴.
58 | // 요것이 functions/index.js 안의 api: require("./api")에 들어가는 것.
59 | return app(req, res);
60 | });
61 |
--------------------------------------------------------------------------------
/functions/api/routes/auth/authDoorbellGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const jwtHandlers = require('../../../lib/jwtHandlers');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 | const { userDB } = require('../../../db');
9 |
10 | /**
11 | * @회원가입_로그인_분기처리
12 | * @route GET /auth/doorbell?socialId=
13 | * @error
14 | * 1. socialId 또는 fcmToken 누락
15 | */
16 |
17 | module.exports = async (req, res) => {
18 | const { socialId, fcmToken, os } = req.query;
19 |
20 | // @error 1. socialId 또는 fcmToken 누락
21 | if (!socialId || !fcmToken) {
22 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
23 | }
24 |
25 | let client;
26 |
27 | try {
28 | client = await db.connect();
29 |
30 | const user = await userDB.getUserBySocialId(client, socialId);
31 |
32 | // 회원가입 한적 없는 사용자
33 | if (!user) {
34 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.NOT_SIGNED_UP, { isNew: true }));
35 | }
36 |
37 | let data = jwtHandlers.sign(user);
38 | data.isNew = false;
39 |
40 | if (!os) {
41 | await userDB.updateDeviceTokenById(client, user.userId, fcmToken);
42 | } else {
43 | await userDB.updateDeviceTokenAndOsById(client, user.userId, fcmToken, os);
44 | }
45 |
46 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.ALREADY_SIGNED_UP, data));
47 | } catch (error) {
48 | console.log(error);
49 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
50 | const slackMessage = `[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
51 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
52 |
53 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
54 | } finally {
55 | client.release();
56 | }
57 | };
58 |
--------------------------------------------------------------------------------
/functions/api/routes/auth/authSignOutPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { userDB } = require('../../../db');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 |
9 | /**
10 | * @로그아웃
11 | * @route POST /auth/signout
12 | * @error
13 | */
14 |
15 | module.exports = async (req, res) => {
16 | const user = req.user;
17 |
18 | let client;
19 |
20 | try {
21 | client = await db.connect();
22 |
23 | await userDB.emptyDeviceTokenById(client, user.userId);
24 |
25 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.LOGOUT_SUCCESS));
26 | } catch (error) {
27 | console.log(error);
28 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
29 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
30 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
31 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
32 | } finally {
33 | client.release();
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/functions/api/routes/auth/authSignUpPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { userDB, ownershipDB } = require('../../../db');
7 | const { DEFAULT_PROFILE_IMG_URL } = require('../../../constants/defaultProfileImg');
8 | const jwtHandlers = require('../../../lib/jwtHandlers');
9 | const slackAPI = require('../../../middlewares/slackAPI');
10 |
11 | /**
12 | * @회원가입
13 | * @route POST /auth/signup
14 | * @body socialId:string, nickname:string, profileImg:file
15 | * @error
16 | * 1. socialId/nickname이 전달되지 않음
17 | * 2. 이미 존재하는 socialId
18 | * 3. 닉네임 10자 초과
19 | */
20 |
21 | module.exports = async (req, res) => {
22 | const { socialId, nickname, fcmToken } = req.body;
23 |
24 | // @error 1. socialId/nickname이 전달되지 않음
25 | if (!socialId || !nickname || !fcmToken) {
26 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
27 | }
28 | // @error 3. 닉네임 10자 초과
29 | if (nickname.length > 10) {
30 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.TOO_LONG_NICKNAME));
31 | }
32 | let client;
33 | try {
34 | client = await db.connect();
35 |
36 | // @error 2. 이미 존재하는 socialIds
37 | const alreaySocialId = await userDB.getUserBySocialId(client, socialId);
38 |
39 | if (alreaySocialId) {
40 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ALREADY_SOCIALID));
41 | }
42 |
43 | let profileImg = req.imageUrls;
44 |
45 | // 프로필 이미지가 넘어오지 않았으면, 기본 이미지로 설정
46 | if (profileImg.length === 0) {
47 | profileImg.push(DEFAULT_PROFILE_IMG_URL);
48 | }
49 |
50 | const user = await userDB.addUser(client, socialId, nickname, profileImg[0], fcmToken);
51 |
52 | // 프로필 이미지를 등록한 사용자라면 프로필 사진에 대한 ownership 부여
53 | if (user.profileImg !== DEFAULT_PROFILE_IMG_URL) {
54 | const profileUrl = user.profileImg;
55 | const profilePath = profileUrl.split('/')[profileUrl.split('/').length - 1].split('?')[0].replace('%2F', '/');
56 | await ownershipDB.insertOwnership(client, user.userId, profilePath);
57 | }
58 |
59 | const { accesstoken } = jwtHandlers.sign(user);
60 |
61 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.CREATED_USER, { user, accesstoken }));
62 | } catch (error) {
63 | console.log(error);
64 | functions.logger.error(`[SIGNUP ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] socialId:${socialId} ${error}`);
65 | const slackMessage = `[SIGNUP ERROR] [${req.method.toUpperCase()}] ${req.originalUrl} [CONTENT] socialId:${socialId} ${error} ${JSON.stringify(error)}`;
66 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
67 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
68 | } finally {
69 | client.release();
70 | }
71 | };
72 |
--------------------------------------------------------------------------------
/functions/api/routes/auth/authTestGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const admin = require('firebase-admin');
3 | const util = require('../../../lib/util');
4 | const statusCode = require('../../../constants/statusCode');
5 | const responseMessage = require('../../../constants/responseMessage');
6 | const db = require('../../../db/db');
7 | const { userDB } = require('../../../db');
8 | const jwtHandlers = require('../../../lib/jwtHandlers');
9 |
10 |
11 | module.exports = async (req, res) => {
12 | const user = req.user;
13 | console.log(user);
14 | if (!user) return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NO_USER));
15 |
16 | let client;
17 |
18 | try {
19 | client = await db.connect();
20 |
21 | res.status(statusCode.OK).send(util.success(statusCode.OK, "token -> user 활용법", user));
22 | } catch (error) {
23 | console.log(error);
24 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
25 |
26 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
27 | } finally {
28 | client.release();
29 | }
30 | };
--------------------------------------------------------------------------------
/functions/api/routes/auth/authUserDELETE.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { userDB, roomDB } = require('../../../db');
7 | const { DEFAULT_PROFILE_IMG_URL } = require('../../../constants/defaultProfileImg');
8 | const jwtHandlers = require('../../../lib/jwtHandlers');
9 | const slackAPI = require('../../../middlewares/slackAPI');
10 |
11 | /**
12 | * @회원_탈퇴
13 | * @route DELETE /auth/user
14 | */
15 |
16 | module.exports = async (req, res) => {
17 | const user = req.user;
18 | let client;
19 |
20 | try {
21 | client = await db.connect();
22 |
23 | const deletedUser = await userDB.deleteUserSoft(client, user.userId, user.socialId);
24 | await roomDB.outByUserId(client, user.userId);
25 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.DELETE_USER_SUCCESS));
26 | } catch (error) {
27 | console.log(error);
28 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
29 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
30 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
31 | } finally {
32 | client.release();
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/functions/api/routes/auth/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const uploadImageIntoSubDir = require('../../../middlewares/uploadImage');
4 | const { checkUser } = require('../../../middlewares/auth');
5 |
6 | router.post('/signup', uploadImageIntoSubDir('users'), require('./authSignUpPOST'));
7 | router.post('/signout', checkUser, require('./authSignOutPOST'));
8 | router.get('/doorbell', require('./authDoorbellGET'));
9 | router.delete('/user', checkUser, require('./authUserDELETE'));
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/functions/api/routes/feed/feedGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const convertDay = require('../../../constants/day');
6 | const db = require('../../../db/db');
7 | const { roomDB, sparkDB, likeDB } = require('../../../db');
8 | const slackAPI = require('../../../middlewares/slackAPI');
9 | const dayjs = require('dayjs');
10 | const _ = require('lodash');
11 |
12 | /**
13 | * @피드_조회
14 | * @route GET /feed?lastId=&size=
15 | * @error
16 | * 1. 잘못된 lastId
17 | */
18 |
19 | module.exports = async (req, res) => {
20 | const lastId = Number(req.query.lastId);
21 | const size = Number(req.query.size);
22 | const user = req.user;
23 |
24 | let client;
25 |
26 | try {
27 | client = await db.connect(req);
28 | const rawRooms = await roomDB.getRoomsIncludingFailByUserId(client, user.userId);
29 |
30 | if (!rawRooms.length) {
31 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_FEED_SUCCESS, { records: [] }));
32 | }
33 | const roomIds = [...new Set(rawRooms.filter(Boolean).map((room) => room.roomId))];
34 | const allRecords = await roomDB.getFeedRecordsByRoomIds(client, roomIds);
35 | const allRecordIds = allRecords.map((record) => record.recordId);
36 |
37 | let responseRecords = [];
38 | let recordIds = [];
39 | // 최초 요청이 아닐시
40 | if (lastId !== -1) {
41 | const lastIndex = _.indexOf(allRecordIds, lastId);
42 | // @error 1. 잘못된 last id
43 | if (lastIndex === -1) {
44 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.INVALID_LASTID));
45 | }
46 | responseRecords = allRecords.slice(lastIndex + 1, lastIndex + 1 + size);
47 | recordIds = allRecordIds.slice(lastIndex + 1, lastIndex + 1 + size);
48 | }
49 | // 최초 요청시
50 | else {
51 | responseRecords = allRecords.slice(0, size);
52 | recordIds = allRecordIds.slice(0, size);
53 | }
54 | if (!recordIds.length) {
55 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_FEED_SUCCESS, { records: [] }));
56 | }
57 | let likeNums = [];
58 | let sparkNums = [];
59 | let isLikes = [];
60 | for (let i = 0; i < recordIds.length; i++) {
61 | const like = await likeDB.countLikeByRecordId(client, recordIds[i]);
62 | const isLike = await likeDB.checkIsLike(client, recordIds[i], user.userId);
63 | likeNums.push(Number(like[0].count));
64 | if (isLike) {
65 | isLikes.push(true);
66 | } else {
67 | isLikes.push(false);
68 | }
69 | const spark = await sparkDB.countSparkByRecordId(client, recordIds[i]);
70 | sparkNums.push(Number(spark.count));
71 | }
72 |
73 | let records = [];
74 | for (let i = 0; i < recordIds.length; i++) {
75 | const date = dayjs(responseRecords[i].date).format('YYYY-M-D');
76 | const day = convertDay.numToString[dayjs(responseRecords[i].date).day()];
77 | const roomName = _.find(rawRooms, { roomId: responseRecords[i].roomId }).roomName;
78 | records.push({
79 | date,
80 | day,
81 | userId: responseRecords[i].userId,
82 | recordId: recordIds[i],
83 | nickname: responseRecords[i].nickname,
84 | profileImg: responseRecords[i].profileImg,
85 | roomName,
86 | certifyingImg: responseRecords[i].certifyingImg,
87 | likeNum: likeNums[i],
88 | sparkCount: sparkNums[i],
89 | isLiked: isLikes[i],
90 | timerRecord: responseRecords[i].timerRecord,
91 | isMyRecord: responseRecords[i].userId === user.userId ? true : false,
92 | });
93 | }
94 |
95 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_FEED_SUCCESS, { records }));
96 | } catch (error) {
97 | console.log(error);
98 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
99 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
100 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
101 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
102 | } finally {
103 | client.release();
104 | }
105 | };
106 |
--------------------------------------------------------------------------------
/functions/api/routes/feed/feedLikePOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const alarmMessage = require('../../../constants/alarmMessage');
5 | const responseMessage = require('../../../constants/responseMessage');
6 | const db = require('../../../db/db');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 | const { roomDB, recordDB, likeDB, noticeDB } = require('../../../db');
9 |
10 | /**
11 | * @피드_좋아요_및_취소
12 | * @route POST /feed/:recordId/like
13 | * @body
14 | * @error
15 | * 1. 유효하지 않은 recordId
16 | */
17 |
18 | module.exports = async (req, res) => {
19 | const { recordId } = req.params;
20 | const user = req.user;
21 | const userId = user.userId;
22 |
23 | let client;
24 |
25 | try {
26 | client = await db.connect(req);
27 |
28 | const record = await recordDB.getRecordById(client, recordId);
29 | const entry = await roomDB.getEntryById(client, record.entryId);
30 | const room = await roomDB.getRoomById(client, entry.roomId);
31 | const { title, body, isService } = alarmMessage.FEED_LIKE(user.nickname, room.roomName);
32 |
33 | // @error 1. 유효하지 않은 recordId
34 | if (!record) {
35 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.RECORD_ID_NOT_VALID));
36 | }
37 |
38 | const isLike = await likeDB.checkIsLike(client, recordId, userId);
39 |
40 | // Like
41 | if (!isLike) {
42 | await likeDB.sendLike(client, recordId, userId);
43 |
44 | // 본인이 본인의 게시물을 좋아할 경우 알림을 생성하지 않음
45 | if (userId !== entry.userId) {
46 | await noticeDB.addNotification(client, title, body, record.certifyingImg, entry.userId, isService, false, room.roomId);
47 | }
48 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.SEND_LIKE_SUCCESS));
49 | }
50 |
51 | // Unlike
52 | await likeDB.cancelLike(client, recordId, userId);
53 | await noticeDB.deleteNoticeByContentReceiverAndThumbnail(client, title, body, isService, entry.userId, record.certifyingImg);
54 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.CANCEL_LIKE_SUCCESS));
55 | } catch (error) {
56 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
57 | console.log(error);
58 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
59 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
60 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
61 | } finally {
62 | client.release();
63 | }
64 | };
65 |
--------------------------------------------------------------------------------
/functions/api/routes/feed/feedReportPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { recordDB, likeDB, roomDB, reportDB } = require('../../../db');
8 | const dayjs = require('dayjs');
9 |
10 | /**
11 | * @피드_신고
12 | * @route POST /feed/:recordId/report
13 | * @body
14 | * @error
15 | * 1. reportReason이 전달되지 않음
16 | * 2. 유효하지 않은 recordId
17 | */
18 |
19 | module.exports = async (req, res) => {
20 | const { recordId } = req.params;
21 | const { reportReason } = req.body;
22 | const user = req.user;
23 |
24 | let client;
25 |
26 | try {
27 | client = await db.connect(req);
28 |
29 | // @error 1. reportReason이 전달되지 않음
30 | if (!reportReason) {
31 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
32 | }
33 |
34 | const record = await recordDB.getRecordById(client, recordId);
35 |
36 | // @error 2. 유효하지 않은 recordId
37 | if (!record) {
38 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.RECORD_ID_NOT_VALID));
39 | }
40 |
41 | const entry = await roomDB.getUserInfoByEntryId(client, record.entryId);
42 |
43 | await reportDB.addReport(client, user.userId, entry.userId, recordId);
44 | const reportData = `
45 | 신고한 유저 ID: ${user.userId}
46 | 신고한 유저 닉네임: ${user.nickname}
47 | 신고대상 유저 ID: ${entry.userId}
48 | 신고대상 유저 닉네임: ${entry.nickname}
49 | 신고대상 record ID: ${record.recordId}
50 | 신고대상 record 사진: ${record.certifyingImg}
51 | 신고사유: ${reportReason}
52 | 신고일자: ${dayjs().add(9, 'hour')}
53 | `;
54 | slackAPI.feedReporotToSlack(reportData, slackAPI.DEV_WEB_HOOK_FEED_REPORT);
55 |
56 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.REPORT_FEED_SUCCESS));
57 | } catch (error) {
58 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
59 | console.log(error);
60 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
61 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
62 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
63 | } finally {
64 | client.release();
65 | }
66 | };
67 |
--------------------------------------------------------------------------------
/functions/api/routes/feed/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const { checkUser } = require('../../../middlewares/auth');
4 |
5 | router.get('', checkUser, require('./feedGET'));
6 | router.post('/:recordId/like', checkUser, require('./feedLikePOST'));
7 | router.post('/:recordId/report', checkUser, require('./feedReportPOST'));
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/functions/api/routes/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | router.use('/auth', require('./auth'));
5 | router.use('/feed', require('./feed'));
6 | router.use('/myroom', require('./myroom'));
7 | router.use('/notice', require('./notice'));
8 | router.use('/room', require('./room'));
9 | router.use('/user', require('./user'));
10 | router.use('/scheduling', require('./scheduling'));
11 | router.use('/version', require('./version'));
12 |
13 | module.exports = router;
14 |
--------------------------------------------------------------------------------
/functions/api/routes/myroom/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const { checkUser } = require('../../../middlewares/auth');
4 |
5 | router.get('/:roomId', checkUser, require('./myroomRoomGET'));
6 | router.get('', checkUser, require('./myroomGET'));
7 | router.get('/thumbnail/:roomId', checkUser, require('./myroomThumbnailListGET'));
8 | router.patch('/:roomId/thumbnail/:recordId', checkUser, require('./myroomThumbnailPATCH'));
9 |
10 | module.exports = router;
11 |
--------------------------------------------------------------------------------
/functions/api/routes/myroom/myroomGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { roomDB, sparkDB } = require('../../../db');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 | const dayjs = require('dayjs');
9 | const _ = require('lodash');
10 |
11 | /**
12 | * @보관함_리스트_불러오기
13 | * @route GET /myroom/:roomType?lastId=&size=
14 | * @error
15 | * 1. 잘못된 roomType
16 | * 2. 잘못된 lastId
17 | */
18 |
19 | module.exports = async (req, res) => {
20 | const lastId = Number(req.query.lastId);
21 | const size = Number(req.query.size);
22 | const roomType = req.query.type;
23 | const user = req.user;
24 |
25 | let client;
26 |
27 | // @error 1. 잘못된 roomType
28 | if (!(roomType === 'ONGOING' || roomType === 'COMPLETE' || roomType === 'FAIL')) {
29 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.OUT_OF_VALUE));
30 | }
31 |
32 | try {
33 | client = await db.connect(req);
34 | const totalRooms = await roomDB.getCardsByUserId(client, user.userId);
35 | let ongoingRooms = totalRooms.filter((rawRoom) => rawRoom.status === 'ONGOING' && rawRoom.isOut === false);
36 | let completeRooms = totalRooms.filter((rawRoom) => rawRoom.status === 'COMPLETE' && rawRoom.isOut === false);
37 | let failRooms = totalRooms.filter((rawRoom) => rawRoom.status === 'FAIL' || rawRoom.isOut === true);
38 |
39 | const ongoingRoomNum = ongoingRooms.length;
40 | const completeRoomNum = completeRooms.length;
41 | const failRoomNum = failRooms.length;
42 | const totalRoomNum = ongoingRoomNum + completeRoomNum + failRoomNum;
43 |
44 | let rooms = [];
45 | let roomIds = [];
46 |
47 | if (roomType === 'ONGOING') {
48 | roomIds = ongoingRooms.map((room) => room.roomId);
49 | rooms = ongoingRooms;
50 | rooms = _.sortBy(rooms, 'startAt').reverse();
51 | } else if (roomType === 'COMPLETE') {
52 | roomIds = completeRooms.map((room) => room.roomId);
53 | rooms = completeRooms;
54 | rooms = _.sortBy(rooms, 'outAt').reverse();
55 | } else if (roomType === 'FAIL') {
56 | roomIds = failRooms.map((room) => room.roomId);
57 | rooms = failRooms;
58 | // 중도 퇴장으로 인한 미완료일 경우, end_date 보정
59 | for (let i = 0; i < rooms.length; i++) {
60 | const room = rooms[i];
61 | if (room.isOut) {
62 | room.endAt = room.outAt;
63 | }
64 | }
65 | rooms = _.sortBy(rooms, 'endAt').reverse();
66 | }
67 |
68 | if (lastId !== -1) {
69 | const lastIndex = _.indexOf(roomIds, lastId);
70 | // @error 1. 잘못된 last id
71 | if (lastIndex === -1) {
72 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.INVALID_LASTID));
73 | }
74 | roomIds = roomIds.slice(lastIndex + 1, lastIndex + 1 + size);
75 | ongoingRooms = ongoingRooms.slice(lastIndex + 1, lastIndex + 1 + size);
76 | }
77 | // 최초 요청시
78 | else {
79 | roomIds = roomIds.slice(0, size);
80 | rooms = rooms.slice(0, size);
81 | }
82 | let roomData = [];
83 |
84 | for (let i = 0; i < rooms.length; i++) {
85 | const room = rooms[i];
86 | const startDate = dayjs(room.startAt);
87 | const endDate = dayjs(room.endAt);
88 | const now = dayjs().add(9, 'hour');
89 | const today = dayjs(now.format('YYYY-MM-DD'));
90 | const leftDay = endDate.diff(today, 'day');
91 | const sparkCount = await sparkDB.countSparkByEntryId(client, room.entryId);
92 | let oneRoom = {
93 | roomId: room.roomId,
94 | roomName: room.roomName,
95 | leftDay,
96 | thumbnail: room.thumbnail,
97 | totalReceivedSpark: parseInt(sparkCount[0].count) ? parseInt(sparkCount[0].count) : 0,
98 | startDate: startDate.format('YYYY-MM-DD'),
99 | endDate: endDate.format('YYYY-MM-DD'),
100 | failDay: null,
101 | comment: room.comment,
102 | };
103 | if (roomType === 'FAIL') {
104 | const failDay = endDate.diff(startDate, 'day') + 1;
105 | oneRoom.failDay = failDay;
106 | }
107 | roomData.push(oneRoom);
108 | }
109 |
110 | const data = {
111 | nickname: user.nickname,
112 | totalRoomNum,
113 | ongoingRoomNum,
114 | completeRoomNum,
115 | failRoomNum,
116 | rooms: roomData,
117 | };
118 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_MYROOM_SUCCESS, data));
119 | } catch (error) {
120 | console.log(error);
121 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
122 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
123 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
124 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
125 | } finally {
126 | client.release();
127 | }
128 | };
129 |
--------------------------------------------------------------------------------
/functions/api/routes/myroom/myroomRoomGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { roomDB, sparkDB, recordDB } = require('../../../db');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 | const _ = require('lodash');
9 |
10 | /**
11 | * @인증사진_모아보기
12 | * @route GET /myroom/:roomId?lastId=&size=
13 | * @error
14 | * 1. roomId가 없음
15 | * 2. 존재하지 않는 습관방인 경우
16 | */
17 |
18 | module.exports = async (req, res) => {
19 | let lastId = Number(req.query.lastId);
20 | const size = Number(req.query.size);
21 | const user = req.user;
22 | const { roomId } = req.params;
23 |
24 | let client;
25 |
26 | // @error 1. roomId가 없음
27 | if (!roomId) {
28 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
29 | }
30 |
31 | try {
32 | client = await db.connect(req);
33 |
34 | const room = await roomDB.getRoomById(client, roomId);
35 |
36 | // @error 2. 존재하지 않는 습관방인 경우
37 | if (!room) {
38 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.NO_CONTENT, responseMessage.GET_ROOM_DATA_FAIL));
39 | }
40 |
41 | // @error 3. 접근 권한이 없는 유저인 경우
42 | const entry = await roomDB.checkEnteredById(client, roomId, user.userId);
43 | if (!entry) {
44 | return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.NOT_MEMBER));
45 | }
46 |
47 | const pagedRecords = await recordDB.getPagedRecordsByEntryId(client, entry.entryId, lastId, size);
48 |
49 | // 해당하는 record가 없을 경우
50 | if (!pagedRecords.length) {
51 | const data = {
52 | roomName: room.roomName,
53 | records: [],
54 | };
55 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_MYROOM_DETAIL_SUCCESS, data));
56 | }
57 |
58 | const recordIds = pagedRecords.map((o) => o.recordId);
59 |
60 | const sparkNums = await sparkDB.countSparkByRecordIds(client, recordIds);
61 |
62 | const records = pagedRecords.map((record) => {
63 | const sparkCount = _.find(sparkNums, { recordId: record.recordId });
64 | const sparkNum = sparkCount ? Number(sparkCount.sparkNum) : 0;
65 | return {
66 | recordId: record.recordId,
67 | leftDay: 66 - record.day,
68 | certifyingImg: record.certifyingImg,
69 | sparkNum,
70 | status: record.status,
71 | timerRecord: record.timerRecord,
72 | };
73 | });
74 |
75 | const data = {
76 | roomName: room.roomName,
77 | records,
78 | };
79 |
80 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_MYROOM_DETAIL_SUCCESS, data));
81 | } catch (error) {
82 | console.log(error);
83 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
84 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
85 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
86 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
87 | } finally {
88 | client.release();
89 | }
90 | };
91 |
--------------------------------------------------------------------------------
/functions/api/routes/myroom/myroomThumbnailListGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { roomDB, sparkDB, recordDB } = require('../../../db');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 | const _ = require('lodash');
9 |
10 | /**
11 | * @대표사진_변경_뷰에서_인증사진_모아보기
12 | * @route GET /myroom/:roomId?lastId=&size=
13 | * @error
14 | * 1. roomId가 없음
15 | * 2. 존재하지 않는 습관방인 경우
16 | */
17 |
18 | module.exports = async (req, res) => {
19 | let lastId = Number(req.query.lastId);
20 | const size = Number(req.query.size);
21 | const user = req.user;
22 | const { roomId } = req.params;
23 |
24 | let client;
25 |
26 | // @error 1. roomId가 없음
27 | if (!roomId) {
28 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
29 | }
30 |
31 | try {
32 | client = await db.connect(req);
33 |
34 | const room = await roomDB.getRoomById(client, roomId);
35 |
36 | // @error 2. 존재하지 않는 습관방인 경우
37 | if (!room) {
38 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.NO_CONTENT, responseMessage.GET_ROOM_DATA_FAIL));
39 | }
40 |
41 | // @error 3. 접근 권한이 없는 유저인 경우
42 | const entry = await roomDB.checkEnteredById(client, roomId, user.userId);
43 | if (!entry) {
44 | return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.NOT_MEMBER));
45 | }
46 |
47 | const pagedRecords = await recordDB.getDonePagedRecordsByEntryId(client, entry.entryId, lastId, size);
48 |
49 | // 해당하는 record가 없을 경우
50 | if (!pagedRecords.length) {
51 | const data = {
52 | roomName: room.roomName,
53 | records: [],
54 | };
55 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_MYROOM_DETAIL_SUCCESS, data));
56 | }
57 |
58 | const recordIds = pagedRecords.map((o) => o.recordId);
59 |
60 | const sparkNums = await sparkDB.countSparkByRecordIds(client, recordIds);
61 |
62 | const records = pagedRecords.map((record) => {
63 | const sparkCount = _.find(sparkNums, { recordId: record.recordId });
64 | const sparkNum = sparkCount ? Number(sparkCount.sparkNum) : 0;
65 | return {
66 | recordId: record.recordId,
67 | leftDay: 66 - record.day,
68 | certifyingImg: record.certifyingImg,
69 | sparkNum,
70 | status: record.status,
71 | timerRecord: record.timerRecord,
72 | };
73 | });
74 |
75 | const data = {
76 | roomName: room.roomName,
77 | records,
78 | };
79 |
80 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_THUMBNAIL_LIST_SUCCESS, data));
81 | } catch (error) {
82 | console.log(error);
83 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
84 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
85 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
86 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
87 | } finally {
88 | client.release();
89 | }
90 | };
91 |
--------------------------------------------------------------------------------
/functions/api/routes/myroom/myroomThumbnailPATCH.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { roomDB, sparkDB, recordDB } = require('../../../db');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 | const _ = require('lodash');
9 |
10 | /**
11 | * @보관함_대표사진_변경
12 | * @route PATCH /myroom/:roomId/thumbnail/:recordId
13 | * @error
14 | * 1. roomId/recordId 가 없음
15 | * 2. 존재하지 않는 습관방인 경우
16 | * 3. 접근 권한이 없는 유저인 경우
17 | * 4. 올바르지 않은 recordId인 경우 (해당 습관방의 recordId가 아님, NONE/REST상태의 record, 없는 record)
18 | */
19 |
20 | module.exports = async (req, res) => {
21 | const user = req.user;
22 | const { roomId, recordId } = req.params;
23 |
24 | let client;
25 |
26 | // @error 1. roomId/recordId 가 없음
27 | if (!roomId || !recordId) {
28 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
29 | }
30 |
31 | try {
32 | client = await db.connect(req);
33 |
34 | const room = await roomDB.getRoomById(client, roomId);
35 | console.log(room);
36 | // @error 2. 존재하지 않는 습관방인 경우
37 | if (!room) {
38 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.NO_CONTENT, responseMessage.GET_ROOM_DATA_FAIL));
39 | }
40 |
41 | // @error 3. 접근 권한이 없는 유저인 경우
42 | const entry = await roomDB.checkEnteredById(client, roomId, user.userId);
43 | if (!entry) {
44 | return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.NOT_MEMBER));
45 | }
46 |
47 | const record = await recordDB.getRecordById(client, recordId);
48 | // @error 4. 올바르지 않은 recordId인 경우 (해당 습관방의 recordId가 아님, NONE/REST상태의 record, 없는 record)
49 | if (!record || record.entryId != entry.entryId || record.status !== 'DONE' || !record.certifyingImg) {
50 | res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.INCORRECT_RECORD));
51 | }
52 |
53 | await roomDB.updateThumbnail(client, entry.entryId, record.certifyingImg);
54 |
55 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_THUMBNAIL_SUCCESS));
56 | } catch (error) {
57 | console.log(error);
58 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
59 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
60 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
61 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
62 | } finally {
63 | client.release();
64 | }
65 | };
66 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/active/activeGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const dayjs = require('dayjs');
3 | const util = require('../../../../lib/util');
4 | const statusCode = require('../../../../constants/statusCode');
5 | const responseMessage = require('../../../../constants/responseMessage');
6 | const db = require('../../../../db/db');
7 | const slackAPI = require('../../../../middlewares/slackAPI');
8 | const { noticeDB } = require('../../../../db');
9 | const { passedDayToStr } = require('../../../../lib/passedDayToStr');
10 |
11 | /**
12 | * @활동_알림_조회
13 | * @route GET /notice/active?lastId=&size=
14 | * @error
15 | */
16 |
17 | module.exports = async (req, res) => {
18 | const user = req.user;
19 | const userId = user.userId;
20 | const { lastId, size } = req.query;
21 |
22 | let client;
23 |
24 | try {
25 | client = await db.connect(req);
26 |
27 | const newServiceNum = await noticeDB.getNumberOfUnreadServiceNoticeById(client, userId);
28 | const newService = newServiceNum > 0 ? true : false;
29 |
30 | const actives = await noticeDB.getActivesByUserId(client, userId, parseInt(lastId), parseInt(size));
31 | let now = dayjs().add(9, 'hour');
32 | now = now.set('hour', 0);
33 | now = now.set('minute', 0);
34 | now = now.set('second', 1);
35 |
36 | const notices = actives.map((a) => {
37 | const notice = {};
38 | let createdAt = dayjs(a.createdAt);
39 | createdAt = createdAt.set('hour', 0);
40 | createdAt = createdAt.set('minute', 0);
41 | createdAt = createdAt.set('second', 0);
42 | const passedDay = now.diff(createdAt, 'd');
43 |
44 | notice['noticeId'] = a.notificationId;
45 | notice['noticeTitle'] = a.title;
46 | notice['noticeContent'] = a.content;
47 | notice['noticeImg'] = a.thumbnail;
48 | notice['isThumbProfile'] = a.isThumbProfile;
49 | notice['day'] = passedDayToStr(passedDay);
50 | notice['isNew'] = !a.isRead;
51 | return notice;
52 | });
53 |
54 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_ACTIVE_SUCCESS, { newService, notices }));
55 | } catch (error) {
56 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
57 | console.log(error);
58 |
59 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
60 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
61 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
62 | } finally {
63 | client.release();
64 | }
65 | };
66 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/active/activeReadPATCH.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../../lib/util');
3 | const statusCode = require('../../../../constants/statusCode');
4 | const responseMessage = require('../../../../constants/responseMessage');
5 | const db = require('../../../../db/db');
6 | const { noticeDB } = require('../../../../db');
7 |
8 | /**
9 | * @활동_알림_읽음_처리
10 | * @route PATCH /notice/active/read
11 | * @body
12 | * @error
13 | */
14 |
15 | module.exports = async (req, res) => {
16 | const user = req.user;
17 | const userId = user.userId;
18 |
19 | let client;
20 |
21 | try {
22 | client = await db.connect(req);
23 | await noticeDB.activeReadByUserId(client, userId);
24 |
25 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.ACTIVE_READ_SUCCESS));
26 | } catch (error) {
27 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
28 | console.log(error);
29 |
30 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
31 | } finally {
32 | client.release();
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/active/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const { checkUser } = require('../../../../middlewares/auth');
4 |
5 | router.get('', checkUser, require('./activeGET'));
6 | router.patch('/read', checkUser, require('./activeReadPATCH'));
7 |
8 | module.exports = router;
9 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const { checkUser } = require('../../../middlewares/auth');
4 |
5 | router.use('/active', require('./active'));
6 | router.use('/service', require('./service'));
7 |
8 | router.get('/new', checkUser, require('./noticeNewGET'));
9 | router.get('/setting', checkUser, require('./noticeSettingGET'));
10 | router.patch('/setting', checkUser, require('./noticeSettingPATCH'));
11 | router.delete('/:noticeId', checkUser, require('./noticeDELETE'));
12 |
13 | module.exports = router;
14 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/noticeDELETE.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { noticeDB } = require('../../../db');
7 |
8 | /**
9 | * @서비스_및_활동_알림_삭제
10 | * @route GET /notice/:noticeId
11 | * @error
12 | * 1. 알림 id가 전달되지 않음
13 | * 2. 올바르지 않은 noticeId가 전달된 경우
14 | * 3. 알림 삭제 권한이 없는 사용자가 요청을 보낸 경우
15 | */
16 |
17 | module.exports = async (req, res) => {
18 | const { noticeId } = req.params;
19 | const user = req.user;
20 | const userId = user.userId;
21 |
22 | // @error 1. 알림 id가 전달되지 않음
23 | if (!noticeId) {
24 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
25 | }
26 |
27 | let client;
28 |
29 | try {
30 | client = await db.connect(req);
31 |
32 | const notice = await noticeDB.getNoticeByNoticeId(client, noticeId);
33 |
34 | // @error 2. 올바르지 않은 noticeId가 전달된 경우
35 | if (!notice) {
36 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NOTICE_ID_NOT_VALID));
37 | }
38 |
39 | // @error 3. 알림 삭제 권한이 없는 사용자가 요청을 보낸 경우
40 | if (userId !== notice.receiverId) {
41 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.PRIV_NOT_FOUND));
42 | }
43 |
44 | await noticeDB.deleteNoticeByNoticeId(client, noticeId);
45 |
46 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.NOTICE_DELETE_SUCCESS));
47 | } catch (error) {
48 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
49 | console.log(error);
50 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
51 | } finally {
52 | client.release();
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/noticeNewGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { noticeDB } = require('../../../db');
8 |
9 | /**
10 | * @새로운_빨콩_알림_조회
11 | * @route GET /notice/new
12 | * @error
13 | *
14 | */
15 |
16 | module.exports = async (req, res) => {
17 | const user = req.user;
18 |
19 | let client;
20 |
21 | try {
22 | client = await db.connect(req);
23 |
24 | const numOfUnreadNotice = await noticeDB.getNumberOfUnreadNoticeById(client, user.userId);
25 |
26 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_NEW_NOTICE_SUCCESS, { newNotice: numOfUnreadNotice > 0 ? true : false }));
27 | } catch (error) {
28 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
29 | console.log(error);
30 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
31 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
32 |
33 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
34 | } finally {
35 | client.release();
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/noticeSettingGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const slackAPI = require('../../../middlewares/slackAPI');
6 |
7 | /**
8 | * @푸시알림_설정_조회
9 | * @route GET /notice/setting
10 | * @error
11 | *
12 | */
13 |
14 | module.exports = async (req, res) => {
15 | const user = req.user;
16 |
17 | try {
18 | const { pushRoomStart, pushSpark, pushConsider, pushCertification, pushRemind } = user;
19 |
20 | const data = {
21 | roomStart: pushRoomStart,
22 | spark: pushSpark,
23 | consider: pushConsider,
24 | certification: pushCertification,
25 | remind: pushRemind,
26 | };
27 |
28 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_NOTICE_SETTING_SUCCESS, data));
29 | } catch (error) {
30 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
31 | console.log(error);
32 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
33 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
34 |
35 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
36 | } finally {
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/noticeSettingPATCH.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { userDB } = require('../../../db');
8 |
9 | /**
10 | * @푸시알림_설정_토글
11 | * @route PATCH /notice/setting
12 | * @error
13 | * 1. 유효하지 않은 category
14 | */
15 |
16 | module.exports = async (req, res) => {
17 | const user = req.user;
18 | const category = req.query.category;
19 |
20 | // @error 1. 유효하지 않은 category
21 | if (!['roomStart', 'spark', 'consider', 'certification', 'remind'].includes(category)) {
22 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.PUSH_CATEGORY_INVALID));
23 | }
24 |
25 | let client;
26 |
27 | try {
28 | client = await db.connect(req);
29 |
30 | await userDB.togglePushSettingById(client, user.userId, category);
31 |
32 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.PUSH_TOGGLE_SUCCESS));
33 | } catch (error) {
34 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
35 | console.log(error);
36 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
37 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
38 |
39 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
40 | } finally {
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/service/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const { checkUser } = require('../../../../middlewares/auth');
4 |
5 | router.get('', checkUser, require('./serviceGET'));
6 | router.patch('/read', checkUser, require('./serviceReadPATCH'));
7 |
8 | module.exports = router;
9 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/service/serviceGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const dayjs = require('dayjs');
3 | const util = require('../../../../lib/util');
4 | const statusCode = require('../../../../constants/statusCode');
5 | const responseMessage = require('../../../../constants/responseMessage');
6 | const db = require('../../../../db/db');
7 | const slackAPI = require('../../../../middlewares/slackAPI');
8 | const { noticeDB } = require('../../../../db');
9 | const { passedDayToStr } = require('../../../../lib/passedDayToStr');
10 |
11 | /**
12 | * @서비스_알림_조회
13 | * @route GET /notice/service?lastId=&size=
14 | * @error
15 | */
16 |
17 | module.exports = async (req, res) => {
18 | const user = req.user;
19 | const userId = user.userId;
20 | const { lastId, size } = req.query;
21 |
22 | let client;
23 |
24 | try {
25 | client = await db.connect(req);
26 |
27 | const newActiveNum = await noticeDB.getNumberOfUnreadActiveNoticeById(client, userId);
28 | const newActive = newActiveNum > 0 ? true : false;
29 |
30 | const services = await noticeDB.getServicesByUserId(client, userId, parseInt(lastId), parseInt(size));
31 | let now = dayjs().add(9, 'hour');
32 | now = now.set('hour', 0);
33 | now = now.set('minute', 0);
34 | now = now.set('second', 1);
35 |
36 | const notices = services.map((s) => {
37 | const notice = {};
38 | let createdAt = dayjs(s.createdAt);
39 | createdAt = createdAt.set('hour', 0);
40 | createdAt = createdAt.set('minute', 0);
41 | createdAt = createdAt.set('second', 0);
42 | const passedDay = now.diff(createdAt, 'd');
43 |
44 | notice['noticeId'] = s.notificationId;
45 | notice['noticeTitle'] = s.title;
46 | notice['noticeContent'] = s.content;
47 | notice['day'] = passedDayToStr(passedDay);
48 | notice['isNew'] = !s.isRead;
49 | return notice;
50 | });
51 |
52 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_SERVICE_SUCCESS, { newActive, notices }));
53 | } catch (error) {
54 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
55 | console.log(error);
56 |
57 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
58 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
59 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
60 | } finally {
61 | client.release();
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/functions/api/routes/notice/service/serviceReadPATCH.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../../lib/util');
3 | const statusCode = require('../../../../constants/statusCode');
4 | const responseMessage = require('../../../../constants/responseMessage');
5 | const db = require('../../../../db/db');
6 | const { noticeDB } = require('../../../../db');
7 |
8 | /**
9 | * @서비스_알림_읽음_처리
10 | * @route PATCH /notice/service/read
11 | * @body
12 | * @error
13 | */
14 |
15 | module.exports = async (req, res) => {
16 | const user = req.user;
17 | const userId = user.userId;
18 |
19 | let client;
20 |
21 | try {
22 | client = await db.connect(req);
23 | await noticeDB.serviceReadByUserId(client, userId);
24 |
25 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.SERVICE_READ_SUCCESS));
26 | } catch (error) {
27 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
28 | console.log(error);
29 |
30 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
31 | } finally {
32 | client.release();
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/functions/api/routes/room/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const { checkUser } = require('../../../middlewares/auth');
4 | const uploadImageIntoSubDir = require('../../../middlewares/uploadImage');
5 |
6 | router.get('/code/:code', checkUser, require('./roomCodeGET'));
7 | router.post('', checkUser, require('./roomPOST'));
8 | router.post('/:roomId/enter', checkUser, require('./roomEnterPOST'));
9 | router.patch('/:roomId/purpose', checkUser, require('./roomPurposePATCH'));
10 | router.get('/:roomId/waiting', checkUser, require('./roomWaitingGET'));
11 | router.get('/:roomId', checkUser, require('./roomDetailGET'));
12 | router.get('', checkUser, require('./roomListGET'));
13 | router.get('/:roomId/waiting/member', checkUser, require('./roomWaitingMemberGET'));
14 | router.post('/:roomId/start', checkUser, require('./roomStartPOST'));
15 | router.post('/:roomId/status', checkUser, require('./roomStatusPOST'));
16 | router.post('/:roomId/spark', checkUser, require('./sparkPOST'));
17 | router.post('/:roomId/record', checkUser, uploadImageIntoSubDir('certification'), require('./roomRecordPOST'));
18 | router.delete('/:roomId', checkUser, require('./roomDELETE'));
19 | router.delete('/:roomId/out', checkUser, require('./roomOutDELETE'));
20 | router.patch('/:roomId/read', checkUser, require('./roomReadPATCH'));
21 | router.get('/:roomId/timeline', checkUser, require('./roomTimelineGET'));
22 |
23 | module.exports = router;
24 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomCodeGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { userDB, roomDB } = require('../../../db');
8 |
9 | /**
10 | * @코드로_대기_방_정보_확인
11 | * @route GET /room/code/:code
12 | * @error
13 | * 1. 참여 코드가 전달되지 않음
14 | * 2. 참여코드에 일치하는 방이 없는 경우
15 | * 3. 이미 시작된 습관방인 경우
16 | * 4. 이미 참여중인 방인 경우
17 | * 5. 한번 내보내진 사용자인 경우
18 | * 6. 정원이 가득찬 습관방
19 | */
20 |
21 | module.exports = async (req, res) => {
22 | const { code } = req.params;
23 | const user = req.user;
24 | const userId = user.userId;
25 |
26 | // @error 1. 참여 코드가 전달되지 않음
27 | if (!code) {
28 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
29 | }
30 |
31 | let client;
32 |
33 | try {
34 | client = await db.connect(req);
35 |
36 | const room = await roomDB.getRoomByCode(client, code);
37 |
38 | // @error 2. 참여 코드에 일치하는 방이 없음
39 | if (!room) {
40 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.GET_WAITROOM_DATA_NULL));
41 | }
42 |
43 | // @error 3. 참여 코드에 해당하는 방이 대기상태가 아님
44 | if (room.status === 'ONGOING') {
45 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.GET_WAITROOM_DATA_STARTED));
46 | } else if (room.status === 'COMPLETE' || room.status === 'FAIL') {
47 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.GET_WAITROOM_DATA_IMPOSSIBLE));
48 | }
49 |
50 | // @error 5. 한번 내보내진 사용자인 경우
51 | const kickedHistory = await roomDB.kickedHistoryByIds(client, room.roomId, userId);
52 | if (kickedHistory.length !== 0) {
53 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.GET_WAITROOM_DATA_IMPOSSIBLE));
54 | }
55 |
56 | const creator = await userDB.getUserById(client, room.creator);
57 | const entries = await roomDB.getEntriesByRoomId(client, room.roomId);
58 | const imageNum = 3;
59 | let profileImgs = [];
60 |
61 | for (let i = 0; i < entries.length; i++) {
62 | if (i < imageNum) {
63 | let user = await userDB.getUserById(client, entries[i].userId);
64 | profileImgs.push(user.profileImg);
65 | }
66 |
67 | // @error 4. 이미 해당 습관에 참여중인 사용자
68 | if (userId === entries[i].userId) {
69 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.GET_WAITROOM_DATA_ALREADY));
70 | }
71 | }
72 |
73 | // @error 6. 정원이 가득찬 습관방
74 | if (entries.length > 9) {
75 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.GET_WAITROOM_DATA_FULL));
76 | }
77 |
78 | const data = {
79 | roomId: room.roomId,
80 | roomName: room.roomName,
81 | creatorName: creator.nickname,
82 | creatorImg: creator.profileImg,
83 | profileImgs,
84 | totalNums: entries.length,
85 | };
86 |
87 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_WAITROOM_DATA_SUCCESS, data));
88 | } catch (error) {
89 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
90 | console.log(error);
91 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
92 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
93 |
94 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
95 | } finally {
96 | client.release();
97 | }
98 | };
99 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomDELETE.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const alarmMessage = require('../../../constants/alarmMessage');
5 | const responseMessage = require('../../../constants/responseMessage');
6 | const db = require('../../../db/db');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 | const { userDB, roomDB, noticeDB } = require('../../../db');
9 |
10 | /**
11 | * @대기방_삭제하기
12 | * @route DELETE /room/:roomId
13 | * @body
14 | * @error
15 | * 1. roomId가 전달되지 않음
16 | * 2. 존재하지 않는 습관방
17 | * 3. 대기중인 습관방이 아닌 경우
18 | * 4. 요청을 보낸 사용자가 대기방의 호스트가 아닌 경우
19 | */
20 |
21 | module.exports = async (req, res) => {
22 | const { roomId } = req.params;
23 | const user = req.user;
24 |
25 | // @error 1. roomId가 전달되지 않음
26 | if (!roomId) {
27 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
28 | }
29 |
30 | let client;
31 |
32 | try {
33 | client = await db.connect(req);
34 |
35 | const room = await roomDB.getRoomById(client, roomId);
36 |
37 | // @error 2. 존재하지 않는 습관방
38 | if (!room) {
39 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_INVALID));
40 | }
41 |
42 | // @error 3. 대기중인 습관방이 아닌 경우
43 | if (room.status !== 'NONE') {
44 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_NOT_WAITING));
45 | }
46 |
47 | // @error 4. 요청을 보낸 사용자가 대기방의 호스트가 아닌 경우
48 | if (room.creator !== user.userId) {
49 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.PRIV_NOT_FOUND));
50 | }
51 |
52 | // 대기방 삭제하기
53 | await roomDB.deleteRoomById(client, room.roomId);
54 |
55 | // 본인을 제외한 참여자들에게 서비스 알림 보내기
56 | const { title, body, isService } = alarmMessage.ROOM_DELETE(room.roomName);
57 |
58 | const friends = await roomDB.getFriendsByIds(client, room.roomId, user.userId);
59 |
60 | const notifications = friends.map((f) => {
61 | return `('${title}', '${body}', '', ${f.userId}, ${isService}, false, ${room.roomId})`;
62 | });
63 |
64 | if (notifications.length > 0) {
65 | await noticeDB.addNotifications(client, notifications);
66 | }
67 |
68 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.ROOM_DELETE_SUCCESS));
69 | } catch (error) {
70 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
71 | console.log(error);
72 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
73 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
74 |
75 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
76 | } finally {
77 | client.release();
78 | }
79 | };
80 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomDetailGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { roomDB, sparkDB, dialogDB, lifeTimelineDB } = require('../../../db');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 | const dayjs = require('dayjs');
9 |
10 | /**
11 | * @습관방_상세_조회
12 | * @route GET /room/:roomId
13 | * @error
14 | * 1. 존재하지 않는 습관방인 경우
15 | * 2. 진행중인 습관방이 아닌 경우
16 | * 3. 접근 권한이 없는 유저인 경우
17 | */
18 |
19 | module.exports = async (req, res) => {
20 | const { roomId } = req.params;
21 | const user = req.user;
22 |
23 | let client;
24 |
25 | try {
26 | client = await db.connect(req);
27 |
28 | const room = await roomDB.getRoomById(client, roomId);
29 |
30 | // @error 1. 존재하지 않는 습관방인 경우
31 | if (!room) {
32 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.NO_CONTENT, responseMessage.GET_ROOM_DATA_FAIL));
33 | }
34 |
35 | const startDate = dayjs(room.startAt);
36 | const endDate = dayjs(room.endAt);
37 | const now = dayjs().add(9, 'hour');
38 | const today = dayjs(now.format('YYYY-MM-DD'));
39 | const leftDay = endDate.diff(today, 'day');
40 | const day = dayjs(today).diff(startDate, 'day') + 1;
41 |
42 | // @error 2. 진행중인 습관방이 아닌 경우
43 | if (room.status !== 'ONGOING' || leftDay < 0) {
44 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NOT_ONGOING_ROOM));
45 | }
46 | const entries = await roomDB.getEntriesByRoomId(client, roomId);
47 |
48 | // @error 3. 접근 권한이 없는 유저인 경우
49 | const userEntry = entries.filter((entry) => entry.userId === user.userId);
50 | if (!userEntry.length) {
51 | return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.NOT_MEMBER));
52 | }
53 |
54 | let isTimelineNew = false;
55 | let newTimelineCount = await lifeTimelineDB.getNewTimelineCount(client, roomId, user.userId);
56 | newTimelineCount > 0 ? (isTimelineNew = true) : (isTimelineNew = false);
57 |
58 | const records = await roomDB.getRecordsByDay(client, roomId, day);
59 |
60 | let myRecord = null;
61 | let considerRecords = [];
62 | let noneRecords = [];
63 | let restRecords = [];
64 | let doneRecords = [];
65 |
66 | records.map((record) => {
67 | if (record.userId === user.userId) {
68 | myRecord = {
69 | recordId: record.recordId,
70 | userId: record.userId,
71 | profileImg: record.profileImg,
72 | nickname: record.nickname,
73 | status: record.status,
74 | rest: record.rest,
75 | };
76 | } else {
77 | if (record.status === 'CONSIDER') {
78 | considerRecords.push({
79 | recordId: record.recordId,
80 | userId: record.userId,
81 | profileImg: record.profileImg,
82 | nickname: record.nickname,
83 | status: record.status,
84 | });
85 | } else if (record.status === 'NONE') {
86 | noneRecords.push({
87 | recordId: record.recordId,
88 | userId: record.userId,
89 | profileImg: record.profileImg,
90 | nickname: record.nickname,
91 | status: record.status,
92 | });
93 | } else if (record.status === 'REST') {
94 | restRecords.push({
95 | recordId: record.recordId,
96 | userId: record.userId,
97 | profileImg: record.profileImg,
98 | nickname: record.nickname,
99 | status: record.status,
100 | });
101 | } else {
102 | doneRecords.push({
103 | recordId: record.recordId,
104 | userId: record.userId,
105 | profileImg: record.profileImg,
106 | nickname: record.nickname,
107 | status: record.status,
108 | });
109 | }
110 | }
111 | });
112 |
113 | const otherRecords = [...considerRecords, ...noneRecords, ...doneRecords, ...restRecords];
114 |
115 | const receivedSpark = await sparkDB.countSparkByRecordId(client, myRecord.recordId);
116 | myRecord.receivedSpark = parseInt(receivedSpark.count);
117 |
118 | // new_term false처리
119 | if (userEntry[0].newTerm) {
120 | await roomDB.updateTermFalseByEntryId(client, userEntry[0].entryId);
121 | }
122 |
123 | const data = {
124 | roomId: room.roomId,
125 | roomName: room.roomName,
126 | startDate: startDate.format('YYYY.MM.DD.'),
127 | endDate: endDate.format('YYYY.MM.DD.'),
128 | moment: userEntry[0].moment,
129 | purpose: userEntry[0].purpose,
130 | leftDay,
131 | life: room.life,
132 | fromStart: room.fromStart,
133 | lifeDeductionCount: 0,
134 | isTimelineNew,
135 | isTermNew: userEntry[0].newTerm,
136 | myRecord,
137 | otherRecords,
138 | };
139 |
140 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_ROOM_DETAIL_SUCCESS, data));
141 | } catch (error) {
142 | console.log(error);
143 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
144 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
145 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
146 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
147 | } finally {
148 | client.release();
149 | }
150 | };
151 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomEnterPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { roomDB } = require('../../../db');
8 |
9 | /**
10 | * @습관방_참여
11 | * @route POST /room/:roomId/enter
12 | * @body
13 | * @error
14 | * 1. roomId가 올바르지 않음 (이미 삭제된 방이거나 존재하지 않은 방)
15 | * 2. 사용자가 이미 참여중인 방임
16 | * 3. 정원이 가득찬 습관방
17 | * 4. 습관방 참여 실패
18 | */
19 |
20 | module.exports = async (req, res) => {
21 | const { roomId } = req.params;
22 | const user = req.user;
23 | const userId = user.userId;
24 |
25 | let client;
26 |
27 | try {
28 | client = await db.connect(req);
29 |
30 | const room = await roomDB.getRoomById(client, roomId);
31 |
32 | // @error 1. roomId가 올바르지 않음 (이미 삭제된 방이거나 존재하지 않은 방)
33 | if (!room) {
34 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_INVALID));
35 | }
36 |
37 | // @error 2. 사용자가 이미 참여중인 방임
38 | const isEntered = await roomDB.checkEnteredById(client, roomId, userId);
39 | if (isEntered) {
40 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ENTER_ROOM_ALREADY));
41 | }
42 |
43 | // error 3. 정원이 가득찬 습관방
44 | const entries = await roomDB.getEntriesByRoomId(client, roomId);
45 | if (entries.length > 9) {
46 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.GET_WAITROOM_DATA_FULL));
47 | }
48 |
49 | const enterEntry = await roomDB.enterById(client, roomId, userId);
50 |
51 | // @error 4. 습관 방 참여 실패
52 | if (!enterEntry) {
53 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ENTER_ROOM_FAIL));
54 | }
55 |
56 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.ENTER_ROOM_SUCCESS));
57 | } catch (error) {
58 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
59 | console.log(error);
60 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
61 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
62 |
63 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
64 | } finally {
65 | client.release();
66 | }
67 | };
68 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomListGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { roomDB, dialogDB } = require('../../../db');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 | const dayjs = require('dayjs');
9 | const _ = require('lodash');
10 |
11 | /**
12 | * @습관방_리스트_조회
13 | * @route GET /room?lastId=&size=
14 | * @error
15 | * 1. lastId 또는 size 값이 전달되지 않음
16 | * 2. 잘못된 lastId
17 | */
18 |
19 | module.exports = async (req, res) => {
20 | const lastId = Number(req.query.lastId);
21 | const size = Number(req.query.size);
22 | const user = req.user;
23 |
24 | // @error 1. lastId 또는 size 값이 전달되지 않음
25 | if (!lastId || !size) {
26 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
27 | }
28 |
29 | let client;
30 |
31 | try {
32 | client = await db.connect(req);
33 | let dialogs = await dialogDB.getUserDialogs(client, user.userId, ["'COMPLETE'", "'FAIL'"]);
34 | dialogs = _.sortBy(dialogs, 'type');
35 | const rawRooms = await roomDB.getRoomsByUserId(client, user.userId);
36 | let waitingRooms = rawRooms.filter((rawRoom) => rawRoom.status === 'NONE');
37 | waitingRooms = _.sortBy(waitingRooms, 'createdAt').reverse(); // 최근에 생성된 대기방이 위로
38 | let ongoingRooms = rawRooms.filter((rawRoom) => rawRoom.status === 'ONGOING');
39 | ongoingRooms = _.sortBy(ongoingRooms, 'startTime').reverse(); // 최근에 시작한 습관방이 위로
40 |
41 | const dialogRoomIds = [...new Set(dialogs.filter(Boolean).map((room) => room.roomId))];
42 | const waitingRoomIds = [...new Set(waitingRooms.filter(Boolean).map((room) => room.roomId))];
43 | const ongoingRoomIds = [...new Set(ongoingRooms.filter(Boolean).map((room) => room.roomId))];
44 | const roomIds = waitingRoomIds.concat(dialogRoomIds.concat(ongoingRoomIds));
45 |
46 | let responseRoomIds = [];
47 |
48 | // 최초 요청이 아닐시
49 | if (lastId !== -1) {
50 | const lastIndex = _.indexOf(roomIds, lastId);
51 | // @error 2. 잘못된 last Id
52 | if (lastIndex === -1) {
53 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.INVALID_LASTID));
54 | }
55 | responseRoomIds = roomIds.slice(lastIndex + 1, lastIndex + 1 + size);
56 | }
57 | // 최초 요청시
58 | else {
59 | responseRoomIds = roomIds.slice(0, size);
60 | }
61 |
62 | // 마지막일 때 -> 빈 배열 return
63 | if (!responseRoomIds.length) {
64 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_ROOM_LIST_SUCCESS, { rooms: [] }));
65 | }
66 |
67 | const rawRoomInfo = await roomDB.getRoomsByIds(client, responseRoomIds);
68 | const roomInfoMap = new Map();
69 | rawRoomInfo.map((o) => {
70 | roomInfoMap.set(o.roomId, o);
71 | });
72 | let rooms = [];
73 |
74 | const roomInfo = rawRoomInfo.sort((a, b) => responseRoomIds.indexOf(a.roomId) - responseRoomIds.indexOf(b.roomId));
75 | const today = dayjs(dayjs().add(9, 'hour').format('YYYY-M-D'));
76 |
77 | // roomIds 빈 배열일 때 처리
78 | const rawProfiles = await roomDB.getUserProfilesByRoomIds(client, responseRoomIds, today);
79 | const profiles = rawProfiles.sort((a, b) => responseRoomIds.indexOf(a.roomId) - responseRoomIds.indexOf(b.roomId));
80 |
81 | let dialogRecords = [];
82 | if (dialogRoomIds.length > 0) {
83 | dialogRecords = await roomDB.getAllRecordsByUserIdAndRoomIds(client, user.userId, dialogRoomIds);
84 | }
85 |
86 | rooms = responseRoomIds.map((roomId) => {
87 | const isDialog = dialogRoomIds.includes(roomId);
88 | const roomInfo = roomInfoMap.get(roomId);
89 | let dialog;
90 | let status = 'NONE';
91 | let doneMemberNum = 0;
92 | let isUploaded = false;
93 | if (isDialog) {
94 | dialog = dialogs.filter((o) => o.roomId === roomId)[0];
95 | // dialog이면 status로 dialogType (COMPLETE / FAIL) 전달
96 | status = dialog.type;
97 | const endRecord = _(dialogRecords)
98 | .filter((o) => o.roomId === roomId)
99 | .sortBy('date')
100 | .value()
101 | .reverse()[0];
102 | if (endRecord.status === 'DONE') {
103 | isUploaded = true;
104 | }
105 | } else {
106 | const userStatus = profiles.filter(Boolean).filter((o) => {
107 | if (o.roomId === roomId && o.userId === user.userId) {
108 | return true;
109 | }
110 | return false;
111 | });
112 |
113 | // myStatus가 CONSIDER인 경우, NONE으로 전달
114 | const myStatus = userStatus[0].status;
115 | if (myStatus !== 'CONSIDER') {
116 | status = myStatus;
117 | }
118 |
119 | if (myStatus === 'DONE') {
120 | isUploaded = true;
121 | }
122 | }
123 |
124 | const doneMembers = profiles.filter(Boolean).filter((o) => {
125 | if (o.roomId === roomId && (o.status === 'REST' || o.status === 'DONE')) {
126 | return true;
127 | }
128 | return false;
129 | });
130 | doneMemberNum = doneMembers.length;
131 |
132 | let profileImgs = profiles
133 | .filter(Boolean)
134 | .filter((o) => o.roomId === roomId)
135 | .map((o) => o.profileImg);
136 |
137 | const memberNum = profileImgs.length;
138 | if (profileImgs.length < 3) {
139 | const length = profileImgs.length;
140 |
141 | for (let i = 0; i < 3 - length; i++) {
142 | profileImgs.push(null);
143 | }
144 | } else {
145 | profileImgs = profileImgs.slice(0, 3);
146 | }
147 |
148 | const endDate = dayjs(roomInfo.endAt);
149 | let leftDay = 0;
150 | if (isDialog) {
151 | if (dialog.type === 'COMPLETE') {
152 | leftDay = 0;
153 | } else {
154 | const startDate = dayjs(roomInfo.startAt);
155 | // 기존: startDate + 65 = endDate
156 | // startDate + 65 - endDate = leftDay
157 | // leftDay = 65 - (endDate - startDate)
158 | leftDay = 65 - endDate.diff(startDate, 'day');
159 | }
160 | } else {
161 | leftDay = endDate.diff(today, 'day');
162 | }
163 |
164 | const room = {
165 | roomId,
166 | roomName: roomInfo.roomName,
167 | leftDay,
168 | profileImg: profileImgs,
169 | life: roomInfo.life,
170 | isStarted: roomInfo.status === 'NONE' ? false : true,
171 | myStatus: status,
172 | memberNum,
173 | doneMemberNum,
174 | isUploaded,
175 | };
176 |
177 | return room;
178 | });
179 |
180 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_ROOM_LIST_SUCCESS, { rooms }));
181 | } catch (error) {
182 | console.log(error);
183 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
184 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
185 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
186 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
187 | } finally {
188 | client.release();
189 | }
190 | };
191 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomOutDELETE.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const alarmMessage = require('../../../constants/alarmMessage');
5 | const responseMessage = require('../../../constants/responseMessage');
6 | const db = require('../../../db/db');
7 | const slackAPI = require('../../../middlewares/slackAPI');
8 | const { userDB, roomDB, noticeDB } = require('../../../db');
9 |
10 | /**
11 | * @대기방_및_습관방_나가기
12 | * @route DELETE /room/:roomId/out
13 | * @body
14 | * @error
15 | * 1. roomId가 전달되지 않음
16 | * 2. 존재하지 않는 습관방
17 | * 3. 유저가 해당 습관방에 참여하지 않는 경우
18 | * 4. 본인이 host인데 대기방 나가기 요청을 보낸 경우
19 | */
20 |
21 | module.exports = async (req, res) => {
22 | const { roomId } = req.params;
23 | const user = req.user;
24 | const userId = user.userId;
25 |
26 | // @error 1. roomId가 전달되지 않음
27 | if (!roomId) {
28 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
29 | }
30 |
31 | let client;
32 |
33 | try {
34 | client = await db.connect(req);
35 |
36 | const room = await roomDB.getRoomById(client, roomId);
37 | // @error 2. 존재하지 않는 습관방
38 | if (!room) {
39 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_INVALID));
40 | }
41 |
42 | const entry = await roomDB.getEntryByIds(client, roomId, user.userId);
43 |
44 | // @error 3. 유저가 해당 습관방에 참여하지 않는 경우
45 | if (!entry) {
46 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NOT_MEMBER));
47 | }
48 |
49 | // @error 4. 본인이 host인데 대기방 나가기 요청을 보낸 경우
50 | if (room.status === 'NONE' && userId === room.creator) {
51 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.HOST_WAITROOM_OUT_FAIL));
52 | }
53 |
54 | // 대기방 또는 습관방 나가기
55 | await roomDB.outById(client, room, userId);
56 | const friends = await roomDB.getFriendsByIds(client, roomId, userId);
57 |
58 | // 습관방의 마지막 인원이었다면, 방 END 처리
59 | if (!friends.length) {
60 | await roomDB.endById(client, roomId);
61 | }
62 |
63 | // 진행중인 방을 나갔을 경우에는 본인을 제외한 참여자들에게 활동 알림 보내기
64 | if (room.status === 'ONGOING') {
65 | const { title, body, isService } = alarmMessage.ROOM_OUT(user.nickname, room.roomName);
66 |
67 | for (let i = 0; i < friends.length; i++) {
68 | const target = await userDB.getUserById(client, friends[i].userId);
69 | await noticeDB.addNotification(client, title, body, user.profileImg, target.userId, isService, true, room.roomId);
70 | }
71 | }
72 |
73 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.ROOM_OUT_SUCCESS));
74 | } catch (error) {
75 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
76 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
77 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
78 |
79 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
80 | } finally {
81 | client.release();
82 | }
83 | };
84 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { roomDB } = require('../../../db');
8 | const { nanoid } = require('nanoid');
9 |
10 | /**
11 | * @습관방_생성
12 | * @route POST /room
13 | * @body roomName:string, fromStart:boolean
14 | * @error
15 | * 1. 습관방 이름 / 습관방 타입이 전달되지 않음
16 | */
17 |
18 | module.exports = async (req, res) => {
19 | const { roomName, fromStart } = req.body;
20 | const user = req.user;
21 | const userId = user.userId;
22 |
23 | console.log(roomName, fromStart);
24 |
25 | // error 1. 습관방 이름 또는 타입이 전달되지 않음
26 | if (!roomName || typeof fromStart !== 'boolean') {
27 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
28 | }
29 |
30 | let client, code;
31 | let isCodeUnique = false;
32 |
33 | try {
34 | client = await db.connect(req);
35 | while (!isCodeUnique) {
36 | code = nanoid(7);
37 | isCodeUnique = await roomDB.isCodeUnique(client, code);
38 | }
39 |
40 | // 습관 방 생성
41 | const room = await roomDB.addRoom(client, roomName, code, userId, fromStart);
42 | const roomId = room.roomId;
43 |
44 | // 생성한 방에 입장
45 | await roomDB.enterById(client, roomId, userId);
46 |
47 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.CREATE_ROOM_SUCCESS, { roomId }));
48 | } catch (error) {
49 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
50 | console.log(error);
51 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
52 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
53 |
54 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
55 | } finally {
56 | client.release();
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomPurposePATCH.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { roomDB } = require('../../../db');
8 |
9 | /**
10 | * @나의_목표_설정하기
11 | * @route PATCH /room/:roomId/purpose
12 | * @body moment:string, purpose:boolean
13 | * @error
14 | * 1. moment 또는 purpose가 전달되지 않음
15 | * 2. 권한이 없는 사용자로부터의 요청
16 | */
17 |
18 | module.exports = async (req, res) => {
19 | const { moment, purpose } = req.body;
20 | const { roomId } = req.params;
21 | const user = req.user;
22 | const userId = user.userId;
23 |
24 | // error 1. moment 또는 purpose가 전달되지 않음
25 | if (!moment || !purpose) {
26 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
27 | }
28 |
29 | let client;
30 |
31 | try {
32 | client = await db.connect(req);
33 |
34 | let entry = await roomDB.getEntryByIds(client, roomId, userId);
35 |
36 | // error 2. 권한이 없는 사용자로부터의 요청
37 | if (!entry) {
38 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.PRIV_NOT_FOUND));
39 | }
40 |
41 | const entryId = entry.entryId;
42 | entry = await roomDB.updatePurposeByEntryId(client, entryId, moment, purpose);
43 |
44 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.PURPOSE_SET_SUCCESS));
45 | } catch (error) {
46 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
47 | console.log(error);
48 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
49 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
50 |
51 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
52 | } finally {
53 | client.release();
54 | }
55 | };
56 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomReadPATCH.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { roomDB, dialogDB } = require('../../../db');
8 |
9 | /**
10 | * @나의_목표_설정하기
11 | * @route PATCH /room/:roomId/read
12 | * @error
13 | * 1. 잘못된 roomId
14 | * 2. 권한이 없는 사용자로부터의 요청
15 | */
16 |
17 | module.exports = async (req, res) => {
18 | const { roomId } = req.params;
19 | const user = req.user;
20 | const userId = user.userId;
21 | let client;
22 |
23 | try {
24 | client = await db.connect(req);
25 |
26 | const dialog = await dialogDB.getUnReadDialogByRoomAndUser(client, roomId, userId);
27 | if (!dialog) {
28 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_INVALID));
29 | }
30 | let entry = await roomDB.getEntryByIds(client, roomId, userId);
31 |
32 | // error 2. 권한이 없는 사용자로부터의 요청
33 | if (!entry) {
34 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.PRIV_NOT_FOUND));
35 | }
36 |
37 | await dialogDB.setDialogRead(client, dialog.dialogId);
38 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.DIALOG_READ_SUCCESS));
39 | } catch (error) {
40 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
41 | console.log(error);
42 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
43 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
44 |
45 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
46 | } finally {
47 | client.release();
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomRecordPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const alarmMessage = require('../../../constants/alarmMessage');
5 | const responseMessage = require('../../../constants/responseMessage');
6 | const db = require('../../../db/db');
7 | const pushAlarm = require('../../../lib/pushAlarm');
8 | const slackAPI = require('../../../middlewares/slackAPI');
9 | const { roomDB, recordDB, noticeDB } = require('../../../db');
10 |
11 | /**
12 | * @습관인증하기
13 | * @route POST /room/:roomId/record
14 | * @body img: file, timerRecord: string
15 | * @error
16 | * 1. certiftyingImg가 전달되지 않음
17 | * 2. 해당 roomId의 습관방이 존재하지 않음
18 | * 3. 현재 진행중인 습관방이 아님
19 | * 4. 해당 습관방의 member가 아님
20 | * 5. 이미 인증 완료하거나 쉴래요 한 member
21 | */
22 |
23 | module.exports = async (req, res) => {
24 | const { roomId } = req.params;
25 | const user = req.user;
26 | const certifyingImg = req.imageUrls;
27 | let { timerRecord } = req.body;
28 | if (!timerRecord) {
29 | timerRecord = null;
30 | }
31 | // @error 1. certifyingImg가 전달되지 않음
32 | if (!certifyingImg) {
33 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
34 | }
35 |
36 | let client;
37 |
38 | try {
39 | client = await db.connect(req);
40 |
41 | const room = await roomDB.getRoomById(client, roomId);
42 | // @error 2. 해당 roomId의 습관방이 존재하지 않음
43 | if (!room) {
44 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_INVALID));
45 | }
46 | const entry = await roomDB.getEntryByIds(client, roomId, user.userId);
47 |
48 | // @error 3. 현재 진행중인 습관방이 아님
49 | if (room.status !== 'ONGOING') {
50 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NOT_ONGOING_ROOM));
51 | }
52 |
53 | // @error 4. 해당 습관방의 멤버가 아님
54 | if (!entry) {
55 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NOT_MEMBER));
56 | }
57 |
58 | const record = await recordDB.getRecentRecordByEntryId(client, entry.entryId);
59 |
60 | // @error 5. 이미 인증 완료하거나 쉴래요 한 MEMBER
61 | if (record.status === 'DONE' || record.status === 'REST') {
62 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.DONE_OR_REST_MEMBER));
63 | }
64 |
65 | // 인증한 record update
66 | await recordDB.uploadRecord(client, record.recordId, certifyingImg[0], timerRecord);
67 |
68 | if (!entry.thumbnail) {
69 | await roomDB.updateThumbnail(client, entry.entryId, certifyingImg[0]);
70 | // @TODO: 첫번째 습관 인증 축하 알림
71 | }
72 |
73 | // 인증을 완료하면 본인을 제외한 참여자들에게 알림 및 푸시알림 보내기
74 | const friends = await roomDB.getFriendsByIds(client, roomId, user.userId);
75 | const receiverTokens = friends.filter((f) => f.pushCertification).map((f) => f.deviceToken);
76 | const { title, body, isService, category } = alarmMessage.CERTIFICATION_COMPLETE(user.nickname, room.roomName);
77 |
78 | const notifications = friends.map((f) => {
79 | return `('${title}', '${body}', '${certifyingImg[0]}', ${f.userId}, ${isService}, false, ${room.roomId})`;
80 | });
81 |
82 | if (notifications.length) {
83 | await noticeDB.addNotifications(client, notifications);
84 | }
85 |
86 | if (receiverTokens.length) {
87 | pushAlarm.sendMulticastByTokens(req, res, title, body, receiverTokens, category, certifyingImg[0], roomId, record.recordId);
88 | }
89 |
90 | const data = {
91 | userId: user.userId,
92 | nickname: user.nickname,
93 | profileImg: user.profileImg,
94 | roomId: room.roomId,
95 | roomName: room.roomName,
96 | recordId: record.recordId,
97 | day: record.day,
98 | certifyingImg: record.certifyingImg,
99 | timerRecord: record.timerRecord,
100 | };
101 |
102 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.CERTIFY_SUCCESS, data));
103 | } catch (error) {
104 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
105 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
106 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
107 |
108 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
109 | } finally {
110 | client.release();
111 | }
112 | };
113 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomStartPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const alarmMessage = require('../../../constants/alarmMessage');
5 | const responseMessage = require('../../../constants/responseMessage');
6 | const db = require('../../../db/db');
7 | const pushAlarm = require('../../../lib/pushAlarm');
8 | const dayjs = require('dayjs');
9 | const slackAPI = require('../../../middlewares/slackAPI');
10 | const { roomDB, recordDB, noticeDB, lifeTimelineDB } = require('../../../db');
11 |
12 | /**
13 | * @습관방_시작
14 | * @route POST /room/:roomId/start
15 | * @body
16 | * @error
17 | * 1. roomId가 숫자가 아님
18 | * 2. 유효하지 않은 roomId
19 | * 3. 권한이 없는 사용자로부터의 요청
20 | * 4. 이미 시작된 방
21 | */
22 |
23 | module.exports = async (req, res) => {
24 | const { roomId } = req.params;
25 | const user = req.user;
26 | const userId = user.userId;
27 |
28 | let client;
29 |
30 | try {
31 | client = await db.connect(req);
32 |
33 | // @error 1. roomId가 숫자가 아님
34 | if (isNaN(roomId)) {
35 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_NOT_FOUND));
36 | }
37 |
38 | let room = await roomDB.getRoomById(client, roomId);
39 |
40 | // @error 2. 유효하지 않은 roomId
41 | if (!room) {
42 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_NOT_FOUND));
43 | }
44 |
45 | // @error 3. 권한이 없는 사용자로부터의 요청
46 | if (userId !== room.creator) {
47 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.PRIV_NOT_FOUND));
48 | }
49 |
50 | // 습관방 status ONGOING으로 변경
51 | room = await roomDB.startRoomById(client, roomId);
52 |
53 | // @error 4. 이미 시작된 방
54 | if (!room) {
55 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.START_ROOM_ALREADY));
56 | }
57 |
58 | const entries = await roomDB.getEntriesByRoomIds(client, [roomId]);
59 |
60 | let insertRecords = [];
61 | let insertTimelines = [];
62 | for (let i = 0; i < entries.length; i++) {
63 | const entry = entries[i];
64 | // 추가해줄 record들의 속성들 빚어주기
65 | const startDate = dayjs(entry.startAt);
66 | const now = dayjs().add(9, 'hour');
67 | const today = now.format('YYYY-MM-DD');
68 | const day = dayjs(today).diff(startDate, 'day') + 1;
69 | const record = '(' + entry.entryId + ",'" + now.format('YYYY-MM-DD') + "'," + day + ')';
70 |
71 | insertRecords.push(record);
72 | insertTimelines.push(`('${entry.userId}', '${entry.roomId}', false, 1)`);
73 | }
74 |
75 | if (insertRecords.length > 0) {
76 | // 참여자들의 1일차 record 생성
77 | await recordDB.insertRecords(client, insertRecords);
78 | }
79 |
80 | if (insertTimelines.length > 0) {
81 | // 참여자들의 1일차 Life Timeline 생성
82 | await lifeTimelineDB.addFillTimelines(client, insertTimelines);
83 | }
84 |
85 | const { title, body, isService, category } = alarmMessage.ROOM_NEW(room.roomName);
86 |
87 | const allUsers = await roomDB.getAllUsersById(client, roomId);
88 |
89 | // 푸시알림 전송
90 | const receiverTokens = allUsers.filter((u) => u.pushRoomStart).map((u) => u.deviceToken);
91 | pushAlarm.sendMulticastByTokens(req, res, title, body, receiverTokens, category, null, roomId);
92 |
93 | // notification 생성
94 | const notifications = allUsers.map((u) => {
95 | return `('${title}', '${body}', '', ${u.userId}, ${isService}, false, ${room.roomId})`;
96 | });
97 |
98 | if (notifications.length) {
99 | await noticeDB.addNotifications(client, notifications);
100 | }
101 |
102 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.START_ROOM_SUCCESS));
103 | } catch (error) {
104 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
105 | console.log(error);
106 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
107 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
108 |
109 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
110 | } finally {
111 | client.release();
112 | }
113 | };
114 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomStatusPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const alarmMessage = require('../../../constants/alarmMessage');
5 | const responseMessage = require('../../../constants/responseMessage');
6 | const db = require('../../../db/db');
7 | const pushAlarm = require('../../../lib/pushAlarm');
8 | const slackAPI = require('../../../middlewares/slackAPI');
9 | const { userDB, roomDB, recordDB, noticeDB } = require('../../../db');
10 |
11 | /**
12 | * @쉴래요_고민중
13 | * @route POST /room/:roomId/status
14 | * @body
15 | * @error
16 | * 1. 유효하지 않은 statusType
17 | * 2. 유효하지 않은 roomId
18 | * 3. 권한이 없는 사용자로부터의 요청
19 | * 4. 습관 시작하지 않은 방에서의 요청
20 | * 5. 이미 인증을 완료한 사용자로부터의 요청
21 | * 6. 이미 쉴래요를 사용한 사용자로부터의 요청
22 | * 7. 쉴래요 잔여횟수가 0인 사용쟈로부터의 요청
23 | * 8. 이미 고민중 상태의 사용자로부터의 요청
24 | */
25 |
26 | module.exports = async (req, res) => {
27 | const { statusType } = req.body;
28 | const { roomId } = req.params;
29 | const user = req.user;
30 | const userId = user.userId;
31 |
32 | let client;
33 |
34 | try {
35 | client = await db.connect(req);
36 |
37 | const room = await roomDB.getRoomById(client, roomId);
38 |
39 | // @error 1. 유효하지 않은 statusType
40 | if (statusType !== 'REST' && statusType !== 'CONSIDER') {
41 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.INVALID_USER_STATUS));
42 | }
43 |
44 | // @error 2. 유효하지 않은 roomId
45 | if (!room) {
46 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_NOT_FOUND));
47 | }
48 |
49 | const entry = await roomDB.getEntryByIds(client, roomId, userId);
50 |
51 | // @error 3. 권한이 없는 사용자로부터의 요청
52 | if (!entry) {
53 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.PRIV_NOT_FOUND));
54 | }
55 |
56 | const recentRecord = await recordDB.getRecentRecordByEntryId(client, entry.entryId);
57 |
58 | // @error 4. 습관 시작하지 않은 방에서의 요청
59 | if (!recentRecord) {
60 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NOT_STARTED_ROOM));
61 | }
62 |
63 | // @error 5. 이미 인증을 완료한 사용자로부터의 요청
64 | if (recentRecord.status === 'DONE') {
65 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.CERTIFICATION_ALREADY_DONE));
66 | }
67 |
68 | // @error 6. 이미 인증을 완료한 사용자로부터의 요청
69 | if (recentRecord.status === 'REST') {
70 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.REST_ALREADY_DONE));
71 | }
72 |
73 | if (statusType === 'REST') {
74 | // @error 7. 쉴래요 잔여횟수가 0인 사용쟈로부터의 요청
75 | const rawRest = await roomDB.getRestCountByIds(client, roomId, userId);
76 | const restCount = rawRest.rest;
77 | if (restCount < 1) {
78 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.REST_COUNT_ZERO));
79 | }
80 |
81 | const newRestCount = restCount - 1;
82 | await roomDB.updateRestByIds(client, roomId, userId, newRestCount);
83 | }
84 |
85 | await recordDB.updateStatusByRecordId(client, recentRecord.recordId, statusType);
86 |
87 | // 고민중을 눌렀으면 Notification에 추가, PushAlarm 전송
88 | if (statusType === 'CONSIDER') {
89 | // @error 8. 이미 고민중 상태의 사용자로부터의 요청
90 | if (recentRecord.status === 'CONSIDER') {
91 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.CONSIDER_ALREADY_DONE));
92 | }
93 | const sender = await userDB.getUserById(client, userId);
94 | const friends = await roomDB.getFriendsByIds(client, roomId, userId);
95 | const receiverTokens = friends.filter((f) => f.pushConsider).map((f) => f.deviceToken);
96 | const { title, body, isService, category } = alarmMessage.STATUS_CONSIDERING(sender.nickname, room.roomName);
97 |
98 | const notifications = friends.map((f) => {
99 | return `('${title}', '${body}', '${user.profileImg}', ${f.userId}, ${isService}, true, ${room.roomId})`;
100 | });
101 |
102 | if (notifications.length) {
103 | await noticeDB.addNotifications(client, notifications);
104 | }
105 |
106 | if (receiverTokens.length > 0) {
107 | pushAlarm.sendMulticastByTokens(req, res, title, body, receiverTokens, category, null, roomId);
108 | }
109 | }
110 |
111 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_STATUS_SUCCESS));
112 | } catch (error) {
113 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
114 | console.log(error);
115 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
116 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
117 |
118 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
119 | } finally {
120 | client.release();
121 | }
122 | };
123 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomTimelineGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { roomDB, lifeTimelineDB } = require('../../../db');
7 | const timelineMessage = require('../../../constants/lifeTimelineMessage');
8 | const slackAPI = require('../../../middlewares/slackAPI');
9 | const dayjs = require('dayjs');
10 | const { passedDayToStr } = require('../../../lib/passedDayToStr');
11 |
12 | /**
13 | * @습관방_생명_타임라인_조회
14 | * @route GET /room/:roomId/timeline
15 | * @error
16 | * 1. 존재하지 않는 습관방인 경우
17 | * 2. 진행중인 습관방이 아닌 경우
18 | * 3. 접근 권한이 없는 유저인 경우
19 | */
20 |
21 | module.exports = async (req, res) => {
22 | const { roomId } = req.params;
23 | const user = req.user;
24 |
25 | let client;
26 |
27 | try {
28 | client = await db.connect(req);
29 |
30 | const room = await roomDB.getRoomById(client, roomId);
31 |
32 | // @error 1. 존재하지 않는 습관방인 경우
33 | if (!room) {
34 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.NO_CONTENT, responseMessage.GET_ROOM_DATA_FAIL));
35 | }
36 |
37 | const endDate = dayjs(room.endAt);
38 | const now = dayjs().add(9, 'hour');
39 | const today = dayjs(now.format('YYYY-MM-DD'));
40 | const leftDay = endDate.diff(today, 'day');
41 |
42 | // @error 2. 진행중인 습관방이 아닌 경우
43 | if (room.status !== 'ONGOING' || leftDay < 0) {
44 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NOT_ONGOING_ROOM));
45 | }
46 |
47 | const entries = await roomDB.getEntriesByRoomId(client, roomId);
48 | const userEntry = entries.filter((entry) => entry.userId === user.userId);
49 | // @error 3. 접근 권한이 없는 유저인 경우
50 | if (!userEntry.length) {
51 | return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.NOT_MEMBER));
52 | }
53 |
54 | let timelines = await lifeTimelineDB.getLifeTimeline(client, roomId, user.userId);
55 |
56 | timelines = timelines.map((t) => {
57 | if (t.isDecrease) {
58 | const timeline = {};
59 | const { title, content } = timelineMessage.LIFE_DECREASE(t.decreaseCount);
60 |
61 | let createdAt = dayjs(t.createdAt);
62 | const passedDay = now.diff(createdAt, 'd');
63 | const profiles = [t.profile_1, t.profile_2].filter((profile) => profile !== 'null');
64 |
65 | timeline['title'] = title;
66 | timeline['content'] = content;
67 | timeline['profiles'] = profiles;
68 | timeline['day'] = passedDayToStr(passedDay);
69 | timeline['isNew'] = !t.isRead;
70 |
71 | return timeline;
72 | } else {
73 | const timeline = {};
74 | const { title, content } = timelineMessage.LIFE_FILL(t.termDay);
75 |
76 | let createdAt = dayjs(t.createdAt);
77 | const passedDay = now.diff(createdAt, 'd');
78 |
79 | timeline['title'] = title;
80 | timeline['content'] = content;
81 | timeline['day'] = passedDayToStr(passedDay);
82 | timeline['isNew'] = !t.isRead;
83 |
84 | return timeline;
85 | }
86 | });
87 |
88 | await lifeTimelineDB.readLifeTimeline(client, roomId, user.userId);
89 |
90 | return res.status(statusCode.OK).send(
91 | util.success(statusCode.OK, responseMessage.GET_LIFE_TIMELINE_SUCCESS, {
92 | timelines,
93 | }),
94 | );
95 | } catch (error) {
96 | console.log(error);
97 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
98 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
99 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
100 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
101 | } finally {
102 | client.release();
103 | }
104 | };
105 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomWaitingGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { userDB, roomDB } = require('../../../db');
8 |
9 | /**
10 | * @대기_방_조회
11 | * @route GET /room/:roomId/waiting
12 | * @error
13 | * 1. 유효하지 않은 roomId
14 | * 2. 권한이 없는 사용자로부터의 요청
15 | */
16 |
17 | module.exports = async (req, res) => {
18 | const { roomId } = req.params;
19 | const user = req.user;
20 | console.log(user);
21 | const userId = user.userId;
22 |
23 | let client;
24 |
25 | try {
26 | client = await db.connect(req);
27 |
28 | const room = await roomDB.getRoomById(client, roomId);
29 |
30 | // @error 1. 유효하지 않은 roomId
31 | if (!room) {
32 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_NOT_FOUND));
33 | }
34 |
35 | const selfEntry = await roomDB.getEntryByIds(client, roomId, userId);
36 |
37 | // error 2. 권한이 없는 사용자로부터의 요청
38 | if (!selfEntry) {
39 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.PRIV_NOT_FOUND));
40 | }
41 |
42 | // 요청을 보낸 사용자 본인 data
43 | const reqUser = {
44 | userId,
45 | nickname: user.nickname,
46 | profileImg: user.profileImg,
47 | isPurposeSet: selfEntry.moment !== null && selfEntry.purpose !== null,
48 | moment: selfEntry.moment,
49 | purpose: selfEntry.purpose,
50 | isHost: room.creator === userId,
51 | };
52 |
53 | // 요청을 보낸 사용자를 제외한 member list
54 | const friendsEntries = await roomDB.getFriendsByIds(client, roomId, userId);
55 | const friendsIds = friendsEntries.map((f) => f.userId);
56 |
57 | let members = [];
58 |
59 | // 대기방에 참여중인 친구가 없는 경우
60 | if (friendsIds.length !== 0) {
61 | const users = await userDB.getUsersByIds(client, friendsIds);
62 | users.map((u) =>
63 | members.push({
64 | userId: u.userId,
65 | nickname: u.nickname,
66 | profileImg: u.profileImg,
67 | }),
68 | );
69 | }
70 |
71 | const data = {
72 | roomId: parseInt(roomId),
73 | roomName: room.roomName,
74 | roomCode: room.code,
75 | fromStart: room.fromStart,
76 | reqUser,
77 | members,
78 | };
79 |
80 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_WAITROOM_DATA_SUCCESS, data));
81 | } catch (error) {
82 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
83 | console.log(error);
84 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
85 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
86 |
87 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
88 | } finally {
89 | client.release();
90 | }
91 | };
92 |
--------------------------------------------------------------------------------
/functions/api/routes/room/roomWaitingMemberGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { userDB, roomDB } = require('../../../db');
8 |
9 | /**
10 | * @대기_방_인원_조회
11 | * @route GET /room/:roomId/waiting/member
12 | * @error
13 | * 1. 유효하지 않은 roomId
14 | * 2. 권한이 없는 사용자로부터의 요청
15 | */
16 |
17 | module.exports = async (req, res) => {
18 | const { roomId } = req.params;
19 | const user = req.user;
20 | const userId = user.userId;
21 |
22 | let client;
23 |
24 | try {
25 | client = await db.connect(req);
26 |
27 | const room = await roomDB.getRoomById(client, roomId);
28 |
29 | // @error 1. 유효하지 않은 roomId
30 | if (!room) {
31 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_NOT_FOUND));
32 | }
33 |
34 | const selfEntry = await roomDB.getEntryByIds(client, roomId, userId);
35 |
36 | // error 2. 권한이 없는 사용자로부터의 요청
37 | if (!selfEntry) {
38 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.PRIV_NOT_FOUND));
39 | }
40 |
41 | // 요청을 보낸 사용자를 제외한 member list
42 | const friendsEntries = await roomDB.getFriendsByIds(client, roomId, userId);
43 | if (!friendsEntries.length) {
44 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_WAITROOM_DATA_SUCCESS, { members: [] }));
45 | }
46 | const friendsIds = friendsEntries.map((f) => f.userId);
47 | const users = await userDB.getUsersByIds(client, friendsIds);
48 | let members = [];
49 |
50 | users.map((u) =>
51 | members.push({
52 | userId: u.userId,
53 | nickname: u.nickname,
54 | profileImg: u.profileImg,
55 | }),
56 | );
57 |
58 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_WAITROOM_DATA_SUCCESS, { members }));
59 | } catch (error) {
60 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
61 | console.log(error);
62 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
63 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
64 |
65 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
66 | } finally {
67 | client.release();
68 | }
69 | };
70 |
--------------------------------------------------------------------------------
/functions/api/routes/room/sparkPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const alarmMessage = require('../../../constants/alarmMessage');
5 | const responseMessage = require('../../../constants/responseMessage');
6 | const db = require('../../../db/db');
7 | const pushAlarm = require('../../../lib/pushAlarm');
8 | const slackAPI = require('../../../middlewares/slackAPI');
9 | const { userDB, roomDB, sparkDB, noticeDB } = require('../../../db');
10 |
11 | /**
12 | * @스파크_보내기
13 | * @route POST /room/:roomId/spark
14 | * @body content: string
15 | * @error
16 | * 1. content/ roomId가 전달되지 않음
17 | * 2. 존재하지 않는 습관방
18 | * 3. 유저가 해당 습관방에 참여하지 않는 경우
19 | * 4. 쉴래요 습관완료한 사람한테 스파크 보내려함
20 | * 5. 자신에게 스파크를 보내려 할 때
21 | * 6. 해당 습관방의 record가 아닐 때
22 | */
23 |
24 | module.exports = async (req, res) => {
25 | const { recordId, content } = req.body;
26 | const { roomId } = req.params;
27 | const user = req.user;
28 | const userId = user.userId;
29 |
30 | // @error 1. content/ roomId가 전달되지 않음
31 | if (!roomId || !content) {
32 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
33 | }
34 |
35 | let client;
36 |
37 | try {
38 | client = await db.connect(req);
39 |
40 | const room = await roomDB.getRoomById(client, roomId);
41 | // @error 2. 존재하지 않는 습관방
42 | if (!room) {
43 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ROOM_ID_INVALID));
44 | }
45 |
46 | const entry = await roomDB.getEntryByIds(client, roomId, user.userId);
47 |
48 | // @error 3. 유저가 해당 습관방에 참여하지 않는 경우
49 | if (!entry) {
50 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NOT_MEMBER));
51 | }
52 |
53 | // @error 6. 해당 습관방의 record가 아닐 때
54 | const record = await roomDB.getRecordById(client, recordId);
55 |
56 | if (record.roomId !== Number(roomId)) {
57 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NOT_MATCH_ROOM_AND_RECORD));
58 | }
59 |
60 | // @error 4. 쉴래요 습관완료한 사람한테 스파크 보내려함
61 | // if (record.status === 'DONE' || record.status === 'REST') {
62 | // return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.DONE_OR_REST_MEMBER));
63 | // }
64 |
65 | // @error 5. 자신에게 스파크를 보내려 할 때
66 | if (record.userId === userId) {
67 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.CANNOT_SEND_SPARK_SELF));
68 | }
69 |
70 | const spark = await sparkDB.insertSpark(client, recordId, userId, content);
71 |
72 | // 스파크를 보내면 받는 사람에게 알림 및 푸시알림 보내기
73 | const { title, body, isService, category } = alarmMessage.SEND_SPARK(user.nickname, room.roomName, content);
74 | const receiver = await userDB.getUserById(client, record.userId);
75 | await noticeDB.addNotification(client, title, body, user.profileImg, receiver.userId, isService, true, room.roomId);
76 |
77 | // 푸시알림 허용한 사용자일 경우
78 | if (receiver.pushSpark) {
79 | pushAlarm.send(req, res, title, body, receiver.deviceToken, category, null, roomId);
80 | }
81 |
82 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.SEND_SPARK_SUCCESS));
83 | } catch (error) {
84 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
85 | console.log(error);
86 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
87 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
88 |
89 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
90 | } finally {
91 | client.release();
92 | }
93 | };
94 |
--------------------------------------------------------------------------------
/functions/api/routes/scheduling/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | router.post('/inspection', require('./inspectPOST'));
5 | router.post('/remind', require('./remindPOST'));
6 |
7 | module.exports = router;
8 |
--------------------------------------------------------------------------------
/functions/api/routes/scheduling/inspectPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const slackAPI = require('../../../middlewares/slackAPI');
6 | const { checkLife } = require('../../../scheduler/funcs');
7 |
8 | /**
9 | * @인증체크_및_생명감소
10 | * @route POST /scheduling/inspection
11 | * @error
12 | */
13 |
14 | module.exports = async (req, res) => {
15 | try {
16 | const timeLog = `[TIME STAMP] 인증 체크 시작: ${new Date()}`;
17 | console.log(timeLog);
18 | slackAPI.sendMessageToSlack(timeLog, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
19 |
20 | // 인증체크 및 생명감소
21 | await checkLife();
22 |
23 | const slackMessage = `[🦋CERTIFICATION INSPECTION SUCCESS!🦋] [${req.method.toUpperCase()}]`;
24 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
25 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.CERTIFICATION_INSPECTION_SUCCESS));
26 | } catch (error) {
27 | console.log(error);
28 | functions.logger.error(`[🚨CERTIFICATION INSPECTION ERROR🚨] [${req.method.toUpperCase()}]`);
29 | const slackMessage = `[🚨CERTIFICATION INSPECTION ERROR🚨] [${req.method.toUpperCase()}]`;
30 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
31 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
32 | } finally {
33 | const timeLog = `[TIME STAMP] 인증 체크 종료: ${new Date()}`;
34 | console.log(timeLog);
35 | slackAPI.sendMessageToSlack(timeLog, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/functions/api/routes/scheduling/remindPOST.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const slackAPI = require('../../../middlewares/slackAPI');
6 | const { sendRemind } = require('../../../scheduler/funcs');
7 |
8 | /**
9 | * @리마인드_푸시알림_전송
10 | * @route POST /scheduling/remind
11 | * @error
12 | */
13 |
14 | module.exports = async (req, res) => {
15 | try {
16 | const timeLog = `[TIME STAMP] 리마인드 알림 시작: ${new Date()}`;
17 | console.log(timeLog);
18 | slackAPI.sendMessageToSlack(timeLog, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
19 |
20 | // 리마인드 푸시알림 전송
21 | sendRemind();
22 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.SEND_REMIND_SUCCESS));
23 | } catch (error) {
24 | console.log(error);
25 | functions.logger.error(`[SEND REMIND ERROR] [${req.method.toUpperCase()}]`);
26 | const slackMessage = `[SEND REMIND ERROR] [${req.method.toUpperCase()}]`;
27 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
28 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
29 | } finally {
30 | const timeLog = `[TIME STAMP] 리마인드 알림 종료: ${new Date()}`;
31 | console.log(timeLog);
32 | slackAPI.sendMessageToSlack(timeLog, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
33 | }
34 | };
35 |
--------------------------------------------------------------------------------
/functions/api/routes/user/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | const { checkUser } = require('../../../middlewares/auth');
4 | const uploadImageIntoSubDir = require('../../../middlewares/uploadImage');
5 |
6 | router.get('/profile', checkUser, require('./userProfileGET'));
7 | router.patch('/profile', checkUser, uploadImageIntoSubDir('users'), require('./userProfilePATCH'));
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/functions/api/routes/user/userProfileGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { userDB } = require('../../../db');
8 |
9 | /**
10 | * @프로필_조회
11 | * @route GET /user/profile
12 | * @error
13 | */
14 |
15 | module.exports = async (req, res) => {
16 | const user = req.user;
17 | const userId = user.userId;
18 |
19 | let client;
20 |
21 | try {
22 | client = await db.connect(req);
23 |
24 | const { nickname, profileImg } = await userDB.getUserById(client, userId);
25 |
26 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_USER_PROFILE_SUCCESS, { nickname, profileImg }));
27 | } catch (error) {
28 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
29 | console.log(error);
30 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
31 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
32 |
33 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
34 | } finally {
35 | client.release();
36 | }
37 | };
38 |
--------------------------------------------------------------------------------
/functions/api/routes/user/userProfilePATCH.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const slackAPI = require('../../../middlewares/slackAPI');
7 | const { userDB } = require('../../../db');
8 | const { DEFAULT_PROFILE_IMG_URL } = require('../../../constants/defaultProfileImg');
9 |
10 | /**
11 | * @프로필_변경
12 | * @route PATCH /user/profile
13 | * @error
14 | * 1. nickname이 전달되지 않음
15 | *
16 | */
17 |
18 | module.exports = async (req, res) => {
19 | const user = req.user;
20 | const userId = user.userId;
21 | let profileImg = req.imageUrls;
22 | const { nickname } = req.body;
23 |
24 | // @error 1. 닉네임이 전달되지 않았을 경우
25 | if (!nickname) {
26 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
27 | }
28 |
29 | // 프로필 이미지가 없으면, 기본 이미지로 설정
30 | if (profileImg.length === 0) {
31 | profileImg.push(DEFAULT_PROFILE_IMG_URL);
32 | }
33 |
34 | let client;
35 |
36 | try {
37 | client = await db.connect(req);
38 |
39 | await userDB.updateProfileById(client, userId, nickname, profileImg[0]);
40 |
41 | res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.PATCH_USER_PROFILE_SUCCESS));
42 | } catch (error) {
43 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
44 | console.log(error);
45 | const slackMessage = `[ERROR BY ${user.nickname} (${user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
46 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
47 |
48 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
49 | } finally {
50 | client.release();
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/functions/api/routes/version/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 |
4 | router.get('', require('./versionGET'));
5 | router.patch('', require('./versionPATCH'));
6 |
7 | module.exports = router;
8 |
--------------------------------------------------------------------------------
/functions/api/routes/version/versionGET.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { versionDB } = require('../../../db');
7 |
8 | /**
9 | * @릴리즈_버전정보_조회
10 | * @route GET /version
11 | * @error
12 | */
13 |
14 | module.exports = async (req, res) => {
15 | let client;
16 |
17 | try {
18 | client = await db.connect(req);
19 |
20 | const { version } = await versionDB.getRecentVersion(client);
21 |
22 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.GET_RECENT_VERSION_SUCCESS, { version }));
23 | } catch (error) {
24 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
25 |
26 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
27 | } finally {
28 | client.release();
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/functions/api/routes/version/versionPATCH.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const util = require('../../../lib/util');
3 | const statusCode = require('../../../constants/statusCode');
4 | const responseMessage = require('../../../constants/responseMessage');
5 | const db = require('../../../db/db');
6 | const { versionDB } = require('../../../db');
7 |
8 | /**
9 | * @릴리즈_버전정보_갱신
10 | * @route PATCH /version
11 | * @error
12 | * 1. 신규 버전이 전달되지 않음
13 | */
14 |
15 | module.exports = async (req, res) => {
16 | const { newVersion } = req.body;
17 |
18 | // error 1. 신규 버전이 전달되지 않음
19 | if (!newVersion) {
20 | return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NULL_VALUE));
21 | }
22 |
23 | let client;
24 |
25 | try {
26 | client = await db.connect(req);
27 |
28 | await versionDB.updateRecentVersion(client, String(newVersion));
29 |
30 | return res.status(statusCode.OK).send(util.success(statusCode.OK, responseMessage.UPDATE_RECENT_VERSION_SUCCESS));
31 | } catch (error) {
32 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
33 |
34 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
35 | } finally {
36 | client.release();
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/functions/config/dbConfig.js:
--------------------------------------------------------------------------------
1 | const dotenv = require('dotenv');
2 | dotenv.config();
3 |
4 | let user = process.env.DEV_DB_USER;
5 | let host = process.env.DEV_DB_HOST;
6 | let database = process.env.DEV_DB_DB;
7 | let password = process.env.DEV_DB_PASSWORD;
8 |
9 | if (process.env.NODE_ENV === 'production') {
10 | user = process.env.DB_USER;
11 | host = process.env.DB_HOST;
12 | database = process.env.DB_DB;
13 | password = process.env.DB_PASSWORD;
14 | }
15 |
16 | module.exports = {
17 | user,
18 | host,
19 | database,
20 | password,
21 | };
22 |
--------------------------------------------------------------------------------
/functions/config/firebaseClient.js:
--------------------------------------------------------------------------------
1 | const { initializeApp } = require('firebase/app');
2 | const { getAuth } = require('firebase/auth');
3 |
4 | const firebaseConfig = {
5 | apiKey: 'AIzaSyA1TT7TFz5wJvQJS0nblrEXCag79rzyM0Y',
6 | authDomain: 'we-sopt-spark.firebaseapp.com',
7 | projectId: 'we-sopt-spark',
8 | storageBucket: 'we-sopt-spark.appspot.com',
9 | messagingSenderId: '849294133156',
10 | appId: '1:849294133156:web:dc6dc49c19dfe0e6b02331',
11 | measurementId: 'G-PWLFVZQH06',
12 | };
13 |
14 | const firebaseApp = initializeApp(firebaseConfig);
15 | const firebaseAuth = getAuth(firebaseApp);
16 |
17 | module.exports = { firebaseApp, firebaseAuth, firebaseConfig };
18 |
--------------------------------------------------------------------------------
/functions/constants/alarmMessage.js:
--------------------------------------------------------------------------------
1 | const CERTIFICATION_COMPLETE = (who, roomName) => {
2 | const title = `${who}님의 인증 완료!`;
3 | const body = `${roomName}방 인증을 완료했어요.`;
4 | const isService = false;
5 | const category = 'certification';
6 |
7 | return { title, body, isService, category };
8 | };
9 |
10 | const STATUS_CONSIDERING = (who, roomName) => {
11 | const title = `${who}님 고민중..💭`;
12 | const body = `${roomName}, 오늘 좀 힘든걸? 스파크 plz`;
13 | const isService = false;
14 | const category = 'consider';
15 |
16 | return { title, body, isService, category };
17 | };
18 |
19 | const ROOM_OUT = (who, roomName) => {
20 | const title = `${roomName}방 인원 변동 🚨`;
21 | const body = `${who}님이 습관방에서 나갔어요.`;
22 | const isService = false;
23 |
24 | return { title, body, isService };
25 | };
26 |
27 | const SEND_SPARK = (who, roomName, content) => {
28 | const title = `${roomName}방에서 보낸 스파크`;
29 | const isService = false;
30 | const category = 'spark';
31 | const body = `${who} : ${content}`;
32 |
33 | return { title, body, isService, category };
34 | };
35 |
36 | const ROOM_NEW = (roomName) => {
37 | const title = `새로운 습관 시작 🔥`;
38 | const body = `${roomName}방에서 가장 먼저 스파크를 보내볼까요?`;
39 | const isService = true;
40 | const category = 'roomStart';
41 |
42 | return { title, body, isService, category };
43 | };
44 |
45 | const ROOM_DELETE = (roomName) => {
46 | const title = `${roomName} 대기방 삭제`;
47 | const body = `방 개설자에 의해 대기방이 삭제되었어요.`;
48 | const isService = true;
49 |
50 | return { title, body, isService };
51 | };
52 |
53 | const FEED_LIKE = (who, roomName) => {
54 | const title = `${who}님이 좋아한 피드`;
55 | const body = `${roomName}방 인증을 좋아해요.`;
56 | const isService = false;
57 |
58 | return { title, body, isService };
59 | };
60 |
61 | const REMIND_ALERT_NONE = (roomName) => {
62 | const title = `${roomName}방의 인증을 하지 않았어요!`;
63 | const body = `생명이 줄기 전에 서둘러 인증해주세요🏃♂️`;
64 | const isService = true;
65 | const category = 'remind';
66 |
67 | return { title, body, isService, category };
68 | };
69 |
70 | const REMIND_ALERT_DONE = (roomName) => {
71 | const title = `${roomName}방 미인증 스파커 발견!`;
72 | const body = `지금 스파크를 보내 친구를 응원해주세요🔥`;
73 | const isService = true;
74 | const category = 'remind';
75 |
76 | return { title, body, isService, category };
77 | };
78 |
79 | module.exports = {
80 | CERTIFICATION_COMPLETE,
81 | STATUS_CONSIDERING,
82 | ROOM_OUT,
83 | SEND_SPARK,
84 | ROOM_NEW,
85 | ROOM_DELETE,
86 | FEED_LIKE,
87 | REMIND_ALERT_NONE,
88 | REMIND_ALERT_DONE,
89 | };
90 |
--------------------------------------------------------------------------------
/functions/constants/day.js:
--------------------------------------------------------------------------------
1 | const numToString = ["일요일", "월요일", "화요일", "수요일", "목요일", "금요일", "토요일"]
2 |
3 | module.exports = {
4 | numToString
5 | };
--------------------------------------------------------------------------------
/functions/constants/defaultProfileImg.js:
--------------------------------------------------------------------------------
1 | const DEFAULT_PROFILE_IMG_URL = 'https://firebasestorage.googleapis.com/v0/b/we-sopt-spark.appspot.com/o/common%2Fprofile_empty.png?alt=media&token=194cf154-6a1b-4ffe-9e51-6f07b3c45490';
2 |
3 | module.exports = {
4 | DEFAULT_PROFILE_IMG_URL,
5 | };
6 |
--------------------------------------------------------------------------------
/functions/constants/jwt.js:
--------------------------------------------------------------------------------
1 | const TOKEN_EXPIRED = -3;
2 | const TOKEN_INVALID = -2;
3 |
4 | module.exports = {
5 | TOKEN_EXPIRED,
6 | TOKEN_INVALID,
7 | };
--------------------------------------------------------------------------------
/functions/constants/lifeTimelineMessage.js:
--------------------------------------------------------------------------------
1 | const LIFE_DECREASE = (count) => {
2 | const title = `생명 ${count}개 감소💧`;
3 | const content = `인증하지 않은 스파커가 있었네요. 응원이 더 필요해요!`;
4 | return { title, content };
5 | };
6 |
7 | const LIFE_FILL = (termDay) => {
8 | const title = `생명 충전 완료🔋`;
9 | let content = '';
10 |
11 | if (termDay === 1) {
12 | content = '66일의 도전을 시작했네요. 인증하고 생명을 지켜요!';
13 | } else if (termDay === 4) {
14 | content = '3일 달성 선물로 생명이 충전됐어요. 잘하고 있어요!';
15 | } else if (termDay === 8) {
16 | content = '7일 달성 선물로 생명이 충전됐어요. 더 힘내봐요!';
17 | } else if (termDay === 34) {
18 | content = '33일 달성 선물로 생명이 충전됐어요. 할 수 있어요!';
19 | } else if (termDay === 60) {
20 | content = '마지막 7일 선물로 생명이 충전됐어요. 끝까지 힘내요!';
21 | }
22 |
23 | return { title, content };
24 | };
25 |
26 | module.exports = {
27 | LIFE_DECREASE,
28 | LIFE_FILL,
29 | };
30 |
--------------------------------------------------------------------------------
/functions/constants/responseMessage.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | NULL_VALUE: '필요한 값이 없습니다',
3 | OUT_OF_VALUE: '파라미터 값이 잘못되었습니다',
4 | PATH_ERROR: '요청 경로가 올바르지 않습니다',
5 | INTERNAL_SERVER_ERROR: '서버 내부 오류',
6 | PRIV_NOT_FOUND: '권한이 없는 요청입니다',
7 | INVALID_LASTID: '잘못된 lastId 입니다',
8 |
9 | // 회원가입
10 | CREATED_USER: '회원 가입 성공',
11 | DELETE_USER: '회원 탈퇴 성공',
12 | ALREADY_SOCIALID: '이미 사용중인 소셜 아이디입니다.',
13 | TOO_LONG_NICKNAME: '닉네임은 10자를 초과할 수 없습니다',
14 | NOT_SIGNED_UP: '회원가입을 하지 않은 사용자입니다',
15 | ALREADY_SIGNED_UP: '회원 정보를 불러왔습니다',
16 |
17 | // 로그인
18 | LOGIN_SUCCESS: '로그인 성공',
19 | LOGIN_FAIL: '로그인 실패',
20 | NO_USER: '존재하지 않는 회원입니다.',
21 | MISS_MATCH_PW: '비밀번호가 맞지 않습니다.',
22 |
23 | // 회원 탈퇴
24 | ALREADY_DELETED_USER: '이미 탈퇴한 회원입니다.',
25 | DELETE_USER_SUCCESS: '회원 탈퇴 성공',
26 |
27 | // 로그아웃
28 | LOGOUT_SUCCESS: '로그아웃 성공',
29 |
30 | // 사용자 프로필
31 | GET_USER_PROFILE_SUCCESS: '프로필 조회 성공',
32 | PATCH_USER_PROFILE_SUCCESS: '프로필 변경 성공',
33 |
34 | // Room
35 | CREATE_ROOM_SUCCESS: '습관 방 생성 성공',
36 | CREATE_ROOM_FAIL: '습관 방 생성 실패',
37 | GET_WAITROOM_DATA_SUCCESS: '대기방 정보 확인 완료',
38 | GET_WAITROOM_DATA_IMPOSSIBLE: '참여할 수 없는 코드예요.',
39 | GET_WAITROOM_DATA_NULL: '참여할 수 없는 코드예요.',
40 | GET_WAITROOM_DATA_STARTED: '이미 시작된 습관방이에요.',
41 | GET_WAITROOM_DATA_ALREADY: '이미 참여중인 코드예요.',
42 | GET_WAITROOM_DATA_FULL: '인원초과로 참여할 수 없어요.',
43 | ENTER_ROOM_SUCCESS: '습관 방 참여 완료',
44 | ENTER_ROOM_FAIL: '습관 방 참여 실패',
45 | ENTER_ROOM_ALREADY: '이미 참여중인 습관 방입니다',
46 | ROOM_ID_INVALID: '올바르지 않은 roomId입니다',
47 | PURPOSE_SET_SUCCESS: '목표 설정 성공',
48 | ROOM_ID_NOT_FOUND: '유효하지 않은 roomId입니다',
49 | GET_ROOM_DATA_FAIL: '존재하지 않는 습관방입니다',
50 | NOT_ONGOING_ROOM: '현재 진행중인 습관방이 아닙니다',
51 | NOT_MEMBER: '참여중인 습관방이 아닙니다',
52 | GET_ROOM_LIST_SUCCESS: '참여중인 습관방 조회 완료',
53 | GET_ROOM_DETAIL_SUCCESS: '특정 습관방 상세조회 성공',
54 | NOT_MATCH_ROOM_AND_RECORD: '해당 습관방의 record가 아닙니다',
55 | NOT_STARTED_ROOM: '아직 대기중인 방입니다',
56 | ROOM_NOT_WAITING: '대기중인 습관방이 아닙니다',
57 | INCORRECT_RECORD: '올바르지 않은 recordId 입니다',
58 | GET_THUMBNAIL_LIST_SUCCESS: '보관함 대표사진 변경 뷰 사진 불러오기 성공',
59 | UPDATE_THUMBNAIL_SUCCESS: '보관함 대표사진 변경 성공',
60 |
61 | START_ROOM_SUCCESS: '습관 방 시작 완료',
62 | START_ROOM_ALREADY: '이미 시작된 방입니다',
63 | DONE_OR_REST_MEMBER: '습관 인증 완료 혹은 쉴래요 한 사용자입니다',
64 | CERTIFY_SUCCESS: '습관인증 업로드 성공',
65 | ROOM_OUT_SUCCESS: '습관 방 퇴장 완료',
66 | ROOM_DELETE_SUCCESS: '대기방 삭제 완료',
67 | HOST_WAITROOM_OUT_FAIL: '방 생성자는 대기방을 나갈 수 없습니다',
68 | DIALOG_READ_SUCCESS: '성공/실패한 습관방 읽음처리 완료',
69 |
70 | // Life Timeline
71 | GET_LIFE_TIMELINE_SUCCESS: '생명 타임라인 조회 성공',
72 | LIFE_TIMELINE_READ_SUCCESS: '생명 타임라인 읽음처리 완료',
73 |
74 | // Feed
75 | GET_FEED_SUCCESS: '피드 조회 성공',
76 | RECORD_ID_NOT_VALID: '유효하지 않은 recordId입니다',
77 | SEND_LIKE_SUCCESS: '좋아요 성공',
78 | CANCEL_LIKE_SUCCESS: '좋아요 취소 성공',
79 | REPORT_FEED_SUCCESS: '피드 신고 성공',
80 |
81 | // Spark
82 | CANNOT_SEND_SPARK_SELF: '자기자신에게 스파크를 보낼 수 없습니다',
83 | SEND_SPARK_SUCCESS: '스파크 전송 선공',
84 |
85 | // Myroom
86 | GET_MYROOM_SUCCESS: '보관함 리스트 불러오기 성공',
87 | GET_MYROOM_DETAIL_SUCCESS: '인증사진 모아보기 성공',
88 |
89 | // Notice
90 | SERVICE_READ_SUCCESS: '서비스 알림 읽음처리 완료',
91 | ACTIVE_READ_SUCCESS: '활동 알림 읽음처리 완료',
92 | GET_SERVICE_SUCCESS: '서비스 알림 조회 완료',
93 | GET_ACTIVE_SUCCESS: '활동 알림 조회 완료',
94 | NOTICE_DELETE_SUCCESS: '알림 삭제 완료',
95 | NOTICE_ID_NOT_VALID: '유효하지 않은 알림 ID 입니다',
96 | PUSH_SEND_SUCCESS: '푸시알림 전송 완료',
97 | GET_NEW_NOTICE_SUCCESS: '새로운 알림 조회 완료',
98 | GET_NOTICE_SETTING_SUCCESS: '푸시알림 설정 조회 완료',
99 | PUSH_CATEGORY_INVALID: '유효하지 않은 category입니다',
100 | PUSH_TOGGLE_SUCCESS: '푸시알림 설정 변경 완료',
101 |
102 | // Status
103 | INVALID_USER_STATUS: '유효하지 않은 status type입니다',
104 | CERTIFICATION_ALREADY_DONE: '이미 인증을 완료하였습니다',
105 | REST_ALREADY_DONE: '이미 쉴래요를 사용한 사용자입니다',
106 | CONSIDER_ALREADY_DONE: '이미 고민중 상태의 사용자입니다',
107 | UPDATE_STATUS_SUCCESS: '상태 변경 완료',
108 | REST_COUNT_ZERO: '쉴래요 사용 가능 횟수가 0인 사용자입니다',
109 |
110 | // Token
111 | TOKEN_EXPIRED: '만료된 토큰입니다',
112 | TOKEN_INVALID: '유효하지 않은 토큰입니다',
113 |
114 | // Scheduling
115 | CERTIFICATION_INSPECTION_SUCCESS: '인증 검사 성공',
116 | SEND_REMIND_SUCCESS: '리마인드 알림 보내기 성공',
117 |
118 | // Version
119 | GET_RECENT_VERSION_SUCCESS: '최신 버전정보 불러오기 성공',
120 | UPDATE_RECENT_VERSION_SUCCESS: '최신 버전정보 갱신 성공',
121 | };
122 |
--------------------------------------------------------------------------------
/functions/constants/statusCode.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | OK: 200,
3 | CREATED: 201,
4 | NO_CONTENT: 204,
5 | BAD_REQUEST: 400,
6 | UNAUTHORIZED: 401,
7 | FORBIDDEN: 403,
8 | NOT_FOUND: 404,
9 | CONFLICT: 409,
10 | INTERNAL_SERVER_ERROR: 500,
11 | SERVICE_UNAVAILABLE: 503,
12 | DB_ERROR: 600,
13 | };
14 |
--------------------------------------------------------------------------------
/functions/constants/termList.js:
--------------------------------------------------------------------------------
1 | const termList = [1, 4, 8, 34, 60];
2 |
3 | module.exports = {
4 | termList,
5 | };
6 |
--------------------------------------------------------------------------------
/functions/db/db.js:
--------------------------------------------------------------------------------
1 | // 필요한 모듈들
2 | const functions = require('firebase-functions');
3 | const { Pool, Query } = require('pg');
4 | const dayjs = require('dayjs');
5 | const dotenv = require('dotenv');
6 |
7 | dotenv.config();
8 |
9 | // DB Config (유저, 호스트, DB 이름, 패스워드)를 로딩해줍시다.
10 | const dbConfig = require('../config/dbConfig');
11 |
12 | // NODE_ENV라는 글로벌 환경변수를 사용해서, 현재 환경이 어떤 '모드'인지 판별해줍시다.
13 | let devMode = process.env.NODE_ENV === 'development';
14 |
15 | // SQL 쿼리문을 콘솔에 프린트할지 말지 결정해주는 변수를 선언합시다.
16 | const sqlDebug = true;
17 |
18 | // 기본 설정에서는 우리가 실행하게 되는 SQL 쿼리문이 콘솔에 찍히지 않기 때문에,
19 | // pg 라이브러리 내부의 함수를 살짝 손봐서 SQL 쿼리문이 콘솔에 찍히게 만들어 줍시다.
20 | const submit = Query.prototype.submit;
21 | Query.prototype.submit = function () {
22 | const text = this.text;
23 | const values = this.values || [];
24 | const query = text.replace(/\$([0-9]+)/g, (m, v) => JSON.stringify(values[parseInt(v) - 1]));
25 | // devMode === true 이면서 sqlDebug === true일 때 SQL 쿼리문을 콘솔에 찍겠다는 분기입니다.
26 | devMode && sqlDebug && console.log(`\n\n[👻 SQL STATEMENT]\n${query}\n_________\n`);
27 | submit.apply(this, arguments);
28 | };
29 |
30 | // 서버가 실행되면 현재 환경이 개발 모드(로컬)인지 프로덕션 모드(배포)인지 콘솔에 찍어줍시다.
31 | console.log(`[🔥DB] ${process.env.NODE_ENV} / DB: ${dbConfig.database}`);
32 |
33 | // 커넥션 풀을 생성해줍니다.
34 | const pool = new Pool({
35 | ...dbConfig,
36 | connectionTimeoutMillis: 60 * 1000,
37 | idleTimeoutMillis: 60 * 1000,
38 | });
39 |
40 | // 위에서 생성한 커넥션 풀에서 커넥션을 빌려오는 함수를 정의합니다.
41 | // 기본적으로 제공되는 pool.connect()와 pool.connect().release() 함수에 디버깅용 메시지를 추가하는 작업입니다.
42 | const connect = async (req) => {
43 | const now = dayjs();
44 | const string =
45 | !!req && !!req.method
46 | ? `[${req.method}] ${!!req.user ? `${req.user.id}` : ``} ${req.originalUrl}\n ${!!req.query && `query: ${JSON.stringify(req.query)}`} ${!!req.body && `body: ${JSON.stringify(req.body)}`} ${
47 | !!req.params && `params ${JSON.stringify(req.params)}`
48 | }`
49 | : `request 없음`;
50 | const callStack = new Error().stack;
51 | const client = await pool.connect();
52 | const query = client.query;
53 | const release = client.release;
54 |
55 | const releaseChecker = setTimeout(() => {
56 | devMode
57 | ? console.error('[ERROR] client connection이 15초 동안 릴리즈되지 않았습니다.', { callStack })
58 | : functions.logger.error('[ERROR] client connection이 15초 동안 릴리즈되지 않았습니다.', { callStack });
59 | devMode ? console.error(`마지막으로 실행된 쿼리문입니다. ${client.lastQuery}`) : functions.logger.error(`마지막으로 실행된 쿼리문입니다. ${client.lastQuery}`);
60 | }, 15 * 1000);
61 |
62 | client.query = (...args) => {
63 | client.lastQuery = args;
64 | return query.apply(client, args);
65 | };
66 | client.release = () => {
67 | clearTimeout(releaseChecker);
68 | const time = dayjs().diff(now, 'millisecond');
69 | if (time > 4000) {
70 | const message = `[RELEASE] in ${time} | ${string}`;
71 | devMode && console.log(message);
72 | }
73 | client.query = query;
74 | client.release = release;
75 | return release.apply(client);
76 | };
77 | return client;
78 | };
79 |
80 | module.exports = {
81 | connect,
82 | };
83 |
--------------------------------------------------------------------------------
/functions/db/dialog.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const _ = require('lodash');
3 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
4 |
5 | const insertDialogs = async (client, dialogs) => {
6 | const { rows } = await client.query(
7 | `
8 | INSERT INTO spark.dialog
9 | (user_id, room_id, type, date)
10 | VALUES
11 | ${dialogs.join()}
12 | RETURNING *
13 | `,
14 | );
15 |
16 | return convertSnakeToCamel.keysToCamel(rows);
17 | };
18 |
19 | const getUserDialogs = async (client, userId, types) => {
20 | const { rows } = await client.query(
21 | `
22 | SELECT *
23 | FROM spark.dialog d
24 | INNER JOIN spark.room r
25 | ON d.room_id = r.room_id
26 | WHERE user_id = $1
27 | AND type IN (${types.join()})
28 | AND is_read = FALSE
29 | `,
30 | [userId],
31 | );
32 | return convertSnakeToCamel.keysToCamel(rows);
33 | };
34 |
35 | const setDialogRead = async (client, dialogId) => {
36 | const now = dayjs().add(9, 'h');
37 | const { rows } = await client.query(
38 | `
39 | UPDATE spark.dialog
40 | SET is_read = TRUE, updated_at = $2, read_at = $2
41 | WHERE dialog_id = $1
42 | RETURNING *
43 | `,
44 | [dialogId, now],
45 | );
46 |
47 | return convertSnakeToCamel.keysToCamel(rows[0]);
48 | };
49 |
50 | const setLifeDeductionDialogsRead = async (client, userId, roomId) => {
51 | const now = dayjs().add(9, 'hour');
52 | const { rows } = await client.query(
53 | `
54 | UPDATE spark.dialog
55 | SET is_read = TRUE, updated_at = $3, read_at = $3
56 | WHERE user_id = $1
57 | AND room_id = $2
58 | AND is_read = FALSE
59 | AND type = 'LIFE_DEDUCTION'
60 | RETURNING *
61 | `,
62 | [userId, roomId, now],
63 | );
64 | return convertSnakeToCamel.keysToCamel(rows);
65 | };
66 |
67 | const getUnReadDialogByRoomAndUser = async (client, roomId, userId) => {
68 | const { rows } = await client.query(
69 | `
70 | SELECT *
71 | FROM spark.dialog
72 | WHERE user_id = $2
73 | AND room_id = $1
74 | AND is_read = FALSE
75 | AND type IN ('COMPLETE', 'FAIL')
76 | `,
77 | [roomId, userId],
78 | );
79 | return convertSnakeToCamel.keysToCamel(rows[0]);
80 | };
81 |
82 | module.exports = {
83 | insertDialogs,
84 | getUserDialogs,
85 | setDialogRead,
86 | getUnReadDialogByRoomAndUser,
87 | setLifeDeductionDialogsRead,
88 | };
89 |
--------------------------------------------------------------------------------
/functions/db/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | userDB: require('./user'),
3 | roomDB: require('./room'),
4 | sparkDB: require('./spark'),
5 | likeDB: require('./like'),
6 | noticeDB: require('./notice'),
7 | recordDB: require('./record'),
8 | scheduleDB: require('./schedule'),
9 | reportDB: require('./report'),
10 | dialogDB: require('./dialog'),
11 | remindDB: require('./remind'),
12 | ownershipDB: require('./ownership'),
13 | versionDB: require('./version'),
14 | lifeTimelineDB: require('./lifeTimeline'),
15 | };
16 |
--------------------------------------------------------------------------------
/functions/db/lifeTimeline.js:
--------------------------------------------------------------------------------
1 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
2 |
3 | const addDecreaseTimelines = async (client, timelines) => {
4 | const { rows } = await client.query(
5 | `
6 | INSERT INTO spark.life_timeline
7 | (receiver_id, room_id, is_decrease, decrease_count, profile_1, profile_2)
8 | VALUES
9 | ${timelines.join()}
10 | RETURNING *
11 | `,
12 | );
13 | return convertSnakeToCamel.keysToCamel(rows[0]);
14 | };
15 |
16 | const addFillTimelines = async (client, timelines) => {
17 | const { rows } = await client.query(
18 | `
19 | INSERT INTO spark.life_timeline
20 | (receiver_id, room_id, is_decrease, term_day)
21 | VALUES
22 | ${timelines.join()}
23 | RETURNING *
24 | `,
25 | );
26 | return convertSnakeToCamel.keysToCamel(rows[0]);
27 | };
28 |
29 | const getLifeTimeline = async (client, roomId, userId) => {
30 | const { rows } = await client.query(
31 | `
32 | SELECT * FROM spark.life_timeline
33 | WHERE room_id = $1
34 | AND receiver_id = $2
35 | ORDER BY life_timeline_id DESC
36 | `,
37 | [roomId, userId],
38 | );
39 | return convertSnakeToCamel.keysToCamel(rows);
40 | };
41 |
42 | const readLifeTimeline = async (client, roomId, userId) => {
43 | const { rows } = await client.query(
44 | `
45 | UPDATE spark.life_timeline
46 | SET is_read = TRUE
47 | WHERE room_id = $1
48 | AND receiver_id = $2
49 | RETURNING *
50 | `,
51 | [roomId, userId],
52 | );
53 | return convertSnakeToCamel.keysToCamel(rows);
54 | };
55 |
56 | const getNewTimelineCount = async (client, roomId, userId) => {
57 | const { rows } = await client.query(
58 | `
59 | SELECT count(*) as number FROM spark.life_timeline
60 | WHERE room_id = $1
61 | AND receiver_id = $2
62 | AND is_read = FALSE
63 | `,
64 | [roomId, userId],
65 | );
66 | return convertSnakeToCamel.keysToCamel(rows[0].number);
67 | };
68 |
69 | module.exports = {
70 | addDecreaseTimelines,
71 | addFillTimelines,
72 | getLifeTimeline,
73 | readLifeTimeline,
74 | getNewTimelineCount,
75 | };
76 |
--------------------------------------------------------------------------------
/functions/db/like.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const _ = require('lodash');
3 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
4 |
5 | const countLikeByRecordId = async (client, recordId) => {
6 | const { rows } = await client.query(
7 | `
8 | SELECT COUNT(*)
9 | FROM spark.like
10 | WHERE record_id = $1
11 | `,
12 | [recordId],
13 | );
14 | return convertSnakeToCamel.keysToCamel(rows);
15 | };
16 |
17 | const checkIsLike = async (client, recordId, userId) => {
18 | const { rows } = await client.query(
19 | `
20 | SELECT * FROM spark.like
21 | WHERE record_id = $1
22 | AND sender_id = $2
23 | `,
24 | [recordId, userId],
25 | );
26 | return convertSnakeToCamel.keysToCamel(rows[0]);
27 | };
28 |
29 | const sendLike = async (client, recordId, senderId) => {
30 | const now = dayjs().add(9, 'hour');
31 | const { rows } = await client.query(
32 | `
33 | INSERT INTO spark.like
34 | (record_id, sender_id, created_at)
35 | VALUES
36 | ($1, $2, $3)
37 | RETURNING *
38 | `,
39 | [recordId, senderId, now],
40 | );
41 |
42 | return convertSnakeToCamel.keysToCamel(rows[0]);
43 | };
44 |
45 | const cancelLike = async (client, recordId, senderId) => {
46 | const { rows } = await client.query(
47 | `
48 | DELETE FROM spark.like
49 | WHERE record_id = $1
50 | AND sender_id = $2
51 | RETURNING *
52 | `,
53 | [recordId, senderId],
54 | );
55 |
56 | return convertSnakeToCamel.keysToCamel(rows[0]);
57 | };
58 |
59 | module.exports = {
60 | countLikeByRecordId,
61 | checkIsLike,
62 | sendLike,
63 | cancelLike,
64 | };
65 |
--------------------------------------------------------------------------------
/functions/db/notice.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const _ = require('lodash');
3 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
4 |
5 | const serviceReadByUserId = async (client, userId) => {
6 | const now = dayjs().add(9, 'hour');
7 | const { rows } = await client.query(
8 | `
9 | UPDATE spark.notification
10 | SET is_read = TRUE, read_at = $2, updated_at = $2
11 | WHERE is_read = FALSE
12 | AND is_deleted = FALSE
13 | AND is_service = TRUE
14 | AND receiver_id = $1
15 | RETURNING *
16 | `,
17 | [userId, now],
18 | );
19 | return convertSnakeToCamel.keysToCamel(rows);
20 | };
21 |
22 | const activeReadByUserId = async (client, userId) => {
23 | const now = dayjs().add(9, 'hour');
24 | const { rows } = await client.query(
25 | `
26 | UPDATE spark.notification
27 | SET is_read = TRUE, read_at = $2, updated_at = $2
28 | WHERE is_read = FALSE
29 | AND is_deleted = FALSE
30 | AND is_service = FALSE
31 | AND receiver_id = $1
32 | RETURNING *
33 | `,
34 | [userId, now],
35 | );
36 | return convertSnakeToCamel.keysToCamel(rows);
37 | };
38 |
39 | const getNoticeByNoticeId = async (client, noticeId) => {
40 | const { rows } = await client.query(
41 | `
42 | SELECT * FROM spark.notification
43 | WHERE notification_id = $1
44 | `,
45 | [noticeId],
46 | );
47 | return convertSnakeToCamel.keysToCamel(rows[0]);
48 | };
49 |
50 | const deleteNoticeByNoticeId = async (client, noticeId) => {
51 | const now = dayjs().add(9, 'h');
52 | const { rows } = await client.query(
53 | `
54 | UPDATE spark.notification
55 | SET is_deleted = TRUE, deleted_at = $2, updated_at = $2
56 | WHERE notification_id = $1
57 | RETURNING *
58 | `,
59 | [noticeId, now],
60 | );
61 | return convertSnakeToCamel.keysToCamel(rows[0]);
62 | };
63 |
64 | const getServicesByUserId = async (client, userId, lastId, size) => {
65 | if (lastId === -1) {
66 | const { rows } = await client.query(
67 | `
68 | SELECT * FROM spark.notification
69 | WHERE receiver_id = $1
70 | AND room_id in (
71 | SELECT room_id
72 | FROM spark.entry
73 | WHERE user_id = $1
74 | AND is_out = FALSE
75 | AND is_kicked = FALSE
76 | )
77 | AND is_deleted = FALSE
78 | AND is_service = TRUE
79 | AND created_at >= (CURRENT_TIMESTAMP - INTERVAL '7 days - 9 hours')::date
80 | ORDER BY notification_id DESC
81 | LIMIT $2
82 | `,
83 | [userId, size],
84 | );
85 | return convertSnakeToCamel.keysToCamel(rows);
86 | } else {
87 | const { rows } = await client.query(
88 | `
89 | SELECT * FROM spark.notification
90 | WHERE receiver_id = $1
91 | AND room_id in (
92 | SELECT room_id
93 | FROM spark.entry
94 | WHERE user_id = $1
95 | AND is_out = FALSE
96 | AND is_kicked = FALSE
97 | )
98 | AND is_deleted = FALSE
99 | AND is_service = TRUE
100 | AND notification_id < $2
101 | AND created_at >= (CURRENT_TIMESTAMP - INTERVAL '7 days - 9 hours')::date
102 | ORDER BY notification_id DESC
103 | LIMIT $3
104 | `,
105 | [userId, lastId, size],
106 | );
107 | return convertSnakeToCamel.keysToCamel(rows);
108 | }
109 | };
110 |
111 | const getActivesByUserId = async (client, userId, lastId, size) => {
112 | if (lastId === -1) {
113 | const { rows } = await client.query(
114 | `
115 | SELECT * FROM spark.notification
116 | WHERE receiver_id = $1
117 | AND room_id in (
118 | SELECT room_id
119 | FROM spark.entry
120 | WHERE user_id = $1
121 | AND is_out = FALSE
122 | AND is_kicked = FALSE
123 | )
124 | AND is_deleted = FALSE
125 | AND is_service = FALSE
126 | AND created_at >= (CURRENT_TIMESTAMP - INTERVAL '7 days - 9 hours')::date
127 | ORDER BY notification_id DESC
128 | LIMIT $2
129 | `,
130 | [userId, size],
131 | );
132 | return convertSnakeToCamel.keysToCamel(rows);
133 | } else {
134 | const { rows } = await client.query(
135 | `
136 | SELECT * FROM spark.notification
137 | WHERE receiver_id = $1
138 | AND room_id in (
139 | SELECT room_id
140 | FROM spark.entry
141 | WHERE user_id = $1
142 | AND is_out = FALSE
143 | AND is_kicked = FALSE
144 | )
145 | AND is_deleted = FALSE
146 | AND is_service = FALSE
147 | AND notification_id < $2
148 | AND created_at >= (CURRENT_TIMESTAMP - INTERVAL '7 days - 9 hours')::date
149 | ORDER BY notification_id DESC
150 | LIMIT $3
151 | `,
152 | [userId, lastId, size],
153 | );
154 | return convertSnakeToCamel.keysToCamel(rows);
155 | }
156 | };
157 |
158 | const addNotification = async (client, title, body, thumbnail, receiverId, isService, isThumbProfile, roomId) => {
159 | const { rows } = await client.query(
160 | `
161 | INSERT INTO spark.notification
162 | (title, content, thumbnail, receiver_id, is_service, is_thumb_profile, room_id)
163 | VALUES
164 | ($1, $2, $3, $4, $5, $6, $7)
165 | RETURNING *
166 | `,
167 | [title, body, thumbnail, receiverId, isService, isThumbProfile, roomId],
168 | );
169 | return convertSnakeToCamel.keysToCamel(rows[0]);
170 | };
171 |
172 | const addNotifications = async (client, notifications) => {
173 | const { rows } = await client.query(
174 | `
175 | INSERT INTO spark.notification
176 | (title, content, thumbnail, receiver_id, is_service, is_thumb_profile, room_id)
177 | VALUES
178 | ${notifications.join()}
179 | RETURNING *
180 | `,
181 | );
182 | return convertSnakeToCamel.keysToCamel(rows[0]);
183 | };
184 |
185 | const getNumberOfUnreadNoticeById = async (client, userId) => {
186 | const { rows } = await client.query(
187 | `
188 | SELECT count(*) as number FROM spark.notification
189 | WHERE receiver_id = $1
190 | AND room_id in (
191 | SELECT room_id
192 | FROM spark.entry
193 | WHERE user_id = $1
194 | AND is_out = FALSE
195 | AND is_kicked = FALSE
196 | )
197 | AND is_deleted = FALSE
198 | AND is_read = FALSE
199 | AND created_at >= (CURRENT_TIMESTAMP - INTERVAL '7 days - 9 hours')::date
200 | `,
201 | [userId],
202 | );
203 | return convertSnakeToCamel.keysToCamel(rows[0].number);
204 | };
205 |
206 | const getNumberOfUnreadServiceNoticeById = async (client, userId) => {
207 | const { rows } = await client.query(
208 | `
209 | SELECT count(*) as number FROM spark.notification
210 | WHERE receiver_id = $1
211 | AND room_id in (
212 | SELECT room_id
213 | FROM spark.entry
214 | WHERE user_id = $1
215 | AND is_out = FALSE
216 | AND is_kicked = FALSE
217 | )
218 | AND is_deleted = FALSE
219 | AND is_read = FALSE
220 | AND is_service = TRUE
221 | AND created_at >= (CURRENT_TIMESTAMP - INTERVAL '7 days - 9 hours')::date
222 | `,
223 | [userId],
224 | );
225 | return convertSnakeToCamel.keysToCamel(rows[0].number);
226 | };
227 |
228 | const getNumberOfUnreadActiveNoticeById = async (client, userId) => {
229 | const { rows } = await client.query(
230 | `
231 | SELECT count(*) as number FROM spark.notification
232 | WHERE receiver_id = $1
233 | AND room_id in (
234 | SELECT room_id
235 | FROM spark.entry
236 | WHERE user_id = $1
237 | AND is_out = FALSE
238 | AND is_kicked = FALSE
239 | )
240 | AND is_deleted = FALSE
241 | AND is_read = FALSE
242 | AND is_service = FALSE
243 | AND created_at >= (CURRENT_TIMESTAMP - INTERVAL '7 days - 9 hours')::date
244 | `,
245 | [userId],
246 | );
247 | return convertSnakeToCamel.keysToCamel(rows[0].number);
248 | };
249 |
250 | const deleteNoticeByContentReceiverAndThumbnail = async (client, title, body, isService, receiverId, thumbnail) => {
251 | const { rows } = await client.query(
252 | `
253 | DELETE FROM spark.notification
254 | WHERE title = $1
255 | AND content = $2
256 | AND is_service = $3
257 | AND receiver_id = $4
258 | AND thumbnail = $5
259 | RETURNING *
260 | `,
261 | [title, body, isService, receiverId, thumbnail],
262 | );
263 | return convertSnakeToCamel.keysToCamel(rows[0]);
264 | };
265 |
266 | module.exports = {
267 | serviceReadByUserId,
268 | activeReadByUserId,
269 | getNoticeByNoticeId,
270 | deleteNoticeByNoticeId,
271 | getServicesByUserId,
272 | getActivesByUserId,
273 | addNotification,
274 | addNotifications,
275 | getNumberOfUnreadNoticeById,
276 | getNumberOfUnreadServiceNoticeById,
277 | getNumberOfUnreadActiveNoticeById,
278 | deleteNoticeByContentReceiverAndThumbnail,
279 | };
280 |
--------------------------------------------------------------------------------
/functions/db/ownership.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
3 |
4 | const insertOwnership = async (client, userId, filePath) => {
5 | const now = dayjs().add(9, 'hour');
6 | const { rows } = await client.query(
7 | `
8 | INSERT INTO spark.ownership
9 | (user_id, file_path, created_at)
10 | VALUES
11 | ($1, $2, $3)
12 | RETURNING *
13 | `,
14 | [userId, filePath, now],
15 | );
16 | return convertSnakeToCamel.keysToCamel(rows[0]);
17 | };
18 |
19 | const deleteAllOwnershipByUserId = async (client, userId) => {
20 | const now = dayjs().add(9, 'hour');
21 | const { rows } = await client.query(
22 | `
23 | UPDATE spark.ownership
24 | SET file_deleted = true, file_deleted_at = $2
25 | WHERE user_id = $1
26 | RETURNING *
27 | `,
28 | [userId, now],
29 | );
30 | return convertSnakeToCamel.keysToCamel(rows);
31 | };
32 |
33 | const getOwnershipByUserId = async (client, userId) => {
34 | const { rows } = await client.query(
35 | `
36 | SELECT * FROM spark.ownership
37 | WHERE user_id = $1
38 | AND file_deleted = FALSE
39 | `,
40 | [userId],
41 | );
42 | return convertSnakeToCamel.keysToCamel(rows);
43 | };
44 |
45 | module.exports = {
46 | insertOwnership,
47 | deleteAllOwnershipByUserId,
48 | getOwnershipByUserId,
49 | };
50 |
--------------------------------------------------------------------------------
/functions/db/record.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const _ = require('lodash');
3 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
4 |
5 | const getRecordById = async (client, recordId) => {
6 | const { rows } = await client.query(
7 | `
8 | SELECT * FROM spark.record
9 | WHERE record_id = $1
10 | `,
11 | [recordId],
12 | );
13 | return convertSnakeToCamel.keysToCamel(rows[0]);
14 | };
15 |
16 | const getRecentRecordByEntryId = async (client, entryId) => {
17 | const { rows } = await client.query(
18 | `
19 | SELECT * FROM spark.record
20 | WHERE entry_id = $1
21 | ORDER BY record_id desc
22 | LIMIT 1
23 | `,
24 | [entryId],
25 | );
26 | return convertSnakeToCamel.keysToCamel(rows[0]);
27 | };
28 |
29 | const updateStatusByRecordId = async (client, recordId, statusType) => {
30 | const now = dayjs().add(9, 'hour');
31 | const { rows } = await client.query(
32 | `
33 | UPDATE spark.record
34 | SET status = $2, updated_at = $3
35 | WHERE record_id = $1
36 | RETURNING *
37 | `,
38 | [recordId, statusType, now],
39 | );
40 | return convertSnakeToCamel.keysToCamel(rows[0]);
41 | };
42 |
43 | const uploadRecord = async (client, recordId, certifyingImg, timerRecord) => {
44 | const now = dayjs().add(9, 'hour');
45 | const { rows } = await client.query(
46 | `
47 | UPDATE spark.record
48 | SET certifying_img = $2, timer_record = $3, status = 'DONE', updated_at = $4, certified_at = $4
49 | WHERE record_id = $1
50 | RETURNING *
51 | `,
52 | [recordId, certifyingImg, timerRecord, now],
53 | );
54 | return convertSnakeToCamel.keysToCamel(rows[0]);
55 | };
56 |
57 | const getPagedRecordsByEntryId = async (client, entryId, lastId, size) => {
58 | const now = dayjs().add(9, 'hour');
59 | const today = dayjs(now).format('YYYY-MM-DD');
60 | if (lastId === -1) {
61 | const { rows } = await client.query(
62 | `
63 | SELECT *
64 | FROM spark.record r
65 | WHERE r.entry_id = $1
66 | AND r.day != 0
67 | AND (r.status in ('DONE', 'REST') OR r.date != $3)
68 | ORDER BY r.day DESC
69 | LIMIT $2
70 | `,
71 | [entryId, size, today],
72 | );
73 | return convertSnakeToCamel.keysToCamel(rows);
74 | } else {
75 | const { rows } = await client.query(
76 | `
77 | SELECT *
78 | FROM spark.record r
79 | WHERE r.entry_id = $1
80 | AND r.record_id < $2
81 | AND (r.status in ('DONE', 'REST') OR r.date != $4)
82 | ORDER BY r.day DESC
83 | LIMIT $3
84 | `,
85 | [entryId, lastId, size, today],
86 | );
87 | return convertSnakeToCamel.keysToCamel(rows);
88 | }
89 | };
90 |
91 | const getDonePagedRecordsByEntryId = async (client, entryId, lastId, size) => {
92 | const now = dayjs().add(9, 'hour');
93 | const today = dayjs(now).format('YYYY-MM-DD');
94 | if (lastId === -1) {
95 | const { rows } = await client.query(
96 | `
97 | SELECT *
98 | FROM spark.record r
99 | WHERE r.entry_id = $1
100 | AND r.day != 0
101 | AND r.status = 'DONE'
102 | ORDER BY r.day DESC
103 | LIMIT $2
104 | `,
105 | [entryId, size],
106 | );
107 | return convertSnakeToCamel.keysToCamel(rows);
108 | } else {
109 | const { rows } = await client.query(
110 | `
111 | SELECT *
112 | FROM spark.record r
113 | WHERE r.entry_id = $1
114 | AND r.record_id < $2
115 | AND r.status = 'DONE'
116 | ORDER BY r.day DESC
117 | LIMIT $3
118 | `,
119 | [entryId, lastId, size],
120 | );
121 | return convertSnakeToCamel.keysToCamel(rows);
122 | }
123 | };
124 |
125 | const insertRecords = async (client, insertEntries) => {
126 | const { rows } = await client.query(
127 | `
128 | INSERT INTO spark.record
129 | (entry_id, date, day)
130 | VALUES
131 | ${insertEntries.join()}
132 | RETURNING *
133 | `,
134 | );
135 |
136 | return convertSnakeToCamel.keysToCamel(rows);
137 | };
138 |
139 | const getNoneOrConsiderEntryIdsByDate = async (client, date) => {
140 | const { rows } = await client.query(
141 | `
142 | SELECT entry_id FROM spark.record
143 | WHERE date = $1
144 | AND status IN ('NONE', 'CONSIDER')
145 | `,
146 | [date],
147 | );
148 | return convertSnakeToCamel.keysToCamel(rows);
149 | };
150 |
151 | const getPushRemindUsers = async (client, date) => {
152 | const { rows } = await client.query(
153 | `
154 | SELECT e.user_id, r.room_name, r.room_id, rec.status, u.device_token
155 | FROM spark.entry e
156 | INNER JOIN spark.room r
157 | ON e.room_id = r.room_id
158 | INNER JOIN spark.record rec
159 | ON rec.entry_id = e.entry_id
160 | INNER JOIN spark.user u
161 | ON u.user_id = e.user_id
162 | WHERE e.is_out = FALSE
163 | AND e.is_deleted = FALSE
164 | AND e.is_kicked = FALSE
165 | AND u.is_deleted = FALSE
166 | AND u.push_remind = TRUE
167 | AND rec.date = $1
168 | AND e.room_id IN (
169 | SELECT DISTINCT e.room_id FROM spark.entry e
170 | INNER JOIN spark.record r
171 | ON e.entry_id = r.entry_id
172 | WHERE r.date = $1
173 | AND r.status IN ('NONE', 'CONSIDER')
174 | AND e.is_kicked = FALSE
175 | AND e.is_deleted = FALSE
176 | AND e.is_kicked = FALSE
177 | )
178 | `,
179 | [date],
180 | );
181 | return convertSnakeToCamel.keysToCamel(rows);
182 | };
183 |
184 | module.exports = {
185 | getRecordById,
186 | getRecentRecordByEntryId,
187 | updateStatusByRecordId,
188 | uploadRecord,
189 | getPagedRecordsByEntryId,
190 | getDonePagedRecordsByEntryId,
191 | insertRecords,
192 | getNoneOrConsiderEntryIdsByDate,
193 | getPushRemindUsers,
194 | };
195 |
--------------------------------------------------------------------------------
/functions/db/remind.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const _ = require('lodash');
3 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
4 |
5 | const insertRemind = async (client) => {
6 | const now = dayjs().add(9, 'hour');
7 | const today = dayjs(now).format('YYYY-MM-DD');
8 | const { rows } = await client.query(
9 | `
10 | INSERT INTO spark.remind
11 | (date)
12 | VALUES
13 | ($1)
14 | RETURNING *
15 | `,
16 | [today],
17 | );
18 |
19 | return convertSnakeToCamel.keysToCamel(rows);
20 | };
21 |
22 | module.exports = {
23 | insertRemind,
24 | };
25 |
--------------------------------------------------------------------------------
/functions/db/report.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const _ = require('lodash');
3 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
4 |
5 | const checkAlreadyReport = async (client, userId, recordId) => {
6 | const { rows } = await client.query(
7 | `
8 | SELECT *
9 | FROM spark.report
10 | WHERE user_id = $1
11 | AND record_id = $2
12 | `,
13 | [userId, recordId],
14 | );
15 |
16 | return convertSnakeToCamel.keysToCamel(rows[0]);
17 | }
18 |
19 | const addReport = async (client, userId, targetUserId, recordId) => {
20 | const { rows } = await client.query(
21 | `
22 | INSERT INTO spark.report
23 | (user_id, target_user_id, record_id)
24 | VALUES
25 | ($1, $2, $3)
26 | RETURNING *
27 | `,
28 | [userId, targetUserId, recordId],
29 | );
30 |
31 | return convertSnakeToCamel.keysToCamel(rows);
32 | }
33 |
34 | module.exports = {
35 | addReport,
36 | checkAlreadyReport,
37 | };
38 |
--------------------------------------------------------------------------------
/functions/db/schedule.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const _ = require('lodash');
3 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
4 |
5 |
6 | const insertSchedule = async (client) => {
7 | const now = dayjs().add(9, 'hour');
8 | const today = dayjs(now).format('YYYY-MM-DD');
9 | const { rows } = await client.query(
10 | `
11 | INSERT INTO spark.schedule
12 | (date)
13 | VALUES
14 | ($1)
15 | RETURNING *
16 | `,
17 | [today]
18 | );
19 |
20 | return convertSnakeToCamel.keysToCamel(rows);
21 | };
22 |
23 |
24 | module.exports = {
25 | insertSchedule,
26 | };
27 |
--------------------------------------------------------------------------------
/functions/db/spark.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
3 |
4 | const countSparkByRecordId = async (client, recordId) => {
5 | const { rows } = await client.query(
6 | `
7 | SELECT COUNT(*)
8 | FROM spark.spark
9 | WHERE record_id = $1
10 | `,
11 | [recordId],
12 | );
13 | return convertSnakeToCamel.keysToCamel(rows[0]);
14 | };
15 |
16 | const insertSpark = async (client, recordId, senderId, content) => {
17 | const { rows } = await client.query(
18 | `
19 | INSERT INTO spark.spark
20 | (record_id, sender_id, content)
21 | VALUES
22 | ($1, $2, $3)
23 | RETURNING *
24 | `,
25 | [recordId, senderId, content],
26 | );
27 | return convertSnakeToCamel.keysToCamel(rows[0]);
28 | }
29 |
30 | const countSparkByEntryId = async (client, entryId) => {
31 | const { rows } = await client.query(
32 | `
33 | SELECT COUNT(*)
34 | FROM spark.spark s
35 | WHERE s.record_id IN (
36 | SELECT r.record_id
37 | FROM spark.record r
38 | WHERE r.entry_id = $1
39 | )
40 | `,
41 | [entryId],
42 | );
43 | return convertSnakeToCamel.keysToCamel(rows);
44 | }
45 |
46 | const countSparkByRecordIds = async (client, recordIds) => {
47 | const { rows } = await client.query(
48 | `
49 | SELECT s.record_id, COUNT(record_id) AS spark_num
50 | FROM spark.spark s
51 | WHERE record_id in (${recordIds.join()})
52 | GROUP BY record_id
53 | `,
54 | );
55 | return convertSnakeToCamel.keysToCamel(rows);
56 | }
57 |
58 | module.exports = {
59 | countSparkByRecordId,
60 | insertSpark,
61 | countSparkByEntryId,
62 | countSparkByRecordIds
63 | };
64 |
65 |
--------------------------------------------------------------------------------
/functions/db/user.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const _ = require('lodash');
3 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
4 |
5 | const getAllUsers = async (client) => {
6 | const { rows } = await client.query(
7 | `
8 | SELECT * FROM spark.user as u
9 | WHERE is_deleted = FALSE
10 | `,
11 | );
12 | return convertSnakeToCamel.keysToCamel(rows);
13 | };
14 |
15 | const getUserById = async (client, userId) => {
16 | const { rows } = await client.query(
17 | `
18 | SELECT * FROM spark.user as u
19 | WHERE user_id = $1
20 | AND is_deleted = FALSE
21 | `,
22 | [userId],
23 | );
24 | return convertSnakeToCamel.keysToCamel(rows[0]);
25 | };
26 |
27 | const getUserWithDelete = async (client, userId) => {
28 | const { rows } = await client.query(
29 | `
30 | SELECT * FROM spark.user as u
31 | WHERE user_id = $1
32 | `,
33 | [userId],
34 | );
35 | return convertSnakeToCamel.keysToCamel(rows[0]);
36 | };
37 |
38 | const getUsersByIds = async (client, userIds) => {
39 | const { rows } = await client.query(
40 | `
41 | SELECT DISTINCT * FROM spark.user
42 | WHERE user_id in (${userIds.join()})
43 | AND is_deleted = FALSE
44 | `,
45 | );
46 | return convertSnakeToCamel.keysToCamel(rows);
47 | };
48 |
49 | const getUserBySocialId = async (client, socialId) => {
50 | const { rows } = await client.query(
51 | `
52 | SELECT * FROM spark.user u
53 | WHERE social_id = $1
54 | AND is_deleted = FALSE
55 | `,
56 | [socialId],
57 | );
58 | return convertSnakeToCamel.keysToCamel(rows[0]);
59 | };
60 |
61 | const addUser = async (client, socialId, nickname, profileImg, fcmToken) => {
62 | const { rows } = await client.query(
63 | `
64 | INSERT INTO spark.user
65 | (social_id, nickname, profile_img, device_token)
66 | VALUES
67 | ($1, $2, $3, $4)
68 | RETURNING user_id, nickname, profile_img
69 | `,
70 | [socialId, nickname, profileImg, fcmToken],
71 | );
72 | return convertSnakeToCamel.keysToCamel(rows[0]);
73 | };
74 |
75 | const updateDeviceTokenById = async (client, userId, fcmToken) => {
76 | const now = dayjs().add(9, 'hour');
77 | const { rows } = await client.query(
78 | `
79 | UPDATE spark.user
80 | SET device_token = $2, updated_at = $3
81 | WHERE user_id = $1
82 | RETURNING *
83 | `,
84 | [userId, fcmToken, now],
85 | );
86 | return convertSnakeToCamel.keysToCamel(rows[0]);
87 | };
88 |
89 | const updateDeviceTokenAndOsById = async (client, userId, fcmToken, os) => {
90 | const now = dayjs().add(9, 'hour');
91 | const { rows } = await client.query(
92 | `
93 | UPDATE spark.user
94 | SET device_token = $2, os = $3, updated_at = $4
95 | WHERE user_id = $1
96 | RETURNING *
97 | `,
98 | [userId, fcmToken, os, now],
99 | );
100 | return convertSnakeToCamel.keysToCamel(rows[0]);
101 | };
102 |
103 | const updateProfileById = async (client, userId, nickname, profileImg) => {
104 | const now = dayjs().add(9, 'hour');
105 | const { rows } = await client.query(
106 | `
107 | UPDATE spark.user
108 | SET nickname = $2, profile_img = $3, updated_at = $4
109 | WHERE user_id = $1
110 | RETURNING *
111 | `,
112 | [userId, nickname, profileImg, now],
113 | );
114 | return convertSnakeToCamel.keysToCamel(rows[0]);
115 | };
116 |
117 | const togglePushSettingById = async (client, userId, category) => {
118 | const categoryColumn = 'push_' + convertSnakeToCamel.keysToSnake([category])[0];
119 | const now = dayjs().add(9, 'hour');
120 | const { rows } = await client.query(
121 | `
122 | UPDATE spark.user
123 | SET ${categoryColumn} = NOT ${categoryColumn}, updated_at = $2
124 | WHERE user_id = $1
125 | RETURNING *
126 | `,
127 | [userId, now],
128 | );
129 | return convertSnakeToCamel.keysToCamel(rows[0]);
130 | };
131 |
132 | const emptyDeviceTokenById = async (client, userId) => {
133 | const now = dayjs().add(9, 'hour');
134 | const { rows } = await client.query(
135 | `
136 | UPDATE spark.user
137 | SET device_token = '', updated_at = $2
138 | WHERE user_id = $1
139 | RETURNING *
140 | `,
141 | [userId, now],
142 | );
143 | return convertSnakeToCamel.keysToCamel(rows[0]);
144 | };
145 |
146 | const deleteUserSoft = async (client, userId, socialId) => {
147 | const now = dayjs().add(9, 'hour');
148 | const nowToString = now.format('YYYYMMDDHHmmssSSS');
149 | const newSocialId = `${socialId}-out-${nowToString}`;
150 | const { rows } = await client.query(
151 | `
152 | UPDATE spark.user
153 | SET device_token = '', is_deleted = TRUE, social_id = $3, updated_at = $2, deleted_at = $2
154 | WHERE user_id = $1
155 | RETURNING *
156 | `,
157 | [userId, now, newSocialId],
158 | );
159 | return convertSnakeToCamel.keysToCamel(rows[0]);
160 | };
161 |
162 | module.exports = {
163 | getAllUsers,
164 | getUserById,
165 | getUsersByIds,
166 | getUserBySocialId,
167 | addUser,
168 | updateDeviceTokenById,
169 | updateDeviceTokenAndOsById,
170 | updateProfileById,
171 | togglePushSettingById,
172 | getUserWithDelete,
173 | emptyDeviceTokenById,
174 | deleteUserSoft,
175 | };
176 |
--------------------------------------------------------------------------------
/functions/db/version.js:
--------------------------------------------------------------------------------
1 | const dayjs = require('dayjs');
2 | const convertSnakeToCamel = require('../lib/convertSnakeToCamel');
3 |
4 | const getRecentVersion = async (client) => {
5 | const { rows } = await client.query(
6 | `
7 | SELECT * FROM spark.version
8 | ORDER BY created_at DESC
9 | LIMIT 1
10 | `,
11 | );
12 | return convertSnakeToCamel.keysToCamel(rows[0]);
13 | };
14 |
15 | const updateRecentVersion = async (client, newVersion) => {
16 | const now = dayjs().add(9, 'hour');
17 | const { rows } = await client.query(
18 | `
19 | INSERT INTO spark.version
20 | (version, created_at)
21 | VALUES
22 | ($1, $2)
23 | RETURNING *
24 | `,
25 | [newVersion, now],
26 | );
27 | return convertSnakeToCamel.keysToCamel(rows[0]);
28 | };
29 |
30 | module.exports = {
31 | getRecentVersion,
32 | updateRecentVersion,
33 | };
34 |
--------------------------------------------------------------------------------
/functions/index.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin');
2 | const serviceAccount = require('./we-sopt-spark-firebase-adminsdk-emnjd-30d5170309.json');
3 | const dotenv = require('dotenv');
4 |
5 | dotenv.config();
6 |
7 | let firebase;
8 | if (admin.apps.length === 0) {
9 | firebase = admin.initializeApp({
10 | credential: admin.credential.cert(serviceAccount),
11 | });
12 | } else {
13 | firebase = admin.app();
14 | }
15 |
16 | module.exports = {
17 | api: require('./api'),
18 | };
19 |
--------------------------------------------------------------------------------
/functions/lib/convertSnakeToCamel.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash');
2 |
3 | const toCamel = (s) => {
4 | return s.replace(/([-_][a-z])/gi, ($1) => {
5 | return $1.toUpperCase().replace('-', '').replace('_', '');
6 | });
7 | };
8 |
9 | const isArray = function (a) {
10 | return Array.isArray(a);
11 | };
12 |
13 | const isObject = function (o) {
14 | return o === Object(o) && !isArray(o) && typeof o !== 'function';
15 | };
16 |
17 | const keysToCamel = function (o) {
18 | if (isObject(o)) {
19 | const n = {};
20 |
21 | Object.keys(o).forEach((k) => {
22 | n[toCamel(k)] = o[k];
23 | });
24 |
25 | return n;
26 | } else if (isArray(o)) {
27 | return o.map((i) => {
28 | return keysToCamel(i);
29 | });
30 | }
31 |
32 | return o;
33 | };
34 | const keysToSnake = function (o) {
35 | if (isObject(o)) {
36 | const n = {};
37 |
38 | Object.keys(o).forEach((k) => {
39 | n[_.snakeCase(k)] = o[k];
40 | });
41 |
42 | return n;
43 | } else if (isArray(o)) {
44 | return o.map((i) => {
45 | return _.snakeCase(i);
46 | });
47 | }
48 |
49 | return o;
50 | };
51 |
52 | module.exports = {
53 | keysToCamel,
54 | keysToSnake,
55 | };
56 |
--------------------------------------------------------------------------------
/functions/lib/deleteImage.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin');
2 | const { firebaseConfig } = require('../config/firebaseClient');
3 | const { ownershipDB } = require('../db');
4 |
5 | const deleteFirebaseImage = async (filePath) => {
6 | const fileName = filePath.split('.')[0];
7 | const fileExtension = filePath.split('.')[1];
8 | await admin.storage().bucket(firebaseConfig.storageBucket).file(`${fileName}.${fileExtension}`).delete();
9 | await admin.storage().bucket(firebaseConfig.storageBucket).file(`${fileName}_270x270.${fileExtension}`).delete();
10 | await admin.storage().bucket(firebaseConfig.storageBucket).file(`${fileName}_360x360.${fileExtension}`).delete();
11 | await admin.storage().bucket(firebaseConfig.storageBucket).file(`${fileName}_720x720.${fileExtension}`).delete();
12 | };
13 |
14 | const deleteFirebaseImageByUserId = async (client, userId) => {
15 | const ownership = await ownershipDB.getOwnershipByUserId(client, userId);
16 | for (let i = 0; i < ownership.length; i++) {
17 | await deleteFirebaseImage(ownership[i].filePath);
18 | }
19 | await ownershipDB.deleteAllOwnershipByUserId(client, userId);
20 | };
21 |
22 | module.exports = {
23 | deleteFirebaseImage,
24 | deleteFirebaseImageByUserId,
25 | };
26 |
--------------------------------------------------------------------------------
/functions/lib/jwtHandlers.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const jwt = require('jsonwebtoken');
3 | const { TOKEN_INVALID, TOKEN_EXPIRED } = require('../constants/jwt');
4 | const secretKey = process.env.JWT_SECRET;
5 | const options = {
6 | algorithm: 'HS256',
7 | expiresIn: '30d',
8 | issuer: 'spark',
9 | };
10 |
11 | const sign = (user) => {
12 | const payload = {
13 | userId: user.userId
14 | };
15 |
16 | const result = {
17 | accesstoken: jwt.sign(payload, secretKey, options),
18 | // refreshToken: jwt.sign(payload, secretKey, refreshOptions),
19 | };
20 | return result;
21 | };
22 |
23 | const verify = (token) => {
24 | let decoded;
25 | try {
26 | // console.log("token:",token);
27 | decoded = jwt.verify(token, secretKey);
28 | } catch (err) {
29 | if (err.message === 'jwt expired') {
30 | console.log('expired token');
31 | return TOKEN_EXPIRED;
32 | } else if (err.message === 'invalid token') {
33 | console.log("decoded:", decoded);
34 | console.log('invalid token');
35 | console.log(TOKEN_INVALID);
36 | return TOKEN_INVALID;
37 | } else {
38 | console.log('invalid token');
39 | return TOKEN_INVALID;
40 | }
41 | }
42 | return decoded;
43 | };
44 |
45 | module.exports = {
46 | sign,
47 | verify,
48 | };
--------------------------------------------------------------------------------
/functions/lib/passedDayToStr.js:
--------------------------------------------------------------------------------
1 | const passedDayToStr = (passedDay) => {
2 | if (passedDay === 0) {
3 | return '오늘';
4 | }
5 |
6 | if (passedDay < 7) {
7 | return `${passedDay}일 전`;
8 | }
9 |
10 | if (passedDay < 30) {
11 | return `${Math.floor(passedDay / 7)}주 전`;
12 | }
13 | };
14 |
15 | module.exports = {
16 | passedDayToStr,
17 | };
18 |
--------------------------------------------------------------------------------
/functions/lib/pushAlarm.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin');
2 | const functions = require('firebase-functions');
3 | const util = require('./util');
4 | const statusCode = require('../constants/statusCode');
5 | const responseMessage = require('../constants/responseMessage');
6 |
7 | const send = async (req, res, title, body, receiverToken, category, imageUrl = null, roomId = '', recordId = '') => {
8 | let mutableContent = 1;
9 | if (!imageUrl) {
10 | mutableContent = 0;
11 | imageUrl = '';
12 | }
13 |
14 | if (!receiverToken) {
15 | return true;
16 | }
17 |
18 | if (!roomId) roomId = '';
19 | if (!recordId) recordId = '';
20 |
21 | try {
22 | const message = {
23 | data: {
24 | roomId: String(roomId),
25 | recordId: String(recordId),
26 | },
27 | android: {
28 | data: {
29 | title,
30 | body,
31 | imageUrl,
32 | category,
33 | roomId: String(roomId),
34 | recordId: String(recordId),
35 | },
36 | },
37 | apns: {
38 | payload: {
39 | aps: {
40 | alert: {
41 | title,
42 | body,
43 | },
44 | category,
45 | 'thread-id': category,
46 | 'mutable-content': mutableContent,
47 | },
48 | },
49 | fcm_options: {
50 | image: imageUrl,
51 | },
52 | },
53 | token: receiverToken,
54 | };
55 |
56 | admin
57 | .messaging()
58 | .send(message)
59 | .then(function (response) {
60 | return true;
61 | })
62 | .catch(function (err) {
63 | return res.status(400).json({ success: false });
64 | });
65 | return true;
66 | } catch (error) {
67 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
68 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
69 | } finally {
70 | }
71 | };
72 |
73 | const getMessage = (title, body, receiverToken, category, imageUrl = null, roomId = '', recordId = '') => {
74 | let mutableContent = 1;
75 | if (!imageUrl) {
76 | mutableContent = 0;
77 | imageUrl = '';
78 | }
79 |
80 | if (!roomId) roomId = '';
81 | if (!recordId) recordId = '';
82 |
83 | const message = {
84 | data: {
85 | roomId: String(roomId),
86 | recordId: String(recordId),
87 | },
88 | android: {
89 | data: {
90 | title,
91 | body,
92 | imageUrl,
93 | category,
94 | roomId: String(roomId),
95 | recordId: String(recordId),
96 | },
97 | },
98 | apns: {
99 | payload: {
100 | aps: {
101 | alert: {
102 | title,
103 | body,
104 | },
105 | category,
106 | 'thread-id': category,
107 | 'mutable-content': mutableContent,
108 | },
109 | },
110 | fcm_options: {
111 | image: imageUrl,
112 | },
113 | },
114 | token: receiverToken,
115 | };
116 | return message;
117 | };
118 |
119 | const sendMessages = async (req, res, messages) => {
120 | admin
121 | .messaging()
122 | .sendAll(messages)
123 | .then(function (response) {
124 | return true;
125 | })
126 | .catch(function (err) {
127 | console.log(err);
128 | return false;
129 | });
130 | };
131 |
132 | const sendMulticastByTokens = async (req, res, title, body, receiverTokens, category, imageUrl = null, roomId = '', recordId = '') => {
133 | let mutableContent = 1;
134 | if (!imageUrl) {
135 | mutableContent = 0;
136 | imageUrl = '';
137 | }
138 |
139 | if (!roomId) roomId = '';
140 | if (!recordId) recordId = '';
141 |
142 | // FCM Token이 empty인 경우 제외
143 | receiverTokens = receiverTokens.filter((t) => t);
144 | if (!receiverTokens.length) {
145 | return true;
146 | }
147 |
148 | try {
149 | const message = {
150 | data: {
151 | roomId: String(roomId),
152 | recordId: String(recordId),
153 | },
154 | android: {
155 | data: {
156 | title,
157 | body,
158 | imageUrl,
159 | category,
160 | roomId: String(roomId),
161 | recordId: String(recordId),
162 | },
163 | },
164 | apns: {
165 | payload: {
166 | aps: {
167 | alert: {
168 | title,
169 | body,
170 | },
171 | category,
172 | 'thread-id': category,
173 | 'mutable-content': mutableContent,
174 | },
175 | },
176 | fcm_options: {
177 | image: imageUrl,
178 | },
179 | },
180 | tokens: receiverTokens,
181 | };
182 |
183 | admin
184 | .messaging()
185 | .sendMulticast(message)
186 | .then(function (response) {
187 | return true;
188 | })
189 | .catch(function (err) {
190 | return res.status(400).json({ success: false });
191 | });
192 | return true;
193 | } catch (error) {
194 | functions.logger.error(`[ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`, `[CONTENT] ${error}`);
195 | return res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
196 | } finally {
197 | }
198 | };
199 |
200 | module.exports = {
201 | send,
202 | sendMulticastByTokens,
203 | getMessage,
204 | sendMessages,
205 | };
206 |
--------------------------------------------------------------------------------
/functions/lib/util.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | success: (status, message, data) => {
3 | return {
4 | status,
5 | success: true,
6 | message,
7 | data,
8 | };
9 | },
10 | fail: (status, message) => {
11 | return {
12 | status,
13 | success: false,
14 | message,
15 | };
16 | },
17 | };
18 |
--------------------------------------------------------------------------------
/functions/middlewares/auth.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const jwtHandlers = require('../lib/jwtHandlers');
3 | const db = require('../db/db');
4 | const util = require('../lib/util');
5 | const statusCode = require('../constants/statusCode');
6 | const responseMessage = require('../constants/responseMessage');
7 | const slackAPI = require('./slackAPI');
8 | const { userDB } = require('../db');
9 | const { TOKEN_INVALID, TOKEN_EXPIRED } = require('../constants/jwt');
10 |
11 | const checkUser = async (req, res, next) => {
12 | let client;
13 | try {
14 | client = await db.connect(req);
15 |
16 | let user;
17 |
18 | if (!req.headers) return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.NO_AUTH_HEADER));
19 |
20 | const token = String(req.headers.authorization || '');
21 | if (!token) return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.TOKEN_EMPTY));
22 | const decodedToken = jwtHandlers.verify(token);
23 | if (decodedToken === TOKEN_EXPIRED) return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.TOKEN_EXPIRED));
24 | if (decodedToken === TOKEN_INVALID) return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.TOKEN_INVALID));
25 |
26 | console.log(decodedToken);
27 | const userId = decodedToken.userId;
28 |
29 | if (!userId) return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.TOKEN_INVALID));
30 |
31 | user = await userDB.getUserWithDelete(client, userId);
32 |
33 | if (!user) return res.status(statusCode.UNAUTHORIZED).send(util.fail(statusCode.UNAUTHORIZED, responseMessage.NO_USER));
34 | if (user.isDeleted) return res.status(statusCode.BAD_REQUEST).send(util.fail(statusCode.BAD_REQUEST, responseMessage.ALREADY_DELETED_USER));
35 | req.user = user;
36 | } catch (error) {
37 | console.log(error);
38 |
39 | const slackMessage = `[ERROR BY ${req.user.nickname} (${req.user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${error} ${JSON.stringify(error)}`;
40 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
41 |
42 | res.status(statusCode.INTERNAL_SERVER_ERROR).send(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
43 | } finally {
44 | client.release();
45 | }
46 |
47 | next();
48 | };
49 |
50 | module.exports = { checkUser };
51 |
--------------------------------------------------------------------------------
/functions/middlewares/slackAPI.js:
--------------------------------------------------------------------------------
1 | const functions = require('firebase-functions');
2 | const axios = require('axios');
3 |
4 | const dotenv = require('dotenv');
5 |
6 | dotenv.config();
7 |
8 | // 슬랙 Webhook에서 발급받은 endpoint를 .env 파일에서 끌어옴
9 | // endpoint 자체는 깃허브에 올라가면 안 되기 때문!
10 | const DEV_WEB_HOOK_ERROR_MONITORING = process.env.DEV_WEB_HOOK_ERROR_MONITORING;
11 | const DEV_WEB_HOOK_FEED_REPORT = process.env.DEV_WEB_HOOK_FEED_REPORT;
12 |
13 | const sendMessageToSlack = (message, apiEndPoint = DEV_WEB_HOOK_ERROR_MONITORING) => {
14 | // 슬랙 Webhook을 이용해 슬랙에 메시지를 보내는 코드
15 | try {
16 | axios
17 | .post(apiEndPoint, { text: message })
18 | .then((response) => {})
19 | .catch((e) => {
20 | throw e;
21 | });
22 | } catch (e) {
23 | console.error(e);
24 | // 슬랙 Webhook 자체에서 에러가 났을 경우,
25 | // Firebase 콘솔에 에러를 찍는 코드
26 | functions.logger.error('[slackAPI 에러]', { error: e });
27 | }
28 | };
29 | const feedReporotToSlack = (message, apiEndPoint = DEV_WEB_HOOK_FEED_REPORT) => {
30 | // 슬랙 Webhook을 이용해 슬랙에 메시지를 보내는 코드
31 | try {
32 | axios
33 | .post(apiEndPoint, { text: message })
34 | .then((response) => {})
35 | .catch((e) => {
36 | throw e;
37 | });
38 | } catch (e) {
39 | console.error(e);
40 | // 슬랙 Webhook 자체에서 에러가 났을 경우,
41 | // Firebase 콘솔에 에러를 찍는 코드
42 | functions.logger.error('[slackAPI 에러]', { error: e });
43 | }
44 | };
45 |
46 | // 이 파일에서 정의한 변수 / 함수를 export 해서, 다른 곳에서 사용할 수 있게 해주는 코드
47 | module.exports = {
48 | sendMessageToSlack,
49 | DEV_WEB_HOOK_ERROR_MONITORING,
50 | feedReporotToSlack,
51 | DEV_WEB_HOOK_FEED_REPORT,
52 | };
--------------------------------------------------------------------------------
/functions/middlewares/uploadImage.js:
--------------------------------------------------------------------------------
1 | const admin = require('firebase-admin');
2 | const functions = require('firebase-functions');
3 | const BusBoy = require('busboy');
4 | const path = require('path');
5 | const os = require('os');
6 | const fs = require('fs');
7 | const dayjs = require('dayjs');
8 | const db = require('../db/db');
9 | const { ownershipDB } = require('../db');
10 | const { firebaseConfig } = require('../config/firebaseClient');
11 | const util = require('../lib/util');
12 | const statusCode = require('../constants/statusCode');
13 | const responseMessage = require('../constants/responseMessage');
14 | const slackAPI = require('./slackAPI');
15 |
16 | const uploadImageIntoSubDir = (subDir) => {
17 | return function (req, res, next) {
18 | const busboy = new BusBoy({ headers: req.headers });
19 |
20 | let imageFileName = {};
21 | let imagesToUpload = [];
22 | let imageToAdd = {};
23 | let imageUrls = [];
24 | let filePaths = [];
25 |
26 | let fields = {};
27 |
28 | // req.body로 들어오는 key:value 페어들을 처리
29 | busboy.on('field', (fieldName, val) => {
30 | fields[fieldName] = val;
31 | });
32 |
33 | // req.body로 들어오는 게 파일일 경우 처리
34 | busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
35 | if (mimetype !== 'image/jpeg' && mimetype !== 'image/png') {
36 | const slackMessage = `[Image Upload File Type Exception]\n [${req.user.nickname} (${req.user.userId})] tried to upload ${mimetype} file`;
37 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
38 |
39 | return res.status(400).json({ error: 'Wrong file type submitted' });
40 | }
41 | // my.image.png => ['my', 'image', 'png']
42 | const imageExtension = filename.split('.')[filename.split('.').length - 1];
43 | // 32756238461724837.png
44 | imageFileName = `${dayjs().format('YYYYMMDD_HHmmss_')}${Math.round(Math.random() * 1000000000000).toString()}.${imageExtension}`;
45 | const filepath = path.join(os.tmpdir(), imageFileName);
46 | imageToAdd = { imageFileName, filepath, mimetype };
47 | file.pipe(fs.createWriteStream(filepath));
48 | imagesToUpload.push(imageToAdd);
49 | });
50 |
51 | // req.body로 들어온 파일들을 Firebase Storage에 업로드
52 | busboy.on('finish', async () => {
53 | let promises = [];
54 | imagesToUpload.forEach((imageToBeUploaded) => {
55 | imageUrls.push(`https://firebasestorage.googleapis.com/v0/b/${firebaseConfig.storageBucket}/o/${subDir}%2F${imageToBeUploaded.imageFileName}?alt=media`);
56 | filePaths.push(`${subDir}/${imageToBeUploaded.imageFileName}`);
57 | promises.push(
58 | admin
59 | .storage()
60 | .bucket(firebaseConfig.storageBucket)
61 | .upload(imageToBeUploaded.filepath, {
62 | destination: `${subDir}/${imageToBeUploaded.filepath.split('/').slice(-1)[0]}`,
63 | resumable: false,
64 | metadata: {
65 | metadata: {
66 | contentType: imageToBeUploaded.mimetype,
67 | },
68 | },
69 | }),
70 | );
71 | });
72 |
73 | let client;
74 | try {
75 | // File에 대한 Ownership 등록
76 | client = await db.connect(req);
77 | if (req.user) {
78 | filePaths.forEach(async (filePath) => {
79 | await ownershipDB.insertOwnership(client, req.user.userId, filePath);
80 | });
81 | }
82 |
83 | await Promise.all(promises);
84 | req.body = fields;
85 | req.imageUrls = imageUrls;
86 | next();
87 | } catch (err) {
88 | functions.logger.error(`[FILE UPLOAD ERROR] [${req.method.toUpperCase()}] ${req.originalUrl}`);
89 |
90 | const slackMessage = `[ERROR BY ${req.user.nickname} (${req.user.userId})] [${req.method.toUpperCase()}] ${req.originalUrl} ${err} ${JSON.stringify(err)}`;
91 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
92 | return res.status(500).json(util.fail(statusCode.INTERNAL_SERVER_ERROR, responseMessage.INTERNAL_SERVER_ERROR));
93 | } finally {
94 | client.release();
95 | }
96 | });
97 |
98 | busboy.end(req.rawBody);
99 | };
100 | };
101 | module.exports = uploadImageIntoSubDir;
102 |
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "description": "Cloud Functions for Firebase",
4 | "scripts": {
5 | "lint": "eslint .",
6 | "develop": "cross-env NODE_ENV=development firebase emulators:start --only functions",
7 | "serve": "cross-env NODE_ENV=production firebase emulators:start --only functions",
8 | "shell": "firebase functions:shell",
9 | "start": "npm run shell",
10 | "deploy": "cross-env NODE_ENV=production firebase deploy --only functions",
11 | "logs": "firebase functions:log"
12 | },
13 | "engines": {
14 | "node": "12"
15 | },
16 | "main": "index.js",
17 | "dependencies": {
18 | "axios": "^0.24.0",
19 | "busboy": "^0.3.1",
20 | "cookie-parser": "^1.4.6",
21 | "cors": "^2.8.5",
22 | "cross-env": "^7.0.3",
23 | "dayjs": "^1.10.7",
24 | "dotenv": "^10.0.0",
25 | "eslint-config-prettier": "^8.3.0",
26 | "express": "^4.17.2",
27 | "firebase": "^9.5.0",
28 | "firebase-admin": "^10.3.0",
29 | "firebase-functions": "^3.21.2",
30 | "helmet": "^4.6.0",
31 | "hpp": "^0.2.3",
32 | "jsonwebtoken": "^8.5.1",
33 | "lodash": "^4.17.21",
34 | "multer": "^1.4.3",
35 | "nanoid": "^3.1.30",
36 | "node-schedule": "^2.1.0",
37 | "pg": "^8.7.1"
38 | },
39 | "devDependencies": {
40 | "eslint": "^7.6.0",
41 | "eslint-config-google": "^0.14.0",
42 | "firebase-functions-test": "^0.2.0"
43 | },
44 | "private": true
45 | }
46 |
--------------------------------------------------------------------------------
/functions/scheduler/funcs.js:
--------------------------------------------------------------------------------
1 | const db = require('../db/db');
2 | const { roomDB, recordDB, scheduleDB, remindDB, dialogDB, lifeTimelineDB } = require('../db');
3 | const _ = require('lodash');
4 | const dayjs = require('dayjs');
5 | const slackAPI = require('../middlewares/slackAPI');
6 | const alarmMessage = require('../constants/alarmMessage');
7 | const pushAlarm = require('../lib/pushAlarm');
8 | const { termList } = require('../constants/termList');
9 |
10 | const checkLife = async () => {
11 | let client;
12 | try {
13 | client = await db.connect();
14 | const scheduleCheck = await scheduleDB.insertSchedule(client);
15 | if (!scheduleCheck.length) {
16 | return;
17 | }
18 |
19 | const now = dayjs().add(9, 'hour');
20 | const today = now.format('YYYY-MM-DD');
21 |
22 | const ongoingRooms = await roomDB.getOngoingRoomIds(client);
23 | const ongoingRoomIds = ongoingRooms.map((o) => o.roomId);
24 | let failRecords = [];
25 | if (ongoingRoomIds.length) {
26 | failRecords = await roomDB.getFailRecords(client, ongoingRoomIds); // 습관방별 [실패한 record 개수(failCount)] 불러오기
27 | }
28 | const roomGroupByFailCount = _.groupBy(failRecords, 'failCount'); // failCount별 roomId 묶어주기 (ex. [{"failCount": 1, "roomId": [1,2,3]}, {"failCount":2, "roomId": [4,5,6]}])
29 | const failCountList = [...new Set(failRecords.map((o) => Number(o.failCount)))]; // failCount 뭐뭐있는지~ (ex. [1,2,3])
30 | const roomIdsByFailCount = { 1: [], 2: [], 3: [] };
31 | failCountList.map((failCount) => {
32 | const roomIds = roomGroupByFailCount[failCount].map((o) => o.roomId); // 해당 failCount의 roomId배열
33 | if (failCount < 3) {
34 | roomIdsByFailCount[failCount] = roomIds;
35 | } else {
36 | // failCount 3보다 크면 3으로 일괄처리
37 | roomIdsByFailCount[3] = roomIdsByFailCount[3].concat(roomIds);
38 | }
39 | });
40 |
41 | const lifeDeductionRooms = [];
42 | const lifeDeductionMap = new Map();
43 |
44 | let afterLife = []; // 수명 깎아 준 후 습관방별 수명들 [{ roomId: 100, life: 1 }, ...]
45 | for (let i = 1; i <= 3; i++) {
46 | // 수명 깎아주기! - 3번 진행 (수명 1개깎이는 방 / 2개 깎이는 방 / 3개 깎이는 방)
47 | if (roomIdsByFailCount[i].length) {
48 | const updatedLife = await roomDB.updateLife(client, i, roomIdsByFailCount[i]); // { roomId: 100, life: 1 }
49 | updatedLife.map((o) => {
50 | if (o.life) {
51 | lifeDeductionRooms.push(o.roomId);
52 | lifeDeductionMap.set(o.roomId, i);
53 | }
54 | });
55 | afterLife = afterLife.concat(updatedLife);
56 | const slackMessage = `[Life Deduction] life: -${i} / Target Room: ${roomIdsByFailCount[i]}`;
57 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
58 | }
59 | }
60 |
61 | const failRoomIds = _.filter(afterLife, { life: 0 }).map((o) => o.roomId); // 수명 깎아주고 나서 {life: 0} 이면 폭파된 방
62 | const successRoomIds = _.difference(ongoingRoomIds, failRoomIds); // 살아남은 방들
63 | let completeRooms = [];
64 |
65 | if (successRoomIds.length) {
66 | completeRooms = await roomDB.setRoomsComplete(client, successRoomIds);
67 | }
68 | const completeRoomIds = completeRooms.map((o) => o.roomId);
69 | const lifeDeductionRoomIds = _.difference(_.difference(lifeDeductionRooms, completeRoomIds), failRoomIds);
70 | const completeOrFailRoomIds = completeRoomIds.concat(failRoomIds);
71 | let dialogUsers = [];
72 | if (completeOrFailRoomIds.length) {
73 | dialogUsers = await roomDB.getAllUsersByIds(client, completeOrFailRoomIds);
74 | }
75 | let insertDialogs = [];
76 | dialogUsers.map((o) => {
77 | insertDialogs.push(`(${o.userId}, ${o.roomId}, '${o.status}', '${today}')`);
78 | });
79 | if (insertDialogs.length) {
80 | await dialogDB.insertDialogs(client, insertDialogs);
81 | }
82 |
83 | let decreaseMessageUsers = [];
84 | if (lifeDeductionRoomIds.length) {
85 | decreaseMessageUsers = await roomDB.getAllUsersByIds(client, lifeDeductionRoomIds);
86 | }
87 | let failProfiles = {}; // 인증 안한 사용자 프로필 사진, key: roomId, value: profile 배열
88 | let decreaseCount = {}; // 인증 안한 사용자 수, key: roomId, value: decreaseCount
89 | let decreaseTimelines = [];
90 | for (let i = 0; i < decreaseMessageUsers.length; i++) {
91 | const { userId, roomId } = decreaseMessageUsers[i];
92 | if (!Object.keys(failProfiles).includes(roomId)) {
93 | let profiles = await roomDB.getFailProfiles(client, roomId);
94 | profiles = profiles.map((p) => p.profile).sort(() => Math.random() - 0.5);
95 | decreaseCount[roomId] = profiles.length;
96 | while (profiles.length < 2) {
97 | profiles.push(null);
98 | }
99 | failProfiles[roomId] = profiles;
100 | }
101 |
102 | decreaseTimelines.push(`('${userId}', '${roomId}', true, ${decreaseCount[roomId]}, '${failProfiles[roomId][0]}', '${failProfiles[roomId][1]}')`);
103 | }
104 |
105 | // 생명 감소시 Time Line Insert
106 | if (decreaseTimelines.length) {
107 | await lifeTimelineDB.addDecreaseTimelines(client, decreaseTimelines);
108 | }
109 |
110 | // 살아남은 방 없으면 return
111 | if (!successRoomIds.length) {
112 | return;
113 | }
114 |
115 | const survivedRoomIds = _.difference(successRoomIds, completeRoomIds);
116 | const ongoingEntries = await roomDB.getEntriesByRoomIds(client, survivedRoomIds); // 성공한 방들의 entry 불러오기
117 |
118 | // 추가해줄 records
119 | let insertRecords = [];
120 |
121 | // 생명 충전 lifeTimelines
122 | let fillTimelines = [];
123 |
124 | // 생명 충전해줄 RoomIds
125 | let fillLifeRoomIds = new Set();
126 |
127 | for (let i = 0; i < ongoingEntries.length; i++) {
128 | // insert할 record 값들 생성
129 | const entry = ongoingEntries[i];
130 | const day = dayjs(today).diff(dayjs(entry.startAt), 'day') + 1;
131 | const record = '(' + entry.entryId + ",'" + now.format('YYYY-MM-DD') + "'," + day + ')';
132 | insertRecords.push(record);
133 |
134 | if (termList.includes(day)) {
135 | fillLifeRoomIds.add(entry.roomId);
136 | fillTimelines.push(`('${entry.userId}', '${entry.roomId}', false, ${day})`);
137 | }
138 | }
139 |
140 | fillLifeRoomIds = Array.from(fillLifeRoomIds);
141 |
142 | if (insertRecords.length > 0) {
143 | await recordDB.insertRecords(client, insertRecords); // record 추가!
144 | }
145 | if (fillTimelines.length > 0) {
146 | await lifeTimelineDB.addFillTimelines(client, fillTimelines); // lifeTimeline 추가!
147 | }
148 | if (fillLifeRoomIds.length > 0) {
149 | await roomDB.fillLifeByRoomIds(client, fillLifeRoomIds); // 생명 충전
150 | // 생명 충전시, 습관 방의 분기가 변한 것이므로, entry 테이블의 term_new 값을 True로 업데이트
151 | await roomDB.updateTermNewByRoomIds(client, fillLifeRoomIds);
152 | }
153 |
154 | const slackMessage = `폭파된 방 목록: ${failRoomIds} \n 생명 충전 방 목록: ${fillLifeRoomIds} \n 살아남은 방 목록: ${survivedRoomIds}`;
155 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
156 | } catch (error) {
157 | console.log(error);
158 | const slackMessage = `[ERROR] ${error} ${JSON.stringify(error)}`;
159 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
160 | } finally {
161 | client.release();
162 | }
163 | };
164 |
165 | const sendRemind = async () => {
166 | let client;
167 | try {
168 | client = await db.connect();
169 |
170 | const scheduleCheck = await remindDB.insertRemind(client);
171 | if (!scheduleCheck.length) {
172 | return;
173 | }
174 |
175 | const now = dayjs().add(9, 'hour');
176 | const today = now.format('YYYY-MM-DD');
177 |
178 | const remindUsers = await recordDB.getPushRemindUsers(client, today);
179 | const slackInfo = [];
180 |
181 | if (remindUsers.length) {
182 | const messages = [];
183 |
184 | remindUsers.map((u) => {
185 | if (u.status == 'NONE' || u.status == 'CONSIDER') {
186 | const { title, body, category } = alarmMessage.REMIND_ALERT_NONE(u.roomName);
187 | messages.push(pushAlarm.getMessage(title, body, u.deviceToken, category, null, u.roomId));
188 | slackInfo.push(`[X]${u.userId}(${u.roomId})`);
189 | } else {
190 | const { title, body, category } = alarmMessage.REMIND_ALERT_DONE(u.roomName);
191 | messages.push(pushAlarm.getMessage(title, body, u.deviceToken, category, null, u.roomId));
192 | slackInfo.push(`[O]${u.userId}(${u.roomId})`);
193 | }
194 | });
195 |
196 | pushAlarm.sendMessages(null, null, messages);
197 |
198 | const slackMessage = `[REMIND SEND SUCCESS]: To ${slackInfo.length} users \n${slackInfo}`;
199 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
200 | return;
201 | }
202 |
203 | slackAPI.sendMessageToSlack('[REMIND NOT SENT]: 모든 방 습관인증 완료', slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
204 | return;
205 | } catch (error) {
206 | const slackMessage = `[ERROR] ${error} ${JSON.stringify(error)}`;
207 | slackAPI.sendMessageToSlack(slackMessage, slackAPI.DEV_WEB_HOOK_ERROR_MONITORING);
208 | } finally {
209 | client.release();
210 | }
211 | };
212 |
213 | module.exports = {
214 | checkLife,
215 | sendRemind,
216 | };
217 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "dayjs": "^1.10.7"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------