expense-approval-bot/README.md

71 lines
3.2 KiB
Markdown
Raw Normal View History

# Expense Approval Bot
Slack reaction-driven expense approval router. Replaces the Pipedream `expenses_pipeline` workflow.
## Flow
A user reacts ✅ to a message in an expense channel. The bot:
1. Maps the source channel to the next stage's channel.
2. Copies the message text (stripped of any prior "react to advance" hint) into the next channel. When advancing out of **Submitted**, the copy also gets a permalink back to the user's original message.
3. Depending on where we're advancing *from*:
- **From Submitted:** keeps the user's original message in place and posts a threaded `➡️ Advanced to {Stage}` reply. The Submitted channel is the permanent audit trail.
- **From Processed or Authorized:** deletes the old copy so each intermediate channel stays a clean "current work" queue.
Channel chain: Submitted → Processed → Authorized → Matched. The bot can only delete its own posts, which is why the Submitted original (a real user message) is preserved by design.
## Architecture
- **Receiver Lambda** (`src/receiver/`) — fronted by API Gateway HTTP API. Verifies the Slack request signature, handles the URL-verification handshake, and async-invokes the processor. Returns 200 within Slack's 3-second window to prevent retries.
- **Processor Lambda** (`src/processor/`) — does the 5-step sequence of Slack API calls. No inbound HTTP; invoked only by the receiver.
- **Secrets** — Slack bot token and signing secret each stored as separate AWS Secrets Manager secrets. Each Lambda only has IAM access to the secret it needs.
Runtime: Python 3.12 on arm64, no third-party dependencies (stdlib `urllib` for HTTP, `boto3` from the Lambda runtime).
## Setup
1. Store the two Slack credentials in Secrets Manager:
```bash
aws secretsmanager create-secret \
--name expense-bot-slack-token \
--secret-string "xoxb-..."
aws secretsmanager create-secret \
--name expense-bot-slack-signing-secret \
--secret-string "..."
```
2. Copy the sample SAM config and fill in real values:
```bash
cp samconfig.toml.example samconfig.toml
```
- `SlackBotTokenSecretArn` — ARN of the bot-token secret
- `SlackSigningSecretArn` — ARN of the signing-secret secret
3. Build and deploy:
```bash
sam build && sam deploy
```
4. After the first deploy, take the `SlackEventsUrl` output and paste it into the Slack app's **Event Subscriptions → Request URL**. Subscribe the bot to the `reaction_added` event.
## Configuration
Channel IDs live in `src/processor/app.py`:
- `SUBMITTED_CHANNEL` — the user-authored origin channel. Controls both the "include a permalink" behavior and the "preserve vs delete source" branch.
- `STAGES` — the full chain mapping each source channel to its `next` channel and human-readable `label`.
If the Submitted channel changes, update `SUBMITTED_CHANNEL` and the first key of `STAGES`. For any other stage change, update `STAGES` only.
## Manual testing
Invoke the processor directly with a synthetic Slack reaction event:
```bash
aws lambda invoke \
--function-name expense-approval-processor \
--payload '{"reaction":"white_check_mark","item":{"channel":"C0AQ2AWLNEN","ts":"1700000000.000000"}}' \
/dev/stdout
```