Files
HealthLog/src/lib/i18n/source-labels.ts
T
Marc-André Bombeck e2ba86b12f feat(garmin): sync Garmin Connect through the Personal API
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.
2026-11-04 10:14:10 +01:00

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",
};