mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-11 22:02:16 +00:00
feat: add record keyed settings
- Separate managed record settings from actor identity\n- Limit integration reads to the active managed record
This commit is contained in:
1 parent
1eb0d22285
commit
009de7cb5f
7 files changed
+198
No files matched your search
@@ -0,0 +1,51 @@
|
||||
import {
|
||||
apiHandler,
|
||||
requireGuardianAuth,
|
||||
SharingAccessDeniedError,
|
||||
} from "@/lib/api-handler";
|
||||
import { apiSuccess } from "@/lib/api-response";
|
||||
import {
|
||||
getIntegrationStatus,
|
||||
type IntegrationKey,
|
||||
} from "@/lib/integrations/status";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { resolveGuardianRecordSettingsAccess } from "@/lib/record-settings";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
const MANAGED_INTEGRATIONS: readonly IntegrationKey[] = [
|
||||
"withings",
|
||||
"whoop",
|
||||
"fitbit",
|
||||
"nightscout",
|
||||
"polar",
|
||||
"oura",
|
||||
"google-health",
|
||||
"strava",
|
||||
];
|
||||
|
||||
/**
|
||||
* A managed profile may disclose whether its existing integrations are
|
||||
* connected, but this endpoint deliberately has no control or credential
|
||||
* projection. OAuth and provider state cannot safely carry a record selector.
|
||||
*/
|
||||
export const GET = apiHandler(async () => {
|
||||
const context = await requireGuardianAuth();
|
||||
const access = resolveGuardianRecordSettingsAccess(context);
|
||||
if (!access) throw new SharingAccessDeniedError();
|
||||
const integrations = await Promise.all(
|
||||
MANAGED_INTEGRATIONS.map(async (integration) => {
|
||||
const status = await getIntegrationStatus(access.recordId, integration);
|
||||
return {
|
||||
integration: status.integration,
|
||||
state: status.state,
|
||||
lastSuccessAt: status.lastSuccessAt,
|
||||
lastAttemptAt: status.lastAttemptAt,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
annotate({ action: { name: "record-settings.integrations.get" } });
|
||||
|
||||
return apiSuccess({ recordId: access.recordId, integrations });
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import {
|
||||
apiHandler,
|
||||
requireGuardianAuth,
|
||||
SharingAccessDeniedError,
|
||||
} from "@/lib/api-handler";
|
||||
import { apiSuccess } from "@/lib/api-response";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import {
|
||||
resolveGuardianRecordSettingsAccess,
|
||||
toRecordSettingsDto,
|
||||
} from "@/lib/record-settings";
|
||||
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
/**
|
||||
* The active managed record's configuration identity. This is intentionally a
|
||||
* distinct record DTO rather than another actor field on `/api/auth/me`.
|
||||
*/
|
||||
export const GET = apiHandler(async () => {
|
||||
const context = await requireGuardianAuth();
|
||||
const access = resolveGuardianRecordSettingsAccess(context);
|
||||
if (!access) throw new SharingAccessDeniedError();
|
||||
|
||||
annotate({ action: { name: "record-settings.get" } });
|
||||
|
||||
return apiSuccess(
|
||||
toRecordSettingsDto({
|
||||
id: access.recordId,
|
||||
name: context.user.displayName ?? context.user.username,
|
||||
locale: context.user.locale,
|
||||
timezone: context.user.timezone,
|
||||
recordKind: "managed",
|
||||
}),
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user