Make imported-event dedup bulletproof (server-side, id-based)
Repeated Contacts syncs must never create duplicate birthdays. Enforce it on the server, independent of the client's reconcile: - POST /local/events is now idempotent on external_uid: if an event with the same (calendar_id, external_uid) exists, update it in place instead of inserting a new row - startup cleanup removes existing duplicates sharing the same (calendar_id, external_uid), keeping the earliest — auto-heals old data - unique DB index on (calendar_id, external_uid) as a hard guarantee external_uid is the stable "contact:<deviceId>:<contactId>", so the same contact from the same device always maps to exactly one event. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -282,6 +282,33 @@ def _migrate():
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# One-time cleanup of duplicate imported events sharing the same
|
||||
# (calendar_id, external_uid) — e.g. birthdays created repeatedly by an
|
||||
# older build without idempotent upsert. Keep the earliest row.
|
||||
# Idempotent: after cleanup there is nothing left to delete.
|
||||
try:
|
||||
conn.execute(text(
|
||||
"DELETE FROM local_events WHERE external_uid IS NOT NULL AND id NOT IN "
|
||||
"(SELECT MIN(id) FROM local_events WHERE external_uid IS NOT NULL "
|
||||
"GROUP BY calendar_id, external_uid)"
|
||||
))
|
||||
conn.commit()
|
||||
logging.info("Migration: de-duplicated local_events by external_uid")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# Hard guarantee: the DB itself forbids two events with the same
|
||||
# external_uid in one calendar (imported entries only; NULLs unconstrained).
|
||||
try:
|
||||
conn.execute(text(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS ux_local_events_calendar_external "
|
||||
"ON local_events(calendar_id, external_uid) WHERE external_uid IS NOT NULL"
|
||||
))
|
||||
conn.commit()
|
||||
logging.info("Migration: unique index on (calendar_id, external_uid)")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_migrate()
|
||||
|
||||
app = FastAPI(title="Calendarr", docs_url=None, redoc_url=None)
|
||||
|
||||
Reference in New Issue
Block a user