Commit graph

8 commits

Author SHA1 Message Date
375022e739 Add backup_remote.py and wire up the rest of the rclone rework
Continuation of the previous commit (gdrive_backup.py removal landed
separately by accident) — adds the new module itself, the admin
routes/template using it, generalized Setting keys, updated docs/
provisioning notes, and tests.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-05 22:57:59 +03:00
391da771f7 Document Postgres backup gap as a TODO before any real migration 2026-08-27 15:19:10 +03:00
1ede4b9692 Add opt-in PostgreSQL support alongside default SQLite
SQLite stays the default (fine for a small single-cafe deployment); this
makes Postgres a supported alternative via DATABASE_URL for clients who
outgrow SQLite's single-writer lock (multiple concurrent admin users,
high write volume).

- requirements.txt: add psycopg2-binary (Postgres driver for SQLAlchemy).
- bober_bbq/migrate.py: the admin_users.created_at/last_login_at ALTER
  TABLE DDL used the type name DATETIME, which SQLite accepts (it only
  has type affinity) but Postgres doesn't recognize as a real type — use
  TIMESTAMP instead, valid on both.
- bober_bbq/admin/inventory.py: the ingredient-name duplicate check used
  db.func.lower() in SQL, which is ASCII-only on SQLite and silently
  fails to match Cyrillic names (same bug already fixed this session in
  admin/suppliers.py's supplier-name dedup) — compare in Python instead.
  Not a Postgres crash risk (Postgres's LOWER() is Unicode-aware), but
  worth fixing for consistency since moving to Postgres would otherwise
  silently change this check's behavior.
- bober_bbq/utils/gdrive_backup.py and admin/system.py were audited for
  SQLite-specific behavior (PRAGMA integrity_check, direct file
  copy/restore) but already guard every such path behind
  `uri.startswith("sqlite:///")` / `_sqlite_path()` checks that
  gracefully no-op with a clear message on Postgres — no changes needed.
- scripts/migrate_sqlite_to_postgres.py (new): standalone, manually-run,
  one-off tool for actually moving a specific client's data off SQLite.
  Reuses the app's own SQLAlchemy models (db.metadata) rather than raw
  table introspection, copies rows in FK-safe order, refuses to run
  against a non-empty target unless --truncate is passed, and resets
  Postgres auto-increment sequences after copying explicit ids. Not
  wired into run_web.py or any automatic startup path.
- docs/DEPLOYMENT.md: new section on when/how to use Postgres instead of
  SQLite, standing up a Postgres instance, the DATABASE_URL format, and
  when/how to run the migration script.

Verified: create_app() + db.create_all() + migrate() + seed() (including
the ALTER-TABLE-ADD-COLUMN code path exercised against a DB missing
those columns) still work cleanly against a fresh temp SQLite DB. The
migration script's row-copy/FK-ordering/truncate/non-empty-guard logic
was verified end-to-end using SQLite as a stand-in target, since no
Postgres server is available in this environment — the Postgres-specific
pieces (actual connection, pg_get_serial_sequence/setval) are
unverified beyond code review.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 13:31:12 +03:00
a3c29095c8 Edit supplier deliveries after the fact; downloadable Excel/PDF statements
Two gaps: an unpaid delivery had no way to later be marked paid (or
corrected at all) short of delete-and-recreate, losing history; and
there was no way to hand a supplier or an accountant a statement of
account.

- New GET+POST /suppliers/<id>/deliveries/<id>/edit
  (supplier_delivery_edit.html) — edits every field including
  paid_amount/payment_date. Deliberately a pure ledger correction: like
  the existing delete button already warns, this never touches
  current_stock/StockMovement even if quantity or the ingredient link
  changes — stock only moves through the ingredient's own actions.
- New bober_bbq/utils/supplier_reports.py generates both a per-supplier
  detailed statement and an all-suppliers summary, each in Excel
  (openpyxl, already a dependency) and PDF (new: fpdf2), optionally
  scoped to a delivery-date range. Download buttons added to the
  supplier detail page and the suppliers list.
- PDF needs a real Cyrillic TTF; added fpdf2 to requirements.txt and
  documented (and will install) fonts-dejavu-core on the server rather
  than bundling a font in the repo — DejaVu is freely embeddable,
  Windows fonts are not. A missing font produces a clear flash message,
  not a 500.

Verified with a 33-check smoke test across both features (edit
correctness + stock isolation, date-range filtering on both statement
shapes, real route responses reopened/parsed, and the missing-font
fallback path) plus a full regression rerun of every supplier/inventory
smoke suite from this session.
2026-08-26 20:17:48 +03:00
c1e3e83d55 Add owner-alerting on unhandled errors, backup integrity checks, and a client-provisioning script
Reliability gaps found while investigating today's suppliers 500 (which I
only learned about because the user reported it — nothing in the app
itself said anything):

- New bober_bbq/utils/error_alerts.py: notifies OWNER_IDS via the existing
  Telegram send_message pattern (already used by gdrive_backup/
  reconciliation/checkbox_prro) on ANY unhandled request exception, via
  Flask's got_request_exception signal — purely observational, doesn't
  change the actual error response. Same helper now also wraps every
  APScheduler job in run_web.py, closing the one job (reconcile_payments)
  that had no failure handling at all and could die silently. A 15-minute
  per-(exception type, source) cooldown keeps a repeating failure from
  spamming the chat.
- gdrive_backup.py: verifies each local DB backup with PRAGMA
  integrity_check before it's ever uploaded (a corrupt copy now fails
  loudly instead of silently becoming an unusable "backup"), and now also
  archives+uploads static/uploads/ (product photos) alongside the DB —
  previously never backed up at all.

