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

3.5 KiB
Raw Permalink Blame History

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 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_roleadmin|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=all403. Cross-assignee GET → 404. Reply on another agents 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).