Создан контроллер для CRUD операций с дашбордами

This commit is contained in:
Pavel Gnedov 2023-09-20 10:12:45 +07:00
parent 0e6b64fb27
commit c8e71d3926
8 changed files with 117 additions and 31 deletions

View file

@ -43,7 +43,8 @@
"url": "", "url": "",
"dbs": { "dbs": {
"users": "", "users": "",
"issues": "" "issues": "",
"dashboards": ""
} }
} }
} }

View file

@ -4,8 +4,7 @@
"changes": "", "changes": "",
"userMetaInfo": "", "userMetaInfo": "",
"eccmDailyReports": "", "eccmDailyReports": "",
"eccmDailyReportsUserComments": "", "eccmDailyReportsUserComments": ""
"dashboards": ""
} }
}, },
"telegramBotToken": "", "telegramBotToken": "",

View file

@ -0,0 +1,38 @@
import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common';
import { DashboardsService } from './dashboards.service';
import { BadRequestErrorHandler, getOrAppErrorOrThrow } from '../utils/result';
@Controller('dashboard')
export class DashboardController {
constructor(private dashboardsService: DashboardsService) {}
@Post(':name')
async create(@Param('name') name: string): Promise<string> {
if (name === 'anonymous') {
name = null;
}
const res = await getOrAppErrorOrThrow(
() => this.dashboardsService.create(name),
BadRequestErrorHandler,
);
return res.id;
}
@Get(':name')
async load(@Param('name') name: string): Promise<any> {
const res = await getOrAppErrorOrThrow(
() => this.dashboardsService.load(name),
BadRequestErrorHandler,
);
return res;
}
@Put(':name')
async save(@Param('name') name: string, @Body() data: any): Promise<void> {
const res = await getOrAppErrorOrThrow(
() => this.dashboardsService.save(name, data),
BadRequestErrorHandler,
);
return res;
}
}

View file

@ -1,19 +1,28 @@
import { Injectable } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { Dashboards as DashboardsDb } from '../couchdb-datasources/dashboards'; import { Dashboards as DashboardsDb } from '../couchdb-datasources/dashboards';
import { Dashboard as DashboardModel } from '../models/dashboard'; import {
Dashboard as DashboardModel,
Data as DashboardData,
} from '../models/dashboard';
import nano from 'nano'; import nano from 'nano';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
import { Result, fail, getOrThrow, success } from '../utils/result'; import { createAppError } from '../utils/result';
@Injectable() @Injectable()
export class DashboardsService { export class DashboardsService {
private logger = new Logger(DashboardsService.name);
constructor(private db: DashboardsDb) {} constructor(private db: DashboardsDb) {}
async create(name?: string): Promise<Result<DashboardModel, string>> { async create(name?: string): Promise<DashboardModel> {
if (!name) { if (!name) {
name = randomUUID(); name = randomUUID();
} else if (await this.isExists(name)) { }
return fail('ALREADY_EXISTS'); this.logger.debug(`Create new dashboard with name - ${name}`);
if (await this.isExists(name)) {
const err = createAppError('ALREADY_EXISTS');
this.logger.error(`Error - ${JSON.stringify(err)}`);
throw err;
} }
const ds = await this.db.getDatasource(); const ds = await this.db.getDatasource();
const doc: nano.MaybeDocument & DashboardModel = { const doc: nano.MaybeDocument & DashboardModel = {
@ -22,15 +31,23 @@ export class DashboardsService {
data: null, data: null,
}; };
await ds.insert(doc); await ds.insert(doc);
return success(await ds.get(name)); return await ds.get(name);
} }
async load( async loadRawData(
name: string, name: string,
): Promise<Result<DashboardModel & nano.MaybeDocument, string>> { ): Promise<DashboardModel & nano.MaybeDocument> {
this.logger.debug(`Load raw data, dashboard name - ${name}`);
const ds = await this.db.getDatasource(); const ds = await this.db.getDatasource();
if (!(await this.isExists(name))) return fail('NOT_EXISTS'); if (!(await this.isExists(name))) throw createAppError('NOT_EXISTS');
return success(await ds.get(name)); const res = await ds.get(name);
return res;
}
async load(name: string): Promise<DashboardData> {
this.logger.debug(`Load dashboard name - ${name}`);
const rawData = await this.loadRawData(name);
return rawData.data;
} }
async isExists(name: string): Promise<boolean> { async isExists(name: string): Promise<boolean> {
@ -43,14 +60,20 @@ export class DashboardsService {
} }
} }
async save(name: string, data: any): Promise<Result<any, string>> { async save(name: string, data: any): Promise<void> {
this.logger.debug(
`Save dashboard name - ${name}, data - ${JSON.stringify(data)}`,
);
const ds = await this.db.getDatasource(); const ds = await this.db.getDatasource();
const loadRes = await this.load(name); const prevValue = await this.loadRawData(name);
if (loadRes.error) return fail(loadRes.error);
const prevValue = getOrThrow(loadRes); const newValue = {
const newValue = { ...prevValue, data: data }; _id: prevValue._id,
_rev: prevValue._rev,
id: prevValue.id,
data: data,
};
await ds.insert(newValue); await ds.insert(newValue);
return success(newValue); return;
} }
} }

View file

