Compare commits

..

3 Commits

Author SHA1 Message Date
DmitriyA dadb6f3bcb sidebar menu improvement 2025-06-10 09:28:20 -04:00
Vladislav Drozdov f1527abae6 Merge pull request 'added ranges for charts' (#26) from swagger into rc
test-org/trust-module-backend/pipeline/pr-main Build succeeded
Reviewed-on: http://git.enode/deployer3000/trust-module-backend/pulls/26
Reviewed-by: Vladislav Drozdov <ya2@ya.ru>
Reviewed-by: YurijO <ya@ya.ru>
2025-06-06 14:44:37 +03:00
YurijO 7f6f2171a3 Merge pull request 'websocket fix' (#24) from swagger into rc
test-org/trust-module-backend/pipeline/pr-main Build succeeded
Reviewed-on: http://git.enode/deployer3000/trust-module-backend/pulls/24
Reviewed-by: Vladislav Drozdov <ya2@ya.ru>
Reviewed-by: YurijO <ya@ya.ru>
2025-06-03 13:10:59 +03:00
2 changed files with 66 additions and 17 deletions

View File

@ -1,14 +1,41 @@
import { Controller, Get, Param, Post, Body, Put } from '@nestjs/common'; import { Controller, Get, Post, Put, Body, Param, Headers, HttpException, HttpStatus } from '@nestjs/common';
import { MenuService } from './menu.service'; import { MenuService } from './menu.service';
import { MenuItem } from './menu.interface'; import { MenuItem } from './menu.interface';
import { Response } from 'express';
@Controller('menu') @Controller('menu')
export class MenuController { export class MenuController {
constructor(private readonly menuService: MenuService) { } constructor(private readonly menuService: MenuService) { }
@Get() @Get('full')
async getMenu(): Promise<MenuItem> { async getFullMenu(@Headers('if-modified-since') ifModifiedSince?: string) {
return this.menuService.getFullMenu(); try {
const result = await this.menuService.getFullMenuWithCache(ifModifiedSince);
if (!result.fresh && ifModifiedSince) {
throw new HttpException('Not Modified', HttpStatus.NOT_MODIFIED);
}
return result.menu;
} catch (error) {
if (error.status === HttpStatus.NOT_MODIFIED) {
throw error;
}
throw new HttpException(
error.message || 'Failed to load menu',
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
@Get('check-updates')
async checkUpdates(@Headers('if-modified-since') ifModifiedSince: string) {
if (!ifModifiedSince) {
throw new HttpException('If-Modified-Since header is required', HttpStatus.BAD_REQUEST);
}
const hasUpdates = await this.menuService.checkForUpdates(ifModifiedSince);
return { hasUpdates };
} }
@Post('save') @Post('save')
@ -17,21 +44,18 @@ export class MenuController {
return { status: 'saved' }; return { status: 'saved' };
} }
@Get('full')
async getFullMenu(): Promise<MenuItem> {
return this.menuService.getFullMenu();
}
@Post('overrides') @Post('overrides')
async saveOverrides(@Body() data: { overrides: Partial<MenuItem>[] }) { async saveOverrides(@Body() data: { overrides: Partial<MenuItem>[] }) {
return this.menuService.saveOverrides(data.overrides); await this.menuService.saveOverrides(data.overrides);
return { status: 'success' };
} }
@Put(':id') @Put(':id')
async updateMenuItem( async updateMenuItem(
@Param('id') id: string, @Param('id') id: string,
@Body() update: Partial<MenuItem> @Body() update: Partial<MenuItem>
): Promise<MenuItem> { ) {
return this.menuService.updateMenuItem(id, update); const updatedItem = await this.menuService.updateMenuItem(id, update);
return updatedItem;
} }
} }

View File

@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common'; import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { PrometheusService } from '../prometheus.service'; import { PrometheusService } from '../prometheus.service';
import { MenuItem } from './menu.interface'; import { MenuItem } from './menu.interface';
import * as fs from 'fs/promises'; import * as fs from 'fs/promises';
@ -7,6 +7,10 @@ import { RangeService } from './range.service';
@Injectable() @Injectable()
export class MenuService { export class MenuService {
private menuCache: MenuItem | null = null;
private lastModified: Date | null = null;
private cacheInitialized = false;
constructor( constructor(
private readonly prometheusService: PrometheusService, private readonly prometheusService: PrometheusService,
private readonly rangeService: RangeService private readonly rangeService: RangeService
@ -15,16 +19,33 @@ export class MenuService {
private readonly menuOverridesPath = path.join(process.cwd(), 'data', 'menu.json'); private readonly menuOverridesPath = path.join(process.cwd(), 'data', 'menu.json');
async saveMenuToFile(): Promise<void> { async saveMenuToFile(): Promise<void> {
const menu = await this.getFullMenu(); const { menu } = await this.getFullMenuWithCache();
await fs.mkdir(path.dirname(this.menuOverridesPath), { recursive: true }); await fs.mkdir(path.dirname(this.menuOverridesPath), { recursive: true });
await fs.writeFile(this.menuOverridesPath, JSON.stringify(menu, null, 2), 'utf-8'); await fs.writeFile(this.menuOverridesPath, JSON.stringify(menu, null, 2), 'utf-8');
} }
async getFullMenu(): Promise<MenuItem> { async getFullMenuWithCache(ifModifiedSince?: string): Promise<{ menu: MenuItem; fresh: boolean }> {
if (this.menuCache && this.lastModified && (!ifModifiedSince || new Date(ifModifiedSince) >= this.lastModified)) {
return { menu: this.menuCache, fresh: false };
}
const dynamicItemsPromise = this.generateDynamicItems(); const dynamicItemsPromise = this.generateDynamicItems();
const baseMenu = await this.injectDynamicItems(this.getStaticStructure(), dynamicItemsPromise); const baseMenu = await this.injectDynamicItems(this.getStaticStructure(), dynamicItemsPromise);
const overrides = await this.loadOverrides(); const overrides = await this.loadOverrides();
return this.applyOverrides(baseMenu, overrides); const freshMenu = this.applyOverrides(baseMenu, overrides);
this.menuCache = freshMenu;
this.lastModified = new Date();
this.cacheInitialized = true;
return { menu: freshMenu, fresh: true };
}
async checkForUpdates(ifModifiedSince: string): Promise<boolean> {
if (!this.cacheInitialized) {
await this.getFullMenuWithCache();
}
return !this.lastModified || new Date(ifModifiedSince) < this.lastModified;
} }
private applyOverrides(menu: MenuItem, overrides: Partial<MenuItem>[]): MenuItem { private applyOverrides(menu: MenuItem, overrides: Partial<MenuItem>[]): MenuItem {
@ -262,17 +283,21 @@ export class MenuService {
} }
async updateMenuItem(id: string, update: Partial<MenuItem>): Promise<MenuItem> { async updateMenuItem(id: string, update: Partial<MenuItem>): Promise<MenuItem> {
const fullMenu = await this.getFullMenu(); const { menu: fullMenu } = await this.getFullMenuWithCache();
const item = this.findMenuItem(fullMenu, id); const item = this.findMenuItem(fullMenu, id);
if (!item) throw new Error('Menu item not found'); if (!item) throw new Error('Menu item not found');
Object.assign(item, update); Object.assign(item, update);
// Инвалидируем кэш после изменения
this.menuCache = null;
return item; return item;
} }
async saveOverrides(overrides: Partial<MenuItem>[]): Promise<void> { async saveOverrides(overrides: Partial<MenuItem>[]): Promise<void> {
await fs.writeFile(this.menuOverridesPath, JSON.stringify({ overrides }, null, 2), 'utf-8'); await fs.writeFile(this.menuOverridesPath, JSON.stringify({ overrides }, null, 2), 'utf-8');
// Инвалидируем кэш после изменения оверрайдов
this.menuCache = null;
} }
private findMenuItem(menu: MenuItem, id: string): MenuItem | null { private findMenuItem(menu: MenuItem, id: string): MenuItem | null {