Files
greeneclipse 8580c996c3 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.
2026-08-09 22:47:43 +02:00

89 lines
3.5 KiB
Markdown
Raw Permalink 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.
# 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:** Designs `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 agents 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`).