feat(import): bring the account's own mood vocabulary along

A full backup carries the account's own mood tags, rated factors and
tag groups, and the JSON import left them out: a mood entry imported
into another account landed without every link to a tag the person had
made.

Before the entries are written, every key they or the file name is
resolved once. A catalogue tag or one the account owns is used as it
is; an own tag whose label the file states differently is kept, never
overwritten, and named in keptMoodTagKeys (keptMoodCategoryKeys for a
group). A tag the file defines and the account lacks is created in its
group, the group created the same way, within the account's caps of 50
tags and 12 groups. A key another account of the server holds comes
over under a key derived from the account and the file's key, so a
second import of the same file finds the copy instead of minting
another. The links are written through that resolution, own rated
factors included, inside the entry's transaction; anything unresolved
is still dropped and named. The import card names kept keys in all
nine locales.

The field-coverage guard now holds the backup's customMoodTags and
customMoodTagCategories to the import schema. The round trip moves a
backup to another server (the same seeded catalogue, none of the
account's rows), imports it into a fresh account and compares the
re-export, own tags, group and rated factor included; a second case
imports into another account of the same server, and a third keeps a
renamed own tag.
This commit is contained in:
Marc-André Bombeck committed 2026-10-23 22:41:36 +02:00
1 parent d1545cf554
commit 2357807706
25 files changed
+995 -67

No files matched your search

@@ -13,6 +13,9 @@ import {
jsonImportMeasurementSchema,
jsonImportMoodContextSchema,
jsonImportMoodEntrySchema,
jsonImportMoodTagCategorySchema,
jsonImportMoodTagSchema,
jsonImportSchema,
} from "@/lib/import/json/schema";
import { buildOpenApiDocument } from "@/lib/openapi/registry";
@@ -63,6 +66,18 @@ describe("the published JSON import contract", () => {
expect(fields).toEqual(Object.keys(jsonImportMoodEntrySchema.shape).sort());
});
it("names every section and every vocabulary field the import reads", () => {
expect(documented("JsonImportRequest")).toEqual(
Object.keys(jsonImportSchema.shape).sort(),
);
expect(documented("JsonImportMoodTag")).toEqual(
Object.keys(jsonImportMoodTagSchema.shape).sort(),
);
expect(documented("JsonImportMoodTagCategory")).toEqual(
Object.keys(jsonImportMoodTagCategorySchema.shape).sort(),
);
});
it("names every day-context field the import reads, and no other", () => {
const entry = resolve(doc.components.schemas.JsonImportMoodEntry!);
const context = resolve(entry.properties!.context!);