-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b24becb
commit a2c4524
Showing
17 changed files
with
234 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
<p> | ||
{{ this.healthString }} | ||
</p> |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
})); | ||
} | ||
} |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters