feat(cardav): add Migration 30 for CardDAV contacts schema
Add comprehensive database schema for CardDAV multi-account contacts sync: New tables: - cardav_accounts: Store CardDAV server credentials - cardav_addressbook_selection: Per-account addressbook enable/disable - contact_phones: Multiple phone numbers per contact with labels - contact_emails: Multiple email addresses per contact with labels - contact_addresses: Multiple postal addresses per contact with labels Extended contacts table with 9 new columns: - organization, job_title, birthday, website, photo, nickname - cardav_account_id (FK to cardav_accounts, ON DELETE SET NULL) - cardav_uid (vCard UID from server) - cardav_addressbook_url (source addressbook URL) Features: - UNIQUE constraints on (cardav_url, username) and (account_id, addressbook_url) - CASCADE delete for addressbook selection and contact sub-tables - Performance indices for cardav_uid and email lookups - Support for manual contacts (NULL cardav_account_id) - is_primary flag for phone/email/address selection Test coverage: - 23 comprehensive tests verify all tables, constraints, indices - FK CASCADE delete behavior validated - UNIQUE constraints enforced - Data integrity scenarios tested Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+101
@@ -1075,6 +1075,107 @@ const MIGRATIONS = [
|
||||
db.exec(`CREATE UNIQUE INDEX idx_calendar_sub_extid ON calendar_events (subscription_id, external_calendar_id)`);
|
||||
},
|
||||
},
|
||||
{
|
||||
version: 30,
|
||||
description: 'CardDAV multi-account contacts sync',
|
||||
up: `
|
||||
-- ========================================
|
||||
-- CardDAV Accounts
|
||||
-- ========================================
|
||||
CREATE TABLE cardav_accounts (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
cardav_url TEXT NOT NULL,
|
||||
username TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
||||
last_sync TEXT,
|
||||
UNIQUE(cardav_url, username)
|
||||
);
|
||||
|
||||
-- ========================================
|
||||
-- CardDAV Addressbook Selection
|
||||
-- ========================================
|
||||
CREATE TABLE cardav_addressbook_selection (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
account_id INTEGER NOT NULL,
|
||||
addressbook_url TEXT NOT NULL,
|
||||
addressbook_name TEXT NOT NULL,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
|
||||
UNIQUE(account_id, addressbook_url),
|
||||
FOREIGN KEY(account_id) REFERENCES cardav_accounts(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_cardav_addressbook_account
|
||||
ON cardav_addressbook_selection(account_id, enabled);
|
||||
|
||||
-- ========================================
|
||||
-- Extend Contacts Table for CardDAV
|
||||
-- ========================================
|
||||
ALTER TABLE contacts ADD COLUMN organization TEXT;
|
||||
ALTER TABLE contacts ADD COLUMN job_title TEXT;
|
||||
ALTER TABLE contacts ADD COLUMN birthday TEXT;
|
||||
ALTER TABLE contacts ADD COLUMN website TEXT;
|
||||
ALTER TABLE contacts ADD COLUMN photo TEXT;
|
||||
ALTER TABLE contacts ADD COLUMN nickname TEXT;
|
||||
ALTER TABLE contacts ADD COLUMN cardav_account_id INTEGER
|
||||
REFERENCES cardav_accounts(id) ON DELETE SET NULL;
|
||||
ALTER TABLE contacts ADD COLUMN cardav_uid TEXT;
|
||||
ALTER TABLE contacts ADD COLUMN cardav_addressbook_url TEXT;
|
||||
|
||||
CREATE INDEX idx_contacts_cardav_uid ON contacts(cardav_uid);
|
||||
CREATE INDEX idx_contacts_email ON contacts(email);
|
||||
|
||||
-- ========================================
|
||||
-- Contact Phones (Multiple per Contact)
|
||||
-- ========================================
|
||||
CREATE TABLE contact_phones (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
contact_id INTEGER NOT NULL,
|
||||
label TEXT,
|
||||
value TEXT NOT NULL,
|
||||
is_primary INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY(contact_id) REFERENCES contacts(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_contact_phones_contact ON contact_phones(contact_id);
|
||||
CREATE INDEX idx_contact_phones_value ON contact_phones(value);
|
||||
|
||||
-- ========================================
|
||||
-- Contact Emails (Multiple per Contact)
|
||||
-- ========================================
|
||||
CREATE TABLE contact_emails (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
contact_id INTEGER NOT NULL,
|
||||
label TEXT,
|
||||
value TEXT NOT NULL,
|
||||
is_primary INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY(contact_id) REFERENCES contacts(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_contact_emails_contact ON contact_emails(contact_id);
|
||||
CREATE INDEX idx_contact_emails_value ON contact_emails(value);
|
||||
|
||||
-- ========================================
|
||||
-- Contact Addresses (Multiple per Contact)
|
||||
-- ========================================
|
||||
CREATE TABLE contact_addresses (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
contact_id INTEGER NOT NULL,
|
||||
label TEXT,
|
||||
street TEXT,
|
||||
city TEXT,
|
||||
state TEXT,
|
||||
postal_code TEXT,
|
||||
country TEXT,
|
||||
is_primary INTEGER NOT NULL DEFAULT 0,
|
||||
FOREIGN KEY(contact_id) REFERENCES contacts(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX idx_contact_addresses_contact ON contact_addresses(contact_id);
|
||||
`,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user