Also, per the earlier per-client template-readiness pass: provision_client.sh
automates docs/DEPLOYMENT.md's clone/.env/nginx/TLS/systemd steps for a
NEW client deployment (confirmation prompt before every irreversible
step; refuses to run against an existing install-dir). Not executed
anywhere this session — no target VPS yet, verified via `bash -n` and a
step-by-step review against the runbook it automates.

All of this is additive and was smoke-tested to confirm zero behavior
change for the live instance: same error responses, same backup content,
same scheduler behavior when nothing is actually broken.
2026-08-25 18:11:15 +03:00
922d9c5172 Make brand identity reusable for redeploying to other clients
Groundwork for reusing this codebase per-client (own VPS/DB/bot per
cafe): the operational config (bot token, DB URL, secrets, Checkbox PRRO
creds) was already .env/Setting-driven, but ~13 spots still hardcoded the
literal string "Bober BBQ" instead of reading the existing cafe_name
Setting, and the webapp's static shell (index.html title, PWA manifest)
had no templating at all since Settings only load after JS boots.

- Route the remaining hardcoded strings through Setting.get("cafe_name",
  "Bober BBQ") — same pattern already used correctly in bot/handlers/
  start.py. Every fallback stays "Bober BBQ", so production is byte-
  identical with no cafe_name override.
- Template webapp/index.html via Vite's native %VITE_CAFE_NAME% HTML
  replacement, backed by a committed webapp/.env (default "Bober BBQ",
  no secrets) with a per-client override via gitignored .env.local.
- Add webapp/scripts/gen-manifest.mjs to generate manifest.json from a
  new manifest.template.json the same way, since Vite doesn't process
  public/ assets — wired into the build script.
- Parameterize deploy.sh's systemd unit names and the admin log-viewer's
  log paths via optional SERVICE_WEB/SERVICE_BOT/LOG_FILE_WEB/
  LOG_FILE_BOT env vars, defaulting to today's literal values.
- Rewrite docs/DEPLOYMENT.md into a repeatable new-client runbook (fixed
  an existing /opt/bober-bbq vs /opt/bober-bbq-bot inconsistency, added
  a rebranding checklist).

Verified via a smoke test that every changed string still renders
"Bober BBQ" with no overrides present (matching prod's actual .env/DB
state), and that webapp builds both with and without a VITE_CAFE_NAME
override produce the expected output — including a no-override rebuild
confirming byte-identical output to before this change.
2026-08-25 17:54:29 +03:00
c3b01952c4 Document the one-command deploy.sh script on the live server 2026-08-08 22:54:54 +03:00
0eabebaca3 Initial commit: Bober BBQ Telegram bot + backend + admin + Mini App
Flask API/admin backend, aiogram bot with delivery/pickup FSM flows,
monobank payment integration, and a Vite/React Telegram Mini App for
menu browsing and cart management.
2026-08-08 15:50:50 +03:00