fix: typing/dependecy/validation fixes, installed ts-reset & zod, fixed responses never being sent on invalid query params

This commit is contained in:
Benjamin Singleton 2025-09-13 13:16:23 -05:00 committed by Sheldon Cooper
parent ece6deface
commit cc4bef8ec4
4 changed files with 211 additions and 203 deletions

View file

@ -20,17 +20,19 @@
"packageManager": "pnpm@10.11.0",
"dependencies": {
"@feathersjs/feathers": "^5.0.6",
"@google-cloud/local-auth": "^2.1.0",
"@google-cloud/local-auth": "^3.0.1",
"@repo/common": "workspace:*",
"express": "^5.1.0",
"google-auth-library": "^10.3.0",
"googleapis": "^159.0.0",
"google-auth-library": "^9.15.1",
"googleapis": "^149.0.0",
"node-fetch": "^3.3.2",
"reflect-metadata": "^0.2.2",
"sqlite3": "^5.1.7",
"typeorm": "0.3.26"
"typeorm": "0.3.26",
"zod": "^4.1.8"
},
"devDependencies": {
"@total-typescript/ts-reset": "^0.6.1",
"@types/express": "^5.0.3",
"@types/node": "^22.5.1",
"tsx": "^4.19.4"

View file

@ -1,62 +1,114 @@
import { promises as fs } from "fs";
import fetch from 'node-fetch';
import * as path from "path";
import { authenticate } from "@google-cloud/local-auth";
import { google } from "googleapis";
import { OAuth2Client } from 'google-auth-library';
import { JWTInput, OAuth2Client } from "google-auth-library";
import {
GoogleAuth,
JSONClient,
} from "google-auth-library/build/src/auth/googleauth.js";
import z from "zod";
//setup google auth
const SCOPES = ["https://www.googleapis.com/auth/spreadsheets.readonly"];
const TOKEN_PATH = path.join(process.cwd(), "res/token.secret.json");
const CREDENTIALS_PATH = path.join(process.cwd(), "res/credentials.secret.json");
const CREDENTIALS_PATH = path.join(
process.cwd(),
"res/credentials.secret.json",
);
/**
* Load or request or authorization to call APIs.
*/
*/
export async function authorize(): Promise<
GoogleAuth<JSONClient> | OAuth2Client
> {
const savedClient = await loadSavedCredentialsIfExist();
if (savedClient) {
return savedClient;
}
export async function authorize() {
let client: any = await loadSavedCredentialsIfExist();
if (client) {
return client;
}
client = await authenticate({
scopes: SCOPES,
keyfilePath: CREDENTIALS_PATH,
});
if (client.credentials) {
await saveCredentials(client);
}
return client;
const oauthClient = await authenticate({
scopes: SCOPES,
keyfilePath: CREDENTIALS_PATH,
});
if (oauthClient.credentials) {
await saveCredentials(oauthClient);
}
return oauthClient;
}
async function loadSavedCredentialsIfExist() {
try {
const content = await fs.readFile(TOKEN_PATH);
const credentials = JSON.parse(content.toString());
return google.auth.fromJSON(credentials);
} catch (err) {
return null;
}
const jwtInputZod = z.object({
type: z.string(),
client_id: z.string(),
client_secret: z.string(),
refresh_token: z.string(),
}) satisfies z.ZodType<JWTInput>;
async function loadSavedCredentialsIfExist(): Promise<GoogleAuth<JSONClient> | null> {
try {
const content = await fs.readFile(TOKEN_PATH);
const jwtInputRaw = JSON.parse(content.toString());
const jwtInputRes = jwtInputZod.safeParse(jwtInputRaw);
if (!jwtInputRes.success) {
throw new Error("Malformed JWT/token credentials");
}
const jwtInput = jwtInputRes.data;
const jsonClient = google.auth.fromJSON(jwtInput);
const auth = new GoogleAuth({ authClient: jsonClient });
return auth;
} catch (err) {
return null;
}
}
const credentialsZod = z.object({
installed: z.object({
client_id: z.string(),
project_id: z.string(),
auth_uri: z.string(),
token_uri: z.string(),
auth_provider_x509_cert_url: z.string(),
client_secret: z.string(),
redirect_uris: z.array(z.string()),
}),
web: z.optional(
z.object({
client_id: z.string(),
project_id: z.string(),
auth_uri: z.string(),
token_uri: z.string(),
auth_provider_x509_cert_url: z.string(),
client_secret: z.string(),
redirect_uris: z.array(z.string()),
}),
),
});
/**
* Serializes credentials to a file compatible with GoogleAuth.fromJSON.
*
* @param {OAuth2Client} client
* @return {Promise<void>}
*/
async function saveCredentials(client: OAuth2Client) {
const content = await fs.readFile(CREDENTIALS_PATH);
const keys = JSON.parse(content.toString());
const key = keys.installed || keys.web;
const payload = JSON.stringify({
type: "authorized_user",
client_id: key.client_id,
client_secret: key.client_secret,
refresh_token: client.credentials.refresh_token,
});
await fs.writeFile(TOKEN_PATH, payload);
}
async function saveCredentials(client: OAuth2Client): Promise<void> {
const content = await fs.readFile(CREDENTIALS_PATH);
const keysRaw = JSON.parse(content.toString());
const keysRes = credentialsZod.safeParse(keysRaw);
if (!keysRes.success) {
throw new Error("Malformed credentials/keys");
}
const keys = keysRes.data;
const key = keys.installed ?? keys.web;
const payload = JSON.stringify({
type: "authorized_user",
client_id: key.client_id,
client_secret: key.client_secret,
refresh_token: client.credentials.refresh_token,
});
await fs.writeFile(TOKEN_PATH, payload);
}

View file

@ -6,16 +6,12 @@ import { google, sheets_v4 } from "googleapis";
import { OAuth2Client } from "google-auth-library";
import { appDataSource } from "./config/dbconfig.js";
import { authorize } from "./auth.js";
import { Lemma, WordForm, Lect } from "./db/dbmodel.js";
import {
Lemma,
WordForm,
Example,
Media,
Definition,
Comment,
PartOfSpeech,
Lect,
} from "./db/dbmodel.js";
GoogleAuth,
JSONClient,
} from "google-auth-library/build/src/auth/googleauth.js";
import "@total-typescript/ts-reset";
const RELOAD_SHEET_ON_START = false;
@ -56,7 +52,7 @@ function initExpress() {
const search_term = req.query.search_term?.toString();
if (!search_term) {
return;
return void res.sendStatus(400);
}
const lemmas: Lemma[] = (
@ -81,21 +77,21 @@ function initExpress() {
const name = req.query.name?.toString();
if (!name) {
return;
return void res.sendStatus(400);
}
const lect = await Lect.findOne({
where: { name: name },
relations: { word_forms: { lemma: true } },
});
res.status(200).send({ lect });
});
app.get("/lects", async (req, res) => {
const lects = await Lect.find()
res.status(200).send({
lects
app.get("/lects", async (_req, res) => {
const lects = await Lect.find();
res.status(200).send({
lects,
});
});
@ -106,13 +102,13 @@ function initExpress() {
}
/**
* @param {google.auth.OAuth2Client} auth The authenticated Google OAuth client.
* @param auth The authenticated Google OAuth client.
*/
async function loadSheet(auth: OAuth2Client) {
async function loadSheet(auth: OAuth2Client | GoogleAuth<JSONClient>) {
const lect_repository = appDataSource.getRepository(Lect);
const word_form_repository = appDataSource.getRepository(WordForm);
const lemma_repository = appDataSource.getRepository(Lemma);
const options: any = { version: "v4", auth };
const options: sheets_v4.Options = { version: "v4", auth };
const sheets = google.sheets(options);
const res = await sheets.spreadsheets.values.get({
spreadsheetId: "1-YkCeynx_-KYdubvt14augSPo37_20YgUv_f-i8HVwY",
@ -181,9 +177,9 @@ async function loadSheet(auth: OAuth2Client) {
for (let i = 0; i < rows.length; i++) {
const cell = row?.[i];
if (
cell === null
|| cell === undefined
|| (typeof cell === "string" && cell.length === 0)
cell === null ||
cell === undefined ||
(typeof cell === "string" && cell.length === 0)
) {
continue;
}
@ -201,7 +197,9 @@ async function loadSheet(auth: OAuth2Client) {
for (let i = 0; i < lemmas.length; i += 100) {
await lemma_repository.save(lemmas.slice(i, i + 100));
console.log(
`Saved ${Math.min(i + 100, lemmas.length)} / ${lemmas.length} lemmas...`,
`Saved ${Math.min(i + 100, lemmas.length)} / ${
lemmas.length
} lemmas...`,
);
}