mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 06:40:50 +00:00
feat(integrations): remove the moodLog integration
Mood has been a first-class module here for a long time, with entries,
structured tags, rated factors and notes. The bridge to the external moodLog
service had nothing left to add, and its Settings card was withdrawn in
a56e6fe61 on the promise of a dedicated removal. This delivers it.
Removed: the hourly `moodlog-sync` queue, cron and handler binding; the
inbound webhook and its shared-secret handling; the reverse push and its four
call sites; the pull-sync module; the per-user credential routes and the
instance-wide toggle; the entry on the `/api/integrations/status` envelope,
the `IntegrationKey` union, the cadence map and the display-name table; the
boot-time secret rotation pass; 22 message keys per locale.
Migration 0271 drops the five `users` credential columns and
`app_settings.mood_log_global`, clears the ledger row for the provider, and
deletes the retired pg-boss schedule so no cron keeps enqueuing work no
handler drains.
Mood data is untouched. `mood_entries` is not named anywhere in the migration
and cannot be reached from it: the dropped columns carry no index, constraint
or foreign key, and `mood_entries.user_id` references `users(id)`, not any of
them. Rows the bridge imported keep `source = 'MOODLOG'`, and every read path
- list filter, source badge, exports, backups - still resolves that value.
`src/lib/validations/moodlog.ts` held the native mood-entry schemas alongside
the bridge's own; it is now `validations/mood.ts` and carries only the former.
This commit is contained in:
1 parent
4ed8c8f39b
commit
d0b45e36b3
106 files changed
+379
-3326
No files matched your search
@@ -109,7 +109,6 @@ const LEGIT_IDENTICAL = new Set<string>([
|
||||
"settings.ai.providerChain.types.anthropic",
|
||||
"settings.ai.providerChain.types.codex",
|
||||
"settings.ai.providerSelect.anthropic",
|
||||
"settings.moodLogUrl",
|
||||
"settings.sections.export.import.appleHealth.title",
|
||||
"settings.sections.sources.sourceLabels.APPLE_HEALTH",
|
||||
"settings.appleHealth.title",
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* The moodLog bridge stays removed.
|
||||
*
|
||||
* It was retired once before, in pieces: the Settings card went in June, the
|
||||
* cron, webhook, credentials, ledger entry and roughly twenty i18n keys stayed
|
||||
* behind, and for a month the code ran hourly against an upstream that had
|
||||
* stopped answering while nothing on any screen said so. Half-removed was
|
||||
* worse than either finished state.
|
||||
*
|
||||
* This test pins the finished state. It is a structural guard, not a
|
||||
* behavioural one: it fails the moment a moodLog surface reappears, so a
|
||||
* reintroduction has to be a decision someone writes down rather than a merge
|
||||
* that slips through.
|
||||
*
|
||||
* What it deliberately does NOT forbid: the `MOODLOG` mood-entry source value
|
||||
* and its label. Rows the bridge imported are the user's own history, they
|
||||
* keep their provenance, and every read path must go on resolving it.
|
||||
*/
|
||||
import { readFileSync, existsSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { INTEGRATION_CADENCE } from "@/lib/integrations/sync-verdict";
|
||||
import { INTEGRATION_DISPLAY_NAMES } from "@/components/settings/integrations/integration-fallback-row";
|
||||
|
||||
const ROOT = path.resolve(__dirname, "../..");
|
||||
|
||||
const LOCALES = ["de", "en", "es", "fr", "it", "pl"] as const;
|
||||
|
||||
/** Every path the retired bridge occupied. */
|
||||
const REMOVED_PATHS = [
|
||||
"src/lib/moodlog",
|
||||
"src/lib/moodlog-secret.ts",
|
||||
"src/lib/jobs/reminder/moodlog-sync.ts",
|
||||
"src/app/api/integrations/moodlog",
|
||||
"src/app/api/settings/moodlog",
|
||||
"src/lib/validations/moodlog.ts",
|
||||
];
|
||||
|
||||
/** Columns migration 0271 dropped. */
|
||||
const REMOVED_SCHEMA_FIELDS = [
|
||||
"moodLogUrlEncrypted",
|
||||
"moodLogApiKeyEncrypted",
|
||||
"moodLogEnabled",
|
||||
"moodLogLastSyncedAt",
|
||||
"moodLogWebhookSecret",
|
||||
"moodLogGlobal",
|
||||
];
|
||||
|
||||
describe("moodLog integration stays removed", () => {
|
||||
it("has no moodLog module, route or validation file", () => {
|
||||
for (const rel of REMOVED_PATHS) {
|
||||
expect(existsSync(path.join(ROOT, rel)), `${rel} is back`).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it("declares no moodLog column on the Prisma schema", () => {
|
||||
const schema = readFileSync(
|
||||
path.join(ROOT, "prisma/schema.prisma"),
|
||||
"utf8",
|
||||
);
|
||||
for (const field of REMOVED_SCHEMA_FIELDS) {
|
||||
expect(schema, `${field} is back on the schema`).not.toContain(field);
|
||||
}
|
||||
});
|
||||
|
||||
it("registers no moodlog queue, cron or handler binding", () => {
|
||||
const registrar = readFileSync(
|
||||
path.join(ROOT, "src/lib/jobs/reminder/register-integration-sync.ts"),
|
||||
"utf8",
|
||||
);
|
||||
expect(registrar).not.toContain("moodlog");
|
||||
expect(registrar).not.toContain("MoodLog");
|
||||
});
|
||||
|
||||
it("carries no moodlog integration key on the status envelope", () => {
|
||||
expect(Object.keys(INTEGRATION_CADENCE)).not.toContain("moodlog");
|
||||
expect(Object.keys(INTEGRATION_DISPLAY_NAMES)).not.toContain("moodlog");
|
||||
});
|
||||
|
||||
it("keeps no orphaned moodLog message key in any locale", () => {
|
||||
for (const locale of LOCALES) {
|
||||
const bundle = JSON.parse(
|
||||
readFileSync(path.join(ROOT, `messages/${locale}.json`), "utf8"),
|
||||
) as {
|
||||
settings: Record<string, unknown>;
|
||||
admin: Record<string, unknown>;
|
||||
};
|
||||
const orphans = [
|
||||
...Object.keys(bundle.settings),
|
||||
...Object.keys(bundle.admin),
|
||||
].filter((key) => key.startsWith("moodLog"));
|
||||
expect(orphans, `${locale} still carries moodLog keys`).toEqual([]);
|
||||
}
|
||||
});
|
||||
|
||||
it("still resolves the MOODLOG provenance of imported mood rows", () => {
|
||||
// The other half of the contract: removing the bridge must not erase the
|
||||
// history it wrote. The source badge label has to survive the removal.
|
||||
const en = JSON.parse(
|
||||
readFileSync(path.join(ROOT, "messages/en.json"), "utf8"),
|
||||
) as { mood: Record<string, string> };
|
||||
expect(en.mood.sourceMoodlog).toBeTruthy();
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user