The contact create/delete endpoints and their Pydantic model were defined
twice (identical blocks). Python kept the second definition, so the first was
dead code. Drop the duplicate; module still compiles and each symbol is now
defined exactly once.
README, docs/{ARCHITECTURE,CONFIGURATION,INSTALL,OPERATIONS,SECURITY,TESTING}
and CHANGELOG were stale: they described the original SQLite + CardDAV +
zero-JS vm_web portal. Brought them in line with the actual code:
- MySQL (PyMySQL) as the default store; SQLite fallback only
- vm_contacts resolves from the MySQL contacts table (file/google/carddav removed)
- React SPA frontend + vm_api.py JSON API (:8098); vm_web.py now serves /audio/
- Apache vhost proxies /api/ -> :8098, /audio/ -> :8099, serves the SPA
- CSP relaxed to script-src 'self' 'unsafe-inline' (UI is now JavaScript)
- Added CHANGELOG 1.2.0 entry for the React/vm_api frontend work
- use flexbox space-between in .who
- name/number truncates with ellipsis
- menu pinned right with flex-shrink:0
- tighter button padding and dropdown gap
- ⋮ button beside contact name/number
- Call back uses tel: link with sanitised digits
- Lookup number opens Google search in new tab
- dropdown closes on outside click
- create_contact/update_contact now update matching messages
- normalize callerid spaces/quotes before LIKE match
- match both E.164 and 0-prefix variants
- extract E.164 from angle brackets when present
- show 'Number: ...' at top of modal instead of readonly input
- use same sanitiseNumber() for message list callback links
- replace var(--bg-surface) with literal #0a0e17 in input CSS
- add -webkit-appearance:none to prevent native widget override
- apply to both main and .modal input rules
- vm_api.py: pure FastAPI JSON API on :8098, CORS enabled
- vm-api.service: systemd unit for API backend
- frontend/: Vite + React 18 + react-router-dom 7 + TanStack Query
- React pages: messages, contacts, contact history, settings, login, logout
- Apache: /api/ proxy to :8098, DocumentRoot -> frontend/dist, SPA fallback
- Backward compat: old :8099 portal still running as fallback
- vm_api.py: pure FastAPI JSON API on :8098 with CORS
- vm-api.service: systemd unit for API (replaces inline HTML vm_web.py for frontend)
- vm_web.py: kept intact as fallback; Apache now proxies /api/ -> :8098
- frontend/: Vite + React + TanStack Query scaffold
- Apache vhost updated: DocumentRoot -> frontend/dist, /api/ proxy, SPA fallback
- All original HTML pages still functional via :8099 during transition
- Messages: query q, date_from, date_to, unread filters via GET
- Contacts: q search across name/number/email
- Contact history: /contacts/history/{num} lists all voicemails from caller
- Contact names link to history page from message list
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.
- 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)
- 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
- 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.