From 773439d2e0d4020ddda1fac09eec7b6f846f64ba Mon Sep 17 00:00:00 2001 From: Daniel Flanagan Date: Thu, 10 Nov 2022 10:25:35 -0600 Subject: [PATCH] Configuration pedantry --- config.ts | 56 +++++++++++++++++++++++++++++++++------------ db/mod.ts | 14 ++++++++++++ log.ts | 12 ++++++---- routes/register.tsx | 1 - 4 files changed, 62 insertions(+), 21 deletions(-) diff --git a/config.ts b/config.ts index 19560fc..08ffdae 100644 --- a/config.ts +++ b/config.ts @@ -9,6 +9,7 @@ export interface LogConfig { export interface PostgresConfig { url: string; + poolSize: number; } export interface MailgunConfig { @@ -28,8 +29,12 @@ function truthyEnv(key: string): boolean { ["true", "1"]; } -function isLogLevelName(s: string): s is LevelName { - return s in LogLevels; +function toLogLevelName(s: string): LevelName | undefined { + if (s in LogLevels) { + return s as LevelName; + } else { + return undefined; + } } export let config: Readonly; @@ -45,7 +50,7 @@ export function reload(): [ ((logger: Logger) => void)[], ] { // since we want configuration to be setup before any logging, lets save logs here until the logger is setup - const logCalls = []; + const logCalls: ((logger: Logger) => void)[] = []; const envOrWarn = (key: string, fallback: string): string => { const val = Deno.env.get(key); @@ -57,28 +62,49 @@ export function reload(): [ return val || fallback; }; - const desiredLogLevel = envOrWarn("LOG_LEVEL", "INFO").toUpperCase(); - if (!isLogLevelName(desiredLogLevel)) { - logCalls.push((logger: Logger) => - logger.warning( - `Specified LOG_LEVEL '${desiredLogLevel}' is invalid. Falling back to INFO`, - ) - ); - } + const envToIntOrWarn = (key: string, fallback: number): number => { + const ev = envOrWarn(key, fallback.toString()); + let result = parseInt(ev); + if (isNaN(result)) { + logCalls.push((logger: Logger) => + logger.warning( + `Specified ${key} '${ev}' is not a valid integer. Falling back to ${fallback}.`, + ) + ); + result = fallback; + } + return result; + }; - const logLevel: LevelName = isLogLevelName(desiredLogLevel) - ? desiredLogLevel - : "INFO"; + const envToEnumOrWarn = ( + key: string, + converter: (s: string) => T | undefined, + fallback: T, + ): T => { + const ev = envOrWarn(key, `${fallback}`); + const result = converter(ev); + if (result) { + return result; + } else { + logCalls.push((logger: Logger) => + logger.warning( + `Specified ${key} '${ev}' is invalid. Falling back to ${fallback}`, + ) + ); + return fallback; + } + }; const config: Config = { log: { - consoleLevelName: logLevel, + consoleLevelName: envToEnumOrWarn("LOG_LEVEL", toLogLevelName, "INFO"), }, postgres: { url: envOrWarn( "POSTGRES_URL", "postgresql://postgres:@127.0.0.1:5432/lyricscreen", ), + poolSize: envToIntOrWarn("POSTGRES_POOL_SIZE", 3), }, mailgun: {}, isDevelopmentMode: truthyEnv("DEVELOPMENT_MODE"), diff --git a/db/mod.ts b/db/mod.ts index 7d74ee7..e97c93f 100644 --- a/db/mod.ts +++ b/db/mod.ts @@ -35,6 +35,18 @@ let pool: Pool; export function initDatabaseConnectionPool({ url }: PostgresConfig) { pool = new Pool(url, 3, true); + testDbConnection(); +} + +export async function testDbConnection(): Promise { + try { + await dbOp((conn) => conn.queryObject("select 1")); + log.info("Successfully connected to database"); + return true; + } catch (e) { + log.critical("Failed to connect to database:", e); + return false; + } } type QueryResult = { rows: T[] } | null; @@ -383,6 +395,8 @@ export async function getUser( } } +// TODO: refresh token? + export async function getUserFromNonExpiredLoginToken( token: TokenDigest, ): Promise { diff --git a/log.ts b/log.ts index 18dbae4..3ed95a4 100644 --- a/log.ts +++ b/log.ts @@ -42,19 +42,21 @@ class CustomConsoleHandler extends log.handlers.ConsoleHandler { } } -export function setupLoggers(config: LogConfig) { - // TODO: support for colors or nah? +export function setupLoggers(config: LogConfig, redact?: RegExp) { + // TODO: check support for colors and adjust as needed log.setup({ handlers: { console: new CustomConsoleHandler( config.consoleLevelName, { formatter: ({ level, datetime, msg, args }: log.LogRecord) => { - // TODO: use a replacer for redacting secrets? - const fullMessage = format( + let fullMessage = format( msg, - args, + ...args, ); + if (redact) { + fullMessage = fullMessage.replaceAll(redact, ""); + } return `\x1b[m[\x1b[${levelColors[level] || ""}m${ short[level] || "UNK" }\x1b[m] \x1b[90m${datetime.toISOString()}\x1b[m \x1b[${ diff --git a/routes/register.tsx b/routes/register.tsx index 1ef1adf..0d4416e 100644 --- a/routes/register.tsx +++ b/routes/register.tsx @@ -13,7 +13,6 @@ export const handler: Handlers = { const formData = (await request.formData()); const username = formData.get("username"); const password = formData.get("password"); - // TODO: verify that username conforms to some regex? no spaces? if (!username) { return await context.render({ message: "no username provided" }); }