From 9393346e4009eae69449bc3b035d5ec8727f2b71 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sun, 7 Jan 2024 17:25:12 +0000 Subject: [PATCH 01/11] Docs: rooms.js basic format --- src/routes/docs/rooms.js | 96 ++++++++++++++++++++++++++++++++++ src/routes/docs/swaggerDocs.js | 6 +++ 2 files changed, 102 insertions(+) create mode 100644 src/routes/docs/rooms.js diff --git a/src/routes/docs/rooms.js b/src/routes/docs/rooms.js new file mode 100644 index 00000000..ecdffeb5 --- /dev/null +++ b/src/routes/docs/rooms.js @@ -0,0 +1,96 @@ +const tag = "rooms"; +const apiPrefix = "/rooms"; + +const roomsDocs = {}; + +roomsDocs[`${apiPrefix}/create`] = { + post: { + tags: [tag], + summary: "", + description: "", + requestBody: {}, + responses: {}, + }, +}; + +roomsDocs[`${apiPrefix}/publicInfo`] = { + get: { + tags: [tag], + summary: "", + description: "", + requestBody: {}, + responses: {}, + }, +}; + +roomsDocs[`${apiPrefix}/info`] = { + get: { + tags: [tag], + summary: "", + description: "", + requestBody: {}, + responses: {}, + }, +}; + +roomsDocs[`${apiPrefix}/join`] = { + post: { + tags: [tag], + summary: "", + description: "", + requestBody: {}, + responses: {}, + }, +}; + +roomsDocs[`${apiPrefix}/abort`] = { + post: { + tags: [tag], + summary: "", + description: "", + requestBody: {}, + responses: {}, + }, +}; + +roomsDocs[`${apiPrefix}/search`] = { + get: { + tags: [tag], + summary: "", + description: "", + requestBody: {}, + responses: {}, + }, +}; + +roomsDocs[`${apiPrefix}/searchByUser`] = { + post: { + tags: [tag], + summary: "", + description: "", + requestBody: {}, + responses: {}, + }, +}; + +roomsDocs[`${apiPrefix}/commitPayment`] = { + post: { + tags: [tag], + summary: "", + description: "", + requestBody: {}, + responses: {}, + }, +}; + +roomsDocs[`${apiPrefix}/commitSettlement`] = { + post: { + tags: [tag], + summary: "", + description: "", + requestBody: {}, + responses: {}, + }, +}; + +module.exports = roomsDocs; diff --git a/src/routes/docs/swaggerDocs.js b/src/routes/docs/swaggerDocs.js index 84f4e040..5b995c27 100644 --- a/src/routes/docs/swaggerDocs.js +++ b/src/routes/docs/swaggerDocs.js @@ -4,6 +4,7 @@ const logininfoDocs = require("./logininfo"); const locationsDocs = require("./locations"); const authDocs = require("./auth"); const usersDocs = require("./users"); +const roomsDocs = require("./rooms"); const { port, nodeEnv } = require("../../../loadenv"); const serverList = [ @@ -56,6 +57,10 @@ const swaggerDocs = { name: "users", description: "유저 계정 정보 수정 및 조회", }, + { + name: "rooms", + description: "방 생성/수정/삭제/조회 및 관리 지원", + }, ], consumes: ["application/json"], produces: ["application/json"], @@ -65,6 +70,7 @@ const swaggerDocs = { ...locationsDocs, ...usersDocs, ...authDocs, + ...roomsDocs, }, components: { schemas: { From 22a4e660d5adcf97953f567ab56c2cd65b6ff088 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sat, 13 Jan 2024 17:22:20 +0000 Subject: [PATCH 02/11] Docs: update utils.js --- src/routes/docs/utils.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/routes/docs/utils.js diff --git a/src/routes/docs/utils.js b/src/routes/docs/utils.js new file mode 100644 index 00000000..8181ceac --- /dev/null +++ b/src/routes/docs/utils.js @@ -0,0 +1,12 @@ +const objectIdPattern = `^[a-fA-F\d]{24}$`; +const roomsPattern = { + rooms: { + name: RegExp( + "^[A-Za-z0-9가-힣ㄱ-ㅎㅏ-ㅣ,.?! _~/#'\\\\@=\"\\-\\^()+*<>{}[\\]]{1,50}$" + ), + from: RegExp("^[A-Za-z0-9가-힣 -]{1,20}$"), + to: RegExp("^[A-Za-z0-9가-힣 -]{1,20}$"), + }, +}; + +module.exports = { objectIdPattern, roomsPattern }; From 8a181d8b1449ff7fdd7d9415a915658680e90fb8 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sat, 13 Jan 2024 17:23:10 +0000 Subject: [PATCH 03/11] Docs: add room and part schema --- src/routes/docs/roomsSchema.js | 74 ++++++++++++++++++++++++++++++++++ src/routes/docs/swaggerDocs.js | 3 ++ 2 files changed, 77 insertions(+) create mode 100644 src/routes/docs/roomsSchema.js diff --git a/src/routes/docs/roomsSchema.js b/src/routes/docs/roomsSchema.js new file mode 100644 index 00000000..0cbe79b5 --- /dev/null +++ b/src/routes/docs/roomsSchema.js @@ -0,0 +1,74 @@ +const { objectIdPattern, roomsPattern } = require("./utils"); + +const participantSchema = { + part: { + type: "object", + required: ["user", "settlementStatus", "readAt"], + properties: { + user: { + type: "string", + pattern: objectIdPattern, + }, + settlementStatus: { + type: "string", + enum: ["not-departed", "paid", "send-required", "sent"], + default: "not-departed", + }, + readAt: { + type: "string", + format: "date-time", + }, + }, + }, +}; + +const roomsSchema = { + room: { + type: "object", + required: [ + "name", + "from", + "to", + "time", + "madeat", + "settlementTotal", + "maxPartLength", + ], + properties: { + name: { + type: "string", + pattern: objectIdPattern, + }, + from: { + type: "string", + pattern: objectIdPattern, + }, + to: { + type: "string", + pattern: objectIdPattern, + }, + time: { + type: "string", + format: "date-time", + }, + part: { + type: "array", + items: participantSchema["part"], + }, + madeat: { + type: "string", + format: "date-time", + }, + settlementTotal: { + type: "integer", + default: 0, + }, + maxPartLength: { + type: "integer", + default: 4, + }, + }, + }, +}; + +module.exports = { roomsSchema, participantSchema }; diff --git a/src/routes/docs/swaggerDocs.js b/src/routes/docs/swaggerDocs.js index 5b995c27..35231aaa 100644 --- a/src/routes/docs/swaggerDocs.js +++ b/src/routes/docs/swaggerDocs.js @@ -1,4 +1,5 @@ const reportsSchema = require("./reportsSchema"); +const { participantSchema, roomsSchema } = require("./roomsSchema"); const reportsDocs = require("./reports"); const logininfoDocs = require("./logininfo"); const locationsDocs = require("./locations"); @@ -75,6 +76,8 @@ const swaggerDocs = { components: { schemas: { ...reportsSchema, + ...participantSchema, + ...roomsSchema, }, }, }; From e7746fd30b32cc4c651d07e83580694f03016fe2 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sat, 13 Jan 2024 17:23:50 +0000 Subject: [PATCH 04/11] Docs: add /create /info /publicinfo in rooms.js --- src/routes/docs/rooms.js | 194 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 182 insertions(+), 12 deletions(-) diff --git a/src/routes/docs/rooms.js b/src/routes/docs/rooms.js index ecdffeb5..56dcda9d 100644 --- a/src/routes/docs/rooms.js +++ b/src/routes/docs/rooms.js @@ -1,3 +1,5 @@ +const { objectIdPattern, roomsPattern } = require("./utils"); + const tag = "rooms"; const apiPrefix = "/rooms"; @@ -6,30 +8,198 @@ const roomsDocs = {}; roomsDocs[`${apiPrefix}/create`] = { post: { tags: [tag], - summary: "", - description: "", - requestBody: {}, - responses: {}, + summary: "방 생성", + description: `방을 생성합니다. 한 유저당 최대 5개의 진행중인 방에 참여할 수 있습니다.
`, + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + name: { + type: "string", + pattern: roomsPattern.rooms.name, + description: `방 이름
+ 1~50 글자로 구성되며 영어 대소문자, 숫자, 한글, 특정 특수기호("-", ",", ".", "?", "!", "_")만 가능`, + }, + from: { + type: "string", + pattern: roomsPattern.rooms.from, + description: "출발지 location Document의 ObjectId", + }, + to: { + type: "string", + pattern: roomsPattern.rooms.to, + description: "도착지 location Document의 ObjectId", + }, + time: { + type: "string", + format: "date-time", + description: "방 출발 시각. 현재 이후여야 함.", + }, + maxPartLength: { + type: "integer", + minimum: 2, + maximum: 4, + description: "방의 최대 인원 수", + }, + }, + }, + }, + }, + }, + responses: { + 200: { + description: "생성 완성된 방 목록", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/room", + }, + }, + }, + }, + 400: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + examples: { + "출발지와 도착지가 같음": { + value: { + error: "Room/create : locations are same", + }, + }, + "현재로부터 2주일보다 이후의 방을 생성": { + value: { + error: + "Room/create : cannot over 2 weeks on the basis of current Date", + }, + }, + "존재하지 않는 location Document를 입력": { + value: { + error: "Rooms/create : no corresponding locations", + }, + }, + "사용자가 참여하는 진행 중 방이 5개 이상": { + value: { + error: "Rooms/create : participating in too many rooms", + }, + }, + }, + }, + }, + }, + 500: { + description: "내부 서버 오류", + content: { + "text/html": { + example: "Rooms/create : internal server error", + }, + }, + }, + }, }, }; roomsDocs[`${apiPrefix}/publicInfo`] = { get: { tags: [tag], - summary: "", - description: "", - requestBody: {}, - responses: {}, + summary: "정산 정보를 제외한 방 세부 사항 반환 (로그인 필요 x)", + description: + "특정 id 방의 정산 정보를 제외한 세부사항을 반환합니다. 로그인을 하지 않아도 접근 가능합니다.", + parameters: [ + { + in: "query", + name: "id", + schema: { + type: "string", + pattern: objectIdPattern, + }, + description: "찾고 싶은 방의 Object id", + }, + ], + responses: { + 200: { + description: "방의 세부 정보가 담긴 room Object", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/room", + }, + }, + }, + }, + 404: { + description: "해당 id가 존재하지 않음", + content: { + "text/html": { + example: "Rooms/publicInfo : id does not exist", + }, + }, + }, + 500: { + description: "내부 서버 오류", + content: { + "text/html": { + example: "Rooms/publicInfo : internal server error", + }, + }, + }, + }, }, }; roomsDocs[`${apiPrefix}/info`] = { get: { tags: [tag], - summary: "", - description: "", - requestBody: {}, - responses: {}, + summary: "방 세부 사항 반환", + description: "유저가 참여한 방의 세부사항을 반환합니다.", + parameters: [ + { + in: "query", + name: "id", + schema: { + type: "string", + pattern: objectIdPattern, + }, + description: "찾고 싶은 방의 Object id", + }, + ], + responses: { + 200: { + description: "방의 세부 정보가 담긴 room Object", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/room", + }, + }, + }, + }, + 404: { + description: "해당 id가 존재하지 않음", + content: { + "text/html": { + example: "Rooms/info : id does not exist", + }, + }, + }, + 500: { + description: "내부 서버 오류", + content: { + "text/html": { + example: "Rooms/info : internal server error", + }, + }, + }, + }, }, }; From e77feef34b7f1dc7a83a09b357a98151011a202e Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sun, 28 Jan 2024 14:47:38 +0000 Subject: [PATCH 05/11] Merge branch 'dev' of https://github.com/sparcs-kaist/taxi-back into #135.8-docs-rooms --- .env.example | 7 + .github/workflows/test_ci.yml | 14 - .gitmodules | 4 - README.md | 1 - app.js | 9 +- loadenv.js | 2 +- package.json | 4 +- pnpm-lock.yaml | 146 +------- sampleGenerator | 1 - src/modules/stores/mongo.js | 27 +- src/routes/docs/chats.js | 513 +++++++++++++++++++++++++++ src/routes/docs/chats.md | 99 ------ src/routes/docs/locations.js | 4 +- src/routes/docs/logininfo.js | 3 + src/routes/docs/reports.js | 10 + src/routes/docs/reportsSchema.js | 4 +- src/routes/docs/swaggerDocs.js | 6 + src/routes/docs/users.js | 1 + src/sampleGenerator/.gitignore | 107 ++++++ src/sampleGenerator/README.md | 28 ++ src/sampleGenerator/index.js | 47 +++ src/sampleGenerator/loadenv.js | 13 + src/sampleGenerator/package.json | 17 + src/sampleGenerator/sampleData.json | 112 ++++++ src/sampleGenerator/src/testData.js | 199 +++++++++++ src/sampleGenerator/tools/dump.js | 20 ++ src/sampleGenerator/tools/restore.js | 23 ++ src/services/chats.js | 4 +- test/utils.js | 3 +- 29 files changed, 1153 insertions(+), 275 deletions(-) delete mode 100644 .gitmodules delete mode 160000 sampleGenerator create mode 100644 src/routes/docs/chats.js delete mode 100644 src/routes/docs/chats.md create mode 100644 src/sampleGenerator/.gitignore create mode 100644 src/sampleGenerator/README.md create mode 100644 src/sampleGenerator/index.js create mode 100644 src/sampleGenerator/loadenv.js create mode 100644 src/sampleGenerator/package.json create mode 100644 src/sampleGenerator/sampleData.json create mode 100644 src/sampleGenerator/src/testData.js create mode 100644 src/sampleGenerator/tools/dump.js create mode 100644 src/sampleGenerator/tools/restore.js diff --git a/.env.example b/.env.example index 769e0fa8..ea107da9 100644 --- a/.env.example +++ b/.env.example @@ -16,3 +16,10 @@ CORS_WHITELIST=[CORS 정책에서 허용하는 도메인의 목록(e.g. ["http:/ GOOGLE_APPLICATION_CREDENTIALS=[GOOGLE_APPLICATION_CREDENTIALS JSON] TEST_ACCOUNTS=[스팍스SSO로 로그인시 무조건 테스트로 로그인이 가능한 허용 아이디 목록] SLACK_REPORT_WEBHOOK_URL=[Slack 웹훅 URL들이 담긴 JSON] + +# optional environment variables for taxiSampleGenerator +SAMPLE_NUM_OF_ROOMS=[방의 개수] +SAMPLE_NUM_OF_CHATS=[각 방의 채팅 개수] +SAMPLE_MAXIMUM_INTERVAL_BETWEEN_CHATS=[채팅 간 최대 시간 간격(단위: 초, 실수도 가능)] +SAMPLE_OCCURENCE_OF_JOIN=[새로운 채팅이 입장 메세지일 확률(0 ~ 1 사이의 값)] +SAMPLE_OCCURENCE_OF_ABORT=[새로운 채팅이 퇴장 메세지일 확률(0 ~ 1 사이의 값)] \ No newline at end of file diff --git a/.github/workflows/test_ci.yml b/.github/workflows/test_ci.yml index 29f7f21b..2ccc5415 100644 --- a/.github/workflows/test_ci.yml +++ b/.github/workflows/test_ci.yml @@ -29,20 +29,6 @@ jobs: with: node-version: ${{ matrix.node-version }} cache: 'pnpm' - - id: submodule-local - name: Save local version of submodule - run: echo "ver=`cd sampleGenerator && git log --pretty="%h" -1 && cd ..`" >> $GITHUB_OUTPUT - - id: submodule-origin - name: Save origin version of submodule - run: echo "ver=`cd sampleGenerator && git log origin --pretty="%h" -1 && cd ..`" >> $GITHUB_OUTPUT - - name: Check submodule version - if: ${{ steps.submodule-local.outputs.ver != steps.submodule-origin.outputs.ver }} - uses: actions/github-script@v3 - with: - script: | - core.setFailed('Please update submodule to the latest version by using \"git submodule update --remote\"') - - name: Install sampleGenerator dependencies from package-lock.json - run: cd sampleGenerator && pnpm i --force --frozen-lockfile && cd .. - name: Install taxi-back dependencies from package-lock.json run: pnpm i --force --frozen-lockfile - name: Run unit tests diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index f15db4d8..00000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "sampleGenerator"] - path = sampleGenerator - url = https://github.com/sparcs-kaist/taxiSampleGenerator - branch = main diff --git a/README.md b/README.md index 26cda418..9a335131 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,3 @@ See [contributors](https://github.com/sparcs-kaist/taxi-front/graphs/contributor - app : https://github.com/sparcs-kaist/taxi-app - docker : https://github.com/sparcs-kaist/taxi-docker - figma : https://www.figma.com/file/li34hP1oStJAzLNjcG5KjN/SPARCS-Taxi?node-id=0%3A1 - - taxiSampleGenerator : https://github.com/sparcs-kaist/taxiSampleGenerator diff --git a/app.js b/app.js index f8c24842..a26c4b46 100644 --- a/app.js +++ b/app.js @@ -1,7 +1,12 @@ // 모듈 require const express = require("express"); const http = require("http"); -const { nodeEnv, port: httpPort, eventConfig } = require("./loadenv"); +const { + nodeEnv, + port: httpPort, + eventConfig, + mongo: mongoUrl, +} = require("./loadenv"); const logger = require("./src/modules/logger"); const { connectDatabase } = require("./src/modules/stores/mongo"); const { startSocketServer } = require("./src/modules/socket"); @@ -13,7 +18,7 @@ require("./src/modules/fcm").initializeApp(); const app = express(); // 데이터베이스 연결 -connectDatabase(); +connectDatabase(mongoUrl); // [Middleware] request body 파싱 app.use(express.urlencoded({ extended: false })); diff --git a/loadenv.js b/loadenv.js index f7224601..789e21db 100644 --- a/loadenv.js +++ b/loadenv.js @@ -43,5 +43,5 @@ module.exports = { slackWebhookUrl: { report: process.env.SLACK_REPORT_WEBHOOK_URL || "", // optional }, - eventConfig: process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG), + eventConfig: process.env.EVENT_CONFIG && JSON.parse(process.env.EVENT_CONFIG), // optional }; diff --git a/package.json b/package.json index 307baba9..7c0c7248 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "serve": "cross-env TZ='Asia/Seoul' NODE_ENV=production node app.js", "runscript": "cross-env TZ='Asia/Seoul' NODE_ENV=production node", "lint": "npx eslint --fix .", - "sample": "cd sampleGenerator && npm start && cd .." + "sample": "cd src/sampleGenerator && npm start && cd .." }, "engines": { "node": ">=18.0.0", @@ -60,7 +60,7 @@ "eslint": "^8.22.0", "eslint-plugin-mocha": "^10.1.0", "mocha": "^10.2.0", - "mongodb": "^6.2.0", + "mongodb": "^4.1.0", "nodemon": "^3.0.1", "supertest": "^6.2.4" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 545f1c18..d7206c1e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -34,7 +34,7 @@ dependencies: version: 2.2.0 connect-mongo: specifier: ^4.6.0 - version: 4.6.0(express-session@1.17.3)(mongodb@6.2.0) + version: 4.6.0(express-session@1.17.3)(mongodb@4.17.1) connect-redis: specifier: ^6.1.3 version: 6.1.3 @@ -122,8 +122,8 @@ devDependencies: specifier: ^10.2.0 version: 10.2.0 mongodb: - specifier: ^6.2.0 - version: 6.2.0 + specifier: ^4.1.0 + version: 4.17.1 nodemon: specifier: ^3.0.1 version: 3.0.1 @@ -228,7 +228,6 @@ packages: '@aws-crypto/util': 3.0.0 '@aws-sdk/types': 3.425.0 tslib: 1.14.1 - dev: false optional: true /@aws-crypto/ie11-detection@3.0.0: @@ -236,7 +235,6 @@ packages: requiresBuild: true dependencies: tslib: 1.14.1 - dev: false optional: true /@aws-crypto/sha256-browser@3.0.0: @@ -251,7 +249,6 @@ packages: '@aws-sdk/util-locate-window': 3.310.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 - dev: false optional: true /@aws-crypto/sha256-js@3.0.0: @@ -261,7 +258,6 @@ packages: '@aws-crypto/util': 3.0.0 '@aws-sdk/types': 3.425.0 tslib: 1.14.1 - dev: false optional: true /@aws-crypto/supports-web-crypto@3.0.0: @@ -269,7 +265,6 @@ packages: requiresBuild: true dependencies: tslib: 1.14.1 - dev: false optional: true /@aws-crypto/util@3.0.0: @@ -279,7 +274,6 @@ packages: '@aws-sdk/types': 3.425.0 '@aws-sdk/util-utf8-browser': 3.259.0 tslib: 1.14.1 - dev: false optional: true /@aws-sdk/client-cognito-identity@3.427.0: @@ -326,7 +320,6 @@ packages: tslib: 2.6.2 transitivePeerDependencies: - aws-crt - dev: false optional: true /@aws-sdk/client-sso@3.427.0: @@ -370,7 +363,6 @@ packages: tslib: 2.6.2 transitivePeerDependencies: - aws-crt - dev: false optional: true /@aws-sdk/client-sts@3.427.0: @@ -418,7 +410,6 @@ packages: tslib: 2.6.2 transitivePeerDependencies: - aws-crt - dev: false optional: true /@aws-sdk/credential-provider-cognito-identity@3.427.0: @@ -433,7 +424,6 @@ packages: tslib: 2.6.2 transitivePeerDependencies: - aws-crt - dev: false optional: true /@aws-sdk/credential-provider-env@3.425.0: @@ -445,7 +435,6 @@ packages: '@smithy/property-provider': 2.0.12 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/credential-provider-http@3.425.0: @@ -460,7 +449,6 @@ packages: '@smithy/protocol-http': 3.0.7 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/credential-provider-ini@3.427.0: @@ -480,7 +468,6 @@ packages: tslib: 2.6.2 transitivePeerDependencies: - aws-crt - dev: false optional: true /@aws-sdk/credential-provider-node@3.427.0: @@ -501,7 +488,6 @@ packages: tslib: 2.6.2 transitivePeerDependencies: - aws-crt - dev: false optional: true /@aws-sdk/credential-provider-process@3.425.0: @@ -514,7 +500,6 @@ packages: '@smithy/shared-ini-file-loader': 2.2.0 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/credential-provider-sso@3.427.0: @@ -531,7 +516,6 @@ packages: tslib: 2.6.2 transitivePeerDependencies: - aws-crt - dev: false optional: true /@aws-sdk/credential-provider-web-identity@3.425.0: @@ -543,7 +527,6 @@ packages: '@smithy/property-provider': 2.0.12 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/credential-providers@3.427.0: @@ -569,7 +552,6 @@ packages: tslib: 2.6.2 transitivePeerDependencies: - aws-crt - dev: false optional: true /@aws-sdk/middleware-host-header@3.425.0: @@ -581,7 +563,6 @@ packages: '@smithy/protocol-http': 3.0.7 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/middleware-logger@3.425.0: @@ -592,7 +573,6 @@ packages: '@aws-sdk/types': 3.425.0 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/middleware-recursion-detection@3.425.0: @@ -604,7 +584,6 @@ packages: '@smithy/protocol-http': 3.0.7 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/middleware-sdk-sts@3.425.0: @@ -616,7 +595,6 @@ packages: '@aws-sdk/types': 3.425.0 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/middleware-signing@3.425.0: @@ -631,7 +609,6 @@ packages: '@smithy/types': 2.3.5 '@smithy/util-middleware': 2.0.4 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/middleware-user-agent@3.427.0: @@ -644,7 +621,6 @@ packages: '@smithy/protocol-http': 3.0.7 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/region-config-resolver@3.425.0: @@ -657,7 +633,6 @@ packages: '@smithy/util-config-provider': 2.0.0 '@smithy/util-middleware': 2.0.4 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/token-providers@3.427.0: @@ -702,7 +677,6 @@ packages: tslib: 2.6.2 transitivePeerDependencies: - aws-crt - dev: false optional: true /@aws-sdk/types@3.425.0: @@ -712,7 +686,6 @@ packages: dependencies: '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/util-endpoints@3.427.0: @@ -723,7 +696,6 @@ packages: '@aws-sdk/types': 3.425.0 '@smithy/node-config-provider': 2.1.1 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/util-locate-window@3.310.0: @@ -732,7 +704,6 @@ packages: requiresBuild: true dependencies: tslib: 2.6.2 - dev: false optional: true /@aws-sdk/util-user-agent-browser@3.425.0: @@ -743,7 +714,6 @@ packages: '@smithy/types': 2.3.5 bowser: 2.11.0 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/util-user-agent-node@3.425.0: @@ -760,7 +730,6 @@ packages: '@smithy/node-config-provider': 2.1.1 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@aws-sdk/util-utf8-browser@3.259.0: @@ -768,7 +737,6 @@ packages: requiresBuild: true dependencies: tslib: 2.6.2 - dev: false optional: true /@babel/code-frame@7.22.5: @@ -2276,7 +2244,7 @@ packages: resolution: {integrity: sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==} dependencies: '@firebase/util': 1.9.3 - tslib: 2.6.1 + tslib: 2.6.2 dev: false /@firebase/database-compat@0.3.4: @@ -2287,7 +2255,7 @@ packages: '@firebase/database-types': 0.10.4 '@firebase/logger': 0.4.0 '@firebase/util': 1.9.3 - tslib: 2.6.1 + tslib: 2.6.2 dev: false /@firebase/database-types@0.10.4: @@ -2305,19 +2273,19 @@ packages: '@firebase/logger': 0.4.0 '@firebase/util': 1.9.3 faye-websocket: 0.11.4 - tslib: 2.6.1 + tslib: 2.6.2 dev: false /@firebase/logger@0.4.0: resolution: {integrity: sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==} dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false /@firebase/util@1.9.3: resolution: {integrity: sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==} dependencies: - tslib: 2.6.1 + tslib: 2.6.2 dev: false /@floating-ui/core@1.4.1: @@ -2533,6 +2501,7 @@ packages: requiresBuild: true dependencies: sparse-bitfield: 3.0.3 + optional: true /@nodelib/fs.scandir@2.1.5: resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -2787,7 +2756,6 @@ packages: dependencies: '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/config-resolver@2.0.14: @@ -2800,7 +2768,6 @@ packages: '@smithy/util-config-provider': 2.0.0 '@smithy/util-middleware': 2.0.4 tslib: 2.6.2 - dev: false optional: true /@smithy/credential-provider-imds@2.0.16: @@ -2813,7 +2780,6 @@ packages: '@smithy/types': 2.3.5 '@smithy/url-parser': 2.0.11 tslib: 2.6.2 - dev: false optional: true /@smithy/eventstream-codec@2.0.11: @@ -2824,7 +2790,6 @@ packages: '@smithy/types': 2.3.5 '@smithy/util-hex-encoding': 2.0.0 tslib: 2.6.2 - dev: false optional: true /@smithy/fetch-http-handler@2.2.2: @@ -2836,7 +2801,6 @@ packages: '@smithy/types': 2.3.5 '@smithy/util-base64': 2.0.0 tslib: 2.6.2 - dev: false optional: true /@smithy/hash-node@2.0.11: @@ -2848,7 +2812,6 @@ packages: '@smithy/util-buffer-from': 2.0.0 '@smithy/util-utf8': 2.0.0 tslib: 2.6.2 - dev: false optional: true /@smithy/invalid-dependency@2.0.11: @@ -2857,7 +2820,6 @@ packages: dependencies: '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/is-array-buffer@2.0.0: @@ -2866,7 +2828,6 @@ packages: requiresBuild: true dependencies: tslib: 2.6.2 - dev: false optional: true /@smithy/middleware-content-length@2.0.13: @@ -2877,7 +2838,6 @@ packages: '@smithy/protocol-http': 3.0.7 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/middleware-endpoint@2.0.11: @@ -2890,7 +2850,6 @@ packages: '@smithy/url-parser': 2.0.11 '@smithy/util-middleware': 2.0.4 tslib: 2.6.2 - dev: false optional: true /@smithy/middleware-retry@2.0.16: @@ -2906,7 +2865,6 @@ packages: '@smithy/util-retry': 2.0.4 tslib: 2.6.2 uuid: 8.3.2 - dev: false optional: true /@smithy/middleware-serde@2.0.11: @@ -2916,7 +2874,6 @@ packages: dependencies: '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/middleware-stack@2.0.5: @@ -2926,7 +2883,6 @@ packages: dependencies: '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/node-config-provider@2.1.1: @@ -2938,7 +2894,6 @@ packages: '@smithy/shared-ini-file-loader': 2.2.0 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/node-http-handler@2.1.7: @@ -2951,7 +2906,6 @@ packages: '@smithy/querystring-builder': 2.0.11 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/property-provider@2.0.12: @@ -2961,7 +2915,6 @@ packages: dependencies: '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/protocol-http@3.0.7: @@ -2971,7 +2924,6 @@ packages: dependencies: '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/querystring-builder@2.0.11: @@ -2982,7 +2934,6 @@ packages: '@smithy/types': 2.3.5 '@smithy/util-uri-escape': 2.0.0 tslib: 2.6.2 - dev: false optional: true /@smithy/querystring-parser@2.0.11: @@ -2992,7 +2943,6 @@ packages: dependencies: '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/service-error-classification@2.0.4: @@ -3001,7 +2951,6 @@ packages: requiresBuild: true dependencies: '@smithy/types': 2.3.5 - dev: false optional: true /@smithy/shared-ini-file-loader@2.2.0: @@ -3011,7 +2960,6 @@ packages: dependencies: '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/signature-v4@2.0.11: @@ -3027,7 +2975,6 @@ packages: '@smithy/util-uri-escape': 2.0.0 '@smithy/util-utf8': 2.0.0 tslib: 2.6.2 - dev: false optional: true /@smithy/smithy-client@2.1.10: @@ -3039,7 +2986,6 @@ packages: '@smithy/types': 2.3.5 '@smithy/util-stream': 2.0.15 tslib: 2.6.2 - dev: false optional: true /@smithy/types@2.3.5: @@ -3048,7 +2994,6 @@ packages: requiresBuild: true dependencies: tslib: 2.6.2 - dev: false optional: true /@smithy/url-parser@2.0.11: @@ -3058,7 +3003,6 @@ packages: '@smithy/querystring-parser': 2.0.11 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/util-base64@2.0.0: @@ -3068,7 +3012,6 @@ packages: dependencies: '@smithy/util-buffer-from': 2.0.0 tslib: 2.6.2 - dev: false optional: true /@smithy/util-body-length-browser@2.0.0: @@ -3076,7 +3019,6 @@ packages: requiresBuild: true dependencies: tslib: 2.6.2 - dev: false optional: true /@smithy/util-body-length-node@2.1.0: @@ -3085,7 +3027,6 @@ packages: requiresBuild: true dependencies: tslib: 2.6.2 - dev: false optional: true /@smithy/util-buffer-from@2.0.0: @@ -3095,7 +3036,6 @@ packages: dependencies: '@smithy/is-array-buffer': 2.0.0 tslib: 2.6.2 - dev: false optional: true /@smithy/util-config-provider@2.0.0: @@ -3104,7 +3044,6 @@ packages: requiresBuild: true dependencies: tslib: 2.6.2 - dev: false optional: true /@smithy/util-defaults-mode-browser@2.0.14: @@ -3117,7 +3056,6 @@ packages: '@smithy/types': 2.3.5 bowser: 2.11.0 tslib: 2.6.2 - dev: false optional: true /@smithy/util-defaults-mode-node@2.0.18: @@ -3132,7 +3070,6 @@ packages: '@smithy/smithy-client': 2.1.10 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/util-hex-encoding@2.0.0: @@ -3141,7 +3078,6 @@ packages: requiresBuild: true dependencies: tslib: 2.6.2 - dev: false optional: true /@smithy/util-middleware@2.0.4: @@ -3151,7 +3087,6 @@ packages: dependencies: '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/util-retry@2.0.4: @@ -3162,7 +3097,6 @@ packages: '@smithy/service-error-classification': 2.0.4 '@smithy/types': 2.3.5 tslib: 2.6.2 - dev: false optional: true /@smithy/util-stream@2.0.15: @@ -3178,7 +3112,6 @@ packages: '@smithy/util-hex-encoding': 2.0.0 '@smithy/util-utf8': 2.0.0 tslib: 2.6.2 - dev: false optional: true /@smithy/util-uri-escape@2.0.0: @@ -3187,7 +3120,6 @@ packages: requiresBuild: true dependencies: tslib: 2.6.2 - dev: false optional: true /@smithy/util-utf8@2.0.0: @@ -3197,7 +3129,6 @@ packages: dependencies: '@smithy/util-buffer-from': 2.0.0 tslib: 2.6.2 - dev: false optional: true /@socket.io/component-emitter@3.1.0: @@ -4211,7 +4142,6 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: false /base64id@2.0.0: resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==} @@ -4270,7 +4200,6 @@ packages: /bowser@2.11.0: resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} requiresBuild: true - dev: false optional: true /brace-expansion@1.1.11: @@ -4310,11 +4239,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: buffer: 5.7.1 - dev: false - - /bson@6.2.0: - resolution: {integrity: sha512-ID1cI+7bazPDyL9wYy9GaQ8gEEohWvcUl/Yf0dIdutJxnmInEEyCsb4awy/OiBfall7zBA179Pahi3vCdFze3Q==} - engines: {node: '>=16.20.1'} /buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -4337,7 +4261,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.1.13 - dev: false /builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} @@ -4570,7 +4493,7 @@ packages: /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - /connect-mongo@4.6.0(express-session@1.17.3)(mongodb@6.2.0): + /connect-mongo@4.6.0(express-session@1.17.3)(mongodb@4.17.1): resolution: {integrity: sha512-8new4Z7NLP3CGP65Aw6ls3xDBeKVvHRSh39CXuDZTQsvpeeU9oNMzfFgvqmHqZ6gWpxIl663RyoVEmCAGf1yOg==} engines: {node: '>=10'} peerDependencies: @@ -4580,7 +4503,7 @@ packages: debug: 4.3.4 express-session: 1.17.3 kruptein: 3.0.6 - mongodb: 6.2.0 + mongodb: 4.17.1 transitivePeerDependencies: - supports-color dev: false @@ -5287,7 +5210,6 @@ packages: requiresBuild: true dependencies: strnum: 1.0.5 - dev: false optional: true /fast-xml-parser@4.2.7: @@ -5795,7 +5717,6 @@ packages: /ieee754@1.1.13: resolution: {integrity: sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==} - dev: false /ignore-by-default@1.0.1: resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==} @@ -5827,7 +5748,6 @@ packages: /ip@2.0.0: resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==} - dev: false /ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} @@ -6429,6 +6349,7 @@ packages: /memory-pager@1.5.0: resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} requiresBuild: true + optional: true /merge-descriptors@1.0.1: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} @@ -6577,38 +6498,6 @@ packages: '@mongodb-js/saslprep': 1.1.0 transitivePeerDependencies: - aws-crt - dev: false - - /mongodb@6.2.0: - resolution: {integrity: sha512-d7OSuGjGWDZ5usZPqfvb36laQ9CPhnWkAGHT61x5P95p/8nMVeH8asloMwW6GcYFeB0Vj4CB/1wOTDG2RA9BFA==} - engines: {node: '>=16.20.1'} - peerDependencies: - '@aws-sdk/credential-providers': ^3.188.0 - '@mongodb-js/zstd': ^1.1.0 - gcp-metadata: ^5.2.0 - kerberos: ^2.0.1 - mongodb-client-encryption: '>=6.0.0 <7' - snappy: ^7.2.2 - socks: ^2.7.1 - peerDependenciesMeta: - '@aws-sdk/credential-providers': - optional: true - '@mongodb-js/zstd': - optional: true - gcp-metadata: - optional: true - kerberos: - optional: true - mongodb-client-encryption: - optional: true - snappy: - optional: true - socks: - optional: true - dependencies: - '@mongodb-js/saslprep': 1.1.0 - bson: 6.2.0 - mongodb-connection-string-url: 2.6.0 /mongoose@6.12.0: resolution: {integrity: sha512-sd/q83C6TBRPBrrD2A/POSbA/exbCFM2WOuY7Lf2JuIJFlHFG39zYSDTTAEiYlzIfahNOLmXPxBGFxdAch41Mw==} @@ -7822,7 +7711,6 @@ packages: /smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} - dev: false /socket.io-adapter@2.5.2: resolution: {integrity: sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==} @@ -7866,7 +7754,6 @@ packages: dependencies: ip: 2.0.0 smart-buffer: 4.2.0 - dev: false /source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -7895,6 +7782,7 @@ packages: requiresBuild: true dependencies: memory-pager: 1.5.0 + optional: true /stack-trace@0.0.10: resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==} @@ -7946,7 +7834,6 @@ packages: /strnum@1.0.5: resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} requiresBuild: true - dev: false optional: true /stubs@3.0.0: @@ -8175,16 +8062,10 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} requiresBuild: true - dev: false optional: true - /tslib@2.6.1: - resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==} - dev: false - /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - dev: false /type-check@0.3.2: resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} @@ -8359,7 +8240,6 @@ packages: /uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true - dev: false /uuid@9.0.0: resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==} diff --git a/sampleGenerator b/sampleGenerator deleted file mode 160000 index bd4329c1..00000000 --- a/sampleGenerator +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bd4329c15405a09c94e7b78e19ff296b4c2d0fb3 diff --git a/src/modules/stores/mongo.js b/src/modules/stores/mongo.js index f05f266a..695845fc 100755 --- a/src/modules/stores/mongo.js +++ b/src/modules/stores/mongo.js @@ -1,7 +1,6 @@ const mongoose = require("mongoose"); const Schema = mongoose.Schema; -const { mongo: mongoUrl } = require("../../../loadenv"); const logger = require("../logger"); const userSchema = Schema({ @@ -184,23 +183,27 @@ database.on("error", function (err) { logger.error("데이터베이스 연결 에러 발생: " + err); mongoose.disconnect(); }); -database.on("disconnected", function () { - // 데이터베이스 연결이 끊어지면 5초 후 재연결을 시도합니다. - logger.error("데이터베이스와 연결이 끊어졌습니다!"); - setTimeout(() => { - mongoose.connect(mongoUrl, { - useNewUrlParser: true, - useUnifiedTopology: true, - }); - }, 5000); -}); -const connectDatabase = () => +const connectDatabase = (mongoUrl) => { + database.on("disconnected", function () { + // 데이터베이스 연결이 끊어지면 5초 후 재연결을 시도합니다. + logger.error("데이터베이스와 연결이 끊어졌습니다!"); + setTimeout(() => { + mongoose.connect(mongoUrl, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + }, 5000); + }); + mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true, }); + return database; +}; + module.exports = { connectDatabase, userModel: mongoose.model("User", userSchema), diff --git a/src/routes/docs/chats.js b/src/routes/docs/chats.js new file mode 100644 index 00000000..97fc1352 --- /dev/null +++ b/src/routes/docs/chats.js @@ -0,0 +1,513 @@ +const { objectIdPattern } = require("./utils"); + +const tag = "chats"; +const apiPrefix = "/chats"; + +const chatsDocs = {}; +chatsDocs[`${apiPrefix}`] = { + post: { + tags: [tag], + summary: "가장 최근 채팅 가져오기", + description: "가장 최근에 도착한 60개의 채팅을 가져옵니다.", + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + description: "채팅을 보내는 방의 id", + }, + }, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + result: { + type: "boolean", + value: true, + }, + }, + }, + }, + }, + }, + 403: { + content: { + "text/html": { + examples: { + "소켓 연결 오류": { value: "Chat/ : socket did not connected" }, + "유저가 방에 참여하지 않음": { + value: "Chat/ : user did not participated in the room", + }, + }, + }, + }, + }, + 500: { + content: { + "text/html": { + example: "Chat/ : internal server error", + }, + }, + }, + }, + }, +}; + +chatsDocs[`${apiPrefix}/load/before`] = { + post: { + tags: [tag], + summary: "특정 시점 이전의 채팅 가져오기", + description: "lastMsgDate 이전에 도착한 60개의 채팅을 가져옵니다.", + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + description: "채팅을 보내는 방의 id", + }, + lastMsgDate: { + type: "string", + format: "date-time", + description: "이전 채팅을 가져올 특정 시점", + }, + }, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + result: { + type: "boolean", + value: true, + }, + }, + }, + }, + }, + }, + 403: { + content: { + "text/html": { + examples: { + "소켓 연결 오류": { + value: "Chat/load/before : socket did not connected", + }, + "유저가 방에 참여하지 않음": { + value: + "Chat/load/before : user did not participated in the room", + }, + }, + }, + }, + }, + 500: { + content: { + "text/html": { + example: "Chat/load/before : internal server error", + }, + }, + }, + }, + }, +}; + +chatsDocs[`${apiPrefix}/load/after`] = { + post: { + tags: [tag], + summary: "특정 시점 이후 채팅 가져오기", + description: "lastMsgDate 이후에 도착한 60개의 채팅을 가져옵니다.", + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + description: "채팅을 보내는 방의 id", + }, + lastMsgDate: { + type: "string", + format: "date-time", + description: "이전 채팅을 가져올 특정 시점", + }, + }, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + result: { + type: "boolean", + value: true, + }, + }, + }, + }, + }, + }, + 403: { + content: { + "text/html": { + examples: { + "소켓 연결 오류": { + value: "Chat/load/after : socket did not connected", + }, + "유저가 방에 참여하지 않음": { + value: + "Chat/load/after : user did not participated in the room", + }, + }, + }, + }, + }, + 500: { + content: { + "text/html": { + example: "Chat/load/after : internal server error", + }, + }, + }, + }, + }, +}; + +chatsDocs[`${apiPrefix}/send`] = { + post: { + tags: [tag], + summary: "채팅 요청 처리", + description: `채팅 요청을 처리합니다.
+ socker 통신을 통하여 같은 방에 있는 user들에게 이 채팅을 전송합니다.
+
+ 채팅 기록은 아래와 같이 구성됩니다.
+ + Chat { + roomId: ObjectId, //방의 objectId + type: String, // 메시지 종류 ("text": 일반 메시지, "s3img": S3에 업로드된 이미지, "in": 입장 메시지, "out": 퇴장 메시지, "payment": 결제 메시지, "settlement": 정산 완료 메시지, "account": 계좌 전송 메시지) + authorId: ObejctId, //작성자의 objectId + content: String, // 메시지 내용 (메시지 종류에 따라 포맷이 상이함) + time: String(ISO 8601), // ex) 2024-01-08T01:52:00.000Z + isValid: Boolean, // 클라이언트가 보낸 메시지가 유효한 지 여부. 클라이언트가 이미지를 업로드했을 때, 해당 이미지가 제대로 업로드됐는지 확인하기 전까지 이미지를 보여주지 않기 위해 사용됨. + } + `, + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + description: "채팅을 보내는 방의 id", + }, + type: { + type: "string", + enum: [ + "text", + "s3img", + "in", + "out", + "payment", + "settlement", + "account", + "departure", + "arrival", + ], + description: `채팅 메시지의 유형
+ 일반 text의 경우 *text*, 사진의 경우 *s3img*
+ 기타 종류의 채팅의 경우(입장, 퇴장 메시지 등) 정해진 type의 채팅을 사용`, + }, + content: { + type: "string", + example: "안녕하세요~!", + description: "채팅 메세지의 본문", + }, + }, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + result: { + type: "boolean", + value: true, + }, + }, + }, + }, + }, + }, + 403: { + content: { + "text/html": { + examples: { + "소켓 연결 오류": { + value: "Chat/send : socket did not connected", + }, + "유저가 방에 참여하지 않음": { + value: "Chat/send : user did not participated in the room", + }, + }, + }, + }, + }, + 500: { + content: { + "text/html": { + example: "Chat/send : internal server error", + }, + }, + }, + }, + }, +}; + +chatsDocs[`${apiPrefix}/read`] = { + post: { + tags: [tag], + summary: "채팅 읽은 시각 업데이트 요청", + description: `채팅 읽은 시각의 업데이트 요청을 처리합니다.
+ socket 통신을 통하여 같은 방에 있는 user들에게 업데이트를 요청합니다.`, + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + description: "채팅을 보내는 방의 id", + }, + }, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + result: { + type: "boolean", + value: true, + }, + }, + }, + }, + }, + }, + 403: { + content: { + "text/html": { + example: "Chat/read : socket did not connected", + }, + }, + }, + 404: { + content: { + "text/html": { + example: "Chat/read : cannot find room info", + }, + }, + }, + 500: { + content: { + "text/html": { + examples: { + "소켓 이벤트 전송 오류": { + value: "Chat/read : failed to emit socket events", + }, + "기타 서버 오류": { + value: "Chat/read : internal server error", + }, + }, + }, + }, + }, + }, + }, +}; + +chatsDocs[`${apiPrefix}/uploadChatImg/getPUrl`] = { + post: { + tags: [tag], + summary: "채팅 이미지를 업로드할 수 있는 Presigned-url을 발급", + description: `채팅 이미지를 업로드 하기 위한 Presigned-url을 발급합니다.
+ 이미지 전송을 위해 \`s3img\` 형식의 chat document를 생성 후 저장하며,
+ presigned-url은 aws S3 api를 통해 생성됩니다.`, + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + description: "채팅 이미지를 보내는 방의 id", + }, + type: { + type: "string", + enum: ["image/png", "image/jpg", "image/jpeg"], + description: "채팅 이미지의 파일 형식", + }, + }, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + id: { + type: "string", + pattern: objectIdPattern, + description: "생성된 chat Document의 object id", + }, + url: { + type: "string", + example: "https://s3.{region}.amazonaws.com/{bucket-name}", + description: "taxi s3 url 주소", + }, + fields: { + type: "object", + properties: { + bucket: { + type: "string", + example: "bucket-name", + }, + "Content-Type": { + type: "string", + enum: ["image/png", "image/jpg", "image/jpeg"], + }, + key: { + type: "string", + pattern: `^chat-img/[a-fA-F\d]{24}$`, + }, + }, + description: "image의 key, type, bucket와 같은 정보", + }, + }, + }, + }, + }, + }, + 403: { + content: { + "text/html": { + example: "Chat/uploadChatImg/getPUrl : did not joined the chatting", + }, + }, + }, + 500: { + content: { + "text/html": { + example: "Chat/uploadChatImg/getPUrl : internal server error", + }, + }, + }, + }, + }, +}; + +chatsDocs[`${apiPrefix}/uploadChatImg/done`] = { + post: { + tags: [tag], + summary: "채팅 이미지 업로드 완료 여부 확인", + description: `채팅 이미지가 제대로 업로드 되었는지 확인합니다.
+ 이미지가 제대로 업로드 되었다면, socket 통신을 통해 채팅 이미지를 전송합니다.
+ 이때 채팅의 \`content\`에는 s3에 저장된 url을 나타내는 채팅의 object id를 넣어줍니다.`, + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + description: "채팅 이미지를 보내는 방의 id", + }, + }, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + result: { + type: "boolean", + value: true, + }, + }, + }, + }, + }, + }, + 404: { + content: { + "text/html": { + example: "Chat/uploadChatImg/done : no corresponding chat", + }, + }, + }, + 500: { + content: { + "text/html": { + example: "Chat/uploadChatImg/getPUrl : internal server error", + }, + }, + }, + }, + }, +}; + +module.exports = chatsDocs; diff --git a/src/routes/docs/chats.md b/src/routes/docs/chats.md deleted file mode 100644 index 307d2def..00000000 --- a/src/routes/docs/chats.md +++ /dev/null @@ -1,99 +0,0 @@ -## `chats`: 채팅 시 발생하는 이벤트 정리 - -Taxi의 채팅 기능은 Socket.IO 라이브러리를 이용해 구현되어 있습니다. -클라이언트에서의 일반적인 Socket.IO 사용법은 [공식 문서](https://socket.io/docs/v4/client-socket-instance/)를 참조해주세요. -아래와 같은 채팅 이벤트들이 구현되어 있습니다. - -- `chats-join` -- `chats-receive` -- `chats-send` -- `chats-load` -- `chats-disconnected` (삭제해야 함) - -서버로부터 받은 모든 채팅 기록은 아래와 같은 자료형으로 구성되어 있습니다. - -```javascript -Chat { - roomId: ObjectId, //방의 objectId - type: String, // 메시지 종류("text": 일반 메시지, "in": 입장 메시지, "out": 퇴장 메시지, "s3img": S3에 업로드된 이미지, "payment": 결제 메시지, "settlement": 정산 완료 메시지) - authorId: ObejctId, //작성자의 objectId - content: String, // 메시지 내용(메시지 종류에 따라 포맷이 상이하며, 하단 참조) - time: String(ISO 8601), // ex) '2022-01-12T13:58:20.180Z' - isValid: Boolean, // 클라이언트가 보낸 메시지가 유효한 지 여부. 클라이언트가 이미지를 업로드했을 때, 해당 이미지가 제대로 업로드됐는지 확인하기 전까지 이미지를 보여주지 않기 위해 사용됨. -} -``` - -### 1. `chats-join` - -방에 새 사용자가 참여할 때 이 이벤트를 발생시키세요. -필요한 인자는 `roomId`입니다. - -- `roomId`: 방의 ObjectID (`String`), Socket.IO 서버와 연결을 시도할 때 사용자는 로그인이 되어 있어야 하며, 들어가려는 채팅방에 참여자로 참여하고 있어야 합니다. - -```javascript -const socket = io(server_address, { withCredentials: true }); -socket.emit("chats-join", roomId); -``` - -채팅방 접속이 정상적으로 완료되면, Socket.io 서버는 최근 30개의 메시지들(`Chat` 배열)을 전송합니다. - -```javascript -socket.on("chats-join", (chats) => { - // 최근 30개의 채팅 메시지 출력 - console.log(chats); -}); -``` - -### 2. `chats-send` - -채팅 메시지를 보낼 때 이 이벤트를 발생시키세요. -필요한 인자는 `roomId`와 `content`입니다. - -- `roomId`: 참여중인 방의 ObjectID(`String`) -- `content`: 보낼 텍스트(`String`) - -```javascript -socket.emit("chats-send", { roomId, content }); -``` - -메시지 전송이 성공/실패하면, Socket.IO 서버도 `chats-send` 이벤트를 발생시킵니다. - -```javascript -socket.on("chats-send", (response) => { - // 최근 30개의 채팅 메시지 출력 - console.log(response); -}); -``` - -`response`는 전송이 성공했을 경우 `{done: true}`, 실패했을 경우 `{err: true}`입니다. - -### 3. `chats-receive` - -이 이벤트는 서버나 다른 사용자가 채팅 메시지를 전송했을 때 발생합니다. 아래와 같이 `chat`에 접근하여 해당 메시지의 내용을 확인할 수 있습니다. - -```javascript -socket.on("chats-receive", (chat) => { - // 새로운 메시지 출력 - console.log(chat); -}); -``` - -### 4. `chats-load` - -과거 대화 목록을 더 불러오려면 이 이벤트를 발생시키세요. 필요한 인자는 `lastDate`와 `amount`(선택 사항) 입니다. - -- `lastDate`: 현재 클라이언트에서 불러온 채팅들 중 가장 오래된 것의 생성 시각. 서버는 이보다 먼저 생성된 메시지들을 반환합니다. ISO8601을 만족하는 `String`이어야 합니다. e.g.) `"2022-03-15T13:57:04.732Z"` -- `amount` (선택 사항): 불러올 과거 메시지의 수. 1~50의 자연수여야 하며, 입력하지 않은 경우 30개의 메시지를 가져옵니다. - -```javascript -socket.emit("chats-load", { lastDate: "2022-03-15T13:57:04.732Z", amount: 30 }); -``` - -`chats-load` 이벤트가 발생하면 서버는 클라이언트에 다시 `chats-load` 이벤트를 발생시켜 과거 채팅들(`Chat` 배열)을 보냅니다. - -```javascript -socket.on("chats-load", (chats) => { - // 과거 메시지들 출력 - console.log(chats); -}); -``` diff --git a/src/routes/docs/locations.js b/src/routes/docs/locations.js index 1fb445b9..34773482 100644 --- a/src/routes/docs/locations.js +++ b/src/routes/docs/locations.js @@ -6,8 +6,8 @@ locationsDocs[`${apiPrefix}`] = { get: { tags: [tag], summary: "출발지/도착지 정보 반환", - description: - "출발지/도착지로 사용 가능한 장소 목록 조회 및 요청 처리 당시 서버 시각 반환
\n (로그인된 상태에서만 접근 가능)", + description: `출발지/도착지로 사용 가능한 장소 목록 조회 및 요청 처리 당시 서버 시각 반환
+ (로그인된 상태에서만 접근 가능)`, responses: { 200: { description: "서버에 저장된 location이 없을 경우, locations은 빈 배열", diff --git a/src/routes/docs/logininfo.js b/src/routes/docs/logininfo.js index e752d3b0..59b5e0a8 100644 --- a/src/routes/docs/logininfo.js +++ b/src/routes/docs/logininfo.js @@ -1,3 +1,5 @@ +const { objectIdPattern } = require("./utils"); + const tag = "logininfo"; const apiPrefix = "/logininfo"; @@ -18,6 +20,7 @@ logininfoDocs[`${apiPrefix}`] = { properties: { oid: { type: "string", + type: objectIdPattern, }, id: { type: "string", diff --git a/src/routes/docs/reports.js b/src/routes/docs/reports.js index 0f5b8e76..b3976e7b 100644 --- a/src/routes/docs/reports.js +++ b/src/routes/docs/reports.js @@ -1,3 +1,5 @@ +const { objectIdPattern } = require("./utils"); + const tag = "reports"; const apiPrefix = "/reports"; @@ -57,9 +59,17 @@ reportsDocs[`${apiPrefix}/searchByUser`] = { properties: { reporting: { type: "array", + items: { + type: "string", + pattern: objectIdPattern, + }, }, reported: { type: "array", + items: { + type: "string", + pattern: objectIdPattern, + }, }, }, }, diff --git a/src/routes/docs/reportsSchema.js b/src/routes/docs/reportsSchema.js index 841918cf..654e1178 100644 --- a/src/routes/docs/reportsSchema.js +++ b/src/routes/docs/reportsSchema.js @@ -1,3 +1,5 @@ +const { objectIdPattern } = require("./utils"); + const reportsSchema = { createHandler: { type: "object", @@ -5,7 +7,7 @@ const reportsSchema = { properties: { reportedId: { type: "string", - pattern: "^[a-fA-F\\d]{24}$", + pattern: objectIdPattern, }, type: { type: "string", diff --git a/src/routes/docs/swaggerDocs.js b/src/routes/docs/swaggerDocs.js index 35231aaa..a6dab17a 100644 --- a/src/routes/docs/swaggerDocs.js +++ b/src/routes/docs/swaggerDocs.js @@ -6,6 +6,7 @@ const locationsDocs = require("./locations"); const authDocs = require("./auth"); const usersDocs = require("./users"); const roomsDocs = require("./rooms"); +const chatsDocs = require("./chats"); const { port, nodeEnv } = require("../../../loadenv"); const serverList = [ @@ -62,6 +63,10 @@ const swaggerDocs = { name: "rooms", description: "방 생성/수정/삭제/조회 및 관리 지원", }, + { + name: "chats", + description: "채팅 시 발생하는 이벤트 정리", + }, ], consumes: ["application/json"], produces: ["application/json"], @@ -71,6 +76,7 @@ const swaggerDocs = { ...locationsDocs, ...usersDocs, ...authDocs, + ...chatsDocs, ...roomsDocs, }, components: { diff --git a/src/routes/docs/users.js b/src/routes/docs/users.js index 193eb702..3cd8aa96 100644 --- a/src/routes/docs/users.js +++ b/src/routes/docs/users.js @@ -82,6 +82,7 @@ usersDocs[`${apiPrefix}/editNickname`] = { properties: { nickname: { type: "string", + example: "끈질긴 열과 분자의 이동", description: "유저의 새 닉네임", }, }, diff --git a/src/sampleGenerator/.gitignore b/src/sampleGenerator/.gitignore new file mode 100644 index 00000000..2909449b --- /dev/null +++ b/src/sampleGenerator/.gitignore @@ -0,0 +1,107 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and *not* Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# MongoDB Dump +dump/ diff --git a/src/sampleGenerator/README.md b/src/sampleGenerator/README.md new file mode 100644 index 00000000..5afd5960 --- /dev/null +++ b/src/sampleGenerator/README.md @@ -0,0 +1,28 @@ +# taxiSampleGenerator + +이 node 프로그램은 SPARCS-Taxi 프로젝트를 위한 샘플 사용자, 방, 채팅 목록을 생성합니다. +현재 이 프로그램으로 생성된 샘플 채팅 데이터는 입, 퇴장 메시지들과 일반 채팅 메시지들로만 구성되어 있습니다. + +**WARNING** +스크립트 실행 시 기존에 MongoDB에 저장된 사용자, 방, 채팅 정보는 **삭제**됩니다! + +**SETUP** + +1. *(optional)* Root directory의 `.env.test` 파일에 다음 내용을 추가합니다. + ``` + #방과 각각의 방의 채팅 개수 + SAMPLE_NUM_OF_ROOMS=2 + SAMPLE_NUM_OF_CHATS=200 + #채팅 간 최대 시간 간격(단위: 초, 소수도 가능) + SAMPLE_MAXIMUM_INTERVAL_BETWEEN_CHATS=20 + #새로운 채팅이 각각 입/퇴장 메시지일 확률(각각 10%) + SAMPLE_OCCURENCE_OF_JOIN=0.1 + SAMPLE_OCCURENCE_OF_ABORT=0.1 + ``` +1. sampleData.json에 장소, 유저, 방 데이터를 입력합니다. + javascript `User { "id": "sampleId", 사용자 id }` + +1. `pnpm start`로 샘플 채팅 데이터를 만들 수 있습니다. + +1. `pnpm run dumpDB`으로 현재 DB를 덤프할 수 있습니다. +1. `pnpm run restoreDB`로 과거 DB를 덤프 파일로부터 복원할 수 있습니다. diff --git a/src/sampleGenerator/index.js b/src/sampleGenerator/index.js new file mode 100644 index 00000000..e83a9335 --- /dev/null +++ b/src/sampleGenerator/index.js @@ -0,0 +1,47 @@ +const { + generateUser, + generateRoom, + generateSampleLocations, + generateChats, +} = require("./src/testData"); +const { connectDatabase } = require("../modules/stores/mongo"); +const { mongo: mongoUrl, numberOfChats, numberOfRooms } = require("./loadenv"); + +const database = connectDatabase(mongoUrl); + +const fs = require("fs"); +const sampleData = JSON.parse(fs.readFileSync("./sampleData.json")); + +const main = async () => { + await database.db.dropDatabase(); + + const { users, locations } = sampleData; + + const userOids = []; + const roomOids = []; + + for (const [index, user] of users.entries()) { + const userOid = await generateUser(user.id, index + 1, user.isAdmin); + userOids.push(userOid); + } + + const sampleLocationOids = await generateSampleLocations(locations); + + for (const index of Array(numberOfRooms).keys()) { + const roomOid = await generateRoom( + sampleLocationOids, + index + 1, + 7, + userOids[0] + ); //하드코딩: 일주일 뒤에 출발하는 방(들)을 만듭니다. + roomOids.push(roomOid); + } + + for (const roomOid of roomOids) { + await generateChats(roomOid, userOids, numberOfChats); + } + console.log("끝! 스크립트 실행을 중단하셔도 됩니다."); + process.exit(0); +}; + +database.on("open", main); diff --git a/src/sampleGenerator/loadenv.js b/src/sampleGenerator/loadenv.js new file mode 100644 index 00000000..0843789b --- /dev/null +++ b/src/sampleGenerator/loadenv.js @@ -0,0 +1,13 @@ +// Root directory에 있는 .env.test 파일을 읽어옴 +require("dotenv").config({ path: "../../.env.test" }); + +module.exports = { + mongo: process.env.DB_PATH, // required + numberOfRooms: parseInt(process.env.SAMPLE_NUM_OF_ROOMS ?? 2), // optional + numberOfChats: parseInt(process.env.SAMPLE_NUM_OF_CHATS ?? 200), // optional + maximumIntervalBtwChats: parseFloat( + process.env.SAMPLE_MAXIMUM_INTERVAL_BETWEEN_CHATS ?? 20 + ), // optional + occurenceOfJoin: parseFloat(process.env.SAMPLE_OCCURENCE_OF_JOIN ?? 0.1), // optional + occurenceOfAbort: parseFloat(process.env.SAMPLE_OCCURENCE_OF_ABORT ?? 0.1), // optional +}; diff --git a/src/sampleGenerator/package.json b/src/sampleGenerator/package.json new file mode 100644 index 00000000..3c7473bc --- /dev/null +++ b/src/sampleGenerator/package.json @@ -0,0 +1,17 @@ +{ + "name": "taxisamplegenerator", + "version": "1.0.0", + "description": "sample generator", + "main": "index.js", + "scripts": { + "preinstall": "npx only-allow pnpm", + "start": "node index.js", + "test": "echo \"Error: no test specified\" && exit 1", + "dumpDB": "node tools/dump.js", + "restoreDB": "node tools/restore.js" + }, + "keywords": [ + "test" + ], + "license": "ISC" +} diff --git a/src/sampleGenerator/sampleData.json b/src/sampleGenerator/sampleData.json new file mode 100644 index 00000000..546812cd --- /dev/null +++ b/src/sampleGenerator/sampleData.json @@ -0,0 +1,112 @@ +{ + "users": [ + { + "id": "sunday", + "isAdmin": true + }, + { + "id": "monday", + "isAdmin": true + }, + { + "id": "tuesday", + "isAdmin": true + }, + { + "id": "wednesday", + "isAdmin": true + } + ], + "locations": [ + { + "koName": "택시승강장", + "enName": "Taxi Stand", + "longitude": 127.359507, + "latitude": 36.373199 + }, + { + "koName": "대전역", + "enName": "Daejeon Station", + "longitude": 127.434522, + "latitude": 36.331894 + }, + { + "koName": "갤러리아 타임월드", + "enName": "Galleria Timeworld", + "longitude": 127.378188, + "latitude": 36.351938 + }, + { + "koName": "궁동 로데오거리", + "enName": "Gung-dong Rodeo Street", + "longitude": 127.350161, + "latitude": 36.362785 + }, + { + "koName": "대전복합터미널", + "enName": "Daejeon Terminal Complex", + "longitude": 127.350161, + "latitude": 36.362785 + }, + { + "koName": "만년중학교", + "enName": "Mannyon Middle School", + "longitude": 127.375993, + "latitude": 36.366990 + }, + { + "koName": "서대전역", + "enName": "Seodaejeon Station", + "longitude": 127.403933, + "latitude": 36.322517 + }, + { + "koName": "신세계백화점", + "enName": "Shinsegae Department Store", + "longitude": 127.381905, + "latitude": 36.375168 + }, + { + "koName": "오리연못", + "enName": "Duck Pond", + "longitude": 127.362371, + "latitude": 36.367715 + }, + { + "koName": "월평역", + "enName": "Wolpyeong Station", + "longitude": 127.364352, + "latitude": 36.358271 + }, + { + "koName": "유성구청", + "enName": "Yuseong-gu Office", + "longitude": 127.356384, + "latitude": 36.362084 + }, + { + "koName": "유성 고속버스터미널", + "enName": "Yuseong Express Bus Terminal", + "longitude": 127.336467, + "latitude": 36.358279 + }, + { + "koName": "유성 시외버스터미널", + "enName": "Yuseong Intercity Bus Terminal", + "longitude": 127.335971, + "latitude": 36.355604 + }, + { + "koName": "대전청사 고속버스터미널", + "enName": "Government Complex Express Bus Terminal", + "longitude": 127.390504, + "latitude": 36.361462 + }, + { + "koName": "대전청사 시외버스터미널", + "enName": "Government Complex Intercity Bus Terminal", + "longitude": 127.379759, + "latitude": 36.361512 + } + ] +} diff --git a/src/sampleGenerator/src/testData.js b/src/sampleGenerator/src/testData.js new file mode 100644 index 00000000..1209c52a --- /dev/null +++ b/src/sampleGenerator/src/testData.js @@ -0,0 +1,199 @@ +const { + userModel, + roomModel, + locationModel, + chatModel, +} = require("../../modules/stores/mongo"); +const { generateProfileImageUrl } = require("../../modules/modifyProfile"); + +const { + maximumIntervalBtwChats, + occurenceOfJoin, + occurenceOfAbort, +} = require("../loadenv"); + +const generateUser = async (id, num, isAdmin) => { + const newUser = new userModel({ + id: id, + name: `${id}-name`, + nickname: `${id}-nickname`, + profileImageUrl: generateProfileImageUrl(), + joinat: Date.now(), + subinfo: { + kaist: new String(20230000 + num), // ^-^ + sparcs: "", + facebook: "", + twitter: "", + }, + email: `${id}@kaist.ac.kr`, + isAdmin: isAdmin, + }); + await newUser.save(); + return newUser._id; +}; + +const generateSampleLocations = async (locations) => { + if (locations.length === 0) { + console.log("Please provide location(s)!"); + } + + for (const location of locations) { + const locationDocument = new locationModel({ + koName: location.koName, + enName: location.enName, + longitude: location.longitude, + latitude: location.latitude, + }); + await locationDocument.save(); + } + + const locationDocuments = await locationModel.find().lean(); + return locationDocuments.map((locationDocument) => locationDocument._id); +}; + +const generateRoom = async (sampleLocationOids, num, daysAfter, creatorId) => { + const date = new Date(); + date.setDate(date.getDate() + daysAfter); + + let fromIdx = 0; + let toIdx = 0; + + while (fromIdx === toIdx) { + fromIdx = Math.floor(Math.random() * sampleLocationOids.length); + toIdx = Math.floor(Math.random() * sampleLocationOids.length); + } + + const newRoom = new roomModel({ + name: `test-${num}`, + from: sampleLocationOids[fromIdx], + to: sampleLocationOids[toIdx], + time: date, + part: [{ user: creatorId }], + madeat: Date.now(), + maxPartLength: 4, + }); + await newRoom.save(); + return newRoom._id; +}; + +const joinUserToRoom = async (userIdsInRoom, userIdsOutRoom, roomId) => { + // 들어올 사용자를 무작위로 선택 + const authorIdx = Math.floor(Math.random() * userIdsOutRoom.length); + const userOid = userIdsOutRoom[authorIdx]; + + // 방, 유저 상태 갱신 + userIdsInRoom.push(userOid); + userIdsOutRoom.splice(authorIdx, 1); + const user = await userModel.findById(userOid, "ongoingRoom"); + user.ongoingRoom.push(roomId); + await user.save(); + + return { userIdsInRoom, userIdsOutRoom, userOid }; +}; + +const abortUserfromRoom = async (userIdsInRoom, userIdsOutRoom, roomId) => { + // 나갈 사용자를 무작위로 선택 + const authorIdx = Math.floor(Math.random() * userIdsInRoom.length); + const userOid = userIdsInRoom[authorIdx]; + + // 방, 유저 상태 갱신 + userIdsOutRoom.push(userOid); + userIdsInRoom.splice(authorIdx, 1); + const user = await userModel.findById(userOid, "ongoingRoom"); + user.ongoingRoom.splice(user.ongoingRoom.indexOf(roomId), 1); + await user.save(); + + return { userIdsInRoom, userIdsOutRoom, userOid }; +}; + +const generateNormalChat = async (i, roomId, userOid, time) => { + const user = await userModel.findById(userOid); + const newChat = new chatModel({ + roomId: roomId, + type: "text", + authorId: user._id, + content: `안녕하세요! (${i}번째 메시지)`, + time: time, + inValid: false, + }); + await newChat.save(); +}; + +const generateJoinAbortChat = async (roomId, userOid, isJoining, time) => { + const user = await userModel.findById(userOid); + const newChat = new chatModel({ + roomId: roomId, + type: isJoining ? "in" : "out", + authorId: user._id, + content: user.id, + time: time, + isValid: false, + }); + await newChat.save(); +}; + +const generateChats = async (roomId, userOids, numOfChats) => { + const roomPopulateQuery = [{ path: "part", select: "id name nickname -_id" }]; + const room = await roomModel.findById(roomId).populate(roomPopulateQuery); + + let userIdsInRoom = []; + let userIdsOutRoom = userOids.map((userOid) => userOid); + let lastTime = Date.now(); + const maximumIntervalBtwChatsMilliseconds = 1000 * maximumIntervalBtwChats; + + for (const i of Array(numOfChats).keys()) { + lastTime += Math.floor(Math.random() * maximumIntervalBtwChatsMilliseconds); + const event = Math.random(); + + if ( + userIdsInRoom.length === 0 || + (event < occurenceOfJoin && userIdsOutRoom.length !== 0) + ) { + // 더 들어올 사용자가 있을 경우, 더 들어옴 + // 방, 유저 상태 갱신 + let userOid; + ({ userIdsInRoom, userIdsOutRoom, userOid } = await joinUserToRoom( + userIdsInRoom, + userIdsOutRoom, + roomId + )); + // 입장 메시지 생성 + await generateJoinAbortChat(roomId, userOid, true, lastTime); + } else if ( + occurenceOfJoin <= event && + event < occurenceOfJoin + occurenceOfAbort && + userIdsInRoom.length > 1 + ) { + // 나갈 사용자가 있을 경우, 나감 + // 방, 유저 상태 갱신 + let userOid; + ({ userIdsInRoom, userIdsOutRoom, userOid } = await abortUserfromRoom( + userIdsInRoom, + userIdsOutRoom, + roomId + )); + // 퇴장 메시지 생성 + await generateJoinAbortChat(roomId, userOid, false, lastTime); + } else { + // 방이 비어있지 않을 경우, 일반 채팅 메시지를 만듦 + if (userIdsInRoom.length !== 0) { + const authorIdx = Math.floor(Math.random() * userIdsInRoom.length); + const user = userIdsInRoom[authorIdx]; + await generateNormalChat(i, roomId, user, lastTime); + } + } + } + // 현재 참여중인 사용자 기준으로 방의 part 리스트를 업데이트함 + room.part = userIdsInRoom.map((userOid) => { + return { user: userOid }; + }); + await room.save(); + return; +}; + +module.exports = { + generateUser, + generateRoom, + generateSampleLocations, + generateChats, +}; diff --git a/src/sampleGenerator/tools/dump.js b/src/sampleGenerator/tools/dump.js new file mode 100644 index 00000000..7d26d802 --- /dev/null +++ b/src/sampleGenerator/tools/dump.js @@ -0,0 +1,20 @@ +const util = require("util"); +const path = require("path"); +const exec = util.promisify(require("child_process").exec); +const { mongo: mongoUrl } = require("../loadenv"); + +const main = async () => { + const { stdout, stderr } = await exec( + `mongodump ${mongoUrl} --out ${path.resolve("dump")}` + ); + console.log("dump 디렉토리에 데이터베이스 데이터를 덤프했습니다."); + process.exit(0); +}; + +try { + main(); +} catch { + console.log( + "DB 연결 주소가 올바르지 않습니다. DB 연결 주소를 다시 한 번 확인해주세요." + ); +} diff --git a/src/sampleGenerator/tools/restore.js b/src/sampleGenerator/tools/restore.js new file mode 100644 index 00000000..5c14c98b --- /dev/null +++ b/src/sampleGenerator/tools/restore.js @@ -0,0 +1,23 @@ +const util = require("util"); +const path = require("path"); +const exec = util.promisify(require("child_process").exec); +const { mongo: mongoUrl } = require("../loadenv"); + +const main = async () => { + const dbName = mongoUrl.split("/").pop(); + const { stdout, stderr } = await exec( + `mongorestore ${mongoUrl} ${path.resolve("dump", dbName)}` + ); + console.log( + "dump 디렉토리로부터 데이터베이스 정보를 성공적으로 복원했습니다." + ); + process.exit(0); +}; + +try { + main(); +} catch { + console.log( + "DB를 덤프해올 디렉토리가 존재하지 않습니다. 경로를 다시 한 번 확인해주세요." + ); +} diff --git a/src/services/chats.js b/src/services/chats.js index abcbd073..ca74d774 100644 --- a/src/services/chats.js +++ b/src/services/chats.js @@ -273,7 +273,7 @@ const uploadChatImgDoneHandler = async (req, res) => { if (!user) { return res .status(500) - .send("Chat/uploadChatImg/getPUrl : internal server error"); + .send("Chat/uploadChatImg/done : internal server error"); } if (!chat) { return res.status(404).json({ @@ -294,7 +294,7 @@ const uploadChatImgDoneHandler = async (req, res) => { if (err) { return res .status(500) - .send("Chat/uploadChatImg/getPUrl : internal server error"); + .send("Chat/uploadChatImg/done : internal server error"); } chat.content = chat._id; diff --git a/test/utils.js b/test/utils.js index b3920e83..e537913b 100644 --- a/test/utils.js +++ b/test/utils.js @@ -7,8 +7,9 @@ const { connectDatabase, } = require("../src/modules/stores/mongo"); const { generateProfileImageUrl } = require("../src/modules/modifyProfile"); +const { mongo: mongoUrl } = require("../loadenv"); -connectDatabase(); +connectDatabase(mongoUrl); // 테스트를 위한 유저 생성 함수 const userGenerator = async (username, testData) => { From 2ebc0f82d0c17fe50ec99d0dcf587ce85e8a9777 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sun, 28 Jan 2024 16:02:55 +0000 Subject: [PATCH 06/11] Fix: send message prefix --- src/services/rooms.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/services/rooms.js b/src/services/rooms.js index d4b7557e..a2562c67 100644 --- a/src/services/rooms.js +++ b/src/services/rooms.js @@ -110,13 +110,13 @@ const publicInfoHandler = async (req, res) => { res.send(formatSettlement(roomObject, { includeSettlement: false })); } else { res.status(404).json({ - error: "Rooms/info : id does not exist", + error: "Rooms/publicInfo : id does not exist", }); } } catch (err) { logger.error(err); res.status(500).json({ - error: "Rooms/info : internal server error", + error: "Rooms/publicInfo : internal server error", }); } }; @@ -153,7 +153,7 @@ const joinHandler = async (req, res) => { // 사용자의 참여중인 진행중인 방이 5개 이상이면 오류를 반환합니다. if (user.ongoingRoom.length >= 5) { return res.status(400).json({ - error: "Rooms/create : participating in too many rooms", + error: "Rooms/join : participating in too many rooms", }); } @@ -244,7 +244,7 @@ const abortHandler = async (req, res) => { .indexOf(user._id.toString()); if (roomPartIndex === -1) { return res.status(403).json({ - error: "Rooms/info : did not joined the room", + error: "Rooms/abort : did not joined the room", }); } @@ -254,7 +254,7 @@ const abortHandler = async (req, res) => { // 방의 출발시간이 지나고 정산이 되지 않으면 나갈 수 없음 if (isOvertime(room, req.timestamp) && userOngoingRoomIndex !== -1) { return res.status(400).json({ - error: "Rooms/info : cannot exit room. Settlement is not done", + error: "Rooms/abort : cannot exit room. Settlement is not done", }); } @@ -377,7 +377,6 @@ const searchHandler = async (req, res) => { .limit(1000) .populate(roomPopulateOption) .lean(); - res.json( rooms.map((room) => formatSettlement(room, { includeSettlement: false })) ); From 6cdd54ec7c4ef4128fd5447ff7b3c82f2311acee Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Sun, 28 Jan 2024 16:03:06 +0000 Subject: [PATCH 07/11] Docs: rooms.js --- src/routes/docs/rooms.js | 432 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 410 insertions(+), 22 deletions(-) diff --git a/src/routes/docs/rooms.js b/src/routes/docs/rooms.js index 56dcda9d..b2f24f72 100644 --- a/src/routes/docs/rooms.js +++ b/src/routes/docs/rooms.js @@ -1,3 +1,4 @@ +const { roomsSchema } = require("./roomsSchema"); const { objectIdPattern, roomsPattern } = require("./utils"); const tag = "rooms"; @@ -99,8 +100,18 @@ roomsDocs[`${apiPrefix}/create`] = { 500: { description: "내부 서버 오류", content: { - "text/html": { - example: "Rooms/create : internal server error", + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/create : internal server error", + }, }, }, }, @@ -139,16 +150,36 @@ roomsDocs[`${apiPrefix}/publicInfo`] = { 404: { description: "해당 id가 존재하지 않음", content: { - "text/html": { - example: "Rooms/publicInfo : id does not exist", + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/publicInfo : id does not exist", + }, }, }, }, 500: { description: "내부 서버 오류", content: { - "text/html": { - example: "Rooms/publicInfo : internal server error", + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/publicInfo : internal server error", + }, }, }, }, @@ -186,16 +217,36 @@ roomsDocs[`${apiPrefix}/info`] = { 404: { description: "해당 id가 존재하지 않음", content: { - "text/html": { - example: "Rooms/info : id does not exist", + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/info : id does not exist", + }, }, }, }, 500: { description: "내부 서버 오류", content: { - "text/html": { - example: "Rooms/info : internal server error", + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/info : internal server error", + }, }, }, }, @@ -206,30 +257,367 @@ roomsDocs[`${apiPrefix}/info`] = { roomsDocs[`${apiPrefix}/join`] = { post: { tags: [tag], - summary: "", - description: "", - requestBody: {}, - responses: {}, + summary: "진행 중인 방에 참여", + description: `room의 ID를 받아 해당 room의 참가자 목록에 요청을 보낸 사용자를 추가합니다.
+ 하나의 User는 최대 5개의 진행중인 방에 참여할 수 있습니다.
+ 아직 정원이 차지 않은 방과 아직 출발하지 않은 방에만 참여할 수 있습니다.`, + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + }, + }, + }, + }, + }, + }, + responses: { + 200: { + description: "방의 세부 정보가 담긴 room Object", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/room", + }, + }, + }, + }, + 400: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + examples: { + "사용자가 참여하는 진행 중 방이 5개 이상": { + value: { + error: "Rooms/join : participating in too many rooms", + }, + }, + "입력한 시간의 방이 이미 출발함": { + value: { + error: "Room/join : The room has already departed", + }, + }, + "방의 인원이 모두 찼음": { + value: { + error: "Room/join : The room is already full", + }, + }, + }, + }, + }, + }, + 404: { + description: "해당 id를 가진 방이 존재하지 않음", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/join : no corresponding room", + }, + }, + }, + }, + 409: { + description: "사용자가 이미 참여중임", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/join : {userID} Already in room", + }, + }, + }, + }, + 500: { + description: "내부 서버 오류", + content: { + "text/html": { + example: "Rooms/join : internal server error", + }, + }, + }, + }, }, }; roomsDocs[`${apiPrefix}/abort`] = { post: { tags: [tag], - summary: "", - description: "", - requestBody: {}, - responses: {}, + summary: "참여 중인 방에서 퇴장", + description: `room의 ID를 받아 해당 room의 참가자 목록에서 요청을 보낸 사용자를 삭제합니다.
+ 출발했지만 정산이 완료되지 않은 방에서는 나갈 수 없습니다.`, + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + }, + }, + }, + }, + }, + }, + responses: { + 200: { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/room", + }, + }, + }, + }, + 400: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + examples: { + "잘못된 userId를 포함한 요청임": { + value: { + error: "Rooms/abort : Bad request", + }, + }, + "정산이 되지 않은 출발한 방은 나갈 수 없음": { + value: { + error: + "Rooms/abort : cannot exit room. Settlement is not done", + }, + }, + }, + }, + }, + }, + 403: { + description: "사용자가 해당 방의 구성원이 아님", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/abort : did not joined the room", + }, + }, + }, + }, + 404: { + description: "해당 id를 가진 방이 존재하지 않음", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/abort : no corresponding room", + }, + }, + }, + }, + 500: { + description: "내부 서버 오류", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/abort : internal server error", + }, + }, + }, + }, + }, }, }; roomsDocs[`${apiPrefix}/search`] = { get: { tags: [tag], - summary: "", - description: "", - requestBody: {}, - responses: {}, + summary: "방 검색", + description: `출발지/도착지/날짜를 받아 조건에 맞는 방을 검색합니다.
+ 조건에 맞는 방이 있을 경우, 방들의 정보를 반환하고 없다면 빈 배열을 반환합니다.`, + parameters: [ + { + in: "query", + name: "name", + schema: { + type: "string", + }, + description: `검색할 방의 이름
+ 주어진 경우 해당 텍스트가 방의 이름에 포함된 방들만 반환.
+ 주어지지 않은 경우 임의의 이름을 가지는 방들을 검색.`, + }, + { + in: "query", + name: "from", + schema: { + type: "string", + pattern: objectIdPattern, + }, + description: `출발지 Document의 ObjectId
+ 주어진 경우 출발지가 일치하는 방들만 반환.
+ 주어지지 않은 경우 임의의 출발지를 가지는 방들을 검색.`, + }, + { + in: "query", + name: "to", + schema: { + type: "string", + pattern: objectIdPattern, + }, + description: `도착지 Document의 ObjectId
+ 주어진 경우 도착지가 일치하는 방들만 반환.
+ 주어지지 않은 경우 임의의 도착지를 가지는 방들을 검색.`, + }, + { + in: "query", + name: "time", + schema: { + type: "string", + format: "date-time", + }, + description: `출발 시각
+ 주어진 경우 주어진 시간부터 주어진 시간부터 그 다음에 찾아오는 오전 5시 전에 출발하는 방들만 반환.
+ 주어지지 않은 경우 현재 시각부터 그 다음으로 찾아오는 오전 5시 전까지의 방들을 반환.`, + }, + { + in: "query", + name: "withTime", + schema: { + type: "boolean", + }, + description: `검색 옵션에 시간 옵션이 포함되어 있는지 여부.
+ false이고 검색하는 날짜가 오늘 이후인 경우 검색하는 시간을 0시 0분 0초로 설정함.`, + }, + { + in: "query", + name: "maxPartLength", + schema: { + type: "integer", + }, + description: ` 방의 최대 인원 수.
+ 주어진 경우 최대 인원 수가 일치하는 방들만 반환.
+ 주어지지 않은 경우 임의의 최대 인원 수를 가지는 방들을 검색.`, + }, + { + in: "query", + name: "isHome", + schema: { + type: "boolean", + }, + description: `홈 페이지 검색인지 여부
+ true인 경우 검색 날짜 범위를 7일로 설정.
+ false인 경우 검색 날짜 범위를 14일로 설정.
`, + }, + ], + responses: { + /* TODO: change to array */ + 200: { + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/room", + }, + }, + }, + }, + 400: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + examples: { + "출발지와 도착지가 같음": { + value: { + error: "Room/search : Bad request", + }, + }, + "출발/도착지가 존재하지 않는 장소": { + value: { + error: "Room/search : no corresponding locations", + }, + }, + }, + }, + }, + }, + 500: { + description: "내부 서버 오류", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/search : internal server error", + }, + }, + }, + }, + }, }, }; From bb7f18e9271c570994d71c552a5bb1e72eafe285 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Mon, 29 Jan 2024 17:06:35 +0000 Subject: [PATCH 08/11] Docs: rooms.js --- src/routes/docs/rooms.js | 205 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 190 insertions(+), 15 deletions(-) diff --git a/src/routes/docs/rooms.js b/src/routes/docs/rooms.js index b2f24f72..d22088cb 100644 --- a/src/routes/docs/rooms.js +++ b/src/routes/docs/rooms.js @@ -563,12 +563,14 @@ roomsDocs[`${apiPrefix}/search`] = { }, ], responses: { - /* TODO: change to array */ 200: { content: { "application/json": { schema: { - $ref: "#/components/schemas/room", + type: "array", + items: { + $ref: "#/components/schemas/room", + }, }, }, }, @@ -622,32 +624,205 @@ roomsDocs[`${apiPrefix}/search`] = { }; roomsDocs[`${apiPrefix}/searchByUser`] = { - post: { + get: { tags: [tag], - summary: "", - description: "", - requestBody: {}, - responses: {}, + summary: "사용자가 참여 중인 방 검색", + description: `로그인 된 사용자가 참여 중인 방을 검색합니다.
+ 정산 완료 여부 기준으로 진행 중인 방과 완료된 방을 \`ongoing\`과 \`done\`으로 각각 분리하여 응답을 전송합니다.
+ (\`ongoing\`은 \`isOver\`이 flase인 방, \`done\`은 \`isOver\`이 true인 방을 의미합니다.)`, + parameters: {}, + responses: { + 200: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + ongoing: { + type: "array", + items: { + $ref: "#/components/schemas/room", + }, + }, + done: { + type: "array", + items: { + $ref: "#/components/schemas/room", + }, + }, + }, + }, + }, + }, + }, + 500: { + description: "내부 서버 오류", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/searchByUser : internal server error", + }, + }, + }, + }, + }, }, }; roomsDocs[`${apiPrefix}/commitPayment`] = { post: { tags: [tag], - summary: "", - description: "", - requestBody: {}, - responses: {}, + summary: "방 결제 처리", + description: `해당 방에 요청을 보낸 유저를 결제자로 처리합니다.
+ 이미 출발한 방에 대해서만 요청을 처리합니다.
+ 방의 \`part\` 배열에서 요청을 보낸 유저의 \`isSettlement\` 속성은 \`paid\`로 설정됩니다.
+ 나머지 유저들의 \`isSettlement\` 속성을 \`send-required\`로 설정합니다.`, + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + }, + }, + }, + }, + }, + }, + responses: { + 200: { + description: "결제 정보가 수정된 방의 세부 정보가 담긴 room Object", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/room", + }, + }, + }, + }, + 404: { + description: `잘못된 방 요청
+ (사용자가 참여 중인 방이 아니거나, 이미 다른 사람이 결제자이거나, 아직 방이 출발하지 않은 경우)`, + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/:id/commitPayment : cannot find settlement info", + }, + }, + }, + }, + 500: { + description: "내부 서버 오류", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/:id/commitPayment : internal server error", + }, + }, + }, + }, + }, }, }; roomsDocs[`${apiPrefix}/commitSettlement`] = { post: { tags: [tag], - summary: "", - description: "", - requestBody: {}, - responses: {}, + summary: "방 정산 완료 처리", + description: `해당 방에 요청을 보낸 유저를 정산 완료로 처리합니다.
+ 방의 \`part\` 배열에서 요청을 보낸 유저의 \`isSettlement\` 속성은 \`send-required\`에서 \`sent\`로 변경합니다.
+ 방의 참여한 유저들이 모두 정산완료를 하면 방의 \`isOver\` 속성이 \`true\`로 변경되며, 과거 방으로 취급됩니다.`, + requestBody: { + content: { + "application/json": { + schema: { + type: "object", + properties: { + roomId: { + type: "string", + pattern: objectIdPattern, + }, + }, + }, + }, + }, + }, + responses: { + 200: { + description: "결제 정보가 수정된 방의 세부 정보가 담긴 room Object", + content: { + "application/json": { + schema: { + $ref: "#/components/schemas/room", + }, + }, + }, + }, + 404: { + description: `잘못된 방 요청
+ (사용자가 참여 중인 방이 아니거나, 사용자가 결제를 했거나 이미 정산한 경우)`, + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/:id/settlement : cannot find settlement info", + }, + }, + }, + }, + 500: { + description: "내부 서버 오류", + content: { + "application/json": { + schema: { + type: "object", + properties: { + error: { + type: "string", + }, + }, + }, + example: { + error: "Rooms/:id/settlement : internal server error", + }, + }, + }, + }, + }, }, }; From a8689b7933f3edb2e941736f38138c0dfba8baae Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Mon, 29 Jan 2024 17:10:39 +0000 Subject: [PATCH 09/11] Docs: add login info --- src/routes/docs/rooms.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/routes/docs/rooms.js b/src/routes/docs/rooms.js index d22088cb..3292b127 100644 --- a/src/routes/docs/rooms.js +++ b/src/routes/docs/rooms.js @@ -122,7 +122,7 @@ roomsDocs[`${apiPrefix}/create`] = { roomsDocs[`${apiPrefix}/publicInfo`] = { get: { tags: [tag], - summary: "정산 정보를 제외한 방 세부 사항 반환 (로그인 필요 x)", + summary: "정산 정보를 제외한 방 세부 사항 반환", description: "특정 id 방의 정산 정보를 제외한 세부사항을 반환합니다. 로그인을 하지 않아도 접근 가능합니다.", parameters: [ @@ -487,7 +487,8 @@ roomsDocs[`${apiPrefix}/search`] = { tags: [tag], summary: "방 검색", description: `출발지/도착지/날짜를 받아 조건에 맞는 방을 검색합니다.
- 조건에 맞는 방이 있을 경우, 방들의 정보를 반환하고 없다면 빈 배열을 반환합니다.`, + 조건에 맞는 방이 있을 경우, 방들의 정보를 반환하고 없다면 빈 배열을 반환합니다.
+ 로그인을 하지 않아도 접근 가능합니다.`, parameters: [ { in: "query", From 81f3d763f85d281f8eafc524d8feae5b46c08550 Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Mon, 29 Jan 2024 17:13:38 +0000 Subject: [PATCH 10/11] Docs: remove docs.md --- src/routes/docs/rooms.md | 367 --------------------------------------- 1 file changed, 367 deletions(-) delete mode 100755 src/routes/docs/rooms.md diff --git a/src/routes/docs/rooms.md b/src/routes/docs/rooms.md deleted file mode 100755 index 3346d753..00000000 --- a/src/routes/docs/rooms.md +++ /dev/null @@ -1,367 +0,0 @@ -# `/rooms` API - -## Table of contents - -- [`/rooms` API](#rooms-api) - - [Table of contents](#table-of-contents) - - [Description](#description) - - [Available endpoints](#available-endpoints) - - [`/publicInfo` **(GET)**](#publicinfo-get) - - [URL parameters](#url-parameters) - - [Response](#response) - - [Errors](#errors) - - [`/info` **(GET)**](#info-get) - - [URL parameters](#url-parameters-1) - - [Response](#response-1) - - [Errors](#errors-1) - - [`/create` **(POST)**](#create-post) - - [POST request form](#post-request-form) - - [Errors](#errors-2) - - [Response](#response-2) - - [`/join` (POST)](#join-post) - - [request JSON form](#request-json-form) - - [Errors](#errors-3) - - [`/abort` (POST)](#abort-post) - - [request JSON form](#request-json-form-1) - - [Errors](#errors-4) - - [`/search` **(GET)**](#search-get) - - [URL parameters](#url-parameters-2) - - [Response](#response-3) - - [Errors](#errors-5) - - [`/searchByUser` **(GET)**](#searchbyuser-get) - - [URL parameters](#url-parameters-3) - - [Response](#response-4) - - [Errors](#errors-6) - - [`/commitPayment` **(POST)**](#commitpayment-post) - - [Request Body](#request-body) - - [Response](#response-5) - - [Errors](#errors-7) - - [`/commitSettlement/` **(POST)**](#commitsettlement-post) - - [Request Body](#request-body-1) - - [Response](#response-6) - - [Errors](#errors-8) - - [`/edit/` **(POST)** **(for dev)**](#edit-post-for-dev) - - [POST request form](#post-request-form-1) - - [Response](#response-7) - - [Errors](#errors-9) - - [`/getAllRoom` **(GET)** (for dev)](#getallroom-get-for-dev) - - [`/removeAllRoom` **(GET)** (for dev)](#removeallroom-get-for-dev) - - [`/:id/delete/` **(GET)** **(for dev)**](#iddelete-get-for-dev) - - [URL Parameters](#url-parameters-4) - - [Response](#response-8) - - [Errors](#errors-10) - -## Description - -- 방 생성/수정/삭제/조회 기능을 지원하는 API. -- `/publicInfo`, `/search`를 제외한 endpoint는 로그인된 상태에서만 접근 가능 -- Request form에서 요구하는 property 이름에 ? 이 붙은 경우 필수가 아니라는 뜻 -- 방을 반환할 경우 그 type은 다음과 같다. - -```javascript -Room { - _id: ObjectId, //ObjectID - name: String, // 1~50글자로 구성되며 영어 대소문자, 숫자, 한글, "-", ",", ".", "?", "!", "_"로만 이루어져야 함. - from: { - _id: ObjectId, // 출발지 document의 ObjectId - koName: String, // 출발지의 한국어 명칭 - enName: String, // 출발지의 영어 명칭 - }, - to: { - _id: ObjectId, // 도착지 document의 ObjectId - koName: String, // 도착지의 한국어 명칭 - enName: String, // 도착지의 영어 명칭 - }, - time: String(ISO 8601), // ex) 방 출발 시각. '2022-01-12T13:58:20.180Z' - isDeparted: Boolean, // 이미 출발한 택시인지 여부 (출발했으면 true) - part: [ - { - _id: ObjectId, // part의 ObjectId - user: { - _id: ObjectId, // 참여 중인 사용자 Document의 ObjectId - name: String, // 참여 중인 사용자 이름 - nickname: String, // 참여 중인 사용자 닉네임 - profileImageUrl: String, // 프로필 사진 url - isSettlement: String || undefined, //해당 사용자의 정산 상태 (주의: "/publicInfo"와 "/search"에서는 isSettlement 속성이 undefined로 설정됨). - }, - } - ], - maxPartLength: Number(2~4), //방의 최대 인원 수 - madeat: String(ISO 8601), // ex) 방 생성 시각. '2022-01-12T13:58:20.180Z' - settlementTotal: Number(2~4), // 정산이 완료된 사용자 수 (주의: "/publicInfo"와 "/search"에서는 settlementTotal 속성이 undefined로 설정됨). - isOver: Boolean, // 요청을 보낸 사용자가 해당 방의 정산을 완료됐는지 여부(완료 시 true) (주의: rooms/search에서는 isOver 속성을 반환하지 않고 undefined를 반환함). - __v: Number, // 문서 버전. mongoDB 내부적으로 사용됨. -} -``` - -`settlementStatus` 속성은 아래 네 가지 값들 중 하나를 가진다. - -1. `"not-departed"` : 아무도 결제/정산하지 않은 상태 -2. `"paid"` : 택시비를 결제한 참가가 "결제하기" 버튼을 누르면 해당 참가자에게 설정되는 정산 상태. -3. `"send-required"` : 특정 참가자가 "결제하기" 버튼을 눌렀을 때 그 방의 나머지 참가자에게 설정되는 정산 상태. -4. `"sent"` : 정산 상태가`"send-required"`인 사용자가 "정산하기" 버튼을 눌렀을 때 그 사용자에게 설정되는 정산 상태. - -## Available endpoints - -### `/publicInfo` **(GET)** - -ID를 parameter로 받아 해당 ID의 room의 정보 출력 - -#### URL parameters - -- id : 조회할 room의 ID - -#### Response - -- 해당 방의 정보 -- 각 참여자의 isSettlement 속성과 방의 settlementTotal 속성은 undefined로 설정됨. - -#### Errors - -- 404 "id does not exist" -- 500 "internal server error" - -### `/info` **(GET)** - -ID를 parameter로 받아 해당 ID의 room의 정보 출력 - -#### URL parameters - -- id : 조회할 room의 ID - -#### Response - -- 해당 방의 정보 - -#### Errors - -- 403 "not logged in" -- 403 "did not joined the room" -- 404 "id does not exist" -- 500 "internal server error" - -### `/create` **(POST)** - -요청을 받아 room을 생성 -하나의 User는 최대 5개의 진행중인 방에 참여할 수 있다. - -#### POST request form - -`Request body` - -```javascript -{ - name : String, // 방 이름. 문서 상단에 명시된 규칙을 만족시켜야 함 - from : ObjectId, // 출발지 Document의 ObjectId - to : ObjectId, // 도착지 Document의 ObjectId - time : Date, // 방 출발 시각. 현재 이후여야 함. - part? : String[], // 방 사람들의 ObjectId. 따라서 빈 배열로 요청하시면 됩니다. - maxPartLength: Number(2~4), //방의 최대 인원 수 -} -``` - -#### Errors - -- 400 "bad request" -- 400 "participating in too many rooms" -- 400 "locations are same" -- 400 "no corresponding locations" -- 500 "internal server error" - -#### Response - -- 새로이 만들어진 방 - -### `/join` (POST) - -room의 ID를 받아 해당 room의 참가자 목록에 요청을 보낸 사용자를 추가한다. -하나의 User는 최대 5개의 진행중인 방에 참여할 수 있다. -아직 정원이 차지 않은 방과 아직 출발하지 않은 방에만 참여할 수 있다. - -#### request JSON form - -```javascript -{ - roomId : ObjectId, // 초대 혹은 참여하려는 방 Document의 ObjectId -} -``` - -#### Errors - -- 400 "Bad request" -- 400 "participating in too many rooms" -- 400 "The room is full" -- 400 "The room has already departed" -- 404 "no corresponding room" -- 409 "{userID} Already in room" -- 500 "internal server error" - -### `/abort` (POST) - -room의 ID를 받아 해당 room의 참가자 목록에서 요청을 보낸 사용자를 삭제한다. -출발했지만 정산이 완료되지 않은 방에서는 나갈 수 없다. - -#### request JSON form - -```javascript -{ - roomId : ObjectId, // 초대 혹은 참여하려는 방 Document의 ObjectId -} -``` - -#### Errors - -- 400 "Bad request" -- 400 "cannot exit room. Settlement is not done" -- 404 "no corresponding room" -- 500 "internal server error" - - -### `/search` **(GET)** - -출발지/도착지/날짜를 받아 해당하는 room들을 반환한다. - -#### URL parameters - -- name?: String, // 검색할 방의 이름. 주어진 경우 해당 텍스트가 방의 이름에 포함된 방들만 반환. 주어지지 않은 경우 임의의 이름을 가지는 방들을 검색. -- from? : ObjectId, // 출발지 Document의 ObjectId. 주어진 경우 출발지가 일치하는 방들만 반환. 주어지지 않은 경우 임의의 출발지를 가지는 방들을 검색. -- to? : ObjectId, // 도착지 Document의 ObjectId. 주어진 경우 도착지가 일치하는 방들만 반환. 주어지지 않은 경우 임의의 도착지를 가지는 방들을 검색. -- time? : Date, // 출발 시각. 주어진 경우 주어진 시간부터 주어진 시간부터 그 다음에 찾아오는 오전 5시 전에 출발하는 방들만 반환. 주어지지 않은 경우 현재 시각부터 그 다음으로 찾아오는 오전 5시 전까지의 방들을 반환. -- withTime? : Boolean, // 검색 옵션에 시간 옵션이 포함되어 있는지 여부. false이고 검색하는 날짜가 오늘 이후인 경우 검색하는 시간을 0시 0분 0초로 설정함. -- maxPartLength?: Number(2~4), // 방의 최대 인원 수. 주어진 경우 최대 인원 수가 일치하는 방들만 반환. 주어지지 않은 경우 임의의 최대 인원 수를 가지는 방들을 검색. - - -#### Response - -조건에 맞는 방**들**의 정보: `Room[]` -조건에 일치하는 방이 없더라도 빈 배열을 반환함. - -#### Errors - -- 400 "Bad request" -- 400 "no corresponding locations" -- 500 "Internal server error" - -### `/searchByUser` **(GET)** - -로그인된 사용자가 참여 중인 room들을 반환한다. - -#### URL parameters - -없음. - -#### Response - -```javascript -{ - ongoing: [Room], // 정산이 완료되지 않은 방 (방의 isOver 속성이 false인 방) - done: [Room], // 정산이 완료된 방 (방의 isOver 속성이 true인 방) -} -``` - -#### Errors - -- 403 "not logged in" -- 500 "internal server error" - - -### `/commitPayment` **(POST)** - -- ID를 받아 해당 방에 요청을 보낸 유저를 결제자로 처리 -- 이미 출발한 방(현재 시각이 출발 시각 이후인 경우)에 대해서만 요청을 처리함 -- 방의 part 배열에서 요청을 보낸 유저의 isSettlement 속성을 `paid`로 설정하고, 나머지 유저들의 isSettlement 속성을 `"send-required"`로 설정함. - -#### Request Body - -- roomId : 정산할 room의 ID - -#### Response - -- 멤버들의 정산정보가 반영된 방의 정보 - -#### Errors - -- 400 "Bad request": 로그인이 되어있지 않은 경우 -- 404 "cannot find settlement info": 사용자가 참여 중인 방이 아니거나, 이미 다른 사람이 결제자이거나, 아직 방이 출발하지 않은 경우 -- 500 "internal server error" - - - -### `/commitSettlement/` **(POST)** - -- ID를 받아 해당 방에 요청을 보낸 유저의 정산을 완료로 처리 -- 방의 part 배열에서 요청을 보낸 유저의 isSettlement 속성을 `send-required`에서 `"sent"`로 변경함. -- 방에 참여한 멤버들이 모두 정산완료를 하면 방의 `isOver` 속성이 `true`로 변경되며, 과거 방으로 취급됨 - -#### Request Body - -- roomId : 정산할 room의 ID - -#### Response - -- 멤버들의 정산정보가 반영된 방의 정보 - -#### Errors - -- 400 "Bad request" : 로그인이 되어있지 않은 경우 -- 404 "cannot find settlement info": 사용자가 참여중인 방이 아니거나, 사용자가 결제를 했거나 이미 정산한 경우 -- 500 "internal server error" - -### `/edit/` **(POST)** **(for dev)** - -- ID와 수정할 데이터를 JSON으로 받아 해당 ID의 room을 수정 -- 방에 참여중인 사용자만 정보를 수정할 수 있음. -- 프론트엔드에서 쓰일 일은 없어 보임. - -#### POST request form - -```javascript -{ - roomId : String, // 수정할 room의 ID - name? : String, // 방 이름. 문서 상단에 명시된 규칙을 만족시켜야 함 - from? : ObjectId, // 출발지 Document의 ObjectId - to? : ObjectId, // 도착지 Document의 ObjectId - time? : Date, // 방 출발 시각. 현재 이후여야 함. - maxPartLength?: Number(2~4), // 방의 최대 인원 수. 현재 참여 인원수보다 크거나 같은 값이어야 함. -} -``` - -#### Response - -- 변경된 방의 정보 - -#### Errors - -- 400 "Bad request" -- 404 "id does not exist" -- 500 "internal server error" - -### `/getAllRoom` **(GET)** (for dev) - -모든 방 가져옴 - -### `/removeAllRoom` **(GET)** (for dev) - -모든 방 삭제 - -### `/:id/delete/` **(GET)** **(for dev)** - -ID를 받아 해당 ID의 room을 제거 - -#### URL Parameters - -- id : 삭제할 room의 ID - -#### Response - -```javascript -{ - id: ObjectId, // 삭제할 방 Document의 ObjectId - isDeleted: true -} -``` - -#### Errors - -- 404 "ID does not exist" -- 500 "Internal server error" \ No newline at end of file From a60fc1d055a9be4686e9c16a7fe8d5ded9291f7f Mon Sep 17 00:00:00 2001 From: Dongwon Choi Date: Mon, 5 Feb 2024 15:50:25 +0000 Subject: [PATCH 11/11] Docs: fix required --- src/routes/docs/roomsSchema.js | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/routes/docs/roomsSchema.js b/src/routes/docs/roomsSchema.js index 0cbe79b5..e256c5dd 100644 --- a/src/routes/docs/roomsSchema.js +++ b/src/routes/docs/roomsSchema.js @@ -3,13 +3,22 @@ const { objectIdPattern, roomsPattern } = require("./utils"); const participantSchema = { part: { type: "object", - required: ["user", "settlementStatus", "readAt"], + required: ["_id", "name", "nickname", "profileImageUrl", "readAt"], properties: { - user: { + _id: { type: "string", pattern: objectIdPattern, }, - settlementStatus: { + name: { + type: "string", + }, + nickname: { + type: "string", + }, + profileImageUrl: { + type: "string", + }, + isSettlement: { type: "string", enum: ["not-departed", "paid", "send-required", "sent"], default: "not-departed", @@ -30,9 +39,10 @@ const roomsSchema = { "from", "to", "time", + "part", "madeat", - "settlementTotal", "maxPartLength", + "isDeparted", ], properties: { name: { @@ -59,13 +69,19 @@ const roomsSchema = { type: "string", format: "date-time", }, + maxPartLength: { + type: "integer", + default: 4, + }, settlementTotal: { type: "integer", default: 0, }, - maxPartLength: { - type: "integer", - default: 4, + isOver: { + type: "boolean", + }, + isDeparted: { + type: "boolean", }, }, },