From 248681367c02551b2940fa348a623f99aad57400 Mon Sep 17 00:00:00 2001 From: Ajeyakrishna <98796547+Ajeyakrishna-k@users.noreply.github.com> Date: Wed, 13 Dec 2023 03:06:46 +0530 Subject: [PATCH] Adds util functions and changes configs (#25) * fix: add npm test * feat: mocks jwt sign function * fix: lint issue * feat: add pull request template * feat: adds config and util functions --- src/config/config.ts | 37 ++++++++++++------- src/constants/commons.ts | 3 ++ src/constants/urls.ts | 7 ++++ src/tests/arrayUtils.test.ts | 35 ++++++++++++++++++ src/tests/generateJwt.test.ts | 69 ++++++++++++++++++++++++----------- src/types/global.types.ts | 28 +++++++++++++- src/utils/arrayUtils.ts | 19 ++++++++++ src/utils/generateJwt.ts | 15 ++++++++ wrangler.toml | 9 ++++- 9 files changed, 185 insertions(+), 37 deletions(-) create mode 100644 src/constants/commons.ts create mode 100644 src/constants/urls.ts create mode 100644 src/tests/arrayUtils.test.ts create mode 100644 src/utils/arrayUtils.ts diff --git a/src/config/config.ts b/src/config/config.ts index 48446f4..963c707 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -1,15 +1,26 @@ -import { env } from '../types/global.types'; +import { MISSED_UPDATES_DEVELOPMENT_ROLE_ID, MISSED_UPDATES_PROD_ROLE_ID, MISSED_UPDATES_STAGING_ROLE_ID } from '../constants/commons'; +import { RDS_BASE_API_URL, RDS_BASE_DEVELOPMENT_API_URL, RDS_BASE_STAGING_API_URL } from '../constants/urls'; +import { env, environment } from '../types/global.types'; -export const handleConfig = (env: env) => { - let baseUrl: string; - if (env.CURRENT_ENVIRONMENT) { - if (env.CURRENT_ENVIRONMENT.toLowerCase() === 'production') { - baseUrl = 'https://api.realdevsquad.com'; - } else { - baseUrl = 'https://staging-api.realdevsquad.com'; - } - } else { - baseUrl = 'https://staging-api.realdevsquad.com'; - } - return { baseUrl }; +const config = (env: env) => { + const environment: environment = { + production: { + RDS_BASE_API_URL: RDS_BASE_API_URL, + DISCORD_BOT_API_URL: env.DISCORD_BOT_API_URL, + MISSED_UPDATES_ROLE_ID: MISSED_UPDATES_PROD_ROLE_ID, + }, + staging: { + RDS_BASE_API_URL: RDS_BASE_STAGING_API_URL, + DISCORD_BOT_API_URL: env.DISCORD_BOT_API_URL, + MISSED_UPDATES_ROLE_ID: MISSED_UPDATES_STAGING_ROLE_ID, + }, + default: { + RDS_BASE_API_URL: RDS_BASE_DEVELOPMENT_API_URL, + DISCORD_BOT_API_URL: env.DISCORD_BOT_API_URL, + MISSED_UPDATES_ROLE_ID: MISSED_UPDATES_DEVELOPMENT_ROLE_ID, + }, + }; + + return environment[env.CURRENT_ENVIRONMENT] || environment.default; }; +export default config; diff --git a/src/constants/commons.ts b/src/constants/commons.ts new file mode 100644 index 0000000..77deb0f --- /dev/null +++ b/src/constants/commons.ts @@ -0,0 +1,3 @@ +export const MISSED_UPDATES_PROD_ROLE_ID = '1183553844811153458'; +export const MISSED_UPDATES_STAGING_ROLE_ID = '1183553844811153458'; +export const MISSED_UPDATES_DEVELOPMENT_ROLE_ID = '1181214205081296896'; diff --git a/src/constants/urls.ts b/src/constants/urls.ts new file mode 100644 index 0000000..013eeff --- /dev/null +++ b/src/constants/urls.ts @@ -0,0 +1,7 @@ +export const RDS_BASE_API_URL = 'https://api.realdevsquad.com'; +export const RDS_BASE_STAGING_API_URL = 'https://staging-api.realdevsquad.com'; +export const RDS_BASE_DEVELOPMENT_API_URL = 'http://localhost:3000'; // If needed, modify the URL to your local API server run through ngrok + +export const DISCORD_BOT_API_URL = 'env'; +export const DISCORD_BOT_STAGING_API_URL = ''; +export const DISCORD_BOT_DEVELOPMENT_API_URL = ''; diff --git a/src/tests/arrayUtils.test.ts b/src/tests/arrayUtils.test.ts new file mode 100644 index 0000000..ccf5515 --- /dev/null +++ b/src/tests/arrayUtils.test.ts @@ -0,0 +1,35 @@ +import { chunks } from '../utils/arrayUtils'; + +describe('chunks function', () => { + it('should return an empty array if size is less than 1', () => { + expect(chunks([1, 2, 3], 0)).toEqual([]); + }); + + it('should return an empty array if the input array is empty', () => { + expect(chunks([], 2)).toEqual([]); + }); + + it('should split the array into chunks of the specified size', () => { + const inputArray = [1, 2, 3, 4, 5, 6]; + const size = 2; + const expectedResult = [ + [1, 2], + [3, 4], + [5, 6], + ]; + expect(chunks(inputArray, size)).toEqual(expectedResult); + }); + + it('should split the array into chunks of size 1 if size is not specified', () => { + const inputArray = [1, 2, 3, 4, 5, 6]; + const expectedResult = [[1], [2], [3], [4], [5], [6]]; + expect(chunks(inputArray)).toEqual(expectedResult); + }); + + it('should not modify the original array', () => { + const inputArray = [1, 2, 3, 4, 5, 6]; + const size = 2; + chunks(inputArray, size); + expect(inputArray).toEqual(inputArray); + }); +}); diff --git a/src/tests/generateJwt.test.ts b/src/tests/generateJwt.test.ts index 57f2c31..4f412c1 100644 --- a/src/tests/generateJwt.test.ts +++ b/src/tests/generateJwt.test.ts @@ -1,9 +1,9 @@ import jwt from '@tsndr/cloudflare-worker-jwt'; -import { generateJwt } from '../utils/generateJwt'; +import { generateDiscordBotJwt, generateJwt } from '../utils/generateJwt'; import { privateKey } from './config/keys'; -describe('Mock test', () => { +describe('Generate Jwt', () => { let signSpy: jest.SpyInstance>; beforeEach(() => { signSpy = jest.spyOn(jwt, 'sign'); @@ -11,26 +11,53 @@ describe('Mock test', () => { afterEach(() => { signSpy.mockReset(); }); - test('Generate JWT function works', async () => { - signSpy.mockResolvedValue('Hello'); - const authToken = await generateJwt({ CRON_JOB_PRIVATE_KEY: privateKey }); - expect(authToken).not.toBeUndefined(); - }); - test('Should call sign method', async () => { - signSpy.mockResolvedValue('Hello'); - await generateJwt({ CRON_JOB_PRIVATE_KEY: privateKey }); - expect(signSpy).toBeCalledTimes(1); - }); - test('Should return promise without await', async () => { - signSpy.mockResolvedValue('Hello'); - const authToken = generateJwt({ CRON_JOB_PRIVATE_KEY: privateKey }); - expect(authToken).toBeInstanceOf(Promise); + describe('For Rds Backend', () => { + test('Generate JWT function works', async () => { + signSpy.mockResolvedValue('Hello'); + const authToken = await generateJwt({ CRON_JOB_PRIVATE_KEY: privateKey }); + expect(authToken).not.toBeUndefined(); + }); + test('Should call sign method', async () => { + signSpy.mockResolvedValue('Hello'); + await generateJwt({ CRON_JOB_PRIVATE_KEY: privateKey }); + expect(signSpy).toBeCalledTimes(1); + }); + test('Should return promise without await', async () => { + signSpy.mockResolvedValue('Hello'); + const authToken = generateJwt({ CRON_JOB_PRIVATE_KEY: privateKey }); + expect(authToken).toBeInstanceOf(Promise); + }); + test('Throws an error if generation fails', async () => { + signSpy.mockRejectedValue('Error'); + await generateJwt({ CRON_JOB_PRIVATE_KEY: privateKey }).catch((err) => { + expect(err).toBeInstanceOf(Error); + expect(err.message).toEqual('Error in generating the auth token'); + }); + }); }); - test('Throws an error if generation fails', async () => { - signSpy.mockRejectedValue('Error'); - await generateJwt({ CRON_JOB_PRIVATE_KEY: privateKey }).catch((err) => { - expect(err).toBeInstanceOf(Error); - expect(err.message).toEqual('Error in generating the auth token'); + + describe('For Discord Bot', () => { + test('Generate JWT function works', async () => { + signSpy.mockResolvedValue('Hello'); + const authToken = await generateDiscordBotJwt({ DISCORD_BOT_PRIVATE_KEY: privateKey }); + expect(authToken).not.toBeUndefined(); + }); + test('Should call sign method', async () => { + signSpy.mockResolvedValue('Hello'); + await generateDiscordBotJwt({ DISCORD_BOT_PRIVATE_KEY: privateKey }); + expect(signSpy).toBeCalledTimes(1); + }); + test('Should return promise without await', async () => { + signSpy.mockResolvedValue('Hello'); + const authToken = generateDiscordBotJwt({ DISCORD_BOT_PRIVATE_KEY: privateKey }); + expect(authToken).toBeInstanceOf(Promise); + }); + test('Throws an error if generation fails', async () => { + signSpy.mockRejectedValue('Error'); + await generateDiscordBotJwt({ DISCORD_BOT_PRIVATE_KEY: privateKey }).catch((err) => { + expect(err).toBeInstanceOf(Error); + expect(err.message).toEqual('Error in generating the auth token'); + }); }); }); }); diff --git a/src/types/global.types.ts b/src/types/global.types.ts index 69dcd12..6652346 100644 --- a/src/types/global.types.ts +++ b/src/types/global.types.ts @@ -1,5 +1,14 @@ export type env = { - [key: string]: string; + [key: string]: any; +}; + +export type environment = { + [key: string]: variables; +}; +export type variables = { + RDS_BASE_API_URL: string; + DISCORD_BOT_API_URL: string; + MISSED_UPDATES_ROLE_ID: string; }; export type NicknameUpdateResponseType = { @@ -10,3 +19,20 @@ export type NicknameUpdateResponseType = { unsuccessfulNicknameUpdates: number; }; }; + +export type DiscordUserIdList = { + usersToAddRole: string[]; + tasks: number; + missedUpdatesTasks: number; + cursor: string; +}; + +export interface DiscordUserRole { + userid: string; + roleid: string; +} +export type DiscordRoleUpdatedList = { + userid: string; + roleid: string; + success: boolean; +}; diff --git a/src/utils/arrayUtils.ts b/src/utils/arrayUtils.ts new file mode 100644 index 0000000..4df4943 --- /dev/null +++ b/src/utils/arrayUtils.ts @@ -0,0 +1,19 @@ +/** + * 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. + * description credit: https://lodash.com/docs/4.17.15#chunk + * source code inspiration https://youmightnotneed.com/lodash#chunk + * @param {array}: array to be splitted into groups + * @param {size}: size of array groups + * @return {array}: array of arrays of elements split into groups the length of size. + */ +export const chunks = (array: any[], size: number = 1): any[][] => { + if (!Array.isArray(array) || size < 1) { + return []; + } + const temp = [...array]; + const result = []; + while (temp.length) { + result.push(temp.splice(0, size)); + } + return result; +}; diff --git a/src/utils/generateJwt.ts b/src/utils/generateJwt.ts index 3581335..b086ae0 100644 --- a/src/utils/generateJwt.ts +++ b/src/utils/generateJwt.ts @@ -18,3 +18,18 @@ export const generateJwt = async (env: env) => { throw new Error('Error in generating the auth token'); } }; + +export const generateDiscordBotJwt = async (env: env) => { + try { + const authToken = await jwt.sign( + { + exp: Math.floor(Date.now() / 1000) + 2, + }, + env.DISCORD_BOT_PRIVATE_KEY, + { algorithm: 'RS256' }, + ); + return authToken; + } catch (err) { + throw new Error('Error in generating the auth token'); + } +}; diff --git a/wrangler.toml b/wrangler.toml index dc032f7..2d67b82 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -3,19 +3,24 @@ main = "src/worker.ts" compatibility_date = "2023-07-17" - [env.staging] kv_namespaces = [ { binding = "CronJobsTimestamp", id = "509cd24483a24ddcb79f85f657274508" } ] +services = [ + { binding = "DISCORD_BOT", service = "discord-slash-commands", environment = "staging" } +] [env.production] kv_namespaces = [ { binding = "CronJobsTimestamp", id = "3a10f726c95d4afea9dee5fd00f029b9" } ] +services = [ + { binding = "DISCORD_BOT", service = "discord-slash-commands", environment = "production" } +] [triggers] -crons = ["0 */4 * * *", "0 */6 * * *"] +crons = ["0 */4 * * *", "0 */6 * * *","0 */12 * * *" ] # # Durable Object binding - For more information: https://developers.cloudflare.com/workers/runtime-apis/durable-objects # [[durable_objects]]