Переглянути джерело

Server: GET /services/{:serverId} nochmal geändert; db-migration fix; WIP: PUT /services/{:serverId}

Christian Kahlau 3 роки тому
батько
коміт
224410ee1a

+ 1 - 1
common/interfaces/service-config.interface.ts

@@ -11,7 +11,7 @@ export interface ServiceConfig {
   }>;
 }
 
-export const ParamTypes = ['text', 'number', 'check'] as const;
+export const ParamTypes = ['text', 'number', 'regexp'] as const;
 export type ParamType = typeof ParamTypes[number];
 
 export function validateParamType(input: string): ParamType {

+ 9 - 0
common/types/http-check-config.d.ts

@@ -0,0 +1,9 @@
+type HttpCheckConfig = {
+  id: number;
+  serverId?: number;
+  title: string;
+  type: 'http';
+  url: string;
+  interval: number;
+  checks: string[];
+};

+ 13 - 0
common/util/object-utils.ts

@@ -35,3 +35,16 @@ export function deepCopy<T>(obj: T, keys?: string[]): T {
     return obj;
   }
 }
+
+type DiffResult<T> = { left: T[]; both: T[]; right: T[] };
+export function arrayDiff<T>(left: T[], right: T[], fn = (a: T, b: T) => a === b): DiffResult<T> {
+  const diff: DiffResult<T> = { left: [], both: [], right: [] };
+  for (const a of left) {
+    if (right.find(b => fn(a, b))) diff.both.push(a);
+    else diff.left.push(a);
+  }
+  for (const b of right) {
+    if (!left.some(a => fn(a, b))) diff.right.push(b);
+  }
+  return diff;
+}

+ 88 - 6
server/src/ctrl/database.class.ts

@@ -6,6 +6,7 @@ import { Database as SQLiteDB, OPEN_CREATE, OPEN_READWRITE } from 'sqlite3';
 
 import { ServiceConfig, validateParamType } from '../../../common/interfaces/service-config.interface';
 import { Logger } from '../../../common/util/logger.class';
+import { arrayDiff } from '../../../common/util/object-utils';
 
 import { DBMigration } from './db-migration.class';
 import { SQLiteController } from './sqlite-controller.base';
@@ -224,7 +225,7 @@ export class Database extends SQLiteController {
     return result.rows.map(r => ({ time: new Date(r.Timegroup), avg: r.avg, peak: r.peak, max: r.max }));
   }
 
