├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .github └── workflows │ ├── codeql.yml │ └── test.yml ├── .gitignore ├── .mocharc.js ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app.js ├── config ├── custom-environment-variables.js ├── default.js ├── development.js ├── production.js └── staging.js ├── constants ├── answers.ts ├── application.ts ├── authorities.ts ├── badges.ts ├── bot.ts ├── cacheKeys.ts ├── cloudflareCache.ts ├── cloudinary.ts ├── constants.ts ├── errorMessages.ts ├── events.ts ├── extensionRequests.ts ├── external-accounts.ts ├── firebase.ts ├── imageVerificationTypes.ts ├── items.ts ├── logs.ts ├── monitor.ts ├── multer.ts ├── profileDiff.ts ├── progresses.ts ├── rateLimiting.ts ├── requests.ts ├── roles.ts ├── subscription-validator.ts ├── tags.ts ├── taskRequests.ts ├── tasks.ts ├── urls.ts ├── userDataLevels.ts ├── userStatus.ts ├── users.ts └── wallets.ts ├── controllers ├── answers.ts ├── applications.ts ├── arts.js ├── auction.js ├── auth.js ├── awsAccess.ts ├── badges.js ├── challenge.js ├── cloudflareCache.js ├── contributions.js ├── discordactions.js ├── events.js ├── extensionRequests.js ├── extensionRequestsv2.ts ├── external-accounts.js ├── fcmToken.js ├── goals.js ├── health.js ├── invites.ts ├── issues.js ├── items.js ├── levels.js ├── logs.js ├── members.js ├── monitor.js ├── notify.js ├── onboardingExtension.ts ├── oooRequests.ts ├── profileDiffs.js ├── progresses.js ├── pullRequests.js ├── questions.ts ├── recruiters.js ├── requests.ts ├── staging.js ├── stocks.js ├── subscription.ts ├── tags.js ├── taskRequestsv2.ts ├── tasks.js ├── tasksRequests.js ├── trading.js ├── userStatus.js ├── users.js └── wallets.js ├── docs ├── User Status Flow.md └── backendFlow.md ├── firebase.json ├── middlewares ├── assignTask.js ├── authenticate.js ├── authenticateProfile.js ├── authinticateServiceRequest.ts ├── authorization.js ├── authorizeBot.js ├── authorizeOwnOrSuperUser.ts ├── authorizeOwner.js ├── authorizeRoles.js ├── authorizeUsersAndService.ts ├── checkCanGenerateDiscordLink.ts ├── conditionalMiddleware.ts ├── contentTypeCheck.js ├── devFlag.ts ├── index.js ├── passport.js ├── rateLimiting.js ├── responseHeaders.js ├── shortCircuit.ts ├── skipAuthenticateForOnboardingExtension.ts ├── skipAuthorizeRolesWrapper.js ├── taskRequests.js ├── userAuthorization.ts ├── validators │ ├── answers.ts │ ├── application.ts │ ├── arts.js │ ├── auctions.js │ ├── badges.js │ ├── challenges.js │ ├── discordactions.js │ ├── events.js │ ├── extensionRequests.js │ ├── extensionRequestsv2.ts │ ├── external-accounts.js │ ├── fcmToken.js │ ├── invites.ts │ ├── items.js │ ├── levels.js │ ├── members.js │ ├── monitor.js │ ├── notify.js │ ├── onboardingExtensionRequest.ts │ ├── oooRequests.ts │ ├── progresses.js │ ├── qrCodeAuth.js │ ├── questions.ts │ ├── recruiter.js │ ├── requests.ts │ ├── staging.js │ ├── stocks.js │ ├── subscription.ts │ ├── tags.js │ ├── task-requests.js │ ├── taskRequests.ts │ ├── tasks.js │ ├── trading.js │ ├── user.js │ ├── userStatus.js │ └── utils.ts └── verifydiscord.js ├── mockdata └── appOwners.js ├── models ├── answers.ts ├── applications.ts ├── arts.js ├── auctions.js ├── badges.js ├── chaincodes.js ├── challenges.js ├── discordactions.js ├── events.js ├── extensionRequests.js ├── external-accounts.js ├── fcmToken.js ├── items.js ├── levels.js ├── logs.js ├── members.js ├── monitor.js ├── profileDiffs.js ├── progresses.js ├── qrCodeAuth.js ├── questions.ts ├── recruiters.js ├── requests.ts ├── stocks.js ├── tags.js ├── taskRequests.js ├── tasks.js ├── userFutureStatus.ts ├── userStatus.js ├── users.js └── wallets.js ├── newrelic.js ├── nyc.config.js ├── package.json ├── renovate.json ├── routes ├── answers.ts ├── applications.ts ├── arts.ts ├── auctions.ts ├── auth.ts ├── awsAccess.ts ├── badges.js ├── challenges.ts ├── cloudflareCache.ts ├── contributions.ts ├── discordactions.js ├── events.js ├── extensionRequests.js ├── external-accounts.js ├── fcmToken.js ├── goals.ts ├── healthCheck.ts ├── index.ts ├── invites.ts ├── issues.ts ├── items.js ├── levels.js ├── logs.js ├── members.js ├── monitor.js ├── notify.ts ├── profileDiffs.js ├── progresses.ts ├── pullrequests.ts ├── questions.ts ├── requests.ts ├── staging.ts ├── stocks.js ├── subscription.ts ├── tags.js ├── taskRequests.js ├── tasks.js ├── trading.ts ├── userStatus.js ├── users.js └── wallets.js ├── scripts ├── tests │ ├── tdd-files-list.txt │ ├── tdd.sh │ ├── testIntegration.sh │ └── testUnit.sh └── validateSetup.js ├── server.js ├── services ├── EventAPIService.js ├── EventTokenService.js ├── authService.js ├── botVerificationService.js ├── cloudflareService.js ├── contributions.js ├── dataAccessLayer.js ├── discordMembersService.js ├── discordService.js ├── getFcmTokenFromUserId.js ├── getUserIdsFromRoleId.js ├── githubService.js ├── goalService.js ├── imageService.js ├── index.js ├── issuesService.js ├── logService.ts ├── onboardingExtension.ts ├── oooRequest.ts ├── tasks.js ├── tradingService.js └── users.js ├── test ├── config │ └── test.js ├── fixtures │ ├── abandoned-tasks │ │ └── departed-users.js │ ├── answers │ │ └── answers.ts │ ├── applications │ │ └── applications.ts │ ├── arts │ │ └── arts.js │ ├── auctions │ │ └── auctions.js │ ├── auth │ │ ├── githubUserInfo.js │ │ └── googleUserInfo.js │ ├── badges │ │ └── badges.js │ ├── cache │ │ └── cache.js │ ├── challenges │ │ └── challenges.js │ ├── cloudflareCache │ │ └── data.js │ ├── contributions │ │ └── githubPRInfo.js │ ├── currencies │ │ └── currencies.js │ ├── discordResponse │ │ └── discord-response.js │ ├── discordactions │ │ └── discordactions.js │ ├── events │ │ ├── event-codes.js │ │ ├── events.js │ │ └── peers.js │ ├── extension-requests │ │ └── extensionRequests.ts │ ├── external-accounts │ │ └── external-accounts.js │ ├── featureFlag │ │ └── featureFlag.js │ ├── goals │ │ └── Token.js │ ├── invites │ │ └── invitesData.ts │ ├── issues │ │ └── issues.js │ ├── logs │ │ ├── archievedUsers.js │ │ ├── extensionRequests.js │ │ ├── requests.js │ │ └── tasks.js │ ├── oooRequest │ │ └── oooRequest.ts │ ├── profileDiffs │ │ └── profileDiffs.js │ ├── progress │ │ └── progresses.js │ ├── pullrequests │ │ └── pullrequests.js │ ├── qrCodeAuth │ │ └── qrCodeAuth.js │ ├── questions │ │ └── questions.ts │ ├── recruiter │ │ └── recruiter.js │ ├── standup │ │ └── standup.js │ ├── subscription │ │ └── subscription.ts │ ├── task-requests │ │ └── task-requests.js │ ├── taskRequests │ │ └── taskRequests.ts │ ├── tasks │ │ ├── tasks.js │ │ └── tasks1.js │ ├── time │ │ └── time.js │ ├── trackedProgress │ │ └── index.js │ ├── user │ │ ├── inDiscord.js │ │ ├── join.js │ │ ├── photo-verification.js │ │ ├── removalData.js │ │ ├── search.js │ │ └── user.js │ ├── userBadges │ │ └── userBadges.js │ ├── userDeviceInfo │ │ └── userDeviceInfo.js │ ├── userFutureStatus │ │ └── userFutureStatusData.ts │ ├── userStatus │ │ └── userStatus.js │ └── wallet │ │ └── wallet.js ├── integration │ ├── answers.test.ts │ ├── application.test.ts │ ├── arts.test.js │ ├── auction.test.js │ ├── auth.test.js │ ├── authorization.test.js │ ├── authorizeRoles.test.js │ ├── authorizeUsersAndService.test.ts │ ├── awsAccess.test.ts │ ├── badges.test.js │ ├── cloudflareCache.test.js │ ├── contributions.test.js │ ├── discord.test.js │ ├── discordactions.test.js │ ├── events.test.js │ ├── extensionRequests.test.js │ ├── external-accounts.test.js │ ├── fcmToken.test.js │ ├── goals.test.js │ ├── health.test.js │ ├── issues.test.js │ ├── logs.test.js │ ├── members.test.js │ ├── monitor.js │ ├── notify.test.js │ ├── onboardingExtension.test.ts │ ├── profileDiffs.test.js │ ├── profileDiffsDev.test.js │ ├── progressesTasks.test.js │ ├── progressesUsers.test.js │ ├── qrCodeAuth.test.js │ ├── questions.test.ts │ ├── recruiters.test.js │ ├── requests.test.ts │ ├── restricted.test.js │ ├── stocks.test.ts │ ├── subscription.test.js │ ├── taskBasedStatusUpdate.test.js │ ├── taskRequests.test.js │ ├── tasks.test.js │ ├── trading.test.ts │ ├── userStatus.test.js │ ├── users.test.js │ ├── usersFilter.test.js │ └── wallet.test.js ├── unit │ ├── middlewares │ │ ├── application-validator.test.ts │ │ ├── application.test.ts │ │ ├── arts-validator.test.js │ │ ├── auctions-validator.test.js │ │ ├── authenticate.test.js │ │ ├── authenticateProfile.test.js │ │ ├── authorization.test.js │ │ ├── authorizeBot.test.js │ │ ├── cache.test.js │ │ ├── challenges-validator.test.js │ │ ├── conditionalMiddleware.test.ts │ │ ├── contentTypeCheck.test.js │ │ ├── data-access-layer.test.js │ │ ├── devFlag.test.js │ │ ├── discordactions-validators.test.js │ │ ├── extension-request-validator.test.js │ │ ├── extensionRequests.test.ts │ │ ├── external-accounts-validator.test.js │ │ ├── fcmToken-validator.test.js │ │ ├── invite.test.ts │ │ ├── notify-validator.test.js │ │ ├── onboardingExtensionRequestValidator.test.ts │ │ ├── oooRequests.test.ts │ │ ├── qrCodeAuthValidator.test.js │ │ ├── rateLimiting.test.js │ │ ├── requests.test.ts │ │ ├── security.test.js │ │ ├── skipAuthenticateForOnboardingExtension.test.ts │ │ ├── skipAuthorizeRolesWrapper.test.js │ │ ├── subscription-validator.test.js │ │ ├── task-request.test.js │ │ ├── taskRequests.test.ts │ │ ├── tasks-validator.test.js │ │ ├── tasks.test.js │ │ ├── user-validator.test.js │ │ ├── userAuthorization.test.ts │ │ ├── userStatusValidator.js │ │ ├── userStatusValidator.test.js │ │ └── users-validator.test.js │ ├── models │ │ ├── answers.test.ts │ │ ├── application.test.ts │ │ ├── auctions.test.js │ │ ├── challenges.test.js │ │ ├── discordactions.test.js │ │ ├── events.test.js │ │ ├── external-accounts.test.js │ │ ├── fcmToken.test.js │ │ ├── logs.test.js │ │ ├── profileDiffs.test.js │ │ ├── progresses.test.js │ │ ├── qrCodeAuth.test.js │ │ ├── questions.test.ts │ │ ├── recruiters.test.js │ │ ├── requests.test.ts │ │ ├── task-requests.test.js │ │ ├── taskBasedStatusUpdate.test.js │ │ ├── tasks.test.js │ │ ├── userFutureStatus.test.ts │ │ ├── userStatus.js │ │ ├── users.test.js │ │ └── wallet.test.js │ ├── services │ │ ├── authService.test.js │ │ ├── awsAccess.test.ts │ │ ├── dataAccessLayer.test.js │ │ ├── discordMembersService.test.js │ │ ├── discordService.test.js │ │ ├── generateAuthToken.test.js │ │ ├── getFcmTokenFromUserId.test.js │ │ ├── getUserIdsFromRoleId.test.js │ │ ├── githubService.test.js │ │ ├── logService.test.ts │ │ ├── onboardingExtension.test.ts │ │ ├── oooRequest.test.ts │ │ ├── tasks.test.js │ │ └── users.test.js │ └── utils │ │ ├── application.test.ts │ │ ├── cache.test.js │ │ ├── customWordCountValidator.test.js │ │ ├── data-access.test.js │ │ ├── discord-actions.test.ts │ │ ├── events.test.js │ │ ├── generateAuthTokenForCloudflare.test.js │ │ ├── genrateCloudFlareHeaders.test.js │ │ ├── helper.test.js │ │ ├── logs.test.js │ │ ├── parseSearchQuery.test.js │ │ ├── progresses.test.js │ │ ├── queryParser.test.js │ │ ├── remvoeDiscordRoleFromUser.test.ts │ │ ├── requests.test.ts │ │ ├── rqlQueryParser.test.js │ │ ├── sendTaskUpdate.test.js │ │ ├── taskBasedStatusUpdates.test.js │ │ ├── tasks.test.js │ │ ├── time.test.js │ │ ├── transformQuery.test.js │ │ ├── userStatus.test.js │ │ ├── userStatusValidator.test.js │ │ ├── username.test.ts │ │ ├── users.test.js │ │ └── verifyAuthToken.test.ts └── utils │ ├── addProfileDiffs.js │ ├── addUser.js │ ├── cleanDb.js │ ├── deleteRoles.js │ ├── deleteRolesObject.js │ ├── generateBotToken.js │ ├── github.js │ ├── googleauth.js │ └── user.ts ├── tsconfig.json ├── typeDefinitions ├── answers.d.ts ├── global.d.ts ├── rqlParser.ts ├── task-requests.ts └── users.ts ├── types ├── application.d.ts ├── extensionRequests.d.ts ├── global.d.ts ├── impersonationRequest.d.ts ├── invites.d.ts ├── onboardingExtension.d.ts ├── oooRequest.d.ts ├── questions.d.ts ├── requests.d.ts ├── taskRequests.d.ts ├── userFutureStatus.d.ts └── userStatus.ts ├── utils ├── RQLParser.ts ├── application.ts ├── array.js ├── awsFunctions.ts ├── badges.js ├── cache.js ├── cloudinary.js ├── customWordCountValidator.js ├── data-access.js ├── discord-actions.js ├── events.js ├── extensionRequests.js ├── fetch.js ├── fetchMultiplePageResults.js ├── firestore.js ├── helper.js ├── logger.ts ├── logs.js ├── monitor.js ├── multer.js ├── obfuscate.js ├── profileDiffs.js ├── progresses.js ├── pullRequests.js ├── queryParser.js ├── rateLimiting.js ├── removeDiscordRoleFromUser.ts ├── requests.ts ├── sendTaskUpdate.js ├── task-requests.ts ├── tasks.js ├── time.js ├── userStatus.js ├── username.ts ├── users.js └── verifyAuthToken.ts └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | indent_size = 2 5 | indent_style = space 6 | insert_final_newline = true 7 | max_line_length = 120 8 | tab_width = 2 9 | trim_trailing_whitespace = true 10 | 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Eslint config file. 3 | */ 4 | module.exports = { 5 | env: { 6 | commonjs: true, 7 | es2021: true, 8 | node: true, 9 | mocha: true, 10 | }, 11 | extends: ["standard", "plugin:mocha/recommended", "plugin:security/recommended", "plugin:prettier/recommended"], 12 | plugins: ["mocha", "security", "prettier"], 13 | parserOptions: { 14 | ecmaVersion: 13, 15 | }, 16 | globals: { 17 | config: "readonly", 18 | logger: "readonly", 19 | }, 20 | rules: { 21 | // Custom eslint rules 22 | "no-trailing-spaces": "error", 23 | "consistent-return": "error", 24 | "no-console": "error", 25 | 26 | // Custom mocha rules 27 | "mocha/no-skipped-tests": "error", 28 | "mocha/no-exclusive-tests": "error", 29 | 30 | // Prettier for formatting 31 | "prettier/prettier": "error", 32 | }, 33 | ignorePatterns: ["public/*", "dist/*"], 34 | }; 35 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # http://git-scm.com/docs/gitattributes#_end_of_line_conversion 2 | 3 | *.ts text eol=lf 4 | *.js text eol=lf 5 | *.json text eol=lf 6 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Tests 5 | 6 | on: 7 | pull_request: 8 | branches: 9 | - "**" 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | timeout-minutes: 5 15 | 16 | strategy: 17 | matrix: 18 | node-version: [22.10.0] 19 | 20 | steps: 21 | - uses: actions/checkout@v3 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v3 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: yarn 27 | - run: yarn test 28 | -------------------------------------------------------------------------------- /.mocharc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mocha configuration file 3 | * Info: https://mochajs.org/#configuring-mocha-nodejs 4 | */ 5 | module.exports = { 6 | timeout: "5000", 7 | extension: ["ts", "js"], 8 | require: "ts-node/register", 9 | }; 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Real Dev Squad API Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | 9 | 10 | ## [Unreleased](https://github.com/Real-Dev-Squad/website-backend/compare/develop) 11 | 12 | 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Real Dev Squad 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const createError = require("http-errors"); 2 | const express = require("express"); 3 | const { isMulterError, multerErrorHandling } = require("./utils/multer"); 4 | 5 | // Attach response headers 6 | const responseHeaders = require("./middlewares/responseHeaders"); 7 | 8 | // import app middlewares 9 | const AppMiddlewares = require("./middlewares"); 10 | 11 | // import routes 12 | const indexRouter = require("./routes/index"); 13 | 14 | const app = express(); 15 | 16 | // Add Middlewares, routes 17 | AppMiddlewares(app); 18 | app.use("/", responseHeaders, indexRouter); 19 | 20 | // catch 404 and forward to error handler 21 | app.use(function (req, res, next) { 22 | next(createError(404)); 23 | }); 24 | 25 | // error handler 26 | app.use(function (err, req, res, _next) { 27 | if (isMulterError(err)) { 28 | return multerErrorHandling(err, req, res); 29 | } 30 | return res.boom.boomify(err, { 31 | statusCode: err.statusCode, 32 | }); 33 | }); 34 | 35 | module.exports = app; 36 | -------------------------------------------------------------------------------- /config/production.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set the environment specific config in this file. 3 | * Defaults set from default.js 4 | */ 5 | module.exports = { 6 | discordUnverifiedRoleId: "1103047289330745386", 7 | discordDeveloperRoleId: "915490782939582485", 8 | discordNewComersChannelId: "709080951824842783", 9 | discordMavenRoleId: "875564640438997043", 10 | discordMissedUpdatesRoleId: "1183553844811153458", 11 | userToken: { 12 | cookieName: "rds-session", 13 | cookieV2Name: "rds-session-v2", 14 | }, 15 | 16 | services: { 17 | goalAPI: { 18 | baseUrl: "https://goals-api.realdevsquad.com", 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /config/staging.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Set the environment specific config in this file. 3 | * Defaults set from default.js 4 | */ 5 | module.exports = { 6 | discordUnverifiedRoleId: "1120875993771544687", 7 | discordDeveloperRoleId: "1121445071213056071", 8 | discordMavenRoleId: "1152361736456896586", 9 | discordMissedUpdatesRoleId: "1184201657404362772", 10 | discordNewComersChannelId: "896184507080769559", 11 | enableFileLogs: false, 12 | enableConsoleLogs: true, 13 | 14 | githubOauth: { 15 | clientId: "c4a84431feaf604e89d1", 16 | }, 17 | 18 | services: { 19 | rdsApi: { 20 | baseUrl: "https://staging-api.realdevsquad.com", 21 | }, 22 | goalAPI: { 23 | baseUrl: "https://staging-goals-api.realdevsquad.com", 24 | secretKey: "123456789", 25 | }, 26 | }, 27 | 28 | integrations: { 29 | newrelic: { 30 | appName: "RDS_API_staging", 31 | }, 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /constants/answers.ts: -------------------------------------------------------------------------------- 1 | const ANSWER_STATUS = { 2 | PENDING: "PENDING", 3 | APPROVED:"APPROVED", 4 | REJECTED: "REJECTED", 5 | } 6 | 7 | module.exports = { ANSWER_STATUS } -------------------------------------------------------------------------------- /constants/application.ts: -------------------------------------------------------------------------------- 1 | const APPLICATION_STATUS_TYPES = ["accepted", "rejected", "pending"]; 2 | 3 | const API_RESPONSE_MESSAGES = { 4 | APPLICATION_RETURN_SUCCESS: "Applications returned successfully", 5 | }; 6 | 7 | module.exports = { APPLICATION_STATUS_TYPES, API_RESPONSE_MESSAGES }; 8 | -------------------------------------------------------------------------------- /constants/authorities.ts: -------------------------------------------------------------------------------- 1 | const AUTHORITIES = { 2 | SUPERUSER: "super_user", 3 | MEMBER: "member", 4 | USER: "user", 5 | }; 6 | 7 | module.exports = { AUTHORITIES }; 8 | -------------------------------------------------------------------------------- /constants/badges.ts: -------------------------------------------------------------------------------- 1 | const ERROR_MESSAGES = { 2 | MODELS: { 3 | REMOVE_BADGES: "Error removing badges", 4 | ASSIGN_BADGES: "Error assigning badges", 5 | CREATE_BADGE: "Error creating badge", 6 | FETCH_USER_BADGES: "Error fetching all user badges", 7 | FETCH_BADGES: "Error fetching badges", 8 | }, 9 | CONTROLLERS: { 10 | DELETE_USER_BADGES: "Failed to remove badges", 11 | POST_USER_BADGES: "Failed to assign badges", 12 | POST_BADGE: "Failed to create badge", 13 | GET_USER_BADGES: "Failed to get user badges", 14 | GET_BADGES: "Failed to get all badges.", 15 | }, 16 | MISC: { 17 | USER_ID_DOES_NOT_EXIST: "The User-Id does not exsit", 18 | UNAUTHENTICATED_USER: "Unauthenticated User", 19 | }, 20 | VALIDATORS: { 21 | CREATE_BADGE: { 22 | FILE_IS_MISSING: "Badge image file is missing", 23 | VALIDATON_FAILED: "Error validating createBadge payload", 24 | }, 25 | ASSIGN_OR_REMOVE_BADGES: { 26 | VALIDATON_FAILED: "Error validating assign or remove badges payload", 27 | }, 28 | API_PAYLOAD_VALIDATION_FAILED: "API payload failed validation", 29 | }, 30 | }; 31 | 32 | const SUCCESS_MESSAGES = { 33 | CONTROLLERS: { 34 | DELETE_USER_BADGES: "Badges removed successfully", 35 | POST_USER_BADGES: "Badges assigned successfully", 36 | POST_BADGE: "Badge created successfully", 37 | GET_USER_BADGES: "User Badges returned succesfully", 38 | GET_BADGES: "Badges returned successfully", 39 | }, 40 | }; 41 | 42 | module.exports = { 43 | ERROR_MESSAGES, 44 | SUCCESS_MESSAGES, 45 | }; 46 | -------------------------------------------------------------------------------- /constants/bot.ts: -------------------------------------------------------------------------------- 1 | const CLOUDFLARE_WORKER = "Cloudflare Worker"; 2 | const BAD_TOKEN = "BAD.JWT.TOKEN"; 3 | const CRON_JOB_HANDLER = "Cron Job Handler"; 4 | 5 | const Services = { 6 | CLOUDFLARE_WORKER: CLOUDFLARE_WORKER, 7 | CRON_JOB_HANDLER: CRON_JOB_HANDLER, 8 | }; 9 | 10 | module.exports = { CLOUDFLARE_WORKER, BAD_TOKEN, CRON_JOB_HANDLER, Services }; 11 | -------------------------------------------------------------------------------- /constants/cacheKeys.ts: -------------------------------------------------------------------------------- 1 | const ALL_TASKS = "cache:ALL-TASKS"; 2 | 3 | module.exports = { ALL_TASKS }; 4 | -------------------------------------------------------------------------------- /constants/cloudflareCache.ts: -------------------------------------------------------------------------------- 1 | const MAX_CACHE_PURGE_COUNT = 3; 2 | const CLOUDFLARE_ZONE_ID = config.get("cloudflare.CLOUDFLARE_ZONE_ID"); 3 | const CLOUDFLARE_PURGE_CACHE_API = `https://api.cloudflare.com/client/v4/zones/${CLOUDFLARE_ZONE_ID}/purge_cache`; 4 | 5 | module.exports = { MAX_CACHE_PURGE_COUNT, CLOUDFLARE_PURGE_CACHE_API }; 6 | -------------------------------------------------------------------------------- /constants/cloudinary.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | PROFILE: { 3 | FOLDER: "/profile/", 4 | TAGS: ["profile", "user"], 5 | TRANSFORMATIONS: { 6 | transformation: [ 7 | { 8 | quality: "auto", 9 | fetch_format: "auto", 10 | }, 11 | ], 12 | }, 13 | }, 14 | BADGE: { 15 | FOLDER: "/badge/", 16 | TAGS: ["badge", "badges"], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /constants/constants.ts: -------------------------------------------------------------------------------- 1 | const DOCUMENT_WRITE_SIZE = 500; 2 | const daysOfWeek = { 3 | sun: 0, 4 | mon: 1, 5 | tue: 2, 6 | wed: 3, 7 | thu: 4, 8 | fri: 5, 9 | sat: 6, 10 | }; 11 | 12 | //This is the headers used in server sent events APIs 13 | const HEADERS_FOR_SSE = { 14 | "Content-Type": "text/event-stream", 15 | Connection: "keep-alive", 16 | "Cache-Control": "no-cache", 17 | }; 18 | 19 | module.exports = { 20 | DOCUMENT_WRITE_SIZE, 21 | daysOfWeek, 22 | HEADERS_FOR_SSE, 23 | }; 24 | -------------------------------------------------------------------------------- /constants/errorMessages.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | INTERNAL_SERVER_ERROR: "An internal server error occurred", 3 | SOMETHING_WENT_WRONG: "Something went wrong. Please try again or contact admin", 4 | ONLY_IMAGE_SUPPORTED: "Only image/jpeg, image/png supported", 5 | ONLY_ONE_FILE_ALLOWED: "Only one file allowed", 6 | DATA_ADDED_SUCCESSFULLY: "User Device Info added successfully!", 7 | USER_DATA_ALREADY_PRESENT: "The authentication document has already been created", 8 | BAD_REQUEST: "BAD_REQUEST", 9 | INVALID_QUERY_PARAM: "Invalid Query Parameters Passed", 10 | FILE_TOO_LARGE: (size) => `File too large, max accepted size is ${size} MB`, 11 | USER_DOES_NOT_EXIST_ERROR: "User does not exist!", 12 | }; 13 | -------------------------------------------------------------------------------- /constants/events.ts: -------------------------------------------------------------------------------- 1 | const API_100MS_BASE_URL = "https://api.100ms.live/v2"; 2 | const GET_ALL_EVENTS_LIMIT_MIN = 10; 3 | const UNWANTED_PROPERTIES_FROM_100MS = [ 4 | "customer_id", 5 | "app_id", 6 | "recording_info", 7 | "template_id", 8 | "template", 9 | "customer", 10 | ]; 11 | 12 | const EVENT_ROLES = { 13 | HOST: "host", 14 | MODERATOR: "moderator", 15 | MAVEN: "maven", 16 | GUEST: "guest", 17 | }; 18 | 19 | module.exports = { API_100MS_BASE_URL, GET_ALL_EVENTS_LIMIT_MIN, UNWANTED_PROPERTIES_FROM_100MS, EVENT_ROLES }; 20 | -------------------------------------------------------------------------------- /constants/extensionRequests.ts: -------------------------------------------------------------------------------- 1 | const EXTENSION_REQUEST_STATUS = { 2 | PENDING: "PENDING", 3 | APPROVED: "APPROVED", 4 | DENIED: "DENIED", 5 | }; 6 | 7 | module.exports = { 8 | EXTENSION_REQUEST_STATUS, 9 | }; 10 | -------------------------------------------------------------------------------- /constants/external-accounts.ts: -------------------------------------------------------------------------------- 1 | const EXTERNAL_ACCOUNTS_POST_ACTIONS = { 2 | DISCORD_USERS_SYNC: "discord-users-sync", 3 | }; 4 | 5 | module.exports = { 6 | EXTERNAL_ACCOUNTS_POST_ACTIONS, 7 | }; 8 | -------------------------------------------------------------------------------- /constants/firebase.ts: -------------------------------------------------------------------------------- 1 | const BATCH_SIZE_IN_CLAUSE = 30; // since only 30 comparision values are allowed in 'in' clause 2 | 3 | module.exports = { 4 | BATCH_SIZE_IN_CLAUSE, 5 | }; 6 | -------------------------------------------------------------------------------- /constants/imageVerificationTypes.ts: -------------------------------------------------------------------------------- 1 | const IMAGE_VERIFICATION_TYPES = ["profile", "discord"]; 2 | 3 | module.exports = { IMAGE_VERIFICATION_TYPES }; 4 | -------------------------------------------------------------------------------- /constants/items.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | TYPES: ["TASK", "USER"], 3 | }; 4 | -------------------------------------------------------------------------------- /constants/logs.ts: -------------------------------------------------------------------------------- 1 | import { REQUEST_LOG_TYPE } from "./requests"; 2 | 3 | export const logType = { 4 | PROFILE_DIFF_APPROVED: "PROFILE_DIFF_APPROVED", 5 | PROFILE_DIFF_REJECTED: "PROFILE_DIFF_REJECTED", 6 | CLOUDFLARE_CACHE_PURGED: "CLOUDFLARE_CACHE_PURGED", 7 | APPLICATION_UPDATED: "USER_APPLICATION_UPDATED", 8 | EVENTS_REMOVE_PEER: "EVENTS_REMOVE_PEER", 9 | APPLICATION_ADDED: "USER_APPLICATION_ADDED", 10 | TASKS_MISSED_UPDATES_ERRORS: "TASKS_MISSED_UPDATES_ERRORS", 11 | DISCORD_INVITES: "DISCORD_INVITES", 12 | EXTERNAL_SERVICE: "EXTERNAL_SERVICE", 13 | ADD_UNVERIFIED_ROLE: "ADD_UNVERIFIED_ROLE", 14 | REMOVE_ROLE_FROM_USER_SUCCESS: "REMOVE_ROLE_FROM_USER_SUCCESS", 15 | REMOVE_ROLE_FROM_USER_FAILED: "REMOVE_ROLE_FROM_USER_FAILED", 16 | EXTENSION_REQUESTS: "extensionRequests", 17 | TASK: "task", 18 | TASK_REQUESTS: "taskRequests", 19 | USER_DETAILS_UPDATED: "USER_DETAILS_UPDATED", 20 | REQUEST_DOES_NOT_EXIST:"REQUEST_DOES_NOT_EXIST", 21 | UNAUTHORIZED_TO_UPDATE_REQUEST: "UNAUTHORIZED_TO_UPDATE_REQUEST", 22 | INVALID_REQUEST_TYPE: "INVALID_REQUEST_TYPE", 23 | PENDING_REQUEST_CAN_BE_UPDATED: "PENDING_REQUEST_CAN_BE_UPDATED", 24 | INVALID_REQUEST_DEADLINE: "INVALID_REQUEST_DEADLINE", 25 | USER_STATUS_NOT_FOUND: "USER_STATUS_NOT_FOUND", 26 | OOO_STATUS_FOUND: "OOO_STATUS_FOUND", 27 | ...REQUEST_LOG_TYPE, 28 | }; 29 | 30 | export const ALL_LOGS_FETCHED_SUCCESSFULLY = "All Logs fetched successfully"; 31 | export const LOGS_FETCHED_SUCCESSFULLY = "Logs fetched successfully"; 32 | export const ERROR_WHILE_FETCHING_LOGS = "Error while fetching logs"; 33 | -------------------------------------------------------------------------------- /constants/monitor.ts: -------------------------------------------------------------------------------- 1 | const RESOURCE_CREATED_SUCCESSFULLY = "Resource created successfully."; 2 | const RESOURCE_UPDATED_SUCCESSFULLY = "Resource updated successfully."; 3 | const RESOURCE_RETRIEVED_SUCCESSFULLY = "Resource retrieved successfully."; 4 | const RESOURCE_NOT_FOUND = "Resource not found."; 5 | const RESOURCE_ALREADY_TRACKED = "Resource is already being tracked."; 6 | 7 | const RESPONSE_MESSAGES = { 8 | RESOURCE_CREATED_SUCCESSFULLY, 9 | RESOURCE_UPDATED_SUCCESSFULLY, 10 | RESOURCE_RETRIEVED_SUCCESSFULLY, 11 | RESOURCE_NOT_FOUND, 12 | RESOURCE_ALREADY_TRACKED, 13 | }; 14 | 15 | module.exports = { RESPONSE_MESSAGES }; 16 | -------------------------------------------------------------------------------- /constants/multer.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | FILE_SIZE_1MB: 1_000_000, // in bytes, 1000000 bytes = 1MB 3 | PROFILE_FILE_SIZE: 2_000_000, // Limiting profile upload size to 2MB 4 | }; 5 | -------------------------------------------------------------------------------- /constants/profileDiff.ts: -------------------------------------------------------------------------------- 1 | const profileDiffStatus = { 2 | APPROVED: "APPROVED", 3 | REJECTED: "NOT APPROVED", 4 | PENDING: "PENDING", 5 | }; 6 | 7 | module.exports = { profileDiffStatus }; 8 | -------------------------------------------------------------------------------- /constants/progresses.ts: -------------------------------------------------------------------------------- 1 | const PROGRESS_DOCUMENT_CREATED_SUCCEEDED = "Progress document created successfully."; 2 | const PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED = "Progress document retrieved successfully."; 3 | const PROGRESS_DOCUMENT_NOT_FOUND = "No progress records found."; 4 | const PROGRESS_ALREADY_CREATED = "Progress for the day has already been created."; 5 | const MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000; 6 | const INTERNAL_SERVER_ERROR_MESSAGE = 7 | "The server has encountered an unexpected error. Please contact the administrator for more information."; 8 | 9 | const PROGRESSES_RESPONSE_MESSAGES = { 10 | PROGRESS_DOCUMENT_CREATED_SUCCEEDED, 11 | PROGRESS_DOCUMENT_RETRIEVAL_SUCCEEDED, 12 | PROGRESS_DOCUMENT_NOT_FOUND, 13 | PROGRESS_ALREADY_CREATED, 14 | }; 15 | 16 | const TYPE_MAP = { 17 | user: "userId", 18 | task: "taskId", 19 | }; 20 | const PROGRESS_VALID_SORT_FIELDS = ["date", "-date"]; 21 | const PROGRESSES_SIZE = 20; 22 | const PROGRESSES_PAGE_SIZE = 0; 23 | const VALID_PROGRESS_TYPES = ["task", "user"]; 24 | 25 | const UNAUTHORIZED_WRITE = "Unauthorized to write progress of task"; 26 | 27 | module.exports = { 28 | PROGRESSES_RESPONSE_MESSAGES, 29 | MILLISECONDS_IN_DAY, 30 | INTERNAL_SERVER_ERROR_MESSAGE, 31 | TYPE_MAP, 32 | VALID_PROGRESS_TYPES, 33 | PROGRESS_VALID_SORT_FIELDS, 34 | PROGRESSES_SIZE, 35 | PROGRESSES_PAGE_SIZE, 36 | UNAUTHORIZED_WRITE, 37 | }; 38 | -------------------------------------------------------------------------------- /constants/rateLimiting.ts: -------------------------------------------------------------------------------- 1 | const TOO_MANY_REQUESTS = { 2 | ERROR_TYPE: "Too Many Requests", 3 | STATUS_CODE: 429, 4 | }; 5 | 6 | module.exports = { 7 | TOO_MANY_REQUESTS, 8 | }; 9 | -------------------------------------------------------------------------------- /constants/roles.ts: -------------------------------------------------------------------------------- 1 | // Use Roles with authorizeRoles middleware 2 | const ROLES = { 3 | SUPERUSER: "super_user", 4 | APPOWNER: "app_owner", 5 | MEMBER: "member", 6 | ARCHIVED: "archived", 7 | INDISCORD: "in_discord", 8 | }; 9 | 10 | module.exports = ROLES; 11 | -------------------------------------------------------------------------------- /constants/subscription-validator.ts: -------------------------------------------------------------------------------- 1 | export const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/; 2 | export const phoneNumberRegex = /^[+]{1}(?:[0-9\-\\(\\)\\/.]\s?){6,15}[0-9]{1}$/; -------------------------------------------------------------------------------- /constants/tags.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | TYPES: ["TASK", "USER", "SKILL"], 3 | }; 4 | -------------------------------------------------------------------------------- /constants/taskRequests.ts: -------------------------------------------------------------------------------- 1 | const TASK_REQUEST_STATUS = { 2 | WAITING: "WAITING", 3 | APPROVED: "APPROVED", 4 | PENDING: "PENDING", 5 | DENIED: "DENIED", 6 | }; 7 | 8 | const TASK_REQUEST_ACTIONS = { 9 | APPROVE: "approve", 10 | REJECT: "reject", 11 | }; 12 | const TASK_REQUEST_ERROR_MESSAGE = { 13 | INVALID_PREV: "Invalid 'prev' value", 14 | INVALID_NEXT: "Invalid 'next' value", 15 | }; 16 | export const TASK_REQUEST_TYPE = { 17 | ASSIGNMENT: "ASSIGNMENT", 18 | CREATION: "CREATION", 19 | }; 20 | const TASK_REQUEST_FILTER_KEYS = { 21 | status: "status", 22 | "request-type": "requestType", 23 | }; 24 | const TASK_REQUEST_FILTER_VALUES = { 25 | pending: "PENDING", 26 | approved: "APPROVED", 27 | denied: "DENIED", 28 | assignment: "ASSIGNMENT", 29 | creation: "CREATION", 30 | }; 31 | const TASK_REQUEST_SORT_KEYS = { 32 | created: "createdAt", 33 | requestors: "usersCount", 34 | }; 35 | const TASK_REQUEST_SORT_VALUES = { 36 | asc: "asc", 37 | desc: "desc", 38 | }; 39 | const MIGRATION_TYPE = { 40 | ADD_NEW_FIELDS: "add-new-fields", 41 | REMOVE_OLD_FIELDS: "remove-redundant-fields", 42 | ADD_COUNT_CREATED: "add-count-created-time" 43 | }; 44 | 45 | module.exports = { 46 | TASK_REQUEST_STATUS, 47 | TASK_REQUEST_TYPE, 48 | TASK_REQUEST_FILTER_KEYS, 49 | TASK_REQUEST_FILTER_VALUES, 50 | TASK_REQUEST_SORT_KEYS, 51 | TASK_REQUEST_ERROR_MESSAGE, 52 | TASK_REQUEST_SORT_VALUES, 53 | MIGRATION_TYPE, 54 | TASK_REQUEST_ACTIONS, 55 | }; 56 | -------------------------------------------------------------------------------- /constants/tasks.ts: -------------------------------------------------------------------------------- 1 | const TASK_TYPE = { 2 | FEATURE: "feature", 3 | GROUP: "group", 4 | STORY: "story", 5 | }; 6 | 7 | const TASK_STATUS = { 8 | AVAILABLE: "AVAILABLE", 9 | ASSIGNED: "ASSIGNED", 10 | COMPLETED: "COMPLETED", 11 | IN_PROGRESS: "IN_PROGRESS", 12 | BLOCKED: "BLOCKED", 13 | SMOKE_TESTING: "SMOKE_TESTING", 14 | NEEDS_REVIEW: "NEEDS_REVIEW", 15 | IN_REVIEW: "IN_REVIEW", 16 | APPROVED: "APPROVED", 17 | MERGED: "MERGED", 18 | SANITY_CHECK: "SANITY_CHECK", 19 | REGRESSION_CHECK: "REGRESSION_CHECK", 20 | RELEASED: "RELEASED", 21 | VERIFIED: "VERIFIED", 22 | DONE: "DONE", 23 | OVERDUE: "OVERDUE", 24 | BACKLOG: "BACKLOG", 25 | }; 26 | 27 | // TODO: convert this to new task status 28 | const TASK_STATUS_OLD = { 29 | OLD_ACTIVE: "active", 30 | OLD_BLOCKED: "blocked", 31 | OLD_PENDING: "pending", 32 | OLD_COMPLETED: "completed", 33 | }; 34 | const DEFAULT_TASK_PRIORITY = "TBD"; 35 | 36 | const MAPPED_TASK_STATUS = { 37 | ...TASK_STATUS, 38 | UNASSIGNED: "AVAILABLE", 39 | }; 40 | 41 | const COMPLETED_TASK_STATUS = { 42 | VERIFIED: "VERIFIED", 43 | DONE: "DONE", 44 | COMPLETED: "COMPLETED", 45 | }; 46 | const TASK_SIZE = 5; 47 | 48 | const tasksUsersStatus = { 49 | MISSED_UPDATES: "missed-updates", 50 | }; 51 | 52 | module.exports = { 53 | TASK_TYPE, 54 | TASK_STATUS, 55 | TASK_STATUS_OLD, 56 | MAPPED_TASK_STATUS, 57 | TASK_SIZE, 58 | DEFAULT_TASK_PRIORITY, 59 | COMPLETED_TASK_STATUS, 60 | tasksUsersStatus, 61 | }; 62 | -------------------------------------------------------------------------------- /constants/urls.ts: -------------------------------------------------------------------------------- 1 | export const GITHUB_URL = "https://github.com"; 2 | export const PROFILE_SVC_GITHUB_URL = "https://github.com/Real-Dev-Squad/sample-profile-service"; 3 | 4 | module.exports = { 5 | GITHUB_URL, 6 | PROFILE_SVC_GITHUB_URL, 7 | }; 8 | -------------------------------------------------------------------------------- /constants/userDataLevels.ts: -------------------------------------------------------------------------------- 1 | const ACCESS_LEVEL = { 2 | PUBLIC: "public", 3 | INTERNAL: "internal", 4 | PRIVATE: "private", 5 | CONFIDENTIAL: "confidential", 6 | }; 7 | 8 | const ROLE_LEVEL = { 9 | private: ["super_user"], 10 | internal: ["super_user", "cloudfare_worker"], 11 | confidential: ["super_user"], 12 | }; 13 | 14 | const KEYS_NOT_ALLOWED = { 15 | public: ["email", "phone", "chaincode"], 16 | internal: ["phone", "chaincode"], 17 | private: ["chaincode"], 18 | confidential: [], 19 | }; 20 | 21 | module.exports = { ACCESS_LEVEL, KEYS_NOT_ALLOWED, ROLE_LEVEL }; 22 | -------------------------------------------------------------------------------- /constants/userStatus.ts: -------------------------------------------------------------------------------- 1 | export const userState = { 2 | ACTIVE: "ACTIVE", 3 | IDLE: "IDLE", 4 | OOO: "OOO", 5 | ONBOARDING: "ONBOARDING", 6 | }; 7 | 8 | export const statusState = { 9 | UPCOMING: "UPCOMING", 10 | APPLIED: "APPLIED", 11 | NOT_APPLIED: "NOT_APPLIED", 12 | }; 13 | 14 | export const CANCEL_OOO = "cancelOoo"; 15 | -------------------------------------------------------------------------------- /constants/wallets.ts: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | DINERO: "dinero", 3 | NEELAM: "neelam", 4 | INITIAL_WALLET: { 5 | dinero: 1000, 6 | }, 7 | }; 8 | -------------------------------------------------------------------------------- /controllers/contributions.js: -------------------------------------------------------------------------------- 1 | const contributionsService = require("../services/contributions"); 2 | const { SOMETHING_WENT_WRONG } = require("../constants/errorMessages"); 3 | const dataAccess = require("../services/dataAccessLayer"); 4 | /** 5 | * Get the contributions of the user 6 | * @param {Object} req - Express request object 7 | * @param {Object} res - Express response object 8 | */ 9 | 10 | const getUserContributions = async (req, res) => { 11 | try { 12 | const { username } = req.params; 13 | const result = await dataAccess.retrieveUsers({ username: req.params.username }); 14 | if (result.userExists) { 15 | const contributions = await contributionsService.getUserContributions(username); 16 | return res.json(contributions); 17 | } 18 | return res.boom.notFound("User doesn't exist"); 19 | } catch (err) { 20 | logger.error(`Error while retriving contributions ${err}`); 21 | return res.boom.badImplementation(SOMETHING_WENT_WRONG); 22 | } 23 | }; 24 | 25 | module.exports = { 26 | getUserContributions, 27 | }; 28 | -------------------------------------------------------------------------------- /controllers/fcmToken.js: -------------------------------------------------------------------------------- 1 | const { saveFcmToken } = require("../models/fcmToken"); 2 | const { Conflict } = require("http-errors"); 3 | 4 | /** 5 | * Route used to get the health status of teh server 6 | * 7 | * @param req {Object} - Express request object 8 | * @param res {Object} - Express response object 9 | */ 10 | const fcmTokenController = async (req, res) => { 11 | try { 12 | const { fcmToken } = req.body; 13 | 14 | const fcmTokenId = await saveFcmToken({ userId: req.userData.id, fcmToken }); 15 | if (fcmTokenId) res.status(200).json({ status: 200, message: "Device registered successfully" }); 16 | } catch (error) { 17 | if (error instanceof Conflict) { 18 | return res.status(409).json({ 19 | message: error.message, 20 | }); 21 | } 22 | res.status(500).send("Something went wrong, please contact admin"); 23 | } 24 | return res.status(500).send("Internal server error"); 25 | }; 26 | 27 | module.exports = { 28 | fcmTokenController, 29 | }; 30 | -------------------------------------------------------------------------------- /controllers/goals.js: -------------------------------------------------------------------------------- 1 | const { SOMETHING_WENT_WRONG } = require("../constants/errorMessages"); 2 | const goals = require("../services/goalService"); 3 | 4 | const getGoalSiteToken = async (req, res) => { 5 | try { 6 | const { roles, id: userId } = req.userData; 7 | 8 | const goalApiResponse = await goals.getOrCreateGoalUser({ userId, roles }); 9 | 10 | if (goalApiResponse.status === 201) { 11 | let goalApiData = await goalApiResponse.json(); 12 | goalApiData = goalApiData.data; 13 | const userData = goalApiData.attributes; 14 | return res.status(200).json({ message: "success", user: { ...userData, id: goalApiData.id } }); 15 | } 16 | return res.status(goalApiResponse.status).json({ message: "error" }); 17 | } catch { 18 | return res.boom.badImplementation(SOMETHING_WENT_WRONG); 19 | } 20 | }; 21 | 22 | module.exports = { 23 | getGoalSiteToken, 24 | }; 25 | -------------------------------------------------------------------------------- /controllers/health.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Route used to get the health status of teh server 3 | * 4 | * @param req {Object} - Express request object 5 | * @param res {Object} - Express response object 6 | */ 7 | const healthCheck = (req, res) => { 8 | return res.json({ 9 | uptime: process.uptime(), 10 | }); 11 | }; 12 | 13 | module.exports = { 14 | healthCheck, 15 | }; 16 | -------------------------------------------------------------------------------- /controllers/recruiters.js: -------------------------------------------------------------------------------- 1 | const recruiterQuery = require("../models/recruiters"); 2 | const { INTERNAL_SERVER_ERROR, SOMETHING_WENT_WRONG } = require("../constants/errorMessages"); 3 | 4 | /** 5 | * Posts the data about the recruiter 6 | * 7 | * @param req {Object} - Express request object 8 | * @param res {Object} - Express response object 9 | */ 10 | 11 | const addRecruiter = async (req, res) => { 12 | try { 13 | const result = await recruiterQuery.addRecruiterInfo(req.body, req.params.username); 14 | if (!result) { 15 | return res.boom.notFound("User doesn't exist"); 16 | } 17 | return res.json({ 18 | message: "Request Submission Successful!!", 19 | result, 20 | }); 21 | } catch (error) { 22 | logger.error(`Error while adding recruiterInfo: ${error}`); 23 | return res.boom.serverUnavailable(SOMETHING_WENT_WRONG); 24 | } 25 | }; 26 | 27 | /** 28 | * Fetch all the recruiters information 29 | * 30 | * @param req {Object} - Express request object 31 | * @param res {Object} - Express response object 32 | */ 33 | const fetchRecruitersInfo = async (req, res) => { 34 | try { 35 | const allRecruiter = await recruiterQuery.fetchRecruitersInfo(); 36 | return res.json({ 37 | message: "Recruiters returned successfully!", 38 | recruiters: allRecruiter.length > 0 ? allRecruiter : [], 39 | }); 40 | } catch (error) { 41 | logger.error(`Error while fetching recruiters: ${error}`); 42 | return res.boom.badImplementation(INTERNAL_SERVER_ERROR); 43 | } 44 | }; 45 | 46 | module.exports = { 47 | addRecruiter, 48 | fetchRecruitersInfo, 49 | }; 50 | -------------------------------------------------------------------------------- /controllers/trading.js: -------------------------------------------------------------------------------- 1 | const tradeService = require("../services/tradingService"); 2 | const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages"); 3 | /** 4 | * New Trading Request 5 | * 6 | * @param req {Object} - Express request object 7 | * @param res {Object} - Express response object 8 | */ 9 | const trade = async (req, res) => { 10 | try { 11 | const { id: userId, username } = req.userData; 12 | const tradeStockData = { 13 | ...req.body, 14 | username, 15 | userId, 16 | }; 17 | const { canUserTrade, errorMessage, userBalance } = await tradeService.trade(tradeStockData); 18 | 19 | if (!canUserTrade) { 20 | return res.boom.forbidden(errorMessage); 21 | } 22 | return res.json({ message: "Congrats, Stock Trade done successfully!! 🎉🎉🎉🎊🎊🎊", userBalance }); 23 | } catch (err) { 24 | logger.error(`Error during trading: ${err}`); 25 | return res.boom.badImplementation(INTERNAL_SERVER_ERROR); 26 | } 27 | }; 28 | 29 | module.exports = { 30 | trade, 31 | }; 32 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "emulators": { 3 | "firestore": { 4 | "host": "0.0.0.0", 5 | "port": 8081 6 | }, 7 | "ui": { 8 | "host": "0.0.0.0", 9 | "enabled": true, 10 | "port": 4000 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /middlewares/assignTask.js: -------------------------------------------------------------------------------- 1 | const { fetchSkillLevelTask } = require("../models/tasks"); 2 | const firestore = require("../utils/firestore"); 3 | const tasks = firestore.collection("tasks"); 4 | 5 | const assignTask = async function (req, res) { 6 | try { 7 | // this hardcoded value will be removed once we have user skill 8 | const { task } = await fetchSkillLevelTask("FRONTEND", 1); 9 | if (!task) return res.json({ message: "Task updated but another task not found" }); 10 | 11 | const docId = task.id; 12 | const userId = req.userData.id; 13 | await tasks.doc(docId).set({ assignee: userId, status: "ASSIGNED" }, { merge: true }); 14 | return res.json({ message: "task updated and another task got assigned" }); 15 | } catch { 16 | return res.boom.badImplementation("Something went wrong!"); 17 | } 18 | }; 19 | 20 | module.exports = assignTask; 21 | -------------------------------------------------------------------------------- /middlewares/authenticateProfile.js: -------------------------------------------------------------------------------- 1 | const authenticateProfile = (authenticate) => { 2 | return async (req, res, next) => { 3 | if (req.query.profile === "true") { 4 | return await authenticate(req, res, next); 5 | } 6 | return next(); 7 | }; 8 | }; 9 | 10 | module.exports = authenticateProfile; 11 | -------------------------------------------------------------------------------- /middlewares/authinticateServiceRequest.ts: -------------------------------------------------------------------------------- 1 | import { verifyAuthToken } from "../utils/verifyAuthToken"; 2 | import { CustomResponse } from "../types/global"; 3 | import { NextFunction } from "express"; 4 | import { InviteBodyRequest } from "../types/invites"; 5 | 6 | const authinticateServiceRequest = async (req: InviteBodyRequest, res: CustomResponse, next: NextFunction) => { 7 | try { 8 | const authHeader = req.headers?.authorization; 9 | if (!authHeader) { 10 | return res.boom.unauthorized(); 11 | } 12 | if (!authHeader.startsWith("Bearer ")) { 13 | return res.boom.unauthorized(); 14 | } 15 | const token = authHeader.split(" ")[1]; 16 | const isValid = await verifyAuthToken(token); 17 | if (!isValid) { 18 | return res.boom.unauthorized(); 19 | } 20 | next(); 21 | } catch (error) { 22 | logger.error("Internal server error", error); 23 | return res.boom.internal(error); 24 | } 25 | }; 26 | 27 | export default authinticateServiceRequest; 28 | -------------------------------------------------------------------------------- /middlewares/authorizeBot.js: -------------------------------------------------------------------------------- 1 | const botVerifcation = require("../services/botVerificationService"); 2 | const { CLOUDFLARE_WORKER, CRON_JOB_HANDLER } = require("../constants/bot"); 3 | 4 | const verifyCronJob = async (req, res, next) => { 5 | try { 6 | const token = req.headers.authorization.split(" ")[1]; 7 | const data = botVerifcation.verifyCronJob(token); 8 | if (data.name !== CRON_JOB_HANDLER) { 9 | return res.boom.unauthorized("Cron job not verified"); 10 | } 11 | 12 | return next(); 13 | } catch (error) { 14 | return res.boom.badRequest("Unauthorized Cron Worker"); 15 | } 16 | }; 17 | 18 | const verifyDiscordBot = async (req, res, next) => { 19 | try { 20 | const token = req.headers.authorization.split(" ")[1]; 21 | const data = botVerifcation.verifyToken(token); 22 | 23 | if (data.name !== CLOUDFLARE_WORKER) { 24 | return res.boom.unauthorized("Unauthorized Bot"); 25 | } 26 | 27 | return next(); 28 | } catch (error) { 29 | if (error.message === "invalid token") { 30 | return res.boom.unauthorized("Unauthorized Bot"); 31 | } 32 | return res.boom.badRequest("Invalid Request"); 33 | } 34 | }; 35 | 36 | module.exports = { verifyDiscordBot, verifyCronJob }; 37 | -------------------------------------------------------------------------------- /middlewares/authorizeOwnOrSuperUser.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This middleware authorizes if the requested resource belongs to that user or the user is a superuser 3 | * for that route. 4 | * Note: This must be added on routes after the `authenticate` middleware. 5 | * @param req {Object} - Express request object 6 | * @param res {Object} - Express response object 7 | * @param next {Function} - Express middleware function 8 | * @return {Object} - Returns unauthorized user if the role is not assigned 9 | * 10 | **/ 11 | 12 | import { NextFunction } from "express"; 13 | import { CustomRequest, CustomResponse } from "../types/global"; 14 | 15 | const authorizeOwnOrSuperUser = (req: CustomRequest, res: CustomResponse, next: NextFunction) => { 16 | try { 17 | const isSuperUser = req.userData.roles.super_user; 18 | const { id } = req.userData; 19 | const userIdInQuery = req.query.userId; 20 | 21 | if (isSuperUser || userIdInQuery === id) return next(); 22 | else return res.boom.forbidden("Unauthorized User"); 23 | } catch (err) { 24 | logger.error(err); 25 | return res.boom.badImplementation("Something went wrong please contact admin"); 26 | } 27 | }; 28 | 29 | export { authorizeOwnOrSuperUser }; 30 | -------------------------------------------------------------------------------- /middlewares/authorizeOwner.js: -------------------------------------------------------------------------------- 1 | const users = require("../controllers/users"); 2 | /** 3 | * Middleware to validate the authorized routes to be able to create & Update tasks 4 | * 1] Verifies the user's role as Application owner 5 | * * 2] In case of absence of user role, error is invoked 6 | * 7 | * The currently implemented mechanism satisfies the current use case. 8 | * Authentication with JWT and a refreshToken to be added once we have user permissions and authorizations to be handled 9 | * 10 | * 11 | * 12 | * @param req {Object} - Express request object 13 | * @param res {Object} - Express response object 14 | * @param next {Function} - Express middleware function 15 | * @return {Object} - Returns unauthorized user if the role is not assigned 16 | */ 17 | module.exports = async (req, res, next) => { 18 | try { 19 | // get user data from `req.userData` for further use 20 | const accountOwners = await users.getAccountOwners(); 21 | const { username } = req.userData; 22 | 23 | if (accountOwners.some((owner) => owner.username === username)) { 24 | return next(); 25 | } else { 26 | return res.boom.forbidden("Unauthorized User"); 27 | } 28 | } catch (err) { 29 | logger.error(err); 30 | return res.boom.badImplementation("Something went wrong please contact admin"); 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /middlewares/authorizeRoles.js: -------------------------------------------------------------------------------- 1 | const ROLES = require("../constants/roles"); 2 | 3 | /** 4 | * Create an authorization middleware for a route based on the required role needed 5 | * for that route. 6 | * Note: This must be added on routes after the `authenticate` middleware. 7 | * @param {Array.} allowedRoles - Roles allowed for a route. 8 | * @returns {Function} - A middleware function that authorizes given role. 9 | */ 10 | const authorizeRoles = (allowedRoles) => { 11 | return (req, res, next) => { 12 | const { roles = {} } = req.userData; 13 | 14 | const rolesAreValid = allowedRoles.every((role) => Object.values(ROLES).includes(role)); 15 | if (!rolesAreValid) { 16 | return res.boom.badImplementation("Route authorization failed. Please contact admin"); 17 | } 18 | 19 | const userHasPermission = allowedRoles.some((role) => roles[`${role}`]); 20 | if (!userHasPermission) { 21 | return res.boom.unauthorized("You are not authorized for this action."); 22 | } 23 | 24 | return next(); 25 | }; 26 | }; 27 | 28 | module.exports = authorizeRoles; 29 | -------------------------------------------------------------------------------- /middlewares/conditionalMiddleware.ts: -------------------------------------------------------------------------------- 1 | const conditionalMiddleware = (validator) => { 2 | return async (req, res, next) => { 3 | if (req.params.userId === req.userData.id && req.query.profile === "true") { 4 | return validator(req, res, next); 5 | } 6 | next(); 7 | }; 8 | }; 9 | 10 | module.exports = conditionalMiddleware; 11 | -------------------------------------------------------------------------------- /middlewares/contentTypeCheck.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware to validate the content-type header. 3 | * Only `application/json` content-type is supported by the API 4 | * 5 | * @param {object} req - Express request object 6 | * @param {object} res - Express response object 7 | * @param {function} next - Express middleware function 8 | */ 9 | module.exports = (req, res, next) => { 10 | const contentType = req.headers["content-type"]; 11 | if (contentType && contentType !== "application/json") { 12 | const notMultiPart = !contentType.includes("multipart/form-data"); 13 | if (notMultiPart) { 14 | return res.boom.unsupportedMediaType( 15 | `Invalid content-type header: ${contentType}, expected: application/json or multipart/form-data` 16 | ); 17 | } 18 | } 19 | 20 | return next(); 21 | }; 22 | -------------------------------------------------------------------------------- /middlewares/devFlag.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction } from "express"; 2 | import { CustomRequest, CustomResponse } from "../types/global"; 3 | 4 | export const devFlagMiddleware = (req: CustomRequest, res: CustomResponse, next: NextFunction) => { 5 | try { 6 | const dev = req.query.dev === "true"; 7 | if (!dev) { 8 | return res.boom.notFound("Route not found"); 9 | } 10 | next(); 11 | } catch (err) { 12 | logger.error("Error occurred in devFlagMiddleware:", err.message); 13 | next(err); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /middlewares/index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cookieParser = require("cookie-parser"); 3 | const morgan = require("morgan"); 4 | const boom = require("express-boom"); 5 | const helmet = require("helmet"); 6 | const cors = require("cors"); 7 | const passport = require("passport"); 8 | const contentTypeCheck = require("./contentTypeCheck"); 9 | 10 | // require middlewares 11 | require("./passport"); 12 | 13 | const middleware = (app) => { 14 | // Middleware for sending error responses with express response object. To be required above all middlewares 15 | app.use(boom()); 16 | 17 | // Initialise logging middleware 18 | app.use(morgan("combined", { stream: logger.stream })); 19 | 20 | // Request parsing middlewares 21 | app.use(express.json()); 22 | app.use(express.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | 25 | // Middleware to add security headers. Few headers have been disabled as it does not serve any purpose for the API. 26 | app.use( 27 | helmet({ 28 | contentSecurityPolicy: false, 29 | dnsPrefetchControl: false, 30 | ieNoOpen: false, 31 | referrerPolicy: false, 32 | xssFilter: false, 33 | }) 34 | ); 35 | 36 | app.use( 37 | cors({ 38 | origin: config.get("cors.allowedOrigins"), 39 | credentials: true, 40 | optionsSuccessStatus: 200, 41 | }) 42 | ); 43 | app.use(contentTypeCheck); 44 | 45 | // Initialise authentication middleware 46 | app.use(passport.initialize()); 47 | }; 48 | 49 | module.exports = middleware; 50 | -------------------------------------------------------------------------------- /middlewares/passport.js: -------------------------------------------------------------------------------- 1 | const passport = require("passport"); 2 | const GitHubStrategy = require("passport-github2").Strategy; 3 | const GoogleStrategy = require("passport-google-oauth20").Strategy; 4 | 5 | try { 6 | passport.use( 7 | new GitHubStrategy( 8 | { 9 | clientID: config.get("githubOauth.clientId"), 10 | clientSecret: config.get("githubOauth.clientSecret"), 11 | callbackURL: `${config.get("services.rdsApi.baseUrl")}/auth/github/callback`, 12 | }, 13 | (accessToken, refreshToken, profile, done) => { 14 | return done(null, accessToken, profile); 15 | } 16 | ) 17 | ); 18 | passport.use( 19 | new GoogleStrategy( 20 | { 21 | clientID: config.get("googleOauth.clientId"), 22 | clientSecret: config.get("googleOauth.clientSecret"), 23 | callbackURL: `${config.get("services.rdsApi.baseUrl")}/auth/google/callback`, 24 | }, 25 | (accessToken, refreshToken, profile, done) => { 26 | return done(null, accessToken, profile); 27 | } 28 | ) 29 | ); 30 | } catch (err) { 31 | logger.error("Error initialising passport:", err); 32 | } 33 | -------------------------------------------------------------------------------- /middlewares/responseHeaders.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware to attach Cache header. 3 | * https://support.cloudflare.com/hc/en-us/articles/200172516-Understanding-Cloudflare-s-CDN 4 | * @todo: Remove the middleware for all routes and modify cache max-age of each route individually as per required 5 | * 6 | * @param {object} req - Express request object 7 | * @param {object} res - Express response object 8 | * @param {function} next - Express middleware function 9 | */ 10 | 11 | /** 12 | * Read about CF and Cache headers here: https://developers.cloudflare.com/cache/about/cache-control 13 | * Cache assets with revalidation, but allow stale responses if origin server is unreachable 14 | * e.g Cache-Control: public, max-age=900, stale-if-error=600 15 | * With this configuration, Cloudflare attempts to revalidate the content with the origin server after it has been in cache for N seconds. 16 | * If the server returns an error instead of proper revalidation responses, Cloudflare continues serving the stale resource for a total M seconds beyond the expiration of the resource. 17 | */ 18 | 19 | module.exports = (req, res, next) => { 20 | try { 21 | const cacheExpiry = config.get("routesCacheTTL"); 22 | let cacheControl = "private, max-age=0"; 23 | const ttl = cacheExpiry[req.path]; 24 | 25 | if (ttl > 0) { 26 | cacheControl = `public, max-age=${ttl}, stale-if-error=300`; 27 | } 28 | 29 | res.header("Cache-Control", cacheControl); 30 | } catch (e) { 31 | logger.error(`Error finding TTL config:: ${e}`); 32 | } 33 | 34 | next(); 35 | }; 36 | -------------------------------------------------------------------------------- /middlewares/shortCircuit.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction } from "express"; 2 | import { CustomRequest, CustomResponse } from "../types/global"; 3 | 4 | export const disableRoute = (_req: CustomRequest, res: CustomResponse, _next: NextFunction) => { 5 | return res.boom.serverUnavailable( 6 | "This route has been temporarily disabled. If you need assistance, please reach out to the team." 7 | ); 8 | }; 9 | -------------------------------------------------------------------------------- /middlewares/skipAuthenticateForOnboardingExtension.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Request, Response } from "express" 2 | import { REQUEST_TYPE } from "../constants/requests"; 3 | /** 4 | * Middleware to selectively authenticate or verify Discord bot based on the request type. 5 | * Specifically handles requests for onboarding extensions by skipping authentication. 6 | * 7 | * @param {Function} authenticate - The authentication middleware to apply for general requests. 8 | * @param {Function} verifyDiscordBot - The middleware to verify requests from a Discord bot. 9 | * @returns {Function} A middleware function that processes the request based on its type. 10 | * 11 | * @example 12 | * app.use(skipAuthenticateForOnboardingExtensionRequest(authenticate, verifyDiscordBot)); 13 | */ 14 | export const skipAuthenticateForOnboardingExtensionRequest = (authenticate, verifyDiscordBot) => { 15 | return async (req: Request, res: Response, next: NextFunction) => { 16 | const type = req.body.type; 17 | 18 | if(type === REQUEST_TYPE.ONBOARDING){ 19 | return await verifyDiscordBot(req, res, next); 20 | } 21 | 22 | return await authenticate(req, res, next) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /middlewares/skipAuthorizeRolesWrapper.js: -------------------------------------------------------------------------------- 1 | const skipAuthorizeRolesUnderFF = (authorizeMiddleware) => { 2 | return (req, res, next) => { 3 | const { dev } = req.query; 4 | const isDev = dev === "true"; 5 | if (isDev) { 6 | next(); 7 | } else { 8 | authorizeMiddleware(req, res, next); 9 | } 10 | }; 11 | }; 12 | 13 | module.exports = skipAuthorizeRolesUnderFF; 14 | -------------------------------------------------------------------------------- /middlewares/taskRequests.js: -------------------------------------------------------------------------------- 1 | const { SOMETHING_WENT_WRONG } = require("../constants/errorMessages"); 2 | const dataAccess = require("../services/dataAccessLayer"); 3 | const { TASK_REQUEST_ACTIONS } = require("../constants/taskRequests"); 4 | /** 5 | * Validates user id for task request 6 | * 7 | * @param userId { string }: user id of the user 8 | */ 9 | async function validateUser(req, res, next) { 10 | try { 11 | const { action } = req.query; 12 | if (action === TASK_REQUEST_ACTIONS.REJECT) { 13 | return next(); 14 | } 15 | const { userId } = req.body; 16 | 17 | if (!userId) { 18 | return res.boom.badRequest("userId not provided"); 19 | } 20 | 21 | const { userExists, user } = await dataAccess.retrieveUsers({ id: userId }); 22 | if (!userExists) { 23 | return res.boom.conflict("User does not exist"); 24 | } 25 | 26 | req.body.user = user; 27 | 28 | return next(); 29 | } catch (err) { 30 | logger.error("Error while creating task request"); 31 | return res.boom.serverUnavailable(SOMETHING_WENT_WRONG); 32 | } 33 | } 34 | 35 | module.exports = { 36 | validateUser, 37 | }; 38 | -------------------------------------------------------------------------------- /middlewares/userAuthorization.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction } from "express"; 2 | import { CustomRequest, CustomResponse } from "../types/global"; 3 | 4 | export const userAuthorization = (req: CustomRequest, res: CustomResponse, next: NextFunction) => { 5 | if (req.params.userId === req.userData.id) { 6 | return next(); 7 | } 8 | res.boom.forbidden("Unauthorized access"); 9 | }; 10 | -------------------------------------------------------------------------------- /middlewares/validators/answers.ts: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | import { Request, NextFunction } from "express"; 3 | import { CustomResponse } from "../../typeDefinitions/global"; 4 | 5 | const createAnswer = async (req: Request, res: CustomResponse, next: NextFunction) => { 6 | const schema = joi.object({ 7 | answer: joi.string().required(), 8 | answeredBy: joi.string().required(), 9 | eventId: joi.string().required(), 10 | questionId: joi.string().required(), 11 | }); 12 | 13 | try { 14 | await schema.validateAsync(req.body); 15 | next(); 16 | } catch (error) { 17 | logger.error(`Error validating answer: ${error}`); 18 | res.boom.badRequest(error.details[0].message); 19 | } 20 | }; 21 | 22 | const updateAnswer = async (req: Request, res: CustomResponse, next: NextFunction) => { 23 | const schema = joi.object({ 24 | status: joi.string().optional(), 25 | }); 26 | 27 | try { 28 | await schema.validateAsync(req.body); 29 | next(); 30 | } catch (error) { 31 | logger.error(`Error validating answer: ${error}`); 32 | res.boom.badRequest(error.details[0].message); 33 | } 34 | }; 35 | 36 | module.exports = { createAnswer, updateAnswer }; 37 | -------------------------------------------------------------------------------- /middlewares/validators/arts.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | const createArt = async (req, res, next) => { 4 | const schema = joi 5 | .object() 6 | .strict() 7 | .keys({ 8 | title: joi.string().required(), 9 | price: joi.number().min(0).required(), 10 | css: joi.string().required(), 11 | }); 12 | try { 13 | await schema.validateAsync(req.body); 14 | next(); 15 | } catch (error) { 16 | logger.error(`Error creating art : ${error}`); 17 | res.boom.badRequest(error.details[0].message); 18 | } 19 | }; 20 | 21 | module.exports = { 22 | createArt, 23 | }; 24 | -------------------------------------------------------------------------------- /middlewares/validators/auctions.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | const createAuction = async (req, res, next) => { 4 | const schema = joi.object().strict().keys({ 5 | item_type: joi.string().required(), 6 | quantity: joi.number().required(), 7 | initial_price: joi.number().required(), 8 | end_time: joi.number().required(), 9 | }); 10 | try { 11 | await schema.validateAsync(req.body); 12 | next(); 13 | } catch (error) { 14 | logger.error(`Error creating auction : ${error}`); 15 | res.boom.badRequest(error.details[0].message); 16 | } 17 | }; 18 | 19 | const placeBid = async (req, res, next) => { 20 | const schema = joi.object().strict().keys({ 21 | bid: joi.number().required(), 22 | }); 23 | try { 24 | await schema.validateAsync(req.body); 25 | next(); 26 | } catch (error) { 27 | logger.error(`Error creating auction : ${error}`); 28 | res.boom.badRequest(error.details[0].message); 29 | } 30 | }; 31 | 32 | module.exports = { 33 | createAuction, 34 | placeBid, 35 | }; 36 | -------------------------------------------------------------------------------- /middlewares/validators/challenges.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | const createChallenge = async (req, res, next) => { 4 | const schema = joi.object().strict().keys({ 5 | title: joi.string().required(), 6 | level: joi.string().required(), 7 | start_date: joi.number().required(), 8 | end_date: joi.number().required(), 9 | }); 10 | 11 | try { 12 | await schema.validateAsync(req.body); 13 | next(); 14 | } catch (error) { 15 | logger.error(`Error validating createChallenge payload : ${error}`); 16 | res.boom.badRequest(error.details[0].message); 17 | } 18 | }; 19 | 20 | module.exports = { 21 | createChallenge, 22 | }; 23 | -------------------------------------------------------------------------------- /middlewares/validators/fcmToken.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | export const fcmTokenValidator = async (req, res, next) => { 4 | const schema = joi.object().strict().keys({ 5 | fcmToken: joi.string().required(), 6 | }); 7 | try { 8 | await schema.validateAsync(req.body); 9 | next(); 10 | } catch (error) { 11 | logger.error(`Bad request body : ${error}`); 12 | res.boom.badRequest(error.details[0].message); 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /middlewares/validators/invites.ts: -------------------------------------------------------------------------------- 1 | import joi from "joi"; 2 | import { NextFunction } from "express"; 3 | import { InviteBodyRequest } from "../../types/invites"; 4 | import {CustomResponse} from "../../types/global"; 5 | 6 | export const createInviteValidator = async (req: InviteBodyRequest, res: CustomResponse, next: NextFunction) => { 7 | const schema = joi 8 | .object() 9 | .strict() 10 | .keys({ 11 | userId: joi.string().required().messages({ 12 | "any.required": "userId is required", 13 | "string.empty": "userId cannot be empty", 14 | }), 15 | purpose: joi.string().required().messages({ 16 | "any.required": "purpose is required", 17 | "string.empty": "purpose cannot be empty", 18 | }), 19 | }); 20 | 21 | try { 22 | await schema.validateAsync(req.body, { abortEarly: false }); 23 | next(); 24 | } catch (error) { 25 | const errorMessages = error.details.map((detail) => detail.message); 26 | logger.error(`Error while validating invite creation payload: ${errorMessages}`); 27 | res.boom.badRequest(errorMessages); 28 | } 29 | }; 30 | 31 | -------------------------------------------------------------------------------- /middlewares/validators/items.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | 3 | const { TYPES } = require("../../constants/items"); 4 | 5 | const validateItemsPayload = async (req, res, next) => { 6 | const schema = Joi.object({ 7 | itemId: Joi.string().trim().required(), 8 | itemType: Joi.string() 9 | .uppercase() 10 | .custom((value, helper) => { 11 | if (!TYPES.includes(value)) return helper.message("Not a valid type"); 12 | return value; 13 | }), 14 | 15 | tagPayload: Joi.array().items( 16 | Joi.object({ 17 | tagId: Joi.string().required(), 18 | levelId: Joi.string().required(), 19 | }) 20 | ), 21 | }); 22 | 23 | try { 24 | await schema.validateAsync(req.body); 25 | next(); 26 | } catch (error) { 27 | logger.error(`Error validating adding tags payload : ${error}`); 28 | res.boom.badRequest(error.details[0].message); 29 | } 30 | }; 31 | 32 | const validateItemQuery = async (req, res, next) => { 33 | const schema = Joi.object().strict().keys({ 34 | itemType: Joi.string().uppercase().optional(), 35 | itemId: Joi.string().optional(), 36 | levelId: Joi.string().optional(), 37 | levelName: Joi.string().optional(), 38 | levelNumber: Joi.number().optional(), 39 | tagId: Joi.string().optional, 40 | tagType: Joi.string().uppercase().optional(), 41 | }); 42 | 43 | try { 44 | await schema.validateAsync(req.query); 45 | next(); 46 | } catch (error) { 47 | logger.error(`Error validating query params : ${error}`); 48 | res.boom.badRequest(error.details[0].message); 49 | } 50 | }; 51 | 52 | module.exports = { 53 | validateItemsPayload, 54 | validateItemQuery, 55 | }; 56 | -------------------------------------------------------------------------------- /middlewares/validators/levels.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | 3 | const validateLevelBody = async (req, res, next) => { 4 | const schema = Joi.object({ 5 | name: Joi.string().trim().required(), 6 | value: Joi.number().integer().min(0).required(), 7 | }); 8 | 9 | try { 10 | await schema.validateAsync(req.body); 11 | next(); 12 | } catch (error) { 13 | logger.error(`Error validating createLevel payload : ${error}`); 14 | res.boom.badRequest(error.details[0].message); 15 | } 16 | }; 17 | 18 | module.exports = { 19 | validateLevelBody, 20 | }; 21 | -------------------------------------------------------------------------------- /middlewares/validators/members.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | const validateGetMembers = async (req, res, next) => { 4 | const querySchema = joi.object().keys({ 5 | showArchived: joi.boolean().optional(), 6 | }); 7 | 8 | try { 9 | await querySchema.validateAsync(req.query); 10 | next(); 11 | } catch (error) { 12 | logger.error(`Error validating getMembers query params : ${error}`); 13 | res.boom.badRequest(error.details[0].message); 14 | } 15 | }; 16 | 17 | module.exports = { 18 | validateGetMembers, 19 | }; 20 | -------------------------------------------------------------------------------- /middlewares/validators/notify.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | export const notifyValidator = async (req, res, next) => { 4 | const MAX_TITLE_LENGTH = 512; 5 | const MAX_BODY_LENGTH = 1536; 6 | 7 | const schema = joi 8 | .object() 9 | .strict() 10 | .keys({ 11 | title: joi.string().required().max(MAX_TITLE_LENGTH).required(), 12 | body: joi.string().required().max(MAX_BODY_LENGTH).required(), 13 | userId: joi.string(), 14 | groupRoleId: joi.string(), 15 | }) 16 | .xor("userId", "groupRoleId"); 17 | try { 18 | await schema.validateAsync(req.body); 19 | next(); 20 | } catch (error) { 21 | logger.error(`Bad request body : ${error}`); 22 | res.boom.badRequest(error.details[0].message); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /middlewares/validators/oooRequests.ts: -------------------------------------------------------------------------------- 1 | import joi from "joi"; 2 | import { NextFunction } from "express"; 3 | import { REQUEST_STATE, REQUEST_TYPE } from "../../constants/requests"; 4 | import { OooRequestCreateRequest, OooRequestResponse } from "../../types/oooRequest"; 5 | 6 | export const createOooStatusRequestValidator = async ( 7 | req: OooRequestCreateRequest, 8 | res: OooRequestResponse, 9 | next: NextFunction 10 | ) => { 11 | const schema = joi 12 | .object() 13 | .strict() 14 | .keys({ 15 | from: joi 16 | .number() 17 | .min(Date.now()) 18 | .messages({ 19 | "number.min": "from date must be greater than or equal to Today's date", 20 | }) 21 | .required(), 22 | until: joi 23 | .number() 24 | .min(joi.ref("from")) 25 | .messages({ 26 | "number.min": "until date must be greater than or equal to from date", 27 | }) 28 | .required(), 29 | reason: joi.string().required().messages({ 30 | "any.required": "reason is required", 31 | "string.empty": "reason cannot be empty", 32 | }), 33 | type: joi.string().valid(REQUEST_TYPE.OOO).required().messages({ 34 | "string.empty": "type cannot be empty", 35 | "any.required": "type is required", 36 | }), 37 | }); 38 | 39 | await schema.validateAsync(req.body, { abortEarly: false }); 40 | }; 41 | -------------------------------------------------------------------------------- /middlewares/validators/qrCodeAuth.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | const storeUserDeviceInfo = async (req, res, next) => { 4 | const schema = joi.object().strict().keys({ 5 | user_id: joi.string().required(), 6 | device_info: joi.string().required(), 7 | device_id: joi.string().required(), 8 | }); 9 | 10 | try { 11 | await schema.validateAsync(req.body); 12 | next(); 13 | } catch (error) { 14 | logger.error(`Error validating newDeviceInfo payload : ${error}`); 15 | res.boom.badRequest(error.details[0].message); 16 | } 17 | }; 18 | 19 | const validateAuthStatus = async (req, res, next) => { 20 | const schema = joi 21 | .object() 22 | .strict() 23 | .keys({ 24 | authorization_status: joi.string().valid("AUTHORIZED", "REJECTED", "NOT_INIT"), 25 | }); 26 | 27 | try { 28 | await schema.validateAsync(req.params); 29 | next(); 30 | } catch (error) { 31 | logger.error(`Error updating auth status ${error}`); 32 | res.boom.badRequest(error.details[0].message); 33 | } 34 | }; 35 | 36 | const validateFetchingUserDocument = async (req, res, next) => { 37 | const schema = joi.object().strict().keys({ 38 | device_id: joi.string().required(), 39 | }); 40 | 41 | try { 42 | await schema.validateAsync(req.query); 43 | next(); 44 | } catch (error) { 45 | logger.error("Invalid Query Parameters Passed"); 46 | res.boom.badRequest("Invalid Query Parameters Passed"); 47 | } 48 | }; 49 | 50 | module.exports = { 51 | storeUserDeviceInfo, 52 | validateAuthStatus, 53 | validateFetchingUserDocument, 54 | }; 55 | -------------------------------------------------------------------------------- /middlewares/validators/questions.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction } from "express"; 2 | import { CustomRequest, CustomResponse } from "../../types/global"; 3 | const joi = require("joi"); 4 | 5 | const createQuestion = async (req: CustomRequest, res: CustomResponse, next: NextFunction) => { 6 | const schema = joi.object({ 7 | question: joi.string().required(), 8 | createdBy: joi.string().required(), 9 | eventId: joi.string().required(), 10 | maxCharacters: joi.optional(), 11 | }); 12 | 13 | try { 14 | await schema.validateAsync(req.body); 15 | next(); 16 | } catch (error) { 17 | logger.error(`Error validating question: ${error}`); 18 | res.boom.badRequest(error.details[0].message); 19 | } 20 | }; 21 | 22 | module.exports = { createQuestion }; 23 | -------------------------------------------------------------------------------- /middlewares/validators/recruiter.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | const validateRecruiter = async (req, res, next) => { 4 | const schema = joi.object().strict().keys({ 5 | company: joi.string().required(), 6 | first_name: joi.string().required(), 7 | last_name: joi.string().required(), 8 | designation: joi.string().required(), 9 | reason: joi.string().required(), 10 | email: joi.string().required(), 11 | currency: joi.string().required(), 12 | package: joi.number().optional(), 13 | }); 14 | 15 | try { 16 | await schema.validateAsync(req.body); 17 | next(); 18 | } catch (error) { 19 | logger.error(`Error in validating recruiter data: ${error}`); 20 | res.boom.badRequest(error.details[0].message); 21 | } 22 | }; 23 | 24 | module.exports = { 25 | validateRecruiter, 26 | }; 27 | -------------------------------------------------------------------------------- /middlewares/validators/staging.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | const validateUserRoles = async (req, res, next) => { 4 | const schema = joi.object().strict().keys({ 5 | super_user: joi.boolean().optional(), 6 | member: joi.boolean().optional(), 7 | archived: joi.boolean().optional(), 8 | in_discord: joi.boolean().optional(), 9 | }); 10 | 11 | try { 12 | await schema.validateAsync(req.body); 13 | next(); 14 | } catch (err) { 15 | logger.error(`Error validating validateUserRoles payload : ${err}`); 16 | res.boom.badRequest(JSON.stringify({ allowedParameters: { super_user: "boolean", member: "boolean" } })); 17 | } 18 | }; 19 | 20 | const validateRevokePrivileges = async (req, res, next) => { 21 | const schema = joi 22 | .object() 23 | .strict() 24 | .keys({ 25 | action: joi.string().equal("revoke"), 26 | }); 27 | try { 28 | await schema.validateAsync(req.body); 29 | next(); 30 | } catch (err) { 31 | logger.error(`Error validating validateUserRoles payload : ${err}`); 32 | res.boom.badRequest(JSON.stringify({ allowedParameters: { action: "revoke" } })); 33 | } 34 | }; 35 | 36 | module.exports = { 37 | validateUserRoles, 38 | validateRevokePrivileges, 39 | }; 40 | -------------------------------------------------------------------------------- /middlewares/validators/stocks.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | const createStock = async (req, res, next) => { 4 | const schema = joi.object().strict().keys({ 5 | name: joi.string().required(), 6 | quantity: joi.number().required(), 7 | price: joi.number().required(), 8 | }); 9 | 10 | try { 11 | await schema.validateAsync(req.body); 12 | next(); 13 | } catch (error) { 14 | logger.error(`Error validating createStock payload : ${error}`); 15 | res.boom.badRequest(error.details[0].message); 16 | } 17 | }; 18 | 19 | module.exports = { 20 | createStock, 21 | }; 22 | -------------------------------------------------------------------------------- /middlewares/validators/subscription.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction } from "express"; 2 | import { CustomRequest, CustomResponse } from "../../types/global"; 3 | import { emailRegex, phoneNumberRegex } from "../../constants/subscription-validator"; 4 | import Joi from 'joi'; 5 | 6 | export const validateSubscribe = (req: CustomRequest, res: CustomResponse, next: NextFunction) => { 7 | 8 | if(req.body.email){ 9 | req.body.email = req.body.email.trim(); 10 | } 11 | if (req.body.phone) { 12 | req.body.phone = req.body.phone.trim(); 13 | } 14 | const subscribeSchema = Joi.object({ 15 | phone: Joi.string().allow('').optional().regex(phoneNumberRegex), 16 | email: Joi.string().required().regex(emailRegex) 17 | }); 18 | const { error } = subscribeSchema.validate(req.body); 19 | if (error) { 20 | return res.status(400).json({ error: error.details[0].message }); 21 | } 22 | next(); 23 | }; 24 | -------------------------------------------------------------------------------- /middlewares/validators/tags.js: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | const { TYPES } = require("../../constants/tags"); 3 | 4 | const validTagBody = async (req, res, next) => { 5 | const schema = Joi.object({ 6 | name: Joi.string().trim().required(), 7 | type: Joi.string() 8 | .uppercase() 9 | .custom((value, helper) => { 10 | if (!TYPES.includes(value)) { 11 | return helper.message("Not a valid type"); 12 | } 13 | return value; 14 | }), 15 | reason: Joi.string().exist(), 16 | }); 17 | 18 | try { 19 | await schema.validateAsync(req.body); 20 | next(); 21 | } catch (error) { 22 | logger.error(`Error validating createTag payload : ${error}`); 23 | res.boom.badRequest(error.details[0].message); 24 | } 25 | }; 26 | 27 | module.exports = { 28 | validTagBody, 29 | }; 30 | -------------------------------------------------------------------------------- /middlewares/validators/trading.js: -------------------------------------------------------------------------------- 1 | const joi = require("joi"); 2 | 3 | const newTrade = async (req, res, next) => { 4 | const schema = joi.object().strict().keys({ 5 | stockId: joi.string().required(), 6 | tradeType: joi.string().required(), 7 | stockName: joi.string().required(), 8 | quantity: joi.number().required(), 9 | listedPrice: joi.number().required(), 10 | totalPrice: joi.number().required(), 11 | }); 12 | 13 | try { 14 | await schema.validateAsync(req.body); 15 | next(); 16 | } catch (error) { 17 | logger.error(`Error validating newTrade payload : ${error}`); 18 | res.boom.badRequest(error.details[0].message); 19 | } 20 | }; 21 | 22 | module.exports = { 23 | newTrade, 24 | }; 25 | -------------------------------------------------------------------------------- /middlewares/validators/utils.ts: -------------------------------------------------------------------------------- 1 | const Joi = require("joi"); 2 | 3 | const validateMillisecondsTimestamp = async (reqBody, timestampProperty) => { 4 | const schema = Joi.object({ 5 | [timestampProperty]: Joi.number().unit("milliseconds").required(), 6 | }); 7 | return schema.validateAsync(reqBody); 8 | }; 9 | 10 | module.exports = { 11 | validateMillisecondsTimestamp 12 | } 13 | -------------------------------------------------------------------------------- /middlewares/verifydiscord.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Middleware to check if the user has verified themself with the discord bot. 3 | * If user has not verified, then no actions based on discord bot should be allowed. 4 | * Note: This requires that user is authenticated hence must be called after 5 | * the user authentication middleware. We are calling it from within the 6 | * `authenticate` middleware itself to avoid explicitly adding this middleware 7 | * while defining routes. 8 | * 9 | * @param {Object} req - Express request object 10 | * @param {Object} res - Express response object 11 | * @param {Function} next - Express middleware function 12 | * @returns {Object} - Returns unauthorized object if user has been restricted. 13 | */ 14 | const checkIsVerifiedDiscord = async (req, res, next) => { 15 | const { discordId, roles } = req.userData; 16 | if (!discordId || roles.archived) { 17 | return res.boom.forbidden("You are restricted from performing this action"); 18 | } 19 | return next(); 20 | }; 21 | 22 | module.exports = checkIsVerifiedDiscord; 23 | -------------------------------------------------------------------------------- /mockdata/appOwners.js: -------------------------------------------------------------------------------- 1 | const appOwners = [ 2 | { 3 | username: "ankur", 4 | first_name: "Ankur", 5 | last_name: "Narkhede", 6 | github_id: "ankurnarkhede", 7 | github_display_name: "Ankur Narkhede", 8 | incompleteUserDetails: false, 9 | }, 10 | { 11 | username: "ankush", 12 | first_name: "Ankush", 13 | last_name: "Dharkar", 14 | github_id: "AnkushDharkar", 15 | github_display_name: "Ankush Dharkar", 16 | incompleteUserDetails: false, 17 | }, 18 | { 19 | username: "kratika", 20 | first_name: "Kratika", 21 | last_name: "Chowdhary", 22 | github_id: "Kratika0907", 23 | github_display_name: "Kratika Chowdhary", 24 | incompleteUserDetails: false, 25 | }, 26 | { 27 | username: "nikhil", 28 | first_name: "Nikhil", 29 | last_name: "Bhandarkar", 30 | github_id: "whydonti", 31 | github_display_name: "Nikhil Bhandarkar", 32 | incompleteUserDetails: false, 33 | }, 34 | { 35 | username: "sumit", 36 | first_name: "Sumit", 37 | last_name: "Dhanania", 38 | github_id: "sumitd94", 39 | github_display_name: "Sumit Dhanania", 40 | incompleteUserDetails: false, 41 | }, 42 | { 43 | username: "swaraj", 44 | first_name: "Swaraj", 45 | last_name: "Rajpure", 46 | github_id: "swarajpure", 47 | github_display_name: "Swaraj Rajpure", 48 | incompleteUserDetails: false, 49 | }, 50 | ]; 51 | 52 | module.exports = appOwners; 53 | -------------------------------------------------------------------------------- /models/arts.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../utils/firestore"); 2 | const artsModel = firestore.collection("arts"); 3 | 4 | /** 5 | * Adds Art 6 | * 7 | * @param artData { Object }: art data object to be stored in DB 8 | */ 9 | const addArt = async (artData, userId) => { 10 | try { 11 | const art = await artsModel.add({ ...artData, userId }); 12 | return art.id; 13 | } catch (err) { 14 | logger.error("Error in creating art", err); 15 | throw err; 16 | } 17 | }; 18 | 19 | /** 20 | * Fetch all arts 21 | * 22 | * @return {Promise} 23 | */ 24 | const fetchArts = async () => { 25 | try { 26 | const artSnapshot = await artsModel.get(); 27 | const arts = []; 28 | artSnapshot.forEach((art) => { 29 | arts.push({ 30 | id: art.id, 31 | ...art.data(), 32 | }); 33 | }); 34 | return arts; 35 | } catch (err) { 36 | logger.error("error getting arts", err); 37 | throw err; 38 | } 39 | }; 40 | 41 | /** 42 | * Fetches the user arts 43 | * @return {Promise} 44 | */ 45 | const fetchUserArts = async (id) => { 46 | try { 47 | let userArtsRef = ""; 48 | 49 | userArtsRef = await artsModel.where("userId", "==", id).get(); 50 | const userArts = []; 51 | userArtsRef.forEach((art) => { 52 | userArts.push({ 53 | id: art.id, 54 | ...art.data(), 55 | }); 56 | }); 57 | 58 | return userArts; 59 | } catch (err) { 60 | logger.error("Error retrieving user arts", err); 61 | throw err; 62 | } 63 | }; 64 | 65 | module.exports = { 66 | addArt, 67 | fetchArts, 68 | fetchUserArts, 69 | }; 70 | -------------------------------------------------------------------------------- /models/chaincodes.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../utils/firestore"); 2 | const admin = require("firebase-admin"); 3 | 4 | const chaincodeModel = firestore.collection("chaincodes"); 5 | const storeChaincode = async (userId) => { 6 | try { 7 | const userChaincode = await chaincodeModel.add({ 8 | userId, 9 | timestamp: admin.firestore.Timestamp.fromDate(new Date()), 10 | }); 11 | return userChaincode.id; 12 | } catch (error) { 13 | logger.error("Error in creating chaincode", error); 14 | throw error; 15 | } 16 | }; 17 | 18 | module.exports = { 19 | storeChaincode, 20 | }; 21 | -------------------------------------------------------------------------------- /models/external-accounts.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../utils/firestore"); 2 | const externalAccountsModel = firestore.collection("external-accounts"); 3 | 4 | const addExternalAccountData = async (data) => { 5 | try { 6 | await externalAccountsModel.add(data); 7 | return { message: "Added data successfully" }; 8 | } catch (err) { 9 | logger.error("Error in adding data", err); 10 | throw err; 11 | } 12 | }; 13 | 14 | const fetchExternalAccountData = async (query, param) => { 15 | try { 16 | let externalAccountQuery; 17 | let data, id; 18 | 19 | externalAccountQuery = externalAccountsModel.where("token", "==", param); 20 | if (query && query?.type) { 21 | externalAccountQuery = externalAccountQuery.where("type", "==", query?.type); 22 | } 23 | 24 | const querySnapshot = await externalAccountQuery.limit(1).get(); 25 | const doc = querySnapshot.docs[0]; 26 | if (doc) { 27 | id = doc.id; 28 | data = doc.data(); 29 | } 30 | 31 | return { 32 | id: id, 33 | ...data, 34 | }; 35 | } catch (err) { 36 | logger.error("Error in fetching external account data", err); 37 | throw err; 38 | } 39 | }; 40 | 41 | module.exports = { addExternalAccountData, fetchExternalAccountData }; 42 | -------------------------------------------------------------------------------- /models/fcmToken.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../utils/firestore"); 2 | const { Conflict } = require("http-errors"); 3 | 4 | const fcmTokenModel = firestore.collection("fcmToken"); 5 | 6 | const saveFcmToken = async (fcmTokenData) => { 7 | try { 8 | const fcmTokenSnapshot = await fcmTokenModel.where("userId", "==", fcmTokenData.userId).limit(1).get(); 9 | 10 | if (fcmTokenSnapshot.empty) { 11 | const fcmToken = await fcmTokenModel.add({ 12 | userId: fcmTokenData.userId, 13 | fcmTokens: [fcmTokenData.fcmToken], 14 | }); 15 | return fcmToken.id; 16 | } else { 17 | let fcmTokenObj = {}; 18 | fcmTokenSnapshot.forEach((fcmToken) => { 19 | fcmTokenObj = { 20 | id: fcmToken.id, 21 | ...fcmToken.data(), 22 | }; 23 | }); 24 | if (!fcmTokenObj.fcmTokens.includes(fcmTokenData.fcmToken)) { 25 | fcmTokenObj.fcmTokens.push(fcmTokenData.fcmToken); 26 | await fcmTokenModel.doc(fcmTokenObj.id).update({ 27 | fcmTokens: fcmTokenObj.fcmTokens, 28 | }); 29 | return fcmTokenObj.id; 30 | } else { 31 | throw new Conflict("Device Already Registered"); 32 | } 33 | } 34 | } catch (err) { 35 | logger.error("Error in adding fcm token", err); 36 | throw err; 37 | } 38 | }; 39 | 40 | module.exports = { saveFcmToken }; 41 | -------------------------------------------------------------------------------- /models/levels.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../utils/firestore"); 2 | const levelModel = firestore.collection("levels"); 3 | 4 | /** 5 | * 6 | * @param levelData { Object }: Data of the level 7 | * @returns {Promise} 8 | */ 9 | 10 | const addLevel = async (levelData) => { 11 | try { 12 | levelData.value = parseInt(levelData.value); 13 | const { id } = await levelModel.add(levelData); 14 | return { id, levelData }; 15 | } catch (err) { 16 | logger.error("Error in creating Level", err); 17 | throw err; 18 | } 19 | }; 20 | 21 | /** 22 | * 23 | * @param id { String }: id of the level to be deleted 24 | * @returns {Promise} 25 | */ 26 | 27 | const deleteLevel = async (id) => { 28 | try { 29 | await levelModel.doc(id).delete(); 30 | } catch (err) { 31 | logger.error("Error in deleting Level", err); 32 | throw err; 33 | } 34 | }; 35 | 36 | /** 37 | * 38 | * @returns {Promise} 39 | */ 40 | 41 | const getAllLevels = async () => { 42 | try { 43 | const data = await levelModel.get(); 44 | const allLevels = []; 45 | data.forEach((doc) => { 46 | const Level = { 47 | id: doc.id, 48 | ...doc.data(), 49 | }; 50 | allLevels.push(Level); 51 | }); 52 | return { allLevels }; 53 | } catch (err) { 54 | logger.error("Error in deleting Level", err); 55 | throw err; 56 | } 57 | }; 58 | 59 | module.exports = { 60 | addLevel, 61 | deleteLevel, 62 | getAllLevels, 63 | }; 64 | -------------------------------------------------------------------------------- /models/questions.ts: -------------------------------------------------------------------------------- 1 | const admin = require("firebase-admin"); 2 | 3 | import { Question, QuestionBody } from "../types/questions"; 4 | 5 | const firestore = require("../utils/firestore"); 6 | 7 | const questionModel = firestore.collection("questions"); 8 | 9 | const createQuestion = async (questionData: QuestionBody): Promise => { 10 | try { 11 | const { eventId: event_id, createdBy: created_by, question, maxCharacters: max_characters } = questionData; 12 | const questionRef = questionModel.doc(questionData.id); 13 | const createdAndUpdatedAt = admin.firestore.Timestamp.now(); 14 | 15 | await questionRef.set({ 16 | question, 17 | event_id, 18 | created_by, 19 | max_characters: max_characters || null, 20 | created_at: createdAndUpdatedAt, 21 | updated_at: createdAndUpdatedAt, 22 | }); 23 | const questionSnapshot = await questionRef.get(); 24 | const id = questionSnapshot.id; 25 | const questionFromDB = questionSnapshot.data(); 26 | 27 | return { id, ...questionFromDB }; 28 | } catch (error) { 29 | logger.error(`Some error occured while creating question ${error}`); 30 | throw error; 31 | } 32 | }; 33 | 34 | module.exports = { createQuestion }; 35 | -------------------------------------------------------------------------------- /nyc.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Nyc coverage config 3 | * Documentation: https://github.com/istanbuljs/nyc#common-configuration-options 4 | */ 5 | module.exports = { 6 | all: true, 7 | "check-coverage": false, // Will be enabled after reaching 50% coverage: https://github.com/Real-Dev-Squad/website-backend/issues/493 8 | exclude: ["test/**", "mockdata/**", "docs/**", "public/**", "dist/**"], 9 | reporter: ["text", "lcov", "text-summary"], 10 | reportDir: ".coverage", 11 | tempDir: ".coverage", 12 | branches: 50, 13 | lines: 50, 14 | functions: 50, 15 | statements: 50, 16 | watermarks: { 17 | lines: [75, 90], 18 | functions: [75, 90], 19 | branches: [75, 90], 20 | statements: [75, 90], 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"] 4 | } 5 | -------------------------------------------------------------------------------- /routes/answers.ts: -------------------------------------------------------------------------------- 1 | import authenticate from "../middlewares/authenticate"; 2 | 3 | const express = require("express"); 4 | const authorizeRoles = require("../middlewares/authorizeRoles"); 5 | const { SUPERUSER, MEMBER } = require("../constants/roles"); 6 | const router = express.Router(); 7 | const answers = require("../controllers/answers"); 8 | const answerValidators = require("../middlewares/validators/answers"); 9 | 10 | router.post("/", answerValidators.createAnswer, answers.createAnswer); 11 | router.patch( 12 | "/:answerId", 13 | authenticate, 14 | authorizeRoles([SUPERUSER, MEMBER]), 15 | answerValidators.updateAnswer, 16 | answers.updateAnswer 17 | ); 18 | 19 | router.get("/", answers.getAnswers); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /routes/applications.ts: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { SUPERUSER } = require("../constants/roles"); 3 | const authenticate = require("../middlewares/authenticate"); 4 | const authorizeRoles = require("../middlewares/authorizeRoles"); 5 | const applications = require("../controllers/applications"); 6 | const { authorizeOwnOrSuperUser } = require("../middlewares/authorizeOwnOrSuperUser"); 7 | const applicationValidator = require("../middlewares/validators/application"); 8 | 9 | const router = express.Router(); 10 | 11 | router.get( 12 | "/", 13 | authenticate, 14 | authorizeOwnOrSuperUser, 15 | applicationValidator.validateApplicationQueryParam, 16 | applications.getAllOrUserApplication 17 | ); 18 | router.get("/:applicationId", authenticate, authorizeRoles([SUPERUSER]), applications.getApplicationById); 19 | router.post("/", authenticate, applicationValidator.validateApplicationData, applications.addApplication); 20 | router.patch( 21 | "/:applicationId", 22 | authenticate, 23 | authorizeRoles([SUPERUSER]), 24 | applicationValidator.validateApplicationUpdateData, 25 | applications.updateApplication 26 | ); 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /routes/arts.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | import authenticate from "../middlewares/authenticate"; 4 | import arts from "../controllers/arts"; 5 | import artValidator from "../middlewares/validators/arts"; 6 | import { devFlagMiddleware } from "../middlewares/devFlag"; 7 | 8 | router.get("/", arts.fetchArts); 9 | router.get("/user/self", authenticate, arts.getSelfArts); // this route is soon going to be deprecated soon, please use /arts/:userId endpoint. 10 | router.get("/user/:userId", authenticate, arts.getUserArts); // this route is soon going to be deprecated soon, please use /arts/:userId endpoint. 11 | router.get("/:userId", authenticate, arts.getUserArts); 12 | router.post("/user/add", authenticate, artValidator.createArt, arts.addArt); 13 | 14 | module.exports = router; 15 | -------------------------------------------------------------------------------- /routes/auctions.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import authenticate from "../middlewares/authenticate"; 3 | import auction from "../controllers/auction"; 4 | import auctionValidator from "../middlewares/validators/auctions"; 5 | const router = express.Router(); 6 | 7 | router.get("/:id", auction.fetchAuctionById); 8 | router.get("/", auction.fetchAvailableAuctions); 9 | router.post("/bid/:id", authenticate, auctionValidator.placeBid, auction.makeNewBid); 10 | router.post("/", authenticate, auctionValidator.createAuction, auction.createNewAuction); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /routes/auth.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | import auth from "../controllers/auth"; 4 | import authenticate from "../middlewares/authenticate"; 5 | import userDeviceInfoValidator from "../middlewares/validators/qrCodeAuth"; 6 | import qrCodeAuthValidator from "../middlewares/validators/qrCodeAuth"; 7 | import { devFlagMiddleware } from "../middlewares/devFlag"; 8 | 9 | router.get("/github/login", auth.githubAuthLogin); 10 | 11 | router.get("/github/callback", auth.githubAuthCallback); 12 | 13 | router.get("/google/login", devFlagMiddleware, auth.googleAuthLogin); 14 | 15 | router.get("/google/callback", auth.googleAuthCallback); 16 | 17 | router.get("/signout", auth.signout); 18 | 19 | router.get("/qr-code-auth", userDeviceInfoValidator.validateFetchingUserDocument, auth.fetchUserDeviceInfo); 20 | 21 | router.get("/device", authenticate, auth.fetchDeviceDetails); 22 | 23 | router.post("/qr-code-auth", userDeviceInfoValidator.storeUserDeviceInfo, auth.storeUserDeviceInfo); 24 | 25 | router.patch( 26 | "/qr-code-auth/authorization_status/:authorization_status", 27 | authenticate, 28 | qrCodeAuthValidator.validateAuthStatus, 29 | auth.updateAuthStatus 30 | ); 31 | 32 | module.exports = router; 33 | -------------------------------------------------------------------------------- /routes/awsAccess.ts: -------------------------------------------------------------------------------- 1 | import express from "express" 2 | import { addUserToAWSGroup } from "../controllers/awsAccess"; 3 | const router = express.Router(); 4 | const { verifyDiscordBot } = require("../middlewares/authorizeBot"); 5 | 6 | router.post("/access", verifyDiscordBot, addUserToAWSGroup); 7 | 8 | module.exports = router; -------------------------------------------------------------------------------- /routes/badges.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const authenticate = require("../middlewares/authenticate"); 4 | const badgesValidator = require("../middlewares/validators/badges"); 5 | const { upload } = require("../utils/multer"); 6 | const badgesController = require("../controllers/badges"); 7 | const authorizeRoles = require("../middlewares/authorizeRoles"); 8 | const { SUPERUSER } = require("../constants/roles"); 9 | 10 | router.get("/", badgesController.getBadges); 11 | // INFO: upload(muter-middelware) looks for form-data key named file 12 | router.post( 13 | "/", 14 | authenticate, 15 | authorizeRoles([SUPERUSER]), 16 | upload.single("file"), 17 | badgesValidator.createBadge, 18 | badgesController.postBadge 19 | ); 20 | router.post( 21 | "/assign", 22 | authenticate, 23 | authorizeRoles([SUPERUSER]), 24 | badgesValidator.assignOrRemoveBadges, 25 | badgesController.postUserBadges 26 | ); 27 | router.delete( 28 | "/remove", 29 | authenticate, 30 | authorizeRoles([SUPERUSER]), 31 | badgesValidator.assignOrRemoveBadges, 32 | badgesController.deleteUserBadges 33 | ); 34 | 35 | module.exports = router; 36 | -------------------------------------------------------------------------------- /routes/challenges.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | import authenticate from "../middlewares/authenticate"; 4 | import challenges from "../controllers/challenge"; 5 | import { createChallenge } from "../middlewares/validators/challenges"; 6 | 7 | router.get("/", authenticate, challenges.fetchChallenges); 8 | router.post("/", authenticate, createChallenge, challenges.createChallenge); 9 | router.post("/subscribe", authenticate, challenges.subscribeToChallenge); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /routes/cloudflareCache.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | import cloudflareCache from "../controllers/cloudflareCache"; 4 | import authenticate from "../middlewares/authenticate"; 5 | 6 | router.get("/", authenticate, cloudflareCache.fetchPurgedCacheMetadata); 7 | router.post("/", authenticate, cloudflareCache.purgeCache); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/contributions.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | import contributions from "../controllers/contributions"; 4 | 5 | router.get("/:username", contributions.getUserContributions); 6 | 7 | module.exports = router; 8 | -------------------------------------------------------------------------------- /routes/external-accounts.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const authorizeBot = require("../middlewares/authorizeBot"); 4 | const validator = require("../middlewares/validators/external-accounts"); 5 | const externalAccount = require("../controllers/external-accounts"); 6 | const authenticate = require("../middlewares/authenticate"); 7 | const authorizeRoles = require("../middlewares/authorizeRoles"); 8 | const { SUPERUSER } = require("../constants/roles"); 9 | const ROLES = require("../constants/roles"); 10 | const { Services } = require("../constants/bot"); 11 | const { authorizeAndAuthenticate } = require("../middlewares/authorizeUsersAndService"); 12 | 13 | router.post("/", validator.externalAccountData, authorizeBot.verifyDiscordBot, externalAccount.addExternalAccountData); 14 | router.get("/:token", authenticate, externalAccount.getExternalAccountData); 15 | router.patch("/link/:token", authenticate, validator.linkDiscord, externalAccount.linkExternalAccount); 16 | router.patch("/discord-sync", authenticate, authorizeRoles([SUPERUSER]), externalAccount.syncExternalAccountData); 17 | router.post( 18 | "/users", 19 | authorizeAndAuthenticate([ROLES.SUPERUSER], [Services.CRON_JOB_HANDLER]), 20 | validator.postExternalAccountsUsers, 21 | externalAccount.externalAccountsUsersPostHandler 22 | ); 23 | 24 | module.exports = router; 25 | -------------------------------------------------------------------------------- /routes/fcmToken.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | 4 | const authenticate = require("../middlewares/authenticate"); 5 | const { fcmTokenController } = require("../controllers/fcmToken"); 6 | const { fcmTokenValidator } = require("../middlewares/validators/fcmToken"); 7 | 8 | router.post("/", authenticate, fcmTokenValidator, fcmTokenController); 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/goals.ts: -------------------------------------------------------------------------------- 1 | import authenticate from "../middlewares/authenticate"; 2 | import express from "express"; 3 | import goals from "../controllers/goals"; 4 | const router = express.Router(); 5 | 6 | router.get("/token", authenticate, goals.getGoalSiteToken); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /routes/healthCheck.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import authenticate from "../middlewares/authenticate"; 3 | import health from "../controllers/health"; 4 | const router = express.Router(); 5 | 6 | router.get("/", health.healthCheck); 7 | router.get("/v2", authenticate, health.healthCheck); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/invites.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | import { createInviteValidator } from "../middlewares/validators/invites"; 4 | import { createInviteLink,getInviteLink } from "../controllers/invites"; 5 | import authinticateServiceRequest from "../middlewares/authinticateServiceRequest"; 6 | 7 | router.post("/",authinticateServiceRequest, createInviteValidator, createInviteLink); 8 | router.get("/:userId" ,authinticateServiceRequest, getInviteLink); 9 | 10 | module.exports = router; -------------------------------------------------------------------------------- /routes/issues.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import issues from "../controllers/issues"; 3 | const router = express.Router(); 4 | 5 | router.get("/", issues.getIssues); 6 | router.post("/updates", issues.issueUpdates); 7 | 8 | module.exports = router; 9 | -------------------------------------------------------------------------------- /routes/items.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { addTagsToItem, removeTagsFromItem, getItemBasedOnFilter } = require("../controllers/items"); 3 | const { validateItemsPayload, validateItemQuery } = require("../middlewares/validators/items"); 4 | const authenticate = require("../middlewares/authenticate"); 5 | const authorizeRoles = require("../middlewares/authorizeRoles"); 6 | const { SUPERUSER } = require("../constants/roles"); 7 | 8 | const router = express.Router(); 9 | 10 | router.post("/", authenticate, authorizeRoles([SUPERUSER]), validateItemsPayload, addTagsToItem); 11 | router.delete("/", authenticate, authorizeRoles([SUPERUSER]), removeTagsFromItem); 12 | router.get("/filter", authenticate, validateItemQuery, getItemBasedOnFilter); 13 | 14 | module.exports = router; 15 | -------------------------------------------------------------------------------- /routes/levels.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { addLevel, deleteLevel, getAllLevels } = require("../controllers/levels"); 3 | const { validateLevelBody } = require("../middlewares/validators/levels"); 4 | const authenticate = require("../middlewares/authenticate"); 5 | const authorizeRoles = require("../middlewares/authorizeRoles"); 6 | const { SUPERUSER } = require("../constants/roles"); 7 | 8 | const router = express.Router(); 9 | 10 | router.post("/", authenticate, authorizeRoles([SUPERUSER]), validateLevelBody, addLevel); 11 | router.delete("/:levelid", authenticate, authorizeRoles([SUPERUSER]), deleteLevel); 12 | router.get("/", getAllLevels); 13 | 14 | module.exports = router; 15 | -------------------------------------------------------------------------------- /routes/logs.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const logs = require("../controllers/logs"); 4 | const authenticate = require("../middlewares/authenticate"); 5 | const authorizeRoles = require("../middlewares/authorizeRoles"); 6 | const { SUPERUSER } = require("../constants/roles"); 7 | 8 | router.get("/:type", authenticate, authorizeRoles([SUPERUSER]), logs.fetchLogs); 9 | router.get("/", authenticate, authorizeRoles([SUPERUSER]), logs.fetchAllLogs); 10 | router.post("/migrate", authenticate, authorizeRoles([SUPERUSER]), logs.updateLogs); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /routes/members.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const members = require("../controllers/members"); 4 | const authorizeRoles = require("../middlewares/authorizeRoles"); 5 | const authenticate = require("../middlewares/authenticate"); 6 | const { addRecruiter, fetchRecruitersInfo } = require("../controllers/recruiters"); 7 | const { validateRecruiter } = require("../middlewares/validators/recruiter"); 8 | const { validateGetMembers } = require("../middlewares/validators/members"); 9 | const { SUPERUSER } = require("../constants/roles"); 10 | 11 | router.get("/", validateGetMembers, members.getMembers); 12 | router.get("/idle", members.getIdleMembers); 13 | router.post("/intro/:username", validateRecruiter, addRecruiter); 14 | router.get("/intro", authenticate, authorizeRoles([SUPERUSER]), fetchRecruitersInfo); 15 | router.patch("/moveToMembers/:username", authenticate, authorizeRoles([SUPERUSER]), members.moveToMembers); 16 | router.patch("/archiveMembers/:username", authenticate, authorizeRoles([SUPERUSER]), members.archiveMembers); 17 | 18 | module.exports = router; 19 | -------------------------------------------------------------------------------- /routes/monitor.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const authenticate = require("../middlewares/authenticate"); 4 | const authorizeRoles = require("../middlewares/authorizeRoles"); 5 | const { SUPERUSER } = require("../constants/roles"); 6 | const { 7 | createTrackedProgressController, 8 | updateTrackedProgressController, 9 | getTrackedProgressController, 10 | } = require("../controllers/monitor"); 11 | const { 12 | validateCreateTrackedProgressRecord, 13 | validateUpdateTrackedProgress, 14 | validateGetTrackedProgressQueryParams, 15 | } = require("../middlewares/validators/monitor"); 16 | 17 | router.post( 18 | "/", 19 | authenticate, 20 | authorizeRoles([SUPERUSER]), 21 | validateCreateTrackedProgressRecord, 22 | createTrackedProgressController 23 | ); 24 | 25 | router.patch( 26 | "/:type/:typeId", 27 | authenticate, 28 | authorizeRoles([SUPERUSER]), 29 | validateUpdateTrackedProgress, 30 | updateTrackedProgressController 31 | ); 32 | 33 | router.get("/", validateGetTrackedProgressQueryParams, getTrackedProgressController); 34 | 35 | module.exports = router; 36 | -------------------------------------------------------------------------------- /routes/notify.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | 4 | import authenticate from "../middlewares/authenticate"; 5 | import { notifyController } from "../controllers/notify"; 6 | import { notifyValidator } from "../middlewares/validators/notify"; 7 | 8 | router.post("/", authenticate, notifyValidator, notifyController); 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/profileDiffs.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const profileDiffs = require("../controllers/profileDiffs"); 4 | const authorizeRoles = require("../middlewares/authorizeRoles"); 5 | const authenticate = require("../middlewares/authenticate"); 6 | const { SUPERUSER } = require("../constants/roles"); 7 | 8 | router.get("/", authenticate, authorizeRoles([SUPERUSER]), profileDiffs.getProfileDiffs); 9 | router.get("/:id", authenticate, authorizeRoles([SUPERUSER]), profileDiffs.getProfileDiff); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /routes/progresses.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import authenticate from "../middlewares/authenticate"; 3 | import { 4 | validateCreateProgressRecords, 5 | validateGetProgressRecordsQuery, 6 | validateGetRangeProgressRecordsParams, 7 | validateGetDayProgressParams, 8 | } from "../middlewares/validators/progresses"; 9 | import { 10 | createProgress, 11 | getProgress, 12 | getProgressRangeData, 13 | getProgressBydDateController, 14 | } from "../controllers/progresses"; 15 | const router = express.Router(); 16 | router.post("/", authenticate, validateCreateProgressRecords, createProgress); 17 | router.get("/", validateGetProgressRecordsQuery, getProgress); 18 | router.get("/:type/:typeId/date/:date", validateGetDayProgressParams, getProgressBydDateController); 19 | router.get("/range", validateGetRangeProgressRecordsParams, getProgressRangeData); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /routes/pullrequests.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import pullRequest from "../controllers/pullRequests"; 3 | const router = express.Router(); 4 | 5 | router.get("/open", pullRequest.getOpenPRs); 6 | router.get("/stale", pullRequest.getStalePRs); 7 | router.get("/user/:username", pullRequest.getUserPRs); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /routes/questions.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | const authorizeRoles = require("../middlewares/authorizeRoles"); 4 | const { SUPERUSER } = require("../constants/roles"); 5 | const authenticate = require("../middlewares/authenticate"); 6 | const questions = require("../controllers/questions"); 7 | const questionValidators = require("../middlewares/validators/questions"); 8 | 9 | router.post( 10 | "/", 11 | authenticate, 12 | authorizeRoles([SUPERUSER]), 13 | questionValidators.createQuestion, 14 | questions.createQuestion 15 | ); 16 | 17 | router.get("/", questions.getQuestions); 18 | 19 | module.exports = router; 20 | -------------------------------------------------------------------------------- /routes/requests.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | const authorizeRoles = require("../middlewares/authorizeRoles"); 4 | const { SUPERUSER } = require("../constants/roles"); 5 | import authenticate from "../middlewares/authenticate"; 6 | import { 7 | createRequestsMiddleware, 8 | updateRequestsMiddleware, 9 | getRequestsMiddleware, 10 | updateRequestValidator 11 | } from "../middlewares/validators/requests"; 12 | import { 13 | createRequestController , 14 | updateRequestController, 15 | getRequestsController, 16 | updateRequestBeforeAcknowledgedController 17 | } from "../controllers/requests"; 18 | import { skipAuthenticateForOnboardingExtensionRequest } from "../middlewares/skipAuthenticateForOnboardingExtension"; 19 | import { verifyDiscordBot } from "../middlewares/authorizeBot"; 20 | 21 | 22 | router.get("/", getRequestsMiddleware, getRequestsController); 23 | router.post("/", skipAuthenticateForOnboardingExtensionRequest(authenticate, verifyDiscordBot), createRequestsMiddleware, createRequestController); 24 | router.put("/:id",authenticate, authorizeRoles([SUPERUSER]), updateRequestsMiddleware, updateRequestController); 25 | router.patch("/:id", authenticate, updateRequestValidator, updateRequestBeforeAcknowledgedController); 26 | module.exports = router; 27 | 28 | -------------------------------------------------------------------------------- /routes/staging.ts: -------------------------------------------------------------------------------- 1 | import { removePrivileges, updateRoles } from "../controllers/staging"; 2 | import { validateRevokePrivileges, validateUserRoles } from "../middlewares/validators/staging"; 3 | 4 | import authenticate from "../middlewares/authenticate"; 5 | import express from "express"; 6 | 7 | const router = express.Router(); 8 | 9 | router.patch("/user", validateUserRoles, authenticate, updateRoles); 10 | router.post("/users/privileges", validateRevokePrivileges, removePrivileges); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /routes/stocks.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const authenticate = require("../middlewares/authenticate"); 4 | const authorizeRoles = require("../middlewares/authorizeRoles"); 5 | const { addNewStock, fetchStocks, getSelfStocks, getUserStocks } = require("../controllers/stocks"); 6 | const { createStock } = require("../middlewares/validators/stocks"); 7 | const { SUPERUSER } = require("../constants/roles"); 8 | const { devFlagMiddleware } = require("../middlewares/devFlag"); 9 | const { userAuthorization } = require("../middlewares/userAuthorization"); 10 | 11 | router.get("/", fetchStocks); 12 | router.post("/", authenticate, authorizeRoles([SUPERUSER]), createStock, addNewStock); 13 | router.get("/user/self", authenticate, getSelfStocks); // this route will soon be deprecated, please use `/stocks/:userId` route. 14 | router.get("/:userId", devFlagMiddleware, authenticate, userAuthorization, getUserStocks); 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /routes/subscription.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import authenticate from "../middlewares/authenticate"; 3 | import { subscribe, unsubscribe, sendEmail } from "../controllers/subscription"; 4 | import { validateSubscribe } from "../middlewares/validators/subscription"; 5 | const authorizeRoles = require("../middlewares/authorizeRoles"); 6 | const router = express.Router(); 7 | const { SUPERUSER } = require("../constants/roles"); 8 | 9 | router.post("/", authenticate, validateSubscribe, subscribe); 10 | router.patch("/", authenticate, unsubscribe); 11 | router.get("/notify", authenticate, authorizeRoles([SUPERUSER]), sendEmail); 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /routes/tags.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | 3 | const { addTag, deleteTag, getAllTags, getTagsByType } = require("../controllers/tags"); 4 | const { validTagBody } = require("../middlewares/validators/tags"); 5 | const authenticate = require("../middlewares/authenticate"); 6 | const authorizeRoles = require("../middlewares/authorizeRoles"); 7 | const { SUPERUSER } = require("../constants/roles"); 8 | 9 | const router = express.Router(); 10 | 11 | router.post("/", authenticate, authorizeRoles([SUPERUSER]), validTagBody, addTag); 12 | router.delete("/:tagid", authenticate, authorizeRoles([SUPERUSER]), deleteTag); 13 | router.get("/", getAllTags); 14 | router.get("/:type", getTagsByType); 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /routes/taskRequests.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { SUPERUSER } = require("../constants/roles"); 3 | const router = express.Router(); 4 | const authenticate = require("../middlewares/authenticate"); 5 | const authorizeRoles = require("../middlewares/authorizeRoles"); 6 | const taskRequests = require("../controllers/tasksRequests"); 7 | const { validateUser } = require("../middlewares/taskRequests"); 8 | const validators = require("../middlewares/validators/task-requests"); 9 | 10 | router.get("/", authenticate, taskRequests.fetchTaskRequests); 11 | router.get("/:id", authenticate, taskRequests.fetchTaskRequestById); 12 | router.patch("/", authenticate, authorizeRoles([SUPERUSER]), validateUser, taskRequests.updateTaskRequests); 13 | router.post("/", authenticate, validators.postTaskRequests, taskRequests.addTaskRequests); 14 | 15 | router.post("/migrations", authenticate, authorizeRoles([SUPERUSER]), taskRequests.migrateTaskRequests); 16 | 17 | // Deprecated | @Ajeyakrishna-k | https://github.com/Real-Dev-Squad/website-backend/issues/1597 18 | router.post("/addOrUpdate", authenticate, validateUser, taskRequests.addOrUpdate); 19 | router.patch("/approve", authenticate, authorizeRoles([SUPERUSER]), validateUser, taskRequests.updateTaskRequests); 20 | 21 | module.exports = router; 22 | -------------------------------------------------------------------------------- /routes/trading.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | import authenticate from "../middlewares/authenticate"; 4 | import { newTrade } from "../middlewares/validators/trading"; 5 | import { trade } from "../controllers/trading"; 6 | import { devFlagMiddleware } from "../middlewares/devFlag"; 7 | import { userAuthorization } from "../middlewares/userAuthorization"; 8 | 9 | router.post("/stock/new/self", authenticate, newTrade, trade); // this route is being deprecated, please use new available route `/stock/new/:userId` 10 | router.post("/stock/new/:userId", devFlagMiddleware, authenticate, userAuthorization, newTrade, trade); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /routes/userStatus.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const { 3 | deleteUserStatus, 4 | getUserStatus, 5 | updateUserStatuses, 6 | updateAllUserStatus, 7 | batchUpdateUsersStatus, 8 | getUserStatusControllers, 9 | updateUserStatusController, 10 | } = require("../controllers/userStatus"); 11 | const router = express.Router(); 12 | const authenticate = require("../middlewares/authenticate"); 13 | const authorizeRoles = require("../middlewares/authorizeRoles"); 14 | const { SUPERUSER } = require("../constants/roles"); 15 | const { 16 | validateUserStatus, 17 | validateMassUpdate, 18 | validateGetQueryParams, 19 | } = require("../middlewares/validators/userStatus"); 20 | const { authorizeAndAuthenticate } = require("../middlewares/authorizeUsersAndService"); 21 | const ROLES = require("../constants/roles"); 22 | const { Services } = require("../constants/bot"); 23 | 24 | router.get("/", validateGetQueryParams, getUserStatusControllers); 25 | router.get("/self", authenticate, getUserStatus); 26 | router.get("/:userId", getUserStatus); 27 | router.patch("/self", authenticate, validateUserStatus, updateUserStatusController); // this route is being deprecated, please use /users/status/:userId PATCH endpoint instead. 28 | router.patch("/update", authorizeAndAuthenticate([ROLES.SUPERUSER], [Services.CRON_JOB_HANDLER]), updateAllUserStatus); 29 | router.patch( 30 | "/batch", 31 | authorizeAndAuthenticate([ROLES.SUPERUSER], [Services.CRON_JOB_HANDLER]), 32 | validateMassUpdate, 33 | batchUpdateUsersStatus 34 | ); 35 | router.patch("/:userId", authenticate, validateUserStatus, updateUserStatuses); 36 | router.delete("/:userId", authenticate, authorizeRoles([SUPERUSER]), deleteUserStatus); 37 | 38 | module.exports = router; 39 | -------------------------------------------------------------------------------- /routes/wallets.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const router = express.Router(); 3 | const wallet = require("../controllers/wallets"); 4 | const authenticate = require("../middlewares/authenticate"); 5 | const authorizeRoles = require("../middlewares/authorizeRoles"); 6 | const { SUPERUSER } = require("../constants/roles"); 7 | 8 | router.get("/", authenticate, wallet.getOwnWallet); 9 | router.get("/:username", authenticate, authorizeRoles([SUPERUSER]), wallet.getUserWallet); 10 | 11 | module.exports = router; 12 | -------------------------------------------------------------------------------- /scripts/tests/tdd-files-list.txt: -------------------------------------------------------------------------------- 1 | test/integration/.test.js 2 | test/integration/.test.js 3 | test/unit//.test.js 4 | test/unit//.test.js 5 | test/unit//.test.js 6 | -------------------------------------------------------------------------------- /scripts/tests/tdd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # "tdd-files-list.txt only contain those files that are to be tested locally during development. 4 | # Wildcards are allowed in the filepaths. You can add any number of paths. 5 | # Note - Don't forget the newline in the end. 6 | 7 | BASEDIR=$(dirname "$0") 8 | tddFilename="$BASEDIR/tdd-files-list.txt" 9 | 10 | allPaths="" 11 | while read -r line; do 12 | name="$line" 13 | echo "Will run tests from file :- $name" 14 | allPaths+="$name " 15 | done < "$tddFilename" 16 | 17 | export NODE_ENV='test' 18 | echo '\nStart firestore emulator and run tests during TDD only:\n' 19 | 20 | # Read config meanings here: https://github.com/istanbuljs/nyc#common-configuration-options 21 | firebase emulators:exec "nyc --check-coverage=true --all=false --skip-full=true --recursive --per-file=true mocha --watch $allPaths" 22 | 23 | # Aim to achieve these results when you exit: 24 | # =============================== Coverage summary =============================== 25 | # Statements : 100% ( ) 26 | # Branches : 100% ( ) 27 | # Functions : 100% ( ) 28 | # Lines : 100% ( ) 29 | # ================================================================================ 30 | 31 | # Upon exiting, the script is expected to say "Script exited unsuccessfully (code 130)" 32 | 33 | # NOTE - Before commiting the code, run `yarn test` to run all the tests. 34 | -------------------------------------------------------------------------------- /scripts/tests/testIntegration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set 'test' environment 4 | export NODE_ENV='test' 5 | export NODE_CONFIG_DIR='./test/config' 6 | 7 | # get project_id value from firestore config 8 | json=$(node -e "console.log(require('config').get('firestore'))") 9 | project_id=$(echo $json | grep -o '"project_id":[^,}]*' | cut -d':' -f2 | tr -d '"' | tr -d '[:space:]') 10 | 11 | echo 'Start firestore emulator and run integration tests:' 12 | firebase emulators:exec 'nyc mocha test/integration/**' --project=$project_id 13 | -------------------------------------------------------------------------------- /scripts/tests/testUnit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set 'test' environment 4 | export NODE_ENV='test' 5 | export NODE_CONFIG_DIR='./test/config' 6 | 7 | # get project_id value from firestore config 8 | json=$(node -e "console.log(require('config').get('firestore'))") 9 | project_id=$(echo $json | grep -o '"project_id":[^,}]*' | cut -d':' -f2 | tr -d '"' | tr -d '[:space:]') 10 | 11 | echo 'Start firestore emulator and run unit tests:' 12 | firebase emulators:exec 'nyc --x=controllers --x=test --x=docs --recursive --x=mockdata --x=dist mocha test/unit/**' --project=$project_id 13 | -------------------------------------------------------------------------------- /services/authService.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | /** 3 | * Generates the JWT 4 | * 5 | * @param payload {Object} - Payload to be added in the JWT 6 | * @return {String} - Generated JWT 7 | */ 8 | const generateAuthToken = (payload) => { 9 | return jwt.sign(payload, config.get("userToken.privateKey"), { 10 | algorithm: "RS256", 11 | expiresIn: config.get("userToken.ttl"), 12 | }); 13 | }; 14 | 15 | /** 16 | * Verifies if the JWT is valid. Throws error in case of signature error or expiry 17 | * 18 | * @param token {String} - JWT to be verified 19 | * @return {Object} - Decode value of JWT 20 | */ 21 | const verifyAuthToken = (token) => { 22 | return jwt.verify(token, config.get("userToken.publicKey"), { algorithms: ["RS256"] }); 23 | }; 24 | 25 | /** 26 | * Decodes the JWT. This is irrespective of the signature error or expiry 27 | * 28 | * @param token {String} - JWT to be decoded 29 | * @return {Object} - Decode value of JWT 30 | */ 31 | const decodeAuthToken = (token) => { 32 | return jwt.decode(token); 33 | }; 34 | 35 | module.exports = { 36 | generateAuthToken, 37 | verifyAuthToken, 38 | decodeAuthToken, 39 | }; 40 | -------------------------------------------------------------------------------- /services/botVerificationService.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | /** 4 | * Verifies if the JWT is valid. Throws error in case of signature error or expiry 5 | * 6 | * @param token {String} - JWT to be verified 7 | * @return {Object} - Decode value of JWT 8 | */ 9 | const verifyToken = (token) => { 10 | return jwt.verify(token, config.get("botToken.botPublicKey"), { algorithms: ["RS256"] }); 11 | }; 12 | 13 | /** 14 | * Verifies if the JWT is valid. Throws error in case of signature error or expiry 15 | * 16 | * @param token {String} - JWT to be verified 17 | * @return {Object} - Decode value of JWT 18 | */ 19 | const verifyCronJob = (token) => { 20 | return jwt.verify(token, config.get("cronJobHandler.publicKey"), { algorithms: ["RS256"] }); 21 | }; 22 | 23 | module.exports = { verifyToken, verifyCronJob }; 24 | -------------------------------------------------------------------------------- /services/cloudflareService.js: -------------------------------------------------------------------------------- 1 | const { fetch } = require("../utils/fetch"); 2 | const { CLOUDFLARE_PURGE_CACHE_API } = require("../constants/cloudflareCache"); 3 | 4 | async function purgeCache(files) { 5 | const response = await fetch( 6 | CLOUDFLARE_PURGE_CACHE_API, 7 | "POST", 8 | null, 9 | { files: files }, 10 | { 11 | "X-Auth-Key": config.get("cloudflare.CLOUDFLARE_X_AUTH_KEY"), 12 | "X-Auth-Email": config.get("cloudflare.CLOUDFLARE_X_AUTH_EMAIL"), 13 | } 14 | ); 15 | 16 | return response; 17 | } 18 | 19 | module.exports = { 20 | purgeCache, 21 | }; 22 | -------------------------------------------------------------------------------- /services/discordMembersService.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const config = require("config"); 3 | const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl"); 4 | 5 | /** 6 | * Extracts the discord data of a user 7 | * @param discordId {String} - User discordId 8 | */ 9 | 10 | const getDiscordMemberDetails = async (discordId) => { 11 | try { 12 | const authToken = jwt.sign({}, config.get("rdsServerlessBot.rdsServerLessPrivateKey"), { 13 | algorithm: "RS256", 14 | expiresIn: config.get("rdsServerlessBot.ttl"), 15 | }); 16 | 17 | const memberDiscordDetails = await ( 18 | await fetch(`${DISCORD_BASE_URL}/member/${discordId}`, { 19 | method: "GET", 20 | headers: { "Content-Type": "application/json", Authorization: `Bearer ${authToken}` }, 21 | }) 22 | ).json(); 23 | return memberDiscordDetails; 24 | } catch (err) { 25 | logger.error(`Error while fetching discord data of the member: ${err}`); 26 | throw err; 27 | } 28 | }; 29 | 30 | module.exports = { getDiscordMemberDetails }; 31 | -------------------------------------------------------------------------------- /services/getFcmTokenFromUserId.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../utils/firestore"); 2 | 3 | const fcmTokenModel = firestore.collection("fcmToken"); 4 | 5 | export const getFcmTokenFromUserId = async (userId) => { 6 | if (!userId) return []; 7 | const fcmTokenSnapshot = await fcmTokenModel.where("userId", "==", userId).limit(1).get(); 8 | if (!fcmTokenSnapshot.empty) { 9 | return fcmTokenSnapshot.docs[0].data().fcmTokens; 10 | } 11 | return []; 12 | }; 13 | -------------------------------------------------------------------------------- /services/getUserIdsFromRoleId.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../utils/firestore"); 2 | const memberRoleModel = firestore.collection("member-group-roles"); 3 | 4 | export const getUserIdsFromRoleId = async (roleId) => { 5 | let userIds = []; 6 | try { 7 | const querySnapshot = await memberRoleModel.where("roleid", "==", roleId).get(); 8 | if (querySnapshot.empty) { 9 | return []; 10 | } 11 | if (!querySnapshot.empty) { 12 | userIds = querySnapshot.docs.map((doc) => doc.data().userid); 13 | } 14 | return userIds; 15 | } catch (error) { 16 | logger.error("error", error); 17 | throw error; 18 | } 19 | }; 20 | -------------------------------------------------------------------------------- /services/goalService.js: -------------------------------------------------------------------------------- 1 | const config = require("config"); 2 | 3 | const getOrCreateGoalUser = async ({ userId, roles }) => { 4 | const body = JSON.stringify({ 5 | data: { 6 | type: "User", 7 | attributes: { 8 | rds_id: userId, 9 | roles: roles, 10 | }, 11 | }, 12 | }); 13 | const goalSiteConfig = config.services.goalAPI; 14 | return fetch(`${goalSiteConfig.baseUrl}/api/v1/user/`, { 15 | method: "POST", 16 | body, 17 | headers: { "Content-Type": "application/vnd.api+json", "Rest-Key": goalSiteConfig.secretKey }, 18 | }); 19 | }; 20 | module.exports = { getOrCreateGoalUser }; 21 | -------------------------------------------------------------------------------- /services/index.js: -------------------------------------------------------------------------------- 1 | const { EventAPIService } = require("../services/EventAPIService"); 2 | const { EventTokenService } = require("../services/EventTokenService"); 3 | 4 | module.exports = { EventAPIService, EventTokenService }; 5 | -------------------------------------------------------------------------------- /services/issuesService.js: -------------------------------------------------------------------------------- 1 | const githubService = require("./githubService"); 2 | /** 3 | * Get the contributions of the user 4 | * @param {string} username 5 | */ 6 | 7 | const getOrgIssues = async () => { 8 | const data = await githubService.fetchIssues(); 9 | return data; 10 | }; 11 | 12 | const searchOrgIssues = async (searchString) => { 13 | const data = await githubService.fetchOpenIssues({ 14 | searchString, 15 | }); 16 | 17 | return data; 18 | }; 19 | 20 | module.exports = { 21 | getOrgIssues, 22 | searchOrgIssues, 23 | }; 24 | -------------------------------------------------------------------------------- /services/logService.ts: -------------------------------------------------------------------------------- 1 | import firestore from "../utils/firestore"; 2 | const logsModel = firestore.collection("logs"); 3 | import admin from "firebase-admin"; 4 | const { INTERNAL_SERVER_ERROR } = require("../constants/errorMessages"); 5 | 6 | interface LogMeta { 7 | userId?: string; 8 | [key: string]: number | string | object; 9 | } 10 | 11 | interface LogBody { 12 | [key: string]: number | string | object; 13 | } 14 | 15 | /** 16 | * Adds log 17 | * 18 | * @param type { string }: Type of the log 19 | * @param meta { LogMeta }: Meta data of the log 20 | * @param body { LogBody }: Body of the log 21 | */ 22 | export const addLog = async (type: string, meta: LogMeta, body: LogBody): Promise> => { 23 | try { 24 | const log = { 25 | type, 26 | timestamp: admin.firestore.Timestamp.fromDate(new Date()), 27 | meta, 28 | body, 29 | }; 30 | return await logsModel.add(log); 31 | } catch (err) { 32 | logger.error("Error in adding log", err); 33 | throw new Error(INTERNAL_SERVER_ERROR); 34 | } 35 | }; -------------------------------------------------------------------------------- /services/users.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../utils/firestore"); 2 | const { formatUsername } = require("../utils/username"); 3 | const userModel = firestore.collection("users"); 4 | const tasksModel = require("../models/tasks"); 5 | 6 | const getUsersWithIncompleteTasks = async (users) => { 7 | if (users.length === 0) return []; 8 | 9 | try { 10 | const userIds = users.map((user) => user.id); 11 | 12 | const abandonedTasksQuerySnapshot = await tasksModel.fetchIncompleteTasksByUserIds(userIds); 13 | 14 | if (abandonedTasksQuerySnapshot.empty) { 15 | return []; 16 | } 17 | 18 | const userIdsWithIncompleteTasks = new Set(abandonedTasksQuerySnapshot.map((doc) => doc.assignee)); 19 | 20 | const eligibleUsersWithTasks = users.filter((user) => userIdsWithIncompleteTasks.has(user.id)); 21 | 22 | return eligibleUsersWithTasks; 23 | } catch (error) { 24 | logger.error(`Error in getting users who abandoned tasks: ${error}`); 25 | throw error; 26 | } 27 | }; 28 | 29 | const generateUniqueUsername = async (firstName, lastName) => { 30 | try { 31 | const snapshot = await userModel 32 | .where("first_name", "==", firstName) 33 | .where("last_name", "==", lastName) 34 | .count() 35 | .get(); 36 | 37 | const existingUserCount = snapshot.data().count || 0; 38 | 39 | const suffix = existingUserCount + 1; 40 | const finalUsername = formatUsername(firstName, lastName, suffix); 41 | 42 | return finalUsername; 43 | } catch (err) { 44 | logger.error(`Error while generating unique username: ${err.message}`); 45 | throw err; 46 | } 47 | }; 48 | 49 | module.exports = { 50 | generateUniqueUsername, 51 | getUsersWithIncompleteTasks, 52 | }; 53 | -------------------------------------------------------------------------------- /test/fixtures/answers/answers.ts: -------------------------------------------------------------------------------- 1 | const answerData = [ 2 | { 3 | id: "dummy-answer-id", 4 | eventId: "event_id", 5 | answeredBy: "satyam-bajpai", 6 | answer: "this is demo answer", 7 | questionId: "demo-question-id-1", 8 | }, 9 | { 10 | eventId: "event_id-2", 11 | answeredBy: "vinayak-trivedi", 12 | answer: "this is my answer", 13 | questionId: "demo-question-id-2", 14 | }, 15 | { 16 | id: "dummy-answer-id-2", 17 | answer: "this is my answer", 18 | event_id: "event_id-2", 19 | answered_by: "vinayak-trivedi", 20 | question_id: "demo-question-id-2", 21 | status: "PENDING", 22 | reviewed_by: null, 23 | created_at: new Date().toString(), 24 | updated_at: new Date().toString(), 25 | }, 26 | { 27 | id: "dummy-answer-id-3", 28 | eventId: "event_id", 29 | answeredBy: "satyam-bajpai", 30 | answer: "this is demo answer 2", 31 | questionId: "demo-question-id-1", 32 | }, 33 | ]; 34 | 35 | module.exports = answerData; 36 | -------------------------------------------------------------------------------- /test/fixtures/arts/arts.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return [ 3 | { 4 | title: "Green Square", 5 | price: 50, 6 | css: "background: green;\nheight: 100;\nwidth: 100;", 7 | }, 8 | { 9 | title: "yellow square", 10 | css: "background: yellow; height: 100; width: 100;", 11 | price: 69, 12 | }, 13 | ]; 14 | }; 15 | -------------------------------------------------------------------------------- /test/fixtures/auctions/auctions.js: -------------------------------------------------------------------------------- 1 | const { NEELAM } = require("../../../constants/wallets"); 2 | 3 | /* Import fixtures 4 | * 5 | * Sample wallet for tests 6 | * 7 | * @return {Object} 8 | */ 9 | 10 | const auctionData = { 11 | item_type: NEELAM, 12 | quantity: 2, 13 | initial_price: 100, 14 | end_time: Date.now() + 60 * 60 * 1000, 15 | }; 16 | 17 | const auctionKeys = ["auctions", "message"]; 18 | 19 | const auctionWithIdKeys = ["bidders_and_bids", "end_time", "highest_bid", "item", "quantity", "seller", "start_time"]; 20 | 21 | module.exports = { auctionData, auctionKeys, auctionWithIdKeys }; 22 | -------------------------------------------------------------------------------- /test/fixtures/cache/cache.js: -------------------------------------------------------------------------------- 1 | const dummyResponse = { body: "test" }; 2 | 3 | module.exports = { dummyResponse }; 4 | -------------------------------------------------------------------------------- /test/fixtures/challenges/challenges.js: -------------------------------------------------------------------------------- 1 | const timeUtils = require("../../../utils/time"); 2 | 3 | module.exports = () => { 4 | return [ 5 | { 6 | title: "Sherlock and Anagrams", 7 | level: "Easy", 8 | start_date: timeUtils.getTimeInSecondAfter({}), 9 | end_date: timeUtils.getTimeInSecondAfter({ days: 10 }), 10 | }, 11 | ]; 12 | }; 13 | -------------------------------------------------------------------------------- /test/fixtures/cloudflareCache/data.js: -------------------------------------------------------------------------------- 1 | const cacheLogs = [ 2 | { 3 | timestamp: { 4 | _seconds: 1657193216, 5 | _nanoseconds: 912000000, 6 | }, 7 | type: "CLOUDFLARE_CACHE_PURGED", 8 | meta: { 9 | userId: "TEST_USER_ID", 10 | }, 11 | body: { 12 | message: "Log", 13 | }, 14 | }, 15 | ]; 16 | 17 | const cacheModelData = [ 18 | { 19 | timestamp: { _seconds: 1659870503, _nanoseconds: 482000000 }, 20 | }, 21 | { 22 | timestamp: { _seconds: 1659843503, _nanoseconds: 680003000 }, 23 | }, 24 | ]; 25 | 26 | const purgeCacheResponse = [ 27 | { 28 | data: { 29 | success: true, 30 | errors: [], 31 | messages: [], 32 | result: { 33 | id: "TEST_RESULT_ID", 34 | }, 35 | }, 36 | }, 37 | ]; 38 | 39 | module.exports = { 40 | cacheLogs, 41 | cacheModelData, 42 | purgeCacheResponse, 43 | }; 44 | -------------------------------------------------------------------------------- /test/fixtures/currencies/currencies.js: -------------------------------------------------------------------------------- 1 | const { DINERO, NEELAM } = require("../../../constants/wallets"); 2 | 3 | /* Import fixtures 4 | * 5 | * Sample wallet for tests 6 | * 7 | * @return {Object} 8 | */ 9 | 10 | module.exports = { 11 | default: { 12 | [DINERO]: 1000, 13 | [NEELAM]: 2, 14 | }, 15 | modified: { 16 | [DINERO]: 2000, 17 | [NEELAM]: 0, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /test/fixtures/discordactions/discordactions.js: -------------------------------------------------------------------------------- 1 | const groupData = [ 2 | { rolename: "Group 1", roleid: "1" }, 3 | { rolename: "Group 2", roleid: "2" }, 4 | { rolename: "Group 3", roleid: "3" }, 5 | { rolename: "admin", roleid: "4" }, 6 | { rolename: "group-test", roleid: "5" }, 7 | ]; 8 | 9 | const groupIdle7d = { rolename: "group-idle-7d+", roleid: 4, createdBy: "1dad23q23j131j" }; 10 | 11 | const groupIdle = { rolename: "group-idle", roleid: 3, createdBy: "1dad23q23j131jj" }; 12 | 13 | const roleData = { 14 | roleid: "test-role-id", 15 | userid: "test-user-id", 16 | }; 17 | 18 | const requestRoleData = { 19 | rolename: "test-role", 20 | }; 21 | 22 | const existingRole = { 23 | roleData: { roleid: "test-role-id", userid: "test-user-id" }, 24 | wasSuccess: false, 25 | }; 26 | 27 | const roleDataFromDiscord = { 28 | roles: [ 29 | { 30 | id: "test-role-id", 31 | name: "test-role-name", 32 | }, 33 | { 34 | id: "test-role-id1", 35 | name: "Group 2", 36 | }, 37 | ], 38 | }; 39 | 40 | const memberGroupData = [ 41 | { roleid: "1234", userid: "12356" }, 42 | { roleid: "12567", userid: "12367" }, 43 | { roleid: "12564", userid: "12350" }, 44 | ]; 45 | 46 | const groupOnboarding31dPlus = { 47 | rolename: "group-onboarding-31d+", 48 | roleid: "11334336", 49 | createdBy: "1dad23q23j131j", 50 | }; 51 | 52 | module.exports = { 53 | groupData, 54 | roleData, 55 | memberGroupData, 56 | existingRole, 57 | requestRoleData, 58 | groupIdle7d, 59 | roleDataFromDiscord, 60 | groupOnboarding31dPlus, 61 | groupIdle, 62 | }; 63 | -------------------------------------------------------------------------------- /test/fixtures/events/peers.js: -------------------------------------------------------------------------------- 1 | const eventData = require("../events/events")(); 2 | const event1Data = eventData[0]; 3 | 4 | const eventOnePeerData = { 5 | peerId: "dummyid", 6 | name: "Satyam Bajpai", 7 | eventId: event1Data.room_id, 8 | role: "guest", 9 | joinedAt: new Date(), 10 | }; 11 | 12 | module.exports = { eventOnePeerData }; 13 | -------------------------------------------------------------------------------- /test/fixtures/extension-requests/extensionRequests.ts: -------------------------------------------------------------------------------- 1 | import { REQUEST_STATE, REQUEST_TYPE } from "../../../constants/requests"; 2 | 3 | export const extensionCreateObject = { 4 | taskId: "4XlEQ64H8puuLTrwIi93", 5 | title: "Extension Request", 6 | oldEndsOn: 1708674980000, 7 | newEndsOn: 1709674980000, 8 | message: "request message", 9 | type: REQUEST_TYPE.EXTENSION, 10 | state: REQUEST_STATE.PENDING, 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/external-accounts/external-accounts.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return [ 3 | { 4 | type: "discord", 5 | token: "", 6 | attributes: { 7 | userName: "", 8 | discriminator: "", 9 | userAvatar: "", 10 | discordId: "", 11 | discordJoinedAt: "", 12 | expiry: 1674041460211, 13 | }, 14 | }, 15 | { 16 | // Bad Data 17 | type: "discord", 18 | token: 123, 19 | attributes: { 20 | userName: "", 21 | discriminator: "", 22 | userAvatar: "", 23 | discordId: "", 24 | discordJoinedAt: "", 25 | expiry: 1674041460211, 26 | }, 27 | }, 28 | { 29 | type: "discord", 30 | token: "", 31 | attributes: { 32 | userName: "", 33 | discriminator: "", 34 | userAvatar: "", 35 | discordId: "", 36 | discordJoinedAt: "", 37 | expiry: Date.now() + 600000, 38 | }, 39 | }, 40 | { 41 | type: "discord", 42 | token: "", 43 | attributes: { 44 | userName: "", 45 | discriminator: "", 46 | userAvatar: "", 47 | discordId: "", 48 | discordJoinedAt: "", 49 | expiry: Date.now() - 600000, 50 | }, 51 | }, 52 | ]; 53 | }; 54 | -------------------------------------------------------------------------------- /test/fixtures/featureFlag/featureFlag.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return [ 3 | { 4 | name: "Test-feature", 5 | title: "Test", 6 | config: { 7 | enabled: true, 8 | }, 9 | }, 10 | ]; 11 | }; 12 | -------------------------------------------------------------------------------- /test/fixtures/goals/Token.js: -------------------------------------------------------------------------------- 1 | const GET_OR_CREATE_GOAL_USER = { 2 | status: 201, 3 | json: () => 4 | Promise.resolve({ 5 | id: "123456", 6 | data: { attributes: { rds_id: "134556", token: "WHhHhWHu9ijHjkKhdbvFFhbnhCj" } }, 7 | }), 8 | }; 9 | module.exports = { GET_OR_CREATE_GOAL_USER }; 10 | -------------------------------------------------------------------------------- /test/fixtures/invites/invitesData.ts: -------------------------------------------------------------------------------- 1 | import { InviteBody, Invite } from "../../../types/invites"; 2 | 3 | export const InviteBodyData: InviteBody = { 4 | userId: "123456", 5 | purpose: "For user who passed the Twitter space test", 6 | }; 7 | 8 | 9 | export const inviteData : Invite[] = [ 10 | { 11 | id: "fsdfdf44f4s5ffsdf1", 12 | userId: "user454fdfff1", 13 | purpose: "For user who passed the Twitter space test", 14 | inviteLink: "https://discord.gg/invite_1", 15 | createdAt: 1705456623990, 16 | }, 17 | 18 | ]; 19 | -------------------------------------------------------------------------------- /test/fixtures/logs/requests.js: -------------------------------------------------------------------------------- 1 | export const requestsLogs = [ 2 | { 3 | meta: { 4 | createdAt: 1709175284035, 5 | createdBy: "iODXB6ns8jaZB9p0XlBw", 6 | requestId: "CNExxpR1F4UPbtYIpxFP", 7 | action: "create", 8 | }, 9 | type: "REQUEST_CREATED", 10 | body: { 11 | createdAt: 1709175282967, 12 | requestedBy: "iODXB6ns8jaZB9p0XlBw", 13 | from: 1711676421000, 14 | until: 1714354821000, 15 | id: "CNExxpR1F4UPbtYIpxFP", 16 | state: "PENDING", 17 | type: "OOO", 18 | message: "Out of office for personal reasons.", 19 | updatedAt: 1709175282967, 20 | }, 21 | timestamp: { 22 | _seconds: 1709175284, 23 | _nanoseconds: 35000000, 24 | }, 25 | }, 26 | { 27 | meta: { 28 | createdAt: 1709170848580, 29 | createdBy: "iODXB6ns8jaZB9p0XlBw", 30 | requestId: "K7ioni8arDgRCBAWwYxp", 31 | action: "create", 32 | }, 33 | type: "REQUEST_CREATED", 34 | body: { 35 | createdAt: 1709170848318, 36 | requestedBy: "iODXB6ns8jaZB9p0XlBw", 37 | from: 1711676421000, 38 | until: 1714354821000, 39 | id: "K7ioni8arDgRCBAWwYxp", 40 | state: "PENDING", 41 | type: "OOO", 42 | message: "Out of office for personal reasons.", 43 | updatedAt: 1709170848318, 44 | }, 45 | timestamp: { 46 | _seconds: 1709170848, 47 | _nanoseconds: 580000000, 48 | }, 49 | }, 50 | ]; 51 | -------------------------------------------------------------------------------- /test/fixtures/logs/tasks.js: -------------------------------------------------------------------------------- 1 | export const taskLogs = [ 2 | { 3 | meta: { 4 | userId: "aaL1MXrpmnUNfLkhgXRj", 5 | taskId: "P8ZZX2ef8heTs0erA6a8", 6 | username: "shubham-sharma", 7 | }, 8 | type: "task", 9 | body: { 10 | new: { 11 | status: "NEEDS_REVIEW", 12 | }, 13 | subType: "update", 14 | }, 15 | timestamp: { 16 | _seconds: 1710731591, 17 | _nanoseconds: 323000000, 18 | }, 19 | }, 20 | { 21 | meta: { 22 | userId: "aaL1MXrpmnUNfLkhgXRj", 23 | taskId: "P8ZZX2ef8heTs0erA6a8", 24 | username: "shubham-sharma", 25 | }, 26 | type: "task", 27 | body: { 28 | new: { 29 | status: "IN_PROGRESS", 30 | }, 31 | subType: "update", 32 | }, 33 | timestamp: { 34 | _seconds: 1710731469, 35 | _nanoseconds: 274000000, 36 | }, 37 | }, 38 | { 39 | meta: { 40 | userId: "aaL1MXrpmnUNfLkhgXRj", 41 | taskId: "P8ZZX2ef8heTs0erA6a8", 42 | username: "shubham-sharma", 43 | }, 44 | type: "task", 45 | body: { 46 | new: { 47 | status: "BLOCKED", 48 | }, 49 | subType: "update", 50 | }, 51 | timestamp: { 52 | _seconds: 1710731408, 53 | _nanoseconds: 684000000, 54 | }, 55 | }, 56 | ]; 57 | -------------------------------------------------------------------------------- /test/fixtures/qrCodeAuth/qrCodeAuth.js: -------------------------------------------------------------------------------- 1 | /* Import fixtures 2 | * 3 | * User Deivce Info for unit testing 4 | * 5 | * @return {Object} 6 | */ 7 | 8 | const userDeviceInfoDataArray = [ 9 | { 10 | user_id: "TEST_USER_ID", 11 | device_info: "TEST_DEVICE_INFO", 12 | device_id: "TEST_DEVICE_ID", 13 | }, 14 | ]; 15 | 16 | const userDeviceInfoIdKeys = ["user_id", "device_info", "device_id"]; 17 | 18 | module.exports = { userDeviceInfoDataArray, userDeviceInfoIdKeys }; 19 | -------------------------------------------------------------------------------- /test/fixtures/recruiter/recruiter.js: -------------------------------------------------------------------------------- 1 | /* Import fixtures 2 | * 3 | * Recruiter info for unit testing 4 | * 5 | * @return {Object} 6 | */ 7 | 8 | const recruiterDataArray = [ 9 | { 10 | company: "Test-feature", 11 | first_name: "Ankita", 12 | last_name: "Bannore", 13 | designation: "Learner", 14 | reason: "Test", 15 | email: "abc@gmail.com", 16 | currency: "$", 17 | package: 100000, 18 | }, 19 | ]; 20 | 21 | const recruiterWithIdKeys = [ 22 | "company", 23 | "first_name", 24 | "last_name", 25 | "designation", 26 | "reason", 27 | "email", 28 | "currency", 29 | "package", 30 | "timestamp", 31 | "id", 32 | "username", 33 | ]; 34 | 35 | module.exports = { recruiterDataArray, recruiterWithIdKeys }; 36 | -------------------------------------------------------------------------------- /test/fixtures/standup/standup.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return [ 3 | { 4 | timestamp: "2023-04-20T16:23:58Z", 5 | yesterday: "Did something", 6 | today: "will do something", 7 | blockers: "nothing", 8 | }, 9 | { 10 | yesterday: "Did something", 11 | today: "will do something", 12 | blockers: "nothing", 13 | }, 14 | ]; 15 | }; 16 | -------------------------------------------------------------------------------- /test/fixtures/subscription/subscription.ts: -------------------------------------------------------------------------------- 1 | export const subscribedMessage = "User subscribed successfully"; 2 | export const unSubscribedMessage = "User unsubscribed successfully"; 3 | export const subscriptionData = { 4 | phone: "+911234567890", 5 | email: "example@gmail.com", 6 | }; 7 | 8 | -------------------------------------------------------------------------------- /test/fixtures/taskRequests/taskRequests.ts: -------------------------------------------------------------------------------- 1 | import { REQUEST_STATE, REQUEST_TYPE } from "../../../constants/requests"; 2 | import { TASK_REQUEST_TYPE } from "../../../constants/taskRequests"; 3 | 4 | export const validTaskCreqtionRequest = { 5 | externalIssueUrl: "https://api.github.com/repos/Real-Dev-Squad/website-my/issues/599", 6 | externalIssueHtmlUrl: "https://github.com/Real-Dev-Squad/website-my/issues/599", 7 | userId: "iODXB6ns8jaZB9p0XlBw", 8 | requestType: TASK_REQUEST_TYPE.CREATION, 9 | proposedStartDate: 1718845551203, 10 | proposedDeadline: 1719450351203, 11 | description: "Task Create Description", 12 | markdownEnabled: true, 13 | state: REQUEST_STATE.PENDING, 14 | type: REQUEST_TYPE.TASK, 15 | }; 16 | 17 | export const validTaskAssignmentRequest = { 18 | externalIssueUrl: "https://api.github.com/repos/Real-Dev-Squad/website-my/issues/599", 19 | externalIssueHtmlUrl: "https://github.com/Real-Dev-Squad/website-my/issues/599", 20 | taskId: "iODXB6ns8jaZB9p0XlBw", 21 | requestType: TASK_REQUEST_TYPE.ASSIGNMENT, 22 | proposedStartDate: 1718845551203, 23 | proposedDeadline: 1719450351203, 24 | description: "Task Create Description", 25 | markdownEnabled: true, 26 | state: REQUEST_STATE.PENDING, 27 | type: REQUEST_TYPE.TASK, 28 | }; 29 | -------------------------------------------------------------------------------- /test/fixtures/tasks/tasks1.js: -------------------------------------------------------------------------------- 1 | const { DINERO, NEELAM } = require("../../../constants/wallets"); 2 | const userData = require("../user/user")(); 3 | const appOwner = userData[3]; 4 | /** 5 | * Sample tasks for tests 6 | * @return {object} 7 | */ 8 | 9 | module.exports = () => { 10 | return [ 11 | { 12 | title: "Test task", 13 | type: "feature", 14 | endsOn: 1234, 15 | startedOn: 4567, 16 | status: "DONE", 17 | percentCompleted: 100, 18 | participants: [], 19 | assignee: appOwner.username, 20 | completionAward: { [DINERO]: 3, [NEELAM]: 300 }, 21 | lossRate: { [DINERO]: 1 }, 22 | isNoteworthy: true, 23 | }, 24 | ]; 25 | }; 26 | -------------------------------------------------------------------------------- /test/fixtures/time/time.js: -------------------------------------------------------------------------------- 1 | const admin = require("firebase-admin"); 2 | 3 | const minutesToMilliseconds = [ 4 | { 5 | param: 60, 6 | result: 3600000, 7 | }, 8 | { 9 | param: 10000000000, 10 | result: 600000000000000, 11 | }, 12 | ]; 13 | 14 | const hoursToMilliseconds = [ 15 | { 16 | param: 60, 17 | result: 216000000, 18 | }, 19 | { 20 | param: 10000000000, 21 | result: 36000000000000000, 22 | }, 23 | ]; 24 | 25 | const daysToMilliseconds = [ 26 | { 27 | param: 60, 28 | result: 5184000000, 29 | }, 30 | { 31 | param: 10000000000, 32 | result: 864000000000000000, 33 | }, 34 | ]; 35 | 36 | const timeInSecondsAfter = [ 37 | { 38 | param: { 39 | timestamp: 1648370545193, 40 | }, 41 | result: 1648370545, 42 | }, 43 | { 44 | param: { 45 | timestamp: 1648370545193, 46 | days: 20, 47 | }, 48 | result: 1648370545 + 1728000, 49 | }, 50 | ]; 51 | 52 | const timeBeforeHour = [ 53 | { 54 | param: { 55 | timestamp: admin.firestore.Timestamp.fromDate(new Date(1671820200 * 1000)), 56 | hours: 24, 57 | }, 58 | result: admin.firestore.Timestamp.fromDate(new Date(1671733800 * 1000))._seconds, 59 | }, 60 | { 61 | param: { 62 | timestamp: admin.firestore.Timestamp.fromDate(new Date()), 63 | }, 64 | result: admin.firestore.Timestamp.fromDate(new Date())._seconds, 65 | }, 66 | ]; 67 | 68 | module.exports = { 69 | minutesToMilliseconds, 70 | hoursToMilliseconds, 71 | daysToMilliseconds, 72 | timeInSecondsAfter, 73 | timeBeforeHour, 74 | }; 75 | -------------------------------------------------------------------------------- /test/fixtures/trackedProgress/index.js: -------------------------------------------------------------------------------- 1 | const predefinedTrackedProgressDataForUser = { 2 | type: "user", 3 | monitored: true, 4 | frequency: 1, 5 | createdAt: "2023-05-20T03:49:20.298Z", 6 | updatedAt: "2023-05-20T03:49:20.298Z", 7 | }; 8 | 9 | const predefinedTrackedProgressDataForTask = { 10 | type: "task", 11 | monitored: true, 12 | frequency: 4, 13 | createdAt: "2023-05-20T03:49:20.298Z", 14 | updatedAt: "2023-05-20T03:49:20.298Z", 15 | }; 16 | 17 | const trackedProgressUserDataForPost = { 18 | type: "user", 19 | monitored: true, 20 | }; 21 | 22 | const trackedProgressTaskDataForPost = { 23 | type: "task", 24 | monitored: true, 25 | frequency: 2, 26 | }; 27 | 28 | const trackedProgressDataForPatch = { 29 | monitored: false, 30 | }; 31 | 32 | /** 33 | * Checks if the given value is a valid ISO string representation of a date. 34 | * 35 | * @param {string} value - The value to check. 36 | * @returns {boolean} - Returns true if the value is a valid ISO string, otherwise false. 37 | * 38 | */ 39 | const isISOString = (value) => { 40 | return new Date(value).toISOString() === value; 41 | }; 42 | 43 | module.exports = { 44 | predefinedTrackedProgressDataForUser, 45 | predefinedTrackedProgressDataForTask, 46 | trackedProgressUserDataForPost, 47 | trackedProgressTaskDataForPost, 48 | trackedProgressDataForPatch, 49 | isISOString, 50 | }; 51 | -------------------------------------------------------------------------------- /test/fixtures/user/photo-verification.js: -------------------------------------------------------------------------------- 1 | const userPhotoVerificationData = { 2 | discordId: "12345", 3 | userId: "1234567abcd", 4 | discord: { 5 | url: "https://cdn.discordapp.com/avatars/abc/1234abcd.png", 6 | approved: true, 7 | date: { 8 | _seconds: 1686518413, 9 | _nanoseconds: 453000000, 10 | }, 11 | }, 12 | profile: { 13 | url: "https://res.cloudinary.com/avatars/1234/something.png", 14 | approved: false, 15 | date: { 16 | _seconds: 1686518413, 17 | _nanoseconds: 453000000, 18 | }, 19 | }, 20 | }; 21 | const newUserPhotoVerificationData = { 22 | discordId: "1234567", 23 | userId: "new-user-id", 24 | discord: { 25 | url: "https://discord.example.com/demo.png", 26 | approved: false, 27 | date: { 28 | _seconds: 1686518413, 29 | _nanoseconds: 453000000, 30 | }, 31 | }, 32 | profile: { 33 | url: "https://cloudinary.example.com/demo.png", 34 | approved: false, 35 | date: { 36 | _seconds: 1686518413, 37 | _nanoseconds: 453000000, 38 | }, 39 | }, 40 | }; 41 | 42 | module.exports = { userPhotoVerificationData, newUserPhotoVerificationData }; 43 | -------------------------------------------------------------------------------- /test/fixtures/user/removalData.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return ["phone", "emails", "tokens", "chaincode"]; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/user/search.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample search values for tests 3 | * @return {object} 4 | */ 5 | 6 | module.exports = () => { 7 | return { an: "an", AN: "AN", null: "", mu: "mu", number23: 23 }; 8 | }; 9 | -------------------------------------------------------------------------------- /test/fixtures/userBadges/userBadges.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User's badges response 3 | * Multiple responses can be added to the object if required 4 | * 5 | * @return {Object} 6 | */ 7 | 8 | const userFound = { 9 | userExists: true, 10 | userBadges: [{ title: "badgeTitle", description: "badgeDescription" }], 11 | }; 12 | 13 | const userNotFound = { 14 | userExists: false, 15 | userBadges: [], 16 | }; 17 | 18 | const badgesEmpty = { 19 | userExists: true, 20 | userBadges: [], 21 | }; 22 | 23 | module.exports = { userFound, userNotFound, badgesEmpty }; 24 | -------------------------------------------------------------------------------- /test/fixtures/userDeviceInfo/userDeviceInfo.js: -------------------------------------------------------------------------------- 1 | /* Import fixtures 2 | * 3 | * User Deivce Info for unit testing 4 | * 5 | * @return {Object} 6 | */ 7 | 8 | const userDeviceInfoDataArray = [ 9 | { 10 | user_id: "TEST_USER_ID", 11 | device_info: "TEST_DEVICE_INFO", 12 | device_id: "TEST_DEVICE_ID", 13 | }, 14 | ]; 15 | 16 | const userDeviceInfoIdKeys = ["user_id", "device_info", "device_id"]; 17 | 18 | module.exports = { userDeviceInfoDataArray, userDeviceInfoIdKeys }; 19 | -------------------------------------------------------------------------------- /test/fixtures/userFutureStatus/userFutureStatusData.ts: -------------------------------------------------------------------------------- 1 | import { REQUEST_TYPE } from "../../../constants/requests"; 2 | import { statusState } from "../../../constants/userStatus"; 3 | 4 | export const userFutureStatusData = { 5 | requestId: "randomId", 6 | status: REQUEST_TYPE.OOO, 7 | state: statusState.UPCOMING, 8 | from: 1712277700000, 9 | endsOn: 1712277700000, 10 | userId: "randomUserId", 11 | message: "I am out of office", 12 | }; 13 | -------------------------------------------------------------------------------- /test/fixtures/wallet/wallet.js: -------------------------------------------------------------------------------- 1 | /* Import fixtures 2 | * 3 | * Sample wallet for tests 4 | * 5 | * @return {Object} 6 | */ 7 | 8 | const walletKeys = ["id", "data"]; 9 | 10 | const walletBodyKeys = ["message", "wallet"]; 11 | 12 | const walletDataKeys = ["userId", "isActive", "currencies"]; 13 | 14 | module.exports = { walletBodyKeys, walletKeys, walletDataKeys }; 15 | -------------------------------------------------------------------------------- /test/integration/issues.test.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const sinon = require("sinon"); 3 | const { expect } = chai; 4 | const chaiHttp = require("chai-http"); 5 | const app = require("../../server"); 6 | const cleanDb = require("../utils/cleanDb"); 7 | const githubService = require("../../services/githubService"); 8 | const issuesMockData = require("../fixtures/issues/issues"); 9 | 10 | chai.use(chaiHttp); 11 | 12 | describe("Issues", function () { 13 | afterEach(async function () { 14 | sinon.restore(); 15 | await cleanDb(); 16 | }); 17 | 18 | describe("GET /issues fetch github issues", function () { 19 | it("Should return issue when valid github page url is passed", async function () { 20 | const fetchIssuesByIdStub = sinon.stub(githubService, "fetchIssuesById").resolves(issuesMockData.issuesData); 21 | const res = await chai.request(app).get("/issues").query({ q: issuesMockData.issuesHtmlUrl }); 22 | expect(res).to.have.status(200); 23 | expect(fetchIssuesByIdStub.calledOnce).to.be.equal(true); 24 | expect(res.body.message).to.equal("Issues returned successfully!"); 25 | expect(res.body.issues).to.deep.equal([issuesMockData.issuesData]); 26 | }); 27 | 28 | it("Should not call fetch issues by id function when random string is passed", async function () { 29 | const fetchIssuesByIdSpy = sinon.spy(githubService, "fetchIssuesById"); 30 | await chai.request(app).get("/issues").query({ q: "abc+def" }); 31 | expect(fetchIssuesByIdSpy.calledOnce).to.be.equal(false); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/unit/middlewares/arts-validator.test.js: -------------------------------------------------------------------------------- 1 | const Sinon = require("sinon"); 2 | const { createArt } = require("../../../middlewares/validators/arts"); 3 | const { expect } = require("chai"); 4 | 5 | describe("Test the arts validator", function () { 6 | it("Allows the request to pass", async function () { 7 | const req = { 8 | body: { 9 | title: "some title", 10 | price: 100, 11 | css: "random css", 12 | }, 13 | }; 14 | const res = {}; 15 | const nextSpy = Sinon.spy(); 16 | await createArt(req, res, nextSpy); 17 | expect(nextSpy.callCount).to.be.equal(1); 18 | }); 19 | 20 | it("Stops the request to propogate to next", async function () { 21 | const req = { 22 | body: { 23 | "": "", 24 | }, 25 | }; 26 | const res = { 27 | boom: { 28 | badRequest: () => {}, 29 | }, 30 | }; 31 | const nextSpy = Sinon.spy(); 32 | await createArt(req, res, nextSpy); 33 | expect(nextSpy.callCount).to.be.equal(0); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /test/unit/middlewares/authenticateProfile.test.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const sinon = require("sinon"); 3 | const { expect } = chai; 4 | const authenticateProfile = require("../../../middlewares/authenticateProfile.js"); 5 | 6 | describe("authenticateProfile Middleware", function () { 7 | let req, res, next, authenticateStub, auth; 8 | 9 | beforeEach(function () { 10 | authenticateStub = sinon.spy(); 11 | auth = authenticateProfile(authenticateStub); 12 | 13 | req = { 14 | query: {}, 15 | }; 16 | res = { 17 | boom: { 18 | unauthorized: sinon.spy(), 19 | forbidden: sinon.spy(), 20 | }, 21 | }; 22 | next = sinon.spy(); 23 | }); 24 | 25 | it("should call authenticate when profile query is true", async function () { 26 | req.query.profile = "true"; 27 | await auth(req, res, next); 28 | 29 | expect(authenticateStub.withArgs(req, res, next).calledOnce).to.equal(true); 30 | expect(next.calledOnce).to.equal(false); 31 | }); 32 | 33 | it("should call next when profile query is not true", async function () { 34 | req.query.profile = "false"; 35 | 36 | await auth(req, res, next); 37 | 38 | expect(authenticateStub.calledOnce).to.equal(false); 39 | expect(next.calledOnce).to.equal(true); 40 | }); 41 | 42 | it("should call next when profile query is missing", async function () { 43 | await auth(req, res, next); 44 | 45 | expect(authenticateStub.calledOnce).to.equal(false); 46 | expect(next.calledOnce).to.equal(true); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /test/unit/middlewares/authorization.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { userHasPermission } = require("../../../middlewares/authorization"); 3 | 4 | describe("userHasPermission", function () { 5 | it("user has default role and no required role is provided", function (done) { 6 | expect(userHasPermission("", { default: true })).to.be.equal(true); 7 | return done(); 8 | }); 9 | 10 | it("user has default role and required role is `appOwner`", function (done) { 11 | expect(userHasPermission("appOwner", { default: true })).to.be.equal(false); 12 | return done(); 13 | }); 14 | 15 | it("user has app_owner role and required role is `appOwner`", function (done) { 16 | expect(userHasPermission("appOwner", { app_owner: true })).to.be.equal(true); 17 | return done(); 18 | }); 19 | 20 | it("user has super_user role and required role is `appOwner`", function (done) { 21 | expect(userHasPermission("appOwner", { super_user: true })).to.be.equal(true); 22 | return done(); 23 | }); 24 | 25 | it("user has app_owner role and required role is `superUser`", function (done) { 26 | expect(userHasPermission("superUser", { app_owner: true })).to.be.equal(false); 27 | return done(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/unit/middlewares/challenges-validator.test.js: -------------------------------------------------------------------------------- 1 | const Sinon = require("sinon"); 2 | const { createChallenge } = require("../../../middlewares/validators/challenges"); 3 | const { expect } = require("chai"); 4 | 5 | describe("Middleware | Validators | Challenges", function () { 6 | describe("create challenge validator", function () { 7 | it("lets the request pass to next", async function () { 8 | const req = { 9 | body: { 10 | level: "Noob", 11 | title: "The noob challenge", 12 | start_date: 1254324345, 13 | end_date: 354654345, 14 | }, 15 | }; 16 | const res = {}; 17 | const nextSpy = Sinon.spy(); 18 | await createChallenge(req, res, nextSpy); 19 | expect(nextSpy.calledOnce).to.be.equal(true); 20 | }); 21 | 22 | it("Stops the propogation of the next", async function () { 23 | const req = { 24 | body: { 25 | level: "Noob", 26 | }, 27 | }; 28 | const res = { 29 | boom: { 30 | badRequest: () => {}, 31 | }, 32 | }; 33 | const nextSpy = Sinon.spy(); 34 | await createChallenge(req, res, nextSpy).catch((err) => { 35 | expect(err).to.be.an.instanceOf(Error); 36 | }); 37 | expect(nextSpy.calledOnce).to.be.equal(false); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /test/unit/middlewares/fcmToken-validator.test.js: -------------------------------------------------------------------------------- 1 | const Sinon = require("sinon"); 2 | 3 | const { expect } = require("chai"); 4 | const { fcmTokenValidator } = require("../../../middlewares/validators/fcmToken"); 5 | 6 | describe("Test the fcmToken validator", function () { 7 | it("Allows the request to pass", async function () { 8 | const req = { 9 | body: { 10 | fcmToken: "some token", 11 | }, 12 | }; 13 | const res = {}; 14 | const nextSpy = Sinon.spy(); 15 | await fcmTokenValidator(req, res, nextSpy); 16 | expect(nextSpy.callCount).to.be.equal(1); 17 | }); 18 | 19 | it("Stops the request to propogate to next", async function () { 20 | const req = { 21 | body: { 22 | "": "", 23 | }, 24 | }; 25 | const res = { 26 | boom: { 27 | badRequest: () => {}, 28 | }, 29 | }; 30 | const nextSpy = Sinon.spy(); 31 | await fcmTokenValidator(req, res, nextSpy); 32 | expect(nextSpy.callCount).to.be.equal(0); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/unit/middlewares/invite.test.ts: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import sinon from "sinon"; 3 | const { expect } = chai; 4 | 5 | const { createInviteValidator } = require("./../../../middlewares/validators/invites"); 6 | import { InviteBodyData, inviteData } from "./../../fixtures/invites/invitesData"; 7 | 8 | describe("Invite Validators", function () { 9 | describe("createInviteValidator", function () { 10 | it("should pass validation for a valid create request", async function () { 11 | const req = { 12 | body: InviteBodyData, 13 | }; 14 | const res = {}; 15 | const nextSpy = sinon.spy(); 16 | 17 | await createInviteValidator(req, res, nextSpy); 18 | 19 | expect(nextSpy.calledOnce).to.equal(true); 20 | }); 21 | 22 | it("should throw an error for an invalid create request", async function () { 23 | const req = { 24 | body: { 25 | // Missing required fields 26 | }, 27 | }; 28 | const res = { 29 | boom: { 30 | badRequest: sinon.spy(), 31 | }, 32 | }; 33 | const nextSpy = sinon.spy(); 34 | 35 | await createInviteValidator(req, res, nextSpy); 36 | 37 | expect(res.boom.badRequest.calledOnce).to.equal(true); 38 | expect(nextSpy.calledOnce).to.equal(false); 39 | }); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/unit/middlewares/skipAuthenticateForOnboardingExtension.test.ts: -------------------------------------------------------------------------------- 1 | import sinon from "sinon"; 2 | import { skipAuthenticateForOnboardingExtensionRequest } from "../../../middlewares/skipAuthenticateForOnboardingExtension"; 3 | import { REQUEST_TYPE } from "../../../constants/requests"; 4 | import { assert } from "chai"; 5 | 6 | describe("skipAuthenticateForOnboardingExtensionRequest Middleware", () => { 7 | let req, res, next, authenticate: sinon.SinonSpy, verifyDiscordBot: sinon.SinonSpy; 8 | let middleware; 9 | beforeEach(() => { 10 | authenticate = sinon.spy(); 11 | verifyDiscordBot = sinon.spy(); 12 | middleware = skipAuthenticateForOnboardingExtensionRequest(authenticate, verifyDiscordBot); 13 | req = { 14 | body:{}, 15 | query:{}, 16 | }, 17 | res = {} 18 | }); 19 | 20 | it("should call authenticate when type is not onboarding", () => { 21 | req.body.type = REQUEST_TYPE.TASK 22 | middleware(req, res, next); 23 | 24 | assert.isTrue(authenticate.calledOnce, "authenticate should be called once"); 25 | assert.isTrue(verifyDiscordBot.notCalled, "verifyDiscordBot should not be called"); 26 | }); 27 | 28 | it("should call verifyDiscordBot when type is onboarding", async () => { 29 | req.body.type = REQUEST_TYPE.ONBOARDING; 30 | 31 | middleware(req, res, next); 32 | 33 | assert.isTrue(verifyDiscordBot.calledOnce, "verifyDiscordBot should be called once"); 34 | assert.isTrue(authenticate.notCalled, "authenticate should not be called"); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /test/unit/middlewares/skipAuthorizeRolesWrapper.test.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const sinon = require("sinon"); 3 | const { assert } = chai; 4 | const skipAuthorizeRolesUnderFF = require("../../../middlewares/skipAuthorizeRolesWrapper"); 5 | 6 | describe("skipAuthorizeRolesUnderFF Middleware", function () { 7 | let req, res, next, authorizeMiddleware; 8 | 9 | beforeEach(function () { 10 | req = { query: {} }; 11 | res = {}; 12 | next = sinon.spy(); 13 | authorizeMiddleware = sinon.spy(); 14 | }); 15 | 16 | it("should call next() when dev is true", function () { 17 | req.query.dev = "true"; 18 | 19 | const middleware = skipAuthorizeRolesUnderFF(authorizeMiddleware); 20 | middleware(req, res, next); 21 | 22 | assert.isTrue(next.calledOnce, "next() should be called once"); 23 | assert.isFalse(authorizeMiddleware.called, "authorizeMiddleware should not be called"); 24 | }); 25 | 26 | it("should call authorizeMiddleware when dev is false", function () { 27 | req.query.dev = "false"; 28 | 29 | const middleware = skipAuthorizeRolesUnderFF(authorizeMiddleware); 30 | middleware(req, res, next); 31 | 32 | assert.isTrue(authorizeMiddleware.calledOnce, "authorizeMiddleware should be called once"); 33 | assert.isFalse(next.called, "next() should not be called"); 34 | }); 35 | 36 | it("should call authorizeMiddleware when dev is not provided", function () { 37 | const middleware = skipAuthorizeRolesUnderFF(authorizeMiddleware); 38 | middleware(req, res, next); 39 | 40 | assert.isTrue(authorizeMiddleware.calledOnce, "authorizeMiddleware should be called once"); 41 | assert.isFalse(next.called, "next() should not be called"); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unit/middlewares/userStatusValidator.js: -------------------------------------------------------------------------------- 1 | const Sinon = require("sinon"); 2 | const { expect } = require("chai"); 3 | const { validateGetQueryParams } = require("../../../middlewares/validators/userStatus"); 4 | 5 | describe("Middleware | Validators | userStatus", function () { 6 | describe("validateRequestQuery", function () { 7 | it("lets the request pass to the next function for a valid query", async function () { 8 | const res = {}; 9 | const req = { 10 | query: { 11 | state: "IDLE", 12 | }, 13 | }; 14 | const nextSpy = Sinon.spy(); 15 | await validateGetQueryParams(req, res, nextSpy); 16 | expect(nextSpy.calledOnce).to.be.equal(true); 17 | 18 | delete req.query.state; 19 | req.query.aggregate = true; 20 | await validateGetQueryParams(req, res, nextSpy); 21 | expect(nextSpy.calledTwice).to.be.equal(true); 22 | }); 23 | 24 | it("stops the propogation of the event to next function", async function () { 25 | const res = { 26 | boom: { 27 | badRequest: () => {}, 28 | }, 29 | }; 30 | const nextSpy = Sinon.spy(); 31 | const req = { 32 | query: { 33 | taskStatus: "invalidKey", 34 | }, 35 | }; 36 | await validateGetQueryParams(req, res, nextSpy).catch((err) => { 37 | expect(err).to.be.an.instanceOf(Error); 38 | }); 39 | expect(nextSpy.callCount).to.be.equal(0); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/unit/middlewares/userStatusValidator.test.js: -------------------------------------------------------------------------------- 1 | const Sinon = require("sinon"); 2 | const { expect } = require("chai"); 3 | const { validateUserStatus } = require("../../../middlewares/validators/userStatus"); 4 | 5 | describe("Validation Tests for Cancel OOO", function () { 6 | let req; 7 | let res; 8 | let nextSpy; 9 | 10 | beforeEach(function () { 11 | res = { 12 | boom: { 13 | badRequest: Sinon.spy(), 14 | }, 15 | }; 16 | nextSpy = Sinon.spy(); 17 | }); 18 | 19 | it("should validate for a valid request", async function () { 20 | req = { 21 | body: { 22 | cancelOoo: true, 23 | }, 24 | }; 25 | await validateUserStatus(req, res, nextSpy); 26 | expect(nextSpy.calledOnce).to.be.equal(true); 27 | }); 28 | 29 | it("should not validate for an invalid request", async function () { 30 | const req = { 31 | body: { 32 | cancelOoo: "not a boolean", 33 | }, 34 | }; 35 | try { 36 | await validateUserStatus(req, res, nextSpy); 37 | } catch (error) { 38 | expect(error).to.be.an.instanceOf(Error); 39 | expect(nextSpy.callCount).to.be.equal(0); 40 | expect(res.boom.badRequest.callCount).to.be.equal(1); 41 | } 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /test/unit/models/fcmToken.test.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const firestore = require("../../../utils/firestore"); 4 | const { saveFcmToken } = require("../../../models/fcmToken"); 5 | const cleanDb = require("../../utils/cleanDb"); 6 | const fcmTokenModel = firestore.collection("fcmToken"); 7 | 8 | describe("FCM token", function () { 9 | describe("Save FCM Token", function () { 10 | afterEach(async function () { 11 | await cleanDb(); 12 | }); 13 | 14 | it("it should save FCM token", async function () { 15 | const fcmTokenData = { userId: "jkkshdsjkh", fcmToken: "iedsijdsdj" }; 16 | await saveFcmToken(fcmTokenData); 17 | const queryResponse = await fcmTokenModel.where("userId", "==", fcmTokenData.userId).get(); 18 | expect(queryResponse.docs[0].data().fcmTokens).includes(fcmTokenData.fcmToken); 19 | }); 20 | 21 | it("it should store another FCM token in same user-id", async function () { 22 | const fcmTokenData1 = { userId: "jkkshdsjkh", fcmToken: "sdjagkjsd" }; 23 | const fcmTokenData2 = { userId: "jkkshdsjkh", fcmToken: "sdsnkj" }; 24 | 25 | await saveFcmToken(fcmTokenData1); 26 | await saveFcmToken(fcmTokenData2); 27 | 28 | const queryResponse = await fcmTokenModel.where("userId", "==", fcmTokenData1.userId).get(); 29 | expect(queryResponse.docs[0].data().fcmTokens.length).equals(2); 30 | }); 31 | }); 32 | }); 33 | -------------------------------------------------------------------------------- /test/unit/models/userFutureStatus.test.ts: -------------------------------------------------------------------------------- 1 | import { createUserFutureStatus, getUserFutureStatus } from "../../../models/userFutureStatus"; 2 | import { expect } from "chai"; 3 | import cleanDb from "../../utils/cleanDb"; 4 | import { UserFutureStatusType } from "../../../types/userFutureStatus"; 5 | import { userFutureStatusData } from "../../fixtures/userFutureStatus/userFutureStatusData"; 6 | 7 | describe("models/userFutureStatus", () => { 8 | afterEach(async () => { 9 | await cleanDb(); 10 | }); 11 | 12 | describe("createUserFutureStatus ", () => { 13 | it("should successfully create a new user future status", async () => { 14 | const userFutureStatus = await createUserFutureStatus(userFutureStatusData as UserFutureStatusType); 15 | expect(userFutureStatus).to.not.be.null; 16 | expect(userFutureStatus).to.have.property("id"); 17 | expect(userFutureStatus).to.have.property("userId"); 18 | }); 19 | }); 20 | 21 | describe("getUserFutureStatus", () => { 22 | it("should successfully get user future status", async () => { 23 | await createUserFutureStatus(userFutureStatusData as UserFutureStatusType); 24 | const userFutureStatus = await getUserFutureStatus( 25 | userFutureStatusData.userId, 26 | userFutureStatusData.status, 27 | userFutureStatusData.state 28 | ); 29 | expect(userFutureStatus).to.not.be.null; 30 | expect(userFutureStatus).to.be.an("array"); 31 | expect(userFutureStatus).to.have.length(1); 32 | }); 33 | }); 34 | }); 35 | -------------------------------------------------------------------------------- /test/unit/services/authService.test.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const { expect } = chai; 3 | const chaiHttp = require("chai-http"); 4 | 5 | const authService = require("../../../services/authService"); 6 | 7 | chai.use(chaiHttp); 8 | 9 | describe("authService", function () { 10 | it("should validate the generated JWT", function (done) { 11 | const payload = { userId: 1 }; 12 | const jwt = authService.generateAuthToken(payload); 13 | const decodedValue = authService.verifyAuthToken(jwt); 14 | 15 | expect(decodedValue).to.have.all.keys("userId", "iat", "exp"); 16 | expect(decodedValue.userId).to.equal(payload.userId); 17 | 18 | return done(); 19 | }); 20 | 21 | it("should decode the generated JWT", function (done) { 22 | const payload = { userId: 1 }; 23 | const jwt = authService.generateAuthToken(payload); 24 | const decodedValue = authService.decodeAuthToken(jwt); 25 | 26 | expect(decodedValue).to.have.all.keys("userId", "iat", "exp"); 27 | expect(decodedValue.userId).to.equal(payload.userId); 28 | 29 | return done(); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/unit/services/generateAuthToken.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { generateAuthToken } = require("../../../services/authService"); 3 | 4 | describe("RDS-session cookie as a unique token", function () { 5 | it("should generate cookie as token", function () { 6 | const userId = "HluRbHU6I7YLqSFBa7qC"; 7 | const data = generateAuthToken({ userId }); 8 | expect(data).to.include("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /test/unit/services/getFcmTokenFromUserId.test.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const { saveFcmToken } = require("../../../models/fcmToken"); 4 | const cleanDb = require("../../utils/cleanDb"); 5 | const { getFcmTokenFromUserId } = require("../../../services/getFcmTokenFromUserId"); 6 | 7 | describe("FCM token services", function () { 8 | describe("Get FCM token from user id", function () { 9 | beforeEach(async function () { 10 | const fcmTokenData = { userId: "jkkshdsjkh", fcmToken: "iedsijdsdj" }; 11 | 12 | await saveFcmToken(fcmTokenData); 13 | }); 14 | 15 | afterEach(async function () { 16 | await cleanDb(); 17 | }); 18 | 19 | it("Get FCM token from user id", async function () { 20 | const fcmToken = await getFcmTokenFromUserId("jkkshdsjkh"); 21 | expect(fcmToken[0]).equals("iedsijdsdj"); 22 | }); 23 | 24 | it("will return blank array for invalid user id", async function () { 25 | const fcmToken = await getFcmTokenFromUserId("sdkfskf"); 26 | expect(fcmToken.length).equals(0); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/unit/services/getUserIdsFromRoleId.test.js: -------------------------------------------------------------------------------- 1 | const chai = require("chai"); 2 | const expect = chai.expect; 3 | const cleanDb = require("../../utils/cleanDb"); 4 | const { addGroupRoleToMember } = require("../../../models/discordactions"); 5 | const { getUserIdsFromRoleId } = require("../../../services/getUserIdsFromRoleId"); 6 | 7 | describe("FCM token services", function () { 8 | describe("get user id from role id", function () { 9 | beforeEach(async function () {}); 10 | 11 | afterEach(async function () { 12 | await cleanDb(); 13 | }); 14 | }); 15 | 16 | it("Should get user id's from role id", async function () { 17 | const memberRoleModelData = { 18 | roleid: "1147354535342383104", 19 | userid: "jskdhaskjhdkasjh", 20 | }; 21 | await addGroupRoleToMember(memberRoleModelData); 22 | const memberRoleModelData2 = { 23 | roleid: "1147354535342383104", 24 | userid: "EFEGFHERIUGHIUER", 25 | }; 26 | await addGroupRoleToMember(memberRoleModelData2); 27 | 28 | const res = await getUserIdsFromRoleId("1147354535342383104"); 29 | 30 | expect(res.length).equals(2); 31 | expect(res).includes("EFEGFHERIUGHIUER"); 32 | expect(res).includes("jskdhaskjhdkasjh"); 33 | }); 34 | 35 | it("will return blank array for invalid role id", async function () { 36 | const userId = await getUserIdsFromRoleId("sdkfskf"); 37 | expect(userId.length).equals(0); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /test/unit/services/logService.test.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | const Sinon = require("sinon"); 3 | const cleanDb = require("../../utils/cleanDb"); 4 | const { addLog } = require("../../../services/logService"); 5 | const { INTERNAL_SERVER_ERROR } = require("../../../constants/errorMessages"); 6 | 7 | describe("Logs services", function () { 8 | beforeEach(function () { 9 | Sinon.restore(); 10 | }); 11 | 12 | afterEach(async function () { 13 | await cleanDb(); 14 | }); 15 | 16 | it("should successfully add a log", async function () { 17 | const type = "TEST_LOG"; 18 | const meta = { 19 | userId: "test-user-123", 20 | action: "test-action" 21 | }; 22 | const body = { 23 | details: "test details", 24 | status: "success" 25 | }; 26 | 27 | const result = await addLog(type, meta, body); 28 | 29 | expect(result).to.have.property('id'); 30 | expect(typeof result.id).to.equal('string'); 31 | expect(result.id).to.not.be.empty; 32 | }); 33 | 34 | it("should handle errors when adding log fails", async function () { 35 | 36 | const type = "TEST_LOG"; 37 | const meta = { userId: "test-user-123" }; 38 | const body = { details: "test details" }; 39 | 40 | try { 41 | await addLog(type, meta, body); 42 | expect.fail(INTERNAL_SERVER_ERROR); 43 | } catch (error) { 44 | expect(error.message).to.equal(INTERNAL_SERVER_ERROR); 45 | } 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/utils/application.test.ts: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | const { expect } = chai; 3 | const { getUserApplicationObject } = require("../../../utils/application"); 4 | 5 | describe("getUserApplicationObject", async function () { 6 | it("should return application object", function () { 7 | const rawData = { 8 | firstName: "vinayak", 9 | lastName: "trivedi", 10 | city: "Kanpur", 11 | state: "UP", 12 | country: "India", 13 | college: "Christ Church College", 14 | skills: "React, NodeJs, Ember", 15 | introduction: "not needed", 16 | funFact: "kdfkasdjfkdk", 17 | forFun: "kfsfakdfjdskfds", 18 | numberOfHours: 10, 19 | whyRds: "aise hi", 20 | foundFrom: "twitter", 21 | }; 22 | const data = getUserApplicationObject(rawData, "kfjasdkf", "December 13, 2023 at 5:44:59 AM UTC+5:30"); 23 | 24 | expect(data).to.be.deep.equal({ 25 | userId: "kfjasdkf", 26 | biodata: { 27 | firstName: rawData.firstName, 28 | lastName: rawData.lastName, 29 | }, 30 | location: { 31 | city: rawData.city, 32 | state: rawData.state, 33 | country: rawData.country, 34 | }, 35 | professional: { 36 | institution: rawData.college, 37 | skills: rawData.skills, 38 | }, 39 | intro: { 40 | introduction: rawData.introduction, 41 | funFact: rawData.funFact, 42 | forFun: rawData.forFun, 43 | whyRds: rawData.whyRds, 44 | numberOfHours: rawData.numberOfHours, 45 | }, 46 | foundFrom: rawData.foundFrom, 47 | status: "pending", 48 | createdAt: "December 13, 2023 at 5:44:59 AM UTC+5:30", 49 | }); 50 | }); 51 | }); 52 | -------------------------------------------------------------------------------- /test/unit/utils/customWordCountValidator.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { customWordCountValidator } = require("./../../../utils/customWordCountValidator"); 3 | 4 | describe("customWordCountValidator", function () { 5 | it("should return an error if the word count is less than the desired count", function () { 6 | const value = "This is a test string with more than 100 words."; 7 | const helpers = { 8 | error: () => "Word count validation failed.", 9 | }; 10 | const wordCount = 100; 11 | 12 | const result = customWordCountValidator(value, helpers, wordCount); 13 | expect(result).to.equal("Word count validation failed."); 14 | }); 15 | 16 | it("should return the original value if the word count meets the desired count", function () { 17 | const value = "This string has more than 100 words. " + "This ".repeat(96) + "word."; 18 | const helpers = { 19 | error: () => "Word count validation failed.", 20 | }; 21 | const wordCount = 100; 22 | 23 | const result = customWordCountValidator(value, helpers, wordCount); 24 | expect(result).to.equal(value); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/utils/discord-actions.test.ts: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import sinon from "sinon"; 3 | const { expect } = chai; 4 | import { generateDiscordInviteLink } from "../../../utils/discord-actions"; 5 | 6 | describe("generateDiscordInviteLink", () => { 7 | let fetchStub; 8 | 9 | beforeEach(function () { 10 | fetchStub = sinon.stub(global, "fetch"); 11 | }); 12 | afterEach(function () { 13 | fetchStub.restore(); 14 | }); 15 | 16 | it("should return the invite link", async () => { 17 | const inviteLink = "discord.gg/123456789"; 18 | const discordInviteLink = { 19 | data: { 20 | code: "123456789", 21 | }, 22 | }; 23 | fetchStub.resolves({ 24 | ok: true, 25 | json: () => discordInviteLink, 26 | }); 27 | 28 | const result = await generateDiscordInviteLink(); 29 | expect(result).to.be.equal(inviteLink); 30 | }); 31 | 32 | it("should resolve with an error", async () => { 33 | const error = new Error("Error"); 34 | fetchStub.rejects(error); 35 | try { 36 | await generateDiscordInviteLink(); 37 | } catch (err) { 38 | expect(err).to.be.equal(error); 39 | } 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /test/unit/utils/generateAuthTokenForCloudflare.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { generateAuthTokenForCloudflare } = require("../../../utils/discord-actions"); 3 | 4 | describe("test generate auth token for cloudflare", function () { 5 | it("generates auth token", function () { 6 | const data = generateAuthTokenForCloudflare(); 7 | expect(data).to.include("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /test/unit/utils/genrateCloudFlareHeaders.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { generateCloudFlareHeaders } = require("../../../utils/discord-actions"); 3 | 4 | describe("generateCloudFlareHeaders", function () { 5 | it("generates headers with property Content-Type and Authorization", function () { 6 | const data = generateCloudFlareHeaders(); 7 | expect(data["Content-Type"]).to.be.eq("application/json"); 8 | expect(data.Authorization).to.include("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"); 9 | }); 10 | 11 | it("generates headers with property Content-Type and Authorization and X-Audit-Log-Reason when id and userName is passed", function () { 12 | const data = generateCloudFlareHeaders({ id: "id", username: "userName" }); 13 | expect(data["Content-Type"]).to.be.eq("application/json"); 14 | expect(data.Authorization).to.include("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"); 15 | expect(data["X-Audit-Log-Reason"]).to.be.eq("Action initiator's username=>userName and id=id"); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/unit/utils/parseSearchQuery.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { parseSearchQuery } = require("../../../utils/tasks"); 3 | 4 | describe("parseSearchQuery", function () { 5 | it("should parse a valid query string", function () { 6 | const queryString = "searchterm:example+assignee:john.doe+status:in_progress"; 7 | const result = parseSearchQuery(queryString); 8 | 9 | expect(result).to.deep.equal({ 10 | searchTerm: "example", 11 | assignee: "john.doe", 12 | status: "in_progress", 13 | }); 14 | }); 15 | 16 | it("should handle an empty query string", function () { 17 | const queryString = ""; 18 | const result = parseSearchQuery(queryString); 19 | 20 | expect(result).to.deep.equal({}); 21 | }); 22 | 23 | it("should ignore unknown keys in the query string", function () { 24 | const queryString = "searchterm:example+assignee:john.doe+category:work"; 25 | const result = parseSearchQuery(queryString); 26 | 27 | expect(result).to.deep.equal({ 28 | searchTerm: "example", 29 | assignee: "john.doe", 30 | }); 31 | }); 32 | 33 | it("should handle query string with duplicate keys", function () { 34 | const queryString = "searchterm:example+searchterm:test+assignee:john.doe"; 35 | const result = parseSearchQuery(queryString); 36 | 37 | expect(result).to.deep.equal({ 38 | searchTerm: "test", 39 | assignee: "john.doe", 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/unit/utils/queryParser.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { parseQueryParams } = require("../../../utils/queryParser"); 3 | 4 | describe("parseQueryParams", function () { 5 | it("parses query parameters correctly", function () { 6 | const queryString = "?q=status:APPROVED+DENIED,assignee:user1"; 7 | const parsedParams = parseQueryParams(queryString); 8 | 9 | expect(parsedParams).to.deep.equal({ 10 | status: ["APPROVED", "DENIED"], 11 | assignee: "user1", 12 | }); 13 | }); 14 | 15 | it("handles empty or malformed query parameters", function () { 16 | const queryString = "?q=;()"; 17 | const parsedParams = parseQueryParams(queryString); 18 | 19 | expect(parsedParams).to.deep.equal({}); 20 | }); 21 | 22 | it('handles multiple values for non-"q" parameters', function () { 23 | const queryString = "?status=APPROVED&status=DENIED&assignee=user1"; 24 | const parsedParams = parseQueryParams(queryString); 25 | 26 | expect(parsedParams).to.deep.equal({ 27 | status: ["APPROVED", "DENIED"], 28 | assignee: "user1", 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/unit/utils/sendTaskUpdate.test.js: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | import sinon from "sinon"; 3 | import { sendTaskUpdate } from "../../../utils/sendTaskUpdate"; 4 | const { expect } = chai; 5 | 6 | describe("sendTaskUpdate function", function () { 7 | let fetchMock; 8 | 9 | beforeEach(function () { 10 | fetchMock = sinon.stub(global, "fetch"); 11 | }); 12 | 13 | afterEach(function () { 14 | fetchMock.restore(); 15 | }); 16 | 17 | it("should send task update successfully", async function () { 18 | fetchMock.resolves({ ok: true }); 19 | 20 | const result = await sendTaskUpdate( 21 | "Task completed", 22 | "No blockers", 23 | "Plan for the next phase", 24 | "userName", 25 | "taskId", 26 | "Task title" 27 | ); 28 | expect(result).to.equal(undefined); 29 | }); 30 | 31 | it("should throw an error if fails", async function () { 32 | const error = new Error("Error"); 33 | fetchMock.rejects(error); 34 | try { 35 | await sendTaskUpdate( 36 | "Task completed", 37 | "No blockers", 38 | "Plan for the next phase", 39 | "userName", 40 | "taskId", 41 | "task title" 42 | ); 43 | } catch (err) { 44 | expect(err).to.be.equal(error); 45 | } 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /test/unit/utils/transformQuery.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require("chai"); 2 | const { transformQuery } = require("../../../utils/tasks"); 3 | const { MAPPED_TASK_STATUS, TASK_STATUS } = require("../../../constants/tasks"); 4 | 5 | describe("transformQuery", function () { 6 | it("should transfrom status to it's mapped value", function () { 7 | const status = "done"; 8 | const mappedStatus = MAPPED_TASK_STATUS[status.toUpperCase()]; 9 | 10 | const transformedQuery = transformQuery(status); 11 | expect(transformedQuery.status).to.equal(mappedStatus); 12 | }); 13 | 14 | it("should transfrom and parse size to integer when passed as param", function () { 15 | const transformedQuery = transformQuery(TASK_STATUS.ASSIGNED, "5"); 16 | expect(transformedQuery.size).to.be.equal(5); 17 | expect(typeof transformedQuery.size).to.equal("number"); 18 | }); 19 | 20 | it("should transfrom and parse page to integer when passed as param", function () { 21 | const transformedQuery = transformQuery(TASK_STATUS.ASSIGNED, 5, "1"); 22 | expect(transformedQuery.page).to.be.equal(1); 23 | expect(typeof transformedQuery.page).to.equal("number"); 24 | }); 25 | 26 | it("should transfrom and parse assignee to lowercase when passed as param", function () { 27 | const transformedQuery = transformQuery(TASK_STATUS.ASSIGNED, 5, 1, "Test"); 28 | expect(transformedQuery.assignee).to.be.equal("test"); 29 | expect(typeof transformedQuery.assignee).to.equal("string"); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/unit/utils/verifyAuthToken.test.ts: -------------------------------------------------------------------------------- 1 | import chai from "chai"; 2 | const { expect } = chai; 3 | import { verifyAuthToken } from "../../../utils/verifyAuthToken"; 4 | 5 | describe("verifyAuthToken", () => { 6 | it("should return false when token is invalid", async () => { 7 | const invalidToken = "invalid-token"; 8 | 9 | const isValid = await verifyAuthToken(invalidToken); 10 | expect(isValid).false; 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/utils/addProfileDiffs.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../../utils/firestore"); 2 | const profileDiffsModel = firestore.collection("profileDiffs"); 3 | 4 | const getProfileDiffs = require("../fixtures/profileDiffs/profileDiffs"); 5 | 6 | module.exports = async (userId) => { 7 | const PROFILE_DIFFS = getProfileDiffs(); 8 | const addPromises = PROFILE_DIFFS.map((profileDiff) => profileDiffsModel.add({ ...profileDiff, userId })); 9 | await Promise.all(addPromises); 10 | }; 11 | -------------------------------------------------------------------------------- /test/utils/addUser.js: -------------------------------------------------------------------------------- 1 | const users = require("../../models/users"); 2 | 3 | // Import fixtures 4 | const userData = require("../fixtures/user/user")(); 5 | 6 | /** 7 | * File to be required in every test file where userId is required to generate the JWT 8 | * 9 | * @return {Promise} userId - userId for the added user 10 | */ 11 | module.exports = async (user) => { 12 | const isValid = user && Object.keys(user).length !== 0 && user.constructor === Object; 13 | // Use the user data sent as arguments, else use data from fixtures 14 | user = isValid ? user : userData[0]; 15 | /* 16 | Making a shallow copy of user allows us to safely modify the user object 17 | without affecting the original fixture 18 | */ 19 | const userWithoutRoles = { ...user }; 20 | const rolesToBeAdded = userWithoutRoles.roles; 21 | // A new user to be added in the DB should not have any roles 22 | delete userWithoutRoles.roles; 23 | const { userId } = await users.addOrUpdate(userWithoutRoles); 24 | 25 | if (rolesToBeAdded) { 26 | const persistedUser = (await users.fetchUser({ userId })).user; 27 | const existingRoles = persistedUser.roles; 28 | const rolesToBeUpdated = { ...existingRoles, ...rolesToBeAdded }; 29 | await users.addOrUpdate({ roles: rolesToBeUpdated }, userId); 30 | } 31 | return userId; 32 | }; 33 | -------------------------------------------------------------------------------- /test/utils/cleanDb.js: -------------------------------------------------------------------------------- 1 | const config = require("config"); 2 | const { fetch } = require("../../utils/fetch"); 3 | const firebaseConfig = require("../../firebase.json"); 4 | 5 | /** 6 | * Deletes all data from firestore emulator running locally. 7 | * To be used during tests for deleting the data as required. 8 | */ 9 | module.exports = async () => { 10 | const credentialsObject = JSON.parse(config.firestore); 11 | const projectId = credentialsObject.project_id; 12 | 13 | const firestoreCleanUrl = 14 | `http://localhost:${firebaseConfig.emulators.firestore.port}` + 15 | `/emulator/v1/projects/${projectId}/databases/(default)/documents`; 16 | 17 | return await fetch(firestoreCleanUrl, "delete"); 18 | }; 19 | -------------------------------------------------------------------------------- /test/utils/deleteRoles.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../../utils/firestore"); 2 | const userCollection = firestore.collection("users"); 3 | 4 | /** 5 | * Deletes the specified roles for a user 6 | * @param {string} userId - to identify the user whose roles are to be deleted 7 | * @param {string[]} rolesToBeDeleted - roles to be deleted 8 | * @return {boolean} success - are roles deleted or not 9 | */ 10 | module.exports = async (userId, rolesToBeDeleted = []) => { 11 | if (!userId) { 12 | logger.info("User id is required to delete roles"); 13 | return false; 14 | } 15 | 16 | try { 17 | const userDoc = await userCollection.doc(userId).get(); 18 | 19 | if (!userDoc.exists) { 20 | logger.info(`User with id : ${userId} not found`); 21 | return false; 22 | } 23 | 24 | const userData = userDoc.data(); 25 | rolesToBeDeleted.forEach((role) => delete userData.roles[String(role)]); 26 | await userCollection.doc(userId).set(userData); 27 | 28 | return true; 29 | } catch (error) { 30 | logger.error(`Error deleting user's roles object: ${error}`); 31 | return false; 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /test/utils/deleteRolesObject.js: -------------------------------------------------------------------------------- 1 | const firestore = require("../../utils/firestore"); 2 | const userCollection = firestore.collection("users"); 3 | 4 | /** 5 | * Deletes the entire roles object for a user 6 | * @param {string} userId - to identify the user whose roles are to be deleted 7 | * @return {boolean} success - are roles deleted or not 8 | */ 9 | module.exports = async (userId) => { 10 | if (!userId) { 11 | logger.info("User id is required to delete roles object"); 12 | return false; 13 | } 14 | try { 15 | const userDoc = await userCollection.doc(userId).get(); 16 | 17 | if (!userDoc.exists) { 18 | logger.info(`User with id : ${userId} not found`); 19 | return false; 20 | } 21 | 22 | const userData = userDoc.data(); 23 | delete userData.roles; 24 | await userCollection.doc(userId).set(userData); 25 | 26 | return true; 27 | } catch (error) { 28 | logger.error(`Error deleting user's roles object: ${error}`); 29 | return false; 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /test/utils/generateBotToken.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | /** 4 | * Generates the JWT 5 | * 6 | * @param payload {Object} - Payload to be added in the JWT 7 | * @return {String} - Generated JWT 8 | */ 9 | const generateToken = (data) => { 10 | return jwt.sign(data, config.get("botToken.botPrivateKey"), { 11 | algorithm: "RS256", 12 | expiresIn: "1m", 13 | }); 14 | }; 15 | 16 | const generateCronJobToken = (data) => { 17 | const token = jwt.sign(data, config.get("cronJobHandler.privateKey"), { 18 | algorithm: "RS256", 19 | expiresIn: "1m", 20 | }); 21 | return token; 22 | }; 23 | 24 | module.exports = { generateToken, generateCronJobToken }; 25 | -------------------------------------------------------------------------------- /test/utils/github.js: -------------------------------------------------------------------------------- 1 | const defaultClientId = config.get("githubOauth.clientId"); 2 | const baseURL = config.get("services.rdsApi.baseUrl"); 3 | 4 | const generateGithubAuthRedirectUrl = function ({ 5 | baseUrl = "https://github.com/login/oauth/authorize", 6 | responseType = "code", 7 | redirectUri = `${baseURL}/auth/github/callback`, 8 | scope = "user:email", 9 | state = "", 10 | clientId = defaultClientId, 11 | }) { 12 | const encodedBaseUrl = encodeURI(baseUrl); 13 | const encodedRedirectUri = encodeURIComponent(redirectUri); 14 | const encodedScope = encodeURIComponent(scope); 15 | let encodedUrl = `${encodedBaseUrl}?response_type=${responseType}&redirect_uri=${encodedRedirectUri}&scope=${encodedScope}`; 16 | if (state) { 17 | encodedUrl += `&state=${encodeURIComponent(state)}`; 18 | } 19 | return `${encodedUrl}&client_id=${clientId}`; 20 | }; 21 | 22 | module.exports = { generateGithubAuthRedirectUrl }; 23 | -------------------------------------------------------------------------------- /test/utils/googleauth.js: -------------------------------------------------------------------------------- 1 | const defaultClientId = config.get("googleOauth.clientId"); 2 | const baseURL = config.get("services.rdsApi.baseUrl"); 3 | const sinon = require("sinon"); 4 | const passport = require("passport"); 5 | 6 | const generateGoogleAuthRedirectUrl = function ({ 7 | baseUrl = "https://accounts.google.com/o/oauth2/v2/auth", 8 | responseType = "code", 9 | redirectUri = `${baseURL}/auth/google/callback`, 10 | scope = "email", 11 | state = "", 12 | clientId = defaultClientId, 13 | }) { 14 | const encodedBaseUrl = encodeURI(baseUrl); 15 | const encodedRedirectUri = encodeURIComponent(redirectUri); 16 | const encodedScope = encodeURIComponent(scope); 17 | let encodedUrl = `${encodedBaseUrl}?response_type=${responseType}&redirect_uri=${encodedRedirectUri}&scope=${encodedScope}`; 18 | if (state) { 19 | encodedUrl += `&state=${encodeURIComponent(state)}`; 20 | } 21 | return `${encodedUrl}&client_id=${clientId}`; 22 | }; 23 | 24 | const stubPassportAuthenticate = function (userData, token = "accessToken") { 25 | return sinon.stub(passport, "authenticate").callsFake((strategy, options, callback) => { 26 | callback(null, token, userData); 27 | return (req, res, next) => {}; 28 | }); 29 | }; 30 | 31 | module.exports = { generateGoogleAuthRedirectUrl, stubPassportAuthenticate }; 32 | -------------------------------------------------------------------------------- /test/utils/user.ts: -------------------------------------------------------------------------------- 1 | import { expect } from "chai"; 2 | 3 | function assertUserIds(users: any, ids: any) { 4 | ids.forEach((id: number | string) => { 5 | /* eslint-disable no-unused-expressions */ 6 | expect(users.some((user: { id: any }) => user.id === id)).to.be.true; 7 | }); 8 | } 9 | module.exports = { assertUserIds }; 10 | -------------------------------------------------------------------------------- /typeDefinitions/answers.d.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "express"; 2 | import * as admin from "firebase-admin"; 3 | 4 | export type AnswerBody = { 5 | id: string; 6 | eventId: string; 7 | answeredBy: string; 8 | answer: string; 9 | questionId: string; 10 | }; 11 | 12 | export type AnswerStatus = "PENDING" | "APPROVED" | "REJECTED"; 13 | 14 | export type AnswerFieldsToUpdate = { 15 | status?: AnswerStatus; 16 | reviewed_by: string; 17 | }; 18 | 19 | export type Answer = { 20 | id: string; 21 | reviewed_by: string | null; 22 | event_id: string; 23 | answer: string; 24 | updated_at: admin.firestore.Timestamp; 25 | answered_by: string; 26 | created_at: admin.firestore.Timestamp; 27 | question_id: string; 28 | status: AnswerStatus; 29 | }; 30 | 31 | export type AnswerQueryFields = { 32 | status: AnswerStatus; 33 | questionId: string; 34 | eventId: string; 35 | }; 36 | 37 | export type AnswerClient = { 38 | id: string; 39 | res: Response; 40 | status?: string; 41 | }; 42 | -------------------------------------------------------------------------------- /typeDefinitions/global.d.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { Boom } from "express-boom"; 3 | 4 | export type UserData = { 5 | id: string; 6 | profileURL: string; 7 | discordJoinedAt: string; 8 | roles: { 9 | archived: boolean; 10 | in_discord: boolean; 11 | member: boolean; 12 | super_user: boolean; 13 | }; 14 | profileStatus: string; 15 | created_at: number; 16 | yoe: number; 17 | github_created_at: number; 18 | company: string; 19 | twitter_id: string; 20 | first_name: string; 21 | incompleteUserDetails: boolean; 22 | discordId: string; 23 | last_name: string; 24 | linkedin_id: string; 25 | picture: { 26 | url: string; 27 | publicId: string; 28 | }; 29 | instagram_id: string; 30 | github_display_name: string; 31 | github_id: string; 32 | designation: string; 33 | status: string; 34 | username: string; 35 | updated_at: number; 36 | }; 37 | 38 | export type CustomRequest = Request & { userData: UserData }; 39 | export type CustomResponse = Response & Boom; 40 | -------------------------------------------------------------------------------- /typeDefinitions/rqlParser.ts: -------------------------------------------------------------------------------- 1 | export enum QueryTypes { 2 | FILTER = "FILTER", 3 | SORT = "SORT", 4 | } 5 | 6 | export enum Operators { 7 | INCLUDE = "INCLUDE", 8 | EXCLUDE = "EXCLUDE", 9 | } 10 | 11 | export interface Queries { 12 | operator: Operators; 13 | key: string; 14 | value: string; 15 | type: QueryTypes; 16 | } 17 | 18 | export interface FilterQueryValue { 19 | operator: Operators; 20 | value: string; 21 | } 22 | 23 | export interface GroupedFilterQueries { 24 | [key: string]: FilterQueryValue[]; 25 | } 26 | 27 | export interface GroupedSortQueries { 28 | [key: string]: string; 29 | } 30 | -------------------------------------------------------------------------------- /typeDefinitions/task-requests.ts: -------------------------------------------------------------------------------- 1 | export type TaskRequestType = { 2 | requestors?: string[]; 3 | status?: string; 4 | taskTitle?: string; 5 | taskId?: string; 6 | externalIssueUrl?: string; 7 | requestType?: string; 8 | users: UserType[]; 9 | createdBy?: string; 10 | createdAt?: number; 11 | lastModifiedBy?: string; 12 | lastModifiedAt?: number; 13 | }; 14 | 15 | export type UserType = { 16 | userId: string; 17 | proposedDeadline?: number; 18 | proposedStartDate?: number; 19 | description?: string; 20 | status?: string; 21 | }; 22 | -------------------------------------------------------------------------------- /typeDefinitions/users.ts: -------------------------------------------------------------------------------- 1 | export type User = { 2 | id?: string 3 | username?: string; 4 | first_name?: string; 5 | last_name?: string; 6 | discordId?: string; 7 | yoe?: number; 8 | img?: string; 9 | linkedin_id?: string; 10 | github_id?: string; 11 | github_display_name?: string; 12 | github_created_at?: number; 13 | isMember?: boolean; 14 | phone?: string; 15 | email?: string; 16 | discordJoinedAt?: string; 17 | joined_discord?: string; 18 | roles?: { 19 | member?: boolean; 20 | in_discord?: boolean; 21 | super_user?: boolean; 22 | } 23 | tokens?: { 24 | githubAccessToken?: string; 25 | }; 26 | status?: string; 27 | profileURL?: string; 28 | picture?: { 29 | publicId?: string; 30 | url?: string; 31 | }; 32 | incompleteUserDetails?: boolean; 33 | nickname_synced?: boolean; 34 | }; -------------------------------------------------------------------------------- /types/application.d.ts: -------------------------------------------------------------------------------- 1 | export type application = { 2 | userId: string; 3 | biodata: { 4 | firstName: string; 5 | lastName: string; 6 | }; 7 | location: { 8 | city: string; 9 | state: string; 10 | country: string; 11 | }; 12 | professional: { 13 | institution: string; 14 | skills: string; 15 | }; 16 | intro: { 17 | introduction: string; 18 | funFact: string; 19 | forFun: string; 20 | whyRds: string; 21 | numberOfHours: number; 22 | }; 23 | status?: string; 24 | createdAt?: string; 25 | foundFrom: string; 26 | }; 27 | 28 | export type applicationPayload = { 29 | firstName: string; 30 | lastName: string; 31 | city: string; 32 | state: string; 33 | country: string; 34 | college: string; 35 | skills: string; 36 | introduction: string; 37 | funFact: string; 38 | forFun: string; 39 | numberOfHours: number; 40 | whyRds: string; 41 | foundFrom: string; 42 | }; 43 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | import { Boom } from "express-boom"; 3 | 4 | export type userData = { 5 | id: string; 6 | profileURL: string; 7 | discordJoinedAt: string; 8 | roles: { 9 | archived: boolean; 10 | in_discord?: boolean; 11 | member?: boolean; 12 | maven?: boolean; 13 | designer?: boolean; 14 | product_manager?: boolean; 15 | super_user?: boolean; 16 | }; 17 | profileStatus: string; 18 | created_at: number; 19 | yoe: number; 20 | github_created_at: number; 21 | company: string; 22 | twitter_id: string; 23 | first_name: string; 24 | incompleteUserDetails: boolean; 25 | discordId: string; 26 | last_name: string; 27 | linkedin_id: string; 28 | picture?: { 29 | url?: string; 30 | publicId?: string; 31 | }; 32 | instagram_id: string; 33 | github_display_name: string; 34 | github_id: string; 35 | designation: string; 36 | status: string; 37 | username: string; 38 | updated_at: number; 39 | isSubscribed: boolean; 40 | phone: string | null; 41 | email: string; 42 | }; 43 | 44 | export type CustomResponse = Response & { boom: Boom }; 45 | export type CustomRequest = Request & { userData }; 46 | -------------------------------------------------------------------------------- /types/invites.d.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from "express"; 2 | import { Boom } from "express-boom"; 3 | 4 | export type InviteBody = { 5 | inviteLink?: string; 6 | userId: string; 7 | purpose: string; 8 | }; 9 | 10 | export type Invite = { 11 | id?: string; 12 | userId: string; 13 | purpose: string; 14 | inviteLink: string; 15 | createdAt?: number; 16 | }; 17 | export type InviteBodyRequest = Request & { InviteBody }; 18 | -------------------------------------------------------------------------------- /types/oooRequest.d.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { REQUEST_STATE, REQUEST_TYPE } from "../constants/requests"; 3 | import { Boom } from "express-boom"; 4 | import { RequestParams, RequestQuery } from "./requests"; 5 | import { userData } from "./global"; 6 | 7 | export type OooStatusRequest = { 8 | id: string; 9 | type: REQUEST_TYPE.OOO; 10 | from: number; 11 | until: number; 12 | reason: string; 13 | status: REQUEST_STATE; 14 | lastModifiedBy: string | null; 15 | requestedBy: string; 16 | userId: string; 17 | createdAt: Timestamp; 18 | updatedAt: Timestamp; 19 | comment: string | null; 20 | }; 21 | export type OooStatusRequestBody = { 22 | from: number; 23 | until: number; 24 | type: REQUEST_TYPE.OOO; 25 | reason: string; 26 | }; 27 | 28 | export type OooRequestUpdateBody = { 29 | lastModifiedBy?: string; 30 | type?: REQUEST_TYPE.OOO; 31 | id?: string; 32 | reason?: string; 33 | state: REQUEST_STATE.APPROVED | REQUEST_STATE.REJECTED; 34 | updatedAt?: admin.firestore.Timestamp; 35 | }; 36 | 37 | export type OooRequestResponse = Response & { boom: Boom }; 38 | export type OooRequestCreateRequest = Request & { 39 | body: OooStatusRequestBody; 40 | userData: userData; 41 | query: RequestQuery; 42 | }; 43 | 44 | export type OooRequestUpdateRequest = Request & { oooRequestUpdateBody , userData: userData , query: RequestQuery , params: RequestParams }; 45 | -------------------------------------------------------------------------------- /types/questions.d.ts: -------------------------------------------------------------------------------- 1 | import * as admin from "firebase-admin"; 2 | import { CustomResponse } from "./global"; 3 | 4 | export type QuestionBody = { 5 | id: string; 6 | question: string; 7 | createdBy: string; 8 | eventId: string; 9 | maxCharacters?: number | null; 10 | }; 11 | 12 | export type Question = { 13 | id: string; 14 | question: string; 15 | created_by: string; 16 | event_id: string; 17 | max_characters: string | null; 18 | created_at: admin.firestore.Timestamp; 19 | }; 20 | 21 | export type Client = { 22 | id: string; 23 | res: CustomResponse; 24 | }; 25 | -------------------------------------------------------------------------------- /types/requests.d.ts: -------------------------------------------------------------------------------- 1 | import { Request } from "express"; 2 | import { REQUEST_STATE, REQUEST_TYPE } from "./../constants/requests"; 3 | import { userData } from "./global"; 4 | 5 | export type UpdateRequestBody = { 6 | type: REQUEST_TYPE.OOO | REQUEST_TYPE.EXTENSION; 7 | reason: string; 8 | state: REQUEST_STATE.APPROVED | REQUEST_STATE.REJECTED; 9 | }; 10 | 11 | export type RequestQuery = { 12 | type?: string; 13 | requestedBy?: string; 14 | state?: REQUEST_STATE.APPROVED | REQUEST_STATE.PENDING | REQUEST_STATE.REJECTED; 15 | id?: string; 16 | prev?: string; 17 | next?: string; 18 | page?: number; 19 | size?: number; 20 | }; 21 | 22 | export type RequestParams = { 23 | id: string; 24 | }; 25 | 26 | export type UpdateRequest = Request & { 27 | UpdateRequestBody; 28 | userData: userData; 29 | query: RequestQuery; 30 | params: RequestParams; 31 | }; 32 | -------------------------------------------------------------------------------- /types/userFutureStatus.d.ts: -------------------------------------------------------------------------------- 1 | import { userState, statusState } from "../constants/userStatus"; 2 | 3 | export type UserFutureStatusType = { 4 | id?: string; 5 | requestId?: string; 6 | status: userState.OOO | userState.IDLE | userState.ACTIVE; 7 | state: statusState.UPCOMING | statusState.APPLIED | statusState.NOT_APPLIED; 8 | from: number; 9 | endsOn?: number; 10 | userId: string; 11 | message?: string; 12 | createdAt?: number; 13 | }; -------------------------------------------------------------------------------- /types/userStatus.ts: -------------------------------------------------------------------------------- 1 | export type CurrentStatus = { 2 | from: number, 3 | until: number, 4 | state: string, 5 | message: string, 6 | updatedAt: number, 7 | }; 8 | 9 | export type UserStatus = { 10 | id: string, 11 | data: { 12 | currentStatus: CurrentStatus 13 | }, 14 | userStatusExists: boolean 15 | }; 16 | -------------------------------------------------------------------------------- /utils/application.ts: -------------------------------------------------------------------------------- 1 | import { applicationPayload, application } from "../types/application"; 2 | 3 | const getUserApplicationObject = (rawData: applicationPayload, userId: string, createdAt: string): application => { 4 | const data = { 5 | userId, 6 | biodata: { 7 | firstName: rawData.firstName, 8 | lastName: rawData.lastName, 9 | }, 10 | location: { 11 | city: rawData.city, 12 | state: rawData.state, 13 | country: rawData.country, 14 | }, 15 | professional: { 16 | institution: rawData.college, 17 | skills: rawData.skills, 18 | }, 19 | intro: { 20 | introduction: rawData.introduction, 21 | funFact: rawData.funFact, 22 | forFun: rawData.forFun, 23 | whyRds: rawData.whyRds, 24 | numberOfHours: rawData.numberOfHours, 25 | }, 26 | foundFrom: rawData.foundFrom, 27 | status: "pending", 28 | createdAt, 29 | }; 30 | return data; 31 | }; 32 | 33 | module.exports = { getUserApplicationObject } 34 | -------------------------------------------------------------------------------- /utils/array.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creates an array of elements split into groups the length of size. If array can't be split evenly, the final chunk will be the remaining elements. 3 | * description credit: https://lodash.com/docs/4.17.15#chunk 4 | * source code inspiron: https://youmightnotneed.com/lodash#chunk 5 | * @param {array}: array to be splitted into groups 6 | * @param {size}: size of array groups 7 | * @return {array}: array of arrays of elements split into groups the length of size. 8 | */ 9 | function chunks(array, size = 1) { 10 | if (!Array.isArray(array) || size < 1) { 11 | return []; 12 | } 13 | const temp = [...array]; 14 | const result = []; 15 | while (temp.length) { 16 | result.push(temp.splice(0, size)); 17 | } 18 | return result; 19 | } 20 | 21 | /** 22 | * Checks if two arrays have any common items 23 | * @param array1 {Array} - first array 24 | * @param array2 {Array} - second array 25 | * @returns {boolean} - true if the arrays have at least one common item, false otherwise 26 | */ 27 | 28 | function arraysHaveCommonItem(array1, array2) { 29 | if (!array1?.length || !array2?.length) { 30 | return false; 31 | } 32 | return array1.some((value) => array2.includes(value)); 33 | } 34 | 35 | module.exports = { 36 | chunks, 37 | arraysHaveCommonItem, 38 | }; 39 | -------------------------------------------------------------------------------- /utils/cloudinary.js: -------------------------------------------------------------------------------- 1 | const cloudinary = require("cloudinary").v2; 2 | const config = require("config"); 3 | 4 | cloudinary.config(config.get("cloudinary")); 5 | 6 | const upload = async (file, options = {}) => { 7 | const response = await cloudinary.uploader.upload(file, options); 8 | return response; 9 | }; 10 | 11 | module.exports = { 12 | upload, 13 | }; 14 | -------------------------------------------------------------------------------- /utils/customWordCountValidator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom validator to check the word count of a string value. 3 | * @param {string} value - The value of the field being validated. 4 | * @param {object} helpers - The Joi validation helpers object. 5 | * @param {number} wordCount - The desired minimum word count for validation. 6 | * @returns {*} The validated value if the word count is met, otherwise an error. 7 | */ 8 | const customWordCountValidator = (value, helpers, wordCount) => { 9 | const words = value.split(/\s+/); 10 | 11 | if (words.length < wordCount) { 12 | return helpers.error("any.invalid"); 13 | } 14 | 15 | return value; 16 | }; 17 | 18 | module.exports = { 19 | customWordCountValidator, 20 | }; 21 | -------------------------------------------------------------------------------- /utils/events.js: -------------------------------------------------------------------------------- 1 | const removeUnwantedProperties = (propertiesToRemove, data) => { 2 | let cleanData = Array.isArray(data) ? [] : {}; 3 | 4 | if (Array.isArray(data)) { 5 | data.forEach((item) => { 6 | const cleanItem = Object.entries(item).reduce((acc, [key, value]) => { 7 | if (!propertiesToRemove.includes(key)) { 8 | return Object.assign(acc, { [key]: value }); 9 | } 10 | return acc; 11 | }, {}); 12 | cleanData.push(cleanItem); 13 | }); 14 | } else if (typeof data === "object") { 15 | Object.entries(data).forEach(([key, value]) => { 16 | if (!propertiesToRemove.includes(key)) { 17 | cleanData = { ...cleanData, [key]: value }; 18 | } 19 | }); 20 | } 21 | 22 | return cleanData; 23 | }; 24 | 25 | module.exports = { 26 | removeUnwantedProperties, 27 | }; 28 | -------------------------------------------------------------------------------- /utils/fetch.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | /** 4 | * Used for network calls 5 | * 6 | * @param url {String} - API Endpoint URL 7 | * @param [method = 'get'] {String} - API Call Method (GET, POST etc.) - optional 8 | * @param [params = null] {Object} - Query Params for the API call - optional 9 | * @param [data = null] {Object} - Body to be sent - optional 10 | * @param [headers = null] {Object} - Headers to be sent - optional 11 | * @param [options = null] {Object} - Options to be sent via axios - optional 12 | */ 13 | 14 | const fetch = async (url, method = "get", params = null, data = null, headers = null, options = null) => { 15 | try { 16 | const response = await axios({ 17 | method, 18 | url, 19 | params, 20 | data, 21 | headers, 22 | ...options, 23 | }); 24 | return response; 25 | } catch (err) { 26 | logger.error("Something went wrong. Please contact admin", err); 27 | throw err; 28 | } 29 | }; 30 | 31 | module.exports = { 32 | fetch, 33 | }; 34 | -------------------------------------------------------------------------------- /utils/fetchMultiplePageResults.js: -------------------------------------------------------------------------------- 1 | const githubService = require("../services/githubService"); 2 | 3 | const fetchMultiplePageResults = async (callbackFn, params) => { 4 | let page = 1; 5 | const allPRs = []; 6 | 7 | do { 8 | const { data } = await callbackFn({ page, ...params }); 9 | const currentPRs = githubService.extractPRdetails(data); 10 | allPRs.push(...currentPRs); 11 | page++; 12 | } while (allPRs.length === 100 && allPRs.length > 0); 13 | 14 | return allPRs; 15 | }; 16 | 17 | module.exports = { 18 | fetchMultiplePageResults, 19 | }; 20 | -------------------------------------------------------------------------------- /utils/firestore.js: -------------------------------------------------------------------------------- 1 | const admin = require("firebase-admin"); 2 | const config = require("config"); 3 | 4 | // Firestore config needs to contain the credentials as a string instead of JS object, 5 | // because we will be setting it as an environment variable during deployment 6 | const credentialsObject = JSON.parse(config.firestore); 7 | 8 | admin.initializeApp({ 9 | credential: admin.credential.cert(credentialsObject), 10 | }); 11 | 12 | const db = admin.firestore(); 13 | 14 | module.exports = db; 15 | -------------------------------------------------------------------------------- /utils/logger.ts: -------------------------------------------------------------------------------- 1 | const winston = require("winston"); 2 | const config = require("config"); 3 | // define the custom settings for each transport (file, console) 4 | const options = { 5 | file: { 6 | level: "info", 7 | filename: "logs/app.log", 8 | handleExceptions: true, 9 | json: true, 10 | maxsize: 5242880, // 5MB 11 | maxFiles: 5, 12 | colorize: false, 13 | }, 14 | console: { 15 | level: "info", 16 | handleExceptions: true, 17 | json: false, 18 | colorize: true, 19 | silent: process.env.NODE_ENV === "test", // Disable logs in test env 20 | }, 21 | }; 22 | 23 | // instantiate a new Winston Logger with the settings defined above 24 | // eslint-disable-line new-cap 25 | /* eslint new-cap: ["error", { "properties": false }] */ 26 | const logger = new winston.createLogger({ 27 | /** 28 | * Application defaults: 29 | * - File logs enabled in: [production, staging] 30 | * - Console logs enabled in: [development] 31 | * 32 | * Modifications to be made through environment variables defined in config files 33 | */ 34 | transports: [ 35 | ...(config.get("enableFileLogs") ? [new winston.transports.File(options.file)] : []), 36 | ...(config.get("enableConsoleLogs") ? [new winston.transports.Console(options.console)] : []), 37 | ], 38 | 39 | exitOnError: false, // do not exit on handled exceptions 40 | }); 41 | 42 | // create a stream object with a 'write' function that will be used by `morgan` 43 | logger.stream = { 44 | write: function (message, encoding) { 45 | // use the 'info' log level so the output will be picked up by both transports (file and console) 46 | logger.info(message); 47 | }, 48 | }; 49 | 50 | module.exports = logger; 51 | -------------------------------------------------------------------------------- /utils/multer.js: -------------------------------------------------------------------------------- 1 | const multer = require("multer"); 2 | const multerConstant = require("../constants/multer"); 3 | const errorMessage = require("../constants/errorMessages"); 4 | const multerMemoryStorage = multer.memoryStorage(); 5 | 6 | const MB_1 = multerConstant.FILE_SIZE_1MB; 7 | const profileFileSize = multerConstant.PROFILE_FILE_SIZE; 8 | 9 | const fileFilterImagesOnly = (req, file, cb) => { 10 | const mimetype = file.mimetype; 11 | const allowedMimeTypes = ["image/png", "image/jpeg"]; 12 | const isMimeTypeAllowed = allowedMimeTypes.includes(mimetype); 13 | if (isMimeTypeAllowed) { 14 | return cb(null, true); 15 | } 16 | return cb(new multer.MulterError("TYPE_UNSUPPORTED_FILE"), false); 17 | }; 18 | 19 | const upload = multer({ 20 | storage: multerMemoryStorage, 21 | limits: { fileSize: profileFileSize }, 22 | fileFilter: fileFilterImagesOnly, 23 | }); 24 | 25 | const multerErrorHandling = (err, req, res, next) => { 26 | if (err.code === "LIMIT_FILE_SIZE") { 27 | res.boom.entityTooLarge(errorMessage.FILE_TOO_LARGE(profileFileSize / MB_1)); 28 | } else if (err.code === "LIMIT_UNEXPECTED_FILE") { 29 | res.boom.badData(errorMessage.ONLY_ONE_FILE_ALLOWED); 30 | } else if (err.code === "TYPE_UNSUPPORTED_FILE") { 31 | res.boom.unsupportedMediaType(errorMessage.ONLY_IMAGE_SUPPORTED); 32 | } else { 33 | res.boom.badImplementation(errorMessage.INTERNAL_SERVER_ERROR); 34 | } 35 | }; 36 | 37 | const isMulterError = (err) => { 38 | return err instanceof multer.MulterError; 39 | }; 40 | 41 | module.exports = { 42 | upload, 43 | multerErrorHandling, 44 | isMulterError, 45 | }; 46 | -------------------------------------------------------------------------------- /utils/obfuscate.js: -------------------------------------------------------------------------------- 1 | const obfuscatePhone = (phone) => { 2 | return phone.slice(0, 1) + phone.slice(1, phone.length - 1).replace(/\d/g, "*") + phone.slice(phone.length - 1); 3 | }; 4 | 5 | const obfuscateMail = (email) => { 6 | return email.slice(0, 2) + email.slice(2, email.length - 2).replace(/./g, "*") + email.slice(email.length - 2); 7 | }; 8 | 9 | module.exports = { 10 | obfuscatePhone, 11 | obfuscateMail, 12 | }; 13 | -------------------------------------------------------------------------------- /utils/profileDiffs.js: -------------------------------------------------------------------------------- 1 | const generateNextLink = (nextPageParams) => { 2 | const urlSearchParams = new URLSearchParams(); 3 | 4 | for (const [key, value] of Object.entries(nextPageParams)) { 5 | if (!value) continue; 6 | urlSearchParams.append(key, value); 7 | } 8 | const nextLink = `/profileDiffs?${urlSearchParams.toString()}`; 9 | return nextLink; 10 | }; 11 | 12 | module.exports = { 13 | generateNextLink, 14 | }; 15 | -------------------------------------------------------------------------------- /utils/rateLimiting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @param msBeforeNext 3 | * @param fallbackValue seconds value to fallback on if `msBeforeNext` is falsy 4 | * @returns retrySeconds: number of seconds to wait before making next request 5 | */ 6 | function getRetrySeconds(msBeforeNext, fallbackValue = 1) { 7 | if (!msBeforeNext) return fallbackValue; 8 | return Math.round(msBeforeNext / 1000) || fallbackValue; 9 | } 10 | 11 | module.exports = { 12 | getRetrySeconds, 13 | }; 14 | -------------------------------------------------------------------------------- /utils/requests.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculates the new deadline based on the current date, the old end date, and the additional duration in milliseconds. 3 | * 4 | * @param {number} currentDate - The current date as a timestamp in milliseconds. 5 | * @param {number} oldEndsOn - The previous end date as a timestamp in milliseconds. 6 | * @param {number} numberOfDaysInMillisecond - The duration to extend the deadline, in milliseconds. 7 | * @returns {number} The new deadline as a timestamp in milliseconds. 8 | */ 9 | export const getNewDeadline = (currentDate: number, oldEndsOn: number, numberOfDaysInMillisecond: number): number => { 10 | if (currentDate > oldEndsOn) { 11 | return currentDate + numberOfDaysInMillisecond; 12 | } 13 | return oldEndsOn + numberOfDaysInMillisecond; 14 | }; 15 | 16 | /** 17 | * Converts a date string into a timestamp in milliseconds. 18 | * Validates whether the provided string is a valid date format. 19 | * 20 | * @param {string} date - The date string to convert (e.g., "2024-10-17T16:10:52.668Z"). 21 | * @returns {{ isDate: boolean, milliseconds?: number }} An object indicating validity and the timestamp if valid. 22 | */ 23 | export const convertDateStringToMilliseconds = (date: string): { isDate: boolean; milliseconds?: number; } => { 24 | const milliseconds = Date.parse(date); 25 | if (!milliseconds) { 26 | return { 27 | isDate: false, 28 | }; 29 | } 30 | return { 31 | isDate: true, 32 | milliseconds, 33 | }; 34 | }; -------------------------------------------------------------------------------- /utils/sendTaskUpdate.js: -------------------------------------------------------------------------------- 1 | import { generateCloudFlareHeaders } from "../utils/discord-actions.js"; 2 | const DISCORD_BASE_URL = config.get("services.discordBot.baseUrl"); 3 | 4 | export const sendTaskUpdate = async (completed, blockers, planned, userName, taskId, taskTitle) => { 5 | try { 6 | const headers = generateCloudFlareHeaders(); 7 | const body = { 8 | content: { 9 | completed, 10 | blockers, 11 | planned, 12 | userName, 13 | taskId, 14 | taskTitle, 15 | }, 16 | }; 17 | await fetch(`${DISCORD_BASE_URL}/task/update`, { 18 | method: "POST", 19 | headers, 20 | body: JSON.stringify(body), 21 | }); 22 | } catch (error) { 23 | logger.error("Something went wrong", error); 24 | throw error; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /utils/verifyAuthToken.ts: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | const externalServicePublicKey = config.get("externalServices.EXTERNAL_SERVICE_PUBLIC_KEY"); 3 | 4 | export const verifyAuthToken = async (token: string) => { 5 | try { 6 | const isValid = jwt.verify(token, externalServicePublicKey, { 7 | algorithms: ["RS256"], 8 | }); 9 | 10 | return isValid; 11 | } catch (error) { 12 | return false; 13 | } 14 | }; 15 | --------------------------------------------------------------------------------