7 Commits

Author SHA1 Message Date
jp
456d5f96f2 Contacts management page + MySQL-only backend
- vm_contacts: mysql-only backend (removed file/google/carddav from _BACKENDS)
- contacts.conf: backends=mysql (no fallback)
- vm_web.py: /contacts CRUD list (list, new, edit, delete, delete_all)
- vm_web.py: /contacts/import_vcf re-imports from VCF (skips junk names)
- nav: Contacts link added for authenticated users
- fixes: _MySQLCon.__iter__ for get_settings loop; add_contact row.id check
2026-08-13 13:49:03 +01:00
jp
247c8f7318 fix: empty _Row truthy bug in add_contact UPSERT
fetchone() returns an empty _Row on no match (no __bool__), so  was
always true and UPDATE ran with a nonexistent id, silently inserting nothing.
Changed to  so missing rows fall through to INSERT.
2026-08-13 13:39:07 +01:00
jp
b09255473a JS delete + add-contact modal + MySQL contacts table
- vm_web.py: deleteVoicemail() XHR removes .card node on success
- vm_web.py: openAddContact() modal with name/number/email fields
- vm_web.py: POST /add_contact adds/updates MySQL contacts + backfills messages
- show '+ Add to contacts' button when caller not in contacts
- contacts.conf: backends=mysql,file
- vm_import_contacts.py: VCF -> MySQL contacts import (527 rows)
- Apache CSP: script-src 'self' 'unsafe-inline' for inline JS handlers
- contacts table created in asterisk MySQL DB (number_e164 UNIQUE)
2026-08-13 13:23:10 +01:00
jp
d2f1ca8f9d Add MySQL contacts table + mysql backend for vm_contacts
- CREATE TABLE contacts (number_e164 UNIQUE, name, email) in asterisk DB
- new lookup_mysql() backend, registered as first in _BACKENDS
- vm_import_contacts.py: import VCF into MySQL (527 added)
- contacts.conf: backends=mysql,file
- live: 16 of 130 messages resolved via MySQL contacts
2026-08-13 13:16:13 +01:00
jp
05c5dbe45a MySQL backend + CDR + contacts email + matcher fix
- vm_store: MySQL (PyMySQL) default backend, SQLite fallback
- CDR: cdr_adaptive_odbc + res_odbc + MariaDB ODBC driver + Master.csv backfill
- contacts: full E.164 matching, WORK-email preference, junk-name guard
- backfill scripts: vm_migrate_sqlite_to_mysql.py, vm_backfill_contacts.py,
  vm_backfill_cdr.py
- live: 131 messages migrated, 7591 CDR rows backfilled
2026-08-13 13:09:03 +01:00
jp
21dc883e99 Fix contacts matcher: full-number (15-digit) matching, junk-name guard
- Index by full E.164 digits instead of last-9 only; previously different
  people sharing a 9-digit suffix collided and lookups returned the wrong
  name. Resolve now tries longest match first.
- match_digits default 9 -> 15 in code and example config.
- Skip emoji/punctuation-only contact names so a junk card can't shadow a
  real one when a number is duplicated in the export.
- Verified: 513 contacts self-consistent; 6 remaining collisions are genuine
  duplicate entries in the source VCF (two cards, same number), not a matcher
  error.

Also: placed /home/jp/contacts.vcf at /var/lib/vm-transcribe/contacts.vcf
(640 asterisk:asterisk), set backends=file, cleared stale cache, restarted
vm-portal.
2026-08-13 11:13:12 +01:00
jp
857284abbf Initial build: Asterisk voicemail transcription + portal
- mailcmd replacement (vm_mailcmd.py): faster-whisper transcription (CPU int8),
  extractive summary + intent tags + spoken-digit number extraction,
  multipart/alternative HTML email, fail-safe relay of original message
- Telegram DM delivery (vm_telegram.py) with per-mailbox routing
- Caller-ID -> name (vm_contacts.py): file / google / carddav backends
- SQLite store (vm_store.py) with content-addressed audio
- FastAPI portal (vm_web.py): PIN login, list/play/delete, per-user settings,
  zero JS, loopback-only behind Apache TLS
- Backfill importer (vm_import.py) for existing spool recordings
- systemd unit, Apache vhost + certbot TLS, install.sh
- Docs: INSTALL, CONFIGURATION, ARCHITECTURE, OPERATIONS, SECURITY, TESTING

Verified end-to-end on mail.txt3.net: 157 historical messages backfilled,
live voicemail -> transcribed -> stored -> visible at https://vm.txt3.net.
2026-08-13 09:31:41 +01:00