# Tasks: CardDAV-сервер для синхронизации контактов ## 1. Адресная книга в Radicale - [ ] 1.1 Создать адресную книгу «Контакты» на ФС: `mkdir -p /opt/hermes/email-assistant/radicale/data/collections/collection-root/estorozhenko/Контакты` и проверить, что Radicale видит её как addressbook: `curl -u estorozhenko:$RADICALE_PASS -X PROPFIND -H 'Depth: 0' http://127.0.0.1:5232/estorozhenko/Контакты/` → HTTP 207 и в XML есть `` (если RS не определяется — добавить `.Radicale.props` с addressbook и повторить). - [ ] 1.2 Положить тестовую vCard (test.vcf) в коллекцию и проверить, что она отдаётся: `curl ... /estorozhenko/Контакты/test.vcf` → 200 + vCard; затем удалить тестовую карточку. ## 2. Синк в contacts_extractor.py - [ ] 2.1 Рефакторинг: вынести генерацию vCard 4.0 из существующей generate_vcard() в отдельную функцию `contact_to_vcard(contact) -> str`, чтобы переиспользовать для CardDAV-карточек. Проверка: скрипт запускается без ошибок, contacts.vcf генерируется как раньше. - [ ] 2.2 Добавить функцию `sync_contacts_to_caldav(contacts_dir, base_url, user, password, prune=False)`: читает contacts.json, строит карту uid→(ETag, href) через PROPFIND Depth:1, PUT создаёт/обновляет vCard, при `prune=True` DELETE удаляет лишние. Использовать urllib + Basic Auth. Проверка: юнит-запуск с тестовым Radicale (см. 3.x). - [ ] 2.3 CLI-флаги: `--sync-caldav`, `--prune-caldav`, `--caldav-url`, `--caldav-user`, `--caldav-pass` (или env CALDAV_PASS). По умолчанию синк выключен. Проверка: `python3 scripts/contacts_extractor.py --help` показывает все флаги; без `--sync-caldav` поведение прежнее. - [ ] 2.4 ETag-конфликты: при PUT с If-Match и ответе 412 — записать `caldav-sync.log` (контакт, conflict) и продолжить. Проверка: ручной тест — изменить vCard на сервере, запустить синк, увидеть conflict в логе. ## 3. Сквозной тест sync - [ ] 3.1 Запустить синк с реальной базой: `python3 scripts/contacts_extractor.py --sync-caldav` (или отдельный скрипт) → в Radicale появились карточки (счётчик PROPFIND/cards): `curl ... PROPFIND Depth:1 /estorozhenko/Контакты/` показывает N карточек ≈ количеству контактов в contacts.json с email. - [ ] 3.2 Повторный запуск — количество карточек не растёт (идемпотентность). - [ ] 3.3 Изменить контакт в contacts.json (добавить телефон) → повторный синк обновляет карточку (TEL появился, ETag изменился). - [ ] 3.4 `--prune-caldav`: удалить контакт из contacts.json → карточка удалена с сервера. ## 4. Интеграция в cron и документация - [ ] 4.1 Обновить `config/contacts-cron.sh`: добавить `--sync-caldav` и подтянуть пароль из radicale/.env (env CALDAV_PASS). Проверка: запуск cron-скрипта вручную синкает контакты без ошибок. - [ ] 4.2 Обновить README/STATUS: раздел «CardDAV (контакты)» — как подключить на Android (DAVx5, URL http://cal.nixg.ru:5232 или cal.nixg.ru, логин estorozhenko), порты, флаги синка. - [ ] 4.3 `openspec validate contacts-caldav-server` → valid. - [ ] 4.4 Git commit и push (gitea.nixg.ru/hermes/email-assistant). ## Verification - [ ] V1: `curl -X PROPFIND -u estorozhenko:PASS http://127.0.0.1:5232/estorozhenko/Контакты/` → 207 + addressbook - [ ] V2: после синка количество vCard в адресной книге == числу контактов с email в contacts.json - [ ] V3: повторный синк → 0 новых карточек - [ ] V4: `python3 scripts/contacts_extractor.py --sync-caldav` из cron-обёртки завершается кодом 0 и пишет caldav-sync.log