Skip to content

Commit

Permalink
add health server
Browse files Browse the repository at this point in the history
  • Loading branch information
tbhanson96 committed Sep 15, 2024
1 parent b24becb commit a2c4524
Show file tree
Hide file tree
Showing 17 changed files with 234 additions and 9 deletions.
2 changes: 2 additions & 0 deletions client/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { HomeComponent } from '@components/layout/main/home.component';
import { AuthService } from '@services/auth.service';
import { EbooksComponent } from '@components/view/ebooks/ebooks.component';
import { TorrentsComponent } from '@components/view/torrents/torrents.component';
import { HealthComponent } from '@components/view/health/health.component';


const routes: Routes = [
Expand All @@ -17,6 +18,7 @@ const routes: Routes = [
]},
{ path: 'ebooks', component: EbooksComponent },
{ path: 'torrents', component: TorrentsComponent },
{ path: 'health', component: HealthComponent },
]},
{ path: 'login', component: AuthComponent },
{ path: '', redirectTo: 'home', pathMatch: 'full' },
Expand Down
4 changes: 4 additions & 0 deletions client/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import { ProgressDialogComponent } from './components/view/progress-dialog/progr
import { LibgenComponent } from '@components/view/ebooks/libgen/libgen.component';
import { StatusService } from '@services/status.service';
import { LazyImgComponent } from './components/view/lazy-img/lazy-img.component';
import { HealthComponent } from '@components/view/health/health.component';
import { HealthService } from '@services/health.service';

