gadgetbridge integration
This commit is contained in:
@@ -6,6 +6,11 @@ export const Config = {
|
||||
api_key: process.env.API_KEY,
|
||||
port: process.env.PORT || "8080",
|
||||
|
||||
gadgetbridge: {
|
||||
db_path:
|
||||
process.env.GADGETBRIDGE_DB_PATH || "src/gadgetbridge/db/Gadgetbridge.db",
|
||||
},
|
||||
|
||||
grist: {
|
||||
api_url: process.env.GRIST_API_URL || "",
|
||||
api_token: process.env.GRIST_API_TOKEN || "",
|
||||
|
||||
27
src/gadgetbridge/client.ts
Normal file
27
src/gadgetbridge/client.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
|
||||
export type StepRow = {
|
||||
date: string;
|
||||
steps: number;
|
||||
};
|
||||
|
||||
export class GadgetbridgeClient {
|
||||
private db: DatabaseSync;
|
||||
|
||||
constructor(dbPath: string) {
|
||||
this.db = new DatabaseSync(dbPath, { open: true });
|
||||
}
|
||||
|
||||
getStepsPerDay(fromTimestamp: number, toTimestamp: number): StepRow[] {
|
||||
const stmt = this.db.prepare(`
|
||||
SELECT
|
||||
DATE(TIMESTAMP, 'unixepoch', 'localtime') AS date,
|
||||
SUM(STEPS) AS steps
|
||||
FROM HUAMI_EXTENDED_ACTIVITY_SAMPLE
|
||||
WHERE TIMESTAMP >= ? AND TIMESTAMP < ?
|
||||
GROUP BY date
|
||||
ORDER BY date
|
||||
`);
|
||||
return stmt.all(fromTimestamp, toTimestamp) as StepRow[];
|
||||
}
|
||||
}
|
||||
53
src/gadgetbridge/private-routes.ts
Normal file
53
src/gadgetbridge/private-routes.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { FastifyInstance } from "fastify";
|
||||
import { z } from "zod";
|
||||
import type { GadgetbridgeService } from "./service.js";
|
||||
|
||||
const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
|
||||
|
||||
export async function privateGadgetbridgeRoutes(
|
||||
fastify: FastifyInstance,
|
||||
{ gadgetbridgeService }: { gadgetbridgeService: GadgetbridgeService },
|
||||
) {
|
||||
fastify.get(
|
||||
"/gadgetbridge/steps",
|
||||
{
|
||||
schema: {
|
||||
description: "Get steps per day for a timespan",
|
||||
tags: ["gadgetbridge"],
|
||||
querystring: z.object({
|
||||
from: z.string().regex(dateRegex, "Must be YYYY-MM-DD"),
|
||||
to: z.string().regex(dateRegex, "Must be YYYY-MM-DD"),
|
||||
}),
|
||||
response: {
|
||||
200: z.array(
|
||||
z.object({
|
||||
date: z.string(),
|
||||
steps: z.number(),
|
||||
}),
|
||||
),
|
||||
418: z.object({
|
||||
error: z.string(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
async (request, reply) => {
|
||||
const { from, to } = request.query as any;
|
||||
|
||||
const fromDate = new Date(`${from}T00:00:00`);
|
||||
const toDate = new Date(`${to}T00:00:00`);
|
||||
|
||||
const service_result = gadgetbridgeService.getStepsForTimespan(
|
||||
fromDate,
|
||||
toDate,
|
||||
);
|
||||
|
||||
if (!service_result.successful) {
|
||||
reply.code(418);
|
||||
return { error: service_result.result };
|
||||
}
|
||||
|
||||
return service_result.result;
|
||||
},
|
||||
);
|
||||
}
|
||||
23
src/gadgetbridge/service.ts
Normal file
23
src/gadgetbridge/service.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { BaseService, type ServiceResult } from "@dpu/shared";
|
||||
import type { GadgetbridgeClient, StepRow } from "./client.js";
|
||||
|
||||
export class GadgetbridgeService extends BaseService<GadgetbridgeClient> {
|
||||
getStepsForTimespan(
|
||||
from: Date,
|
||||
to: Date,
|
||||
): ServiceResult<StepRow[] | string> {
|
||||
try {
|
||||
const fromTs = Math.floor(from.getTime() / 1000);
|
||||
// Add one day to make `to` inclusive
|
||||
const toTs = Math.floor(to.getTime() / 1000) + 86400;
|
||||
|
||||
const rows = this.getClient().getStepsPerDay(fromTs, toTs);
|
||||
return this.getSuccessfulResult(rows);
|
||||
} catch (error) {
|
||||
return this.getErrorResult(
|
||||
"error getting steps from gadgetbridge.",
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/index.ts
10
src/index.ts
@@ -19,6 +19,9 @@ import type { OpenAPIV3 } from "openapi-types";
|
||||
import { SwaggerTheme, SwaggerThemeNameEnum } from "swagger-themes";
|
||||
import { z } from "zod";
|
||||
import { Config } from "./config.js";
|
||||
import { GadgetbridgeClient } from "./gadgetbridge/client.js";
|
||||
import { privateGadgetbridgeRoutes } from "./gadgetbridge/private-routes.js";
|
||||
import { GadgetbridgeService } from "./gadgetbridge/service.js";
|
||||
import { GristClient } from "./grist/client.js";
|
||||
import { privateGristRoutes } from "./grist/private-routes.js";
|
||||
import { GristService } from "./grist/service.js";
|
||||
@@ -82,6 +85,7 @@ await publicServer.register(
|
||||
await privateServer.register(
|
||||
fastifySwagger,
|
||||
getSwaggerObject([
|
||||
{ url: "http://localhost:8080", description: "dev" },
|
||||
{ url: "http://192.168.178.161:20001", description: "prod" },
|
||||
]),
|
||||
);
|
||||
@@ -136,6 +140,9 @@ await privateServer.register(fastifyAxios, getAxiosConfig());
|
||||
await publicServer.register(fastifyWebsocket);
|
||||
|
||||
// Clients and Services
|
||||
const gadgetbridgeClient = new GadgetbridgeClient(Config.gadgetbridge.db_path);
|
||||
const gadgetbridgeService = new GadgetbridgeService(gadgetbridgeClient);
|
||||
|
||||
const gristClient = new GristClient(privateServer.axios.grist);
|
||||
const gristService = new GristService(gristClient);
|
||||
|
||||
@@ -171,6 +178,9 @@ async function verifyAPIKey(
|
||||
// Register routes
|
||||
await publicServer.register(publicWsRoutes, { wsService, hpService });
|
||||
|
||||
await privateServer.register(privateGadgetbridgeRoutes, {
|
||||
gadgetbridgeService,
|
||||
});
|
||||
await privateServer.register(privateGristRoutes, { gristService });
|
||||
await privateServer.register(privateHomeAssistantRoutes, {
|
||||
haService,
|
||||
|
||||
Reference in New Issue
Block a user