Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dev to Main Sync #18

Merged
merged 37 commits into from
Dec 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
874d235
Update Readme.md
RitikJaiswal75 Aug 31, 2023
2b702a2
Update formatting in readme
RitikJaiswal75 Aug 31, 2023
0151087
feat: OOO Nickname Update Job
bharati-21 Sep 22, 2023
21ee1df
refactor: baseUrl to staging API URL
bharati-21 Sep 30, 2023
f1f132d
refactor: namespace key name
bharati-21 Sep 30, 2023
fec88f7
feat: nickname batch update response type
bharati-21 Oct 1, 2023
47859de
refactor: scheduled handler type, return response
bharati-21 Oct 1, 2023
7b0bf91
refactor: key constant, return data
bharati-21 Oct 1, 2023
2a012f1
refactor: production and staging kv namespace template
bharati-21 Oct 1, 2023
40a854b
refactor: throw err for null KV namespace
bharati-21 Oct 1, 2023
2ddb96b
refactor: remove DISCORD_NICKNAME_CHANGED constant
bharati-21 Oct 7, 2023
b856cbf
refactor: rename CRON_JOBS_TIMESTAMP to CronJobsTimestamp
bharati-21 Oct 14, 2023
bb2f61f
refactor: add console errors
bharati-21 Oct 14, 2023
575a8fd
refactor: type name to PascalCase
bharati-21 Oct 14, 2023
c055577
refactor: remove number from union type
bharati-21 Oct 14, 2023
2c6a49f
refactor: log response after nickname update job
bharati-21 Oct 14, 2023
c974078
refactor: check for no value in KV store
bharati-21 Oct 14, 2023
47259cc
define constants for cron string
prakashchoudhary07 Nov 3, 2023
3515c2e
change DISCORD_NICKNAME_CHANGED to DISCORD_NICKNAME_UPDATED_TIME
prakashchoudhary07 Nov 3, 2023
f054f7d
remove disabled eslint rule
prakashchoudhary07 Nov 3, 2023
799b8d0
docs: add prod KV namespace ID
bharati-21 Nov 15, 2023
4cd05df
Merge pull request #11 from Real-Dev-Squad/Feature/1119-OOO-Nickname-…
ankushdharkar Nov 15, 2023
5718e09
Mock jwt functions and update GitHub workflow (#20)
Ajeyakrishna-k Nov 26, 2023
4f882de
fix: adds npm install to github workflows (#21)
Ajeyakrishna-k Nov 28, 2023
966c509
fix: updates staging kv namespace (#23)
Ajeyakrishna-k Nov 30, 2023
94cbd5c
refactor: update timestamp only when no unsuccessful updates (#24)
bharati-21 Dec 12, 2023
2486813
Adds util functions and changes configs (#25)
Ajeyakrishna-k Dec 12, 2023
76bef03
fix: changes old config syntax
Ajeyakrishna-k Dec 13, 2023
de0ad95
Merge pull request #30 from Real-Dev-Squad/fix/config-imports
Ajeyakrishna-k Dec 14, 2023
3e74675
chore: updates staging id (#31)
Ajeyakrishna-k Dec 14, 2023
2bc79c1
chore: adds kv namespace (#32)
Ajeyakrishna-k Dec 14, 2023
176690a
Adds cron job handler functions (#26)
Ajeyakrishna-k Dec 15, 2023
9d19c35
test: kv namespace for staging (#33)
Ajeyakrishna-k Dec 15, 2023
16765a9
Update staging.yaml, added staging env, other secrets (#34)
prakashchoudhary07 Dec 15, 2023
90d8c43
Update wrangler config for service binding (#35)
Ajeyakrishna-k Dec 15, 2023
b0011d3
Removes 4hr cron trigger (#36)
Ajeyakrishna-k Dec 15, 2023
d402821
chore: add new secrets/vars to cf from github secrets/vars (#37)
prakashchoudhary07 Dec 15, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Date: `<DATE>`

Developer Name: `<DEVELOPER NAME>`

----

## Issue Ticket Number:-
`<ISSUE NUMBER>`

## Description:
`<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. )

16 changes: 11 additions & 5 deletions .github/workflows/production.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ jobs:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v2
- uses: cloudflare/[email protected]
- uses: actions/checkout@v3
- run: npm install
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{secrets.CLOUDFLARE_API_TOKEN}}
accountId: ${{secrets.CLOUDFLARE_ACCOUNT_ID}}
command: deploy --env=${{ vars.CURRENT_ENVIRONMENT }}
secrets: |
CRON_JOB_PRIVATE_KEY
CURRENT_ENVIRONMENT
CRON_JOB_PRIVATE_KEY
DISCORD_BOT_PRIVATE_KEY
DISCORD_BOT_API_URL
env:
CURRENT_ENVIRONMENT: production
CRON_JOB_PRIVATE_KEY: ${{secrets.CRON_JOB_PRIVATE_KEY}}
CLOUDFLARE_API_TOKEN: ${{secrets.CLOUDFLARE_API_TOKEN}}
CLOUDFLARE_ACCOUNT_ID: ${{secrets.CLOUDFLARE_ACCOUNT_ID}}
CURRENT_ENVIRONMENT: ${{vars.CURRENT_ENVIRONMENT}}
CRON_JOB_PRIVATE_KEY: ${{secrets.CRON_JOB_PRIVATE_KEY}}
DISCORD_BOT_PRIVATE_KEY: ${{secrets.DISCORD_BOT_PRIVATE_KEY}}
DISCORD_BOT_API_URL: ${{secrets.DISCORD_BOT_API_URL}}
16 changes: 12 additions & 4 deletions .github/workflows/staging.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,23 @@ jobs:
environment: staging
steps:
- uses: actions/checkout@v2
- uses: cloudflare/[email protected]
- run: npm install
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{secrets.CLOUDFLARE_API_TOKEN}}
accountId: ${{secrets.CLOUDFLARE_ACCOUNT_ID}}
command: deploy --env=${{ vars.CURRENT_ENVIRONMENT }}
secrets: |
CRON_JOB_PRIVATE_KEY
CURRENT_ENVIRONMENT
CRON_JOB_PRIVATE_KEY
DISCORD_BOT_PRIVATE_KEY
DISCORD_BOT_API_URL
env:
CURRENT_ENVIRONMENT: staging
CRON_JOB_PRIVATE_KEY: ${{secrets.CRON_JOB_PRIVATE_KEY}}
CLOUDFLARE_API_TOKEN: ${{secrets.CLOUDFLARE_API_TOKEN}}
CLOUDFLARE_ACCOUNT_ID: ${{secrets.CLOUDFLARE_ACCOUNT_ID}}
CURRENT_ENVIRONMENT: ${{vars.CURRENT_ENVIRONMENT}}
CRON_JOB_PRIVATE_KEY: ${{secrets.CRON_JOB_PRIVATE_KEY}}
DISCORD_BOT_PRIVATE_KEY: ${{secrets.DISCORD_BOT_PRIVATE_KEY}}
DISCORD_BOT_API_URL: ${{secrets.DISCORD_BOT_API_URL}}


3 changes: 2 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ jobs:
- uses: actions/checkout@v2
- run: npm install
- run: npm run lint-check
- run: npm run format-check
- run: npm run format-check
- run: npm run test
17 changes: 15 additions & 2 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,17 @@ To ensure secure communication between the cron jobs and the backend, every requ
## How to Set Up Locally

1. Clone this repository to your local machine.

2. Run `npm install` to install the required dependencies.

3. Run `npm deploy` to deploy the Cloudflare Worker.

4. Generate a pair of RSA 2048-bit keys (private and public).

5. Go to `dash.cloudflare.com`, select "Workers," and find the worker named "Cron Jobs."

6. Go to its settings > variables and add a variable named `CRON_JOB_PRIVATE_KEY`, and paste your private key.

7. Copy and paste the public key in the RDS backend configuration file (`config/local.js`) under `cronJobHandler`:

```js
Expand All @@ -50,21 +56,28 @@ publicKey: <copied public key>,
}
```

Make sure the public key is formatted as it is in `config/test.js`. 8. Start the backend by running the following command in your terminal:
Make sure the public key is formatted as it is in `config/test.js`.

8. Start the backend by running the following command in your terminal:

```bash
yarn dev
```

9. With the backend running, use ngrok to expose your local server publicly. Install ngrok if you haven't already.

10. Start ngrok with the following command:

```
ngrok http <PORT_NUMBER>
```

Replace `<PORT_NUMBER>` with the port number your backend is running on (e.g., 3000).
11. Ngrok will generate a public URL (e.g., `https://abc123.ngrok.io`) that forwards requests to your local server. Copy this ngrok URL. 12. Paste the ngrok URL in the "else" part of the Cron Jobs project's `src/config.ts` file, inside the `handleConfig` function.

11. Ngrok will generate a public URL (e.g., `https://abc123.ngrok.io`) that forwards requests to your local server. Copy this ngrok URL.

12. Paste the ngrok URL in the "else" part of the Cron Jobs project's `src/config.ts` file, inside the `handleConfig` function.

13. Now set the desired trigger time as mentioned [here](#how-to-set-trigger-time)

Now, your backend will get the call at the set time using the public ngrok URL. Remember to keep the backend running with ngrok to ensure the scheduled cron jobs work as intended.
Expand Down
7 changes: 7 additions & 0 deletions __mocks__/@tsndr/cloudflare-worker-jwt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const mockJwt = {
sign: jest.fn().mockImplementation(() => {
return "SIGNED_JWT";
}),
};

export default mockJwt;
37 changes: 24 additions & 13 deletions src/config/config.ts
Original file line number Diff line number Diff line change
@@ -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;
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
const NAMESPACE_NAME = 'CronJobsTimestamp';

export { NAMESPACE_NAME };
3 changes: 3 additions & 0 deletions src/constants/commons.ts
Original file line number Diff line number Diff line change
@@ -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';
7 changes: 7 additions & 0 deletions src/constants/urls.ts
Original file line number Diff line number Diff line change
@@ -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 = '';
95 changes: 91 additions & 4 deletions src/handlers/scheduledEventHandler.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,95 @@
import { handleConfig } from '../config/config';
import { env } from '../types/global.types';
import { KVNamespace } from '@cloudflare/workers-types';

import config from '../config/config';
import { NAMESPACE_NAME } from '../constants';
import { updateUserRoles } from '../services/discordBotServices';
import { getMissedUpdatesUsers } from '../services/rdsBackendService';
import { DiscordUserRole, env, NicknameUpdateResponseType } from '../types/global.types';
import { chunks } from '../utils/arrayUtils';
import { generateJwt } from '../utils/generateJwt';

export async function ping(env: env) {
const url = handleConfig(env);
const response = await fetch(`${url.baseUrl}/healthcheck`);
const url = config(env).RDS_BASE_API_URL;
const response = await fetch(`${url}/healthcheck`);
return response;
}

export async function callDiscordNicknameBatchUpdate(env: env) {
const namespace = env[NAMESPACE_NAME] as unknown as KVNamespace;
let lastNicknameUpdate: string | null = '0';
try {
lastNicknameUpdate = await namespace.get('DISCORD_NICKNAME_UPDATED_TIME');
if (lastNicknameUpdate === null) {
throw new Error('Error while fetching KV "DISCORD_NICKNAME_UPDATED_TIME" timestamp');
}
if (!lastNicknameUpdate) {
lastNicknameUpdate = '0';
}
} catch (err) {
console.error(err, 'Error while fetching the timestamp for last nickname update');
throw err;
}

const url = config(env).RDS_BASE_API_URL;
let token;
try {
token = await generateJwt(env);
} catch (err) {
console.error(`Error while generating JWT token: ${err}`);
throw err;
}
const response = await fetch(`${url}/discord-actions/nickname/status`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
lastNicknameUpdate,
}),
});
if (!response.ok) {
throw new Error("Error while trying to update users' discord nickname");
}

const data: NicknameUpdateResponseType = await response.json();
if (data?.data.unsuccessfulNicknameUpdates === 0) {
try {
await namespace.put('DISCORD_NICKNAME_UPDATED_TIME', Date.now().toString());
} catch (err) {
console.error('Error while trying to update the last nickname change timestamp');
}
}
return data;
}

export const addMissedUpdatesRole = async (env: env) => {
const MAX_ROLE_UPDATE = 25;
try {
let cursor: string | undefined = undefined;
for (let index = MAX_ROLE_UPDATE; index > 0; index--) {
if (index < MAX_ROLE_UPDATE && !cursor) break;

const missedUpdatesUsers = await getMissedUpdatesUsers(env, cursor);

if (!!missedUpdatesUsers && missedUpdatesUsers.usersToAddRole?.length > 1) {
const discordUserIdRoleIdList: DiscordUserRole[] = missedUpdatesUsers.usersToAddRole.map((userId) => ({
userid: userId,
roleid: config(env).MISSED_UPDATES_ROLE_ID,
}));

const discordUserRoleChunks = chunks(discordUserIdRoleIdList, MAX_ROLE_UPDATE);
for (const discordUserRoleList of discordUserRoleChunks) {
try {
await updateUserRoles(env, discordUserRoleList);
} catch (error) {
console.error('Error occurred while updating discord users', error);
}
}
}
cursor = missedUpdatesUsers?.cursor;
}
} catch (err) {
console.error('Error while adding missed updates roles');
}
};
27 changes: 27 additions & 0 deletions src/services/discordBotServices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import config from '../config/config';
import { DiscordRoleUpdatedList, DiscordUserRole, env } from '../types/global.types';
import { generateDiscordBotJwt } from '../utils/generateJwt';

export const updateUserRoles = async (env: env, payload: DiscordUserRole[]): Promise<DiscordRoleUpdatedList> => {
try {
const url = config(env).DISCORD_BOT_API_URL;
const token = await generateDiscordBotJwt(env);

const response = await env.DISCORD_BOT.fetch(`${url}/roles?action=add-role`, {
method: 'POST',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
});
if (!response.ok) {
throw Error(`Role Update failed with status: ${response.status}`);
}
const data: DiscordRoleUpdatedList = await response.json();
return data;
} catch (error) {
console.error('Error while updating discord user roles');
throw error;
}
};
32 changes: 32 additions & 0 deletions src/services/rdsBackendService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import config from '../config/config';
import { DiscordUsersResponse, env } from '../types/global.types';
import { generateJwt } from '../utils/generateJwt';

export const getMissedUpdatesUsers = async (env: env, cursor: string | undefined) => {
try {
const baseUrl = config(env).RDS_BASE_API_URL;

const url = new URL(`${baseUrl}/tasks/users/discord`);
url.searchParams.append('q', 'status:missed-updates');
if (cursor) {
url.searchParams.append('cursor', cursor);
}
const token = await generateJwt(env);
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
if (!response.ok) {
throw new Error(`Fetch call to get user discord details failed with status: ${response.status}`);
}

const responseData: DiscordUsersResponse = await response.json();
return responseData?.data;
} catch (error) {
console.error('Error occurrent while fetching discord user details');
throw error;
}
};
Loading
Loading