Configuration pedantry
This commit is contained in:
parent
e5fca7a476
commit
773439d2e0
46
config.ts
46
config.ts
|
@ -9,6 +9,7 @@ export interface LogConfig {
|
||||||
|
|
||||||
export interface PostgresConfig {
|
export interface PostgresConfig {
|
||||||
url: string;
|
url: string;
|
||||||
|
poolSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MailgunConfig {
|
export interface MailgunConfig {
|
||||||
|
@ -28,8 +29,12 @@ function truthyEnv(key: string): boolean {
|
||||||
["true", "1"];
|
["true", "1"];
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLogLevelName(s: string): s is LevelName {
|
function toLogLevelName(s: string): LevelName | undefined {
|
||||||
return s in LogLevels;
|
if (s in LogLevels) {
|
||||||
|
return s as LevelName;
|
||||||
|
} else {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export let config: Readonly<Config>;
|
export let config: Readonly<Config>;
|
||||||
|
@ -45,7 +50,7 @@ export function reload(): [
|
||||||
((logger: Logger) => void)[],
|
((logger: Logger) => void)[],
|
||||||
] {
|
] {
|
||||||
// since we want configuration to be setup before any logging, lets save logs here until the logger is setup
|
// 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 envOrWarn = (key: string, fallback: string): string => {
|
||||||
const val = Deno.env.get(key);
|
const val = Deno.env.get(key);
|
||||||
|
@ -57,28 +62,49 @@ export function reload(): [
|
||||||
return val || fallback;
|
return val || fallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
const desiredLogLevel = envOrWarn("LOG_LEVEL", "INFO").toUpperCase();
|
const envToIntOrWarn = (key: string, fallback: number): number => {
|
||||||
if (!isLogLevelName(desiredLogLevel)) {
|
const ev = envOrWarn(key, fallback.toString());
|
||||||
|
let result = parseInt(ev);
|
||||||
|
if (isNaN(result)) {
|
||||||
logCalls.push((logger: Logger) =>
|
logCalls.push((logger: Logger) =>
|
||||||
logger.warning(
|
logger.warning(
|
||||||
`Specified LOG_LEVEL '${desiredLogLevel}' is invalid. Falling back to INFO`,
|
`Specified ${key} '${ev}' is not a valid integer. Falling back to ${fallback}.`,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
result = fallback;
|
||||||
}
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
const logLevel: LevelName = isLogLevelName(desiredLogLevel)
|
const envToEnumOrWarn = <T extends { toString(self: T): string }>(
|
||||||
? desiredLogLevel
|
key: string,
|
||||||
: "INFO";
|
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 = {
|
const config: Config = {
|
||||||
log: {
|
log: {
|
||||||
consoleLevelName: logLevel,
|
consoleLevelName: envToEnumOrWarn("LOG_LEVEL", toLogLevelName, "INFO"),
|
||||||
},
|
},
|
||||||
postgres: {
|
postgres: {
|
||||||
url: envOrWarn(
|
url: envOrWarn(
|
||||||
"POSTGRES_URL",
|
"POSTGRES_URL",
|
||||||
"postgresql://postgres:@127.0.0.1:5432/lyricscreen",
|
"postgresql://postgres:@127.0.0.1:5432/lyricscreen",
|
||||||
),
|
),
|
||||||
|
poolSize: envToIntOrWarn("POSTGRES_POOL_SIZE", 3),
|
||||||
},
|
},
|
||||||
mailgun: {},
|
mailgun: {},
|
||||||
isDevelopmentMode: truthyEnv("DEVELOPMENT_MODE"),
|
isDevelopmentMode: truthyEnv("DEVELOPMENT_MODE"),
|
||||||
|
|
14
db/mod.ts
14
db/mod.ts
|
@ -35,6 +35,18 @@ let pool: Pool;
|
||||||
|
|
||||||
export function initDatabaseConnectionPool({ url }: PostgresConfig) {
|
export function initDatabaseConnectionPool({ url }: PostgresConfig) {
|
||||||
pool = new Pool(url, 3, true);
|
pool = new Pool(url, 3, true);
|
||||||
|
testDbConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function testDbConnection(): Promise<boolean> {
|
||||||
|
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<T> = { rows: T[] } | null;
|
type QueryResult<T> = { rows: T[] } | null;
|
||||||
|
@ -383,6 +395,8 @@ export async function getUser(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: refresh token?
|
||||||
|
|
||||||
export async function getUserFromNonExpiredLoginToken(
|
export async function getUserFromNonExpiredLoginToken(
|
||||||
token: TokenDigest,
|
token: TokenDigest,
|
||||||
): Promise<User> {
|
): Promise<User> {
|
||||||
|
|
12
log.ts
12
log.ts
|
@ -42,19 +42,21 @@ class CustomConsoleHandler extends log.handlers.ConsoleHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupLoggers(config: LogConfig) {
|
export function setupLoggers(config: LogConfig, redact?: RegExp) {
|
||||||
// TODO: support for colors or nah?
|
// TODO: check support for colors and adjust as needed
|
||||||
log.setup({
|
log.setup({
|
||||||
handlers: {
|
handlers: {
|
||||||
console: new CustomConsoleHandler(
|
console: new CustomConsoleHandler(
|
||||||
config.consoleLevelName,
|
config.consoleLevelName,
|
||||||
{
|
{
|
||||||
formatter: ({ level, datetime, msg, args }: log.LogRecord) => {
|
formatter: ({ level, datetime, msg, args }: log.LogRecord) => {
|
||||||
// TODO: use a replacer for redacting secrets?
|
let fullMessage = format(
|
||||||
const fullMessage = format(
|
|
||||||
msg,
|
msg,
|
||||||
args,
|
...args,
|
||||||
);
|
);
|
||||||
|
if (redact) {
|
||||||
|
fullMessage = fullMessage.replaceAll(redact, "<REDACTED>");
|
||||||
|
}
|
||||||
return `\x1b[m[\x1b[${levelColors[level] || ""}m${
|
return `\x1b[m[\x1b[${levelColors[level] || ""}m${
|
||||||
short[level] || "UNK"
|
short[level] || "UNK"
|
||||||
}\x1b[m] \x1b[90m${datetime.toISOString()}\x1b[m \x1b[${
|
}\x1b[m] \x1b[90m${datetime.toISOString()}\x1b[m \x1b[${
|
||||||
|
|
|
@ -13,7 +13,6 @@ export const handler: Handlers<UserId | RegistrationError | null> = {
|
||||||
const formData = (await request.formData());
|
const formData = (await request.formData());
|
||||||
const username = formData.get("username");
|
const username = formData.get("username");
|
||||||
const password = formData.get("password");
|
const password = formData.get("password");
|
||||||
// TODO: verify that username conforms to some regex? no spaces?
|
|
||||||
if (!username) {
|
if (!username) {
|
||||||
return await context.render({ message: "no username provided" });
|
return await context.render({ message: "no username provided" });
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue