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,90 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user