mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-15 15:02:04 +00:00
Add the WHOOP integration data layer on copy-of-Withings rails: - client.ts: hand-rolled fetch over safeFetch (no SDK) — OAuth authorize URL / code exchange / rotating-refresh, the four v2 collection fetchers (recovery, sleep, cycle, workout) with next_token pagination, single body-measurement + profile reads, and the field->Measurement mappers (RECOVERY_SCORE/HRV_RMSSD/RESTING_HEART_RATE/OXYGEN_SATURATION/ SKIN_TEMPERATURE, per-stage SLEEP_DURATION ms->min, SLEEP_NEED/SLEEP_*, DAY_STRAIN/ENERGY_EXPENDITURE_KJ, source=WHOOP). Day strain maps to DAY_STRAIN and RMSSD to HRV_RMSSD so the device-native values never share a bucket with the COMPUTED STRAIN_SCORE / SDNN proxies. - response-classifier.ts: HTTP-status-driven success/transient/ reauth_required/persistent verdicts + WhoopApiError, mirroring the Withings reason-tag classifier. - credentials.ts: per-user BYO-key resolution, decrypting the client id/secret from the User columns. - oauth-state.ts: opaque-nonce mint helper + cookie/TTL constants. - mapping.md: field->Measurement reference, kept in sync with the mappers. Unit tests cover the classifier, pagination, every fetcher, the mappers (ms->min, kJ->kcal factor, stage mapping, unscored-record handling), and credential resolution against mocked WHOOP JSON. No routes / OAuth flow / sync workers / migration yet.
38 lines
1.1 KiB
TypeScript
38 lines
1.1 KiB
TypeScript
/**
|
|
* Helper to retrieve per-user WHOOP OAuth credentials from the database.
|
|
* Mirrors `src/lib/withings/credentials.ts`.
|
|
*
|
|
* WHOOP ships per-user BYO-keys: each self-hoster registers their own WHOOP
|
|
* dev app and pastes the client id/secret into Settings, stored encrypted on
|
|
* `User` (the per-app authorized-user cap makes a single shared app
|
|
* unworkable for a multi-operator product).
|
|
*/
|
|
import { prisma } from "@/lib/db";
|
|
import { decrypt } from "@/lib/crypto";
|
|
import type { WhoopCredentials } from "./client";
|
|
|
|
/**
|
|
* Fetch and decrypt the user's WHOOP API credentials.
|
|
* Returns null if the user has not configured credentials.
|
|
*/
|
|
export async function getUserWhoopCredentials(
|
|
userId: string,
|
|
): Promise<WhoopCredentials | null> {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: {
|
|
whoopClientIdEncrypted: true,
|
|
whoopClientSecretEncrypted: true,
|
|
},
|
|
});
|
|
|
|
if (!user?.whoopClientIdEncrypted || !user?.whoopClientSecretEncrypted) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
clientId: decrypt(user.whoopClientIdEncrypted),
|
|
clientSecret: decrypt(user.whoopClientSecretEncrypted),
|
|
};
|
|
}
|