Move in progress apps temporarily

This commit is contained in:
Darren Clarke 2023-03-07 14:09:49 +00:00
parent ba04aa108c
commit 6eaaf8e9be
360 changed files with 6171 additions and 55 deletions

View file

@ -0,0 +1,31 @@
import { ConvictSchema } from "./types";
export interface IAppMetaConfig {
name: string;
version: string;
figletFont: string;
}
export const AppMetaConfig: ConvictSchema<IAppMetaConfig> = {
version: {
doc: "The current application version",
format: String,
env: "npm_package_version",
default: null,
skipGenerate: true,
},
name: {
doc: "Application name",
format: String,
env: "npm_package_name",
default: null,
skipGenerate: true,
},
figletFont: {
doc: "The figlet font name used to print the site name on boot",
format: String,
env: "FIGLET_FONT",
default: "Sub-Zero",
skipGenerate: true,
},
};

View file

@ -0,0 +1,23 @@
import { ConvictSchema } from "./types";
export interface ISessionConfig {
sessionMaxAgeSeconds: number;
sessionUpdateAgeSeconds: number;
}
export const SessionConfig: ConvictSchema<ISessionConfig> = {
sessionMaxAgeSeconds: {
doc: "How long in seconds until an idle session expires and is no longer valid.",
format: "positiveInt",
default: 30 * 24 * 60 * 60, // 30 days
env: "SESSION_MAX_AGE_SECONDS",
},
sessionUpdateAgeSeconds: {
doc: `Throttle how frequently in seconds to write to database to extend a session.
Use it to limit write operations. Set to 0 to always update the database.
Note: This option is ignored if using JSON Web Tokens`,
format: "positiveInt",
default: 24 * 60 * 60, // 24 hours
env: "SESSION_UPDATE_AGE_SECONDS",
},
};

View file

@ -0,0 +1,32 @@
import { ConvictSchema } from "./types";
export interface ICorsConfig {
allowedMethods: Array<string>;
allowedOrigins: Array<string>;
allowedHeaders: Array<string>;
}
export const CorsConfig: ConvictSchema<ICorsConfig> = {
allowedMethods: {
doc: "The allowed CORS methods",
format: "Array",
env: "CORS_ALLOWED_METHODS",
default: ["GET", "PUT", "POST", "PATCH", "DELETE", "HEAD", "OPTIONS"],
},
allowedOrigins: {
doc: "The allowed origins",
format: "Array",
env: "CORS_ALLOWED_ORIGINS",
default: [],
},
allowedHeaders: {
doc: "The allowed headers",
format: "Array",
env: "CORS_ALLOWED_HEADERS",
default: [
"content-type",
"authorization",
"cf-access-authenticated-user-email",
],
},
};

View file

@ -0,0 +1,58 @@
import * as Joi from "joi";
import type { Format } from "convict";
const coerceString = (v: any): string => v.toString();
const validator = (s: any) => (v: any) => Joi.assert(v, s);
const url = Joi.string().uri({
scheme: ["http", "https"],
});
const ip = Joi.string().ip({ version: ["ipv4", "ipv6"], cidr: "optional" });
/**
* Additional configuration value formats for convict.
*
* You can use these to achieve richer validation for your configuration.
*/
export const MetamigoConvictFormats: { [index: string]: Format } = {
positiveInt: {
name: "positveInt",
coerce: (n: string): number => Number.parseInt(n, 10),
validate: validator(Joi.number().positive().integer()),
},
port: {
name: "port",
coerce: (n: string): number => Number.parseInt(n, 10),
validate: validator(Joi.number().port()),
},
ipaddress: {
name: "ipaddress",
coerce: coerceString,
validate: validator(ip),
},
url: {
name: "url",
coerce: coerceString,
validate: validator(url),
},
uri: {
name: "uri",
coerce: coerceString,
validate: validator(Joi.string().uri()),
},
optionalUri: {
name: "uri",
coerce: coerceString,
validate: validator(Joi.string().uri().allow("")),
},
email: {
name: "email",
coerce: coerceString,
validate: validator(Joi.string().email()),
},
uuid: {
name: "uuid",
coerce: coerceString,
validate: validator(Joi.string().guid()),
},
};

View file

