From f80c27d11c00329382fc4f9e332bb929ce6370b3 Mon Sep 17 00:00:00 2001 From: Nathaniel Paulus Date: Mon, 22 Apr 2024 14:22:19 -0400 Subject: [PATCH 1/4] Add script to monitor Paratext registry server Co-authored-by: MarkS --- scripts/monitor_pt_registry.ts | 121 +++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100755 scripts/monitor_pt_registry.ts diff --git a/scripts/monitor_pt_registry.ts b/scripts/monitor_pt_registry.ts new file mode 100755 index 0000000000..966c45f70f --- /dev/null +++ b/scripts/monitor_pt_registry.ts @@ -0,0 +1,121 @@ +#!/usr/bin/env -S deno run --allow-read --allow-write --allow-env --allow-net + +import { existsSync } from 'https://deno.land/std@0.223.0/fs/exists.ts'; +import axios from 'npm:axios@1.6.8'; + +if ( + Deno.args.length !== 6 || + Deno.args[0] !== '--project-id' || + Deno.args[2] !== '--client-id' || + Deno.args[4] !== '--client-secret' +) { + console.error( + 'Usage: ./monitor_pt_registry.ts --project-id paratext_id --client-id some_id --client-secret some_secret' + ); + Deno.exit(1); +} + +const tokens = JSON.parse(Deno.readTextFileSync('tokens.json')); + +const clientId = Deno.args[3]; +const clientSecret = Deno.args[5]; +const apiRoot = 'https://registry.paratext.org/api8'; +let accessToken = tokens.access_token; +let refreshToken = tokens.refresh_token; +const intervalRefreshTokenMinutes = 5; +const intervalQueryMembersMinutes = 1; +const projectId = Deno.args[1]; +const logFileName = 'request_log.json'; + +type RequestEvent = { + timestamp: Date; + endpoint: string; + success: boolean; + response_time_ms: number; +}; + +const requestLog: RequestEvent[] = existsSync(logFileName) ? JSON.parse(Deno.readTextFileSync(logFileName)) : []; + +function logRequest(event: RequestEvent) { + requestLog.push(event); + Deno.writeTextFileSync(logFileName, JSON.stringify(requestLog, null, 2)); +} + +await refreshTokenWithRegistry(); // make sure tokens are up to date before starting +setInterval(refreshTokenWithRegistry, 1000 * 60 * intervalRefreshTokenMinutes); +queryMembers(); +setInterval(queryMembers, 1000 * 60 * intervalQueryMembersMinutes); + +async function refreshTokenWithRegistry(): Promise { + const startTime = new Date(); + return await axios + .post(`${apiRoot}/token`, { + client_id: clientId, + client_secret: clientSecret, + grant_type: 'refresh_token', + refresh_token: refreshToken + }) + .catch((error: any) => { + const endTime = new Date(); + console.error(error.response.data); + const duration = endTime.getTime() - startTime.getTime(); + logRequest({ + timestamp: startTime, + endpoint: 'POST /token', + success: false, + response_time_ms: duration + }); + }) + .then((response: any) => { + const endTime = new Date(); + accessToken = response.data.access_token; + refreshToken = response.data.refresh_token; + console.log('Refreshed token'); + console.log('New access token:', accessToken); + console.log('New refresh token:', refreshToken); + + const tokens = { access_token: accessToken, refresh_token: refreshToken }; + Deno.writeTextFileSync('tokens.json', JSON.stringify(tokens, null, 2)); + + const duration = endTime.getTime() - startTime.getTime(); + logRequest({ + timestamp: startTime, + endpoint: 'POST /token', + success: true, + response_time_ms: duration + }); + }); +} + +async function queryMembers(): Promise { + const startTime = new Date(); + return await axios + .get(`${apiRoot}/projects/${projectId}/members`, { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${accessToken}` + } + }) + .catch((error: any) => { + const endTime = new Date(); + console.error(error.response.data); + const duration = endTime.getTime() - startTime.getTime(); + logRequest({ + timestamp: startTime, + endpoint: 'GET /projects/id/members', + success: false, + response_time_ms: duration + }); + }) + .then((response: any) => { + const endTime = new Date(); + console.log(response.data); + const duration = endTime.getTime() - startTime.getTime(); + logRequest({ + timestamp: startTime, + endpoint: 'GET /projects/id/members', + success: true, + response_time_ms: duration + }); + }); +} From 0ce4f896668eacdc0996f6ca81d576e11d449cff Mon Sep 17 00:00:00 2001 From: MarkS Date: Mon, 22 Apr 2024 13:25:00 -0600 Subject: [PATCH 2/4] Improve error reporting --- scripts/monitor_pt_registry.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/monitor_pt_registry.ts b/scripts/monitor_pt_registry.ts index 966c45f70f..6667597635 100755 --- a/scripts/monitor_pt_registry.ts +++ b/scripts/monitor_pt_registry.ts @@ -57,6 +57,7 @@ async function refreshTokenWithRegistry(): Promise { }) .catch((error: any) => { const endTime = new Date(); + console.error(`Error: refreshTokenWithRegistry ${error.response.status} ${error.response.statusText}`); console.error(error.response.data); const duration = endTime.getTime() - startTime.getTime(); logRequest({ @@ -98,6 +99,7 @@ async function queryMembers(): Promise { }) .catch((error: any) => { const endTime = new Date(); + console.error(`Error: queryMembers ${error.response.status} ${error.response.statusText}`); console.error(error.response.data); const duration = endTime.getTime() - startTime.getTime(); logRequest({ From b1b4dfa0b396c57d376bfc302546d7c8c04b70d9 Mon Sep 17 00:00:00 2001 From: MarkS Date: Mon, 22 Apr 2024 13:26:39 -0600 Subject: [PATCH 3/4] Determine project id --- scripts/monitor_pt_registry.ts | 64 +++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/scripts/monitor_pt_registry.ts b/scripts/monitor_pt_registry.ts index 6667597635..7509ae996b 100755 --- a/scripts/monitor_pt_registry.ts +++ b/scripts/monitor_pt_registry.ts @@ -3,28 +3,21 @@ import { existsSync } from 'https://deno.land/std@0.223.0/fs/exists.ts'; import axios from 'npm:axios@1.6.8'; -if ( - Deno.args.length !== 6 || - Deno.args[0] !== '--project-id' || - Deno.args[2] !== '--client-id' || - Deno.args[4] !== '--client-secret' -) { - console.error( - 'Usage: ./monitor_pt_registry.ts --project-id paratext_id --client-id some_id --client-secret some_secret' - ); +if (Deno.args.length !== 4 || Deno.args[0] !== '--client-id' || Deno.args[2] !== '--client-secret') { + console.error('Usage: ./monitor_pt_registry.ts --client-id some_id --client-secret some_secret'); Deno.exit(1); } +const clientId = Deno.args[1]; +const clientSecret = Deno.args[3]; + const tokens = JSON.parse(Deno.readTextFileSync('tokens.json')); -const clientId = Deno.args[3]; -const clientSecret = Deno.args[5]; const apiRoot = 'https://registry.paratext.org/api8'; let accessToken = tokens.access_token; let refreshToken = tokens.refresh_token; const intervalRefreshTokenMinutes = 5; const intervalQueryMembersMinutes = 1; -const projectId = Deno.args[1]; const logFileName = 'request_log.json'; type RequestEvent = { @@ -41,7 +34,9 @@ function logRequest(event: RequestEvent) { Deno.writeTextFileSync(logFileName, JSON.stringify(requestLog, null, 2)); } -await refreshTokenWithRegistry(); // make sure tokens are up to date before starting +// Make sure tokens are up to date before starting +await refreshTokenWithRegistry(); +const projectId = await getFirstPTProjectId(); setInterval(refreshTokenWithRegistry, 1000 * 60 * intervalRefreshTokenMinutes); queryMembers(); setInterval(queryMembers, 1000 * 60 * intervalQueryMembersMinutes); @@ -88,6 +83,49 @@ async function refreshTokenWithRegistry(): Promise { }); } +async function getFirstPTProjectId(): Promise { + const userProjects = await queryProjects(); + return firstProjectId(userProjects); +} + +async function queryProjects(): Promise { + const startTime = new Date(); + let response; + try { + response = await axios.get(`${apiRoot}/projects`, { + headers: { + Accept: 'application/json', + Authorization: `Bearer ${accessToken}` + } + }); + } catch (error) { + const endTime = new Date(); + const duration = endTime.getTime() - startTime.getTime(); + console.error(`Error: queryProjects ${error.response.status} ${error.response.statusText}`); + console.error(error.response.data); + logRequest({ + timestamp: startTime, + endpoint: 'GET /projects', + success: false, + response_time_ms: duration + }); + return undefined; + } + const endTime = new Date(); + const duration = endTime.getTime() - startTime.getTime(); + logRequest({ + timestamp: startTime, + endpoint: 'GET /projects', + success: true, + response_time_ms: duration + }); + return response.data; +} + +function firstProjectId(projects: any): string { + return projects[0].identification_systemId.filter(item => item.type === 'paratext')[0].text; +} + async function queryMembers(): Promise { const startTime = new Date(); return await axios From d717ddab2d0e7add6ed8b215c5066f5a3a32847a Mon Sep 17 00:00:00 2001 From: MarkS Date: Mon, 22 Apr 2024 14:12:41 -0600 Subject: [PATCH 4/4] Add some more output to see what it is doing --- scripts/monitor_pt_registry.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/monitor_pt_registry.ts b/scripts/monitor_pt_registry.ts index 7509ae996b..ad2f27efd5 100755 --- a/scripts/monitor_pt_registry.ts +++ b/scripts/monitor_pt_registry.ts @@ -43,6 +43,7 @@ setInterval(queryMembers, 1000 * 60 * intervalQueryMembersMinutes); async function refreshTokenWithRegistry(): Promise { const startTime = new Date(); + console.log(`${startTime.toISOString()} Refreshing token`); return await axios .post(`${apiRoot}/token`, { client_id: clientId, @@ -90,6 +91,7 @@ async function getFirstPTProjectId(): Promise { async function queryProjects(): Promise { const startTime = new Date(); + console.log(`${startTime.toISOString()} Querying projects`); let response; try { response = await axios.get(`${apiRoot}/projects`, { @@ -128,6 +130,7 @@ function firstProjectId(projects: any): string { async function queryMembers(): Promise { const startTime = new Date(); + console.log(`${startTime.toISOString()} Querying members`); return await axios .get(`${apiRoot}/projects/${projectId}/members`, { headers: {