# Carrot Patch — Web Control Panel

A full frontend admin dashboard for your bot: users, promo codes, events,
broadcasts, and the admin log — all in one page, no more typing commands
into the bot chat.

## What's in this bundle

- **`admin_panel.py`** — a small Flask server exposing the panel's REST API.
  It imports your existing `db.py` and `config.py` directly, so it reads and
  writes the *same* `game.db` your bot already uses. There's no second
  database and nothing to keep in sync.
- **`panel_static/index.html` + `panel_static/app.js`** — the frontend. Plain
  HTML/CSS/JS, no build step, no framework — just open it through the Flask
  server.
- **`db.py`** — your original file, with a handful of additions appended at
  the bottom: `list_promos()`, and a small `broadcast_queue` table with
  `queue_broadcast()` / `get_pending_broadcasts()` / `finish_broadcast()` /
  `list_broadcasts()`. Also, `DB_PATH` now honors an optional
  `GAME_DB_PATH` environment variable, which is handy for testing against a
  copy of your database without touching the real one.
- **`main.py`** — your original file, with one addition: a
  `process_broadcast_queue()` background task, scheduled once in `main()`,
  that polls the new `broadcast_queue` table every 5 seconds and sends any
  pending job through the bot's own `send_message`.

## Why broadcasting works this way

The panel's Flask process and your bot's rubpy process are two different
programs. Only the bot process is actually logged in to Rubika, so only it
can send messages. When you click "Queue broadcast" in the panel, it just
writes a row to `broadcast_queue`; your running `main.py` picks it up within
a few seconds, sends to everyone in the target audience, and writes back the
sent/failed counts, which show up in the panel's "Recent broadcasts" table.
**This means broadcasts only go out while `main.py` is running** — the panel
will show a job as "pending" indefinitely if the bot isn't up.

Everything else (ban/unban, size changes, promo codes, events) writes
straight to `game.db`, so it takes effect immediately regardless of whether
the bot process is running.

## Setup

```bash
pip install -r requirements.txt

# Pick a strong password for the panel — this is a separate login from your
# Rubika admin IDs, since a browser can't hold a Rubika session.
export ADMIN_PANEL_PASSWORD="choose-a-strong-password"

python admin_panel.py
```

Then open `http://localhost:8787` (or whatever host you deploy it on).

Keep `main.py` running as usual, in a separate process — the panel doesn't
replace it, it complements it.

### Environment variables

| Variable | Purpose | Required |
|---|---|---|
| `ADMIN_PANEL_PASSWORD` | Password to log into the panel | Yes — the app refuses to start without it |
| `PANEL_PORT` | Port to serve the panel on (default `8787`) | No |
| `PANEL_SECRET_KEY` | Flask session signing key | No — a random one is generated per process start if unset, which means logins won't survive a server restart. Set a fixed value if that matters to you. |
| `GAME_DB_PATH` | Override which `.db` file `db.py` connects to | No — defaults to `game.db` next to `db.py`, same as the bot |

### Deploying it somewhere reachable

If you want to reach the panel from outside your server, put it behind a
reverse proxy (nginx/Caddy) with HTTPS — this app is only a plain HTTP dev
server. It has no rate limiting beyond a small delay on failed logins, so
don't expose it directly to the internet without a proxy and preferably an
IP allowlist, since it can ban users and send broadcasts to your whole
user base.

## One thing worth fixing separately

`main.py` currently has:

```python
BOT_TOKEN = os.environ.get("RUBIKA_BOT_TOKEN", "HBFJD0HOQQDBBQWORNGYJRCCYJCJIBEWTJQNQJFXYKLKTLPSJZRMBERKAINWEJAX")
```

That fallback value is a live bot token hardcoded into the source. Since
this file gets shared/uploaded, it's worth rotating that token in Rubika's
bot settings and removing the hardcoded fallback, so the bot only ever runs
with a token supplied via the environment.

## Panel sections

- **Dashboard** — user count, total/average size, banned count, global
  top 10, currently-active events, and how many broadcasts are queued.
- **Users** — search by ID or name; add to size, set size exactly, ban
  (with reason) or unban, and see ban history + achievement progress.
- **Promo codes** — create a code with an amount and expiry; see
  redemption counts for existing codes.
- **Events** — schedule any of your `EVENT_TEMPLATES` for a date/time
  window, see status (upcoming/active/finished), delete an event.
- **Broadcast** — message all users, only active ones, or only banned
  ones; see delivery status once the bot process has processed the job.
- **Admin log** — every action from both the panel and the bot's own
  admin commands, in one timeline.
