From 0e6b64fb278d2c32b03ac1e6e45b0b2afdc8666e Mon Sep 17 00:00:00 2001 From: Pavel Gnedov Date: Tue, 19 Sep 2023 07:34:49 +0700 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D1=8B=20=D1=81=20=D0=B4?= =?UTF-8?q?=D0=B0=D1=88=D0=B1=D0=BE=D1=80=D0=B4=D0=B0=D0=BC=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- configs/main-config.jsonc.dist | 3 +- .../src/couchdb-datasources/dashboards.ts | 36 +++++++++++ .../src/dashboards/dashboards.service.ts | 56 +++++++++++++++++ .../event-emitter/src/event-emitter.module.ts | 14 ++++- libs/event-emitter/src/models/dashboard.ts | 4 ++ .../src/models/main-config-model.ts | 1 + libs/event-emitter/src/utils/result.ts | 61 +++++++++++++++++++ 7 files changed, 171 insertions(+), 4 deletions(-) create mode 100644 libs/event-emitter/src/couchdb-datasources/dashboards.ts create mode 100644 libs/event-emitter/src/dashboards/dashboards.service.ts create mode 100644 libs/event-emitter/src/models/dashboard.ts create mode 100644 libs/event-emitter/src/utils/result.ts diff --git a/configs/main-config.jsonc.dist b/configs/main-config.jsonc.dist index e51d60d..6114f24 100644 --- a/configs/main-config.jsonc.dist +++ b/configs/main-config.jsonc.dist @@ -4,7 +4,8 @@ "changes": "", "userMetaInfo": "", "eccmDailyReports": "", - "eccmDailyReportsUserComments": "" + "eccmDailyReportsUserComments": "", + "dashboards": "" } }, "telegramBotToken": "", diff --git a/libs/event-emitter/src/couchdb-datasources/dashboards.ts b/libs/event-emitter/src/couchdb-datasources/dashboards.ts new file mode 100644 index 0000000..80aa5de --- /dev/null +++ b/libs/event-emitter/src/couchdb-datasources/dashboards.ts @@ -0,0 +1,36 @@ +import { Injectable, Logger } from '@nestjs/common'; +import nano from 'nano'; +import * as DashboardModel from '../models/dashboard'; +import { CouchDb } from './couchdb'; +import configuration from '../configs/main-config'; + +const config = configuration(); + +@Injectable() +export class Dashboards { + private static logger = new Logger(Dashboards.name); + private static dashboardsDb = null; + private static initialized = false; + + static async getDatasource(): Promise< + nano.DocumentScope + > { + if (Dashboards.initialized) { + return Dashboards.dashboardsDb; + } + Dashboards.initialized = true; + const n = CouchDb.getCouchDb(); + const dashboardsDbName = config.couchDb?.dbs?.dashboards; + const dbs = await n.db.list(); + if (!dbs.includes(dashboardsDbName)) { + await n.db.create(dashboardsDbName); + } + Dashboards.dashboardsDb = await n.db.use(dashboardsDbName); + Dashboards.logger.log(`Connected to dashboards db - ${dashboardsDbName}`); + return Dashboards.dashboardsDb; + } + + async getDatasource(): Promise> { + return await Dashboards.getDatasource(); + } +} diff --git a/libs/event-emitter/src/dashboards/dashboards.service.ts b/libs/event-emitter/src/dashboards/dashboards.service.ts new file mode 100644 index 0000000..98efd9e --- /dev/null +++ b/libs/event-emitter/src/dashboards/dashboards.service.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@nestjs/common'; +import { Dashboards as DashboardsDb } from '../couchdb-datasources/dashboards'; +import { Dashboard as DashboardModel } from '../models/dashboard'; +import nano from 'nano'; +import { randomUUID } from 'crypto'; +import { Result, fail, getOrThrow, success } from '../utils/result'; + +@Injectable() +export class DashboardsService { + constructor(private db: DashboardsDb) {} + + async create(name?: string): Promise> { + if (!name) { + name = randomUUID(); + } else if (await this.isExists(name)) { + return fail('ALREADY_EXISTS'); + } + const ds = await this.db.getDatasource(); + const doc: nano.MaybeDocument & DashboardModel = { + _id: name, + id: name, + data: null, + }; + await ds.insert(doc); + return success(await ds.get(name)); + } + + async load( + name: string, + ): Promise> { + const ds = await this.db.getDatasource(); + if (!(await this.isExists(name))) return fail('NOT_EXISTS'); + return success(await ds.get(name)); + } + + async isExists(name: string): Promise { + const ds = await this.db.getDatasource(); + try { + await ds.get(name); + return true; + } catch (ex) { + return false; + } + } + + async save(name: string, data: any): Promise> { + const ds = await this.db.getDatasource(); + const loadRes = await this.load(name); + if (loadRes.error) return fail(loadRes.error); + + const prevValue = getOrThrow(loadRes); + const newValue = { ...prevValue, data: data }; + await ds.insert(newValue); + return success(newValue); + } +} diff --git a/libs/event-emitter/src/event-emitter.module.ts b/libs/event-emitter/src/event-emitter.module.ts index 7383ec6..03ebfca 100644 --- a/libs/event-emitter/src/event-emitter.module.ts +++ b/libs/event-emitter/src/event-emitter.module.ts @@ -87,11 +87,14 @@ export class EventEmitterModule implements OnModuleInit { }, { provide: 'CALENDAR_SERVICE', - useFactory: (calendarEnhancer: CalendarEnhancer, issuesService: IssuesService): CalendarService => { + useFactory: ( + calendarEnhancer: CalendarEnhancer, + issuesService: IssuesService, + ): CalendarService => { const calendarEventsKey = calendarEnhancer.calendarEventsKey; return new CalendarService(calendarEventsKey, issuesService); }, - inject: ['CALENDAR_ENHANCER', IssuesService] + inject: ['CALENDAR_ENHANCER', IssuesService], }, ], exports: [ @@ -129,7 +132,12 @@ export class EventEmitterModule implements OnModuleInit { useExisting: 'CALENDAR_SERVICE', }, ], - controllers: [MainController, UsersController, IssuesController, CalendarController], + controllers: [ + MainController, + UsersController, + IssuesController, + CalendarController, + ], }; } diff --git a/libs/event-emitter/src/models/dashboard.ts b/libs/event-emitter/src/models/dashboard.ts new file mode 100644 index 0000000..ef4a812 --- /dev/null +++ b/libs/event-emitter/src/models/dashboard.ts @@ -0,0 +1,4 @@ +export type Dashboard = { + id: string; + data: any; +}; diff --git a/libs/event-emitter/src/models/main-config-model.ts b/libs/event-emitter/src/models/main-config-model.ts index b1c1342..3e5088a 100644 --- a/libs/event-emitter/src/models/main-config-model.ts +++ b/libs/event-emitter/src/models/main-config-model.ts @@ -16,6 +16,7 @@ export type MainConfigModel = { dbs: { users: string; issues: string; + dashboards: string; }; }; webhooks: WebhookConfigItemModel[]; diff --git a/libs/event-emitter/src/utils/result.ts b/libs/event-emitter/src/utils/result.ts new file mode 100644 index 0000000..945d2eb --- /dev/null +++ b/libs/event-emitter/src/utils/result.ts @@ -0,0 +1,61 @@ +export type Result = { + result?: T; + error?: E; +}; + +export function success(res: T): Result { + return { + result: res, + }; +} + +export function fail(error: E): Result { + return { + error: error, + }; +} + +export function getOrThrow(res: Result): T { + if (res.result) return res.result; + throw res.error ? res.error : 'UNKNOWN_ERROR'; +} + +export async function successOrError( + cb: () => Promise, +): Promise> { + try { + const res = await cb(); + return { + result: res, + }; + } catch (ex) { + return { + error: ex, + }; + } +} + +export type AppError = Error & { + app: true; +}; + +export function createAppError(msg: string): AppError { + const err = new Error(msg); + const app: AppError = { ...err, app: true }; + return app; +} + +export async function getOrAppErrorOrThrow( + fn: () => Promise, + onError: (err: AppError) => Promise, +): Promise { + try { + return await fn(); + } catch (ex) { + if (ex && ex.app) { + onError(ex); + return ex; + } + throw ex; + } +}