11 Commits

Author SHA1 Message Date
jp
e390e3a145 Separate JSON API backend + React+react-router frontend
- 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
2026-08-13 17:55:40 +01:00
jp
49bdd342c2 Remove node_modules from git; add to .gitignore 2026-08-13 17:42:59 +01:00
jp
0917366cdf Separate JSON API backend (vm_api.py) + React+TanStack frontend scaffold
- 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
2026-08-13 17:42:33 +01:00
jp
7570de5cb5 Search/filter voicemails + contacts + per-contact history
- 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
2026-08-13 13:59:29 +01:00
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