Skip events on re-import that the calendar already has #225

Closed
opened 2026-09-02 15:52:46 +00:00 by jcoffey-dev · 0 comments
Owner

Closes #222.

Importing an export twice left second copies of everything. The import has kept the file's own UID since it was written — inventing one only where an event arrives without — so what was needed to recognise an event that is already here was there all along, and nothing looked at it.

Decided by the reporter on #173 after his colleague hit the duplication in testing:

Both of us would prefer duplicate checks on UIDs if UID present in event.

The rule

  • An event whose UID the calendar already holds is skipped.
  • An event that arrives without a UID is imported as before. There is nothing to match it on, and a softer match — title and time, say — guesses in both directions.
  • Matching is per calendar. A UID is what makes an event the same event across calendars, so the same event living in two of them is not a duplicate and the second calendar still gets its copy.

How the UIDs are read

Once per import, not once per event. CalendarEvent/query does take a uid filter — it is what findByUid uses — but a file of two thousand events would be two thousand queries.

The scan runs without expandRecurrences, so a weekly series is one event with one UID rather than one UID per occurrence, and narrows to the target calendar from each event's calendarIds rather than through an inCalendar filter this client has not confirmed the server supports. Paged at maxObjectsInGet.

Counts

importIcs now answers { created, skipped } and the toast keeps them apart. "Imported 40 events" over a file of 240 reads as a failure when 200 were simply already there, and a re-import of an unchanged file would otherwise report importing nothing at all instead of saying everything was already here.

Strings

The three import toasts are now translated in all nine catalogues. Worth flagging as slightly beyond the issue: the plural for the existing "Imported {n} events" had never been added and was falling back to English, so translating only the two new ones would have left the family half-done. Mine, not native-reviewed, like the rest.

Tests

Four new: skipping a UID already in the calendar, importing one whose UID is in a different calendar, still importing an event with no UID, and sending no set at all when the whole file is already present. The existing cases move to the { created, skipped } shape, and the harness now answers the UID scan.

npm run typecheck, npm test (908 web + 122 server), npm run build, i18n:check (no new stale or missing keys) pass.

Not in this PR

Whether a re-imported event whose UID matches should update the existing one rather than be skipped. Re-importing an updated export is a plausible reason to do this at all, but the reporter asked for skip and update is the larger behaviour. Noted on #222.

Merged 2026-09-02 as coffey-labs/ihasmail@a61fe28523

Rebuilt from: git history, session transcript.

Closes #222. Importing an export twice left second copies of everything. The import has kept the file's own UID since it was written — inventing one only where an event arrives without — so what was needed to recognise an event that is already here was there all along, and nothing looked at it. Decided by the reporter on [#173](https://github.com/Coffey-Labs/ihasmail/issues/173#issuecomment-5510955196) after his colleague hit the duplication in testing: > Both of us would prefer duplicate checks on UIDs if UID present in event. ## The rule - An event whose UID the calendar already holds is **skipped**. - An event that arrives **without** a UID is imported as before. There is nothing to match it on, and a softer match — title and time, say — guesses in both directions. - Matching is **per calendar**. A UID is what makes an event the same event *across* calendars, so the same event living in two of them is not a duplicate and the second calendar still gets its copy. ## How the UIDs are read Once per import, not once per event. `CalendarEvent/query` does take a `uid` filter — it is what `findByUid` uses — but a file of two thousand events would be two thousand queries. The scan runs **without `expandRecurrences`**, so a weekly series is one event with one UID rather than one UID per occurrence, and narrows to the target calendar from each event's `calendarIds` rather than through an `inCalendar` filter this client has not confirmed the server supports. Paged at `maxObjectsInGet`. ## Counts `importIcs` now answers `{ created, skipped }` and the toast keeps them apart. "Imported 40 events" over a file of 240 reads as a failure when 200 were simply already there, and a re-import of an unchanged file would otherwise report importing nothing at all instead of saying everything was already here. ## Strings The three import toasts are now translated in all nine catalogues. Worth flagging as slightly beyond the issue: the plural for the *existing* "Imported {n} events" had never been added and was falling back to English, so translating only the two new ones would have left the family half-done. Mine, not native-reviewed, like the rest. ## Tests Four new: skipping a UID already in the calendar, importing one whose UID is in a *different* calendar, still importing an event with no UID, and sending no `set` at all when the whole file is already present. The existing cases move to the `{ created, skipped }` shape, and the harness now answers the UID scan. `npm run typecheck`, `npm test` (908 web + 122 server), `npm run build`, `i18n:check` (no new stale or missing keys) pass. ## Not in this PR Whether a re-imported event whose UID matches should *update* the existing one rather than be skipped. Re-importing an updated export is a plausible reason to do this at all, but the reporter asked for skip and update is the larger behaviour. Noted on #222. **Merged** 2026-09-02 as coffey-labs/ihasmail@a61fe28523a3 <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.