Skip to content

Commit 5a85735

Browse files
committed
Start scraping quiz events
1 parent 2a27c67 commit 5a85735

16 files changed

+248
-252
lines changed

src/dependencies.ts

Lines changed: 0 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import {Logger} from 'pino';
22
import {Failure, Email, DomainEvent, ResourceVersion} from './types';
33
import * as TE from 'fp-ts/TaskEither';
4-
import * as t from 'io-ts';
5-
import * as O from 'fp-ts/Option';
64
import {FailureWithStatus} from './types/failure-with-status';
75
import {StatusCodes} from 'http-status-codes';
86

@@ -40,40 +38,4 @@ export type Dependencies = {
4038
logger: Logger;
4139
rateLimitSendingOfEmails: (email: Email) => TE.TaskEither<Failure, Email>;
4240
sendEmail: (email: Email) => TE.TaskEither<Failure, string>;
43-
getCachedSheetData: (sheetId: string) => TE.TaskEither<
44-
FailureWithStatus,
45-
O.Option<{
46-
cached_at: Date;
47-
cached_data: t.Validation<
48-
ReadonlyArray<
49-
| EventOfType<'EquipmentTrainingQuizResult'>
50-
| EventOfType<'EquipmentTrainingQuizSync'>
51-
>
52-
>;
53-
}>
54-
>;
55-
cacheSheetData: (
56-
cacheTimestamp: Date,
57-
sheetId: GoogleSheetId,
58-
logger: Logger,
59-
data: ReadonlyArray<
60-
| EventOfType<'EquipmentTrainingQuizSync'>
61-
| EventOfType<'EquipmentTrainingQuizResult'>
62-
>
63-
) => Promise<void>;
64-
getCachedTroubleTicketData: (sheetId: string) => TE.TaskEither<
65-
FailureWithStatus,
66-
O.Option<{
67-
cached_at: Date;
68-
cached_data: t.Validation<
69-
ReadonlyArray<EventOfType<'TroubleTicketResponseSubmitted'>>
70-
>;
71-
}>
72-
>;
73-
cacheTroubleTicketData: (
74-
cacheTimestamp: Date,
75-
sheetId: GoogleSheetId,
76-
logger: Logger,
77-
data: ReadonlyArray<EventOfType<'TroubleTicketResponseSubmitted'>>
78-
) => Promise<void>;
7941
};

src/index.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,21 @@ import {initDependencies} from './init-dependencies';
1919
import * as libsqlClient from '@libsql/client';
2020
import cookieSession from 'cookie-session';
2121
import {initRoutes} from './routes';
22-
import {ensureCachedSheetDataTableExists} from './init-dependencies/google/ensure-cached-sheet-data-table-exists';
2322
import {
2423
loadCachedSheetData,
2524
loadCachedTroubleTicketData,
2625
} from './load-cached-sheet-data';
2726
import {timeAsync} from './util';
27+
import {Worker} from 'worker_threads';
28+
29+
// This background worker can be treated as a completely independent process
30+
// but we just spawn it as a thread for now.
31+
const backgroundSyncWorker = new Worker('./training-sheets/sync_worker.js');
32+
backgroundSyncWorker.on('exit', () => {
33+
console.error('Background worker has finished. Stopping parent');
34+
// eslint-disable-next-line n/no-process-exit
35+
process.exit(1);
36+
});
2837

2938
// Dependencies and Config
3039
const conf = loadConfig();
@@ -33,12 +42,7 @@ const dbClient = libsqlClient.createClient({
3342
syncUrl: conf.TURSO_SYNC_URL,
3443
authToken: conf.TURSO_TOKEN,
3544
});
36-
const cacheClient = libsqlClient.createClient({
37-
url: conf.EVENT_DB_URL,
38-
syncUrl: conf.TURSO_SYNC_URL,
39-
authToken: conf.TURSO_TOKEN,
40-
});
41-
const deps = initDependencies(dbClient, cacheClient, conf);
45+
const deps = initDependencies(dbClient, conf);
4246
const routes = initRoutes(deps, conf);
4347

