Compare commits

5 Commits

Author SHA1 Message Date
Darius
197fda9ad9 fix timezone issues 2026-03-06 01:30:05 +01:00
Darius
6e61cff231 steps private => public 2026-03-05 20:18:42 +01:00
Darius
4d7efaf6dc bugfix import 2026-03-05 20:00:46 +01:00
Darius
4569d786d8 status => stats 2026-03-05 19:52:24 +01:00
Darius
963c0ca9a6 status => stats 2026-03-05 19:48:53 +01:00
9 changed files with 139 additions and 127 deletions

160
package-lock.json generated
View File

@@ -33,8 +33,8 @@
} }
}, },
"node_modules/@dpu/shared": { "node_modules/@dpu/shared": {
"version": "1.9.9", "version": "1.10.0",
"resolved": "git+https://git.dariusbag.dev/DarDarBinks/dpu-shared.git#d73d98068a779569e3be375f077de1e63f9efa9c", "resolved": "git+https://git.dariusbag.dev/DarDarBinks/dpu-shared.git#1fcc02ef1f29d72a035042180f29a80f4351c19c",
"dependencies": { "dependencies": {
"@types/ws": "^8.18.1", "@types/ws": "^8.18.1",
"axios": "^1.7.9", "axios": "^1.7.9",
@@ -749,27 +749,6 @@
"ws": "^8.16.0" "ws": "^8.16.0"
} }
}, },
"node_modules/@isaacs/balanced-match": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz",
"integrity": "sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==",
"license": "MIT",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@isaacs/brace-expansion": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
"integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
"license": "MIT",
"dependencies": {
"@isaacs/balanced-match": "^4.0.1"
},
"engines": {
"node": "20 || >=22"
}
},
"node_modules/@lukeed/ms": { "node_modules/@lukeed/ms": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz", "resolved": "https://registry.npmjs.org/@lukeed/ms/-/ms-2.0.2.tgz",
@@ -803,9 +782,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "24.10.12", "version": "24.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.12.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.11.0.tgz",
"integrity": "sha512-68e+T28EbdmLSTkPgs3+UacC6rzmqrcWFPQs1C8mwJhI/r5Uxr0yEuQotczNRROd1gq30NGxee+fo0rSIxpyAw==", "integrity": "sha512-fPxQqz4VTgPI/IQ+lj9r0h+fDR66bzoeMGHp8ASee+32OSGIkeASsoZuJixsQoVef1QJbeubcPBxKk22QVoWdw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"undici-types": "~7.16.0" "undici-types": "~7.16.0"
@@ -827,9 +806,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/ajv": { "node_modules/ajv": {
"version": "8.17.1", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@@ -875,9 +854,19 @@
} }
}, },
"node_modules/avvio": { "node_modules/avvio": {
"version": "9.1.0", "version": "9.2.0",
"resolved": "https://registry.npmjs.org/avvio/-/avvio-9.1.0.tgz", "resolved": "https://registry.npmjs.org/avvio/-/avvio-9.2.0.tgz",
"integrity": "sha512-fYASnYi600CsH/j9EQov7lECAniYiBFiiAtBNuZYLA2leLe9qOvZzqYHFjtIj6gD2VMoMLP14834LFWvr4IfDw==", "integrity": "sha512-2t/sy01ArdHHE0vRH5Hsay+RtCZt3dLPji7W7/MMOCEgze5b7SNDC4j5H6FnVgPkI1MTNFGzHdHrVXDDl7QSSQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fastify/error": "^4.0.0", "@fastify/error": "^4.0.0",
@@ -885,9 +874,9 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.13.5", "version": "1.13.6",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.5.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
"integrity": "sha512-cz4ur7Vb0xS4/KUN0tPWe44eqxrIu31me+fbang3ijiNscE129POzipJJA6zniq2C/Z6sJCjMimjS8Lc/GAs8Q==", "integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.11", "follow-redirects": "^1.15.11",
@@ -895,6 +884,15 @@
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -949,6 +947,18 @@
"readable-stream": "^3.4.0" "readable-stream": "^3.4.0"
} }
}, },
"node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/buffer": { "node_modules/buffer": {
"version": "5.7.1", "version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
@@ -1120,9 +1130,9 @@
} }
}, },
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "17.2.4", "version": "17.3.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
"integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==", "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
"license": "BSD-2-Clause", "license": "BSD-2-Clause",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
@@ -1330,9 +1340,9 @@
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
}, },
"node_modules/fastify": { "node_modules/fastify": {
"version": "5.7.4", "version": "5.8.1",
"resolved": "https://registry.npmjs.org/fastify/-/fastify-5.7.4.tgz", "resolved": "https://registry.npmjs.org/fastify/-/fastify-5.8.1.tgz",
"integrity": "sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA==", "integrity": "sha512-y0kicFvvn7CYWoPOVLOcvn4YyKQz03DIY7UxmyOy21/J8eXm09R+tmb+tVDBW5h+pja30cHI5dqUcSlvY86V2A==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -1355,7 +1365,7 @@
"fast-json-stringify": "^6.0.0", "fast-json-stringify": "^6.0.0",
"find-my-way": "^9.0.0", "find-my-way": "^9.0.0",
"light-my-request": "^6.0.0", "light-my-request": "^6.0.0",
"pino": "^10.1.0", "pino": "^9.14.0 || ^10.1.0",
"process-warning": "^5.0.0", "process-warning": "^5.0.0",
"rfdc": "^1.3.1", "rfdc": "^1.3.1",
"secure-json-parse": "^4.0.0", "secure-json-parse": "^4.0.0",
@@ -1426,9 +1436,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/find-my-way": { "node_modules/find-my-way": {
"version": "9.4.0", "version": "9.5.0",
"resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.4.0.tgz", "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-9.5.0.tgz",
"integrity": "sha512-5Ye4vHsypZRYtS01ob/iwHzGRUDELlsoCftI/OZFhcLs1M0tkGPcXldE80TAZC5yYuJMBPJQQ43UHlqbJWiX2w==", "integrity": "sha512-VW2RfnmscZO5KgBY5XVyKREMW5nMZcxDy+buTOsL+zIPnBlbKm+00sgzoQzq1EVh4aALZLfKdwv6atBGcjvjrQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
@@ -1562,17 +1572,17 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/glob": { "node_modules/glob": {
"version": "13.0.1", "version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.1.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w==", "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"minimatch": "^10.1.2", "minimatch": "^10.2.2",
"minipass": "^7.1.2", "minipass": "^7.1.3",
"path-scurry": "^2.0.0" "path-scurry": "^2.0.2"
}, },
"engines": { "engines": {
"node": "20 || >=22" "node": "18 || 20 || >=22"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
@@ -1770,9 +1780,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "11.2.5", "version": "11.2.6",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.6.tgz",
"integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", "integrity": "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==",
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"engines": { "engines": {
"node": "20 || >=22" "node": "20 || >=22"
@@ -1842,15 +1852,15 @@
} }
}, },
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "10.1.2", "version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw==", "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"@isaacs/brace-expansion": "^5.0.1" "brace-expansion": "^5.0.2"
}, },
"engines": { "engines": {
"node": "20 || >=22" "node": "18 || 20 || >=22"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
@@ -1866,10 +1876,10 @@
} }
}, },
"node_modules/minipass": { "node_modules/minipass": {
"version": "7.1.2", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"license": "ISC", "license": "BlueOak-1.0.0",
"engines": { "engines": {
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
@@ -1936,16 +1946,16 @@
"license": "BlueOak-1.0.0" "license": "BlueOak-1.0.0"
}, },
"node_modules/path-scurry": { "node_modules/path-scurry": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
"integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"lru-cache": "^11.0.0", "lru-cache": "^11.0.0",
"minipass": "^7.1.2" "minipass": "^7.1.2"
}, },
"engines": { "engines": {
"node": "20 || >=22" "node": "18 || 20 || >=22"
}, },
"funding": { "funding": {
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
@@ -2136,12 +2146,12 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/rimraf": { "node_modules/rimraf": {
"version": "6.1.2", "version": "6.1.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.2.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz",
"integrity": "sha512-cFCkPslJv7BAXJsYlK1dZsbP8/ZNLkCAQ0bi1hf5EKX2QHegmDFEFA6QhuYJlk7UDdc+02JjO80YSOrWPpw06g==", "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==",
"license": "BlueOak-1.0.0", "license": "BlueOak-1.0.0",
"dependencies": { "dependencies": {
"glob": "^13.0.0", "glob": "^13.0.3",
"package-json-from-dist": "^1.0.1" "package-json-from-dist": "^1.0.1"
}, },
"bin": { "bin": {
@@ -2288,9 +2298,9 @@
} }
}, },
"node_modules/sonic-boom": { "node_modules/sonic-boom": {
"version": "4.2.0", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
"integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", "integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"atomic-sleep": "^1.0.0" "atomic-sleep": "^1.0.0"

View File

@@ -6,6 +6,8 @@ export const Config = {
api_key: process.env.API_KEY, api_key: process.env.API_KEY,
port: process.env.PORT || "8080", port: process.env.PORT || "8080",
timezone: process.env.TIMEZONE || "Europe/Berlin",
gadgetbridge: { gadgetbridge: {
db_path: db_path:
process.env.GADGETBRIDGE_DB_PATH || "src/gadgetbridge/db/Gadgetbridge.db", process.env.GADGETBRIDGE_DB_PATH || "src/gadgetbridge/db/Gadgetbridge.db",

View File

@@ -1,11 +1,7 @@
import { watchFile } from "node:fs"; import { watchFile } from "node:fs";
import type { StepRow } from "@dpu/shared";
import Database from "better-sqlite3"; import Database from "better-sqlite3";
export type StepRow = {
date: string;
steps: number;
};
export class GadgetbridgeClient { export class GadgetbridgeClient {
private db: Database.Database; private db: Database.Database;
private readonly dbPath: string; private readonly dbPath: string;
@@ -29,29 +25,32 @@ export class GadgetbridgeClient {
return db; return db;
} }
getStepsPerDay(fromTimestamp: number, toTimestamp: number): StepRow[] { getStepsPerDay(fromTimestamp: number, toTimestamp: number, utcOffset: string): StepRow[] {
const stmt = this.db.prepare(` const stmt = this.db.prepare(`
SELECT date, SUM(steps) AS steps SELECT date, SUM(steps) AS steps
FROM ( FROM (
SELECT SELECT
DATE(TIMESTAMP, 'unixepoch', 'localtime') AS date, DATE(TIMESTAMP, 'unixepoch', ?) AS date,
STEPS AS steps STEPS AS steps
FROM HUAMI_EXTENDED_ACTIVITY_SAMPLE FROM HUAMI_EXTENDED_ACTIVITY_SAMPLE
WHERE TIMESTAMP >= ? AND TIMESTAMP < ? WHERE TIMESTAMP >= ? AND TIMESTAMP < ?
UNION ALL UNION ALL
SELECT date, steps SELECT date, steps
FROM old.steps FROM old.steps
WHERE date >= DATE(?, 'unixepoch', 'localtime') WHERE date >= DATE(?, 'unixepoch', ?)
AND date < DATE(?, 'unixepoch', 'localtime') AND date < DATE(?, 'unixepoch', ?)
) )
GROUP BY date GROUP BY date
ORDER BY date ORDER BY date
`); `);
return stmt.all( return stmt.all(
utcOffset,
fromTimestamp, fromTimestamp,
toTimestamp, toTimestamp,
fromTimestamp, fromTimestamp,
utcOffset,
toTimestamp, toTimestamp,
utcOffset,
) as StepRow[]; ) as StepRow[];
} }
} }

View File

@@ -4,7 +4,7 @@ import type { GadgetbridgeService } from "./service.js";
const dateRegex = /^\d{4}-\d{2}-\d{2}$/; const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
export async function privateGadgetbridgeRoutes( export async function publicGadgetbridgeRoutes(
fastify: FastifyInstance, fastify: FastifyInstance,
{ gadgetbridgeService }: { gadgetbridgeService: GadgetbridgeService }, { gadgetbridgeService }: { gadgetbridgeService: GadgetbridgeService },
) { ) {
@@ -34,13 +34,7 @@ export async function privateGadgetbridgeRoutes(
async (request, reply) => { async (request, reply) => {
const { from, to } = request.query as any; const { from, to } = request.query as any;
const fromDate = new Date(`${from}T00:00:00`); const service_result = gadgetbridgeService.getStepsForTimespan(from, to);
const toDate = new Date(`${to}T00:00:00`);
const service_result = gadgetbridgeService.getStepsForTimespan(
fromDate,
toDate,
);
if (!service_result.successful) { if (!service_result.successful) {
reply.code(418); reply.code(418);

View File

@@ -1,17 +1,24 @@
import { BaseService, type ServiceResult } from "@dpu/shared"; import { BaseService, type ServiceResult, type StepRow } from "@dpu/shared";
import type { GadgetbridgeClient, StepRow } from "./client.js"; import { DateTime } from "luxon";
import { Config } from "../config.js";
import type { GadgetbridgeClient } from "./client.js";
export class GadgetbridgeService extends BaseService<GadgetbridgeClient> { export class GadgetbridgeService extends BaseService<GadgetbridgeClient> {
getStepsForTimespan( getStepsForTimespan(from: string, to: string): ServiceResult<StepRow[] | string> {
from: Date,
to: Date,
): ServiceResult<StepRow[] | string> {
try { try {
const fromTs = Math.floor(from.getTime() / 1000); const tz = Config.timezone;
// Add one day to make `to` inclusive const fromDt = DateTime.fromISO(from, { zone: tz }).startOf("day");
const toTs = Math.floor(to.getTime() / 1000) + 86400; const toDt = DateTime.fromISO(to, { zone: tz }).startOf("day").plus({ days: 1 });
const rows = this.getClient().getStepsPerDay(fromTs, toTs); const fromTs = Math.floor(fromDt.toSeconds());
const toTs = Math.floor(toDt.toSeconds());
const offsetMinutes = fromDt.offset;
const sign = offsetMinutes >= 0 ? "+" : "-";
const abs = Math.abs(offsetMinutes);
const utcOffset = `${sign}${String(Math.floor(abs / 60)).padStart(2, "0")}:${String(abs % 60).padStart(2, "0")}`;
const rows = this.getClient().getStepsPerDay(fromTs, toTs, utcOffset);
return this.getSuccessfulResult(rows); return this.getSuccessfulResult(rows);
} catch (error) { } catch (error) {
return this.getErrorResult( return this.getErrorResult(

View File

@@ -3,7 +3,7 @@ import {
type GristRecord_PersonalGoals, type GristRecord_PersonalGoals,
type HA_Update, type HA_Update,
type HomeAssistantEntity, type HomeAssistantEntity,
StatusComponent, StatsComponent,
type TidalGetCurrent, type TidalGetCurrent,
} from "@dpu/shared"; } from "@dpu/shared";
import type { FastifyInstance, FastifyReply } from "fastify"; import type { FastifyInstance, FastifyReply } from "fastify";
@@ -36,7 +36,7 @@ export async function privateHomepageRoutes(
{ {
preHandler: verifyAPIKey, preHandler: verifyAPIKey,
schema: { schema: {
description: "Update information for component on dpu status page", description: "Update information for component on dpu stats page",
tags: ["homepage"], tags: ["homepage"],
body: z.custom<GristRecord_PersonalGoals>(), body: z.custom<GristRecord_PersonalGoals>(),
response: { response: {
@@ -56,7 +56,7 @@ export async function privateHomepageRoutes(
} }
const service_result = await hpService.updatePartial([ const service_result = await hpService.updatePartial([
{ {
component: StatusComponent.GRIST_PERSONAL_GOALS, component: StatsComponent.GRIST_PERSONAL_GOALS,
data: gristService.transformToPersonalGoals(record), data: gristService.transformToPersonalGoals(record),
}, },
]); ]);
@@ -75,7 +75,7 @@ export async function privateHomepageRoutes(
{ {
preHandler: verifyAPIKey, preHandler: verifyAPIKey,
schema: { schema: {
description: "Update information for component on dpu status page", description: "Update information for component on dpu stats page",
tags: ["homepage"], tags: ["homepage"],
body: z.custom<unknown>(), body: z.custom<unknown>(),
response: { response: {
@@ -100,7 +100,7 @@ export async function privateHomepageRoutes(
switch (ha_update.entity_id) { switch (ha_update.entity_id) {
case Config.homeassistant.id_sensor_desk_binary: { case Config.homeassistant.id_sensor_desk_binary: {
updates.push({ updates.push({
component: StatusComponent.HA_DESK_POSITION, component: StatsComponent.HA_DESK_POSITION,
data: haService.convertPosResultToApiAnswer( data: haService.convertPosResultToApiAnswer(
haService.convertHaEntityToPosResult(ha_entity), haService.convertHaEntityToPosResult(ha_entity),
), ),
@@ -109,7 +109,7 @@ export async function privateHomepageRoutes(
} }
case Config.homeassistant.id_sensor_roomtemp: case Config.homeassistant.id_sensor_roomtemp:
updates.push({ updates.push({
component: StatusComponent.HA_TEMP, component: StatsComponent.HA_TEMP,
data: ha_entity.state, data: ha_entity.state,
}); });
break; break;
@@ -132,7 +132,7 @@ export async function privateHomepageRoutes(
{ {
preHandler: verifyAPIKey, preHandler: verifyAPIKey,
schema: { schema: {
description: "Update information for component on dpu status page", description: "Update information for component on dpu stats page",
tags: ["homepage"], tags: ["homepage"],
body: z.custom<TidalGetCurrent>(), body: z.custom<TidalGetCurrent>(),
response: { response: {
@@ -148,7 +148,7 @@ export async function privateHomepageRoutes(
const update = request.body as TidalGetCurrent; const update = request.body as TidalGetCurrent;
const service_result = await hpService.updatePartial([ const service_result = await hpService.updatePartial([
{ {
component: StatusComponent.TIDAL_CURRENT, component: StatsComponent.TIDAL_CURRENT,
data: update, data: update,
}, },
]); ]);

View File

@@ -7,7 +7,7 @@ import {
type GristRecord_PersonalGoals, type GristRecord_PersonalGoals,
type HomeAssistantDeskPositionResult, type HomeAssistantDeskPositionResult,
type ServiceResult, type ServiceResult,
StatusComponent, StatsComponent,
type TidalGetCurrent, type TidalGetCurrent,
type WsService, type WsService,
} from "@dpu/shared"; } from "@dpu/shared";
@@ -75,11 +75,11 @@ export class HomepageService extends BaseService<null> {
try { try {
const [desk, temp, tidal, personal_goals] = await this._getAll(); const [desk, temp, tidal, personal_goals] = await this._getAll();
const updates: ComponentUpdate[] = [ const updates: ComponentUpdate[] = [
createComponentUpdate(StatusComponent.HA_DESK_POSITION, desk), createComponentUpdate(StatsComponent.HA_DESK_POSITION, desk),
createComponentUpdate(StatusComponent.HA_TEMP, temp), createComponentUpdate(StatsComponent.HA_TEMP, temp),
createComponentUpdate(StatusComponent.TIDAL_CURRENT, tidal), createComponentUpdate(StatsComponent.TIDAL_CURRENT, tidal),
createComponentUpdate( createComponentUpdate(
StatusComponent.GRIST_PERSONAL_GOALS, StatsComponent.GRIST_PERSONAL_GOALS,
personal_goals, personal_goals,
), ),
]; ];
@@ -171,26 +171,26 @@ export class HomepageService extends BaseService<null> {
const updates: ComponentUpdate[] = []; const updates: ComponentUpdate[] = [];
for (const component of components) { for (const component of components) {
switch (component.component) { switch (component.component) {
case StatusComponent.HA_DESK_POSITION: { case StatsComponent.HA_DESK_POSITION: {
this.updateHaDesk( this.updateHaDesk(
(component.data as API_HA_DeskPosition) ?? (await this._getDesk()), (component.data as API_HA_DeskPosition) ?? (await this._getDesk()),
updates, updates,
); );
break; break;
} }
case StatusComponent.HA_TEMP: case StatsComponent.HA_TEMP:
this.updateHaTemp( this.updateHaTemp(
(component.data as string) ?? (await this._getTemp()), (component.data as string) ?? (await this._getTemp()),
updates, updates,
); );
break; break;
case StatusComponent.TIDAL_CURRENT: case StatsComponent.TIDAL_CURRENT:
this.updateTidal( this.updateTidal(
(component.data as TidalGetCurrent) ?? (await this._getTidal()), (component.data as TidalGetCurrent) ?? (await this._getTidal()),
updates, updates,
); );
break; break;
case StatusComponent.GRIST_PERSONAL_GOALS: case StatsComponent.GRIST_PERSONAL_GOALS:
this.updateGristPG( this.updateGristPG(
(component.data as GristRecord_PersonalGoals) ?? (component.data as GristRecord_PersonalGoals) ??
(await this._getGristPG()), (await this._getGristPG()),
@@ -220,7 +220,7 @@ export class HomepageService extends BaseService<null> {
this.lastPoll.ha_desk_position = new_ha_desk_position; this.lastPoll.ha_desk_position = new_ha_desk_position;
updates.push( updates.push(
createComponentUpdate( createComponentUpdate(
StatusComponent.HA_DESK_POSITION, StatsComponent.HA_DESK_POSITION,
new_ha_desk_position, new_ha_desk_position,
), ),
); );
@@ -230,7 +230,7 @@ export class HomepageService extends BaseService<null> {
updateHaTemp(new_ha_temp: string | null, updates: ComponentUpdate[]): void { updateHaTemp(new_ha_temp: string | null, updates: ComponentUpdate[]): void {
if (this.lastPoll.ha_temp !== new_ha_temp) { if (this.lastPoll.ha_temp !== new_ha_temp) {
this.lastPoll.ha_temp = new_ha_temp; this.lastPoll.ha_temp = new_ha_temp;
updates.push(createComponentUpdate(StatusComponent.HA_TEMP, new_ha_temp)); updates.push(createComponentUpdate(StatsComponent.HA_TEMP, new_ha_temp));
} }
} }
@@ -246,7 +246,7 @@ export class HomepageService extends BaseService<null> {
) { ) {
this.lastPoll.tidal_current = new_tidal_current; this.lastPoll.tidal_current = new_tidal_current;
updates.push( updates.push(
createComponentUpdate(StatusComponent.TIDAL_CURRENT, new_tidal_current), createComponentUpdate(StatsComponent.TIDAL_CURRENT, new_tidal_current),
); );
} }
} }
@@ -274,7 +274,7 @@ export class HomepageService extends BaseService<null> {
this.lastPoll.grist_personal_goals = new_grist_personal_goals; this.lastPoll.grist_personal_goals = new_grist_personal_goals;
updates.push( updates.push(
createComponentUpdate( createComponentUpdate(
StatusComponent.GRIST_PERSONAL_GOALS, StatsComponent.GRIST_PERSONAL_GOALS,
new_grist_personal_goals, new_grist_personal_goals,
), ),
); );

View File

@@ -20,7 +20,7 @@ import { SwaggerTheme, SwaggerThemeNameEnum } from "swagger-themes";
import { z } from "zod"; import { z } from "zod";
import { Config } from "./config.js"; import { Config } from "./config.js";
import { GadgetbridgeClient } from "./gadgetbridge/client.js"; import { GadgetbridgeClient } from "./gadgetbridge/client.js";
import { privateGadgetbridgeRoutes } from "./gadgetbridge/private-routes.js"; import { publicGadgetbridgeRoutes } from "./gadgetbridge/public-routes.js";
import { GadgetbridgeService } from "./gadgetbridge/service.js"; import { GadgetbridgeService } from "./gadgetbridge/service.js";
import { GristClient } from "./grist/client.js"; import { GristClient } from "./grist/client.js";
import { privateGristRoutes } from "./grist/private-routes.js"; import { privateGristRoutes } from "./grist/private-routes.js";
@@ -178,7 +178,7 @@ async function verifyAPIKey(
// Register routes // Register routes
await publicServer.register(publicWsRoutes, { wsService, hpService }); await publicServer.register(publicWsRoutes, { wsService, hpService });
await privateServer.register(privateGadgetbridgeRoutes, { await publicServer.register(publicGadgetbridgeRoutes, {
gadgetbridgeService, gadgetbridgeService,
}); });
await privateServer.register(privateGristRoutes, { gristService }); await privateServer.register(privateGristRoutes, { gristService });

View File

@@ -13,7 +13,7 @@ export async function publicWsRoutes(
}, },
) { ) {
fastify.get( fastify.get(
"/dpu/status/events", "/dpu/stats/events",
{ {
schema: { schema: {
description: "Register for WebSocket events", description: "Register for WebSocket events",
@@ -25,7 +25,7 @@ export async function publicWsRoutes(
(socket, _request) => { (socket, _request) => {
wsService.addClient(socket); wsService.addClient(socket);
socket.send(JSON.stringify({ type: "message", data: "Connected" })); socket.send(JSON.stringify({ type: "message", data: "Connected" }));
hpService.sendFullInformationToSocket(socket) hpService.sendFullInformationToSocket(socket);
logInfo(`Connection for client established`); logInfo(`Connection for client established`);
socket.on("close", () => { socket.on("close", () => {