@NgModule({
declarations: [
Expand All @@ -54,6 +56,7 @@ import { LazyImgComponent } from './components/view/lazy-img/lazy-img.component'
SettingsComponent,
RenameFileComponent,
TorrentsComponent,
HealthComponent,
FileSizePipe,
ProgressDialogComponent,
LibgenComponent,
Expand Down Expand Up @@ -106,6 +109,7 @@ import { LazyImgComponent } from './components/view/lazy-img/lazy-img.component'
AuthService,
EbooksService,
TorrentsService,
HealthService,
UploadInterceptor,
StatusService,
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
<mat-icon matListItemIcon>download</mat-icon>
<div matListItemTitle>Torrents</div>
</mat-list-item>
<mat-list-item (click)="onNavigatePage()" routerLink="/home/health">
<mat-icon matListItemIcon>favorite</mat-icon>
<div matListItemTitle>Health</div>
</mat-list-item>
<mat-list-item (click)="onOpenSettings()">
<mat-icon matListItemIcon>settings</mat-icon>
<div matListItemTitle>Settings</div>
Expand Down
3 changes: 3 additions & 0 deletions client/src/app/components/view/health/health.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<p>
{{ this.healthString }}
</p>
Empty file.
45 changes: 45 additions & 0 deletions client/src/app/components/view/health/health.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { UiStateActions } from '@actions/ui-state.actions';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { HealthDataDto } from '@api/models';
import { HealthService } from '@services/health.service';

@Component({
selector: 'app-health',
templateUrl: './health.component.html',
styleUrls: ['./health.component.scss'],
})
export class HealthComponent implements OnInit, OnDestroy {

public healthData: HealthDataDto;
// TODO remove and replace with chart.js
public healthString: string;
public from = new Date(new Date().setHours(0, 0, 0, 0));
public to = new Date();
public metrics = ['heart_rate'];

constructor (
private readonly service: HealthService,
private readonly ui: UiStateActions,
) { }

async ngOnInit(): Promise<void> {
this.ui.setAppBusy(true);
try {
await this.getHealthData();
} catch (e: any) {
throw new Error('Failed to get health data.');
} finally {
this.ui.setAppBusy(false);
}
}

public async getHealthData() {
this.healthData = await this.service.getHealthData(this.from, this.to, this.metrics);
this.healthString = JSON.stringify(this.healthData);
}

ngOnDestroy(): void {

}

}
22 changes: 22 additions & 0 deletions client/src/app/services/health.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Injectable } from '@angular/core';
import { HealthDataDto } from '@api/models';
import { ApiService } from '@api/services';
import { lastValueFrom } from 'rxjs';

@Injectable({
providedIn: 'root'
})
export class HealthService {

constructor(
private readonly api: ApiService,
) { }

public async getHealthData(from: Date, to: Date, metrics: string[]): Promise<HealthDataDto> {
return await lastValueFrom(this.api.healthControllerGetHealthData({
from: from.toISOString(),
to: to.toISOString(),
metrics,
}));
}
}
1 change: 1 addition & 0 deletions mock/Documents/Health/2024-09-15T05:00:09.918Z.json

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { FilesMiddleware } from './middlewares/files.middleware';
import { EbookService } from './ebooks/ebook.service';
import { EbooksMiddleware } from './middlewares/ebooks.middleware';
import { BooleanPipe } from './lib/boolean-transform.pipe';
import { DatePipe } from './lib/date-transform.pipe';
import { APP_PIPE } from '@nestjs/core';
import { CalibreService } from './ebooks/calibre.service';
import { RealCalibreService } from './ebooks/real-calibre.service';
Expand All @@ -29,6 +30,7 @@ import { ProxyMiddleware } from './middlewares/proxy.middleware';
import { StatusController } from './status/status.controller';
import { StatusService } from './status/status.service';
import { HealthController } from './health/health.controller';
import { HealthService } from './health/health.service';

@Module({
imports: [
Expand Down Expand Up @@ -90,6 +92,10 @@ import { HealthController } from './health/health.controller';
provide: APP_PIPE,
useClass: BooleanPipe,
},
{
provide: APP_PIPE,
useClass: DatePipe,
},
AuthService,
EbookService,
UpdateService,
Expand All @@ -98,6 +104,7 @@ import { HealthController } from './health/health.controller';
TorrentsService,
LibgenService,
StatusService,
HealthService,
],
})
export class AppModule implements NestModule {
Expand Down
3 changes: 3 additions & 0 deletions server/src/config/config.default.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
"downloadUrl": "http://library.lol/main"
}
},
"health": {
"healthDir": "{.}/../../../mock/Documents/Health"
},
"email": {
"oauth": {
"id": "",
Expand Down
3 changes: 3 additions & 0 deletions server/src/config/config.prod.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
"downloadUrl": "http://library.lol/main"
}
},
"health": {
"healthDir": "/opt/app/mnt/Documents/Health"
},
"email": {
"oauth": {
"id": "",
Expand Down
34 changes: 26 additions & 8 deletions server/src/health/health.controller.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,45 @@
import { Controller, Post, Body, Get, Req, UseGuards, Logger } from '@nestjs/common';
import { Controller, Post, Body, Get, Req, UseGuards, Logger, Query } from '@nestjs/common';
import { routes, joinRoutes } from '../routes';
import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { ApiBody, ApiCreatedResponse, ApiOkResponse, ApiQuery, ApiUnauthorizedResponse } from '@nestjs/swagger';
import { ApiKeyGuard } from '../auth/apikey.guard';
import { JwtGuard } from '../auth/jwt.guard';
import { HealthService } from './health.service';
import { HealthDataDto } from '../models/healthData.dto';

@Controller(joinRoutes(routes.api, routes.health))
@UseGuards(ApiKeyGuard)
export class HealthController {

constructor(private readonly logger: Logger) { }
constructor(
private readonly healthService: HealthService,
private readonly logger: Logger
) { }

@Post()
@ApiCreatedResponse({ description: 'Succesfully uploaded health data'})
@ApiBody({ type: Object })
@UseGuards(ApiKeyGuard)
@ApiUnauthorizedResponse({ description: 'Failed to login' })
async login(@Body() healthData: any) {
this.logger.log("Received health data.");
this.logger.log(JSON.stringify(healthData));
await this.healthService.importHealthData(healthData);
}

@Get()
@ApiOkResponse({ description: 'success'})
@UseGuards(JwtGuard)
@ApiOkResponse({ description: 'success', type: HealthDataDto })
@ApiQuery({ name: 'from', type: Date})
@ApiQuery({ name: 'to', type: Date})
@ApiQuery({ name: 'metrics', type: String, isArray: true })
@ApiUnauthorizedResponse({ description: 'Failed to login' })
isLoggedIn() {
return true;
async getHealthData(
@Query('from') from: Date,
@Query('to') to: Date,
@Query('metrics') metrics: string[]
) {
// Fix bug with query string arrays.
if (typeof metrics === 'string') {
metrics = [metrics]
}
return await this.healthService.getHealthData(from, to, metrics);
}
}
67 changes: 67 additions & 0 deletions server/src/health/health.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "../config/config.service";
import { HealthDataDto } from "../models/healthData.dto";
import { RawHealthData } from "../models/rawHealthData";

import { readFile, readdir, writeFile } from "fs/promises";
import path from "path";

@Injectable()
export class HealthService {
public constructor(
private readonly config: ConfigService,
private readonly log: Logger,
) {
this.healthDir = this.config.config.health.healthDir;
}

private healthDir: string;

public async importHealthData(data: RawHealthData) {
const fileName = `${new Date().toISOString()}.json`;
try {
await writeFile(path.join(this.healthDir, fileName), JSON.stringify(data));
this.log.log(`Imported health data: ${fileName}`);
} catch (e: any) {
this.log.error(`Failed to import data: ${e.message}`);
throw e;
}
}

/**
* Scans health directory and recursively aggregates health data
* exported from apple health.
*/
public async getHealthData(from: Date, to: Date, metrics: string[]): Promise<HealthDataDto> {
try {
const ret: HealthDataDto = { metrics: {} };
const exports = await readdir(this.healthDir);
for (const e of exports) {
const exportDate = new Date(path.basename(e, path.extname(e)));
if (exportDate >= from && exportDate <= to) {
const file = await readFile(path.join(this.healthDir, e), { encoding: 'utf-8'});
const data: RawHealthData = JSON.parse(file);
const rawMetrics = data.data;
for (const m of metrics) {
const sourceMetric = rawMetrics.metrics.find(metric => metric.name === m);
if (!sourceMetric) {
continue;
}
if (!ret.metrics[m]) {
ret.metrics[m] = {
name: m,
units: sourceMetric.units,
data: [],
}
}
ret.metrics[m].data.push(...sourceMetric.data);
}
}
}
return ret;
} catch (e: any) {
this.log.log(`Failed to export health data: ${e.message}`);
throw e;
}
}
}
14 changes: 14 additions & 0 deletions server/src/lib/date-transform.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Injectable, PipeTransform, ArgumentMetadata } from "@nestjs/common";

@Injectable()
export class DatePipe implements PipeTransform {
constructor() { }

transform(arg: any, metadata: ArgumentMetadata) {
if (metadata.metatype === Date) {
return new Date(arg);
} else {
return arg;
}
}
}
15 changes: 15 additions & 0 deletions server/src/models/healthData.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export class HealthDataDto {
metrics: Record<string, HealthMetric>;
}

export class HealthMetric {
name: string;
units: string;
data: HealthData[];
}

export class HealthData {
qty: number;
source: string;
date: Date;
}
17 changes: 17 additions & 0 deletions server/src/models/rawHealthData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export type RawHealthData = {
data: {
metrics: RawHealthMetric[],
}
};

type RawHealthMetric = {
name: string,
units: string,
data: RawHealthDatapoint[],
};

type RawHealthDatapoint = {
date: Date,
qty: number,
source: string,
};
2 changes: 1 addition & 1 deletion server/src/routes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export const joinRoutes = (...routes: string[]) => routes.join('/');

export const routes: any = {
export const routes = {
api: 'api',
files: 'files',
auth: 'auth',
Expand Down

0 comments on commit a2c4524

Please sign in to comment.