From 085c45429723cb85761028fff57bf3558b677a05 Mon Sep 17 00:00:00 2001 From: Ajeyakrishna Date: Fri, 24 Nov 2023 20:45:58 +0530 Subject: [PATCH 1/5] fix: add npm test --- .github/workflows/test.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 2737e68..26938c0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -9,4 +9,5 @@ jobs: - uses: actions/checkout@v2 - run: npm install - run: npm run lint-check - - run: npm run format-check \ No newline at end of file + - run: npm run format-check + - run: npm run test \ No newline at end of file From 4685dc010c5ebbee53a3146ba72a2029f1b34802 Mon Sep 17 00:00:00 2001 From: Ajeyakrishna Date: Fri, 24 Nov 2023 21:30:17 +0530 Subject: [PATCH 2/5] feat: mocks jwt sign function --- __mocks__/@tsndr/cloudflare-worker-jwt.ts | 7 +++++++ src/utils/generateJwt.ts | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 __mocks__/@tsndr/cloudflare-worker-jwt.ts diff --git a/__mocks__/@tsndr/cloudflare-worker-jwt.ts b/__mocks__/@tsndr/cloudflare-worker-jwt.ts new file mode 100644 index 0000000..d90cca5 --- /dev/null +++ b/__mocks__/@tsndr/cloudflare-worker-jwt.ts @@ -0,0 +1,7 @@ +const mockJwt = { + sign: jest.fn().mockImplementation(() => { + return "SIGNED_JWT"; + }), +}; + +export default mockJwt; diff --git a/src/utils/generateJwt.ts b/src/utils/generateJwt.ts index 3581335..a3a2ba7 100644 --- a/src/utils/generateJwt.ts +++ b/src/utils/generateJwt.ts @@ -10,7 +10,7 @@ export const generateJwt = async (env: env) => { exp: Math.floor(Date.now() / 1000) + 2, }, env.CRON_JOB_PRIVATE_KEY, - { algorithm: 'RS256' }, + { algorithm: 'RS256' } ); return authToken; From c089663e2635af14dcc8a1ad0e2dfa6c99e3d1ac Mon Sep 17 00:00:00 2001 From: Ajeyakrishna <98796547+Ajeyakrishna-k@users.noreply.github.com> Date: Fri, 24 Nov 2023 21:32:22 +0530 Subject: [PATCH 3/5] fix: lint issue --- src/utils/generateJwt.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/generateJwt.ts b/src/utils/generateJwt.ts index a3a2ba7..3581335 100644 --- a/src/utils/generateJwt.ts +++ b/src/utils/generateJwt.ts @@ -10,7 +10,7 @@ export const generateJwt = async (env: env) => { exp: Math.floor(Date.now() / 1000) + 2, }, env.CRON_JOB_PRIVATE_KEY, - { algorithm: 'RS256' } + { algorithm: 'RS256' }, ); return authToken; From 9918ac0543d9c7ce644faa1a7de8cc8cb6b1e40b Mon Sep 17 00:00:00 2001 From: Ajeyakrishna Date: Fri, 24 Nov 2023 21:41:14 +0530 Subject: [PATCH 4/5] feat: add pull request template --- .github/pull_request_template.md | 33 ++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..fec9c6a --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,33 @@ +Date: `` + +Developer Name: `` + +---- + +## Issue Ticket Number:- +`` + +## Description: +`` + + +Is Under Feature Flag +- [ ] Yes +- [ ] No + +Database changes +- [ ] Yes +- [ ] No + +Breaking changes (If your feature is breaking/missing something please mention pending tickets) +- [ ] Yes +- [ ] No + +Is Development Tested? + +- [ ] Yes +- [ ] No + + +### Add relevant Screenshot below ( e.g test coverage etc. ) + From 5c8a942929015f28018c9785e732b7e0b79506f0 Mon Sep 17 00:00:00 2001 From: Ajeyakrishna Date: Mon, 11 Dec 2023 19:21:50 +0530 Subject: [PATCH 5/5] 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 | 18 +++++---- 9 files changed, 188 insertions(+), 43 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 dd66e15..2d67b82 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -2,23 +2,25 @@ name = "cron-jobs" main = "src/worker.ts" compatibility_date = "2023-07-17" + +[env.staging] kv_namespaces = [ - { binding = "CronJobsTimestamp", id = "7dcf94601bec415db4b13d5d1faf0fc3" } + { binding = "CronJobsTimestamp", id = "509cd24483a24ddcb79f85f657274508" } +] +services = [ + { binding = "DISCORD_BOT", service = "discord-slash-commands", environment = "staging" } ] - -# [env.staging] -# the BINDING_NAME must be CronJobsTimestamp to override in the staging env -# kv_namespaces = [ -# { binding = "", id = "" } -# ] [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]]