Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# 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`).
|
||||
Reference in New Issue
Block a user