mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-10 21:32:18 +00:00
feat(achievements): worker re-evaluates new + hidden achievements on data change
Two trigger fixes so the new achievements actually fire end-to-end. The doctor-PDF hidden Easter-egg used to watch a non-existent `doctor-report.export` audit action. The real route emits two: `doctor-report.generate` (the JSON helper) and `doctor-report.pdf.generate` (the PDF route). Either now counts. The locale-flip hidden Easter-egg used to watch `settings.locale.update`, which no endpoint emits — the locale field shares the catch-all `profile.update` audit row with every other profile field. Profile-update now emits a separate `settings.locale.update` row when (and only when) the locale value actually changes (compares the new value to the prior persisted locale, suppresses the row when they match), with the from/to values in the details payload for traceability. Already-existing TanStack Query invalidation map covers the new metrics: `measurementDependentKeys`, `moodDependentKeys`, and `medicationDependentKeys` already include `queryKeys.gamificationAchievements()`, so any measurement / mood / intake mutation triggers a refetch and the route persists the new unlock to `UserAchievement` (existing v1.4.15 mechanism). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
6912d4e2b6
commit
75c74f1c49
5 files changed
+40
-6
No files matched your search
@@ -531,7 +531,8 @@ export const GET = apiHandler(async (request: NextRequest) => {
|
||||
"auth.login.password",
|
||||
"bugreport.submit",
|
||||
// v1.4.18 — hidden Easter-egg triggers
|
||||
"doctor-report.export",
|
||||
"doctor-report.generate",
|
||||
"doctor-report.pdf.generate",
|
||||
"settings.locale.update",
|
||||
],
|
||||
},
|
||||
|
||||
@@ -86,6 +86,21 @@ export async function applyProfileUpdate(
|
||||
if (data.locale !== undefined) updates.locale = data.locale;
|
||||
if (data.timezone !== undefined) updates.timezone = data.timezone;
|
||||
|
||||
// v1.4.18 — capture the prior locale so we can emit a separate
|
||||
// `settings.locale.update` audit row when the locale actually
|
||||
// changes. The hidden "polyglot" achievement watches that action,
|
||||
// and we don't want to fire it on profile updates that happen to
|
||||
// touch other fields.
|
||||
const priorLocale =
|
||||
data.locale !== undefined
|
||||
? (
|
||||
await prisma.user.findUnique({
|
||||
where: { id: userId },
|
||||
select: { locale: true },
|
||||
})
|
||||
)?.locale ?? null
|
||||
: null;
|
||||
|
||||
const updatedUser = await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: updates,
|
||||
@@ -96,6 +111,14 @@ export async function applyProfileUpdate(
|
||||
ipAddress: ipAddress ?? null,
|
||||
});
|
||||
|
||||
if (data.locale !== undefined && priorLocale !== data.locale) {
|
||||
await auditLog("settings.locale.update", {
|
||||
userId: updatedUser.id,
|
||||
ipAddress: ipAddress ?? null,
|
||||
details: { from: priorLocale, to: data.locale ?? null },
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
user: {
|
||||
|
||||
@@ -235,8 +235,9 @@ describe("getHiddenMetrics", () => {
|
||||
moodEntries: [],
|
||||
intakeEvents: [],
|
||||
auditEvents: [
|
||||
{ action: "doctor-report.export", createdAt: new Date() },
|
||||
{ action: "doctor-report.export", createdAt: new Date() },
|
||||
// The route's auditLog filter accepts both `generate` actions.
|
||||
{ action: "doctor-report.generate", createdAt: new Date() },
|
||||
{ action: "doctor-report.pdf.generate", createdAt: new Date() },
|
||||
{ action: "settings.locale.update", createdAt: new Date() },
|
||||
{ action: "auth.login.passkey", createdAt: new Date() }, // ignored
|
||||
],
|
||||
|
||||
@@ -276,7 +276,16 @@ export function getHiddenMetrics(input: {
|
||||
let doctorPdfCount = 0;
|
||||
let localeFlipCount = 0;
|
||||
for (const ev of input.auditEvents) {
|
||||
if (ev.action === "doctor-report.export") doctorPdfCount += 1;
|
||||
// Both "generate" + "pdf.generate" count — the PDF route emits the
|
||||
// latter; the JSON-only generate route emits the former. We accept
|
||||
// either as the trigger so the badge fires the first time the user
|
||||
// exercises the doctor-report feature in any shape.
|
||||
if (
|
||||
ev.action === "doctor-report.generate" ||
|
||||
ev.action === "doctor-report.pdf.generate"
|
||||
) {
|
||||
doctorPdfCount += 1;
|
||||
}
|
||||
if (ev.action === "settings.locale.update") localeFlipCount += 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -159,12 +159,12 @@ describe("achievements expansion — fresh user, real prisma rows", () => {
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
action: "doctor-report.export",
|
||||
action: "doctor-report.pdf.generate",
|
||||
},
|
||||
});
|
||||
|
||||
const auditEvents = await prisma.auditLog.findMany({
|
||||
where: { userId: user.id, action: "doctor-report.export" },
|
||||
where: { userId: user.id, action: "doctor-report.pdf.generate" },
|
||||
select: { action: true, createdAt: true },
|
||||
});
|
||||
|
||||
|
||||
Reference in new issue
Block a user