mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-12 22:26:13 +00:00
fix(anamnesis): one newest AUDIT-C entry, scored against the wording offered
The stored language is the wording the read route offers the account; a request naming another wording, or a takenAt more than five minutes ahead, is refused. Latest, offerDue, the Coach block and the doctor report all read the newest entry whose answers open. The old one-word alcohol pattern gives way to an AUDIT-C entry in the Coach block, and the alcohol section lists it with a remove action.
This commit is contained in:
1 parent
e9a0b3d959
commit
b99b70127d
17 files changed
+515
-87
No files matched your search
@@ -26,10 +26,14 @@ import {
|
||||
import { auditLog } from "@/lib/auth/audit";
|
||||
import { withIdempotency } from "@/lib/idempotency";
|
||||
import { annotate } from "@/lib/logging/context";
|
||||
import { auditCInstrument } from "@/lib/mental-health/audit-c";
|
||||
import { createAuditC } from "@/lib/profile/life-circumstances";
|
||||
import { checkRateLimit } from "@/lib/rate-limit";
|
||||
import { auditCRequestSchema } from "@/lib/validations/life-circumstances";
|
||||
|
||||
/** How far ahead of the server clock a `takenAt` may lie. */
|
||||
const AUDIT_C_CLOCK_SKEW_MS = 5 * 60 * 1000;
|
||||
|
||||
export const POST = apiHandler(withIdempotency<[NextRequest]>(postAuditC));
|
||||
|
||||
async function postAuditC(request: NextRequest): Promise<Response> {
|
||||
@@ -54,11 +58,36 @@ async function postAuditC(request: NextRequest): Promise<Response> {
|
||||
}
|
||||
|
||||
const input = parsed.data;
|
||||
|
||||
// The cut-off follows the language of the wording that was answered, and
|
||||
// the only wording this account is ever shown is the one the read route
|
||||
// delivers for its language. The stored language is that one, decided
|
||||
// here; a client naming another wording answered something this server
|
||||
// did not offer (or the account language changed under an open form), and
|
||||
// is refused rather than scored against the wrong table.
|
||||
const offered = auditCInstrument(user.locale ?? "en").locale;
|
||||
if (
|
||||
input.locale !== undefined &&
|
||||
auditCInstrument(input.locale).locale !== offered
|
||||
) {
|
||||
return apiError("The wording answered is not the one offered", 422, {
|
||||
errorCode: "anamnesis.auditC.invalid",
|
||||
});
|
||||
}
|
||||
// An entry dated in the future would hold the "newest" place for as long
|
||||
// as the clock takes to reach it. A few minutes of device clock skew pass.
|
||||
const takenAt = input.takenAt ? new Date(input.takenAt) : undefined;
|
||||
if (takenAt && takenAt.getTime() > Date.now() + AUDIT_C_CLOCK_SKEW_MS) {
|
||||
return apiError("takenAt lies in the future", 422, {
|
||||
errorCode: "anamnesis.auditC.invalid",
|
||||
});
|
||||
}
|
||||
|
||||
const { result, replayed } = await createAuditC(user.id, {
|
||||
items: input.items,
|
||||
takenAt: input.takenAt ? new Date(input.takenAt) : undefined,
|
||||
takenAt,
|
||||
tz: input.tz,
|
||||
locale: input.locale ?? user.locale ?? "en",
|
||||
locale: offered,
|
||||
source: input.source,
|
||||
externalId: input.externalId,
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user