Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0f56f5b
fix: align webhook status storage
tim48-robot Dec 31, 2025
9441371
fix: show org last sync for zero-contribution users
tim48-robot Dec 31, 2025
3897c01
feat(shared): migrate firestore storage to organization-scoped paths
tim48-robot Jan 12, 2026
3616127
feat(notifications): support multiple discord servers per github orga…
tim48-robot Jan 12, 2026
ed20a40
fix(pr-review): resolve asyncio event loop errors and add aiohttp dep…
tim48-robot Jan 12, 2026
cc0bb72
security(auth): protect debug endpoint and add setup success notifica…
tim48-robot Jan 12, 2026
30b92b1
fix(bot): improve setup reminders and update config templates
tim48-robot Jan 12, 2026
757e5fa
fix(firestore): restore notification_config while maintaining SaaS logic
tim48-robot Jan 12, 2026
faf7b1e
fix: resolve NoneType error in webhook status and revert stylistic re…
tim48-robot Jan 12, 2026
660db09
fix: resolve resource leaks and error handling in notification system
tim48-robot Jan 13, 2026
f68b617
refactor: remove on_ready setup reminder and simplify footer
tim48-robot Jan 13, 2026
0e83019
fix: validate setup before webhooks and show per-server timestamps
tim48-robot Jan 13, 2026
cecaf74
feat: add GitHub webhook handler for SaaS PR automation
tim48-robot Jan 13, 2026
0a94ccd
fix: include pr_review in Docker build for webhook automation
tim48-robot Jan 13, 2026
ae4b95b
fix: use try/except import pattern for pr_review package compatibility
tim48-robot Jan 13, 2026
cba969b
fix: install pr_review dependencies from its own requirements.txt
tim48-robot Jan 13, 2026
a750fd1
fix: update all pr_review utils to use try/except import pattern
tim48-robot Jan 13, 2026
36c0acc
fix: prevent OAuth session memory leak and extend setup state expiration
tim48-robot Jan 30, 2026
02830f6
refactor: remove /set_webhook command (PR automation disabled)
tim48-robot Jan 30, 2026
e0456a0
feat: role hierarchy validation + hide PR automation commands
tim48-robot Jan 30, 2026
54b09d2
style: premium UI redesign for all setup flow pages with elegant 500p…
tim48-robot Feb 9, 2026
46127eb
feat: add cross-thread communication infrastructure
tim48-robot Feb 12, 2026
94f9e1b
refactor: async offload all Firestore calls for SaaS responsiveness
tim48-robot Feb 12, 2026
b89bf4d
fix: replace global lock + polling with per-user event-driven /link
tim48-robot Feb 12, 2026
6a5d9ea
docs: add MAINTAINER.md with environment and feature re-enablement gu…
tim48-robot Feb 12, 2026
6e98612
security: add SECRET_KEY to .env.example
tim48-robot Feb 12, 2026
6e41cb1
refactor: optimize analytics responsiveness and update maintainer sec…
tim48-robot Feb 12, 2026
1062fdf
fix: timezone-aware datetimes, deprecated utcnow(), dead code, and do…
tim48-robot Feb 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions MAINTAINER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Maintainer Guide

This document explains how to manage the environment variables and how to re-enable features that are currently disabled (commented out) on the `feature/saas-ready` branch.

## Environment Variables

### Core Variables (Required for Launch)
These are already in your `.env.example`:
- `DISCORD_BOT_TOKEN`: The bot token from Discord Developer Portal.
- `DISCORD_BOT_CLIENT_ID`: The client ID of your Discord bot.
- `GITHUB_CLIENT_ID`: OAuth client ID from your GitHub App.
- `GITHUB_CLIENT_SECRET`: OAuth client secret from your GitHub App.
- `OAUTH_BASE_URL`: The public URL where the bot is hosted (e.g., `https://your-bot.cloudfunctions.net`).
- `GITHUB_APP_ID`: Your GitHub App ID.
- `GITHUB_APP_PRIVATE_KEY_B64`: Your GitHub App's private key, encoded in Base64.
- `GITHUB_APP_SLUG`: The URL-friendly name of your GitHub App.

