mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 06:32:03 +00:00
fix(google-health): surface the connection status and add the resume and test routes
Wire the settings card to the live connection so it reflects reality after a connect. - Emit a google-health entry on the consolidated integration-status envelope (configured, connected, last sync, backfill, and the needs-reconnect flag). Without it the card read an undefined status and showed a permanently disconnected connection even after a successful OAuth grant, hiding the sync, disconnect, and reconnect controls. - Add the resume and test-connection routes the card calls, mirroring the classic Fitbit ones. - Use the shared refresh icon on the reconnect action. - Drop the experimental-scope opt-in from the runbook and describe ECG and irregular-rhythm as a planned addition rather than a current toggle.
This commit is contained in:
1 parent
422a6a7df0
commit
0d3067837e
6 files changed
+241
-14
No files matched your search
@@ -11,6 +11,7 @@ vi.mock("@/lib/db", () => ({
|
||||
withingsConnection: { findUnique: vi.fn(async () => null) },
|
||||
whoopConnection: { findUnique: vi.fn(async () => null) },
|
||||
fitbitConnection: { findUnique: vi.fn(async () => null) },
|
||||
googleHealthConnection: { findUnique: vi.fn(async () => null) },
|
||||
moodEntry: { count: vi.fn(async () => 0) },
|
||||
},
|
||||
}));
|
||||
|
||||
@@ -40,12 +40,14 @@ export const GET = apiHandler(async () => {
|
||||
moodLogStatus,
|
||||
whoopStatus,
|
||||
fitbitStatus,
|
||||
googleHealthStatus,
|
||||
polarStatus,
|
||||
ouraStatus,
|
||||
dbUser,
|
||||
withingsConn,
|
||||
whoopConn,
|
||||
fitbitConn,
|
||||
googleHealthConn,
|
||||
moodLogEntryCount,
|
||||
// v1.17.1 — `available` reports whether usable OAuth credentials resolve
|
||||
// (per-user BYO first, then the shared env app), mirroring the per-card
|
||||
@@ -57,6 +59,7 @@ export const GET = apiHandler(async () => {
|
||||
getIntegrationStatus(user.id, "moodlog"),
|
||||
getIntegrationStatus(user.id, "whoop"),
|
||||
getIntegrationStatus(user.id, "fitbit"),
|
||||
getIntegrationStatus(user.id, "google-health"),
|
||||
getIntegrationStatus(user.id, "polar"),
|
||||
getIntegrationStatus(user.id, "oura"),
|
||||
prisma.user.findUnique({
|
||||
@@ -68,6 +71,8 @@ export const GET = apiHandler(async () => {
|
||||
whoopClientSecretEncrypted: true,
|
||||
fitbitClientIdEncrypted: true,
|
||||
fitbitClientSecretEncrypted: true,
|
||||
googleHealthClientIdEncrypted: true,
|
||||
googleHealthClientSecretEncrypted: true,
|
||||
polarAccessTokenEncrypted: true,
|
||||
polarClientIdEncrypted: true,
|
||||
polarClientSecretEncrypted: true,
|
||||
@@ -108,6 +113,16 @@ export const GET = apiHandler(async () => {
|
||||
backfillCompletedAt: true,
|
||||
},
|
||||
}),
|
||||
prisma.googleHealthConnection.findUnique({
|
||||
where: { userId: user.id },
|
||||
select: {
|
||||
tokenExpiresAt: true,
|
||||
lastSyncedAt: true,
|
||||
createdAt: true,
|
||||
backfillCompletedAt: true,
|
||||
needsReauth: true,
|
||||
},
|
||||
}),
|
||||
// v1.7.0 sync — exclude tombstoned rows from the entry count.
|
||||
prisma.moodEntry.count({
|
||||
where: { userId: user.id, deletedAt: null },
|
||||
@@ -183,6 +198,27 @@ export const GET = apiHandler(async () => {
|
||||
: null,
|
||||
backfillCompleted: fitbitConn ? !!fitbitConn.backfillCompletedAt : null,
|
||||
} satisfies IntegrationViewModel & FitbitExtras,
|
||||
{
|
||||
...googleHealthStatus,
|
||||
configured:
|
||||
!!dbUser?.googleHealthClientIdEncrypted &&
|
||||
!!dbUser?.googleHealthClientSecretEncrypted,
|
||||
connected: !!googleHealthConn,
|
||||
connectedAt: googleHealthConn?.createdAt?.toISOString() ?? null,
|
||||
legacyLastSyncedAt:
|
||||
googleHealthConn?.lastSyncedAt?.toISOString() ?? null,
|
||||
tokenExpiresAt: googleHealthConn?.tokenExpiresAt?.toISOString() ?? null,
|
||||
tokenExpired: googleHealthConn
|
||||
? googleHealthConn.tokenExpiresAt.getTime() <= now
|
||||
: null,
|
||||
backfillCompleted: googleHealthConn
|
||||
? !!googleHealthConn.backfillCompletedAt
|
||||
: null,
|
||||
// v1.27.0 — `needsReauth` is the 7-day Testing-mode refresh expiry (or a
|
||||
// user-revoked grant) surfaced from the connection row; the card reads it
|
||||
// to raise a distinct "Reconnect" CTA separate from parked/disconnected.
|
||||
needsReauth: googleHealthConn ? googleHealthConn.needsReauth : false,
|
||||
} satisfies IntegrationViewModel & GoogleHealthExtras,
|
||||
{
|
||||
...polarStatus,
|
||||
// `connected` = a stored access token; `configured` mirrors it (the
|
||||
@@ -256,6 +292,21 @@ interface FitbitExtras {
|
||||
backfillCompleted: boolean | null;
|
||||
}
|
||||
|
||||
// v1.27.0 — Google Health mirrors the Fitbit shape plus `needsReauth`: Google
|
||||
// expires the refresh token after 7 days in "Testing" publishing mode, so a
|
||||
// connected user is periodically pushed back through OAuth. The card reads the
|
||||
// flag to raise a distinct reconnect banner.
|
||||
interface GoogleHealthExtras {
|
||||
configured: boolean;
|
||||
connected: boolean;
|
||||
connectedAt: string | null;
|
||||
legacyLastSyncedAt: string | null;
|
||||
tokenExpiresAt: string | null;
|
||||
tokenExpired: boolean | null;
|
||||
backfillCompleted: boolean | null;
|
||||
needsReauth: boolean;
|
||||
}
|
||||
|
||||
// v1.17.1 — Polar / Oura fold into the consolidated envelope. They carry the
|
||||
// per-user BYO-key flags the shared OAuth card reads instead of the dedicated
|
||||
// /api/<provider>/status round-trip the page used to fire per card.
|
||||
|
||||
Reference in new issue
Block a user