feat(config): make the config as a singleton class overridable by a json config

This commit is contained in:
Nicolas Meienberger 2022-09-20 08:57:08 +02:00
parent b3f143da57
commit ae2dd8d364
7 changed files with 126 additions and 12 deletions

View file

@ -60,7 +60,6 @@ services:
INTERNAL_IP: ${INTERNAL_IP}
TIPI_VERSION: ${TIPI_VERSION}
JWT_SECRET: ${JWT_SECRET}
ROOT_FOLDER_HOST: ${ROOT_FOLDER_HOST}
NGINX_PORT: ${NGINX_PORT}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_USERNAME: tipi

View file

@ -56,7 +56,8 @@
"type-graphql": "^1.1.1",
"typeorm": "^0.3.6",
"validator": "^13.7.0",
"winston": "^3.7.2"
"winston": "^3.7.2",
"zod": "^3.19.1"
},
"devDependencies": {
"@faker-js/faker": "^7.3.0",

View file

@ -0,0 +1,102 @@
import { z } from 'zod';
import * as dotenv from 'dotenv';
import fs from 'fs-extra';
import config from '../../config';
import { readJsonFile } from '../../modules/fs/fs.helpers';
if (process.env.NODE_ENV !== 'production') {
dotenv.config({ path: '.env.dev' });
} else {
dotenv.config({ path: '.env' });
}
const {
LOGS_FOLDER = 'logs',
LOGS_APP = 'app.log',
LOGS_ERROR = 'error.log',
NODE_ENV = 'development',
JWT_SECRET = '',
INTERNAL_IP = '',
TIPI_VERSION = '',
NGINX_PORT = '80',
APPS_REPO_ID = '',
APPS_REPO_URL = '',
DOMAIN = '',
} = process.env;
const configSchema = z.object({
NODE_ENV: z.string(),
repo: z.string(),
logs: z.object({
LOGS_FOLDER: z.string(),
LOGS_APP: z.string(),
LOGS_ERROR: z.string(),
}),
rootFolder: z.string(),
internalIp: z.string(),
version: z.string(),
jwtSecret: z.string(),
clientUrls: z.array(z.string()),
appsRepoId: z.string(),
appsRepoUrl: z.string(),
domain: z.string(),
});
class Config {
private static instance: Config;
private config: z.infer<typeof configSchema>;
constructor() {
const fileConfig = readJsonFile('/tipi/state/settings.json');
const envConfig: z.infer<typeof configSchema> = {
logs: {
LOGS_FOLDER,
LOGS_APP,
LOGS_ERROR,
},
NODE_ENV,
repo: APPS_REPO_URL,
rootFolder: '/tipi',
internalIp: INTERNAL_IP,
version: TIPI_VERSION,
jwtSecret: JWT_SECRET,
clientUrls: ['http://localhost:3000', `http://${INTERNAL_IP}`, `http://${INTERNAL_IP}:${NGINX_PORT}`, `http://${INTERNAL_IP}:3000`, `https://${DOMAIN}`],
appsRepoId: APPS_REPO_ID,
appsRepoUrl: APPS_REPO_URL,
domain: DOMAIN,
};
const parsed = configSchema.parse({
...envConfig,
...fileConfig,
});
this.config = parsed;
}
public static getInstance(): Config {
if (!Config.instance) {
Config.instance = new Config();
}
return Config.instance;
}
public getConfig() {
return this.config;
}
public setConfig(key: keyof typeof configSchema.shape, value: any) {
const newConf = { ...this.getConfig() };
newConf[key] = value;
this.config = configSchema.parse(newConf);
fs.writeFileSync(`${config.ROOT_FOLDER}/state/settings.json`, JSON.stringify(newConf));
}
}
export const setConfig = (key: keyof typeof configSchema.shape, value: any) => {
Config.getInstance().setConfig(key, value);
};
export const getConfig = () => Config.getInstance().getConfig();

View file

@ -57,7 +57,7 @@ export const checkEnvFile = (appName: string) => {
export const runAppScript = async (params: string[]): Promise<void> => {
return new Promise((resolve, reject) => {
runScript('/scripts/app.sh', [...params, config.ROOT_FOLDER_HOST, config.APPS_REPO_ID], (err: string) => {
runScript('/scripts/app.sh', [...params], (err: string) => {
if (err) {
logger.error(err);
reject(err);

View file

@ -5,6 +5,7 @@ import config from '../../config';
export const getAbsolutePath = (path: string) => `${config.ROOT_FOLDER}${path}`;
export const readJsonFile = (path: string): any => {
try {
const rawFile = fs.readFileSync(getAbsolutePath(path))?.toString();
if (!rawFile) {
@ -12,6 +13,9 @@ export const readJsonFile = (path: string): any => {
}
return JSON.parse(rawFile);
} catch (e) {
return null;
}
};
export const readFile = (path: string): string => {

View file

@ -1,7 +1,6 @@
import 'reflect-metadata';
import express from 'express';
import { ApolloServerPluginLandingPageGraphQLPlayground as Playground } from 'apollo-server-core';
import config from './config';
import { ApolloServer } from 'apollo-server-express';
import { createSchema } from './schema';
import { ApolloLogs } from './config/logger/apollo.logger';
@ -17,6 +16,9 @@ import { runUpdates } from './core/updates/run';
import recover from './core/updates/recover-migrations';
import { cloneRepo, updateRepo } from './helpers/repo-helpers';
import startJobs from './core/jobs/jobs';
import { getConfig } from './core/config/TipiConfig';
const { clientUrls, rootFolder, appsRepoId, appsRepoUrl } = getConfig();
let corsOptions = {
credentials: true,
@ -27,7 +29,7 @@ let corsOptions = {
// disallow requests with no origin
if (!origin) return callback(new Error('Not allowed by CORS'), false);
if (config.CLIENT_URLS.includes(origin)) {
if (clientUrls.includes(origin)) {
return callback(null, true);
}
@ -41,7 +43,7 @@ const main = async () => {
const app = express();
const port = 3001;
app.use(express.static(`${config.ROOT_FOLDER}/repos/${config.APPS_REPO_ID}`));
app.use(express.static(`${rootFolder}/repos/${appsRepoId}`));
app.use(cors(corsOptions));
app.use(getSessionMiddleware());
@ -75,8 +77,8 @@ const main = async () => {
await runUpdates();
httpServer.listen(port, async () => {
await cloneRepo(config.APPS_REPO_URL);
await updateRepo(config.APPS_REPO_URL);
await cloneRepo(appsRepoUrl);
await updateRepo(appsRepoId);
startJobs();
// Start apps
appsService.startAllApps();

View file

@ -193,6 +193,7 @@ importers:
typescript: 4.6.4
validator: ^13.7.0
winston: ^3.7.2
zod: ^3.19.1
dependencies:
apollo-server-core: 3.10.0_graphql@15.8.0
apollo-server-express: 3.9.0_jfj6k5cqxqbusbdzwqjdzioxzm
@ -226,6 +227,7 @@ importers:
typeorm: 0.3.6_pg@8.7.3+ts-node@10.8.2
validator: 13.7.0
winston: 3.7.2
zod: 3.19.1
devDependencies:
'@faker-js/faker': 7.3.0
'@swc/cli': 0.1.57_@swc+core@1.2.210
@ -12920,6 +12922,10 @@ packages:
resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==}
dev: false
/zod/3.19.1:
resolution: {integrity: sha512-LYjZsEDhCdYET9ikFu6dVPGp2YH9DegXjdJToSzD9rO6fy4qiRYFoyEYwps88OseJlPyl2NOe2iJuhEhL7IpEA==}
dev: false
/zustand/3.7.2_react@18.1.0:
resolution: {integrity: sha512-PIJDIZKtokhof+9+60cpockVOq05sJzHCriyvaLBmEJixseQ1a5Kdov6fWZfWOu5SK9c+FhH1jU0tntLxRJYMA==}
engines: {node: '>=12.7.0'}