@ -0,0 +1,44 @@
import convict from "convict";
const visitLeaf = (acc: any, key: any, leaf: any) => {
if (leaf.skipGenerate) {
return;
}
if (leaf.default === undefined) {
acc[key] = undefined;
} else {
acc[key] = leaf.default;
}
};
const visitNode = (acc: any, node: any, key = "") => {
if (node._cvtProperties) {
const keys = Object.keys(node._cvtProperties);
let subacc: any;
if (key === "") {
subacc = acc;
} else {
subacc = {};
acc[key] = subacc;
}
keys.forEach((key) => {
visitNode(subacc, node._cvtProperties[key], key);
});
// In the case that the entire sub-tree specified skipGenerate, remove the empty node
if (Object.keys(subacc).length === 0) {
delete acc[key];
}
} else {
visitLeaf(acc, key, node);
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const generateConfig = (conf: convict.Config<any>): unknown => {
const schema = conf.getSchema();
const generated = {};
visitNode(generated, schema);
return JSON.stringify(generated, undefined, 1);
};

View file

@ -0,0 +1,142 @@
import process from "process";
import convict, { SchemaObj } from "convict";
import { IServerConfig, ServerConfig } from "./server";
import { IMetricsConfig, MetricsConfig } from "./metrics-server";
import { IAppMetaConfig, AppMetaConfig } from "./app-meta";
import { ICorsConfig, CorsConfig } from "./cors";
import { ILoggingConfig, LoggingConfig } from "./logging";
import { ExtendedConvict } from "./types";
import { MetamigoConvictFormats } from "./formats";
type IEnvConfig = "production" | "development" | "test";
const EnvConfig: SchemaObj<IEnvConfig> = {
doc: "The application environment",
format: ["production", "development", "test"],
default: "development",
env: "NODE_ENV",
};
export const configBaseSchema = {
env: EnvConfig,
server: ServerConfig,
meta: AppMetaConfig,
cors: CorsConfig,
metrics: MetricsConfig,
logging: LoggingConfig,
};
/**
*
* The metamigo base configuration object. Use this for easy typed access to your
* config.
*
*/
interface IMetamigoConfig {
env: IEnvConfig;
server: IServerConfig;
meta: IAppMetaConfig;
cors: ICorsConfig;
metrics: IMetricsConfig;
logging: ILoggingConfig;
isProd?: boolean;
isTest?: boolean;
isDev?: boolean;
frontend: any;
nextAuth: any;
}
export type IMetamigoConvict = ExtendedConvict<IMetamigoConfig>;
export type {
IServerConfig,
IMetricsConfig,
IAppMetaConfig,
ICorsConfig,
ILoggingConfig,
IMetamigoConfig,
};
export * from "./formats";
export * from "./generate";
export * from "./print";
export * from "./types";
/**
* Loads your applications configuration from environment variables and configuration files (see METAMIGO_CONFIG).
*
* @param schema your schema definition
* @param override an optional object with config value that will override defaults but not config files and env vars (see [convict precedence docs](https://github.com/mozilla/node-convict/tree/master/packages/convict#precedence-order ))
* @returns the raw convict config object
*/
export const loadConfigurationRaw = async <T extends IMetamigoConfig>(
schema: convict.Schema<T>,
override?: Partial<T>
): Promise<ExtendedConvict<T>> => {
convict.addFormats(MetamigoConvictFormats);
const config: ExtendedConvict<T> = convict(schema);
const env = config.get("env");
config.isProd = env === "production";
config.isTest = env === "test";
config.isDev = env === "development";
try {
if (process.env.METAMIGO_CONFIG) {
config.loadFile(process.env.METAMIGO_CONFIG);
}
} catch (error) {
const msg = `
🚫 Your application's configuration is invalid JSON. 🚫
${error}
`;
throw new Error(msg);
}
if (override) {
config.load(override);
}
try {
config.validate({ allowed: "strict" });
} catch (error: any) {
const msg = `
🚫 Your application's configuration is invalid. 🚫
${error.message}
`;
throw new Error(msg);
}
// set our helpers
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const configDirty = config as any;
configDirty.set("isProd", config.isProd);
configDirty.set("isTest", config.isTest);
configDirty.set("isDev", config.isDev);
return config;
};
/**
* Loads your applications configuration from environment variables and configuration files (see METAMIGO_CONFIG).
*
* @param schema your schema definition
* @param override an optional object with config value that will override defaults but not config files and env vars (see [convict precedence docs](https://github.com/mozilla/node-convict/tree/master/packages/convict#precedence-order ))
* @returns a vanilla javascript object with the config loaded values
*/
export const loadConfiguration = async <T extends IMetamigoConfig>(
schema: convict.Schema<T>,
override?: Partial<T>
): Promise<T> => {
const c = await loadConfigurationRaw(schema, override);
return c.getProperties();
};

View file

@ -0,0 +1,90 @@
import { ConvictSchema } from "./types";
export interface ILoggingConfig {
level: string;
sql: boolean;
redact: string[];
ignorePaths: string[];
ignoreTags: string[];
requestIdHeader: string;
logRequestStart: boolean;
logRequestComplete: boolean;
logRequestPayload: boolean;
logRequestQueryParams: boolean;
prettyPrint: boolean | "auto";
}
export const LoggingConfig: ConvictSchema<ILoggingConfig> = {
level: {
doc: "The logging level",
format: ["trace", "debug", "info", "warn", "error"],
default: "info",
env: "LOG_LEVEL",
},
sql: {
doc: "Whether to log sql statements",
format: "Boolean",
default: false,
env: "LOG_SQL",
},
redact: {
doc: "Pino redaction array. These are always redacted. see https://getpino.io/#/docs/redaction",
format: "Array",
default: [
"req.remoteAddress",
"req.headers.authorization",
`req.headers["cf-access-jwt-assertion"]`,
`req.headers["cf-access-authenticated-user-email"]`,
`req.headers["cf-connecting-ip"]`,
`req.headers["cf-ipcountry"]`,
`req.headers["x-forwarded-for"]`,
"req.headers.cookie",
],
},
ignorePaths: {
doc: "Ignore http paths (exact) when logging requests",
format: "Array",
default: ["/graphql"],
},
ignoreTags: {
doc: "Ignore routes tagged with these tags when logging requests",
format: "Array",
default: ["status", "swagger", "nolog"],
},
requestIdHeader: {
doc: "The header where the request id lives",
format: String,
default: "x-request-id",
env: "REQUEST_ID_HEADER",
},
logRequestStart: {
doc: "Whether hapi-pino should add a log.info() at the beginning of Hapi requests for the given Request.",
format: "Boolean",
default: false,
env: "LOG_REQUEST_START",
},
logRequestComplete: {
doc: "Whether hapi-pino should add a log.info() at the completion of Hapi requests for the given Request.",
format: "Boolean",
default: true,
env: "LOG_REQUEST_COMPLETE",
},
logRequestPayload: {
doc: "When enabled, add the request payload as payload to the response event log.",
format: "Boolean",
default: false,
env: "LOG_REQUEST_PAYLOAD",
},
logRequestQueryParams: {
doc: "When enabled, add the request query as queryParams to the response event log.",
format: "Boolean",
default: false,
env: "LOG_REQUEST_QUERY_PARAMS",
},
prettyPrint: {
doc: "Pretty print the logs",
format: ["auto", true, false],
default: "auto",
env: "LOG_PRETTY_PRINT",
},
};

View file

@ -0,0 +1,22 @@
import { ConvictSchema } from "./types";
export interface IMetricsConfig {
address: string;
port: number;
}
export const MetricsConfig: ConvictSchema<IMetricsConfig> = {
address: {
doc: "The ip address to bind the prometheus metrics to",
format: "ipaddress",
default: "127.0.0.1",
env: "METRICS_ADDRESS",
},
port: {
doc: "The port to bind the prometheus metrics to",
format: "port",
default: 3002,
env: "METRICS_PORT",
arg: "port",
},
};

View file

@ -0,0 +1,41 @@
import chalk from "chalk";
import convict from "convict";
const visitLeaf = (path: any, key: any, leaf: any) => {
if (leaf.skipGenerate) {
return;
}
let name = `${path}.${key}`;
if (path.length === 0) name = key;
console.log(chalk.green(name));
console.log(leaf.doc);
if (leaf.default === undefined) {
console.log(chalk.red("\t required"));
} else {
console.log(`\tdefault: ${JSON.stringify(leaf.default)}`);
}
console.log(`\tformat: ${leaf.format}`);
console.log(`\tenv: ${leaf.env}`);
};
const visitNode = (path: any, node: any, key = "") => {
if (node._cvtProperties) {
const keys = Object.keys(node._cvtProperties);
const subpath = key === "" ? path : `${key}`;
keys.forEach((key) => {
visitNode(subpath, node._cvtProperties[key], key);
});
console.log();
} else {
visitLeaf(path, key, node);
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const printConfigOptions = (conf: convict.Config<any>): void => {
const schema = conf.getSchema();
visitNode("", schema);
};

View file

@ -0,0 +1,21 @@
import { ConvictSchema } from "./types";
export interface IServerConfig {
address: string;
port: number;
}
export const ServerConfig: ConvictSchema<IServerConfig> = {
address: {
doc: "The IP address to bind the server to",
format: "ipaddress",
default: "0.0.0.0",
env: "SERVER_ADDRESS",
},
port: {
doc: "The port to bind the server to",
format: "port",
default: 3001,
env: "SERVER_PORT",
},
};

View file

@ -0,0 +1,26 @@
import convict from "convict";
/*
interface SSMObj {
path: string;
}
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
interface ConvictSchemaObj<T = any> extends convict.SchemaObj<T> {
// ssm?: SSMObj;
/**
* The config item will be ignored for purposes of config file generation
*/
skipGenerate?: boolean;
}
export type ConvictSchema<T> = {
[P in keyof T]: convict.Schema<T[P]> | ConvictSchemaObj<T[P]>;
};
export interface ExtendedConvict<T> extends convict.Config<T> {
isProd?: boolean;
isTest?: boolean;
isDev?: boolean;
}