4448
// Passport Setup
@@ -106,7 +110,6 @@ server.on('close', () => {
106110
void (async () => {
107111
await pipe(
108112
ensureEventTableExists(dbClient),
109-
TE.map(ensureCachedSheetDataTableExists(dbClient)),
110113
TE.mapLeft(e => deps.logger.error(e, 'Failed to start server'))
111114
)();
112115

src/init-dependencies/google/cache-sheet-data.ts

Lines changed: 0 additions & 32 deletions
This file was deleted.

src/init-dependencies/google/cached-data-table.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/init-dependencies/google/ensure-cached-sheet-data-table-exists.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import {Client} from '@libsql/client/.';
2+
import {dbExecute} from '../../util';
3+
4+
// This table contains a copy of all the training sheet data currently in google.
5+
// It is read only on requests from the frontend so it can be accelerated via read-replicas.
6+
// We used to try and turn the google sheet data into events however this proved messier and messier
7+
// once we needed to do things like cache the sheet data (parsing data is slow and prevents startup before healthcheck failure),
8+
// do incremental pulls (parsing data is slow), only pull 1 bit of equipment at a time (otherwise it blocks the event loop).
9+
10+
export const ensureSheetDataTableExists = (dbClient: Client) =>
11+
dbExecute(
12+
dbClient,
13+
`
14+
CREATE TABLE IF NOT EXISTS sheet_data (
15+
sheet_id TEXT PRIMARY KEY,
16+
row_index INTEGER,
17+
member_number_provided INTEGER,
18+
email_provided TEXT,
19+
score INTEGER,
20+
max_score INTEGER,
21+
percentage INTEGER,
22+
cached_at INTEGER
23+
);
24+
`,
25+
{}
26+
);
27+
28+
export const ensureSheetDataSyncMetadataTableExists = (dbClient: Client) =>
29+
dbExecute(
30+
dbClient,
31+
`
32+
CREATE TABLE IF NOT EXISTS sheet_sync_metadata (
33+
sheet_id TEXT PRIMARY KEY,
34+
last_sync INTEGER
35+
);
36+
`,
37+
{}
38+
);
39+
40+
export const ensureTroubleTicketDataTableExists = (dbClient: Client) =>
41+
dbExecute(
42+
dbClient,
43+
`
44+
CREATE TABLE IF NOT EXISTS trouble_ticket_data (
45+
sheet_id TEXT PRIMARY KEY,
46+
response_submitted INTEGER,
47+
cached_at INTEGER,
48+
submitted_email TEXT,
49+
submitted_equipment TEXT,
50+
submitted_name TEXT,
51+
submitted_membership_number INTEGER,
52+
submitted_response_json TEXT
53+
);
54+
`,
55+
{}
56+
);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import * as t from 'io-ts';
2+
import * as tt from 'io-ts-types';
3+
4+
export const SheetDataTable = t.strict({
5+
rows: t.readonlyArray(
6+
t.strict({
7+
sheet_id: t.string,
8+
row_index: t.Integer,
9+
member_number_provided: t.Integer,
10+
email_provided: t.string,
11+
score: t.Integer,
12+
max_score: t.Integer,
13+
percentage: t.Integer,
14+
cached_at: tt.DateFromNumber,
15+
})
16+
),
17+
});
18+
export type SheetDataTable = t.TypeOf<typeof SheetDataTable>;
19+
20+
export const SheetSyncMetadataTable = t.strict({
21+
rows: t.readonlyArray(
22+
t.strict({
23+
sheet_id: t.string,
24+
row_index: t.Integer,
25+
member_number_provided: t.Integer,
26+
email_provided: t.string,
27+
score: t.Integer,
28+
max_score: t.Integer,
29+
percentage: t.Integer,
30+
cached_at: tt.DateFromNumber,
31+
})
32+
),
33+
});
34+
export type SheetSyncMetadataTable = t.TypeOf<typeof SheetSyncMetadataTable>;
35+
36+
export const TroubleTicketDataTable = t.strict({
37+
rows: t.readonlyArray(
38+
t.strict({
39+
sheet_id: t.string,
40+
response_submitted: tt.DateFromNumber,
41+
cached_at: tt.DateFromNumber,
42+
// Do not trust provided data - it is not verified.
43+
submitted_email: t.union([t.string, t.null]),
44+
submitted_equipment: t.union([t.string, t.null]),
45+
submitted_name: t.union([t.string, t.null]),
46+
submitted_membership_number: t.union([t.Integer, t.null]),
47+
submitted_response_json: t.string,
48+
})
49+
),
50+
});
51+
export type TroubleTicketDataTable = t.TypeOf<typeof TroubleTicketDataTable>;

src/init-dependencies/init-dependencies.ts

Lines changed: 9 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,10 @@ import {commitEvent} from './event-store/commit-event';
1010
import {getAllEvents, getAllEventsByType} from './event-store/get-all-events';
1111
import {getResourceEvents} from './event-store/get-resource-events';
1212
import {Client} from '@libsql/client';
13-
import {
14-
GoogleHelpers,
15-
pullGoogleSheetData,
16-
pullGoogleSheetDataMetadata,
17-
} from './google/pull_sheet_data';
13+
1814
import {initSharedReadModel} from '../read-models/shared-state';
19-
import {GoogleAuth} from 'google-auth-library';
20-
import {getCachedSheetData} from './google/get-cached-sheet-data';
21-
import {cacheSheetData} from './google/cache-sheet-data';
2215

23-
export const initDependencies = (
24-
dbClient: Client,
25-
cacheClient: Client,
26-
conf: Config
27-
): Dependencies => {
16+
export const initLogger = (conf: Config) => {
2817
let loggerOptions: LoggerOptions;
2918
loggerOptions = {
3019
formatters: {
@@ -49,8 +38,14 @@ export const initDependencies = (
4938
},
5039
};
5140
}
41+
return createLogger(loggerOptions);
42+
};
5243

53-
const logger = createLogger(loggerOptions);
44+
export const initDependencies = (
45+
dbClient: Client,
46+
conf: Config
47+
): Dependencies => {
48+
const logger = initLogger(conf);
5449

5550
const emailTransporter = nodemailer.createTransport(
5651
smtp({
@@ -64,35 +59,10 @@ export const initDependencies = (
6459
})
6560
);
6661

67-
let googleHelpers: O.Option<GoogleHelpers> = O.none;
68-
if (
69-
conf.GOOGLE_SERVICE_ACCOUNT_KEY_JSON.toLowerCase().trim() !== 'disabled'
70-
) {
71-
const googleAuth = new GoogleAuth({
72-
// Google issues the credentials file and validates it.
73-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
74-
credentials: JSON.parse(conf.GOOGLE_SERVICE_ACCOUNT_KEY_JSON),
75-
scopes: ['https://www.googleapis.com/auth/spreadsheets.readonly'],
76-
});
77-
googleHelpers = O.some({
78-
pullGoogleSheetData: pullGoogleSheetData(googleAuth),
79-
pullGoogleSheetDataMetadata: pullGoogleSheetDataMetadata(googleAuth),
80-
});
81-
}
82-
83-
const _cacheSheetData: Dependencies['cacheSheetData'] =
84-
cacheSheetData(cacheClient);
85-
const _cacheTroubleTicketData: Dependencies['cacheTroubleTicketData'] =
86-
cacheSheetData(cacheClient);
87-
8862
const sharedReadModel = initSharedReadModel(
8963
dbClient,
9064
logger,
91-
googleHelpers,
92-
conf.GOOGLE_RATELIMIT_MS,
9365
O.fromNullable(conf.TROUBLE_TICKET_SHEET),
94-
_cacheSheetData,
95-
_cacheTroubleTicketData,
9666
O.fromNullable(conf.RECURLY_TOKEN)
9767
);
9868

@@ -105,10 +75,6 @@ export const initDependencies = (
10575
rateLimitSendingOfEmails: createRateLimiter(5, 24 * 3600),
10676
sendEmail: sendEmail(emailTransporter, conf.SMTP_FROM),
10777
logger,
108-
getCachedSheetData: getCachedSheetData(dbClient),
109-
getCachedTroubleTicketData: getCachedSheetData(dbClient),
110-
cacheSheetData: _cacheSheetData,
111-
cacheTroubleTicketData: _cacheTroubleTicketData,
11278
};
11379
return deps;
11480
};

0 commit comments

Comments
 (0)