Forráskód Böngészése

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

Nicolas Meienberger 2 éve
szülő
commit
ae2dd8d364

+ 0 - 1
docker-compose.dev.yml

@@ -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

+ 2 - 1
packages/system-api/package.json

@@ -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",

+ 102 - 0
packages/system-api/src/core/config/TipiConfig.ts

@@ -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();

+ 1 - 1
packages/system-api/src/modules/apps/apps.helpers.ts

@@ -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);

+ 8 - 4
packages/system-api/src/modules/fs/fs.helpers.ts

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

+ 7 - 5
packages/system-api/src/server.ts

@@ -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();

+ 6 - 0
pnpm-lock.yaml

@@ -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'}