-
Notifications
You must be signed in to change notification settings - Fork 0
Build initial Argus webhook service with dashboard #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
iefiru
wants to merge
1
commit into
main
Choose a base branch
from
initial-argus-pr
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| .DS_Store | ||
| .git | ||
| .pytest_cache | ||
| .ruff_cache | ||
| .venv | ||
| __pycache__ | ||
| *.py[cod] | ||
| argus.db | ||
| *.db |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # ── Secrets ─────────────────────────────────────────────────────────────────── | ||
|
|
||
| # Webhook authentication | ||
| WEBHOOK_SECRET=your-random-secret-here | ||
|
|
||
| # Discord webhooks per channel (name is case-insensitive in URL; env key must be upper-case) | ||
| DISCORD_WEBHOOK_SPRINT=https://discord.com/api/webhooks/... | ||
| DISCORD_WEBHOOK_MEETUP=https://discord.com/api/webhooks/... | ||
|
|
||
| # ── Settings ────────────────────────────────────────────────────────────────── | ||
|
|
||
| # Daily report schedule (default: 09:00 Asia/Taipei) | ||
| REPORT_HOUR=9 | ||
| REPORT_MINUTE=0 | ||
| REPORT_TIMEZONE=Asia/Taipei | ||
|
|
||
| # Database path | ||
| DB_PATH=argus.db | ||
|
|
||
| # Health check: SQLite connect/query timeout in seconds | ||
| HEALTHCHECK_DB_TIMEOUT=1.0 | ||
|
|
||
| # KKTIX organizer subdomain (e.g. "example" for example.kktix.cc) | ||
| KKTIX_ORGANIZATION=example | ||
|
|
||
| # ── Dashboard (OAuth) ───────────────────────────────────────────────────────── | ||
|
|
||
| # Google OAuth 2.0 credentials — https://console.cloud.google.com/apis/credentials | ||
| # Authorized redirect URI must include: <your-domain>/dashboard/oauth/callback | ||
| GOOGLE_OAUTH_CLIENT_ID= | ||
| GOOGLE_OAUTH_CLIENT_SECRET= | ||
|
|
||
| # Random ≥32-byte hex for signing session cookies | ||
| # Generate with: python -c "import secrets; print(secrets.token_hex(32))" | ||
| SESSION_SECRET= | ||
|
|
||
| # Comma-separated email allowlist for dashboard access | ||
| [email protected],[email protected] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| __pycache__/ | ||
| *.py[cod] | ||
| .DS_Store | ||
| *.egg-info/ | ||
| dist/ | ||
| .env | ||
| *.db | ||
| .hatch/ | ||
| .ruff_cache/ | ||
| .pytest_cache/ | ||
|
|
||
| # AI assistant configs (personal, not shared) | ||
| .claude/ | ||
| CLAUDE.md | ||
| AGENTS.md |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| FROM python:3.12-slim-bookworm | ||
|
|
||
| ENV PYTHONDONTWRITEBYTECODE=1 | ||
| ENV PYTHONUNBUFFERED=1 | ||
|
|
||
| WORKDIR /app | ||
|
|
||
| RUN apt-get update \ | ||
| && apt-get install -y --no-install-recommends sqlite3 \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| COPY pyproject.toml README.md LICENSE.txt ./ | ||
| COPY src ./src | ||
|
|
||
| RUN pip install --no-cache-dir . | ||
|
|
||
| CMD ["argus"] | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is empty, do you plan to add a license, or should we remove it? |
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,114 @@ | ||
| # argus | ||
| # Argus | ||
|
|
||
| KKTIX webhook receiver with daily Discord reports and a Google-OAuth-protected dashboard for visualizing registration trends. | ||
|
|
||
| ## Installation | ||
|
|
||
| ```bash | ||
| pip install git+https://github.com/yourname/argus.git | ||
| ``` | ||
|
|
||
| ## Environment Variables | ||
|
|
||
| ### Secrets | ||
|
|
||
| | Variable | Required | Description | | ||
| |----------|----------|-------------| | ||
| | `WEBHOOK_SECRET` | Yes | KKTIX auth header value | | ||
| | `DISCORD_WEBHOOK_<CHANNEL>` | Yes (≥1) | Discord webhook URL per channel, e.g. `DISCORD_WEBHOOK_SPRINT` | | ||
| | `GOOGLE_OAUTH_CLIENT_ID` | Yes | Google OAuth 2.0 client ID | | ||
| | `GOOGLE_OAUTH_CLIENT_SECRET` | Yes | Google OAuth 2.0 client secret | | ||
| | `SESSION_SECRET` | Yes | Random ≥32-byte hex string for signing session cookies | | ||
|
|
||
| ### Settings | ||
|
|
||
| | Variable | Default | Description | | ||
| |----------|---------|-------------| | ||
| | `KKTIX_ORGANIZATION` | — | KKTIX organizer subdomain, e.g. `example` for `example.kktix.cc`; required to auto-fetch event start time and capacity | | ||
| | `REPORT_HOUR` | `9` | Report hour | | ||
| | `REPORT_MINUTE` | `0` | Report minute | | ||
| | `REPORT_TIMEZONE` | `Asia/Taipei` | Report timezone | | ||
| | `DB_PATH` | `argus.db` | SQLite database path | | ||
| | `HEALTHCHECK_DB_TIMEOUT` | `1.0` | `/health` endpoint DB connect timeout in seconds | | ||
| | `LOG_LEVEL` | `INFO` | Python application log level | | ||
| | `ALLOWED_EMAILS` | — | Comma-separated email allowlist for dashboard access | | ||
| | `ARGUS_HTTPS_ONLY` | `0` | Set to `1` to mark session cookies as Secure | | ||
|
|
||
| ## Usage | ||
|
|
||
| ```bash | ||
| argus | ||
| ``` | ||
|
|
||
| ## KKTIX Webhook Setup | ||
|
|
||
| Configure one endpoint per channel. The channel name (case-insensitive) maps to a `DISCORD_WEBHOOK_<CHANNEL>` env var. | ||
|
|
||
| | Field | Value | | ||
| |-------|-------| | ||
| | URL | `https://your-domain/webhook/kktix/<channel>` | | ||
| | Auth header name | `x-kktix-secret` | | ||
| | Auth header value | value of `WEBHOOK_SECRET` | | ||
|
|
||
| Example: sending to the `sprint` channel → | ||
| URL: `https://your-domain/webhook/kktix/sprint`, env var: `DISCORD_WEBHOOK_SPRINT` | ||
|
|
||
| ## Dashboard | ||
|
|
||
| A Google-OAuth-protected web UI for viewing per-event registration time series. | ||
|
|
||
| - **Event list:** `/dashboard` | ||
| - **Per-event chart:** `/dashboard/events/{slug}` — line chart of Total + each ticket type, with capacity (horizontal dashed) and event start (vertical dashed) reference lines. | ||
|
|
||
| ### One-time Google OAuth setup | ||
|
|
||
| 1. Open [Google Cloud Console — Credentials](https://console.cloud.google.com/apis/credentials). | ||
| 2. Create an **OAuth 2.0 Client ID** (Application type: **Web application**). | ||
| 3. Under **Authorized redirect URIs**, add: | ||
| - `http://localhost:8000/dashboard/oauth/callback` (for local dev) | ||
| - `https://<your-deploy-domain>/dashboard/oauth/callback` (for production) | ||
| 4. Copy the **Client ID** and **Client secret** into `.env` as `GOOGLE_OAUTH_CLIENT_ID` and `GOOGLE_OAUTH_CLIENT_SECRET`. | ||
| 5. Generate a session secret: | ||
| ```bash | ||
| python -c "import secrets; print(secrets.token_hex(32))" | ||
| ``` | ||
| Put the output into `.env` as `SESSION_SECRET`. | ||
| 6. List allowed users in `.env` as `[email protected],[email protected]`. | ||
|
|
||
| ### Try it locally | ||
|
|
||
| ```bash | ||
| set -a && source .env && set +a | ||
| hatch run serve | ||
| # open http://localhost:8000/dashboard | ||
| ``` | ||
|
|
||
| You will be redirected to Google to sign in. Only emails in `ALLOWED_EMAILS` are granted access. | ||
|
|
||
| ## Production / Deployment | ||
|
|
||
| When deploying (e.g. to Railway): | ||
|
|
||
| - **Railway builds the Dockerfile** using `python:3.12-slim-bookworm`, installs `sqlite3` for SSH database inspection, installs the package with `pip install .`, and starts the `argus` console command. `argus` reads Railway's injected `PORT` environment variable at runtime. | ||
| - **Mount a persistent volume** at `/data` (or wherever) and set `DB_PATH=/data/argus.db`. SQLite written to the container's local filesystem will be wiped on every redeploy. | ||
| - **`SESSION_SECRET` is required** — the app refuses to boot without it. Generate with `python -c "import secrets; print(secrets.token_hex(32))"`. | ||
| - **`PORT` is read from env** automatically (Railway and most container platforms inject it). No code change needed. | ||
| - **`ARGUS_HTTPS_ONLY=1`** — set this once the deploy URL is HTTPS-only, to add the `Secure` flag to session cookies. | ||
| - **Google OAuth redirect URI** must be added in Cloud Console: `https://<your-domain>/dashboard/oauth/callback`. | ||
|
|
||
| See [SPEC.md → Deployment](SPEC.md#deployment-railway) for the full Railway walkthrough. | ||
|
|
||
| ## Development | ||
|
|
||
| Copy `.env.example` to `.env` and fill in the values, then source it before running any command: | ||
|
|
||
| ```bash | ||
| set -a && source .env && set +a | ||
| hatch run serve # start server | ||
| hatch run test # run automated tests | ||
| hatch run lint # lint | ||
| hatch run fmt # format | ||
|
|
||
| # Visual inspection of Discord report (sends a real webhook): | ||
| ARGUS_MANUAL_TEST=1 hatch run pytest tests/test_discord_format_manual.py -v -s | ||
| ``` |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using
uvicorn argus.main:appin Docker might give us more flexibility (e.g. configuring workers, reload, etc).