@ -31,6 +31,9 @@ import { IssuesUpdaterService } from './issues-updater/issues-updater.service';
import { CalendarEnhancer } from './issue-enhancers/calendar-enhancer'; import { CalendarEnhancer } from './issue-enhancers/calendar-enhancer';
import { CalendarService } from './calendar/calendar.service'; import { CalendarService } from './calendar/calendar.service';
import { CalendarController } from './calendar/calendar.controller'; import { CalendarController } from './calendar/calendar.controller';
import { Dashboards as DashboardsDs } from './couchdb-datasources/dashboards';
import { DashboardController } from './dashboards/dashboard.controller';
import { DashboardsService } from './dashboards/dashboards.service';
@Module({}) @Module({})
export class EventEmitterModule implements OnModuleInit { export class EventEmitterModule implements OnModuleInit {
@ -48,6 +51,7 @@ export class EventEmitterModule implements OnModuleInit {
CouchDb, CouchDb,
Users, Users,
Issues, Issues,
DashboardsDs,
RedmineUserCacheWriterService, RedmineUserCacheWriterService,
UsersService, UsersService,
IssuesService, IssuesService,
@ -96,6 +100,7 @@ export class EventEmitterModule implements OnModuleInit {
}, },
inject: ['CALENDAR_ENHANCER', IssuesService], inject: ['CALENDAR_ENHANCER', IssuesService],
}, },
DashboardsService,
], ],
exports: [ exports: [
EventEmitterService, EventEmitterService,
@ -105,6 +110,7 @@ export class EventEmitterModule implements OnModuleInit {
CouchDb, CouchDb,
Users, Users,
Issues, Issues,
DashboardsDs,
RedmineUserCacheWriterService, RedmineUserCacheWriterService,
UsersService, UsersService,
IssuesService, IssuesService,
@ -131,12 +137,14 @@ export class EventEmitterModule implements OnModuleInit {
provide: 'CALENDAR_SERVICE', provide: 'CALENDAR_SERVICE',
useExisting: 'CALENDAR_SERVICE', useExisting: 'CALENDAR_SERVICE',
}, },
DashboardsService,
], ],
controllers: [ controllers: [
MainController, MainController,
UsersController, UsersController,
IssuesController, IssuesController,
CalendarController, CalendarController,
DashboardController,
], ],
}; };
} }

View file

@ -1,4 +1,6 @@
export type Data = Record<string, any> | null;
export type Dashboard = { export type Dashboard = {
id: string; id: string;
data: any; data: Data;
}; };

View file

@ -1,3 +1,5 @@
import { BadRequestException } from '@nestjs/common';
export type Result<T, E> = { export type Result<T, E> = {
result?: T; result?: T;
error?: E; error?: E;
@ -39,23 +41,34 @@ export type AppError = Error & {
app: true; app: true;
}; };
export function createAppError(msg: string): AppError { export function createAppError(msg: string | Error): AppError {
const err = new Error(msg); let err: any;
const app: AppError = { ...err, app: true }; if (typeof msg === 'string') {
return app; err = new Error(msg);
} else if (typeof msg === 'object') {
err = msg;
} else {
err = new Error('UNKNOWN_APP_ERROR');
}
err.name = 'ApplicationError';
return err;
} }
export async function getOrAppErrorOrThrow<T>( export async function getOrAppErrorOrThrow<T>(
fn: () => Promise<T>, fn: () => Promise<T>,
onError: (err: AppError) => Promise<void>, onAppError?: (err: Error) => Error,
): Promise<T | AppError> { onOtherError?: (err: Error) => Error,
): Promise<T> {
try { try {
return await fn(); return await fn();
} catch (ex) { } catch (ex) {
if (ex && ex.app) { if (ex && ex.name === 'ApplicationError') {
onError(ex); throw onAppError ? onAppError(ex) : ex;
return ex;
} }
throw ex; throw onOtherError ? onOtherError(ex) : ex;
} }
} }
export function BadRequestErrorHandler(err: Error): Error {
return new BadRequestException(err.message);
}

View file

@ -51,6 +51,7 @@ import { SimpleIssuesListController } from './dashboards/simple-issues-list.cont
import { TagsManagerController } from './tags-manager/tags-manager.controller'; import { TagsManagerController } from './tags-manager/tags-manager.controller';
import { CreateTagManagerServiceProvider } from './tags-manager/tags-manager.service'; import { CreateTagManagerServiceProvider } from './tags-manager/tags-manager.service';
import { CalendarEnhancer } from '@app/event-emitter/issue-enhancers/calendar-enhancer'; import { CalendarEnhancer } from '@app/event-emitter/issue-enhancers/calendar-enhancer';
import { Dashboards as DashboardsDs } from '@app/event-emitter/couchdb-datasources/dashboards';
@Module({ @Module({
imports: [ imports: [
@ -145,6 +146,7 @@ export class AppModule implements OnModuleInit {
UserMetaInfo.getDatasource(); UserMetaInfo.getDatasource();
DailyEccmReportsDatasource.getDatasource(); DailyEccmReportsDatasource.getDatasource();
DailyEccmReportsUserCommentsDatasource.getDatasource(); DailyEccmReportsUserCommentsDatasource.getDatasource();
DashboardsDs.getDatasource();
this.enhancerService.addEnhancer([ this.enhancerService.addEnhancer([
this.timestampEnhancer, this.timestampEnhancer,