graph query

This commit is contained in:
Evert Prants 2023-09-19 20:06:34 +03:00
parent 1ec89c72c1
commit feb6ba49ed
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
6 changed files with 109 additions and 15 deletions

View File

@ -2,6 +2,7 @@ import { Controller, Get, Query, UseInterceptors } from '@nestjs/common';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager'; import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager';
import { HistoryQueryDto } from './dtos/history-query.dto'; import { HistoryQueryDto } from './dtos/history-query.dto';
import { GraphQueryDto } from './dtos/graph-query.dto';
@Controller() @Controller()
export class AppController { export class AppController {
@ -18,4 +19,9 @@ export class AppController {
getWeatherHistory(@Query() query: HistoryQueryDto) { getWeatherHistory(@Query() query: HistoryQueryDto) {
return this.appService.getWeatherHistory(query); return this.appService.getWeatherHistory(query);
} }
@Get('graph')
makeGraph(@Query() query: GraphQueryDto) {
return this.appService.graphWeatherHistory(query);
}
} }

View File

@ -5,11 +5,12 @@ import {
OnApplicationShutdown, OnApplicationShutdown,
} from '@nestjs/common'; } from '@nestjs/common';
import WS1080 from './module/ws1080'; import WS1080 from './module/ws1080';
import { MoreThan, Repository } from 'typeorm'; import { Between, LessThan, MoreThan, Repository } from 'typeorm';
import { WeatherEntity } from './entities/weather.entity'; import { WeatherEntity } from './entities/weather.entity';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { Cron } from '@nestjs/schedule'; import { Cron } from '@nestjs/schedule';
import { HistoryQueryDto } from './dtos/history-query.dto'; import { HistoryQueryDto } from './dtos/history-query.dto';
import { GraphQueryDto } from './dtos/graph-query.dto';
@Injectable() @Injectable()
export class AppService implements OnApplicationShutdown { export class AppService implements OnApplicationShutdown {
@ -21,10 +22,14 @@ export class AppService implements OnApplicationShutdown {
private readonly weatherRepository: Repository<WeatherEntity>, private readonly weatherRepository: Repository<WeatherEntity>,
) {} ) {}
/**
* Get current weather information.
* @returns Weather data
*/
async getWeather() { async getWeather() {
try { try {
// Retrieve from USB // Retrieve from USB
if (!this.station) this.station = WS1080.fromDevice(); await this.createStation();
const data = await this.station.read(); const data = await this.station.read();
// Save weather data to database // Save weather data to database
@ -50,25 +55,53 @@ export class AppService implements OnApplicationShutdown {
this.logger.error('Failed to retrieve weather data:', error.stack); this.logger.error('Failed to retrieve weather data:', error.stack);
// Retrieve previous entry on error // Retrieve previous entry on error
const previous = await this.getPreviousEntry();
if (!previous) throw new InternalServerErrorException();
return previous;
}
}
/**
* Get previous weather entry.
* @returns Previous weather data entry
*/
async getPreviousEntry() {
const [previous] = await this.weatherRepository.find({ const [previous] = await this.weatherRepository.find({
order: { date: -1 }, order: { date: -1 },
take: 1, take: 1,
}); });
if (!previous) throw new InternalServerErrorException(); if (!previous) return null;
previous.rain24h = await this.rainFall24h(previous.date); previous.rain24h = await this.rainFall24h(previous.date);
previous.fresh = false; previous.fresh = false;
return previous; return previous;
} }
}
async getWeatherHistory(query: HistoryQueryDto) { /**
* Get weather history.
* @param query Weather history search
* @returns Paginated history list
*/
async getWeatherHistory(
query: HistoryQueryDto,
select?: (keyof WeatherEntity)[],
) {
const pageSize = Number(query.pageSize) || 100; const pageSize = Number(query.pageSize) || 100;
const page = Number(query.page) || 1; const page = Number(query.page) || 1;
const [list, rowCount] = await this.weatherRepository.findAndCount({ const [list, rowCount] = await this.weatherRepository.findAndCount({
where: query.since select,
? { date: MoreThan(new Date(query.since)) } where:
query.since || query.until
? {
date:
query.since && query.until
? Between(new Date(query.since), new Date(query.until))
: query.since
? MoreThan(new Date(query.since))
: LessThan(new Date(query.until)),
}
: undefined, : undefined,
order: { date: -1 }, order: { date: -1 },
take: pageSize, take: pageSize,
@ -87,6 +120,42 @@ export class AppService implements OnApplicationShutdown {
}; };
} }
/**
* Get graph datasets for weather.
* @param query Weather history search
* @returns Paginated graph dataset
*/
async graphWeatherHistory(query: GraphQueryDto) {
const { list, pagination } = await this.getWeatherHistory(query, [
...(query.columns as (keyof WeatherEntity)[]),
'date',
]);
const datasets = query.columns.reduce(
(mass, key: keyof WeatherEntity) => ({
...mass,
[key]: { label: String(key), data: [] },
}),
{},
);
list.forEach((entry) => {
Object.keys(entry)
.filter((key) => !['id', 'date'].includes(key))
.forEach((key) => {
datasets[key].data.push({
x: entry.date.getTime(),
y: entry[key],
});
});
});
return {
list: Object.values(datasets),
pagination,
};
}
/** /**
* Get rainfall in the last 24h since `since` start point. * Get rainfall in the last 24h since `since` start point.
* @param since Time start point * @param since Time start point
@ -103,6 +172,19 @@ export class AppService implements OnApplicationShutdown {
return Number(rainfall) || 0; return Number(rainfall) || 0;
} }
/**
* Create station instance.
*/
async createStation() {
if (this.station) return;
this.station = WS1080.fromDevice();
const previous = await this.getPreviousEntry();
if (previous) {
this.station.previousRain = previous.totalRain;
}
}
@Cron('0 * * * *') @Cron('0 * * * *')
scheduledPulls() { scheduledPulls() {
this.getWeather().catch(() => { this.getWeather().catch(() => {

View File

@ -0,0 +1,5 @@
import { HistoryQueryDto } from './history-query.dto';
export interface GraphQueryDto extends HistoryQueryDto {
columns: string[];
}

View File

@ -1,5 +1,6 @@
export class HistoryQueryDto { export class HistoryQueryDto {
since?: string; since?: string;
until?: string;
page?: string; page?: string;
pageSize?: string; pageSize?: string;
} }

View File

@ -2,7 +2,7 @@ import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module'; import { AppModule } from './app.module';
async function bootstrap() { async function bootstrap() {
const app = await NestFactory.create(AppModule); const app = await NestFactory.create(AppModule, { cors: true });
await app.listen(3000); await app.listen(3000);
} }
bootstrap(); bootstrap();

View File

@ -44,7 +44,7 @@ const WIND_DIRS = [
* Then remove your device from your machine and plug it back in again * Then remove your device from your machine and plug it back in again
*/ */
class WS1080 { class WS1080 {
private previousRain = 0; public previousRain = 0;
constructor(private device: Device, private dInterface: Interface) {} constructor(private device: Device, private dInterface: Interface) {}