mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
Add the transport that reads a user's device data from the Google Health API (health.googleapis.com/v4): Google OAuth with PKCE and offline access, the paginated dataPoints reader with a daily-rollup fallback for the types that lack a list method, the metric/activity/sleep/workout mappers onto the shared measurement shape, and a response classifier that separates retryable errors, hard rejects, and the re-consent case. Access tokens live an hour and refresh tokens are reused, not rotated. A refresh that comes back invalid_grant raises a distinct reauth-required signal so the app can prompt a fresh consent rather than fail silently — the periodic re-consent a Testing-mode Google client needs. The four core readonly scopes ship by default; the Pixel Watch ECG and irregular-rhythm scopes stay behind an opt-in flag. Only the transport is new: the mapped-measurement output, the upsert/dedup/rollup tail, and the source ladders are the shared ones.
42 lines
1.3 KiB
TypeScript
42 lines
1.3 KiB
TypeScript
/**
|
|
* Helper to retrieve per-user Google Health OAuth credentials from the database.
|
|
* Mirrors `src/lib/whoop/credentials.ts`.
|
|
*
|
|
* Google Health ships per-user BYO-keys: each self-hoster registers their own
|
|
* Google Cloud OAuth client and pastes the client id/secret into Settings,
|
|
* stored encrypted on `User` (the Restricted-scope verification + annual CASA
|
|
* assessment is per-OAuth-client, so a single shared app is unworkable for a
|
|
* multi-operator product).
|
|
*/
|
|
import { prisma } from "@/lib/db";
|
|
import { decrypt } from "@/lib/crypto";
|
|
import type { GoogleHealthCredentials } from "./client";
|
|
|
|
/**
|
|
* Fetch and decrypt the user's Google Health API credentials.
|
|
* Returns null if the user has not configured credentials.
|
|
*/
|
|
export async function getUserGoogleHealthCredentials(
|
|
userId: string,
|
|
): Promise<GoogleHealthCredentials | null> {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: {
|
|
googleHealthClientIdEncrypted: true,
|
|
googleHealthClientSecretEncrypted: true,
|
|
},
|
|
});
|
|
|
|
if (
|
|
!user?.googleHealthClientIdEncrypted ||
|
|
!user?.googleHealthClientSecretEncrypted
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return {
|
|
clientId: decrypt(user.googleHealthClientIdEncrypted),
|
|
clientSecret: decrypt(user.googleHealthClientSecretEncrypted),
|
|
};
|
|
}
|