server/helpers.ts
Rama main · réplica de lectura del repositorio original.
import type { Response } from 'express';
import semver from 'semver';
import Logger from './lib/logger';
import errors from './shared/errors.json';
import {
currency,
DEFERRED_CARD_TYPE,
FETCH_STATUS_SUCCESS,
formatDate,
maybeHas as has,
INTERNAL_TRANSFER_TYPE,
TRANSFER_TYPE,
MIN_WOOB_VERSION,
NONE_CATEGORY_ID,
shouldIncludeInBalance,
shouldIncludeInOutstandingSum,
TRANSACTION_CARD_TYPE,
translate,
UNKNOWN_ACCOUNT_TYPE,
UNKNOWN_TRANSACTION_TYPE,
UNKNOWN_WOOB_VERSION,
} from './shared/helpers';
export {
currency,
DEFERRED_CARD_TYPE,
FETCH_STATUS_SUCCESS,
formatDate,
has,
INTERNAL_TRANSFER_TYPE,
TRANSFER_TYPE,
MIN_WOOB_VERSION,
NONE_CATEGORY_ID,
shouldIncludeInBalance,
shouldIncludeInOutstandingSum,
TRANSACTION_CARD_TYPE,
translate,
UNKNOWN_ACCOUNT_TYPE,
UNKNOWN_TRANSACTION_TYPE,
UNKNOWN_WOOB_VERSION,
};
export function makeLogger(prefix: string): Logger {
return new Logger(prefix);
}
const log = makeLogger('helpers');
export function panic(wat: string): never {
const text = `Assertion error: ${wat}\n${new Error().stack}`;
log.error(text);
throw new Error(text);
}
export function assert(x: boolean, wat: string): asserts x {
if (!x) {
panic(wat);
}
}
export function unwrap<T>(x: T | null | undefined): T | never {
if (typeof x === 'undefined') {
panic('Expected variable to be defined');
}
if (x === null) {
panic('Expected variable to be not null');
}
return x;
}
export function displayLabel({
label,
customLabel,
}: {
label: string;
customLabel?: string | null;
}): string {
return customLabel || label;
}
export class KError extends Error {
message: string;
shortMessage: string | null;
errCode: string | null;
statusCode: number;
constructor(
msg = 'Internal server error',
statusCode: number | null = 500,
errCode: string | null = null,
shortMessage: string | null = null
) {
super();
this.message = msg;
this.shortMessage = shortMessage;
this.errCode = errCode;
this.stack = Error().stack;
if (statusCode === null) {
switch (errCode) {
case errors.INVALID_PARAMETERS:
case errors.NO_PASSWORD:
case errors.MISSING_MANDATORY_FIELD:
case errors.INVALID_ENCRYPTED_EXPORT:
case errors.INVALID_PASSWORD_JSON_EXPORT:
this.statusCode = 400;
break;
case errors.INVALID_PASSWORD:
this.statusCode = 401;
break;
case errors.ACTION_NEEDED:
case errors.EXPIRED_PASSWORD:
case errors.DISABLED_ACCESS:
this.statusCode = 403;
break;
case errors.WOOB_NOT_INSTALLED:
case errors.GENERIC_EXCEPTION:
case errors.INTERNAL_ERROR:
case errors.NO_ACCOUNTS:
case errors.UNKNOWN_WOOB_MODULE:
case errors.CONNECTION_ERROR:
this.statusCode = 500;
break;
default:
this.statusCode = 500;
break;
}
} else {
this.statusCode = statusCode;
}
}
}
export function getErrorCode(name: string): string | never {
const match: string | undefined = (errors as any)[name];
if (typeof match === 'string') {
return match;
}
throw new KError('Unknown error code!');
}
export function asyncErr(res: Response, err: Error, context: string): false {
let statusCode = 500;
let errCode: string | null = null;
let shortMessage: string | null = null;
if (err instanceof KError) {
statusCode = err.statusCode;
errCode = err.errCode;
shortMessage = err.shortMessage;
}
const { message } = err;
log.error(`${context}: ${message}`);
log.info(err.stack);
res.status(statusCode).send({
code: errCode,
shortMessage,
message,
});
return false;
}
export function errorRequiresUserAction(err: KError): boolean {
return (
err.errCode === getErrorCode('INVALID_PASSWORD') ||
err.errCode === getErrorCode('EXPIRED_PASSWORD') ||
err.errCode === getErrorCode('INVALID_PARAMETERS') ||
err.errCode === getErrorCode('NO_PASSWORD') ||
err.errCode === getErrorCode('MISSING_MANDATORY_FIELD') ||
err.errCode === getErrorCode('ACTION_NEEDED') ||
err.errCode === getErrorCode('REQUIRES_INTERACTIVE')
);
}
// Minimum hour of the day at which the automatic poll can occur.
export const POLLER_START_LOW_HOUR = 2;
// Maximum hour of the day at which the automatic poll can occur.
export const POLLER_START_HIGH_HOUR = 4;
export const isEmailEnabled = (): boolean => {
return !!(
process.kresus.emailFrom &&
process.kresus.emailFrom.length &&
((process.kresus.emailTransport === 'smtp' &&
process.kresus.smtpHost &&
process.kresus.smtpPort) ||
process.kresus.emailTransport === 'sendmail')
);
};
export const isAppriseApiEnabled = (): boolean => {
return !!(process.kresus.appriseApiBaseUrl && process.kresus.appriseApiBaseUrl.length);
};
export function normalizeVersion(version: string | null): string | null {
if (version === null) {
return null;
}
const stringifiedVersion = version.toString();
const cleanedVersion = semver.clean(stringifiedVersion);
if (cleanedVersion !== null) {
return cleanedVersion;
}
if (!/\d/.test(stringifiedVersion)) {
throw new Error(`version should contain numbers: ${version}`);
}
let digits = stringifiedVersion.split('.');
// Eliminate extra digits
digits = digits.slice(0, 3);
// Fill missing digits
while (digits.length < 3) {
digits.push('0');
}
// Replace fully string version with '0'
digits = digits.map(digit => {
if (typeof digit === 'string' && /^\D*$/.test(digit)) {
return '0';
}
return digit;
});
return digits.join('.');
}
export function checkMinimalWoobVersion(version: string | null): boolean {
const actualVersion = normalizeVersion(version);
const expectedVersion = normalizeVersion(MIN_WOOB_VERSION);
return (
actualVersion !== null &&
expectedVersion !== null &&
semver.gte(actualVersion, expectedVersion)
);
}
export function makeUrlPrefixRegExp(urlPrefix: string): RegExp {
return new RegExp(`^${urlPrefix}/?`);
}
export type CurrencyFormatter = (value: number) => string;
const currencyFormatterCache: { [key: string]: CurrencyFormatter } = {};
export function currencyFormatter(someCurrency: string): CurrencyFormatter {
if (typeof currencyFormatterCache[someCurrency] === 'undefined') {
currencyFormatterCache[someCurrency] = currency.makeFormat(someCurrency);
}
return currencyFormatterCache[someCurrency];
}
// Express types a route param as `string | string[]`; in practice a single value is always
// provided, so coerce it to a plain string for parsing.
export function asString(value: string | string[] | undefined): string {
return Array.isArray(value) ? value[0] : (value ?? '');
}