Postgres is now the sole source of truth: delete sheets.py and
import_from_sheets.py, strip mirror_entity/mirror_async/_mirror_worker and
POST /api/sync from app.py, drop the tab/mirror keys from db.py's TABLES.
Re-point n8n/renewal-reminder.json at the CRM's own HTTP API (GET
/api/clients, POST /api/activity_log) instead of the Sheets nodes, and drop
SHEET_ID/GOOGLE_SA_JSON from deploy env/compose and requests from
requirements.txt (PyJWT stays — still used by booking_api.py). Updates
docs/README/playbooks accordingly and closes the old #5 (atomic mirror) as
moot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Flask-session login scoped to one client_id (never a request param),
self-service + operator-triggered password reset via single-use tokens,
and an Owner accounts tab on the CRM dashboard.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a `credentials` entity to the back office (never mirrored to
Sheets, gated by the CRM token even to read) so client logins like
the auto-generated Easy!Appointments provider password can be viewed
and copied from the dashboard instead of getting lost — the actual
cause of the happynails password going missing. Onboarding now saves
that generated password instead of discarding it. Also adds
Documentation.md, brings README/TODO in line with the current
Postgres-first architecture, and tidies the backlog.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Header button on the CRM linking back to the operator onboarding page at "/"
(same host, same basic-auth).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the 1280px content cap and the table's overflow scroll; let cells wrap
(word-break) instead of forcing a horizontal scrollbar, so rows span the full
screen width and show full values.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Opening the dashboard at /crm (no trailing slash) made the relative `api/leads`
fetch resolve to /api/leads -> 404. Derive the API base from
location.pathname so it works at both /crm and /crm/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Token-protected mutation endpoints (POST/PATCH/DELETE), audit-logged to
activity_log. Token injected into the basic-auth-gated dashboard.
- Add auto-generates IDs (next C-#### / L-<epoch>), sets created/received/status
defaults, and computes client renewal_date from start + billing cycle
(parity with the onboarding workflow).
- Dashboard: per-row edit (✎) and delete (🗑), "+ Neu" modal form per entity.
Verified end-to-end: add lead/client, edit, delete, renewal compute, token
gating (403), 404s. DB-only for now; DB->Sheets mirror is the next step.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stand up the DB-first CRM backbone (architecture pivot: Postgres is the
source of truth, Google Sheets becomes a one-way downstream mirror).
- backoffice/ stack: smb-db (Postgres 16) + smb-crm (Flask/waitress service).
- Schema mirrors the six Sheet tabs (clients, leads, projects, activity_log,
bookings, invoices) with typed columns + updated_at triggers.
- Service-account Sheets client (PyJWT) for the one-time import + future mirror.
- import_from_sheets.py: idempotent seed of Postgres from the live Sheets.
- Read dashboard (Leads & Clients tables) at onboard.mivanchenko.de/crm,
behind the existing Caddy basic-auth; JSON API reads straight from Postgres.
Deployed + verified: import seeded DB, dashboard/API live, no-auth blocked,
onboarding form unaffected. Add/edit/delete + DB->Sheets sync + n8n ingest
swap are the next steps.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>