### Security Variables (Recommended for Production)
- `SECRET_KEY`: Used by Flask to sign session cookies.
- **Usage**: Encrypting the `discord_user_id` during the `/link` flow.
- **Manual Check**: If you change this key while a user is mid-authentication, their session will be invalidated, and they will see "Authentication failed: No Discord user session".
- **Generation**: `python3 -c "import secrets; print(secrets.token_hex(32))"`

### Feature-Specific Variables (Optional/Disabled)
- `GITHUB_WEBHOOK_SECRET`: Required ONLY for PR automation. Used to verify that webhooks are actually coming from GitHub.
- `GITHUB_TOKEN`: Original personal access token (largely replaced by GitHub App identity).
- `REPO_OWNER` / `REPO_NAME`: Used for triggering the initial sync pipeline. Defaults to `ruxailab/disgitbot`.

---

## Re-enabling PR Automation

PR automation is currently commented out to simplify the SaaS experience. To re-enable it:

### 1. Uncomment Command Registration
In `discord_bot/src/bot/commands/admin_commands.py`:
```python
# In register_commands():
self.bot.tree.add_command(self._add_reviewer_command())
self.bot.tree.add_command(self._remove_reviewer_command())
```

In `discord_bot/src/bot/commands/notification_commands.py`:
```python
# In register_commands():
# self.bot.tree.add_command(self._webhook_status_command())
```

### 2. Configure GitHub App Webhooks
1. Go to your GitHub App settings.
2. Enable **Webhooks**.
3. **Webhook URL**: `{OAUTH_BASE_URL}/github-webhook`
4. **Webhook Secret**: Set a random string and update `GITHUB_WEBHOOK_SECRET` in your `.env`.
5. **Permissions & Events**:
- Push: `read & write` (checks)
- Pull Requests: `read & write`
- Repository metadata: `read-only`
- Subscribe to: `Pull request`, `Push`, `Workflow run`.

### 3. Performance & Responsiveness
- **Async I/O**: Use `await asyncio.to_thread` for all Firestore and synchronous network calls.
- **CPU-Bound Tasks**: Avoid long-running computations (like image generation) in the main thread. Wrap them in `asyncio.to_thread` to keep the bot responsive.
- **Shared Object Model**: Use the `shared.bot_instance` pattern for cross-thread communication between Flask and Discord.

### 4. Async Architecture Pattern
Always use this pattern for blocking calls:

```python
# Offload to thread to keep event loop free
result = await asyncio.to_thread(get_document, 'collection', 'doc_id', discord_server_id)

# Offload CPU-bound calculations
buffer = await asyncio.to_thread(generate_complex_chart, data)
```
50 changes: 6 additions & 44 deletions discord_bot/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,24 +124,21 @@ cp discord_bot/config/.env.example discord_bot/config/.env

**Your `.env` file needs these values:**
- `DISCORD_BOT_TOKEN=` (Discord bot authentication)
- `GITHUB_TOKEN=` (Github API access)
- `GITHUB_CLIENT_ID=` (GitHub OAuth app ID)
- `GITHUB_CLIENT_SECRET=` (GitHub OAuth app secret)
- `OAUTH_BASE_URL=` (Your Cloud Run URL - set in Step 3)
- `DISCORD_BOT_CLIENT_ID=` (Discord application ID)
- `GITHUB_APP_ID=` (GitHub App ID)
- `GITHUB_APP_PRIVATE_KEY_B64=` (GitHub App private key, base64)
- `GITHUB_APP_SLUG=` (GitHub App slug)
- `OAUTH_BASE_URL=` (Your Cloud Run URL - set in Step 4)
- `REPO_OWNER=` (Owner of the Disgitbot repo that hosts the workflow dispatch. Ex: ruxailab)

**Additional files you need:**
- `discord_bot/config/credentials.json` (Firebase/Google Cloud credentials)

**GitHub repository secrets you need to configure:**
Go to your GitHub repository → Settings → Secrets and variables → Actions → Click "New repository secret" for each:
- `DISCORD_BOT_TOKEN`
- `GH_TOKEN`
- `GOOGLE_CREDENTIALS_JSON`
- `REPO_OWNER`
- `CLOUD_RUN_URL`
- `GH_APP_ID`
- `GH_APP_PRIVATE_KEY_B64`
Expand All @@ -150,7 +147,6 @@ If you plan to run GitHub Actions from branches other than `main`, also add the
- `DEV_GOOGLE_CREDENTIALS_JSON`
- `DEV_CLOUD_RUN_URL`

> The workflows only reference `GH_TOKEN`, so you can reuse the same PAT for all branches.

---

Expand Down Expand Up @@ -250,25 +246,7 @@ If you plan to run GitHub Actions from branches other than `main`, also add the
- **Add to GitHub Secrets:** Create secret named `GOOGLE_CREDENTIALS_JSON` with the base64 string
- *(Do this for non-main branches)* Create another secret named `DEV_GOOGLE_CREDENTIALS_JSON` with the same base64 string so development branches can run GitHub Actions.

### Step 3: Get GITHUB_TOKEN (.env) + GH_TOKEN (GitHub Secret)

**What this configures:**
- `.env` file: `GITHUB_TOKEN=your_token_here`
- GitHub Secret: `GH_TOKEN`

**What this does:** Allows the bot to access dispatch the Github Actions Workflow

1. **Go to GitHub Token Settings:** https://github.com/settings/tokens
2. **Create New Token:**
- Click "Generate new token" → "Generate new token (classic)"
3. **Set Permissions:**
- Check only: [x] `repo` (this gives full repository access)
4. **Generate and Save:**
- Click "Generate token" → Copy the token
- **Add to `.env`:** `GITHUB_TOKEN=your_token_here`
- **Add to GitHub Secrets:** Create secret named `GH_TOKEN`

### Step 4: Get Cloud Run URL (Placeholder Deployment)
### Step 3: Get Cloud Run URL (Placeholder Deployment)

**What this configures:**
- `.env` file: `OAUTH_BASE_URL=YOUR_CLOUD_RUN_URL`
Expand Down Expand Up @@ -305,7 +283,7 @@ If you plan to run GitHub Actions from branches other than `main`, also add the
- **Example:** `https://discord-bot-abcd1234-uc.a.run.app/setup`
- Click **Save Changes**

### Step 5: Get GITHUB_CLIENT_ID (.env) + GITHUB_CLIENT_SECRET (.env)
### Step 4: Get GITHUB_CLIENT_ID (.env) + GITHUB_CLIENT_SECRET (.env)

**What this configures:**
- `.env` file: `GITHUB_CLIENT_ID=your_client_id`
Expand All @@ -318,7 +296,7 @@ If you plan to run GitHub Actions from branches other than `main`, also add the
- Click "New OAuth App"
3. **Fill in Application Details:**
- **Application name:** `Your Bot Name` (anything you want)
- **Homepage URL:** `YOUR_CLOUD_RUN_URL` (from Step 4)
- **Homepage URL:** `YOUR_CLOUD_RUN_URL` (from Step 3)
- **Authorization callback URL:** `YOUR_CLOUD_RUN_URL/login/github/authorized`

**Example URLs:** If your Cloud Run URL is `https://discord-bot-abcd1234-uc.a.run.app`, then:
Expand All @@ -331,7 +309,7 @@ If you plan to run GitHub Actions from branches other than `main`, also add the
- Copy the "Client ID" → **Add to `.env`:** `GITHUB_CLIENT_ID=your_client_id`
- Click "Generate a new client secret" → Copy it → **Add to `.env`:** `GITHUB_CLIENT_SECRET=your_secret`

### Step 5b: Create GitHub App (GITHUB_APP_ID / PRIVATE_KEY / SLUG)
### Step 5: Create GitHub App (GITHUB_APP_ID / PRIVATE_KEY / SLUG)

**What this configures:**
- `.env` file: `GITHUB_APP_ID=...`, `GITHUB_APP_PRIVATE_KEY_B64=...`, `GITHUB_APP_SLUG=...`
Expand Down Expand Up @@ -368,21 +346,6 @@ If you plan to run GitHub Actions from branches other than `main`, also add the

**Security note:** Never commit the private key or base64 value to git. Treat it like a password.

### Step 6: Get REPO_OWNER (.env) + REPO_OWNER (GitHub Secret)

**What this configures:**
- `.env` file: `REPO_OWNER=your_org_name`
- GitHub Secret: `REPO_OWNER`

**What this does:** Tells the bot which Disgitbot repo owns the GitHub Actions workflow (used for workflow dispatch). The org you track comes from GitHub App installation during `/setup`.

