Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
91 lines
2.6 KiB
Markdown
91 lines
2.6 KiB
Markdown
# 13 — Support ticket ratings / CSAT
|
||
|
||
**Owner:** agent 13
|
||
**Depends on:** agent 12 schema (`030_support_desk.sql` → `support_csat_ratings`)
|
||
**Status:** implemented (API + tests)
|
||
|
||
## Goal
|
||
|
||
Customers rate **resolved** or **closed** tickets once (score 1–5 + optional comment). Platform admins see aggregate CSAT (no PII).
|
||
|
||
## Schema (agent 12)
|
||
|
||
Table `support_csat_ratings`:
|
||
|
||
| Column | Notes |
|
||
|--------|--------|
|
||
| `ticket_id` | `UNIQUE` — one rating per ticket |
|
||
| `company_id`, `user_id` | owner + tenant |
|
||
| `score` | `SMALLINT` 1–5 |
|
||
| `comment` | optional, max 2000 runes (truncated) |
|
||
| `created_at` | UTC |
|
||
|
||
Ticket columns `csat_token_hash` / `csat_invite_sent_at` exist for optional public token flow (not wired in this agent).
|
||
|
||
## Rules
|
||
|
||
1. Only `created_by_user_id` may rate (others → `404`).
|
||
2. Status must be `resolved` or `closed` else `400` (`ticket not eligible for rating`).
|
||
3. Duplicate insert → `409` (`already rated`).
|
||
4. Logs: `ticket_id` + `score` only — **never** comment, email, or names.
|
||
|
||
## APIs
|
||
|
||
| Method | Path | Auth | Response |
|
||
|--------|------|------|----------|
|
||
| `POST` | `/api/support/tickets/{id}/csat` | session + company | `201` `SupportCsat` |
|
||
| `GET` | `/api/support/tickets/{id}` | customer | additive `csat` / `csat_eligible` |
|
||
| `GET` | `/api/admin/support/tickets/{id}` | support desk | includes `csat` when present |
|
||
| `GET` | `/api/admin/support/csat?from=&to=` | platform admin | aggregate |
|
||
|
||
### Customer submit body
|
||
|
||
```json
|
||
{ "score": 4, "comment": "optional" }
|
||
```
|
||
|
||
### Admin aggregate
|
||
|
||
```json
|
||
{
|
||
"total": 12,
|
||
"average": 4.25,
|
||
"distribution": { "1": 0, "2": 1, "3": 2, "4": 4, "5": 5 },
|
||
"from": null,
|
||
"to": null
|
||
}
|
||
```
|
||
|
||
`from` / `to` are optional RFC3339 bounds on `created_at` (`to` exclusive).
|
||
|
||
## Code map
|
||
|
||
| Piece | Path |
|
||
|-------|------|
|
||
| Service | `apps/api/internal/support/ratings.go` |
|
||
| Types | `CSATInput`, `CSATRating`, `CSATAggregate` in `types.go` |
|
||
| Handlers | `apps/api/internal/httpapi/support_csat_handlers.go` |
|
||
| Routes | customer POST csat; admin GET `/api/admin/support/csat` |
|
||
| Unit tests | `ratings_test.go`, `support_csat_auth_test.go` |
|
||
| Integration | `ratings_integration_test.go` (needs `DATABASE_URL` + goose) |
|
||
|
||
## Verification
|
||
|
||
```bash
|
||
cd apps/api
|
||
go test ./internal/support/ -count=1 -run 'CSAT|NormalizeCSAT|ClientErrorCSAT'
|
||
go test ./internal/httpapi/ -count=1 -run 'SupportCSAT|SupportTicketCRUDAuth'
|
||
```
|
||
|
||
With DB migrated:
|
||
|
||
```bash
|
||
go test ./internal/support/ -count=1 -run TestSubmitCSATOwnershipAndOnce
|
||
```
|
||
|
||
## Out of scope (this agent)
|
||
|
||
- Public token CSAT (`POST /api/public/support/csat`)
|
||
- User/staff UI panels (agents 14–15)
|
||
- Email CSAT invite stubs
|