cloud creation

This commit is contained in:
Первов Артем
2026-06-17 09:36:58 +03:00
commit 123bd97018
46 changed files with 12285 additions and 0 deletions

View File

@@ -0,0 +1,92 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { getIntegerConfig } from '../common/config-number';
import { normalizeRequiredText } from '../common/normalize-text';
import { GetHistoryDto } from './dto/get-history.dto';
import { chooseHistorySource } from './history-source';
import { createHistoryResponse } from './history.mapper';
import { HistoryRepository } from './history.repository';
import { HistoryResponse } from './history.types';
@Injectable()
export class HistoryService {
constructor(
private readonly repository: HistoryRepository,
private readonly config: ConfigService,
) {}
// Use-case для графиков: выбирает latest/raw/aggregate и возвращает единый API-формат.
async findSeries(query: GetHistoryDto): Promise<HistoryResponse> {
const edge = normalizeRequiredText(query.edge, 'edge');
const targetPoints = this.getTargetPoints(query.targetPoints);
const tags = await this.repository.resolveTags(edge, query.tags);
if (!tags.length) {
return createHistoryResponse({
edge,
source: 'empty',
targetPoints,
});
}
if (!query.from && !query.to) {
const rows = await this.repository.findLatest(edge, tags, targetPoints);
return createHistoryResponse({
edge,
source: 'latest',
targetPoints,
rows,
});
}
const { from, to } = this.parseRange(query);
const source = chooseHistorySource(from, to, targetPoints);
const rows =
source.name === 'raw'
? await this.repository.findRawRange(edge, tags, from, to, targetPoints)
: await this.repository.findAggregateRange(
edge,
tags,
from,
to,
targetPoints,
source,
query.valueMode ?? 'avg',
);
return createHistoryResponse({
edge,
from,
to,
source: source.name,
resolutionSeconds: source.intervalSeconds || null,
targetPoints,
valueMode: source.name === 'raw' ? 'raw' : query.valueMode ?? 'avg',
rows,
});
}
// Частично заданный диапазон поддерживается: open-ended сторона получает безопасный default.
private parseRange(query: GetHistoryDto): { from: Date; to: Date } {
const from = query.from ? new Date(query.from) : new Date(0);
const to = query.to ? new Date(query.to) : new Date();
if (Number.isNaN(from.getTime()) || Number.isNaN(to.getTime())) {
throw new BadRequestException('Invalid from/to date.');
}
if (from > to) {
throw new BadRequestException('from cannot be greater than to.');
}
return { from, to };
}
// Ограничивает плотность ответа, чтобы API не возвращал больше точек, чем способен отрисовать UI.
private getTargetPoints(requested?: number): number {
const defaultValue = getIntegerConfig(this.config, 'HISTORY_TARGET_POINTS', 2000);
const maxValue = getIntegerConfig(this.config, 'HISTORY_MAX_TARGET_POINTS', 2000);
const value = requested ?? defaultValue;
return Math.min(Math.max(Math.floor(value), 100), maxValue);
}
}