mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
Garmin Connect now offers a Personal API: a person creates a key for their own account and any client can buy short-lived tokens with it. HealthLog takes that key in a new Garmin card, checks it with Garmin before storing it sealed on the user row, and polls every hour. - Transport: key exchange with a per-process token cache keyed on the key, one re-exchange on a refused token and then reauth_required, the 429 Retry-After carried to the caller, the China host tried when the global one does not know the key, answers read with a byte cap, all egress through safeFetch to the two fixed hosts. - Sync: daily summaries, sleep summaries, HRV, weigh-ins and the activity search, chunked to each resource's documented range, read one after another and stopped at the first rate limit; today's menstrual cycle summary adds logged period days the person has not logged. - Backfill: a resumable walk through the shared admission lane that learns where the history starts from the daily summaries and the activities, waits out Garmin's rate limit and stamps its marker. - Types: GARMIN source; BODY_BATTERY and STRESS_LEVEL for Garmin's own day scores, kept apart from the computed STRESS_SCORE; TRAINING_READINESS and ACUTE_TRAINING_LOAD for figures a bridge pushes through the ingest token. All four chart on the recovery page and sit on the ladders, units, ranges, labels and report groups like the device-native scores. - Key expiry: learned from a token capped short of a day, else estimated from the validity picked at connect; the card warns a week ahead and asks for a new key once Garmin refuses the old one. - Ledger, health, freshness, watchdog, backup classes, wipe plan, encrypted-column registry, rotation script, OpenAPI and all ten locales carry the new integration. The old "no Garmin connector" note is gone.
102 lines
4.2 KiB
TypeScript
102 lines
4.2 KiB
TypeScript
/**
|
|
* One place that says which i18n key labels which provenance enum member.
|
|
*
|
|
* Three surfaces render a source to the user and each one carried its own
|
|
* copy of the map: Settings → Sources, the sleep discrepancy tooltip, and
|
|
* the measurement list (filter dropdown + table badge). Every copy was
|
|
* written when the enum was shorter, and every copy fell through to the raw
|
|
* SCREAMING_SNAKE value for the providers added afterwards — the settings
|
|
* ladder listed `GOOGLE_HEALTH`, `OURA`, `POLAR` and `STRAVA` verbatim.
|
|
*
|
|
* The maps live here so a new provider is one edit, and they are keyed by
|
|
* the enum itself so the edit is not optional: leaving a member out is a
|
|
* type error, and `measurement-source-label-coverage.test.ts` proves the
|
|
* keys resolve in the message bundle.
|
|
*/
|
|
import { MEASUREMENT_SOURCES } from "@/lib/measurements/sources";
|
|
|
|
export type MeasurementSourceValue = (typeof MEASUREMENT_SOURCES)[number];
|
|
|
|
/**
|
|
* Settings → Sources ("Source priority by metric") and the sleep
|
|
* source-discrepancy tooltip. Reads as a provenance name: "Manual entry",
|
|
* "Computed", and the provider's own brand spelling for everything else.
|
|
*/
|
|
export const MEASUREMENT_SOURCE_SETTINGS_LABEL_KEYS: Record<
|
|
MeasurementSourceValue,
|
|
string
|
|
> = {
|
|
WITHINGS: "settings.sections.sources.sourceLabels.WITHINGS",
|
|
APPLE_HEALTH: "settings.sections.sources.sourceLabels.APPLE_HEALTH",
|
|
MANUAL: "settings.sections.sources.sourceLabels.MANUAL",
|
|
IMPORT: "settings.sections.sources.sourceLabels.IMPORT",
|
|
WHOOP: "settings.sections.sources.sourceLabels.WHOOP",
|
|
COMPUTED: "settings.sections.sources.sourceLabels.COMPUTED",
|
|
FITBIT: "settings.sections.sources.sourceLabels.FITBIT",
|
|
NIGHTSCOUT: "settings.sections.sources.sourceLabels.NIGHTSCOUT",
|
|
POLAR: "settings.sections.sources.sourceLabels.POLAR",
|
|
OURA: "settings.sections.sources.sourceLabels.OURA",
|
|
GARMIN: "settings.sections.sources.sourceLabels.GARMIN",
|
|
TELEGRAM: "settings.sections.sources.sourceLabels.TELEGRAM",
|
|
MCP: "settings.sections.sources.sourceLabels.MCP",
|
|
GOOGLE_HEALTH: "settings.sections.sources.sourceLabels.GOOGLE_HEALTH",
|
|
STRAVA: "settings.sections.sources.sourceLabels.STRAVA",
|
|
// v1.38.x — readings pushed in under a `measurements:write` token. On the
|
|
// default ladders right after MANUAL since v1.43.11.
|
|
EXTERNAL: "settings.sections.sources.sourceLabels.EXTERNAL",
|
|
HEALTH_CONNECT: "settings.sections.sources.sourceLabels.HEALTH_CONNECT",
|
|
};
|
|
|
|
/**
|
|
* The measurement list — the source facet in the filter rail and the badge
|
|
* in the table's source column. Its own catalogue because the badge column
|
|
* is narrow: "Manual" where the settings ladder says "Manual entry".
|
|
*/
|
|
export const MEASUREMENT_SOURCE_LIST_LABEL_KEYS: Record<
|
|
MeasurementSourceValue,
|
|
string
|
|
> = {
|
|
MANUAL: "measurements.sourceManual",
|
|
WITHINGS: "measurements.sourceWithings",
|
|
IMPORT: "measurements.sourceImport",
|
|
APPLE_HEALTH: "measurements.sourceAppleHealth",
|
|
COMPUTED: "measurements.sourceComputed",
|
|
WHOOP: "measurements.sourceWhoop",
|
|
FITBIT: "measurements.sourceFitbit",
|
|
NIGHTSCOUT: "measurements.sourceNightscout",
|
|
POLAR: "measurements.sourcePolar",
|
|
OURA: "measurements.sourceOura",
|
|
GARMIN: "measurements.sourceGarmin",
|
|
TELEGRAM: "measurements.sourceTelegram",
|
|
MCP: "measurements.sourceMcp",
|
|
GOOGLE_HEALTH: "measurements.sourceGoogleHealth",
|
|
STRAVA: "measurements.sourceStrava",
|
|
EXTERNAL: "measurements.sourceExternal",
|
|
HEALTH_CONNECT: "measurements.sourceHealthConnect",
|
|
};
|
|
|
|
/**
|
|
* `IntakeSource` — where a medication intake was logged. Not a Zod enum
|
|
* anywhere (the write surfaces validate against narrower literals), so the
|
|
* member list is written out and pinned against `prisma/schema.prisma` by
|
|
* the coverage guard.
|
|
*/
|
|
export const INTAKE_SOURCE_VALUES = [
|
|
"WEB",
|
|
"API",
|
|
"REMINDER",
|
|
"IMPORT",
|
|
"APPLE_HEALTH",
|
|
] as const;
|
|
|
|
export type IntakeSourceValue = (typeof INTAKE_SOURCE_VALUES)[number];
|
|
|
|
/** The "via {origin}" caption on a medication intake row. */
|
|
export const INTAKE_SOURCE_LABEL_KEYS: Record<IntakeSourceValue, string> = {
|
|
WEB: "medications.sourceWeb",
|
|
API: "medications.sourceApi",
|
|
REMINDER: "medications.sourceReminder",
|
|
IMPORT: "medications.sourceImport",
|
|
APPLE_HEALTH: "medications.sourceAppleHealth",
|
|
};
|