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>
8.2 KiB
TODO / Backlog
Rethink the tier model — decouple tier from business type
Goal: tiers should NOT be tied to a kind of business. Which "tier" (and whether a client's site is self-hosted or runs on Google's free services) is a decision made between me and the client, case by case — not something baked into a demo.
Implications to work through:
- Demos are stack-agnostic starters.
Drop tier/stack labels and jargon (self-hosted, Cal.com, Google-Stack, "≈ 0 €") from the public-facing demos & chooser.DONE 2026-06-25: chooser reworded; all three demo pages neutralised — demo bars now read "Beispielseite · So könnte Ihr Online-Auftritt aussehen", booking widgets relabelled to generic "Online-Terminbuchung"/"Sichere Buchung", Google-Kalender/Cal.com/self-hosted/Tier labels removed from rendered copy and HTML comments, demo booking dates now render on the current week via JS so nothing looks stale. - After a client signs, I decide hosting: self-host the page on the homelab, or stand it
up on the client's free Google account. Already how it works in practice — the CRM has
tier+stack_notescolumns recording that per-client decision, and every deploy this session (happynails' credential, the client stacks indeploy/clients/) went through that path rather than a fixed per-tier template. Nothing left to build here. - Needs your call: Revisit the
Clients.tierfield semantics — keep A/B as an internal effort/price band, or replace with something hosting-agnostic (e.g.hosting = google | selfhosted,plan = …). - Blocked on the above: Onboarding form + workflow currently ask for Tier A/B — realign once the field semantics are settled.
Onboard dashboard → manage Leads & Clients (CRUD) — DONE 2026-06-25
Built as a DB-first back-office (architecture pivot: Postgres is the source of truth, Sheets
is a one-way mirror — see backoffice/ and the smb-db-source-of-truth memory). Live at
onboard.mivanchenko.de/crm behind the existing basic-auth.
- Read: Leads & Clients tables, served by
smb-crmstraight from Postgres. - Add: "+ Neu" modal per entity; auto-IDs (C-#### / L-epoch), renewal auto-compute.
- Edit: per-row ✎ modal, token-protected PATCH.
- Delete: per-row 🗑 (token-protected), audit-logged — replaces the scratchpad cleanup.
- Behind Caddy basic-auth; mutations gated by
CRM_API_TOKEN; all changes audit-logged. - One-way DB→Sheets mirror keeps the Sheet overview current after every change.
- n8n ingest swap:
lead-intake&onboardingnow POST into the DB API (not Sheets); Telegram notify unchanged.renewal-reminderstill reads Sheets (fine — Sheets mirrors DB).
Follow-ups (not yet done):
- Extend CRUD UI beyond Leads/Clients (projects, bookings, invoices, activity) if useful.
Dashboard (
backoffice/app/static/index.html) still only has Leads/Clients tabs. - Daily backup of the
smb-dbPostgres volume — DONE:deploy/backup/smb-db-backup.sh(pg_dump+ gzip, keeps 14 days, cron on the homelab). - Make the DB→Sheets overwrite atomic (currently clear-then-write; brief mid-write window a reader could catch the cleared sheet — harmless for a human overview).
Credentials in the CRM — copy-to-clipboard — DONE 2026-07-15
Goal: be able to grab a client's login (e.g. their Easy!Appointments provider password) from
the CRM dashboard itself instead of digging through Vaultwarden or a live n8n execution log —
prompted by not being able to find the happynails booking password anywhere. New credentials
entity (backoffice/db/init.sql, backoffice/app/db.py), deliberately never mirrored to
Sheets (mirror: False, enforced in mirror_async/mirror_entity//api/sync and skipped by
import_from_sheets.py) and gated by X-CRM-Token even to read. Dashboard has a new
Credentials tab with a masked value, "👁 zeigen" reveal toggle, "📋 Kopieren" copy button.
n8n/onboarding.json now saves the auto-generated EA provider password instead of discarding it
(root cause of the happynails password being unrecoverable — it was never persisted anywhere).
Migrated + shipped to the live homelab (smb-db schema, smb-crm rebuilt, confirmed healthy).
Happynails' (C-0002) reset EA password is recorded as CR-1784116056728.
Follow-ups (not yet done — all need you, not more code):
- Re-import the updated
n8n/onboarding.jsoninto the live n8n instance (the new "Save credential" node). Needs the real__CRM_TOKEN__/__EA_AUTH__credential bindings re-attached in the n8n UI — not safely scriptable from outside n8n. - The happynails credential's
usernameis still blank — confirm it in the EA admin UI and fill it in via the dashboard's ✎ edit on the Credentials tab. - Your call, no rush: encrypt
credentials.secretat rest (e.g. pgcrypto) instead of plaintext if the dashboard is ever exposed more broadly than Caddy basic-auth + host security.
Deploy pipeline — trigger from the Gitea UI
Goal: stop hand-running scp/ssh/docker-compose-build for every change (that's how the
credentials feature above shipped). git.mivanchenko.de (Gitea, already the origin remote for
every repo here, running alongside the other homelab containers) supports Gitea Actions —
workflows in .gitea/workflows/ that run on a self-hosted runner and can be started with a
manual "Run workflow" button in the repo UI, not just on push.
- Register a Gitea Actions runner (can run directly on the homelab host, or in its own
container with the Docker socket mounted so it can
docker compose build/up -d). .gitea/workflows/deploy.yml:workflow_dispatchtrigger (the UI button), optionally also on push tomain. Steps: checkout → sync changed paths to/home/mivanchenko/smb-crm/(and whichever otherdeploy/*groups changed) → apply any pending SQL migrations →docker compose build+up -d --no-deps <service>for just the affected Compose group(s) → hit/healthz(or the equivalent) to confirm before declaring success.- Needs real secrets in Gitea's repo/runner secrets store (SSH deploy key or a runner already
on the host,
CRM_API_TOKEN, etc.) — your call on which, since it's a homelab access decision. - Once trustworthy, this replaces the manual migration step described in the credentials
section above and the
new-client.sh→ scp → ssh flow indeploy/clients/README.md.
Other
- Harden n8n auth: compose still has deprecated
N8N_BASIC_AUTH_*with passwordpassword(legacy/ignored by modern n8n owner-login). Needs your call — I can remove the dead env, but whether the owner account itself is strong enough is a judgment only you can make. - Orders: the pizzeria demo posts orders into the
Leadstab via the lead-intake webhook as a stop-gap. Build a proper Orders flow + sheet tab (items, total, mode, status) when the order-management product is real.
Done
- 2026-06-25 — Latency fix (n8n executions 1–4 min → ~6 s; DNS + PostHog telemetry).
- 2026-06-25 — Chooser reworded to client-friendly, jargon-free copy.
- 2026-06-25 — Pizzeria demo with online ordering added.
- 2026-06-25 — Barbershop "Google Kalender" dot-overlap styling fixed.
- 2026-06-25 — All three demo pages neutralised (stack-agnostic, non-stale dates).
- 2026-06-25 — DB-first back office: Postgres source of truth + Sheets mirror + CRUD + n8n ingest swap (lead-intake/onboarding → DB API). Live at onboard.mivanchenko.de/crm.
- 2026-07-14 — Shared self-hosted booking: Easy!Appointments stack (
deploy/booking/), auto-provisioning wired into the onboarding workflow,booking-syncworkflow syncing bookings to the CRM. - 2026-07-14 — Per-client deploy tooling:
deploy/clients/isolated-stack model +new-client.shscaffolder; back-office updates. - 2026-07-14 — Booking-sync workflow, done differently than originally scoped: the planned Tier-B-Cal.com-webhook / Tier-A-GCal-poll split was superseded by the tier-decoupling above — one shared Easy!Appointments instance handles booking for every client instead.
- 2026-07-15 — Credentials-in-the-CRM feature built and shipped live (see above).