diff --git a/src/menu/menu.controller.ts b/src/menu/menu.controller.ts index 1f8f017..40093c4 100644 --- a/src/menu/menu.controller.ts +++ b/src/menu/menu.controller.ts @@ -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 { MenuItem } from './menu.interface'; +import { Response } from 'express'; @Controller('menu') export class MenuController { constructor(private readonly menuService: MenuService) { } - @Get() - async getMenu(): Promise { - return this.menuService.getFullMenu(); + @Get('full') + async getFullMenu(@Headers('if-modified-since') ifModifiedSince?: string) { + 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') @@ -17,21 +44,18 @@ export class MenuController { return { status: 'saved' }; } - @Get('full') - async getFullMenu(): Promise { - return this.menuService.getFullMenu(); - } - @Post('overrides') async saveOverrides(@Body() data: { overrides: Partial[] }) { - return this.menuService.saveOverrides(data.overrides); + await this.menuService.saveOverrides(data.overrides); + return { status: 'success' }; } @Put(':id') async updateMenuItem( @Param('id') id: string, @Body() update: Partial - ): Promise { - return this.menuService.updateMenuItem(id, update); + ) { + const updatedItem = await this.menuService.updateMenuItem(id, update); + return updatedItem; } } \ No newline at end of file diff --git a/src/menu/menu.service.ts b/src/menu/menu.service.ts index b830d8d..2e0dc20 100644 --- a/src/menu/menu.service.ts +++ b/src/menu/menu.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, HttpException, HttpStatus } from '@nestjs/common'; import { PrometheusService } from '../prometheus.service'; import { MenuItem } from './menu.interface'; import * as fs from 'fs/promises'; @@ -7,6 +7,10 @@ import { RangeService } from './range.service'; @Injectable() export class MenuService { + private menuCache: MenuItem | null = null; + private lastModified: Date | null = null; + private cacheInitialized = false; + constructor( private readonly prometheusService: PrometheusService, private readonly rangeService: RangeService @@ -15,16 +19,33 @@ export class MenuService { private readonly menuOverridesPath = path.join(process.cwd(), 'data', 'menu.json'); async saveMenuToFile(): Promise { - const menu = await this.getFullMenu(); + const { menu } = await this.getFullMenuWithCache(); await fs.mkdir(path.dirname(this.menuOverridesPath), { recursive: true }); await fs.writeFile(this.menuOverridesPath, JSON.stringify(menu, null, 2), 'utf-8'); } - async getFullMenu(): Promise { + 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 baseMenu = await this.injectDynamicItems(this.getStaticStructure(), dynamicItemsPromise); 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 { + if (!this.cacheInitialized) { + await this.getFullMenuWithCache(); + } + return !this.lastModified || new Date(ifModifiedSince) < this.lastModified; } private applyOverrides(menu: MenuItem, overrides: Partial[]): MenuItem { @@ -262,17 +283,21 @@ export class MenuService { } async updateMenuItem(id: string, update: Partial): Promise { - const fullMenu = await this.getFullMenu(); + const { menu: fullMenu } = await this.getFullMenuWithCache(); const item = this.findMenuItem(fullMenu, id); if (!item) throw new Error('Menu item not found'); Object.assign(item, update); + // Инвалидируем кэш после изменения + this.menuCache = null; return item; } async saveOverrides(overrides: Partial[]): Promise { await fs.writeFile(this.menuOverridesPath, JSON.stringify({ overrides }, null, 2), 'utf-8'); + // Инвалидируем кэш после изменения оверрайдов + this.menuCache = null; } private findMenuItem(menu: MenuItem, id: string): MenuItem | null {