1. **Find the Disgitbot repo owner:**
- Example repo: `https://github.com/ruxailab/disgitbot`
- The owner is the first path segment (`ruxailab`)
2. **Set in Configuration:**
- **Add to `.env`:** `REPO_OWNER=your_repo_owner` (example: `REPO_OWNER=ruxailab`)
- **Add to GitHub Secrets:** Create secret named `REPO_OWNER` with the same value
- **Important:** Use ONLY the organization name, NOT the full URL

---

Expand Down Expand Up @@ -770,7 +733,6 @@ async def link(interaction: discord.Interaction):
# Check required environment variables
required_vars = [
"DISCORD_BOT_TOKEN",
"GITHUB_TOKEN",
"GITHUB_CLIENT_ID",
"GITHUB_CLIENT_SECRET",
"OAUTH_BASE_URL" # ← This is your Cloud Run URL
Expand Down
3 changes: 1 addition & 2 deletions discord_bot/config/.env.example
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
DISCORD_BOT_TOKEN=
GITHUB_TOKEN=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
REPO_OWNER=
OAUTH_BASE_URL=
DISCORD_BOT_CLIENT_ID=
GITHUB_APP_ID=
GITHUB_APP_PRIVATE_KEY_B64=
GITHUB_APP_SLUG=
SECRET_KEY=
11 changes: 9 additions & 2 deletions discord_bot/deployment/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,17 @@ RUN apt-get update && \
# Copy requirements first to leverage Docker cache
COPY requirements.txt .

# Copy only requirements files first to leverage Docker layer cache
COPY pr_review/requirements.txt ./pr_review/requirements.txt

# Upgrade pip to latest version to avoid upgrade notices
RUN pip install --upgrade pip
RUN pip install --no-cache-dir --upgrade pip

# Install dependencies from both requirements files
RUN pip install --no-cache-dir --root-user-action=ignore -r requirements.txt -r pr_review/requirements.txt

RUN pip install --no-cache-dir --root-user-action=ignore -r requirements.txt
# Copy pr_review package (copied into build context by deploy script)
COPY pr_review ./pr_review

# Create config directory and empty credentials file (will be overwritten by volume mount)
RUN mkdir -p /app/config && echo "{}" > /app/config/credentials.json
Expand Down
50 changes: 34 additions & 16 deletions discord_bot/deployment/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -333,18 +333,12 @@ create_new_env_file() {
print_warning "Discord Bot Token is required!"
done

# GitHub Token (optional for GitHub App mode)
read -p "GitHub Token (optional): " github_token

# GitHub Client ID
read -p "GitHub Client ID: " github_client_id

# GitHub Client Secret
read -p "GitHub Client Secret: " github_client_secret

# Repository Owner
read -p "Repository Owner: " repo_owner

# OAuth Base URL (optional - will auto-detect on Cloud Run)
read -p "OAuth Base URL (optional): " oauth_base_url

Expand All @@ -355,19 +349,26 @@ create_new_env_file() {
read -p "GitHub App ID: " github_app_id
read -p "GitHub App Private Key (base64): " github_app_private_key_b64
read -p "GitHub App Slug: " github_app_slug

# SECRET_KEY (auto-generate if left blank)
echo -e "${BLUE}SECRET_KEY is used to sign session cookies (required for security).${NC}"
read -rp "SECRET_KEY (leave blank to auto-generate): " secret_key
if [ -z "$secret_key" ]; then
secret_key=$(python3 -c "import secrets; print(secrets.token_hex(32))")
print_success "Auto-generated SECRET_KEY"
fi

# Create .env file
cat > "$ENV_PATH" << EOF
DISCORD_BOT_TOKEN=$discord_token
GITHUB_TOKEN=$github_token
GITHUB_CLIENT_ID=$github_client_id
GITHUB_CLIENT_SECRET=$github_client_secret
REPO_OWNER=$repo_owner
OAUTH_BASE_URL=$oauth_base_url
DISCORD_BOT_CLIENT_ID=$discord_bot_client_id
GITHUB_APP_ID=$github_app_id
GITHUB_APP_PRIVATE_KEY_B64=$github_app_private_key_b64
GITHUB_APP_SLUG=$github_app_slug
SECRET_KEY=$secret_key
EOF

print_success ".env file created successfully!"
Expand All @@ -383,18 +384,12 @@ edit_env_file() {
read -p "Discord Bot Token [$DISCORD_BOT_TOKEN]: " new_discord_token
discord_token=${new_discord_token:-$DISCORD_BOT_TOKEN}

read -p "GitHub Token [$GITHUB_TOKEN]: " new_github_token
github_token=${new_github_token:-$GITHUB_TOKEN}

read -p "GitHub Client ID [$GITHUB_CLIENT_ID]: " new_github_client_id
github_client_id=${new_github_client_id:-$GITHUB_CLIENT_ID}

read -p "GitHub Client Secret [$GITHUB_CLIENT_SECRET]: " new_github_client_secret
github_client_secret=${new_github_client_secret:-$GITHUB_CLIENT_SECRET}

read -p "Repository Owner [$REPO_OWNER]: " new_repo_owner
repo_owner=${new_repo_owner:-$REPO_OWNER}

read -p "OAuth Base URL [$OAUTH_BASE_URL]: " new_oauth_base_url
oauth_base_url=${new_oauth_base_url:-$OAUTH_BASE_URL}

Expand All @@ -409,19 +404,28 @@ edit_env_file() {

read -p "GitHub App Slug [$GITHUB_APP_SLUG]: " new_github_app_slug
github_app_slug=${new_github_app_slug:-$GITHUB_APP_SLUG}

read -rp "SECRET_KEY [$SECRET_KEY]: " new_secret_key
secret_key=${new_secret_key:-$SECRET_KEY}

# Auto-generate if still empty (e.g. key was missing in old .env and user pressed Enter)
if [ -z "$secret_key" ]; then
echo -e "${BLUE}SECRET_KEY is empty. Auto-generating a secure key...${NC}"
secret_key=$(python3 -c "import secrets; print(secrets.token_hex(32))")
print_success "Auto-generated SECRET_KEY"
fi

# Update .env file
cat > "$ENV_PATH" << EOF
DISCORD_BOT_TOKEN=$discord_token
GITHUB_TOKEN=$github_token
GITHUB_CLIENT_ID=$github_client_id
GITHUB_CLIENT_SECRET=$github_client_secret
REPO_OWNER=$repo_owner
OAUTH_BASE_URL=$oauth_base_url
DISCORD_BOT_CLIENT_ID=$discord_bot_client_id
GITHUB_APP_ID=$github_app_id
GITHUB_APP_PRIVATE_KEY_B64=$github_app_private_key_b64
GITHUB_APP_SLUG=$github_app_slug
SECRET_KEY=$secret_key
EOF

print_success ".env file updated successfully!"
Expand Down Expand Up @@ -727,6 +731,16 @@ main() {
print_warning "Shared directory not found - skipping shared copy"
fi

# Copy pr_review directory into build context for PR automation
print_step "Copying pr_review directory into build context..."
if [ -d "$(dirname "$ROOT_DIR")/pr_review" ]; then
rm -rf "$ROOT_DIR/pr_review"
cp -r "$(dirname "$ROOT_DIR")/pr_review" "$ROOT_DIR/pr_review"
print_success "pr_review directory copied successfully"
else
print_warning "pr_review directory not found - skipping pr_review copy"
fi

# Use Cloud Build to build and push the image
gcloud builds submit \
--tag gcr.io/$PROJECT_ID/$SERVICE_NAME:latest \
Expand All @@ -739,6 +753,10 @@ main() {
rm -rf "$ROOT_DIR/shared"
print_step "Cleaned up temporary shared directory"
fi
if [ -d "$ROOT_DIR/pr_review" ]; then
rm -rf "$ROOT_DIR/pr_review"
print_step "Cleaned up temporary pr_review directory"
fi
print_success "Build completed and temporary files cleaned up!"

# Clean up existing service configuration if exists
Expand Down
2 changes: 1 addition & 1 deletion discord_bot/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
discord.py[voice]==2.0.0
python-dotenv==1.0.0
firebase-admin==6.7.0
aiohttp==3.9.1
aiohttp>=3.12.14
audioop-lts
Flask==3.0.0
Flask-Dance==7.0.0
Expand Down
Loading
Loading