afterhours-shift-manager/SETUP.md
Adam Moussa 23bad9bee6
Some checks are pending
Deploy / deploy (push) Waiting to run
Deploy / release (push) Blocked by required conditions
feat(roster): add HTTP PUT/DELETE roster API (PLAT-180) (#247)
* feat(roster): add Bearer PUT/DELETE roster API

Identity hire needs to write Slack IDs onto roster rows without a stale
daily 3CX sync clearing them, using the existing HTTP client contract.

* fix(roster): strip Secrets Manager token whitespace

A file:// secret commonly includes a trailing newline, so compare_digest
must strip the cached value the same way it strips the Bearer header.
2026-09-09 21:13:27 +00:00

133 lines
5.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# After-Hours Shift Manager — Setup Guide
## 1. Create a Slack App
1. Go to [api.slack.com/apps](https://api.slack.com/apps) → **Create New App** → **From scratch**
2. Name: `After-Hours Shift Manager`, pick your workspace
3. Under **OAuth & Permissions**, add these **Bot Token Scopes**:
- `chat:write` — post messages to channels
- `commands` — register slash commands
- `chat:write.public` — post to channels the bot isn't in
4. Under **Slash Commands**, create a new command:
- Command: `/oncall`
- Request URL: `(fill in after deploy — see step 4)`
- Short Description: `Manage after-hours on-call shifts`
- Usage Hint: `[schedule | pick <date> | drop <date> | swap <date> @person | register <ext> | roster | help]`
5. Under **Interactivity & Shortcuts**, toggle **ON**:
- Request URL: same URL as the slash command (the `/slack/events` endpoint)
6. **Install to Workspace** — approve the permissions
7. Copy the **Bot User OAuth Token** (`xoxb-...`) and **Signing Secret** (under Basic Information)
## 2. Store Secrets in AWS Secrets Manager
The stack reads these from Secrets Manager (the IAM roles grant
`secretsmanager:GetSecretValue` on `afterhours-shift-manager/*`):
```bash
# Slack
aws secretsmanager create-secret \
--name afterhours-shift-manager/slack-bot-token \
--secret-string "xoxb-YOUR-BOT-TOKEN"
aws secretsmanager create-secret \
--name afterhours-shift-manager/slack-signing-secret \
--secret-string "YOUR-SIGNING-SECRET"
# 3CX Queue XAPI (used by roster-sync and ring-scheduler)
aws secretsmanager create-secret \
--name afterhours-shift-manager/3cx-domain \
--secret-string "yourcompany.3cx.us"
aws secretsmanager create-secret \
--name afterhours-shift-manager/3cx-client-id \
--secret-string "YOUR-3CX-CLIENT-ID"
aws secretsmanager create-secret \
--name afterhours-shift-manager/3cx-client-secret \
--secret-string "YOUR-3CX-CLIENT-SECRET"
# Roster HTTP API bearer token (plain string). Duplicate the same value into
# seahaven-prod as paychex-integrations/afterhours-roster-token. Generate the
# token into a temp file, pass --secret-string file://..., then delete the file.
# Never paste the value into chat, Terraform, or a PR.
aws secretsmanager create-secret \
--name afterhours-shift-manager/roster-api-token \
--secret-string file://./roster-api-token.tmp
```
Create `afterhours-shift-manager/roster-api-token` **before** the first deploy that
includes `afterhours-roster-api`, or live PUT/DELETE calls return 503.
Rotation is coordinated: write the new value to both the mgmt secret and the
prod copy, then recycle `afterhours-roster-api` so cached execution environments
pick it up. Updating only one copy causes 401s. The identity processor
`AFTERHOURS_BASE_URL` is the stack output `AfterhoursApiBaseUrl` (origin only,
no `/mgmt` or `/roster` suffix). Leave that URL empty until the API is live
and smoke-tested.
Daily roster-sync still removes DynamoDB rows that are not in the 3CX `DEFAULT`
group. Hire stays safe because 3CX create lands the extension in that group
before the identity processor PUTs `/roster`.
> The Slack **channel ID** is not a secret — it's passed as the `ShiftChannel`
> deploy parameter in step 3, not stored in Secrets Manager or SSM.
## 3. Deploy the Stack
```bash
# Build and deploy. ShiftChannel is the Slack channel ID for schedule posts
# (right-click the channel in Slack → Copy link → the ID is the last segment).
sam build
sam deploy --guided \
--stack-name afterhours-shift-manager \
--region us-east-1 \
--parameter-overrides ShiftChannel=C0XXXXXXX QueueNumber=801
# Note SlackBotApiUrl (Slack Request URL) and AfterhoursApiBaseUrl
# (paychex AFTERHOURS_BASE_URL origin).
```
## 4. Set the Slack Request URL
After deploy, copy the `SlackBotApiUrl` from the SAM output. Go back to your Slack app settings:
- **Slash Commands** → edit `/oncall` → set **Request URL** to the output URL
- **Interactivity & Shortcuts** → set **Request URL** to the same URL
## 5. Seed the Schedule
```bash
python scripts/seed_schedule.py
```
This populates the DynamoDB table with the employee roster and default weekly schedule.
## 6. Invite the Bot & Register Users
1. Create a channel (e.g. `#after-hours-shifts`) and invite the bot: `/invite @After-Hours Shift Manager`
2. Each employee links their Slack account by running:
```
/oncall register 114
```
(using their own extension number)
## 7. Update the 3CX Scheduler (optional)
To have the 3CX scheduler read overrides from DynamoDB (so Slack-driven changes apply to future dates automatically), deploy the updated 3CX scheduler from the `feature/dynamodb-shift-integration` branch. See that branch's changes for details.
Without this step, the Slack bot still works — it invokes the 3CX scheduler Lambda directly for same-day changes. Future-date overrides would only take effect if the scheduler reads DynamoDB.
## Commands Reference
| Command | Description |
|---|---|
| `/oncall` | Show this week's schedule |
| `/oncall next` | Show next week's schedule |
| `/oncall pick <date>` | Pick up a shift |
| `/oncall drop <date>` | Drop your shift (marks it open) |
| `/oncall swap <date> @person` | Hand your shift to someone else |
| `/oncall register <ext>` | Link your Slack to your extension |
| `/oncall roster` | Show all employees and their link status |
| `/oncall help` | Show help |
Dates can be: `today`, `tomorrow`, `monday`–`sunday`, `4/5`, `2026-04-05`