Commit graph

10 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
27f1d5f149 Add pluggable payment-provider layer; implement LiqPay alongside Monobank
Introduces bober_bbq/payments/base.py as the shared provider contract
(ProviderNotConfigured/ProviderError, the atomic claim_order_paid race
guard, and a documented function-shape convention) and
bober_bbq/payments/registry.py for provider selection/dispatch, so
checkout.py, orders.py, and reconciliation.py no longer hardcode
Monobank. monobank.py's public names/behavior are unchanged -- its
exceptions now subclass the generic ones and claim_order_paid is
re-exported from base.py, but every existing import keeps working.

Adds liqpay.py (Checkout/CNB API: base64(JSON)+sha1 signing, form-encoded
webhook, status polling, a local checkout-redirect bridge page since
LiqPay has no server-side "create invoice" call) and
api/payments_liqpay.py for its webhook + redirect routes. Wires a
"payment_provider" setting (admin-configurable, env fallback) and adds
matching LiqPay admin settings fields/test button.

No live LiqPay credentials were available to test against; verification
is a re-derived payment-race regression test (webhook/poll/reconciliation
racing to mark an order paid, for both providers) plus mocked-HTTP
structural checks. Uncertain LiqPay details (verify_token's error-code
heuristic) are flagged in liqpay.py and docs/PAYMENTS.md, which also
documents adding a third provider (Fondy, not implemented).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-27 13:38:24 +03:00
b636d0584a Rate-limit /admin/login and alert on insecure default credentials
No brute-force protection existed on the admin login form. Added an
in-memory per-IP lockout (5 failed attempts / 15 min -> 15 min lockout),
alerting the owner via Telegram when a lockout triggers.

Also warn loudly at startup (log + owner Telegram alert, not a hard
refusal) if SECRET_KEY or the admin password are still the config.py
defaults — previously a skipped .env line would silently run production
with session-forgeable SECRET_KEY or an admin/admin login.
2026-08-27 13:07:09 +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
10a011ec09 Add optional auto-close for the Checkbox cash-register shift
A ПРРО shift is legally capped at 24h open — nothing was ever closing
it. Adds an opt-in daily auto-close (configurable time, checked every
~10 min via APScheduler) plus a manual "Закрити зміну зараз" button in
Налаштування → Фіскалізація. Off by default like the rest of the
Checkbox integration; failures notify the owner in Telegram rather
than failing silently, since a stuck-open shift is a real compliance
risk.
2026-08-13 18:00:55 +03:00
66f51286ba Add automatic Google Drive database backups with rotation
Opt-in via "gdrive_backup_enabled" — off by default, no effect until
configured. Uses a Google service account (no interactive OAuth, so it
runs unattended from a headless server forever).

- bober_bbq/utils/gdrive_backup.py: uploads a fresh local SQLite backup
  on a configurable interval, then deletes the oldest Drive files once
  the folder holds more than gdrive_retention_count backups. The
  scheduled job (hourly, in run_web.py) re-checks the interval itself
  each tick rather than being tied to a fixed APScheduler schedule, so
  changing the interval in the admin panel takes effect without a
  restart. Failures notify the owner in Telegram, matching the
  existing monobank-token-health pattern.
- Admin Backups page gets a new section: paste the service account's
  JSON key + target Drive folder ID, set interval/retention, "Перевірити
  підключення" to verify without uploading, "Backup зараз" to trigger
  one immediately, plus inline step-by-step setup instructions (Cloud
  Console → enable Drive API → service account → JSON key → share the
  folder with its email → paste both here).
- gdrive_last_backup_at/gdrive_last_backup_status are intentionally
  NOT in DEFAULT_SETTINGS (job-written only) — putting them there would
  make the generic settings-form save loop blank them out on every
  unrelated settings save, since there's no form field for them.
2026-08-09 22:09:04 +03:00
0fe2a64292 Add payment reliability safety-net: reconciliation + token health check
Webhooks can be missed (network blip, mid-migration domain change, etc.)
leaving a paid order stuck as "unpaid" forever. Add two background jobs
(APScheduler, in the web process):

- reconcile_pending_payments — every 10 min, re-checks unpaid card
  orders from the last 6 hours directly against Monobank's invoice
  status API and marks them paid + notifies customer/manager if the
  webhook was missed.
- check_monobank_token_health — hourly ping via verify_token(); if a
  previously-working token starts failing, alerts OWNER_IDS in Telegram
  so a revoked/expired token doesn't silently break card payments.
2026-08-09 05:23:04 +03:00
8a92f9f895 Overhaul admin panel: rich dashboard, order workflow, customers, statistics, Telegram chats, system status, backups, admin users 2026-08-08 17:06:30 +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