Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
3.5 KiB
3.5 KiB
12 — Support tickets backend
Status: Implemented (extends Support Center)
Agent: 12/20
Date: 2026-08-05
Design: 11-support-design.md · 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 forstaff_role=support_staff; full list for platform admins. - Claim / release endpoints; assign force-fields remain admin-oriented.
- Agents directory via
staff_role(maps APIis_support_agentwithout 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):
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
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).