From 97d5d139dee83bdb4ea6765548505099093297ed Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Mon, 15 Apr 2024 19:11:58 +0200 Subject: [PATCH 01/18] BC-5268 move alert api to nest --- .../alert/adapter/dto/component.dto.ts | 61 +++++++++ .../alert/adapter/dto/component.response.ts | 9 ++ .../modules/alert/adapter/dto/incident.dto.ts | 97 +++++++++++++ .../alert/adapter/dto/incidents.response.ts | 13 ++ .../src/modules/alert/adapter/dto/index.ts | 8 ++ .../modules/alert/adapter/dto/links.dto.ts | 10 ++ .../modules/alert/adapter/dto/messages.dto.ts | 12 ++ .../src/modules/alert/adapter/dto/meta.dto.ts | 9 ++ .../alert/adapter/dto/pagination.dto.ts | 31 +++++ .../modules/alert/adapter/enum/importance.ts | 5 + .../src/modules/alert/adapter/enum/index.ts | 2 + .../modules/alert/adapter/enum/instance.ts | 7 + .../server/src/modules/alert/adapter/index.ts | 1 + .../modules/alert/adapter/status.adapter.ts | 128 ++++++++++++++++++ apps/server/src/modules/alert/alert.module.ts | 14 ++ .../src/modules/alert/config/alert-config.ts | 13 ++ apps/server/src/modules/alert/config/index.ts | 1 + .../alert/controller/alert.controller.ts | 19 +++ .../alert/controller/dto/alert.response.ts | 11 ++ .../src/modules/alert/controller/dto/index.ts | 3 + .../alert/controller/dto/message-origin.ts | 14 ++ .../modules/alert/controller/dto/message.ts | 45 ++++++ .../src/modules/alert/controller/index.ts | 1 + .../modules/alert/controller/mapper/index.ts | 1 + .../alert/controller/mapper/message.mapper.ts | 28 ++++ .../modules/alert/service/cache.service.ts | 63 +++++++++ .../server/src/modules/alert/service/index.ts | 1 + apps/server/src/modules/alert/uc/alert.uc.ts | 11 ++ apps/server/src/modules/alert/uc/index.ts | 1 + .../src/modules/server/server.module.ts | 2 + .../alert/MessageProvider/status/index.js | 89 ------------ src/services/alert/adapter/index.js | 18 --- src/services/alert/adapter/message.js | 91 ------------- src/services/alert/adapter/status.js | 48 ------- src/services/alert/cache.js | 58 -------- src/services/alert/docs/openapi.yaml | 74 ---------- src/services/alert/index.js | 43 ------ src/services/index.js | 2 - 38 files changed, 621 insertions(+), 423 deletions(-) create mode 100644 apps/server/src/modules/alert/adapter/dto/component.dto.ts create mode 100644 apps/server/src/modules/alert/adapter/dto/component.response.ts create mode 100644 apps/server/src/modules/alert/adapter/dto/incident.dto.ts create mode 100644 apps/server/src/modules/alert/adapter/dto/incidents.response.ts create mode 100644 apps/server/src/modules/alert/adapter/dto/index.ts create mode 100644 apps/server/src/modules/alert/adapter/dto/links.dto.ts create mode 100644 apps/server/src/modules/alert/adapter/dto/messages.dto.ts create mode 100644 apps/server/src/modules/alert/adapter/dto/meta.dto.ts create mode 100644 apps/server/src/modules/alert/adapter/dto/pagination.dto.ts create mode 100644 apps/server/src/modules/alert/adapter/enum/importance.ts create mode 100644 apps/server/src/modules/alert/adapter/enum/index.ts create mode 100644 apps/server/src/modules/alert/adapter/enum/instance.ts create mode 100644 apps/server/src/modules/alert/adapter/index.ts create mode 100644 apps/server/src/modules/alert/adapter/status.adapter.ts create mode 100644 apps/server/src/modules/alert/alert.module.ts create mode 100644 apps/server/src/modules/alert/config/alert-config.ts create mode 100644 apps/server/src/modules/alert/config/index.ts create mode 100644 apps/server/src/modules/alert/controller/alert.controller.ts create mode 100644 apps/server/src/modules/alert/controller/dto/alert.response.ts create mode 100644 apps/server/src/modules/alert/controller/dto/index.ts create mode 100644 apps/server/src/modules/alert/controller/dto/message-origin.ts create mode 100644 apps/server/src/modules/alert/controller/dto/message.ts create mode 100644 apps/server/src/modules/alert/controller/index.ts create mode 100644 apps/server/src/modules/alert/controller/mapper/index.ts create mode 100644 apps/server/src/modules/alert/controller/mapper/message.mapper.ts create mode 100644 apps/server/src/modules/alert/service/cache.service.ts create mode 100644 apps/server/src/modules/alert/service/index.ts create mode 100644 apps/server/src/modules/alert/uc/alert.uc.ts create mode 100644 apps/server/src/modules/alert/uc/index.ts delete mode 100644 src/services/alert/MessageProvider/status/index.js delete mode 100644 src/services/alert/adapter/index.js delete mode 100644 src/services/alert/adapter/message.js delete mode 100644 src/services/alert/adapter/status.js delete mode 100644 src/services/alert/cache.js delete mode 100644 src/services/alert/docs/openapi.yaml delete mode 100644 src/services/alert/index.js diff --git a/apps/server/src/modules/alert/adapter/dto/component.dto.ts b/apps/server/src/modules/alert/adapter/dto/component.dto.ts new file mode 100644 index 00000000000..204f2d04c07 --- /dev/null +++ b/apps/server/src/modules/alert/adapter/dto/component.dto.ts @@ -0,0 +1,61 @@ +export class ComponentDto { + constructor( + id: number, + name: string, + description: string, + link: string, + status: number, + order: number, + group_id: number, + created_at: Date, + updated_at: Date, + deleted_at: Date, + enabled: boolean, + meta: any[], + status_name: string, + tags: any[] + ) { + this.id = id; + this.name = name; + this.description = description; + this.link = link; + this.status = status; + this.order = order; + this.group_id = group_id; + this.created_at = created_at; + this.updated_at = updated_at; + this.deleted_at = deleted_at; + this.enabled = enabled; + this.meta = meta; + this.status_name = status_name; + this.tags = tags; + } + + id: number; + + name: string; + + description: string; + + link: string; + + status: number; + + order: number; + + group_id: number; + + created_at: Date; + + updated_at: Date; + + deleted_at: Date; + + enabled: boolean; + + meta: any[]; + + status_name: string; + + tags: any[]; +} diff --git a/apps/server/src/modules/alert/adapter/dto/component.response.ts b/apps/server/src/modules/alert/adapter/dto/component.response.ts new file mode 100644 index 00000000000..eb568f4a2e0 --- /dev/null +++ b/apps/server/src/modules/alert/adapter/dto/component.response.ts @@ -0,0 +1,9 @@ +import { ComponentDto } from './component.dto'; + +export class ComponentResponse { + constructor(data: ComponentDto) { + this.data = data; + } + + data: ComponentDto; +} diff --git a/apps/server/src/modules/alert/adapter/dto/incident.dto.ts b/apps/server/src/modules/alert/adapter/dto/incident.dto.ts new file mode 100644 index 00000000000..3f91ccc400f --- /dev/null +++ b/apps/server/src/modules/alert/adapter/dto/incident.dto.ts @@ -0,0 +1,97 @@ +export class IncidentDto { + constructor( + id: number, + component_id: number, + name: string, + status: number, + message: string, + created_at: Date, + updated_at: Date, + deleted_at: Date, + visible: number, + stickied: boolean, + occurred_at: Date, + user_id: number, + notifications: boolean, + is_resolved: boolean, + meta: any[], + updates: any[], + human_status: string, + latest_update_id: number, + latest_status: number, + latest_human_status: string, + latest_icon: string, + permalink: string, + duration: number + ) { + this.id = id; + this.component_id = component_id; + this.name = name; + this.status = status; + this.message = message; + this.created_at = created_at; + this.updated_at = updated_at; + this.deleted_at = deleted_at; + this.visible = visible; + this.stickied = stickied; + this.occurred_at = occurred_at; + this.user_id = user_id; + this.notifications = notifications; + this.is_resolved = is_resolved; + this.meta = meta; + this.updates = updates; + this.human_status = human_status; + this.latest_update_id = latest_update_id; + this.latest_status = latest_status; + this.latest_human_status = latest_human_status; + this.latest_icon = latest_icon; + this.permalink = permalink; + this.duration = duration; + } + + id: number; + + component_id: number; + + name: string; + + status: number; + + message: string; + + created_at: Date; + + updated_at: Date; + + deleted_at: Date; + + visible: number; + + stickied: boolean; + + occurred_at: Date; + + user_id: number; + + notifications: boolean; + + is_resolved: boolean; + + meta: any[]; + + updates: any[]; + + human_status: string; + + latest_update_id: number; + + latest_status: number; + + latest_human_status: string; + + latest_icon: string; + + permalink: string; + + duration: number; +} diff --git a/apps/server/src/modules/alert/adapter/dto/incidents.response.ts b/apps/server/src/modules/alert/adapter/dto/incidents.response.ts new file mode 100644 index 00000000000..f25ea9665ba --- /dev/null +++ b/apps/server/src/modules/alert/adapter/dto/incidents.response.ts @@ -0,0 +1,13 @@ +import { IncidentDto } from './incident.dto'; +import { MetaDto } from './meta.dto'; + +export class IncidentsResponse { + constructor(meta: MetaDto, data: IncidentDto[]) { + this.meta = meta; + this.data = data; + } + + meta: MetaDto; + + data: IncidentDto[]; +} diff --git a/apps/server/src/modules/alert/adapter/dto/index.ts b/apps/server/src/modules/alert/adapter/dto/index.ts new file mode 100644 index 00000000000..dc6f1837720 --- /dev/null +++ b/apps/server/src/modules/alert/adapter/dto/index.ts @@ -0,0 +1,8 @@ +export * from './component.dto'; +export * from './component.response'; +export * from './incident.dto'; +export * from './incidents.response'; +export * from './links.dto'; +export * from './messages.dto'; +export * from './meta.dto'; +export * from './pagination.dto'; diff --git a/apps/server/src/modules/alert/adapter/dto/links.dto.ts b/apps/server/src/modules/alert/adapter/dto/links.dto.ts new file mode 100644 index 00000000000..29f69a34fa4 --- /dev/null +++ b/apps/server/src/modules/alert/adapter/dto/links.dto.ts @@ -0,0 +1,10 @@ +export class LinksDto { + constructor(next_page: number, previous_page: number) { + this.next_page = next_page; + this.previous_page = previous_page; + } + + next_page: number; + + previous_page: number; +} diff --git a/apps/server/src/modules/alert/adapter/dto/messages.dto.ts b/apps/server/src/modules/alert/adapter/dto/messages.dto.ts new file mode 100644 index 00000000000..e3af7e7e3c3 --- /dev/null +++ b/apps/server/src/modules/alert/adapter/dto/messages.dto.ts @@ -0,0 +1,12 @@ +import { Message } from '../../controller/dto'; + +export class MessagesDto { + constructor(messages: [], success: boolean) { + this.messages = messages; + this.success = success; + } + + messages: Message[]; + + success: boolean; +} diff --git a/apps/server/src/modules/alert/adapter/dto/meta.dto.ts b/apps/server/src/modules/alert/adapter/dto/meta.dto.ts new file mode 100644 index 00000000000..8f509c2cda2 --- /dev/null +++ b/apps/server/src/modules/alert/adapter/dto/meta.dto.ts @@ -0,0 +1,9 @@ +import { PaginationDto } from './pagination.dto'; + +export class MetaDto { + constructor(pagination: PaginationDto) { + this.pagination = pagination; + } + + pagination: PaginationDto; +} diff --git a/apps/server/src/modules/alert/adapter/dto/pagination.dto.ts b/apps/server/src/modules/alert/adapter/dto/pagination.dto.ts new file mode 100644 index 00000000000..3f601339c1a --- /dev/null +++ b/apps/server/src/modules/alert/adapter/dto/pagination.dto.ts @@ -0,0 +1,31 @@ +import { LinksDto } from './links.dto'; + +export class PaginationDto { + constructor( + total: number, + count: number, + per_page: number, + current_page: number, + total_pages: number, + links: LinksDto + ) { + this.total = total; + this.count = count; + this.per_page = per_page; + this.current_page = current_page; + this.total_pages = total_pages; + this.links = links; + } + + total: number; + + count: number; + + per_page: number; + + current_page: number; + + total_pages: number; + + links: LinksDto; +} diff --git a/apps/server/src/modules/alert/adapter/enum/importance.ts b/apps/server/src/modules/alert/adapter/enum/importance.ts new file mode 100644 index 00000000000..0644d22952d --- /dev/null +++ b/apps/server/src/modules/alert/adapter/enum/importance.ts @@ -0,0 +1,5 @@ +export enum Importance { + INGORE = -1, + ALL_INSTANCES = 0, + CURRENT_INSTANCE = 1, +} diff --git a/apps/server/src/modules/alert/adapter/enum/index.ts b/apps/server/src/modules/alert/adapter/enum/index.ts new file mode 100644 index 00000000000..6a1a7abf613 --- /dev/null +++ b/apps/server/src/modules/alert/adapter/enum/index.ts @@ -0,0 +1,2 @@ +export * from './importance'; +export * from './instance'; diff --git a/apps/server/src/modules/alert/adapter/enum/instance.ts b/apps/server/src/modules/alert/adapter/enum/instance.ts new file mode 100644 index 00000000000..499ef5a7900 --- /dev/null +++ b/apps/server/src/modules/alert/adapter/enum/instance.ts @@ -0,0 +1,7 @@ +export enum Instance { + DEFAULT = 1, + BRB = 2, + OPEN = 3, + N21 = 6, + THR = 7, +} diff --git a/apps/server/src/modules/alert/adapter/index.ts b/apps/server/src/modules/alert/adapter/index.ts new file mode 100644 index 00000000000..1bc0294fe06 --- /dev/null +++ b/apps/server/src/modules/alert/adapter/index.ts @@ -0,0 +1 @@ +export * from './status.adapter'; diff --git a/apps/server/src/modules/alert/adapter/status.adapter.ts b/apps/server/src/modules/alert/adapter/status.adapter.ts new file mode 100644 index 00000000000..c961f6c87b0 --- /dev/null +++ b/apps/server/src/modules/alert/adapter/status.adapter.ts @@ -0,0 +1,128 @@ +import { HttpService } from '@nestjs/axios'; +import { Injectable, InternalServerErrorException } from '@nestjs/common'; +import { firstValueFrom } from 'rxjs'; +import { AxiosResponse } from 'axios'; +import { ErrorUtils } from '@src/core/error/utils'; +import { ConfigService } from '@nestjs/config'; +import { Importance, Instance } from './enum'; +import { ComponentResponse, IncidentDto, IncidentsResponse, MessagesDto } from './dto'; +import { AlertConfig } from '../config'; +import { MessageMapper } from '../controller/mapper'; + +@Injectable() +export class StatusAdapter { + private readonly url: string; + + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService + ) { + this.url = configService.get('ALERT_STATUS_URL'); + } + + public async getMessage(instance: string) { + const rawData = await this.getIncidentsData(instance); + const data = new MessagesDto([], false); + + if (rawData) { + rawData.forEach((element) => { + const message = MessageMapper.mapToMessage(element, this.url); + data.messages.push(message); + }); + data.success = true; + } else { + data.success = false; + } + + return data; + } + + private async getIncidentsData(instance: string) { + try { + return await this.getData(instance); + } catch (err) { + return null; + } + } + + private async getData(instance: string) { + const instanceSpecific: IncidentDto[] = []; + const noneSpecific: IncidentDto[] = []; + const rawData = await this.getIncidents(); + const statusEnum = { fixed: 4, danger: 2 }; + + const filteredData = rawData.data.filter((element) => element.status !== statusEnum.fixed); + filteredData.map(async (element) => { + const isinstance = await this.getInstance(instance, element.component_id); + if (isinstance !== Importance.ALL_INSTANCES && isinstance !== Importance.INGORE) { + instanceSpecific.push(element); + } else if (isinstance !== Importance.INGORE) { + noneSpecific.push(element); + } + }); + + instanceSpecific.sort(this.compareIncidents); + noneSpecific.sort(this.compareIncidents); + + return instanceSpecific.concat(noneSpecific); + } + + private async getInstance(instance: string, componentId: number): Promise { + if (componentId !== 0) { + return this.getImportanceForComponent(instance, componentId); + } + return Importance.ALL_INSTANCES; + } + + private async getImportanceForComponent(instance: string, componentId: number): Promise { + try { + const response = await this.getComponent(componentId); + if (Instance[instance] && response.data.group_id === Instance[instance]) { + return Importance.CURRENT_INSTANCE; + } + return Importance.INGORE; + } catch (err) { + return Importance.INGORE; + } + } + + private async getComponent(componentId: number): Promise { + return firstValueFrom(this.httpService.get(`/api/v1/components/${componentId}`)) + .then((response: AxiosResponse) => response.data) + .catch((error) => { + throw new InternalServerErrorException( + null, + ErrorUtils.createHttpExceptionOptions(error, 'StatusAdapter:getComponent') + ); + }); + } + + private async getIncidents(): Promise { + return firstValueFrom(this.httpService.get('/api/v1/incidents', { params: { sort: 'id' } })) + .then((response: AxiosResponse) => response.data) + .catch((error) => { + throw new InternalServerErrorException( + null, + ErrorUtils.createHttpExceptionOptions(error, 'StatusAdapter:getIncidents') + ); + }); + } + + private compareIncidents = (a: IncidentDto, b: IncidentDto) => { + const dateA = new Date(a.updated_at); + const dateB = new Date(b.updated_at); + const createdAtA = new Date(a.created_at); + const createdAtB = new Date(b.created_at); + + // sort by status; danger first + if (a.status > b.status) return 1; + if (b.status > a.status) return -1; + // sort by newest + if (dateA > dateB) return -1; + if (dateB > dateA) return 1; + if (createdAtA > createdAtB) return -1; + if (createdAtB > createdAtA) return 1; + + return 0; + }; +} diff --git a/apps/server/src/modules/alert/alert.module.ts b/apps/server/src/modules/alert/alert.module.ts new file mode 100644 index 00000000000..fa099430dd8 --- /dev/null +++ b/apps/server/src/modules/alert/alert.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { HttpModule } from '@nestjs/axios'; +import { AlertController } from './controller'; +import { AlertUc } from './uc'; +import { CacheService } from './service'; +import { StatusAdapter } from './adapter'; +import { ToolConfigModule } from '../tool/tool-config.module'; + +@Module({ + imports: [HttpModule, ToolConfigModule], + controllers: [AlertController], + providers: [AlertUc, CacheService, StatusAdapter], +}) +export class AlertModule {} diff --git a/apps/server/src/modules/alert/config/alert-config.ts b/apps/server/src/modules/alert/config/alert-config.ts new file mode 100644 index 00000000000..cdc91b6c23d --- /dev/null +++ b/apps/server/src/modules/alert/config/alert-config.ts @@ -0,0 +1,13 @@ +import { Configuration } from '@hpi-schul-cloud/commons/lib'; + +export interface AlertConfig { + INSTANCE: string; + ALERT_STATUS_URL: string; +} + +const alertConfig = { + INSTANCE: Configuration.get('SC_THEME') as string, + ALERT_STATUS_URL: Configuration.get('ALERT_STATUS_URL') as string, +}; + +export const config = () => alertConfig; diff --git a/apps/server/src/modules/alert/config/index.ts b/apps/server/src/modules/alert/config/index.ts new file mode 100644 index 00000000000..736a69133ce --- /dev/null +++ b/apps/server/src/modules/alert/config/index.ts @@ -0,0 +1 @@ +export * from './alert-config'; diff --git a/apps/server/src/modules/alert/controller/alert.controller.ts b/apps/server/src/modules/alert/controller/alert.controller.ts new file mode 100644 index 00000000000..9249fcd2a94 --- /dev/null +++ b/apps/server/src/modules/alert/controller/alert.controller.ts @@ -0,0 +1,19 @@ +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { Controller, Get } from '@nestjs/common'; +import { AlertUc } from '../uc'; +import { AlertResponse } from './dto'; + +@ApiTags('Alert') +@Controller('alert') +export class AlertController { + constructor(private readonly alertUc: AlertUc) {} + + @ApiOperation({ summary: 'Get allerts' }) + @ApiResponse({ status: 201, type: AlertResponse }) + @Get() + public find() { + const messages = this.alertUc.find(); + + return new AlertResponse(messages); + } +} diff --git a/apps/server/src/modules/alert/controller/dto/alert.response.ts b/apps/server/src/modules/alert/controller/dto/alert.response.ts new file mode 100644 index 00000000000..65b07eec0bd --- /dev/null +++ b/apps/server/src/modules/alert/controller/dto/alert.response.ts @@ -0,0 +1,11 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Message } from './message'; + +export class AlertResponse { + constructor(data: Message[]) { + this.data = data; + } + + @ApiProperty({ type: [Message] }) + data: Message[]; +} diff --git a/apps/server/src/modules/alert/controller/dto/index.ts b/apps/server/src/modules/alert/controller/dto/index.ts new file mode 100644 index 00000000000..9474caa801d --- /dev/null +++ b/apps/server/src/modules/alert/controller/dto/index.ts @@ -0,0 +1,3 @@ +export * from './alert.response'; +export * from './message'; +export * from './message-origin'; diff --git a/apps/server/src/modules/alert/controller/dto/message-origin.ts b/apps/server/src/modules/alert/controller/dto/message-origin.ts new file mode 100644 index 00000000000..b7b4870fc9e --- /dev/null +++ b/apps/server/src/modules/alert/controller/dto/message-origin.ts @@ -0,0 +1,14 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class MessageOrigin { + constructor(message_id: number, page: string) { + this.message_id = message_id; + this.page = page; + } + + @ApiProperty() + message_id: number; + + @ApiProperty() + page: string; +} diff --git a/apps/server/src/modules/alert/controller/dto/message.ts b/apps/server/src/modules/alert/controller/dto/message.ts new file mode 100644 index 00000000000..cf85f3a40aa --- /dev/null +++ b/apps/server/src/modules/alert/controller/dto/message.ts @@ -0,0 +1,45 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { MessageOrigin } from './message-origin'; + +export type MessageStatus = 'danger' | 'done' | 'info'; + +export class Message { + constructor( + title: string, + text: string, + timestamp: Date, + origin: MessageOrigin, + url: string, + status: MessageStatus, + createdAt: Date + ) { + this.title = title; + this.text = text; + this.timestamp = timestamp; + this.origin = origin; + this.url = url; + this.status = status; + this.createdAt = createdAt; + } + + @ApiProperty() + title: string; + + @ApiProperty() + text: string; + + @ApiProperty() + timestamp: Date; + + @ApiProperty() + origin: MessageOrigin; + + @ApiProperty() + url: string; + + @ApiProperty() + status: MessageStatus; + + @ApiProperty() + createdAt: Date; +} diff --git a/apps/server/src/modules/alert/controller/index.ts b/apps/server/src/modules/alert/controller/index.ts new file mode 100644 index 00000000000..13757baeddf --- /dev/null +++ b/apps/server/src/modules/alert/controller/index.ts @@ -0,0 +1 @@ +export * from './alert.controller'; diff --git a/apps/server/src/modules/alert/controller/mapper/index.ts b/apps/server/src/modules/alert/controller/mapper/index.ts new file mode 100644 index 00000000000..44e680ca395 --- /dev/null +++ b/apps/server/src/modules/alert/controller/mapper/index.ts @@ -0,0 +1 @@ +export * from './message.mapper'; diff --git a/apps/server/src/modules/alert/controller/mapper/message.mapper.ts b/apps/server/src/modules/alert/controller/mapper/message.mapper.ts new file mode 100644 index 00000000000..c72cc00709b --- /dev/null +++ b/apps/server/src/modules/alert/controller/mapper/message.mapper.ts @@ -0,0 +1,28 @@ +import { IncidentDto } from '../../adapter/dto'; +import { Message, MessageOrigin, MessageStatus } from '../dto'; + +export class MessageMapper { + static mapToMessage(incident: IncidentDto, url: string) { + return new Message( + incident.name || '', + incident.message || '', + incident.updated_at || '1970-01-01 00:00:00', + new MessageOrigin(incident.id || -1, 'status'), + url, + this.getStatus(incident.status), + incident.created_at + ); + } + + static getStatus(number: number): MessageStatus { + if (number === 2) { + return 'danger'; + } + + if (number === 4) { + return 'done'; + } + + return 'info'; + } +} diff --git a/apps/server/src/modules/alert/service/cache.service.ts b/apps/server/src/modules/alert/service/cache.service.ts new file mode 100644 index 00000000000..5bd8d1c44de --- /dev/null +++ b/apps/server/src/modules/alert/service/cache.service.ts @@ -0,0 +1,63 @@ +import { Injectable } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { AlertConfig } from '../config'; +import { StatusAdapter } from '../adapter'; +import { Message } from '../controller/dto'; + +@Injectable() +export class CacheService { + private time: number; + + private lastUpdatedTimestamp = 0; + + private messages: Message[] = []; + + private messageProviders: StatusAdapter[] = []; + + private instance: string; + + constructor( + private readonly configService: ConfigService, + private readonly statusAdapter: StatusAdapter + ) { + if (configService.get('ALERT_STATUS_URL')) { + this.addMessageProvider(statusAdapter, true); + } + this.instance = configService.get('INSTANCE'); + this.time = 1; + } + + public updateMessages() { + let success = false; + let newMessages: Message[] = []; + this.lastUpdatedTimestamp = Date.now(); + + this.messageProviders.map(async (provider) => { + const data = await provider.getMessage(this.instance); + if (!data.success) { + success = false; + return; + } + newMessages = newMessages.concat(data.messages); + success = true; + }); + + if (success) { + this.messages = newMessages; + } + } + + public getMessages() { + if (this.lastUpdatedTimestamp < Date.now() - 1000 * 60 * this.time) { + this.updateMessages(); + } + + return this.messages || []; + } + + public addMessageProvider(provider: StatusAdapter, featureEnabled: boolean) { + if (featureEnabled) { + this.messageProviders.push(provider); + } + } +} diff --git a/apps/server/src/modules/alert/service/index.ts b/apps/server/src/modules/alert/service/index.ts new file mode 100644 index 00000000000..865d362ca53 --- /dev/null +++ b/apps/server/src/modules/alert/service/index.ts @@ -0,0 +1 @@ +export * from './cache.service'; diff --git a/apps/server/src/modules/alert/uc/alert.uc.ts b/apps/server/src/modules/alert/uc/alert.uc.ts new file mode 100644 index 00000000000..641746506aa --- /dev/null +++ b/apps/server/src/modules/alert/uc/alert.uc.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { CacheService } from '../service'; + +@Injectable() +export class AlertUc { + constructor(private readonly cacheService: CacheService) {} + + public find() { + return this.cacheService.getMessages(); + } +} diff --git a/apps/server/src/modules/alert/uc/index.ts b/apps/server/src/modules/alert/uc/index.ts new file mode 100644 index 00000000000..7fdb35111a6 --- /dev/null +++ b/apps/server/src/modules/alert/uc/index.ts @@ -0,0 +1 @@ +export * from './alert.uc'; diff --git a/apps/server/src/modules/server/server.module.ts b/apps/server/src/modules/server/server.module.ts index 57e4aa7d704..4927245ac35 100644 --- a/apps/server/src/modules/server/server.module.ts +++ b/apps/server/src/modules/server/server.module.ts @@ -39,6 +39,7 @@ import { ALL_ENTITIES } from '@shared/domain/entity'; import { createConfigModuleOptions, DB_PASSWORD, DB_URL, DB_USERNAME } from '@src/config'; import { CoreModule } from '@src/core'; import { LoggerModule } from '@src/core/logger'; +import { AlertModule } from '@modules/alert/alert.module'; import { ServerConfigController, ServerController, ServerUc } from './api'; import { SERVER_CONFIG_TOKEN, serverConfig } from './server.config'; @@ -92,6 +93,7 @@ const serverModules = [ LegacySchoolApiModule, MeApiModule, MediaBoardApiModule, + AlertModule, ]; export const defaultMikroOrmOptions: MikroOrmModuleSyncOptions = { diff --git a/src/services/alert/MessageProvider/status/index.js b/src/services/alert/MessageProvider/status/index.js deleted file mode 100644 index e7846c7c598..00000000000 --- a/src/services/alert/MessageProvider/status/index.js +++ /dev/null @@ -1,89 +0,0 @@ -const logger = require('../../../../logger'); -const { statusApi } = require('../../../../externalServices'); - -const dict = { - default: 1, - brb: 2, - open: 3, - n21: 6, - thr: 7, -}; - -const importance = { - INGORE: -1, - ALL_INSTANCES: 0, - CURRENT_INSTANCE: 1, -}; - -/** - * Check if Message is instance specific - * @param {string} instance - * @param {number} componentId - * @returns {number} - */ -async function getInstance(instance, componentId) { - if (componentId !== 0) { - try { - const response = await statusApi.getComponent(componentId); - if (dict[instance] && response.data.group_id === dict[instance]) { - return importance.CURRENT_INSTANCE; - } - return importance.INGORE; - } catch (error) { - return importance.INGORE; - } - } else { - return importance.ALL_INSTANCES; - } -} - -function compare(a, b) { - const dateA = new Date(a.updated_at); - const dateB = new Date(b.updated_at); - const createdAtA = new Date(a.createdAt); - const createdAtB = new Date(b.createdAt); - - // sort by status; danger first - if (a.status > b.status) return 1; - if (b.status > a.status) return -1; - // sort by newest - if (dateA > dateB) return -1; - if (dateB > dateA) return 1; - if (createdAtA > createdAtB) return -1; - if (createdAtB > createdAtA) return 1; - - return 0; -} - -module.exports = { - async getData(instance) { - try { - const rawData = await statusApi.getIncidents(); - const instanceSpecific = []; - const noneSpecific = []; - const statusEnum = { fixed: 4, danger: 2 }; - - const filteredData = rawData.data.filter((element) => element.status !== statusEnum.fixed); - const promises = filteredData.map(async (element) => { - const isinstance = await getInstance(instance, element.component_id); - if (isinstance !== importance.ALL_INSTANCES && isinstance !== importance.INGORE) { - instanceSpecific.push(element); - } else if (isinstance !== importance.INGORE) { - noneSpecific.push(element); - } - }); - - await Promise.all(promises); - - // do some sorting - instanceSpecific.sort(compare); - noneSpecific.sort(compare); - - return instanceSpecific.concat(noneSpecific); - } catch (err) { - // return null on error - logger.error(err); - return null; - } - }, -}; diff --git a/src/services/alert/adapter/index.js b/src/services/alert/adapter/index.js deleted file mode 100644 index 24dd91301b2..00000000000 --- a/src/services/alert/adapter/index.js +++ /dev/null @@ -1,18 +0,0 @@ -/* eslint-disable no-unused-vars */ -class Adapter { - constructor() { - if (new.target === Adapter) { - throw new TypeError('Cannot construct Abstract instances directly'); - } - } - - /** - * Return - * @param {String} instances - */ - async getMessage(instances) { - throw new Error('You have to implement the method getMessage!'); - } -} - -module.exports = Adapter; diff --git a/src/services/alert/adapter/message.js b/src/services/alert/adapter/message.js deleted file mode 100644 index 8d63e7c8be5..00000000000 --- a/src/services/alert/adapter/message.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Unified message format - */ -class Message { - constructor(title, text, timestamp, page, messageId, url, status, createdAt) { - this.mTitle = title || ''; - this.mText = text || ''; - this.mTimestamp = timestamp || '1970-01-01 00:00:00'; - // Origin of Message - this.mPage = page || ''; - this.mMessageId = messageId || '-1'; - this.mUrl = url || ''; - this.mStatus = status || ''; - this.mCreatedAt = createdAt || ''; - } - - get getMessage() { - // Unified message format - const message = { - title: this.mTitle, - text: this.mText, - status: this.mStatus, - origin: { - page: this.mPage, - message_id: this.mMessageId, - }, - timestamp: this.mTimestamp, - url: this.mUrl, - createdAt: this.mCreatedAt, - }; - return message; - } - - /** - * Set Title of Message - */ - set title(value) { - this.mTitle = value; - } - - /** - * Set Text of Message - */ - set text(value) { - this.mText = value; - } - - /** - * Set Timestamp of Message - */ - set timestamp(value) { - this.mTimestamp = value; - } - - /** - * Set Origin of Message - */ - set page(value) { - this.mPage = value; - } - - /** - * Set Id of Message - */ - set messageId(value) { - this.mMessageId = value; - } - - /** - * Set URL to link to - */ - set url(value) { - this.mUrl = value; - } - - /** - * Set Status of message - */ - set status(value) { - this.mStatus = value; - } - - /** - * Set Status of message - */ - set createdAt(value) { - this.mCreatedAt = value; - } -} - -module.exports = Message; diff --git a/src/services/alert/adapter/status.js b/src/services/alert/adapter/status.js deleted file mode 100644 index 57a9c91548d..00000000000 --- a/src/services/alert/adapter/status.js +++ /dev/null @@ -1,48 +0,0 @@ -const { Configuration } = require('@hpi-schul-cloud/commons'); -const Status = require('../MessageProvider/status'); -const Adapter = require('./index'); -const Message = require('./message'); - -class StatusAdapter extends Adapter { - async getMessage(instance) { - const data = { - success: false, - messages: [], - }; - - const getStatus = (number) => { - switch (number) { - case 2: - return 'danger'; - case 4: - return 'done'; - default: - return 'info'; - } - }; - - // get raw data from Message Provider - const rawData = await Status.getData(instance); - // transform raw data in unified message format - if (rawData) { - rawData.forEach((element) => { - const message = new Message(); - message.title = element.name; - message.text = element.message; - message.status = getStatus(element.status); - message.page = 'status'; - message.messageId = element.id; - message.timestamp = element.updated_at; - message.createdAt = element.created_at; - message.url = Configuration.get('ALERT_STATUS_URL'); - data.messages.push(message.getMessage); - }); - data.success = true; - } else { - data.success = false; - } - return data; - } -} - -module.exports = StatusAdapter; diff --git a/src/services/alert/cache.js b/src/services/alert/cache.js deleted file mode 100644 index de45f6c0cc5..00000000000 --- a/src/services/alert/cache.js +++ /dev/null @@ -1,58 +0,0 @@ -const { SC_THEME } = require('../../../config/globals'); - -const MessageProvider = []; -let messages = null; -let lastUpdatedTimestamp = 0; - -class Cache { - /** - * @param {number} time how long message should remain cached in minutes - */ - constructor(time) { - this.time = time; - } - - async updateMessages() { - let success = false; - let newMessages = []; - - // set last updated always to avoid DoS in error state - // set last updated here to avoid updating cache simultaneously for multiple times - lastUpdatedTimestamp = Date.now(); - - const promises = MessageProvider.map(async (provider) => { - const data = await provider.getMessage(SC_THEME); - if (!data.success) { - success = false; - return; - } - newMessages = newMessages.concat(data.messages); - success = true; - }); - - await Promise.all(promises); - - if (success) { - messages = newMessages; - } - } - - async getMessages() { - if (lastUpdatedTimestamp < Date.now() - 1000 * 60 * this.time) { - if (!messages) { - await this.updateMessages(); - } else { - this.updateMessages(); - } - } - return messages || []; - } - - addMessageProvider(provider, featureEnabled) { - if (featureEnabled) { - MessageProvider.push(provider); - } - } -} - -module.exports = Cache; diff --git a/src/services/alert/docs/openapi.yaml b/src/services/alert/docs/openapi.yaml deleted file mode 100644 index cfabda27bf5..00000000000 --- a/src/services/alert/docs/openapi.yaml +++ /dev/null @@ -1,74 +0,0 @@ -security: - - jwtBearer: [] -info: - title: HPI Schul-Cloud Alert Service API - description: - This is the API specification for the HPI Schul-Cloud Alert service. - - contact: - name: support - email: info@dbildungscloud.de - license: - name: GPL-3.0 - url: 'https://github.com/hpi-schul-cloud/schulcloud-server/blob/master/LICENSE' - version: 1.0.0 -components: - securitySchemes: - jwtBearer: - type: http - scheme: bearer - bearerFormat: JWT - schemas: - alert: - description: TODO - alert_list: - description: TODO - -paths: - /alert: - get: - parameters: - - description: Number of results to return - in: query - name: $limit - schema: - type: integer - - description: Number of results to skip - in: query - name: $skip - schema: - type: integer - - description: Property to sort results - in: query - name: $sort - style: deepObject - schema: - type: object - - description: Query parameters to filter - in: query - name: filter - style: form - explode: true - schema: - $ref: '#/components/schemas/alert' - responses: - '200': - description: success - content: - application/json: - schema: - $ref: '#/components/schemas/alert_list' - '401': - description: not authenticated - '500': - description: general error - description: Retrieves a list of all resources from the service. - summary: '' - tags: - - alert - security: [] - -openapi: 3.0.2 -tags: - - name: alert - description: An alert service. diff --git a/src/services/alert/index.js b/src/services/alert/index.js deleted file mode 100644 index 42c2db3df4b..00000000000 --- a/src/services/alert/index.js +++ /dev/null @@ -1,43 +0,0 @@ -const hooks = require('feathers-hooks-common'); -const { Configuration } = require('@hpi-schul-cloud/commons'); -const { static: staticContent } = require('@feathersjs/express'); -const path = require('path'); - -const Cache = require('./cache'); - -// add Message Provider Adapter here -const StatusAdapter = require('./adapter/status'); - -const cache = new Cache(1); -// add Message Provider here -cache.addMessageProvider(new StatusAdapter(), Configuration.has('ALERT_STATUS_URL')); - -/** - * Service to get an array of alert messages from added Message Providers (e.g: status.hpi-schul-cloud.de) - */ -class AlertService { - async find() { - return cache.getMessages(); - } -} - -module.exports = function alert() { - const app = this; - - app.use('/alert/api', staticContent(path.join(__dirname, '/docs/openapi.yaml'))); - - app.use('/alert', new AlertService()); - const service = app.service('/alert'); - - service.hooks({ - before: { - all: [], - find: [], - get: [hooks.disallow()], - create: [hooks.disallow()], - update: [hooks.disallow()], - patch: [hooks.disallow()], - remove: [hooks.disallow()], - }, - }); -}; diff --git a/src/services/index.js b/src/services/index.js index 15c868e7bf2..930ad5fde2d 100644 --- a/src/services/index.js +++ b/src/services/index.js @@ -41,7 +41,6 @@ const webuntis = require('./webuntis'); const me = require('./me'); const help = require('./help'); const database = require('../utils/database'); -const alert = require('./alert'); const nexboard = require('./nexboard'); const etherpad = require('./etherpad'); const storageProvider = require('./storageProvider'); @@ -94,7 +93,6 @@ module.exports = function initializeServices() { app.configure(oauth2); app.configure(roster); app.configure(datasources); - app.configure(alert); app.configure(edusharing); app.configure(webuntis); app.configure(nexboard); From c0e4279be7af56b41141381601e21d75bd733537 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Tue, 16 Apr 2024 16:29:59 +0200 Subject: [PATCH 02/18] add basic test --- .../alert/adapter/dto/component.dto.ts | 8 --- .../modules/alert/adapter/dto/incident.dto.ts | 8 --- .../modules/alert/adapter/status.adapter.ts | 6 +- apps/server/src/modules/alert/config/index.ts | 1 + .../src/modules/alert/config/testConfig.ts | 8 +++ .../controller/api-test/alert.api.spec.ts | 57 +++++++++++++++++++ src/externalServices/index.js | 10 ---- src/externalServices/statusApi.js | 22 ------- 8 files changed, 69 insertions(+), 51 deletions(-) create mode 100644 apps/server/src/modules/alert/config/testConfig.ts create mode 100644 apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts delete mode 100644 src/externalServices/index.js delete mode 100644 src/externalServices/statusApi.js diff --git a/apps/server/src/modules/alert/adapter/dto/component.dto.ts b/apps/server/src/modules/alert/adapter/dto/component.dto.ts index 204f2d04c07..d11f8f8d234 100644 --- a/apps/server/src/modules/alert/adapter/dto/component.dto.ts +++ b/apps/server/src/modules/alert/adapter/dto/component.dto.ts @@ -11,9 +11,7 @@ export class ComponentDto { updated_at: Date, deleted_at: Date, enabled: boolean, - meta: any[], status_name: string, - tags: any[] ) { this.id = id; this.name = name; @@ -26,9 +24,7 @@ export class ComponentDto { this.updated_at = updated_at; this.deleted_at = deleted_at; this.enabled = enabled; - this.meta = meta; this.status_name = status_name; - this.tags = tags; } id: number; @@ -53,9 +49,5 @@ export class ComponentDto { enabled: boolean; - meta: any[]; - status_name: string; - - tags: any[]; } diff --git a/apps/server/src/modules/alert/adapter/dto/incident.dto.ts b/apps/server/src/modules/alert/adapter/dto/incident.dto.ts index 3f91ccc400f..7d8f80f7bf9 100644 --- a/apps/server/src/modules/alert/adapter/dto/incident.dto.ts +++ b/apps/server/src/modules/alert/adapter/dto/incident.dto.ts @@ -14,8 +14,6 @@ export class IncidentDto { user_id: number, notifications: boolean, is_resolved: boolean, - meta: any[], - updates: any[], human_status: string, latest_update_id: number, latest_status: number, @@ -38,8 +36,6 @@ export class IncidentDto { this.user_id = user_id; this.notifications = notifications; this.is_resolved = is_resolved; - this.meta = meta; - this.updates = updates; this.human_status = human_status; this.latest_update_id = latest_update_id; this.latest_status = latest_status; @@ -77,10 +73,6 @@ export class IncidentDto { is_resolved: boolean; - meta: any[]; - - updates: any[]; - human_status: string; latest_update_id: number; diff --git a/apps/server/src/modules/alert/adapter/status.adapter.ts b/apps/server/src/modules/alert/adapter/status.adapter.ts index c961f6c87b0..5f7fd1a9660 100644 --- a/apps/server/src/modules/alert/adapter/status.adapter.ts +++ b/apps/server/src/modules/alert/adapter/status.adapter.ts @@ -53,10 +53,10 @@ export class StatusAdapter { const filteredData = rawData.data.filter((element) => element.status !== statusEnum.fixed); filteredData.map(async (element) => { - const isinstance = await this.getInstance(instance, element.component_id); - if (isinstance !== Importance.ALL_INSTANCES && isinstance !== Importance.INGORE) { + const isInstance = await this.getInstance(instance, element.component_id); + if (isInstance !== Importance.ALL_INSTANCES && isInstance !== Importance.INGORE) { instanceSpecific.push(element); - } else if (isinstance !== Importance.INGORE) { + } else if (isInstance !== Importance.INGORE) { noneSpecific.push(element); } }); diff --git a/apps/server/src/modules/alert/config/index.ts b/apps/server/src/modules/alert/config/index.ts index 736a69133ce..3c5e80242a6 100644 --- a/apps/server/src/modules/alert/config/index.ts +++ b/apps/server/src/modules/alert/config/index.ts @@ -1 +1,2 @@ export * from './alert-config'; +export * from './testConfig'; diff --git a/apps/server/src/modules/alert/config/testConfig.ts b/apps/server/src/modules/alert/config/testConfig.ts new file mode 100644 index 00000000000..7b1676328fc --- /dev/null +++ b/apps/server/src/modules/alert/config/testConfig.ts @@ -0,0 +1,8 @@ +import { config } from './alert-config'; + +export const alertTestConfig = () => { + const conf = config(); + conf.INSTANCE = 'dbc'; + conf.ALERT_STATUS_URL = 'test'; + return conf; +}; diff --git a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts new file mode 100644 index 00000000000..c8ded6326a2 --- /dev/null +++ b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts @@ -0,0 +1,57 @@ +import { INestApplication } from '@nestjs/common'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { HttpService } from '@nestjs/axios'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ServerTestModule } from '@modules/server'; +import request from 'supertest'; +import { of } from 'rxjs'; +import { axiosResponseFactory } from '@shared/testing'; +import { ConfigModule } from '@nestjs/config'; +import { createConfigModuleOptions } from '@src/config'; +import { alertTestConfig } from '../../config'; +import { AlertResponse } from '../dto'; + +describe('Alert Controller api', () => { + const alertPath = '/alert'; + + let app: INestApplication; + let httpService: DeepMocked; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ServerTestModule, ConfigModule.forRoot(createConfigModuleOptions(alertTestConfig))], + providers: [ + { + provide: HttpService, + useValue: createMock(), + }, + ], + }).compile(); + app = module.createNestApplication(); + await app.init(); + httpService = module.get(HttpService); + }); + + afterAll(async () => { + await app.close(); + }); + + describe('[GET]', () => { + describe('when no incidents', () => { + const setup = () => { + jest.spyOn(httpService, 'get').mockImplementation(() => { + const response = axiosResponseFactory.build({ data: [] }); + return of(response); + }); + }; + + it('should return empty alert list', async () => { + setup(); + const response = await request(app.getHttpServer()).get(alertPath).expect(200); + + const { data } = response.body as AlertResponse; + expect(data.length).toBe(0); + }); + }); + }); +}); diff --git a/src/externalServices/index.js b/src/externalServices/index.js deleted file mode 100644 index e30c29092f0..00000000000 --- a/src/externalServices/index.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Please add external api services to this location to get an overview over connections. - * For any request that is send please use const Api = require('./apiHelper'); - */ - -const statusApi = require('./statusApi'); - -module.exports = { - statusApi, -}; diff --git a/src/externalServices/statusApi.js b/src/externalServices/statusApi.js deleted file mode 100644 index 6b71611240a..00000000000 --- a/src/externalServices/statusApi.js +++ /dev/null @@ -1,22 +0,0 @@ -const { Configuration } = require('@hpi-schul-cloud/commons'); -const Api = require('./apiHelper'); - -const statusURL = Configuration.get('ALERT_STATUS_URL'); -const baseURL = statusURL.concat('/api/v1'); - -const statusApi = () => { - const api = new Api({ - baseURL, - }); - // TODO: if possible request only related time not all - const getIncidents = (sort = 'id') => api.get('/incidents', { params: { sort } }).then((response) => response.data); - - const getComponent = (componentId) => api.get(`/components/${componentId}`).then((response) => response.data); - - return { - getIncidents, - getComponent, - }; -}; - -module.exports = statusApi(); From 2ba863cede85bd1c6fdbf78644d59b159a2ab8d8 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Tue, 16 Apr 2024 16:51:07 +0200 Subject: [PATCH 03/18] fix lint --- apps/server/src/modules/alert/adapter/dto/component.dto.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/server/src/modules/alert/adapter/dto/component.dto.ts b/apps/server/src/modules/alert/adapter/dto/component.dto.ts index d11f8f8d234..b5d80853327 100644 --- a/apps/server/src/modules/alert/adapter/dto/component.dto.ts +++ b/apps/server/src/modules/alert/adapter/dto/component.dto.ts @@ -11,7 +11,7 @@ export class ComponentDto { updated_at: Date, deleted_at: Date, enabled: boolean, - status_name: string, + status_name: string ) { this.id = id; this.name = name; From 08e5356cbc4789324531a4c554b24c282ac185e1 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Tue, 16 Apr 2024 18:07:58 +0200 Subject: [PATCH 04/18] add api test --- apps/server/src/modules/alert/config/index.ts | 1 - .../controller/api-test/alert.api.spec.ts | 50 ++++++++++++++++++- .../alert/testing/component.factory.ts | 4 ++ .../modules/alert/testing/incident.factory.ts | 26 ++++++++++ .../server/src/modules/alert/testing/index.ts | 3 ++ .../alert/{config => testing}/testConfig.ts | 4 +- 6 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 apps/server/src/modules/alert/testing/component.factory.ts create mode 100644 apps/server/src/modules/alert/testing/incident.factory.ts create mode 100644 apps/server/src/modules/alert/testing/index.ts rename apps/server/src/modules/alert/{config => testing}/testConfig.ts (59%) diff --git a/apps/server/src/modules/alert/config/index.ts b/apps/server/src/modules/alert/config/index.ts index 3c5e80242a6..736a69133ce 100644 --- a/apps/server/src/modules/alert/config/index.ts +++ b/apps/server/src/modules/alert/config/index.ts @@ -1,2 +1 @@ export * from './alert-config'; -export * from './testConfig'; diff --git a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts index c8ded6326a2..06b3ae4380a 100644 --- a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts +++ b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts @@ -8,11 +8,20 @@ import { of } from 'rxjs'; import { axiosResponseFactory } from '@shared/testing'; import { ConfigModule } from '@nestjs/config'; import { createConfigModuleOptions } from '@src/config'; -import { alertTestConfig } from '../../config'; +import { alertTestConfig, createComponent, createIncident } from '../../testing'; import { AlertResponse } from '../dto'; +import { ComponentDto, ComponentResponse, IncidentsResponse, MetaDto } from '../../adapter/dto'; describe('Alert Controller api', () => { const alertPath = '/alert'; + const incidentsPath = '/api/v1/incidents'; + const componentsPath = './api/v1/components/'; + const incident1 = createIncident(1, 1, 2); + const incident2 = createIncident(2, 2, 4); + const incident3 = createIncident(3, 3, 0); + const component1 = createComponent(1, 1); + const component2 = createComponent(2, 2); + const component3 = createComponent(3, 3); let app: INestApplication; let httpService: DeepMocked; @@ -53,5 +62,44 @@ describe('Alert Controller api', () => { expect(data.length).toBe(0); }); }); + + describe('when incidents available', () => { + const setup = () => { + jest.spyOn(httpService, 'get').mockImplementation((url) => { + if (url === incidentsPath) { + const incidents = [incident1, incident2, incident3]; + const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); + const response = axiosResponseFactory.build({ data: incidentResponse }); + return of(response); + } + + if (url.startsWith(componentsPath)) { + const componentId = url.at(-1); + let component: ComponentDto; + if (componentId === '1') { + component = component1; + } + if (componentId === '1') { + component = component2; + } else { + component = component3; + } + const componentResponse = new ComponentResponse(component); + const response = axiosResponseFactory.build({ data: componentResponse }); + return of(response); + } + const response = axiosResponseFactory.build({ data: [] }); + return of(response); + }); + }; + + it('should return filtered alert list', async () => { + setup(); + const response = await request(app.getHttpServer()).get(alertPath).expect(200); + + const { data } = response.body as AlertResponse; + expect(data.length).toBe(2); + }); + }); }); }); diff --git a/apps/server/src/modules/alert/testing/component.factory.ts b/apps/server/src/modules/alert/testing/component.factory.ts new file mode 100644 index 00000000000..a7e5e9fcb29 --- /dev/null +++ b/apps/server/src/modules/alert/testing/component.factory.ts @@ -0,0 +1,4 @@ +import { ComponentDto } from '../adapter/dto'; + +export const createComponent = (id: number, groupId: number) => + new ComponentDto(id, 'test', 'test', 'test', 1, 0, groupId, new Date(), new Date(), new Date(), true, 'test'); diff --git a/apps/server/src/modules/alert/testing/incident.factory.ts b/apps/server/src/modules/alert/testing/incident.factory.ts new file mode 100644 index 00000000000..3415b15421e --- /dev/null +++ b/apps/server/src/modules/alert/testing/incident.factory.ts @@ -0,0 +1,26 @@ +import { IncidentDto } from '../adapter/dto'; + +export const createIncident = (id: number, componentId: number, status: number) => + new IncidentDto( + 1, + componentId, + 'test', + status, + 'test', + new Date(), + new Date(), + new Date(), + 1, + false, + new Date(), + 1, + false, + false, + 'test', + 0, + 0, + 'test', + 'test', + 'test', + 0 + ); diff --git a/apps/server/src/modules/alert/testing/index.ts b/apps/server/src/modules/alert/testing/index.ts new file mode 100644 index 00000000000..c77c3587529 --- /dev/null +++ b/apps/server/src/modules/alert/testing/index.ts @@ -0,0 +1,3 @@ +export * from './testConfig'; +export * from './incident.factory'; +export * from './component.factory'; diff --git a/apps/server/src/modules/alert/config/testConfig.ts b/apps/server/src/modules/alert/testing/testConfig.ts similarity index 59% rename from apps/server/src/modules/alert/config/testConfig.ts rename to apps/server/src/modules/alert/testing/testConfig.ts index 7b1676328fc..95b2013373a 100644 --- a/apps/server/src/modules/alert/config/testConfig.ts +++ b/apps/server/src/modules/alert/testing/testConfig.ts @@ -1,8 +1,8 @@ -import { config } from './alert-config'; +import { config } from '../config/alert-config'; export const alertTestConfig = () => { const conf = config(); - conf.INSTANCE = 'dbc'; + conf.INSTANCE = 'DEFAULT'; conf.ALERT_STATUS_URL = 'test'; return conf; }; From a49c03276f48f95a3cd0931e47e5ec80340c9aac Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Tue, 16 Apr 2024 20:28:07 +0200 Subject: [PATCH 05/18] fix api tests --- .../src/modules/alert/adapter/enum/index.ts | 1 - .../modules/alert/adapter/enum/instance.ts | 7 --- .../modules/alert/adapter/status.adapter.ts | 45 +++++++++++++++---- .../src/modules/alert/config/alert-config.ts | 13 ------ apps/server/src/modules/alert/config/index.ts | 1 - .../alert/controller/alert.controller.ts | 4 +- .../controller/api-test/alert.api.spec.ts | 33 +++++++------- .../modules/alert/service/cache.service.ts | 19 ++++---- .../server/src/modules/alert/testing/index.ts | 1 - .../src/modules/alert/testing/testConfig.ts | 8 ---- 10 files changed, 65 insertions(+), 67 deletions(-) delete mode 100644 apps/server/src/modules/alert/adapter/enum/instance.ts delete mode 100644 apps/server/src/modules/alert/config/alert-config.ts delete mode 100644 apps/server/src/modules/alert/config/index.ts delete mode 100644 apps/server/src/modules/alert/testing/testConfig.ts diff --git a/apps/server/src/modules/alert/adapter/enum/index.ts b/apps/server/src/modules/alert/adapter/enum/index.ts index 6a1a7abf613..35a6bea6b6b 100644 --- a/apps/server/src/modules/alert/adapter/enum/index.ts +++ b/apps/server/src/modules/alert/adapter/enum/index.ts @@ -1,2 +1 @@ export * from './importance'; -export * from './instance'; diff --git a/apps/server/src/modules/alert/adapter/enum/instance.ts b/apps/server/src/modules/alert/adapter/enum/instance.ts deleted file mode 100644 index 499ef5a7900..00000000000 --- a/apps/server/src/modules/alert/adapter/enum/instance.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum Instance { - DEFAULT = 1, - BRB = 2, - OPEN = 3, - N21 = 6, - THR = 7, -} diff --git a/apps/server/src/modules/alert/adapter/status.adapter.ts b/apps/server/src/modules/alert/adapter/status.adapter.ts index 5f7fd1a9660..43c0d251a98 100644 --- a/apps/server/src/modules/alert/adapter/status.adapter.ts +++ b/apps/server/src/modules/alert/adapter/status.adapter.ts @@ -4,10 +4,10 @@ import { firstValueFrom } from 'rxjs'; import { AxiosResponse } from 'axios'; import { ErrorUtils } from '@src/core/error/utils'; import { ConfigService } from '@nestjs/config'; -import { Importance, Instance } from './enum'; +import { Importance } from './enum'; import { ComponentResponse, IncidentDto, IncidentsResponse, MessagesDto } from './dto'; -import { AlertConfig } from '../config'; import { MessageMapper } from '../controller/mapper'; +import { ServerConfig } from '../../server'; @Injectable() export class StatusAdapter { @@ -15,7 +15,7 @@ export class StatusAdapter { constructor( private readonly httpService: HttpService, - private readonly configService: ConfigService + private readonly configService: ConfigService ) { this.url = configService.get('ALERT_STATUS_URL'); } @@ -52,22 +52,24 @@ export class StatusAdapter { const statusEnum = { fixed: 4, danger: 2 }; const filteredData = rawData.data.filter((element) => element.status !== statusEnum.fixed); - filteredData.map(async (element) => { - const isInstance = await this.getInstance(instance, element.component_id); - if (isInstance !== Importance.ALL_INSTANCES && isInstance !== Importance.INGORE) { + const promises = filteredData.map(async (element) => { + const importance = await this.getImportance(instance, element.component_id); + if (importance !== Importance.ALL_INSTANCES && importance !== Importance.INGORE) { instanceSpecific.push(element); - } else if (isInstance !== Importance.INGORE) { + } else if (importance !== Importance.INGORE) { noneSpecific.push(element); } }); + await Promise.all(promises); + instanceSpecific.sort(this.compareIncidents); noneSpecific.sort(this.compareIncidents); return instanceSpecific.concat(noneSpecific); } - private async getInstance(instance: string, componentId: number): Promise { + private async getImportance(instance: string, componentId: number): Promise { if (componentId !== 0) { return this.getImportanceForComponent(instance, componentId); } @@ -77,7 +79,8 @@ export class StatusAdapter { private async getImportanceForComponent(instance: string, componentId: number): Promise { try { const response = await this.getComponent(componentId); - if (Instance[instance] && response.data.group_id === Instance[instance]) { + const instanceNumber = this.getInstanceNumber(instance); + if (instanceNumber === response.data.group_id) { return Importance.CURRENT_INSTANCE; } return Importance.INGORE; @@ -125,4 +128,28 @@ export class StatusAdapter { return 0; }; + + private getInstanceNumber(instance: string) { + if (instance.toLowerCase() === 'default') { + return 1; + } + + if (instance.toLowerCase() === 'brb') { + return 2; + } + + if (instance.toLowerCase() === 'open') { + return 3; + } + + if (instance.toLowerCase() === 'n21') { + return 6; + } + + if (instance.toLowerCase() === 'thr') { + return 7; + } + + return 0; + } } diff --git a/apps/server/src/modules/alert/config/alert-config.ts b/apps/server/src/modules/alert/config/alert-config.ts deleted file mode 100644 index cdc91b6c23d..00000000000 --- a/apps/server/src/modules/alert/config/alert-config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Configuration } from '@hpi-schul-cloud/commons/lib'; - -export interface AlertConfig { - INSTANCE: string; - ALERT_STATUS_URL: string; -} - -const alertConfig = { - INSTANCE: Configuration.get('SC_THEME') as string, - ALERT_STATUS_URL: Configuration.get('ALERT_STATUS_URL') as string, -}; - -export const config = () => alertConfig; diff --git a/apps/server/src/modules/alert/config/index.ts b/apps/server/src/modules/alert/config/index.ts deleted file mode 100644 index 736a69133ce..00000000000 --- a/apps/server/src/modules/alert/config/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './alert-config'; diff --git a/apps/server/src/modules/alert/controller/alert.controller.ts b/apps/server/src/modules/alert/controller/alert.controller.ts index 9249fcd2a94..6ce82bb13cc 100644 --- a/apps/server/src/modules/alert/controller/alert.controller.ts +++ b/apps/server/src/modules/alert/controller/alert.controller.ts @@ -11,8 +11,8 @@ export class AlertController { @ApiOperation({ summary: 'Get allerts' }) @ApiResponse({ status: 201, type: AlertResponse }) @Get() - public find() { - const messages = this.alertUc.find(); + public async find() { + const messages = await this.alertUc.find(); return new AlertResponse(messages); } diff --git a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts index 06b3ae4380a..8f996ac3515 100644 --- a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts +++ b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts @@ -2,20 +2,19 @@ import { INestApplication } from '@nestjs/common'; import { createMock, DeepMocked } from '@golevelup/ts-jest'; import { HttpService } from '@nestjs/axios'; import { Test, TestingModule } from '@nestjs/testing'; -import { ServerTestModule } from '@modules/server'; import request from 'supertest'; import { of } from 'rxjs'; import { axiosResponseFactory } from '@shared/testing'; -import { ConfigModule } from '@nestjs/config'; -import { createConfigModuleOptions } from '@src/config'; -import { alertTestConfig, createComponent, createIncident } from '../../testing'; +import { serverConfig, ServerTestModule } from '../../../server'; +import { createComponent, createIncident } from '../../testing'; import { AlertResponse } from '../dto'; import { ComponentDto, ComponentResponse, IncidentsResponse, MetaDto } from '../../adapter/dto'; +import { SchulcloudTheme } from '../../../server/types/schulcloud-theme.enum'; describe('Alert Controller api', () => { const alertPath = '/alert'; const incidentsPath = '/api/v1/incidents'; - const componentsPath = './api/v1/components/'; + const componentsPath = '/api/v1/components/'; const incident1 = createIncident(1, 1, 2); const incident2 = createIncident(2, 2, 4); const incident3 = createIncident(3, 3, 0); @@ -27,15 +26,16 @@ describe('Alert Controller api', () => { let httpService: DeepMocked; beforeAll(async () => { + const config = serverConfig(); + config.ALERT_STATUS_URL = 'test'; + config.SC_THEME = SchulcloudTheme.DEFAULT; + const module: TestingModule = await Test.createTestingModule({ - imports: [ServerTestModule, ConfigModule.forRoot(createConfigModuleOptions(alertTestConfig))], - providers: [ - { - provide: HttpService, - useValue: createMock(), - }, - ], - }).compile(); + imports: [ServerTestModule], + }) + .overrideProvider(HttpService) + .useValue(createMock()) + .compile(); app = module.createNestApplication(); await app.init(); httpService = module.get(HttpService); @@ -78,8 +78,7 @@ describe('Alert Controller api', () => { let component: ComponentDto; if (componentId === '1') { component = component1; - } - if (componentId === '1') { + } else if (componentId === '2') { component = component2; } else { component = component3; @@ -93,12 +92,12 @@ describe('Alert Controller api', () => { }); }; - it('should return filtered alert list', async () => { + it('should return filtered alert list by status and instance', async () => { setup(); const response = await request(app.getHttpServer()).get(alertPath).expect(200); const { data } = response.body as AlertResponse; - expect(data.length).toBe(2); + expect(data.length).toBe(1); }); }); }); diff --git a/apps/server/src/modules/alert/service/cache.service.ts b/apps/server/src/modules/alert/service/cache.service.ts index 5bd8d1c44de..0a478c84146 100644 --- a/apps/server/src/modules/alert/service/cache.service.ts +++ b/apps/server/src/modules/alert/service/cache.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; -import { AlertConfig } from '../config'; import { StatusAdapter } from '../adapter'; import { Message } from '../controller/dto'; +import { ServerConfig } from '../../server'; @Injectable() export class CacheService { @@ -17,22 +17,23 @@ export class CacheService { private instance: string; constructor( - private readonly configService: ConfigService, + private readonly configService: ConfigService, private readonly statusAdapter: StatusAdapter ) { + this.instance = configService.get('SC_THEME'); + this.time = 1; + if (configService.get('ALERT_STATUS_URL')) { this.addMessageProvider(statusAdapter, true); } - this.instance = configService.get('INSTANCE'); - this.time = 1; } - public updateMessages() { + public async updateMessages() { let success = false; let newMessages: Message[] = []; this.lastUpdatedTimestamp = Date.now(); - this.messageProviders.map(async (provider) => { + const promises = this.messageProviders.map(async (provider) => { const data = await provider.getMessage(this.instance); if (!data.success) { success = false; @@ -42,14 +43,16 @@ export class CacheService { success = true; }); + await Promise.all(promises); + if (success) { this.messages = newMessages; } } - public getMessages() { + public async getMessages() { if (this.lastUpdatedTimestamp < Date.now() - 1000 * 60 * this.time) { - this.updateMessages(); + await this.updateMessages(); } return this.messages || []; diff --git a/apps/server/src/modules/alert/testing/index.ts b/apps/server/src/modules/alert/testing/index.ts index c77c3587529..71769082003 100644 --- a/apps/server/src/modules/alert/testing/index.ts +++ b/apps/server/src/modules/alert/testing/index.ts @@ -1,3 +1,2 @@ -export * from './testConfig'; export * from './incident.factory'; export * from './component.factory'; diff --git a/apps/server/src/modules/alert/testing/testConfig.ts b/apps/server/src/modules/alert/testing/testConfig.ts deleted file mode 100644 index 95b2013373a..00000000000 --- a/apps/server/src/modules/alert/testing/testConfig.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { config } from '../config/alert-config'; - -export const alertTestConfig = () => { - const conf = config(); - conf.INSTANCE = 'DEFAULT'; - conf.ALERT_STATUS_URL = 'test'; - return conf; -}; From d65eca93131c98d24dbc9a25360633c755cf74d5 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Tue, 16 Apr 2024 22:56:35 +0200 Subject: [PATCH 06/18] fix api test --- .../src/modules/alert/controller/api-test/alert.api.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts index 8f996ac3515..e02df5915ab 100644 --- a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts +++ b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts @@ -25,7 +25,7 @@ describe('Alert Controller api', () => { let app: INestApplication; let httpService: DeepMocked; - beforeAll(async () => { + beforeEach(async () => { const config = serverConfig(); config.ALERT_STATUS_URL = 'test'; config.SC_THEME = SchulcloudTheme.DEFAULT; @@ -39,6 +39,7 @@ describe('Alert Controller api', () => { app = module.createNestApplication(); await app.init(); httpService = module.get(HttpService); + jest.useFakeTimers(); }); afterAll(async () => { From 9bb1044ccdb08392b1864858517c91232bec54a2 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Wed, 17 Apr 2024 16:27:20 +0200 Subject: [PATCH 07/18] fix test --- .../controller/api-test/alert.api.spec.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts index e02df5915ab..efa28f2542c 100644 --- a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts +++ b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts @@ -15,12 +15,12 @@ describe('Alert Controller api', () => { const alertPath = '/alert'; const incidentsPath = '/api/v1/incidents'; const componentsPath = '/api/v1/components/'; - const incident1 = createIncident(1, 1, 2); - const incident2 = createIncident(2, 2, 4); - const incident3 = createIncident(3, 3, 0); - const component1 = createComponent(1, 1); - const component2 = createComponent(2, 2); - const component3 = createComponent(3, 3); + const incident1 = createIncident(1, 0, 2); + const incident2 = createIncident(2, 1, 4); + const incident3 = createIncident(3, 2, 0); + const component1 = createComponent(0, 1); + const component2 = createComponent(1, 2); + const component3 = createComponent(2, 3); let app: INestApplication; let httpService: DeepMocked; @@ -77,9 +77,9 @@ describe('Alert Controller api', () => { if (url.startsWith(componentsPath)) { const componentId = url.at(-1); let component: ComponentDto; - if (componentId === '1') { + if (componentId === '0') { component = component1; - } else if (componentId === '2') { + } else if (componentId === '1') { component = component2; } else { component = component3; @@ -98,7 +98,7 @@ describe('Alert Controller api', () => { const response = await request(app.getHttpServer()).get(alertPath).expect(200); const { data } = response.body as AlertResponse; - expect(data.length).toBe(1); + expect(data.length).toBe(2); }); }); }); From ede9fac1b142d8401020770b96587b05dde94382 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Wed, 17 Apr 2024 16:37:27 +0200 Subject: [PATCH 08/18] fix test --- .../alert/controller/api-test/alert.api.spec.ts | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts index efa28f2542c..f04c9bed201 100644 --- a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts +++ b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts @@ -16,11 +16,10 @@ describe('Alert Controller api', () => { const incidentsPath = '/api/v1/incidents'; const componentsPath = '/api/v1/components/'; const incident1 = createIncident(1, 0, 2); - const incident2 = createIncident(2, 1, 4); - const incident3 = createIncident(3, 2, 0); - const component1 = createComponent(0, 1); - const component2 = createComponent(1, 2); - const component3 = createComponent(2, 3); + const incident2 = createIncident(2, 1, 0); + const incident3 = createIncident(3, 2, 4); + const component1 = createComponent(1, 1); + const component2 = createComponent(2, 2); let app: INestApplication; let httpService: DeepMocked; @@ -77,12 +76,10 @@ describe('Alert Controller api', () => { if (url.startsWith(componentsPath)) { const componentId = url.at(-1); let component: ComponentDto; - if (componentId === '0') { + if (componentId === '1') { component = component1; - } else if (componentId === '1') { - component = component2; } else { - component = component3; + component = component2; } const componentResponse = new ComponentResponse(component); const response = axiosResponseFactory.build({ data: componentResponse }); From 65853524bb553c3208acb335c53f175ff26549a1 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Wed, 17 Apr 2024 18:12:03 +0200 Subject: [PATCH 09/18] add status adapter test --- .../alert/adapter/status.adapter.spec.ts | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 apps/server/src/modules/alert/adapter/status.adapter.spec.ts diff --git a/apps/server/src/modules/alert/adapter/status.adapter.spec.ts b/apps/server/src/modules/alert/adapter/status.adapter.spec.ts new file mode 100644 index 00000000000..d381f18385b --- /dev/null +++ b/apps/server/src/modules/alert/adapter/status.adapter.spec.ts @@ -0,0 +1,205 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { HttpService } from '@nestjs/axios'; +import { createMock, DeepMocked } from '@golevelup/ts-jest'; +import { ConfigService } from '@nestjs/config'; +import { ServerConfig } from '@modules/server'; +import { axiosResponseFactory } from '@shared/testing'; +import { of, throwError } from 'rxjs'; +import { StatusAdapter } from './status.adapter'; +import { ComponentDto, ComponentResponse, IncidentsResponse, MetaDto } from './dto'; +import { createComponent, createIncident } from '../testing'; + +describe('StatusAdapter', () => { + const incidentsPath = '/api/v1/incidents'; + const componentsPath = '/api/v1/components/'; + const incident = createIncident(2, 1, 0); + const incidentDefault = createIncident(1, 1, 0); + const incidentBrb = createIncident(2, 2, 0); + const incidentOpen = createIncident(3, 3, 0); + const incidentN21 = createIncident(4, 4, 0); + const incidentThr = createIncident(5, 5, 0); + const componentDefault = createComponent(1, 1); + const componentBrb = createComponent(2, 2); + const componentOpen = createComponent(3, 3); + const componentN21 = createComponent(4, 6); + const componentThr = createComponent(5, 7); + + let module: TestingModule; + let adapter: StatusAdapter; + let httpService: DeepMocked; + + beforeAll(async () => { + module = await Test.createTestingModule({ + providers: [ + StatusAdapter, + { provide: HttpService, useValue: createMock() }, + { provide: ConfigService, useValue: createMock>({ get: () => 'test.url' }) }, + ], + }).compile(); + + adapter = module.get(StatusAdapter); + httpService = module.get(HttpService); + }); + + afterAll(async () => { + await module.close(); + }); + + it('should be defined', () => { + expect(adapter).toBeDefined(); + }); + + describe('getMessage', () => { + describe('when no incidents', () => { + const setup = () => { + jest.spyOn(httpService, 'get').mockReturnValue(of(axiosResponseFactory.build({ data: [] }))); + }; + it('should return empty data', async () => { + setup(); + + const data = await adapter.getMessage('default'); + + expect(data.success).toBe(false); + expect(data.messages.length).toBe(0); + }); + }); + + describe('when get incidents failed', () => { + const setup = () => { + jest.spyOn(httpService, 'get').mockReturnValue(throwError(() => 'error')); + }; + it('should return empty data', async () => { + setup(); + + const data = await adapter.getMessage('default'); + + expect(data.success).toBe(false); + expect(data.messages.length).toBe(0); + }); + }); + + describe('when get components failed', () => { + const setup = () => { + jest.spyOn(httpService, 'get').mockImplementation((url) => { + if (url === incidentsPath) { + const incidents = [incident]; + const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); + const response = axiosResponseFactory.build({ data: incidentResponse }); + return of(response); + } + + if (url.startsWith(componentsPath)) { + throwError(() => 'error'); + } + const response = axiosResponseFactory.build({ data: [] }); + return of(response); + }); + }; + it('should set importance to ignore', async () => { + setup(); + + const data = await adapter.getMessage('default'); + + expect(data.success).toBe(true); + expect(data.messages.length).toBe(0); + }); + }); + + describe('when incidents for different instances provided', () => { + const setup = () => { + jest.spyOn(httpService, 'get').mockImplementation((url) => { + if (url === incidentsPath) { + const incidents = [incidentDefault, incidentBrb, incidentOpen, incidentN21, incidentThr]; + const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); + const response = axiosResponseFactory.build({ data: incidentResponse }); + return of(response); + } + + if (url.startsWith(componentsPath)) { + const componentId = url.at(-1); + let component: ComponentDto; + if (componentId === '1') { + component = componentDefault; + } else if (componentId === '2') { + component = componentBrb; + } else if (componentId === '3') { + component = componentOpen; + } else if (componentId === '4') { + component = componentN21; + } else { + component = componentThr; + } + const componentResponse = new ComponentResponse(component); + const response = axiosResponseFactory.build({ data: componentResponse }); + return of(response); + } + const response = axiosResponseFactory.build({ data: [] }); + return of(response); + }); + }; + it.each(['default', 'brb', 'open', 'n21', 'thr', 'no_instance'])( + 'should return incident only for %s instance', + async (instance) => { + setup(); + + const data = await adapter.getMessage(instance); + + expect(data.success).toBe(true); + if (instance !== 'no_instance') { + expect(data.messages.length).toBe(1); + } else { + expect(data.messages.length).toBe(0); + } + } + ); + }); + + describe('when many incidents provided', () => { + const setup = () => { + jest.spyOn(httpService, 'get').mockImplementation((url) => { + if (url === incidentsPath) { + const firstIncident = createIncident(1, 0, 1); + firstIncident.updated_at = new Date('2024-01-01 10:00:00'); + firstIncident.created_at = new Date('2024-01-01 10:00:00'); + firstIncident.name = '1'; + const secondIncident = createIncident(1, 0, 1); + secondIncident.updated_at = new Date('2024-01-01 10:00:00'); + secondIncident.created_at = new Date('2024-01-01 10:00:00'); + secondIncident.name = '2'; + const thirdIncident = createIncident(1, 0, 2); + thirdIncident.updated_at = new Date('2024-01-03 10:00:00'); + thirdIncident.created_at = new Date('2024-01-02 10:00:00'); + thirdIncident.name = '3'; + const fourthIncident = createIncident(1, 0, 2); + fourthIncident.updated_at = new Date('2024-01-03 10:00:00'); + fourthIncident.created_at = new Date('2024-01-01 10:00:00'); + fourthIncident.name = '4'; + const fifthIncident = createIncident(1, 0, 2); + fifthIncident.updated_at = new Date('2024-01-01 10:00:00'); + fifthIncident.created_at = new Date('2024-01-01 10:00:00'); + fifthIncident.name = '5'; + const incidents = [fifthIncident, fourthIncident, thirdIncident, firstIncident, secondIncident]; + const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); + const response = axiosResponseFactory.build({ data: incidentResponse }); + return of(response); + } + const response = axiosResponseFactory.build({ data: [] }); + return of(response); + }); + }; + it('should return sorted incidents', async () => { + setup(); + + const data = await adapter.getMessage('default'); + + expect(data.success).toBe(true); + expect(data.messages.length).toBe(5); + expect(data.messages[0].title).toBe('1'); + expect(data.messages[1].title).toBe('2'); + expect(data.messages[2].title).toBe('3'); + expect(data.messages[3].title).toBe('4'); + expect(data.messages[4].title).toBe('5'); + }); + }); + }); +}); From bf90fb9b54c86e97680ec87885a883b4138e9045 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Thu, 18 Apr 2024 13:16:40 +0200 Subject: [PATCH 10/18] add tests --- .../alert/adapter/dto/incidents.response.ts | 6 +--- .../src/modules/alert/adapter/dto/index.ts | 3 -- .../modules/alert/adapter/dto/links.dto.ts | 10 ------ .../src/modules/alert/adapter/dto/meta.dto.ts | 9 ------ .../alert/adapter/dto/pagination.dto.ts | 31 ------------------- .../alert/adapter/status.adapter.spec.ts | 13 ++++---- .../modules/alert/adapter/status.adapter.ts | 4 +-- .../controller/api-test/alert.api.spec.ts | 4 +-- .../controller/mapper/message.mapper.spec.ts | 28 +++++++++++++++++ .../alert/controller/mapper/message.mapper.ts | 2 +- .../modules/alert/service/cache.service.ts | 4 +-- config/default.schema.json | 2 +- 12 files changed, 44 insertions(+), 72 deletions(-) delete mode 100644 apps/server/src/modules/alert/adapter/dto/links.dto.ts delete mode 100644 apps/server/src/modules/alert/adapter/dto/meta.dto.ts delete mode 100644 apps/server/src/modules/alert/adapter/dto/pagination.dto.ts create mode 100644 apps/server/src/modules/alert/controller/mapper/message.mapper.spec.ts diff --git a/apps/server/src/modules/alert/adapter/dto/incidents.response.ts b/apps/server/src/modules/alert/adapter/dto/incidents.response.ts index f25ea9665ba..862c1a7f629 100644 --- a/apps/server/src/modules/alert/adapter/dto/incidents.response.ts +++ b/apps/server/src/modules/alert/adapter/dto/incidents.response.ts @@ -1,13 +1,9 @@ import { IncidentDto } from './incident.dto'; -import { MetaDto } from './meta.dto'; export class IncidentsResponse { - constructor(meta: MetaDto, data: IncidentDto[]) { - this.meta = meta; + constructor(data: IncidentDto[]) { this.data = data; } - meta: MetaDto; - data: IncidentDto[]; } diff --git a/apps/server/src/modules/alert/adapter/dto/index.ts b/apps/server/src/modules/alert/adapter/dto/index.ts index dc6f1837720..fc93ba08715 100644 --- a/apps/server/src/modules/alert/adapter/dto/index.ts +++ b/apps/server/src/modules/alert/adapter/dto/index.ts @@ -2,7 +2,4 @@ export * from './component.dto'; export * from './component.response'; export * from './incident.dto'; export * from './incidents.response'; -export * from './links.dto'; export * from './messages.dto'; -export * from './meta.dto'; -export * from './pagination.dto'; diff --git a/apps/server/src/modules/alert/adapter/dto/links.dto.ts b/apps/server/src/modules/alert/adapter/dto/links.dto.ts deleted file mode 100644 index 29f69a34fa4..00000000000 --- a/apps/server/src/modules/alert/adapter/dto/links.dto.ts +++ /dev/null @@ -1,10 +0,0 @@ -export class LinksDto { - constructor(next_page: number, previous_page: number) { - this.next_page = next_page; - this.previous_page = previous_page; - } - - next_page: number; - - previous_page: number; -} diff --git a/apps/server/src/modules/alert/adapter/dto/meta.dto.ts b/apps/server/src/modules/alert/adapter/dto/meta.dto.ts deleted file mode 100644 index 8f509c2cda2..00000000000 --- a/apps/server/src/modules/alert/adapter/dto/meta.dto.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { PaginationDto } from './pagination.dto'; - -export class MetaDto { - constructor(pagination: PaginationDto) { - this.pagination = pagination; - } - - pagination: PaginationDto; -} diff --git a/apps/server/src/modules/alert/adapter/dto/pagination.dto.ts b/apps/server/src/modules/alert/adapter/dto/pagination.dto.ts deleted file mode 100644 index 3f601339c1a..00000000000 --- a/apps/server/src/modules/alert/adapter/dto/pagination.dto.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { LinksDto } from './links.dto'; - -export class PaginationDto { - constructor( - total: number, - count: number, - per_page: number, - current_page: number, - total_pages: number, - links: LinksDto - ) { - this.total = total; - this.count = count; - this.per_page = per_page; - this.current_page = current_page; - this.total_pages = total_pages; - this.links = links; - } - - total: number; - - count: number; - - per_page: number; - - current_page: number; - - total_pages: number; - - links: LinksDto; -} diff --git a/apps/server/src/modules/alert/adapter/status.adapter.spec.ts b/apps/server/src/modules/alert/adapter/status.adapter.spec.ts index d381f18385b..6e6bc97900a 100644 --- a/apps/server/src/modules/alert/adapter/status.adapter.spec.ts +++ b/apps/server/src/modules/alert/adapter/status.adapter.spec.ts @@ -5,6 +5,7 @@ import { ConfigService } from '@nestjs/config'; import { ServerConfig } from '@modules/server'; import { axiosResponseFactory } from '@shared/testing'; import { of, throwError } from 'rxjs'; +import { AxiosError } from 'axios'; import { StatusAdapter } from './status.adapter'; import { ComponentDto, ComponentResponse, IncidentsResponse, MetaDto } from './dto'; import { createComponent, createIncident } from '../testing'; @@ -81,15 +82,15 @@ describe('StatusAdapter', () => { describe('when get components failed', () => { const setup = () => { jest.spyOn(httpService, 'get').mockImplementation((url) => { - if (url === incidentsPath) { + if (url.match(incidentsPath)) { const incidents = [incident]; const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); const response = axiosResponseFactory.build({ data: incidentResponse }); return of(response); } - if (url.startsWith(componentsPath)) { - throwError(() => 'error'); + if (url.match(componentsPath)) { + return throwError(() => new AxiosError()); } const response = axiosResponseFactory.build({ data: [] }); return of(response); @@ -108,14 +109,14 @@ describe('StatusAdapter', () => { describe('when incidents for different instances provided', () => { const setup = () => { jest.spyOn(httpService, 'get').mockImplementation((url) => { - if (url === incidentsPath) { + if (url.match(incidentsPath)) { const incidents = [incidentDefault, incidentBrb, incidentOpen, incidentN21, incidentThr]; const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); const response = axiosResponseFactory.build({ data: incidentResponse }); return of(response); } - if (url.startsWith(componentsPath)) { + if (url.match(componentsPath)) { const componentId = url.at(-1); let component: ComponentDto; if (componentId === '1') { @@ -157,7 +158,7 @@ describe('StatusAdapter', () => { describe('when many incidents provided', () => { const setup = () => { jest.spyOn(httpService, 'get').mockImplementation((url) => { - if (url === incidentsPath) { + if (url.match(incidentsPath)) { const firstIncident = createIncident(1, 0, 1); firstIncident.updated_at = new Date('2024-01-01 10:00:00'); firstIncident.created_at = new Date('2024-01-01 10:00:00'); diff --git a/apps/server/src/modules/alert/adapter/status.adapter.ts b/apps/server/src/modules/alert/adapter/status.adapter.ts index 43c0d251a98..99ba63e613e 100644 --- a/apps/server/src/modules/alert/adapter/status.adapter.ts +++ b/apps/server/src/modules/alert/adapter/status.adapter.ts @@ -90,7 +90,7 @@ export class StatusAdapter { } private async getComponent(componentId: number): Promise { - return firstValueFrom(this.httpService.get(`/api/v1/components/${componentId}`)) + return firstValueFrom(this.httpService.get(`${this.url}/api/v1/components/${componentId}`)) .then((response: AxiosResponse) => response.data) .catch((error) => { throw new InternalServerErrorException( @@ -101,7 +101,7 @@ export class StatusAdapter { } private async getIncidents(): Promise { - return firstValueFrom(this.httpService.get('/api/v1/incidents', { params: { sort: 'id' } })) + return firstValueFrom(this.httpService.get(`${this.url}/api/v1/incidents`, { params: { sort: 'id' } })) .then((response: AxiosResponse) => response.data) .catch((error) => { throw new InternalServerErrorException( diff --git a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts index f04c9bed201..93a82ff6e27 100644 --- a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts +++ b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts @@ -66,14 +66,14 @@ describe('Alert Controller api', () => { describe('when incidents available', () => { const setup = () => { jest.spyOn(httpService, 'get').mockImplementation((url) => { - if (url === incidentsPath) { + if (url.match(incidentsPath)) { const incidents = [incident1, incident2, incident3]; const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); const response = axiosResponseFactory.build({ data: incidentResponse }); return of(response); } - if (url.startsWith(componentsPath)) { + if (url.match(componentsPath)) { const componentId = url.at(-1); let component: ComponentDto; if (componentId === '1') { diff --git a/apps/server/src/modules/alert/controller/mapper/message.mapper.spec.ts b/apps/server/src/modules/alert/controller/mapper/message.mapper.spec.ts new file mode 100644 index 00000000000..df5b62a4784 --- /dev/null +++ b/apps/server/src/modules/alert/controller/mapper/message.mapper.spec.ts @@ -0,0 +1,28 @@ +import { MessageMapper } from './message.mapper'; +import { IncidentDto } from '../../adapter/dto'; + +describe('MessageMapper', () => { + describe('map to message', () => { + describe('when empty object', () => { + it('should map to defaults', () => { + const message = MessageMapper.mapToMessage({} as IncidentDto, ''); + + expect(message.title).toBe(''); + expect(message.text).toBe(''); + expect(message.timestamp).toBe('1970-01-01 00:00:00'); + expect(message.origin.message_id).toBe(-1); + expect(message.origin.page).toBe('status'); + expect(message.url).toBe(''); + expect(message.status).toBe('info'); + }); + }); + }); + + describe('get status', () => { + it('should return correct status from number', () => { + const statuses = [1, 2, 4].map((nb) => MessageMapper.getStatus(nb)); + + expect(statuses).toEqual(['info', 'danger', 'done']); + }); + }); +}); diff --git a/apps/server/src/modules/alert/controller/mapper/message.mapper.ts b/apps/server/src/modules/alert/controller/mapper/message.mapper.ts index c72cc00709b..42303435e6e 100644 --- a/apps/server/src/modules/alert/controller/mapper/message.mapper.ts +++ b/apps/server/src/modules/alert/controller/mapper/message.mapper.ts @@ -2,7 +2,7 @@ import { IncidentDto } from '../../adapter/dto'; import { Message, MessageOrigin, MessageStatus } from '../dto'; export class MessageMapper { - static mapToMessage(incident: IncidentDto, url: string) { + static mapToMessage(incident: IncidentDto, url: string): Message { return new Message( incident.name || '', incident.message || '', diff --git a/apps/server/src/modules/alert/service/cache.service.ts b/apps/server/src/modules/alert/service/cache.service.ts index 0a478c84146..57497f3c5ae 100644 --- a/apps/server/src/modules/alert/service/cache.service.ts +++ b/apps/server/src/modules/alert/service/cache.service.ts @@ -6,7 +6,7 @@ import { ServerConfig } from '../../server'; @Injectable() export class CacheService { - private time: number; + private readonly time: number; private lastUpdatedTimestamp = 0; @@ -14,7 +14,7 @@ export class CacheService { private messageProviders: StatusAdapter[] = []; - private instance: string; + private readonly instance: string; constructor( private readonly configService: ConfigService, diff --git a/config/default.schema.json b/config/default.schema.json index ea655ec06b5..dfdf3ad15ba 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -542,7 +542,7 @@ }, "ALERT_STATUS_URL": { "type": "string", - "default": null, + "default": "https://status.dbildungscloud.dev/", "description": "The url of status message provider (should end without a slash)." }, "NEXBOARD_URL": { From f32f07c5a871655e0fca8b5fd4674b6755df895c Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Thu, 18 Apr 2024 13:32:47 +0200 Subject: [PATCH 11/18] fix tests --- .../src/modules/alert/adapter/status.adapter.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/server/src/modules/alert/adapter/status.adapter.spec.ts b/apps/server/src/modules/alert/adapter/status.adapter.spec.ts index 6e6bc97900a..4eeef059e74 100644 --- a/apps/server/src/modules/alert/adapter/status.adapter.spec.ts +++ b/apps/server/src/modules/alert/adapter/status.adapter.spec.ts @@ -7,7 +7,7 @@ import { axiosResponseFactory } from '@shared/testing'; import { of, throwError } from 'rxjs'; import { AxiosError } from 'axios'; import { StatusAdapter } from './status.adapter'; -import { ComponentDto, ComponentResponse, IncidentsResponse, MetaDto } from './dto'; +import { ComponentDto, ComponentResponse, IncidentsResponse } from './dto'; import { createComponent, createIncident } from '../testing'; describe('StatusAdapter', () => { @@ -84,7 +84,7 @@ describe('StatusAdapter', () => { jest.spyOn(httpService, 'get').mockImplementation((url) => { if (url.match(incidentsPath)) { const incidents = [incident]; - const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); + const incidentResponse = new IncidentsResponse(incidents); const response = axiosResponseFactory.build({ data: incidentResponse }); return of(response); } @@ -111,7 +111,7 @@ describe('StatusAdapter', () => { jest.spyOn(httpService, 'get').mockImplementation((url) => { if (url.match(incidentsPath)) { const incidents = [incidentDefault, incidentBrb, incidentOpen, incidentN21, incidentThr]; - const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); + const incidentResponse = new IncidentsResponse(incidents); const response = axiosResponseFactory.build({ data: incidentResponse }); return of(response); } @@ -180,7 +180,7 @@ describe('StatusAdapter', () => { fifthIncident.created_at = new Date('2024-01-01 10:00:00'); fifthIncident.name = '5'; const incidents = [fifthIncident, fourthIncident, thirdIncident, firstIncident, secondIncident]; - const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); + const incidentResponse = new IncidentsResponse(incidents); const response = axiosResponseFactory.build({ data: incidentResponse }); return of(response); } From 27fdf3068bcd9452f4df004719134930e687c4e5 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Thu, 18 Apr 2024 13:55:46 +0200 Subject: [PATCH 12/18] fix tests --- .../src/modules/alert/controller/api-test/alert.api.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts index 93a82ff6e27..e5149f6c53f 100644 --- a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts +++ b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts @@ -8,7 +8,7 @@ import { axiosResponseFactory } from '@shared/testing'; import { serverConfig, ServerTestModule } from '../../../server'; import { createComponent, createIncident } from '../../testing'; import { AlertResponse } from '../dto'; -import { ComponentDto, ComponentResponse, IncidentsResponse, MetaDto } from '../../adapter/dto'; +import { ComponentDto, ComponentResponse, IncidentsResponse } from '../../adapter/dto'; import { SchulcloudTheme } from '../../../server/types/schulcloud-theme.enum'; describe('Alert Controller api', () => { @@ -68,7 +68,7 @@ describe('Alert Controller api', () => { jest.spyOn(httpService, 'get').mockImplementation((url) => { if (url.match(incidentsPath)) { const incidents = [incident1, incident2, incident3]; - const incidentResponse = new IncidentsResponse({} as MetaDto, incidents); + const incidentResponse = new IncidentsResponse(incidents); const response = axiosResponseFactory.build({ data: incidentResponse }); return of(response); } From 107f0a161d541d5685c3b2dfc417bb487d9ca2fe Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Fri, 19 Apr 2024 09:29:50 +0200 Subject: [PATCH 13/18] restore configuration --- config/default.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/default.schema.json b/config/default.schema.json index dfdf3ad15ba..ea655ec06b5 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -542,7 +542,7 @@ }, "ALERT_STATUS_URL": { "type": "string", - "default": "https://status.dbildungscloud.dev/", + "default": null, "description": "The url of status message provider (should end without a slash)." }, "NEXBOARD_URL": { From 3928f1d2d58b4ccd52d0066d90c93824e28855d4 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Wed, 24 Apr 2024 18:05:53 +0200 Subject: [PATCH 14/18] add alert config --- .../modules/alert/adapter/status.adapter.ts | 4 ++-- apps/server/src/modules/alert/alert.config.ts | 19 +++++++++++++++++++ apps/server/src/modules/alert/alert.module.ts | 4 ++-- apps/server/src/modules/alert/index.ts | 2 ++ ...ache.service.ts => alert-cache.service.ts} | 12 ++++++------ .../server/src/modules/alert/service/index.ts | 2 +- apps/server/src/modules/alert/uc/alert.uc.ts | 4 ++-- .../src/modules/server/server.config.ts | 5 ++++- config/default.schema.json | 5 +++++ 9 files changed, 43 insertions(+), 14 deletions(-) create mode 100644 apps/server/src/modules/alert/alert.config.ts create mode 100644 apps/server/src/modules/alert/index.ts rename apps/server/src/modules/alert/service/{cache.service.ts => alert-cache.service.ts} (83%) diff --git a/apps/server/src/modules/alert/adapter/status.adapter.ts b/apps/server/src/modules/alert/adapter/status.adapter.ts index 99ba63e613e..c12b2a1641e 100644 --- a/apps/server/src/modules/alert/adapter/status.adapter.ts +++ b/apps/server/src/modules/alert/adapter/status.adapter.ts @@ -4,10 +4,10 @@ import { firstValueFrom } from 'rxjs'; import { AxiosResponse } from 'axios'; import { ErrorUtils } from '@src/core/error/utils'; import { ConfigService } from '@nestjs/config'; +import { AlertConfig } from '../alert.config'; import { Importance } from './enum'; import { ComponentResponse, IncidentDto, IncidentsResponse, MessagesDto } from './dto'; import { MessageMapper } from '../controller/mapper'; -import { ServerConfig } from '../../server'; @Injectable() export class StatusAdapter { @@ -15,7 +15,7 @@ export class StatusAdapter { constructor( private readonly httpService: HttpService, - private readonly configService: ConfigService + private readonly configService: ConfigService ) { this.url = configService.get('ALERT_STATUS_URL'); } diff --git a/apps/server/src/modules/alert/alert.config.ts b/apps/server/src/modules/alert/alert.config.ts new file mode 100644 index 00000000000..921589e53ca --- /dev/null +++ b/apps/server/src/modules/alert/alert.config.ts @@ -0,0 +1,19 @@ +import { Configuration } from '@hpi-schul-cloud/commons'; +import { SchulcloudTheme } from '../server/types/schulcloud-theme.enum'; + +export interface AlertConfig { + ALERT_CACHE_INTERVAL: number; + SC_THEME: SchulcloudTheme; + ALERT_STATUS_URL: string | null; +} + +const config: AlertConfig = { + ALERT_STATUS_URL: + Configuration.get('ALERT_STATUS_URL') === null + ? (Configuration.get('ALERT_STATUS_URL') as null) + : (Configuration.get('ALERT_STATUS_URL') as string), + SC_THEME: Configuration.get('SC_THEME') as SchulcloudTheme, + ALERT_CACHE_INTERVAL: Configuration.get('ALERT_CACHE_INTERVAL') as number, +}; + +export const alertConfig = () => config; diff --git a/apps/server/src/modules/alert/alert.module.ts b/apps/server/src/modules/alert/alert.module.ts index fa099430dd8..48dc0a66ad3 100644 --- a/apps/server/src/modules/alert/alert.module.ts +++ b/apps/server/src/modules/alert/alert.module.ts @@ -2,13 +2,13 @@ import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; import { AlertController } from './controller'; import { AlertUc } from './uc'; -import { CacheService } from './service'; +import { AlertCacheService } from './service'; import { StatusAdapter } from './adapter'; import { ToolConfigModule } from '../tool/tool-config.module'; @Module({ imports: [HttpModule, ToolConfigModule], controllers: [AlertController], - providers: [AlertUc, CacheService, StatusAdapter], + providers: [AlertUc, AlertCacheService, StatusAdapter], }) export class AlertModule {} diff --git a/apps/server/src/modules/alert/index.ts b/apps/server/src/modules/alert/index.ts new file mode 100644 index 00000000000..e54c9cd86d9 --- /dev/null +++ b/apps/server/src/modules/alert/index.ts @@ -0,0 +1,2 @@ +export { AlertConfig } from './alert.config'; +export { AlertModule } from './alert.module'; diff --git a/apps/server/src/modules/alert/service/cache.service.ts b/apps/server/src/modules/alert/service/alert-cache.service.ts similarity index 83% rename from apps/server/src/modules/alert/service/cache.service.ts rename to apps/server/src/modules/alert/service/alert-cache.service.ts index 57497f3c5ae..e94df87396b 100644 --- a/apps/server/src/modules/alert/service/cache.service.ts +++ b/apps/server/src/modules/alert/service/alert-cache.service.ts @@ -2,11 +2,11 @@ import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { StatusAdapter } from '../adapter'; import { Message } from '../controller/dto'; -import { ServerConfig } from '../../server'; +import { AlertConfig } from '../alert.config'; @Injectable() -export class CacheService { - private readonly time: number; +export class AlertCacheService { + private readonly cacheInterval: number; private lastUpdatedTimestamp = 0; @@ -17,11 +17,11 @@ export class CacheService { private readonly instance: string; constructor( - private readonly configService: ConfigService, + private readonly configService: ConfigService, private readonly statusAdapter: StatusAdapter ) { this.instance = configService.get('SC_THEME'); - this.time = 1; + this.cacheInterval = configService.get('ALERT_CACHE_INTERVAL'); if (configService.get('ALERT_STATUS_URL')) { this.addMessageProvider(statusAdapter, true); @@ -51,7 +51,7 @@ export class CacheService { } public async getMessages() { - if (this.lastUpdatedTimestamp < Date.now() - 1000 * 60 * this.time) { + if (this.lastUpdatedTimestamp < Date.now() - 1000 * 60 * this.cacheInterval) { await this.updateMessages(); } diff --git a/apps/server/src/modules/alert/service/index.ts b/apps/server/src/modules/alert/service/index.ts index 865d362ca53..b6e6fe7676f 100644 --- a/apps/server/src/modules/alert/service/index.ts +++ b/apps/server/src/modules/alert/service/index.ts @@ -1 +1 @@ -export * from './cache.service'; +export * from './alert-cache.service'; diff --git a/apps/server/src/modules/alert/uc/alert.uc.ts b/apps/server/src/modules/alert/uc/alert.uc.ts index 641746506aa..791fd6d70c3 100644 --- a/apps/server/src/modules/alert/uc/alert.uc.ts +++ b/apps/server/src/modules/alert/uc/alert.uc.ts @@ -1,9 +1,9 @@ import { Injectable } from '@nestjs/common'; -import { CacheService } from '../service'; +import { AlertCacheService } from '../service'; @Injectable() export class AlertUc { - constructor(private readonly cacheService: CacheService) {} + constructor(private readonly cacheService: AlertCacheService) {} public find() { return this.cacheService.getMessages(); diff --git a/apps/server/src/modules/server/server.config.ts b/apps/server/src/modules/server/server.config.ts index a8b11b6a512..9cd5285bb42 100644 --- a/apps/server/src/modules/server/server.config.ts +++ b/apps/server/src/modules/server/server.config.ts @@ -23,6 +23,7 @@ import { VideoConferenceConfiguration, type IVideoConferenceSettings } from '@mo import { LanguageType } from '@shared/domain/interface'; import type { CoreModuleConfig } from '@src/core'; import type { MailConfig } from '@src/infra/mail/interfaces/mail-config'; +import { AlertConfig } from '@modules/alert'; import { SchulcloudTheme } from './types/schulcloud-theme.enum'; import { Timezone } from './types/timezone.enum'; @@ -59,7 +60,8 @@ export interface ServerConfig SynchronizationConfig, DeletionConfig, CollaborativeTextEditorConfig, - ProvisioningConfig { + ProvisioningConfig, + AlertConfig { NODE_ENV: NodeEnvType; SC_DOMAIN: string; ACCESSIBILITY_REPORT_EMAIL: string; @@ -229,6 +231,7 @@ const config: ServerConfig = { FEATURE_OTHER_GROUPUSERS_PROVISIONING_ENABLED: Configuration.get( 'FEATURE_OTHER_GROUPUSERS_PROVISIONING_ENABLED' ) as boolean, + ALERT_CACHE_INTERVAL: Configuration.get('ALERT_CACHE_INTERVAL') as number, }; export const serverConfig = () => config; diff --git a/config/default.schema.json b/config/default.schema.json index 461e2d44d5d..099da7b3e9e 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -545,6 +545,11 @@ "default": null, "description": "The url of status message provider (should end without a slash)." }, + "ALERT_CACHE_INTERVAL": { + "type": "number", + "default": 1, + "description": "Time between updating alerts (in minutes)" + }, "NEXBOARD_URL": { "type": "string", "format": "uri", From a96e3ab5438618e1e2c48cea6bce75bcc3a1931a Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Wed, 24 Apr 2024 19:04:36 +0200 Subject: [PATCH 15/18] add test --- .../src/modules/alert/alert.config.spec.ts | 42 +++++++++++++++++++ apps/server/src/modules/alert/alert.config.ts | 20 ++++----- 2 files changed, 52 insertions(+), 10 deletions(-) create mode 100644 apps/server/src/modules/alert/alert.config.spec.ts diff --git a/apps/server/src/modules/alert/alert.config.spec.ts b/apps/server/src/modules/alert/alert.config.spec.ts new file mode 100644 index 00000000000..72ad18f841d --- /dev/null +++ b/apps/server/src/modules/alert/alert.config.spec.ts @@ -0,0 +1,42 @@ +import { IConfig } from '@hpi-schul-cloud/commons/lib/interfaces/IConfig'; +import { Configuration } from '@hpi-schul-cloud/commons/lib'; +import { getAlertConfig } from './alert.config'; + +describe(getAlertConfig.name, () => { + let configBefore: IConfig; + + beforeAll(() => { + configBefore = Configuration.toObject({ plainSecrets: true }); + }); + + afterEach(() => { + Configuration.reset(configBefore); + }); + + describe('when called', () => { + const setup = () => { + const baseUrl = 'http://alert-status:3349'; + const instance = 'brb'; + + Configuration.set('ALERT_STATUS_URL', baseUrl); + Configuration.set('SC_THEME', instance); + Configuration.set('ALERT_CACHE_INTERVAL', 1); + + const expectedConfig = { + ALERT_CACHE_INTERVAL: 1, + SC_THEME: instance, + ALERT_STATUS_URL: baseUrl, + }; + + return { expectedConfig }; + }; + + it('should return config with proper values', () => { + const { expectedConfig } = setup(); + + const config = getAlertConfig(); + + expect(config).toEqual(expectedConfig); + }); + }); +}); diff --git a/apps/server/src/modules/alert/alert.config.ts b/apps/server/src/modules/alert/alert.config.ts index 921589e53ca..bc2b4cb19dd 100644 --- a/apps/server/src/modules/alert/alert.config.ts +++ b/apps/server/src/modules/alert/alert.config.ts @@ -1,4 +1,4 @@ -import { Configuration } from '@hpi-schul-cloud/commons'; +import { Configuration } from '@hpi-schul-cloud/commons/lib'; import { SchulcloudTheme } from '../server/types/schulcloud-theme.enum'; export interface AlertConfig { @@ -7,13 +7,13 @@ export interface AlertConfig { ALERT_STATUS_URL: string | null; } -const config: AlertConfig = { - ALERT_STATUS_URL: - Configuration.get('ALERT_STATUS_URL') === null - ? (Configuration.get('ALERT_STATUS_URL') as null) - : (Configuration.get('ALERT_STATUS_URL') as string), - SC_THEME: Configuration.get('SC_THEME') as SchulcloudTheme, - ALERT_CACHE_INTERVAL: Configuration.get('ALERT_CACHE_INTERVAL') as number, +export const getAlertConfig = (): AlertConfig => { + return { + ALERT_STATUS_URL: + Configuration.get('ALERT_STATUS_URL') === null + ? (Configuration.get('ALERT_STATUS_URL') as null) + : (Configuration.get('ALERT_STATUS_URL') as string), + SC_THEME: Configuration.get('SC_THEME') as SchulcloudTheme, + ALERT_CACHE_INTERVAL: Configuration.get('ALERT_CACHE_INTERVAL') as number, + }; }; - -export const alertConfig = () => config; From fc4e04dc51bf2d64d9141a242a72c422bc090cc6 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Wed, 24 Apr 2024 19:48:43 +0200 Subject: [PATCH 16/18] add handling diffrent status responses --- .../alert/adapter/status.adapter.spec.ts | 41 ++++++++++++++++ .../modules/alert/adapter/status.adapter.ts | 48 +++++++++++++------ 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/apps/server/src/modules/alert/adapter/status.adapter.spec.ts b/apps/server/src/modules/alert/adapter/status.adapter.spec.ts index 4eeef059e74..c35c17bd001 100644 --- a/apps/server/src/modules/alert/adapter/status.adapter.spec.ts +++ b/apps/server/src/modules/alert/adapter/status.adapter.spec.ts @@ -79,6 +79,20 @@ describe('StatusAdapter', () => { }); }); + describe('when get incidents return status 500', () => { + const setup = () => { + jest.spyOn(httpService, 'get').mockReturnValue(of(axiosResponseFactory.build({ status: 500 }))); + }; + it('should return empty data', async () => { + setup(); + + const data = await adapter.getMessage('default'); + + expect(data.success).toBe(false); + expect(data.messages.length).toBe(0); + }); + }); + describe('when get components failed', () => { const setup = () => { jest.spyOn(httpService, 'get').mockImplementation((url) => { @@ -106,6 +120,33 @@ describe('StatusAdapter', () => { }); }); + describe('when get components return status 404', () => { + const setup = () => { + jest.spyOn(httpService, 'get').mockImplementation((url) => { + if (url.match(incidentsPath)) { + const incidents = [incident]; + const incidentResponse = new IncidentsResponse(incidents); + const response = axiosResponseFactory.build({ data: incidentResponse }); + return of(response); + } + + if (url.match(componentsPath)) { + return of(axiosResponseFactory.build({ status: 500 })); + } + const response = axiosResponseFactory.build({ data: [] }); + return of(response); + }); + }; + it('should set importance to ignore', async () => { + setup(); + + const data = await adapter.getMessage('default'); + + expect(data.success).toBe(true); + expect(data.messages.length).toBe(0); + }); + }); + describe('when incidents for different instances provided', () => { const setup = () => { jest.spyOn(httpService, 'get').mockImplementation((url) => { diff --git a/apps/server/src/modules/alert/adapter/status.adapter.ts b/apps/server/src/modules/alert/adapter/status.adapter.ts index c12b2a1641e..412c691aad8 100644 --- a/apps/server/src/modules/alert/adapter/status.adapter.ts +++ b/apps/server/src/modules/alert/adapter/status.adapter.ts @@ -90,25 +90,43 @@ export class StatusAdapter { } private async getComponent(componentId: number): Promise { - return firstValueFrom(this.httpService.get(`${this.url}/api/v1/components/${componentId}`)) - .then((response: AxiosResponse) => response.data) - .catch((error) => { - throw new InternalServerErrorException( - null, - ErrorUtils.createHttpExceptionOptions(error, 'StatusAdapter:getComponent') - ); - }); + try { + const request = this.httpService.get(`${this.url}/api/v1/components/${componentId}`); + + const resp = await firstValueFrom(request); + + if (resp.status !== 200) { + throw new Error(`invalid HTTP status code in a response from the server - ${resp.status} instead of 200`); + } + + return resp.data; + } catch (error) { + throw new InternalServerErrorException( + null, + ErrorUtils.createHttpExceptionOptions(error, 'StatusAdapter:getComponent') + ); + } } private async getIncidents(): Promise { - return firstValueFrom(this.httpService.get(`${this.url}/api/v1/incidents`, { params: { sort: 'id' } })) - .then((response: AxiosResponse) => response.data) - .catch((error) => { - throw new InternalServerErrorException( - null, - ErrorUtils.createHttpExceptionOptions(error, 'StatusAdapter:getIncidents') - ); + try { + const request = this.httpService.get(`${this.url}/api/v1/incidents`, { + params: { sort: 'id' }, }); + + const resp = await firstValueFrom(request); + + if (resp.status !== 200 && resp.status !== 202) { + throw new Error(`invalid HTTP status code in a response from the server - ${resp.status} instead of 202`); + } + + return resp.data; + } catch (error) { + throw new InternalServerErrorException( + null, + ErrorUtils.createHttpExceptionOptions(error, 'StatusAdapter:getIncidents') + ); + } } private compareIncidents = (a: IncidentDto, b: IncidentDto) => { From a36b14420c92a175325e0fd5c3ca292bc9ec777e Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Wed, 24 Apr 2024 20:08:50 +0200 Subject: [PATCH 17/18] fix lint --- apps/server/src/modules/alert/adapter/status.adapter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/server/src/modules/alert/adapter/status.adapter.ts b/apps/server/src/modules/alert/adapter/status.adapter.ts index 412c691aad8..e164bada4b2 100644 --- a/apps/server/src/modules/alert/adapter/status.adapter.ts +++ b/apps/server/src/modules/alert/adapter/status.adapter.ts @@ -1,7 +1,6 @@ import { HttpService } from '@nestjs/axios'; import { Injectable, InternalServerErrorException } from '@nestjs/common'; import { firstValueFrom } from 'rxjs'; -import { AxiosResponse } from 'axios'; import { ErrorUtils } from '@src/core/error/utils'; import { ConfigService } from '@nestjs/config'; import { AlertConfig } from '../alert.config'; From 077f97b62cac398770e9c1ea97827a459488b060 Mon Sep 17 00:00:00 2001 From: Tomasz Wiaderek Date: Thu, 25 Apr 2024 19:54:39 +0200 Subject: [PATCH 18/18] refactor --- .../src/modules/alert/alert.config.spec.ts | 42 ------------------- apps/server/src/modules/alert/alert.config.ts | 16 +------ apps/server/src/modules/alert/alert.module.ts | 7 ++-- .../controller/api-test/alert.api.spec.ts | 2 +- .../alert/service/alert-cache.service.ts | 2 +- .../modules/server/api/dto/config.response.ts | 2 +- .../src/modules/server/server.config.ts | 4 +- apps/server/src/shared/domain/types/index.ts | 1 + .../domain}/types/schulcloud-theme.enum.ts | 0 config/default.schema.json | 2 +- 10 files changed, 13 insertions(+), 65 deletions(-) delete mode 100644 apps/server/src/modules/alert/alert.config.spec.ts rename apps/server/src/{modules/server => shared/domain}/types/schulcloud-theme.enum.ts (100%) diff --git a/apps/server/src/modules/alert/alert.config.spec.ts b/apps/server/src/modules/alert/alert.config.spec.ts deleted file mode 100644 index 72ad18f841d..00000000000 --- a/apps/server/src/modules/alert/alert.config.spec.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { IConfig } from '@hpi-schul-cloud/commons/lib/interfaces/IConfig'; -import { Configuration } from '@hpi-schul-cloud/commons/lib'; -import { getAlertConfig } from './alert.config'; - -describe(getAlertConfig.name, () => { - let configBefore: IConfig; - - beforeAll(() => { - configBefore = Configuration.toObject({ plainSecrets: true }); - }); - - afterEach(() => { - Configuration.reset(configBefore); - }); - - describe('when called', () => { - const setup = () => { - const baseUrl = 'http://alert-status:3349'; - const instance = 'brb'; - - Configuration.set('ALERT_STATUS_URL', baseUrl); - Configuration.set('SC_THEME', instance); - Configuration.set('ALERT_CACHE_INTERVAL', 1); - - const expectedConfig = { - ALERT_CACHE_INTERVAL: 1, - SC_THEME: instance, - ALERT_STATUS_URL: baseUrl, - }; - - return { expectedConfig }; - }; - - it('should return config with proper values', () => { - const { expectedConfig } = setup(); - - const config = getAlertConfig(); - - expect(config).toEqual(expectedConfig); - }); - }); -}); diff --git a/apps/server/src/modules/alert/alert.config.ts b/apps/server/src/modules/alert/alert.config.ts index bc2b4cb19dd..7189222c8af 100644 --- a/apps/server/src/modules/alert/alert.config.ts +++ b/apps/server/src/modules/alert/alert.config.ts @@ -1,19 +1,7 @@ -import { Configuration } from '@hpi-schul-cloud/commons/lib'; -import { SchulcloudTheme } from '../server/types/schulcloud-theme.enum'; +import { SchulcloudTheme } from '@shared/domain/types'; export interface AlertConfig { - ALERT_CACHE_INTERVAL: number; + ALERT_CACHE_INTERVAL_MIN: number; SC_THEME: SchulcloudTheme; ALERT_STATUS_URL: string | null; } - -export const getAlertConfig = (): AlertConfig => { - return { - ALERT_STATUS_URL: - Configuration.get('ALERT_STATUS_URL') === null - ? (Configuration.get('ALERT_STATUS_URL') as null) - : (Configuration.get('ALERT_STATUS_URL') as string), - SC_THEME: Configuration.get('SC_THEME') as SchulcloudTheme, - ALERT_CACHE_INTERVAL: Configuration.get('ALERT_CACHE_INTERVAL') as number, - }; -}; diff --git a/apps/server/src/modules/alert/alert.module.ts b/apps/server/src/modules/alert/alert.module.ts index 48dc0a66ad3..f45782908ef 100644 --- a/apps/server/src/modules/alert/alert.module.ts +++ b/apps/server/src/modules/alert/alert.module.ts @@ -1,14 +1,15 @@ import { Module } from '@nestjs/common'; import { HttpModule } from '@nestjs/axios'; +import { ConfigService } from '@nestjs/config'; import { AlertController } from './controller'; import { AlertUc } from './uc'; import { AlertCacheService } from './service'; import { StatusAdapter } from './adapter'; -import { ToolConfigModule } from '../tool/tool-config.module'; +import { AlertConfig } from './alert.config'; @Module({ - imports: [HttpModule, ToolConfigModule], + imports: [HttpModule], controllers: [AlertController], - providers: [AlertUc, AlertCacheService, StatusAdapter], + providers: [AlertUc, AlertCacheService, StatusAdapter, ConfigService], }) export class AlertModule {} diff --git a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts index e5149f6c53f..4913bdf0fad 100644 --- a/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts +++ b/apps/server/src/modules/alert/controller/api-test/alert.api.spec.ts @@ -5,11 +5,11 @@ import { Test, TestingModule } from '@nestjs/testing'; import request from 'supertest'; import { of } from 'rxjs'; import { axiosResponseFactory } from '@shared/testing'; +import { SchulcloudTheme } from '@shared/domain/types'; import { serverConfig, ServerTestModule } from '../../../server'; import { createComponent, createIncident } from '../../testing'; import { AlertResponse } from '../dto'; import { ComponentDto, ComponentResponse, IncidentsResponse } from '../../adapter/dto'; -import { SchulcloudTheme } from '../../../server/types/schulcloud-theme.enum'; describe('Alert Controller api', () => { const alertPath = '/alert'; diff --git a/apps/server/src/modules/alert/service/alert-cache.service.ts b/apps/server/src/modules/alert/service/alert-cache.service.ts index e94df87396b..69f1849a343 100644 --- a/apps/server/src/modules/alert/service/alert-cache.service.ts +++ b/apps/server/src/modules/alert/service/alert-cache.service.ts @@ -21,7 +21,7 @@ export class AlertCacheService { private readonly statusAdapter: StatusAdapter ) { this.instance = configService.get('SC_THEME'); - this.cacheInterval = configService.get('ALERT_CACHE_INTERVAL'); + this.cacheInterval = configService.get('ALERT_CACHE_INTERVAL_MIN'); if (configService.get('ALERT_STATUS_URL')) { this.addMessageProvider(statusAdapter, true); diff --git a/apps/server/src/modules/server/api/dto/config.response.ts b/apps/server/src/modules/server/api/dto/config.response.ts index a7133c730c9..23e6265d260 100644 --- a/apps/server/src/modules/server/api/dto/config.response.ts +++ b/apps/server/src/modules/server/api/dto/config.response.ts @@ -1,7 +1,7 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; import { LanguageType } from '@shared/domain/interface'; +import { SchulcloudTheme } from '@shared/domain/types'; import type { ServerConfig } from '../..'; -import { SchulcloudTheme } from '../../types/schulcloud-theme.enum'; import { Timezone } from '../../types/timezone.enum'; export class ConfigResponse { diff --git a/apps/server/src/modules/server/server.config.ts b/apps/server/src/modules/server/server.config.ts index 9cd5285bb42..57611ee3cde 100644 --- a/apps/server/src/modules/server/server.config.ts +++ b/apps/server/src/modules/server/server.config.ts @@ -24,7 +24,7 @@ import { LanguageType } from '@shared/domain/interface'; import type { CoreModuleConfig } from '@src/core'; import type { MailConfig } from '@src/infra/mail/interfaces/mail-config'; import { AlertConfig } from '@modules/alert'; -import { SchulcloudTheme } from './types/schulcloud-theme.enum'; +import { SchulcloudTheme } from '@shared/domain/types'; import { Timezone } from './types/timezone.enum'; export enum NodeEnvType { @@ -231,7 +231,7 @@ const config: ServerConfig = { FEATURE_OTHER_GROUPUSERS_PROVISIONING_ENABLED: Configuration.get( 'FEATURE_OTHER_GROUPUSERS_PROVISIONING_ENABLED' ) as boolean, - ALERT_CACHE_INTERVAL: Configuration.get('ALERT_CACHE_INTERVAL') as number, + ALERT_CACHE_INTERVAL_MIN: Configuration.get('ALERT_CACHE_INTERVAL_MIN') as number, }; export const serverConfig = () => config; diff --git a/apps/server/src/shared/domain/types/index.ts b/apps/server/src/shared/domain/types/index.ts index b47a0d4821b..dacc63de9ee 100644 --- a/apps/server/src/shared/domain/types/index.ts +++ b/apps/server/src/shared/domain/types/index.ts @@ -10,3 +10,4 @@ export * from './school-purpose.enum'; export * from './system.type'; export * from './task.types'; export * from './value-of'; +export * from './schulcloud-theme.enum'; diff --git a/apps/server/src/modules/server/types/schulcloud-theme.enum.ts b/apps/server/src/shared/domain/types/schulcloud-theme.enum.ts similarity index 100% rename from apps/server/src/modules/server/types/schulcloud-theme.enum.ts rename to apps/server/src/shared/domain/types/schulcloud-theme.enum.ts diff --git a/config/default.schema.json b/config/default.schema.json index 099da7b3e9e..5850d8c00e4 100644 --- a/config/default.schema.json +++ b/config/default.schema.json @@ -545,7 +545,7 @@ "default": null, "description": "The url of status message provider (should end without a slash)." }, - "ALERT_CACHE_INTERVAL": { + "ALERT_CACHE_INTERVAL_MIN": { "type": "number", "default": 1, "description": "Time between updating alerts (in minutes)"