From 098b6dfd39953a897eb170474960c8517c977577 Mon Sep 17 00:00:00 2001 From: rogalik27 Date: Thu, 16 Jul 2026 10:42:15 +0200 Subject: [PATCH] TODO: resolve conflict, map backlog to Gitea issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reconcile the stashed rewrite with the newer 2026-07-15 state (credentials store, deploy pipeline). Backlog is now tracked as Gitea issues #1–#12; TODO.md is the high-level map. Notes: #8 (booking-sync) closed as already done via the shared Easy!Appointments instance; #4 narrowed (smb-db pg_dump exists, n8n/Gitea/ offsite/restore remain). Added a "manual operator follow-ups" section for the human-only items (n8n re-import, credential username, deploy-pipeline test, etc). Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01DgbgipE41xwPcQJnhnq1J1 --- TODO.md | 164 +++++++++++++++++++------------------------------------- 1 file changed, 54 insertions(+), 110 deletions(-) diff --git a/TODO.md b/TODO.md index 89287a6..f89c414 100644 --- a/TODO.md +++ b/TODO.md @@ -1,124 +1,68 @@ # 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. +> **Active engineering work is tracked as Gitea issues:** https://git.mivanchenko.de/BPPP/smb-online/issues +> This file is the high-level map; the issues hold the detail, labels, and status. -Implications to work through: -- [x] **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. -- [x] 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_notes` columns recording that per-client decision, and every deploy this - session (happynails' credential, the client stacks in `deploy/clients/`) went through that - path rather than a fixed per-tier template. Nothing left to build here. -- [ ] **Needs your call:** Revisit the `Clients.tier` field 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. +## Open (→ Gitea issue) -## 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. -- [x] **Read:** Leads & Clients tables, served by `smb-crm` straight from Postgres. -- [x] **Add:** "+ Neu" modal per entity; auto-IDs (C-#### / L-epoch), renewal auto-compute. -- [x] **Edit:** per-row ✎ modal, token-protected PATCH. -- [x] **Delete:** per-row 🗑 (token-protected), audit-logged — replaces the scratchpad cleanup. -- [x] Behind Caddy basic-auth; mutations gated by `CRM_API_TOKEN`; all changes audit-logged. -- [x] One-way **DB→Sheets mirror** keeps the Sheet overview current after every change. -- [x] **n8n ingest swap:** `lead-intake` & `onboarding` now POST into the DB API (not Sheets); - Telegram notify unchanged. `renewal-reminder` still reads Sheets (fine — Sheets mirrors DB). +**Bugs** +- **#1** Booking POST is a plain `INSERT` — reschedule re-fires `EA-` → PK conflict / 500 + (`backoffice/app/app.py:205`; needs `ON CONFLICT DO UPDATE`). `bug` `crm` `booking` -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. -- [x] Daily backup of the `smb-db` Postgres 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). +**CRM / back office** +- **#2** Decouple tier from business type → hosting/plan model; realign onboarding form + workflow. `enhancement` `crm` +- **#3** Extend back-office CRUD beyond Leads & Clients (projects, bookings, invoices, activity). `enhancement` `crm` +- **#5** Make the DB→Sheets mirror atomic (no brief empty-sheet window). `tech-debt` `crm` -## 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`. +**Booking** +- **#7** Proper Orders flow + Orders table/tab (pizzeria demo posts into Leads as a stop-gap). `enhancement` `booking` +- **#9** Per-client Easy!Appointments isolation (booking is currently one shared EA instance). `enhancement` `booking` +- **#10** Custom branded slot-grid booking widget (replace raw EA embed). `enhancement` `booking` -Follow-ups (not yet done — all need you, not more code): -- [ ] Re-import the updated `n8n/onboarding.json` into 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 `username` is 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.secret` at rest (e.g. pgcrypto) instead of - plaintext if the dashboard is ever exposed more broadly than Caddy basic-auth + host security. +**Security / ops / infra** +- **#4** Backups: smb-db `pg_dump` script exists (`deploy/backup/smb-db-backup.sh`); still need + n8n + Gitea coverage, an **offsite** copy, and a **tested restore**. `ops` `infra` +- **#6** Harden n8n auth — remove dead `N8N_BASIC_AUTH_*` (password=`password`); confirm strong owner login. `security` `infra` +- **#11** Rotate the leaked Gitea PAT + establish secrets management. `security` `ops` +- **#12** CI (Gitea Actions): validate `n8n/*.json` + compose before deploy. Runner infra already + exists (see deploy pipeline in Done) — this just adds a validate-on-push workflow. `ops` `infra` -## Deploy pipeline — trigger from the Gitea UI — DONE 2026-07-15 (backoffice only) -**Goal:** stop hand-running scp/ssh/docker-compose-build for every change (that's how the -credentials feature above originally shipped). Gitea Actions is now enabled on -`git.mivanchenko.de`, with a self-hosted runner (`homelab-runner`, labels `self-hosted`/ -`homelab`) running natively on the homelab host as the `act-runner` systemd service (survives -reboots). `.gitea/workflows/deploy-backoffice.yml` is a `workflow_dispatch`-only job (manual -"Run workflow" button — deliberately not on push, since triggering it = deploying) that syncs -`backoffice/db` + `backoffice/app`, re-applies the idempotent schema, rebuilds/restarts -`smb-crm`, and health-checks it. -- [x] Runner registered — runs directly on the host with the same `docker`/filesystem access the - operator already has, so no SSH deploy key was needed for the runner itself. -- [x] Fixed along the way: the runner-token generator needs `docker exec -u git gitea …` (Gitea - refuses to run as root, `docker exec` defaults to root); the runner is registered against - Gitea's internal container IP (`http://172.24.0.2:3000`), not `git.mivanchenko.de`, because the - homelab can't reach its own public hostname (NAT hairpin) — `https://git.mivanchenko.de` calls - from the host itself just hang. -- [x] First real run failed twice, both fixed: (1) `actions/checkout@v4` needs Node.js, which the - intentionally-minimal host-mode runner doesn't have — replaced with a plain `git clone`; - (2) the repo is private, so that clone needs auth — added a `DEPLOY_TOKEN` Gitea Actions secret - (a `read:repository,write:repository`-scoped token under the admin account, stored only in - Gitea's encrypted secret store, stripped from the cloned workspace's `git remote` right after - checkout). -- [ ] **Needs you:** actually press "Run workflow" once in the Gitea UI (Actions tab → - Deploy backoffice (smb-crm)) as the real end-to-end test — I deliberately didn't mint a Gitea - API token to test-trigger it myself (that would've left a stray write-scoped credential behind). -- [ ] Extend the same pattern to the other `deploy/*` groups (`booking/`, `smb-demos/`, - `clients//`) — straightforward once the backoffice one is confirmed working, since - they're all simpler (mostly just `docker compose up -d`, no DB migration step). -- [ ] Once you're adding collaborators: anyone with **write** access to this repo can trigger a - deploy. Worth being deliberate about who gets write vs. read/PR-only access — **your call**. - -## Other -- [ ] Harden n8n auth: compose still has deprecated `N8N_BASIC_AUTH_*` with password `password` - (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 `Leads` tab 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. +## Manual / operator follow-ups (not code issues — need you, not more code) +- **n8n re-import:** load the updated `n8n/onboarding.json` (new "Save credential" node) into the + live n8n and re-attach the real `__CRM_TOKEN__` / `__EA_AUTH__` bindings — can't be scripted from outside n8n. +- **happynails credential:** its `username` is still blank — confirm in the EA admin UI and fill it + in via the dashboard's ✎ edit on the Credentials tab (`C-0002`, cred `CR-1784116056728`). +- **Deploy pipeline:** press "Run workflow" once in the Gitea UI (Actions → Deploy backoffice) as + the real end-to-end test; then extend the same pattern to the other `deploy/*` groups + (`booking/`, `smb-demos/`, `clients//`). +- **Collaborator access:** anyone with **write** on this repo can trigger a deploy — be deliberate + about who gets write vs. read/PR-only before adding people. +- **Optional:** encrypt `credentials.secret` at rest (e.g. pgcrypto) if the dashboard is ever + exposed more broadly than Caddy basic-auth + host security. ## Done +- [x] 2026-07-15 — **Credentials in the CRM**: `credentials` entity (never mirrored to Sheets, + `X-CRM-Token`-gated even to read), dashboard Credentials tab with masked value + reveal/copy; + `n8n/onboarding.json` now persists the auto-generated EA provider password. Shipped live. +- [x] 2026-07-15 — **Gitea Actions deploy pipeline** (backoffice): self-hosted `homelab-runner` + (`act-runner` systemd service), `workflow_dispatch` `deploy-backoffice.yml` that syncs + `backoffice/`, re-applies the schema, rebuilds/restarts `smb-crm`, health-checks it. +- [x] 2026-07-14 — **Shared self-hosted booking**: Easy!Appointments stack (`deploy/booking/`), + auto-provisioning wired into onboarding, `booking-sync` workflow syncing bookings to the CRM + (**#8** — done differently than first scoped: one shared EA instance replaced the planned + Tier-B-Cal.com-webhook / Tier-A-GCal-poll split). +- [x] 2026-07-14 — **Per-client deploy tooling**: `deploy/clients/` isolated-stack model + + `new-client.sh` scaffolder. +- [x] 2026-06-30 — Daily **smb-db Postgres backup** script (`deploy/backup/smb-db-backup.sh`: + `pg_dump` + gzip, 14-day retention, cron on the homelab). (Broader coverage → #4.) +- [x] 2026-06-25 — Rethink the tier model: **demos are stack-agnostic starters** — dropped + tier/stack labels + jargon from all three demo pages & the chooser; booking widgets relabelled; + demo dates render on the current week so nothing looks stale. (Remaining model/schema work → #2.) +- [x] 2026-06-25 — **DB-first back office**: Postgres source of truth + Sheets mirror + CRUD + (Read/Add/Edit/Delete for Leads & Clients) + n8n ingest swap (lead-intake/onboarding → DB API). + Behind Caddy basic-auth, mutations gated by `CRM_API_TOKEN`, audit-logged. (Extensions → #3.) - [x] 2026-06-25 — Latency fix (n8n executions 1–4 min → ~6 s; DNS + PostHog telemetry). - [x] 2026-06-25 — Chooser reworded to client-friendly, jargon-free copy. -- [x] 2026-06-25 — Pizzeria demo with online ordering added. +- [x] 2026-06-25 — Pizzeria demo with online ordering added. (Proper Orders product → #7.) - [x] 2026-06-25 — Barbershop "Google Kalender" dot-overlap styling fixed. - [x] 2026-06-25 — All three demo pages neutralised (stack-agnostic, non-stale dates). -- [x] 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. -- [x] 2026-07-14 — Shared self-hosted booking: Easy!Appointments stack (`deploy/booking/`), - auto-provisioning wired into the onboarding workflow, `booking-sync` workflow syncing bookings - to the CRM. -- [x] 2026-07-14 — Per-client deploy tooling: `deploy/clients/` isolated-stack model + - `new-client.sh` scaffolder; back-office updates. -- [x] 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. -- [x] 2026-07-15 — Credentials-in-the-CRM feature built and shipped live (see above).