89 lines
3.5 KiB
Markdown
89 lines
3.5 KiB
Markdown
# 12 — Support tickets backend
|
||||
|
|
|
|||
|
|
**Status:** Implemented (extends Support Center)
|
|||
|
|
**Agent:** 12/20
|
|||
|
|
**Date:** 2026-08-05
|
|||
|
|
**Design:** [11-support-design.md](./11-support-design.md) · [11-support-api-contract.json](./11-support-api-contract.json)
|
|||
|
|
|
|||
|
|
## Summary
|
|||
|
|
|
|||
|
|
Additive support-desk backend on top of `025_support_center` + `029_staff_roles`:
|
|||
|
|
|
|||
|
|
- Customers: create / list / reply with **company + owner isolation** (unchanged contract).
|
|||
|
|
- Staff desk (`RequireSupportDesk`): queue + claim visibility for `staff_role=support_staff`; full list for platform admins.
|
|||
|
|
- Claim / release endpoints; assign force-fields remain admin-oriented.
|
|||
|
|
- Agents directory via `staff_role` (maps API `is_support_agent` without a second boolean column).
|
|||
|
|
- Migration `030_support_desk.sql`: `resolved_by_user_id`, CSAT table (ratings owned by agent 13), unassigned index, notification kinds.
|
|||
|
|
|
|||
|
|
## Schema
|
|||
|
|
|
|||
|
|
| Artifact | Purpose |
|
|||
|
|
|----------|---------|
|
|||
|
|
| `029_staff_roles.sql` | `users.staff_role` ∈ `admin\|developer\|support_staff` |
|
|||
|
|
| `027_capabilities_support_perf.sql` | assignee / status activity indexes |
|
|||
|
|
| `030_support_desk.sql` | `resolved_by_user_id`, CSAT table + token cols, unassigned partial index, `ticket_claimed` / `csat_requested` notification kinds |
|
|||
|
|
|
|||
|
|
**ASSUMPTION:** Design’s `is_support_agent` boolean is implemented as `staff_role='support_staff'` (roles agents landed first). API still exposes `is_support_agent` on agent DTOs / `/me` staff block.
|
|||
|
|
|
|||
|
|
## Auth
|
|||
|
|
|
|||
|
|
| Middleware | Who |
|
|||
|
|
|------------|-----|
|
|||
|
|
| `RequireSession` + `RequireCompany` | Customer ticket + notification routes |
|
|||
|
|
| `RequireSupportDesk` | `/api/admin/support/tickets*` claim/release |
|
|||
|
|
| `RequirePlatformAdmin` | `/api/admin/support/agents*` (+ other admin) |
|
|||
|
|
|
|||
|
|
`auth.ResolveStaffAccess`: `support_staff` → desk only; `admin`/`developer`/legacy `is_platform_admin` → full admin + desk.
|
|||
|
|
|
|||
|
|
## Visibility (queue + claim)
|
|||
|
|
|
|||
|
|
| Scope | Filter |
|
|||
|
|
|-------|--------|
|
|||
|
|
| `inbox` (agent default) | assignee IS NULL OR me; status ∈ open\|pending |
|
|||
|
|
| `mine` | assignee = me |
|
|||
|
|
| `unassigned` | assignee IS NULL; open\|pending |
|
|||
|
|
| `all` (admin default) | optional status / company_id / assignee_id / q |
|
|||
|
|
|
|||
|
|
Agent `scope=all` → **403**. Cross-assignee GET → **404**. Reply on another agent’s ticket → **409 already_claimed**.
|
|||
|
|
|
|||
|
|
Claim SQL (atomic):
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
UPDATE support_tickets
|
|||
|
|
SET assignee_admin_user_id = $actor, updated_at = now()
|
|||
|
|
WHERE id = $id AND assignee_admin_user_id IS NULL AND status IN ('open','pending');
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Key packages
|
|||
|
|
|
|||
|
|
| Path | Role |
|
|||
|
|
|------|------|
|
|||
|
|
| `internal/support/tickets.go` | CRUD, reply, update (+ `resolved_by_user_id` on resolve) |
|
|||
|
|
| `internal/support/desk.go` | scopes, claim, release, `GetAdminForActor` |
|
|||
|
|
| `internal/support/agents.go` | list/set support agents via `staff_role` |
|
|||
|
|
| `internal/httpapi/support_handlers.go` | HTTP + staff visibility helpers |
|
|||
|
|
| `internal/httpapi/admin_staff_handlers.go` | set agent |
|
|||
|
|
| `internal/auth/staff.go` | StaffAccess resolution |
|
|||
|
|
|
|||
|
|
## Tests
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
cd apps/api
|
|||
|
|
go test ./internal/support/ -count=1
|
|||
|
|
# with DATABASE_URL:
|
|||
|
|
go test ./internal/support/ -run 'TicketCRUDAuthOwnership|StaffQueueClaimRelease' -count=1
|
|||
|
|
go test ./internal/httpapi/ -run Support -count=1
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Out of scope here
|
|||
|
|
|
|||
|
|
- CSAT submit/aggregate HTTP (agent 13 — schema prepared in `029`).
|
|||
|
|
- Staff/customer UI (later agents).
|
|||
|
|
- Support email stubs (optional; mailer unused by support MVP).
|
|||
|
|
|
|||
|
|
## Verification notes
|
|||
|
|
|
|||
|
|
- Customer list/get still require `company_id` + `created_by_user_id`.
|
|||
|
|
- Internal notes never returned on customer GET.
|
|||
|
|
- Pagination clamped 50 default / 200 max (`clampListBounds`).
|