-  async getHealthCheckConfigs(serverID: number) {
+  private async getHealthCheckConfigs(serverID: number) {
     const res = await this.stmt(
       `SELECT 
         HealthCheckConfig.*,
@@ -238,7 +239,73 @@ export class Database extends SQLiteController {
       [serverID]
     );
 
-    return res.rows.reduce((res: ServiceConfig[], line, i) => {
+    return this.configFromResultRows(res.rows);
+  }
+
+  public async getHttpCheckConfigs(serverID: number) {
+    return (await this.getHealthCheckConfigs(serverID)).map(this.httpCheckConfigFrom);
+  }
+
+  private async getHealtCheckConfigByID(serverID: number, configID: number) {
+    if (!serverID && !configID) return null;
+
+    const res = await this.stmt(
+      `SELECT 
+        HealthCheckConfig.*,
+        HealthCheckParams.Type as '_ParamType',
+        HealthCheckParams.Key as '_ParamKey',
+        HealthCheckParams.Value as '_ParamValue'
+        FROM HealthCheckConfig
+        LEFT OUTER JOIN HealthCheckParams ON HealthCheckConfig.ID = HealthCheckParams.ConfigID
+        WHERE HealtCheckConfig.ID = ?
+        AND HealthCheckConfig.ServerID = ?
+        ORDER BY HealthCheckConfig.Title, _ParamType, _ParamKey`,
+      [configID, serverID]
+    );
+
+    if (!res.rows.length) return null;
+
+    const configs = this.configFromResultRows(res.rows);
+
+    return configs[0];
+  }
+
+  public async getHttpCheckConfigByID(serverID: number, configID: number) {
+    return this.httpCheckConfigFrom(await this.getHealtCheckConfigByID(serverID, configID));
+  }
+
+  public async saveHttpCheckConfig(serverID: number, conf: HttpCheckConfig) {
+    conf.serverId = serverID;
+
+    const oldConf = await this.getHttpCheckConfigByID(serverID, conf.id);
+    await this.beginTransaction();
+    try {
+      if (oldConf) {
+        // UPDATE
+        if (oldConf.title !== conf.title) {
+          await this.stmt('UPDATE HealthCheckConfig SET Title = ?', [conf.title]);
+        }
+
+        const sql = `UPDATE HealthCheckParams SET Value = ? WHERE ConfigID = ? AND Key = ?;`;
+
+        const updValues: any[][] = [];
+        if (oldConf.interval !== conf.interval) updValues.push([conf.interval, conf.id, 'interval']);
+        if (oldConf.url !== conf.url) updValues.push([conf.url, conf.id, 'url']);
+        const checksDiff = arrayDiff(oldConf.checks, conf.checks);
+      } else {
+        // INSERT
+      }
+
+      this.commit();
+      return conf;
+    } catch (err) {
+      this.rollback();
+      throw err;
+    }
+  }
+
+  private configFromResultRows(rows: any[]) {
+    return rows.reduce((res: ServiceConfig[], line, i) => {
       const configID = line['ID'];
       let config: ServiceConfig;
       if (i === 0 || res[res.length - 1].id !== configID) {
@@ -256,13 +323,13 @@ export class Database extends SQLiteController {
       if (!!line['_ParamKey']) {
         const type = validateParamType(line['_ParamType']);
         const key = line['_ParamKey'];
-        if (type === 'check') {
-          let checkParam = config.params.find(c => c.type === 'check');
+        if (key === 'check') {
+          let checkParam = config.params.find(c => c.key === 'check');
           if (!checkParam) {
             config.params.push(
               (checkParam = {
-                key: 'regexp',
-                type: 'check',
+                key: 'check',
+                type: 'regexp',
                 value: []
               })
             );
@@ -280,4 +347,19 @@ export class Database extends SQLiteController {
       return res;
     }, [] as ServiceConfig[]);
   }
+
+  private httpCheckConfigFrom(hcConf: ServiceConfig | null): HttpCheckConfig | null {
+    if (!hcConf) return null;
+    const params = {
+      url: hcConf.params?.find(p => p.key === 'url')?.value as string,
+      interval: hcConf.params?.find(p => p.key === 'interval')?.value as number,
+      checks: hcConf.params?.reduce((res, p) => (p.key === 'check' && Array.isArray(p.value) ? [...res, ...p.value] : res), [] as string[])
+    };
+    return {
+      id: hcConf.id,
+      title: hcConf.title,
+      type: hcConf.type,
+      ...params
+    };
+  }
 }

+ 3 - 1
server/src/ctrl/db-migration.class.ts

@@ -23,6 +23,8 @@ export class DBMigration extends SQLiteController {
       await this.createMigrationsTable();
       const lastID = await this.getLastID();
 
+      console.log('[DEBUG] lastid', lastID);
+
       for (const file of files) {
         const m = /^(\d{12})_(.*)\.sql$/.exec(file);
 
@@ -65,7 +67,7 @@ export class DBMigration extends SQLiteController {
   }
 
   private async getLastID() {
-    const results = await this.stmt(`SELECT id FROM db_migrations ORDER BY id LIMIT 0, 1;`, []);
+    const results = await this.stmt(`SELECT id FROM db_migrations ORDER BY id DESC LIMIT 0, 1;`, []);
     return Number(results.rows[0]?.['id'] ?? '0');
   }
 }

+ 11 - 0
server/src/migrations/202211072338_website_healthcheck_fix1.sql

@@ -0,0 +1,11 @@
+ALTER TABLE HealthCheckParams RENAME TO HealthCheckParams_OLD;
+CREATE TABLE HealthCheckParams(
+  ID INTEGER PRIMARY KEY AUTOINCREMENT,
+  ConfigID INTEGER NOT NULL,
+  Type TEXT NOT NULL,
+  Key TEXT NOT NULL,
+  Value TEXT NOT NULL,
+  FOREIGN KEY(ConfigID) REFERENCES HealthCheckConfig(ID)
+);
+INSERT INTO HealthCheckParams SELECT * FROM HealthCheckParams_OLD;
+DROP TABLE HealthCheckParams_OLD;

+ 25 - 7
server/src/webhdl/services-api-handler.class.ts

@@ -1,4 +1,4 @@
-import { RouterOptions } from 'express';
+import { RouterOptions, json } from 'express';
 
 import { HttpStatusException } from '../../../common/lib/http-status.exception';
 
@@ -9,22 +9,40 @@ export class ServicesAPIHandler extends WebHandler {
   constructor(protected ctrlPool: ControllerPool, options?: RouterOptions) {
     super(ctrlPool, options);
 
+    this.router.use(json());
     this.router.use(this.avoidCache);
 
     this.router.get('/:serverID', async (req, res, next) => {
       try {
-        const serverID = Number(req.params.serverID);
+        const serverID = this.validateServerID(req.params.serverID);
+        const services = await this.ctrlPool.db.getHttpCheckConfigs(serverID);
 
-        if (Number.isNaN(serverID)) {
-          throw new HttpStatusException(`Not a valid server id: ${req.params.serverID}`, 400);
-        }
+        res.send(services);
+      } catch (err) {
+        next(err);
+      }
+    });
+
+    this.router.put('/:serverID', async (req, res, next) => {
+      try {
+        const serverID = this.validateServerID(req.params.serverID);
 
-        const services = await this.ctrlPool.db.getHealthCheckConfigs(serverID);
+        const service = await this.ctrlPool.db.saveHttpCheckConfig(serverID, req.body);
 
-        res.send(services);
+        res.send(service);
       } catch (err) {
         next(err);
       }
     });
   }
+
+  private validateServerID(id: string) {
+    const serverID = Number(id);
+
+    if (Number.isNaN(serverID)) {
+      throw new HttpStatusException(`Not a valid server id: ${id}`, 400);
+    }
+
+    return serverID;
+  }
 }