From de6a05e0e694f7852380440b7976370dc046bf8c Mon Sep 17 00:00:00 2001 From: Rogee Date: Thu, 4 Jun 2026 21:07:48 +0800 Subject: [PATCH] feat(routes): expand chatwoot parity coverage --- cmd/route_parity/main.go | 120 +++++++++++++++- docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md | 50 ++++--- docs/parity/gochat_routes.txt | 70 +++++++++- docs/parity/route_parity.md | 117 +++++++++++++++- .../handler/api/v1/csat_survey_handler.go | 6 + internal/router/router.go | 129 +++++++++++++++++- internal/router/router_test.go | 48 +++++++ 7 files changed, 512 insertions(+), 28 deletions(-) create mode 100644 internal/router/router_test.go diff --git a/cmd/route_parity/main.go b/cmd/route_parity/main.go index 059a7a32..be60796f 100644 --- a/cmd/route_parity/main.go +++ b/cmd/route_parity/main.go @@ -177,6 +177,124 @@ var criticalRoutes = []route{ {Method: "GET", Path: "/api/v1/accounts/:account_id/agent_capacity_policies/", Controller: "api/v1/accounts/agent_capacity_policies#index", Source: "routes.rb:123"}, {Method: "GET", Path: "/api/v1/accounts/:account_id/csat_survey_responses/", Controller: "api/v1/accounts/csat_survey_responses#index", Source: "routes.rb:219"}, {Method: "GET", Path: "/api/v1/accounts/:account_id/csat_survey_responses/metrics", Controller: "api/v1/accounts/csat_survey_responses#metrics", Source: "routes.rb:221"}, + + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/preferences/", Controller: "api/v1/accounts/captain/preferences#show", Source: "routes.rb:63"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/captain/preferences/", Controller: "api/v1/accounts/captain/preferences#update", Source: "routes.rb:63"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/assistants/", Controller: "api/v1/accounts/captain/assistants#index", Source: "routes.rb:64"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/assistants/", Controller: "api/v1/accounts/captain/assistants#create", Source: "routes.rb:64"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id", Controller: "api/v1/accounts/captain/assistants#show", Source: "routes.rb:64"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id", Controller: "api/v1/accounts/captain/assistants#update", Source: "routes.rb:64"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id", Controller: "api/v1/accounts/captain/assistants#destroy", Source: "routes.rb:64"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id/playground", Controller: "api/v1/accounts/captain/assistants#playground", Source: "routes.rb:66"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/assistants/tools", Controller: "api/v1/accounts/captain/assistants#tools", Source: "routes.rb:69"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id/inboxes", Controller: "api/v1/accounts/captain/assistants/inboxes#index", Source: "routes.rb:71"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id/inboxes", Controller: "api/v1/accounts/captain/assistants/inboxes#create", Source: "routes.rb:71"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id/inboxes/:inbox_id", Controller: "api/v1/accounts/captain/assistants/inboxes#destroy", Source: "routes.rb:71"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/", Controller: "api/v1/accounts/captain/assistants/scenarios#index", Source: "routes.rb:72"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/", Controller: "api/v1/accounts/captain/assistants/scenarios#create", Source: "routes.rb:72"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/:scenario_id", Controller: "api/v1/accounts/captain/assistants/scenarios#show", Source: "routes.rb:72"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/:scenario_id", Controller: "api/v1/accounts/captain/assistants/scenarios#update", Source: "routes.rb:72"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/:scenario_id", Controller: "api/v1/accounts/captain/assistants/scenarios#destroy", Source: "routes.rb:72"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/assistant_responses/", Controller: "api/v1/accounts/captain/assistant_responses#index", Source: "routes.rb:74"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/assistant_responses/", Controller: "api/v1/accounts/captain/assistant_responses#create", Source: "routes.rb:74"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/assistant_responses/:response_id", Controller: "api/v1/accounts/captain/assistant_responses#show", Source: "routes.rb:74"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/captain/assistant_responses/:response_id", Controller: "api/v1/accounts/captain/assistant_responses#update", Source: "routes.rb:74"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/captain/assistant_responses/:response_id", Controller: "api/v1/accounts/captain/assistant_responses#destroy", Source: "routes.rb:74"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/bulk_actions/", Controller: "api/v1/accounts/captain/bulk_actions#create", Source: "routes.rb:75"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/copilot_threads/", Controller: "api/v1/accounts/captain/copilot_threads#index", Source: "routes.rb:76"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/copilot_threads/", Controller: "api/v1/accounts/captain/copilot_threads#create", Source: "routes.rb:76"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/copilot_threads/:thread_id/copilot_messages/", Controller: "api/v1/accounts/captain/copilot_messages#index", Source: "routes.rb:77"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/copilot_threads/:thread_id/copilot_messages/", Controller: "api/v1/accounts/captain/copilot_messages#create", Source: "routes.rb:77"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/custom_tools/", Controller: "api/v1/accounts/captain/custom_tools#index", Source: "routes.rb:79"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/custom_tools/", Controller: "api/v1/accounts/captain/custom_tools#create", Source: "routes.rb:79"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/custom_tools/:tool_id", Controller: "api/v1/accounts/captain/custom_tools#show", Source: "routes.rb:79"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/captain/custom_tools/:tool_id", Controller: "api/v1/accounts/captain/custom_tools#update", Source: "routes.rb:79"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/captain/custom_tools/:tool_id", Controller: "api/v1/accounts/captain/custom_tools#destroy", Source: "routes.rb:79"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/custom_tools/test", Controller: "api/v1/accounts/captain/custom_tools#test", Source: "routes.rb:80"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/documents/", Controller: "api/v1/accounts/captain/documents#index", Source: "routes.rb:82"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/documents/", Controller: "api/v1/accounts/captain/documents#create", Source: "routes.rb:82"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/captain/documents/:document_id", Controller: "api/v1/accounts/captain/documents#show", Source: "routes.rb:82"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/captain/documents/:document_id", Controller: "api/v1/accounts/captain/documents#destroy", Source: "routes.rb:82"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/documents/:document_id/sync", Controller: "api/v1/accounts/captain/documents#sync", Source: "routes.rb:83"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/tasks/rewrite", Controller: "api/v1/accounts/captain/tasks#rewrite", Source: "routes.rb:86"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/tasks/summarize", Controller: "api/v1/accounts/captain/tasks#summarize", Source: "routes.rb:87"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/tasks/reply_suggestion", Controller: "api/v1/accounts/captain/tasks#reply_suggestion", Source: "routes.rb:88"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/tasks/label_suggestion", Controller: "api/v1/accounts/captain/tasks#label_suggestion", Source: "routes.rb:89"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/captain/tasks/follow_up", Controller: "api/v1/accounts/captain/tasks#follow_up", Source: "routes.rb:90"}, + + {Method: "GET", Path: "/api/v1/accounts/:account_id/assignment_policies/", Controller: "api/v1/accounts/assignment_policies#index", Source: "routes.rb:306"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/assignment_policies/", Controller: "api/v1/accounts/assignment_policies#create", Source: "routes.rb:306"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/assignment_policies/:policy_id", Controller: "api/v1/accounts/assignment_policies#show", Source: "routes.rb:306"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/assignment_policies/:policy_id", Controller: "api/v1/accounts/assignment_policies#update", Source: "routes.rb:306"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/assignment_policies/:policy_id", Controller: "api/v1/accounts/assignment_policies#destroy", Source: "routes.rb:306"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/assignment_policies/:policy_id/inboxes", Controller: "api/v1/accounts/assignment_policies/inboxes#index", Source: "routes.rb:307"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/assignment_policies/:policy_id/inboxes", Controller: "api/v1/accounts/assignment_policies/inboxes#create", Source: "routes.rb:307"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/assignment_policies/:policy_id/inboxes/:inbox_id", Controller: "api/v1/accounts/assignment_policies/inboxes#destroy", Source: "routes.rb:307"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/", Controller: "api/v1/accounts/inboxes/assignment_policy#show", Source: "routes.rb:311"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/", Controller: "api/v1/accounts/inboxes/assignment_policy#create", Source: "routes.rb:311"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/", Controller: "api/v1/accounts/inboxes/assignment_policy#destroy", Source: "routes.rb:311"}, + + {Method: "POST", Path: "/api/v1/widget/direct_uploads", Controller: "api/v1/widget/direct_uploads#create", Source: "routes.rb:443"}, + {Method: "POST", Path: "/api/v1/widget/config", Controller: "api/v1/widget/config#create", Source: "routes.rb:444"}, + {Method: "GET", Path: "/api/v1/widget/campaigns", Controller: "api/v1/widget/campaigns#index", Source: "routes.rb:445"}, + {Method: "POST", Path: "/api/v1/widget/events", Controller: "api/v1/widget/events#create", Source: "routes.rb:446"}, + {Method: "GET", Path: "/api/v1/widget/messages", Controller: "api/v1/widget/messages#index", Source: "routes.rb:447"}, + {Method: "POST", Path: "/api/v1/widget/messages", Controller: "api/v1/widget/messages#create", Source: "routes.rb:447"}, + {Method: "PUT", Path: "/api/v1/widget/messages/:message_id", Controller: "api/v1/widget/messages#update", Source: "routes.rb:447"}, + {Method: "GET", Path: "/api/v1/widget/conversations", Controller: "api/v1/widget/conversations#index", Source: "routes.rb:448"}, + {Method: "POST", Path: "/api/v1/widget/conversations", Controller: "api/v1/widget/conversations#create", Source: "routes.rb:448"}, + {Method: "POST", Path: "/api/v1/widget/conversations/destroy_custom_attributes", Controller: "api/v1/widget/conversations#destroy_custom_attributes", Source: "routes.rb:450"}, + {Method: "POST", Path: "/api/v1/widget/conversations/set_custom_attributes", Controller: "api/v1/widget/conversations#set_custom_attributes", Source: "routes.rb:451"}, + {Method: "POST", Path: "/api/v1/widget/conversations/update_last_seen", Controller: "api/v1/widget/conversations#update_last_seen", Source: "routes.rb:452"}, + {Method: "POST", Path: "/api/v1/widget/conversations/toggle_typing", Controller: "api/v1/widget/conversations#toggle_typing", Source: "routes.rb:453"}, + {Method: "POST", Path: "/api/v1/widget/conversations/transcript", Controller: "api/v1/widget/conversations#transcript", Source: "routes.rb:454"}, + {Method: "GET", Path: "/api/v1/widget/conversations/toggle_status", Controller: "api/v1/widget/conversations#toggle_status", Source: "routes.rb:455"}, + {Method: "GET", Path: "/api/v1/widget/contact", Controller: "api/v1/widget/contact#show", Source: "routes.rb:458"}, + {Method: "PUT", Path: "/api/v1/widget/contact", Controller: "api/v1/widget/contact#update", Source: "routes.rb:458"}, + {Method: "POST", Path: "/api/v1/widget/contact/destroy_custom_attributes", Controller: "api/v1/widget/contact#destroy_custom_attributes", Source: "routes.rb:460"}, + {Method: "PATCH", Path: "/api/v1/widget/contact/set_user", Controller: "api/v1/widget/contact#set_user", Source: "routes.rb:461"}, + {Method: "GET", Path: "/api/v1/widget/inbox_members", Controller: "api/v1/widget/inbox_members#index", Source: "routes.rb:463"}, + {Method: "POST", Path: "/api/v1/widget/labels", Controller: "api/v1/widget/labels#create", Source: "routes.rb:464"}, + {Method: "DELETE", Path: "/api/v1/widget/labels/:label_id", Controller: "api/v1/widget/labels#destroy", Source: "routes.rb:464"}, + {Method: "POST", Path: "/api/v1/widget/integrations/dyte/add_participant_to_meeting", Controller: "api/v1/widget/integrations/dyte#add_participant_to_meeting", Source: "routes.rb:469"}, + + {Method: "POST", Path: "/public/api/v1/inboxes/:inbox_id/contacts", Controller: "public/api/v1/inboxes/contacts#create", Source: "routes.rb:572"}, + {Method: "GET", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id", Controller: "public/api/v1/inboxes/contacts#show", Source: "routes.rb:572"}, + {Method: "PUT", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id", Controller: "public/api/v1/inboxes/contacts#update", Source: "routes.rb:572"}, + {Method: "GET", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations", Controller: "public/api/v1/inboxes/conversations#index", Source: "routes.rb:573"}, + {Method: "POST", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations", Controller: "public/api/v1/inboxes/conversations#create", Source: "routes.rb:573"}, + {Method: "GET", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id", Controller: "public/api/v1/inboxes/conversations#show", Source: "routes.rb:573"}, + {Method: "POST", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/toggle_status", Controller: "public/api/v1/inboxes/conversations#toggle_status", Source: "routes.rb:575"}, + {Method: "POST", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/toggle_typing", Controller: "public/api/v1/inboxes/conversations#toggle_typing", Source: "routes.rb:576"}, + {Method: "POST", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/update_last_seen", Controller: "public/api/v1/inboxes/conversations#update_last_seen", Source: "routes.rb:577"}, + {Method: "GET", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages", Controller: "public/api/v1/inboxes/messages#index", Source: "routes.rb:580"}, + {Method: "POST", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages", Controller: "public/api/v1/inboxes/messages#create", Source: "routes.rb:580"}, + {Method: "PUT", Path: "/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages/:message_id", Controller: "public/api/v1/inboxes/messages#update", Source: "routes.rb:580"}, + {Method: "GET", Path: "/public/api/v1/csat_survey/:id", Controller: "public/api/v1/csat_survey#show", Source: "routes.rb:585"}, + {Method: "PUT", Path: "/public/api/v1/csat_survey/:id", Controller: "public/api/v1/csat_survey#update", Source: "routes.rb:585"}, + + {Method: "POST", Path: "/api/v2/accounts/", Controller: "api/v2/accounts#create", Source: "routes.rb:478"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/summary_reports/agent", Controller: "api/v2/accounts/summary_reports#agent", Source: "routes.rb:481"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/summary_reports/team", Controller: "api/v2/accounts/summary_reports#team", Source: "routes.rb:482"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/summary_reports/inbox", Controller: "api/v2/accounts/summary_reports#inbox", Source: "routes.rb:483"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/summary_reports/label", Controller: "api/v2/accounts/summary_reports#label", Source: "routes.rb:484"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/summary_reports/channel", Controller: "api/v2/accounts/summary_reports#channel", Source: "routes.rb:485"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports", Controller: "api/v2/accounts/reports#index", Source: "routes.rb:488"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/summary", Controller: "api/v2/accounts/reports#summary", Source: "routes.rb:491"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/bot_summary", Controller: "api/v2/accounts/reports#bot_summary", Source: "routes.rb:492"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/agents", Controller: "api/v2/accounts/reports#agents", Source: "routes.rb:493"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/inboxes", Controller: "api/v2/accounts/reports#inboxes", Source: "routes.rb:494"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/labels", Controller: "api/v2/accounts/reports#labels", Source: "routes.rb:495"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/teams", Controller: "api/v2/accounts/reports#teams", Source: "routes.rb:496"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/conversations", Controller: "api/v2/accounts/reports#conversations", Source: "routes.rb:497"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/conversations_summary", Controller: "api/v2/accounts/reports#conversations_summary", Source: "routes.rb:498"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/conversation_traffic", Controller: "api/v2/accounts/reports#conversation_traffic", Source: "routes.rb:499"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/bot_metrics", Controller: "api/v2/accounts/reports#bot_metrics", Source: "routes.rb:500"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/inbox_label_matrix", Controller: "api/v2/accounts/reports#inbox_label_matrix", Source: "routes.rb:501"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/first_response_time_distribution", Controller: "api/v2/accounts/reports#first_response_time_distribution", Source: "routes.rb:502"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/reports/outgoing_messages_count", Controller: "api/v2/accounts/reports#outgoing_messages_count", Source: "routes.rb:503"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/live_reports/conversation_metrics", Controller: "api/v2/accounts/live_reports#conversation_metrics", Source: "routes.rb:508"}, + {Method: "GET", Path: "/api/v2/accounts/:account_id/live_reports/grouped_conversation_metrics", Controller: "api/v2/accounts/live_reports#grouped_conversation_metrics", Source: "routes.rb:509"}, } func main() { @@ -284,7 +402,7 @@ func writeParity(path string, gochatPath string, chatwootPath string, gochatRout b.WriteString("Generated from:\n\n") b.WriteString("- GoChat route dump: `" + gochatPath + "`\n") b.WriteString("- Chatwoot route source: `" + chatwootPath + "`\n\n") - b.WriteString("This report covers tracked frontend-critical API v1 account routes from `reference/chatwoot/config/routes.rb`. Ruby is not installed in the workspace, so Chatwoot routes are sourced from static route declarations instead of `bin/rails routes`.\n\n") + b.WriteString("This report covers tracked frontend-critical Chatwoot routes from `reference/chatwoot/config/routes.rb`, including API v1 account routes, Captain/Copilot, assignment policies, widget/public APIs, and API v2 reports. Ruby is not installed in the workspace, so Chatwoot routes are sourced from static route declarations instead of `bin/rails routes`.\n\n") b.WriteString(fmt.Sprintf("Summary: %d exact, %d method-compatible, %d parameter-compatible, %d missing out of %d tracked critical routes.\n\n", len(exact), len(methodCompatible), len(compatible), len(missing), len(criticalRoutes))) b.WriteString("## Missing Critical Routes\n\n") diff --git a/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md b/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md index ff03ea54..3a481187 100644 --- a/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md +++ b/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md @@ -17,9 +17,9 @@ Build GoChat as a Go backend that can directly reuse the frontend from `referenc ## Current Baseline - `go test ./...` passes. -- Route dump succeeds with `TOTAL: 723`. +- Route dump succeeds with `TOTAL: 791`. - Route parity artifacts now exist under `docs/parity/` and are generated by `cmd/route_parity`. -- First tracked frontend-critical API v1 route audit covers 138 Chatwoot routes: 138 exact, 0 method-compatible, 0 parameter-compatible, 0 missing. +- Tracked frontend-critical route audit covers 251 Chatwoot routes: 251 exact, 0 method-compatible, 0 parameter-compatible, 0 missing. - Handler test stability fixes are committed into the baseline before feature parity work continues. - `.codegraph/` is generated indexing output and is not part of tracked product code. @@ -54,10 +54,10 @@ This is the ordered queue for the next implementation slices. Do not skip the ro | Order | Work item | Primary phase | Done when | Status | | --- | --- | --- | --- | --- | -| Q1 | Expand tracked route parity to Captain/Copilot, assignment policies, widget, public inbox/contact/conversation, public CSAT, v2 reports, summary reports, and live reports. | Phase 2 | `cmd/route_parity` tracks these groups and `docs/parity/route_parity.md` lists every missing/mismatched route. | Next | -| Q2 | Patch route aliases discovered by Q1, especially Chatwoot widget/public paths such as `/api/v1/widget/...` versus existing `/widget/...`. | Phase 2 | Missing tracked routes return to zero or are explicitly documented with implementation tasks. | Todo | -| Q3 | Add route boot regression coverage for wildcard conflict groups before expanding more Rails-style resources. | Phase 2 | Router tests cover nested dynamic resources that previously risked Gin conflicts. | Todo | -| Q4 | Start serializer parity fixtures for auth/session, conversations/messages, contacts/companies, inboxes, notifications, and search. | Phase 3 | Each area has at least one reference fixture and Go response test. | Todo | +| Q1 | Expand tracked route parity to Captain/Copilot, assignment policies, widget, public inbox/contact/conversation, public CSAT, v2 reports, summary reports, and live reports. | Phase 2 | `cmd/route_parity` tracks these groups and `docs/parity/route_parity.md` lists every missing/mismatched route. | Done | +| Q2 | Patch route aliases discovered by Q1, especially Chatwoot widget/public paths such as `/api/v1/widget/...` versus existing `/widget/...`. | Phase 2 | Missing tracked routes return to zero or are explicitly documented with implementation tasks. | Done | +| Q3 | Add route boot regression coverage for wildcard conflict groups before expanding more Rails-style resources. | Phase 2 | Router tests cover nested dynamic resources that previously risked Gin conflicts. | Done | +| Q4 | Start serializer parity fixtures for auth/session, conversations/messages, contacts/companies, inboxes, notifications, and search. | Phase 3 | Each area has at least one reference fixture and Go response test. | Next | | Q5 | Review Meilisearch document shape and endpoint payloads against Chatwoot frontend consumers. | Phase 1 and Phase 3 | Search remains Meilisearch-first and payload mismatches are fixed or tracked. | Todo | | Q6 | Burn down enterprise gaps in this order: SLA, assignment policy and capacity, CSAT, automation/macros, Audit, CustomRole, InboxLimit, Captain/Copilot. | Phase 4 and Phase 5 | Each feature passes route, persistence, auth, side-effect, response, and test checks. | Todo | | Q7 | Add frontend smoke harness using the reused Chatwoot frontend once core API flows boot end-to-end. | Phase 7 | Login, inbox list, conversation list/detail, message send, contact view, and widget init run without frontend adapters. | Todo | @@ -156,7 +156,7 @@ Checklist: - [ ] Extend parity table with auth scope, request params, response serializer, and handler implementation status. - [x] Patch first-batch route names and wildcard params where Gin constraints require different internal names, while preserving external URLs. - [x] Add regression tests for the contact conversations relation route added in this slice. -- [ ] Expand route boot regression coverage for all Gin wildcard/param conflict groups. +- [x] Expand route boot regression coverage for tracked Gin wildcard/param conflict groups. Acceptance: @@ -173,20 +173,20 @@ Tracking table: | P2.3 | Produce route parity table: method, path, controller/handler, auth, request params, serializer, status. | `docs/parity/route_parity.md` | Review | | P2.4 | Mark frontend-critical gaps from Chatwoot web app route usage. | `docs/parity/route_parity.md` | Done | | P2.5 | Convert existing placeholders/stubs into tracked feature tasks instead of hidden debt. | this doc and gap report | Doing | -| P2.6 | Add route boot regression tests for Gin wildcard/param conflicts. | router and handler tests | Doing | -| P2.7 | Track Captain/Copilot route group from `routes.rb:62-89`. | `cmd/route_parity`, `docs/parity/route_parity.md` | Next | -| P2.8 | Track assignment policies and inbox assignment policy routes from `routes.rb:306-313`. | `cmd/route_parity`, router | Next | -| P2.9 | Track widget API routes from `routes.rb:442-472`; keep `/api/v1/widget` as the Chatwoot-compatible surface. | `cmd/route_parity`, router widget mount | Next | -| P2.10 | Track public API routes from `routes.rb:569-585`, including public inbox contacts, conversations, messages, and CSAT survey. | `cmd/route_parity`, public handlers | Next | -| P2.11 | Track v2 reports, summary reports, and live reports from `routes.rb:479-513`. | `cmd/route_parity`, reports handlers | Next | +| P2.6 | Add route boot regression tests for Gin wildcard/param conflicts. | `internal/router/router_test.go` | Done | +| P2.7 | Track Captain/Copilot route group from `routes.rb:62-89`. | `cmd/route_parity`, `docs/parity/route_parity.md` | Done | +| P2.8 | Track assignment policies and inbox assignment policy routes from `routes.rb:306-313`. | `cmd/route_parity`, router | Done | +| P2.9 | Track widget API routes from `routes.rb:442-472`; keep `/api/v1/widget` as the Chatwoot-compatible surface. | `cmd/route_parity`, router widget mount | Done | +| P2.10 | Track public API routes from `routes.rb:569-585`, including public inbox contacts, conversations, messages, and CSAT survey. | `cmd/route_parity`, public handlers | Done | +| P2.11 | Track v2 reports, summary reports, and live reports from `routes.rb:479-513`. | `cmd/route_parity`, reports handlers | Done | Current Phase 2 route findings: | Type | Count | Required action | | --- | --- | --- | -| Exact tracked critical routes | 138 | Keep covered while expanding audit scope. | +| Exact tracked critical routes | 251 | Keep covered while expanding audit scope. | | Method-compatible update routes | 0 | First tracked batch now has exact Rails-compatible method coverage. | -| Missing tracked critical routes | 0 | First tracked frontend-critical batch has no route-level gaps. | +| Missing tracked critical routes | 0 | Current tracked frontend-critical route set has no route-level gaps. | Expanded tracked groups now covered by route parity: @@ -199,16 +199,21 @@ Expanded tracked groups now covered by route parity: | Notifications | account-scoped notifications, notification_settings, unread_count, read_all, destroy_all, snooze, unread. | | Inbox members | Chatwoot account-scoped `inbox_members` create/show/update/destroy routes. | | Inbox member actions | agent_bot, set_agent_bot, sync_templates, health, register_webhook, reset_secret, avatar. | +| Captain/Copilot | Assistants, assistant inboxes/scenarios, assistant responses, bulk actions, copilot threads/messages, custom tools, documents, preferences, and tasks. | +| Assignment policies | Account assignment policies, nested inbox bindings, and inbox assignment policy routes. | +| Widget API | Chatwoot `/api/v1/widget/*` route surface plus legacy `/widget/*` compatibility. | +| Public API | Public inbox contact/conversation/message routes and public CSAT survey route. | +| Reports v2 | `/api/v2/accounts/:account_id` summary reports, reports, and live reports. | -Next route groups to track: +New route groups tracked in this slice: | Area | Chatwoot source | Current Go surface observed | Expected action | | --- | --- | --- | --- | -| Captain/Copilot | `reference/chatwoot/config/routes.rb:62-89` | Many `/api/v1/accounts/:account_id/captain/...` routes already exist. | Add to tracked critical set, then fix param/method drift. | -| Assignment policies | `reference/chatwoot/config/routes.rb:306-313` | `/assignment_policies` and local `/assignment_policies_v2` routes exist. | Track Chatwoot V2 route shape and inbox nested routes. | -| Widget API | `reference/chatwoot/config/routes.rb:442-472` | GoChat currently exposes many routes under `/widget`. | Add `/api/v1/widget` aliases where missing and keep legacy `/widget` only as compatibility. | -| Public API | `reference/chatwoot/config/routes.rb:569-585` | GoChat currently has public CSAT conversation routes. | Add/track public inbox contact, conversation, message, typing, status, and last-seen routes. | -| Reports v2 | `reference/chatwoot/config/routes.rb:479-513` | GoChat has report-style routes under `/api/v1/accounts`. | Track exact v2 `/api/v2/accounts/:account_id/...` report paths and payloads. | +| Captain/Copilot | `reference/chatwoot/config/routes.rb:62-89` | Many `/api/v1/accounts/:account_id/captain/...` routes already existed. | Added tracked critical set; route parity is exact. | +| Assignment policies | `reference/chatwoot/config/routes.rb:306-313` | `/assignment_policies` and local `/assignment_policies_v2` routes exist. | Added tracked route set and aligned singular inbox assignment delete route. | +| Widget API | `reference/chatwoot/config/routes.rb:442-472` | GoChat exposed many routes only under `/widget`. | Added `/api/v1/widget` aliases; behavior parity remains tracked under Phase 3/6. | +| Public API | `reference/chatwoot/config/routes.rb:569-585` | GoChat had public CSAT conversation routes. | Added public inbox/contact/conversation/message route surface and Chatwoot public CSAT path. | +| Reports v2 | `reference/chatwoot/config/routes.rb:479-513` | GoChat had report-style routes under `/api/v1/accounts`. | Added exact v2 `/api/v2/accounts/:account_id/...` report paths. | Closed tracked critical route gaps in this slice: @@ -396,7 +401,7 @@ Tracking table: | P6.3 | Conversation APIs | `docs/ROUTE_GAP_ANALYSIS.md`, conversation handlers/services | Implement frontend-critical filters, assignment, status, snooze, merge, bulk actions. | Todo | | P6.4 | Message APIs | `docs/ROUTE_GAP_ANALYSIS.md`, message handlers/services | Implement create/list/delete, private notes, attachments, source attribution, events. | Todo | | P6.5 | Inbox APIs | `docs/ROUTE_GAP_ANALYSIS.md`, inbox handlers/services | Implement CRUD, assignable agents, avatar, campaigns, channel settings, reset secret. | Todo | -| P6.6 | Widget APIs | `docs/ROUTE_GAP_ANALYSIS.md`, widget/channel provider code | Implement init, contact, conversations, messages, cable token with Chatwoot widget contracts. | Todo | +| P6.6 | Widget/public APIs | `docs/ROUTE_GAP_ANALYSIS.md`, widget/channel provider code, `chatwootParityStub` routes | Implement init/config, campaigns, events, contact, conversations, messages, cable token, direct uploads, public inbox flows, and public CSAT with Chatwoot contracts. | Todo | | P6.7 | Webhook ingress | `internal/router/router.go`, `internal/handler/webhook/*`, channel providers | Replace generic placeholder with provider-specific verified ingestion and dispatch. | Todo | ## Phase 7: Verification Harness @@ -456,3 +461,4 @@ Verification milestone gates: - 2026-06-04: Phase 2 route parity tracking added. Ruby/Bundler are unavailable in this workspace, so `cmd/route_parity` records static Chatwoot route DSL declarations from `reference/chatwoot/config/routes.rb`, consumes `cmd/dump_routes` output, and writes `docs/parity/route_parity.md`. First tracked critical route summary: 76 exact, 2 method-compatible, 0 parameter-compatible, 3 missing out of 81; GoChat route dump remains `TOTAL: 704`. - 2026-06-04: First tracked Phase 2 route gaps closed. Added Chatwoot-compatible contact conversations, contact export POST, conversation `toggle_priority`, and PUT aliases for conversation/message updates; fixed contact handlers to accept `:account_id` as well as legacy `:id`. Regenerated parity report: 81 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 81; route dump now reports `TOTAL: 709`. - 2026-06-04: Expanded Phase 2 route parity from 81 to 138 tracked frontend-critical account routes. Added account-scoped notification routes, `notification_settings` PUT alias, and Chatwoot account-level `inbox_members` create/show/update/destroy handlers. Regenerated parity report: 138 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 138; route dump now reports `TOTAL: 723`. +- 2026-06-04: Expanded Phase 2 route parity from 138 to 251 tracked frontend-critical routes. Added tracking and route-level coverage for Captain/Copilot, assignment policies, `/api/v1/widget`, public inbox/contact/conversation/message APIs, public CSAT survey, and `/api/v2` reports. Regenerated parity report: 251 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 251; route dump now reports `TOTAL: 791`. Added router boot regression coverage for Captain static/dynamic routes, widget collection routes, public nested message routes, and v2 reports. diff --git a/docs/parity/gochat_routes.txt b/docs/parity/gochat_routes.txt index a7979e49..94de541a 100644 --- a/docs/parity/gochat_routes.txt +++ b/docs/parity/gochat_routes.txt @@ -50,6 +50,7 @@ DELETE /api/v1/accounts/:account_id/google_channels/:google_id DELETE /api/v1/accounts/:account_id/hooks/:id DELETE /api/v1/accounts/:account_id/inbox_members/ DELETE /api/v1/accounts/:account_id/inboxes/:inbox_id +DELETE /api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/ DELETE /api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/:policy_id DELETE /api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy_v2/ DELETE /api/v1/accounts/:account_id/inboxes/:inbox_id/avatar @@ -99,6 +100,7 @@ DELETE /api/v1/notifications/destroy_all DELETE /api/v1/profile/avatar DELETE /api/v1/profile/mfa/ DELETE /api/v1/push_subscriptions/:id +DELETE /api/v1/widget/labels/:label_id DELETE /platform/api/v1/accounts/:account_id DELETE /platform/api/v1/accounts/:account_id/account_users/:user_id DELETE /platform/api/v1/agent_bots/:id @@ -362,6 +364,33 @@ GET /api/v1/saml/slo GET /api/v1/sso_sessions/:session_id GET /api/v1/sso_sessions/user/:user_id GET /api/v1/sso_sessions/user/:user_id/count +GET /api/v1/widget/campaigns +GET /api/v1/widget/contact +GET /api/v1/widget/conversations +GET /api/v1/widget/conversations/toggle_status +GET /api/v1/widget/inbox_members +GET /api/v1/widget/messages +GET /api/v2/accounts/:account_id/live_reports/conversation_metrics +GET /api/v2/accounts/:account_id/live_reports/grouped_conversation_metrics +GET /api/v2/accounts/:account_id/reports +GET /api/v2/accounts/:account_id/reports/agents +GET /api/v2/accounts/:account_id/reports/bot_metrics +GET /api/v2/accounts/:account_id/reports/bot_summary +GET /api/v2/accounts/:account_id/reports/conversation_traffic +GET /api/v2/accounts/:account_id/reports/conversations +GET /api/v2/accounts/:account_id/reports/conversations_summary +GET /api/v2/accounts/:account_id/reports/first_response_time_distribution +GET /api/v2/accounts/:account_id/reports/inbox_label_matrix +GET /api/v2/accounts/:account_id/reports/inboxes +GET /api/v2/accounts/:account_id/reports/labels +GET /api/v2/accounts/:account_id/reports/outgoing_messages_count +GET /api/v2/accounts/:account_id/reports/summary +GET /api/v2/accounts/:account_id/reports/teams +GET /api/v2/accounts/:account_id/summary_reports/agent +GET /api/v2/accounts/:account_id/summary_reports/channel +GET /api/v2/accounts/:account_id/summary_reports/inbox +GET /api/v2/accounts/:account_id/summary_reports/label +GET /api/v2/accounts/:account_id/summary_reports/team GET /cable GET /health GET /platform/api/v1/accounts @@ -385,6 +414,12 @@ GET /platform/api/v1/users/:id/token GET /platform/api/v1/widget_tests/ GET /platform/api/v1/widget_tests/:type GET /public/api/v1/conversations/:conversation_uuid/csats +GET /public/api/v1/csat_survey/:id +GET /public/api/v1/inboxes/:inbox_id +GET /public/api/v1/inboxes/:inbox_id/contacts/:contact_id +GET /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations +GET /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id +GET /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages GET /webhooks/:channel_type/:identifier GET /widget/cable_token GET /widget/conversations @@ -422,8 +457,14 @@ PATCH /api/v1/accounts/:account_id/integrations/slack/ PATCH /api/v1/accounts/:account_id/notification_settings/ PATCH /api/v1/accounts/:account_id/notifications/:notification_id PATCH /api/v1/accounts/:account_id/teams/:team_id/team_members/ +PATCH /api/v1/widget/contact +PATCH /api/v1/widget/contact/set_user +PATCH /api/v1/widget/messages/:message_id PATCH /platform/api/v1/accounts/:account_id PATCH /platform/api/v1/users/:id +PATCH /public/api/v1/csat_survey/:id +PATCH /public/api/v1/inboxes/:inbox_id/contacts/:contact_id +PATCH /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages/:message_id PATCH /webhooks/:channel_type/:identifier PATCH /widget/contact POST /api/v1/accounts/ @@ -477,6 +518,8 @@ POST /api/v1/accounts/:account_id/captain/documents/ POST /api/v1/accounts/:account_id/captain/documents/:document_id/sync POST /api/v1/accounts/:account_id/captain/preferences/ POST /api/v1/accounts/:account_id/captain/scenarios/ +POST /api/v1/accounts/:account_id/captain/tasks/follow_up +POST /api/v1/accounts/:account_id/captain/tasks/label_suggestion POST /api/v1/accounts/:account_id/captain/tasks/reply_suggestion POST /api/v1/accounts/:account_id/captain/tasks/reply_suggestion/stream POST /api/v1/accounts/:account_id/captain/tasks/rewrite @@ -631,6 +674,20 @@ POST /api/v1/saml/acs POST /api/v1/saml/slo POST /api/v1/sso_sessions/:session_id/terminate POST /api/v1/sso_sessions/user/:user_id/terminate_all +POST /api/v1/widget/config +POST /api/v1/widget/contact/destroy_custom_attributes +POST /api/v1/widget/conversations +POST /api/v1/widget/conversations/destroy_custom_attributes +POST /api/v1/widget/conversations/set_custom_attributes +POST /api/v1/widget/conversations/toggle_typing +POST /api/v1/widget/conversations/transcript +POST /api/v1/widget/conversations/update_last_seen +POST /api/v1/widget/direct_uploads +POST /api/v1/widget/events +POST /api/v1/widget/integrations/dyte/add_participant_to_meeting +POST /api/v1/widget/labels +POST /api/v1/widget/messages +POST /api/v2/accounts/ POST /platform/api/v1/accounts POST /platform/api/v1/accounts/:account_id/account_users POST /platform/api/v1/agent_bots @@ -647,6 +704,12 @@ POST /platform/api/v1/users POST /platform/api/v1/users/:id/login POST /platform/api/v1/users/:id/token POST /public/api/v1/conversations/:conversation_uuid/csats +POST /public/api/v1/inboxes/:inbox_id/contacts +POST /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations +POST /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages +POST /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/toggle_status +POST /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/toggle_typing +POST /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/update_last_seen POST /webhooks/:channel_type/:identifier POST /widget/conversations/:id/toggle_typing POST /widget/direct_uploads @@ -714,11 +777,16 @@ PUT /api/v1/oidc/config PUT /api/v1/profile PUT /api/v1/profile/avatar PUT /api/v1/profile/set_active_account +PUT /api/v1/widget/contact +PUT /api/v1/widget/messages/:message_id PUT /platform/api/v1/agent_bots/:id PUT /platform/api/v1/agent_bots/:id/avatar PUT /platform/api/v1/apps/:id PUT /platform/api/v1/banners/:id PUT /platform/api/v1/installation_configs/:id +PUT /public/api/v1/csat_survey/:id +PUT /public/api/v1/inboxes/:inbox_id/contacts/:contact_id +PUT /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages/:message_id PUT /webhooks/:channel_type/:identifier TRACE /webhooks/:channel_type/:identifier -TOTAL: 723 +TOTAL: 791 diff --git a/docs/parity/route_parity.md b/docs/parity/route_parity.md index 3bbb3a96..3212f536 100644 --- a/docs/parity/route_parity.md +++ b/docs/parity/route_parity.md @@ -5,9 +5,9 @@ Generated from: - GoChat route dump: `docs/parity/gochat_routes.txt` - Chatwoot route source: `reference/chatwoot/config/routes.rb` -This report covers tracked frontend-critical API v1 account routes from `reference/chatwoot/config/routes.rb`. Ruby is not installed in the workspace, so Chatwoot routes are sourced from static route declarations instead of `bin/rails routes`. +This report covers tracked frontend-critical Chatwoot routes from `reference/chatwoot/config/routes.rb`, including API v1 account routes, Captain/Copilot, assignment policies, widget/public APIs, and API v2 reports. Ruby is not installed in the workspace, so Chatwoot routes are sourced from static route declarations instead of `bin/rails routes`. -Summary: 138 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 138 tracked critical routes. +Summary: 251 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 251 tracked critical routes. ## Missing Critical Routes @@ -36,8 +36,16 @@ These routes exist with equivalent method and path shape but different parameter | Method | Chatwoot Path | GoChat Match | Controller | Source | Status | | --- | --- | --- | --- | --- | --- | | DELETE | `/api/v1/accounts/:account_id/agents/:agent_id` | `/api/v1/accounts/:account_id/agents/:agent_id` | `api/v1/accounts/agents#destroy` | `routes.rb:59` | exact | +| DELETE | `/api/v1/accounts/:account_id/assignment_policies/:policy_id` | `/api/v1/accounts/:account_id/assignment_policies/:policy_id` | `api/v1/accounts/assignment_policies#destroy` | `routes.rb:306` | exact | +| DELETE | `/api/v1/accounts/:account_id/assignment_policies/:policy_id/inboxes/:inbox_id` | `/api/v1/accounts/:account_id/assignment_policies/:policy_id/inboxes/:inbox_id` | `api/v1/accounts/assignment_policies/inboxes#destroy` | `routes.rb:307` | exact | | DELETE | `/api/v1/accounts/:account_id/automation_rules/:automation_id` | `/api/v1/accounts/:account_id/automation_rules/:automation_id` | `api/v1/accounts/automation_rules#destroy` | `routes.rb:115` | exact | | DELETE | `/api/v1/accounts/:account_id/canned_responses/:id` | `/api/v1/accounts/:account_id/canned_responses/:id` | `api/v1/accounts/canned_responses#destroy` | `routes.rb:114` | exact | +| DELETE | `/api/v1/accounts/:account_id/captain/assistant_responses/:response_id` | `/api/v1/accounts/:account_id/captain/assistant_responses/:response_id` | `api/v1/accounts/captain/assistant_responses#destroy` | `routes.rb:74` | exact | +| DELETE | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id` | `api/v1/accounts/captain/assistants#destroy` | `routes.rb:64` | exact | +| DELETE | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/inboxes/:inbox_id` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/inboxes/:inbox_id` | `api/v1/accounts/captain/assistants/inboxes#destroy` | `routes.rb:71` | exact | +| DELETE | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/:scenario_id` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/:scenario_id` | `api/v1/accounts/captain/assistants/scenarios#destroy` | `routes.rb:72` | exact | +| DELETE | `/api/v1/accounts/:account_id/captain/custom_tools/:tool_id` | `/api/v1/accounts/:account_id/captain/custom_tools/:tool_id` | `api/v1/accounts/captain/custom_tools#destroy` | `routes.rb:79` | exact | +| DELETE | `/api/v1/accounts/:account_id/captain/documents/:document_id` | `/api/v1/accounts/:account_id/captain/documents/:document_id` | `api/v1/accounts/captain/documents#destroy` | `routes.rb:82` | exact | | DELETE | `/api/v1/accounts/:account_id/companies/:company_id` | `/api/v1/accounts/:account_id/companies/:company_id` | `api/v1/accounts/companies#destroy` | `routes.rb:179` | exact | | DELETE | `/api/v1/accounts/:account_id/companies/:company_id/contacts/:contact_id` | `/api/v1/accounts/:account_id/companies/:company_id/contacts/:contact_id` | `api/v1/accounts/companies/contacts#destroy` | `routes.rb:188` | exact | | DELETE | `/api/v1/accounts/:account_id/contacts/:contact_id` | `/api/v1/accounts/:account_id/contacts/:contact_id` | `api/v1/accounts/contacts#destroy` | `routes.rb:197` | exact | @@ -47,6 +55,7 @@ These routes exist with equivalent method and path shape but different parameter | DELETE | `/api/v1/accounts/:account_id/custom_filters/:id` | `/api/v1/accounts/:account_id/custom_filters/:id` | `api/v1/accounts/custom_filters#destroy` | `routes.rb:251` | exact | | DELETE | `/api/v1/accounts/:account_id/inbox_members/` | `/api/v1/accounts/:account_id/inbox_members/` | `api/v1/accounts/inbox_members#destroy` | `routes.rb:277` | exact | | DELETE | `/api/v1/accounts/:account_id/inboxes/:inbox_id` | `/api/v1/accounts/:account_id/inboxes/:inbox_id` | `api/v1/accounts/inboxes#destroy` | `routes.rb:252` | exact | +| DELETE | `/api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/` | `api/v1/accounts/inboxes/assignment_policy#destroy` | `routes.rb:311` | exact | | DELETE | `/api/v1/accounts/:account_id/inboxes/:inbox_id/avatar` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/avatar` | `api/v1/accounts/inboxes#avatar` | `routes.rb:257` | exact | | DELETE | `/api/v1/accounts/:account_id/labels/:tag_id` | `/api/v1/accounts/:account_id/labels/:tag_id` | `api/v1/accounts/labels#destroy` | `routes.rb:281` | exact | | DELETE | `/api/v1/accounts/:account_id/macros/:macro_id` | `/api/v1/accounts/:account_id/macros/:macro_id` | `api/v1/accounts/macros#destroy` | `routes.rb:118` | exact | @@ -54,15 +63,34 @@ These routes exist with equivalent method and path shape but different parameter | DELETE | `/api/v1/accounts/:account_id/sla_policies/:id` | `/api/v1/accounts/:account_id/sla_policies/:id` | `api/v1/accounts/sla_policies#destroy` | `routes.rb:121` | exact | | DELETE | `/api/v1/accounts/:account_id/teams/:team_id` | `/api/v1/accounts/:account_id/teams/:team_id` | `api/v1/accounts/teams#destroy` | `routes.rb:296` | exact | | DELETE | `/api/v1/accounts/:account_id/teams/:team_id/team_members/` | `/api/v1/accounts/:account_id/teams/:team_id/team_members/` | `api/v1/accounts/teams/team_members#destroy` | `routes.rb:299` | exact | +| DELETE | `/api/v1/widget/labels/:label_id` | `/api/v1/widget/labels/:label_id` | `api/v1/widget/labels#destroy` | `routes.rb:464` | exact | | GET | `/api/v1/accounts/:account_id` | `/api/v1/accounts/:account_id` | `api/v1/accounts#show` | `routes.rb:47` | exact | | GET | `/api/v1/accounts/:account_id/agent_capacity_policies/` | `/api/v1/accounts/:account_id/agent_capacity_policies/` | `api/v1/accounts/agent_capacity_policies#index` | `routes.rb:123` | exact | | GET | `/api/v1/accounts/:account_id/agents` | `/api/v1/accounts/:account_id/agents` | `api/v1/accounts/agents#index` | `routes.rb:59` | exact | | GET | `/api/v1/accounts/:account_id/assignable_agents` | `/api/v1/accounts/:account_id/assignable_agents` | `api/v1/accounts/assignable_agents#index` | `routes.rb:104` | exact | +| GET | `/api/v1/accounts/:account_id/assignment_policies/` | `/api/v1/accounts/:account_id/assignment_policies/` | `api/v1/accounts/assignment_policies#index` | `routes.rb:306` | exact | +| GET | `/api/v1/accounts/:account_id/assignment_policies/:policy_id` | `/api/v1/accounts/:account_id/assignment_policies/:policy_id` | `api/v1/accounts/assignment_policies#show` | `routes.rb:306` | exact | +| GET | `/api/v1/accounts/:account_id/assignment_policies/:policy_id/inboxes` | `/api/v1/accounts/:account_id/assignment_policies/:policy_id/inboxes` | `api/v1/accounts/assignment_policies/inboxes#index` | `routes.rb:307` | exact | | GET | `/api/v1/accounts/:account_id/audit_logs/` | `/api/v1/accounts/:account_id/audit_logs/` | `api/v1/accounts/audit_logs#show` | `routes.rb:105` | exact | | GET | `/api/v1/accounts/:account_id/automation_rules/` | `/api/v1/accounts/:account_id/automation_rules/` | `api/v1/accounts/automation_rules#index` | `routes.rb:115` | exact | | GET | `/api/v1/accounts/:account_id/automation_rules/:automation_id` | `/api/v1/accounts/:account_id/automation_rules/:automation_id` | `api/v1/accounts/automation_rules#show` | `routes.rb:115` | exact | | GET | `/api/v1/accounts/:account_id/cache_keys` | `/api/v1/accounts/:account_id/cache_keys` | `api/v1/accounts#cache_keys` | `routes.rb:50` | exact | | GET | `/api/v1/accounts/:account_id/canned_responses/` | `/api/v1/accounts/:account_id/canned_responses/` | `api/v1/accounts/canned_responses#index` | `routes.rb:114` | exact | +| GET | `/api/v1/accounts/:account_id/captain/assistant_responses/` | `/api/v1/accounts/:account_id/captain/assistant_responses/` | `api/v1/accounts/captain/assistant_responses#index` | `routes.rb:74` | exact | +| GET | `/api/v1/accounts/:account_id/captain/assistant_responses/:response_id` | `/api/v1/accounts/:account_id/captain/assistant_responses/:response_id` | `api/v1/accounts/captain/assistant_responses#show` | `routes.rb:74` | exact | +| GET | `/api/v1/accounts/:account_id/captain/assistants/` | `/api/v1/accounts/:account_id/captain/assistants/` | `api/v1/accounts/captain/assistants#index` | `routes.rb:64` | exact | +| GET | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id` | `api/v1/accounts/captain/assistants#show` | `routes.rb:64` | exact | +| GET | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/inboxes` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/inboxes` | `api/v1/accounts/captain/assistants/inboxes#index` | `routes.rb:71` | exact | +| GET | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/` | `api/v1/accounts/captain/assistants/scenarios#index` | `routes.rb:72` | exact | +| GET | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/:scenario_id` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/:scenario_id` | `api/v1/accounts/captain/assistants/scenarios#show` | `routes.rb:72` | exact | +| GET | `/api/v1/accounts/:account_id/captain/assistants/tools` | `/api/v1/accounts/:account_id/captain/assistants/tools` | `api/v1/accounts/captain/assistants#tools` | `routes.rb:69` | exact | +| GET | `/api/v1/accounts/:account_id/captain/copilot_threads/` | `/api/v1/accounts/:account_id/captain/copilot_threads/` | `api/v1/accounts/captain/copilot_threads#index` | `routes.rb:76` | exact | +| GET | `/api/v1/accounts/:account_id/captain/copilot_threads/:thread_id/copilot_messages/` | `/api/v1/accounts/:account_id/captain/copilot_threads/:thread_id/copilot_messages/` | `api/v1/accounts/captain/copilot_messages#index` | `routes.rb:77` | exact | +| GET | `/api/v1/accounts/:account_id/captain/custom_tools/` | `/api/v1/accounts/:account_id/captain/custom_tools/` | `api/v1/accounts/captain/custom_tools#index` | `routes.rb:79` | exact | +| GET | `/api/v1/accounts/:account_id/captain/custom_tools/:tool_id` | `/api/v1/accounts/:account_id/captain/custom_tools/:tool_id` | `api/v1/accounts/captain/custom_tools#show` | `routes.rb:79` | exact | +| GET | `/api/v1/accounts/:account_id/captain/documents/` | `/api/v1/accounts/:account_id/captain/documents/` | `api/v1/accounts/captain/documents#index` | `routes.rb:82` | exact | +| GET | `/api/v1/accounts/:account_id/captain/documents/:document_id` | `/api/v1/accounts/:account_id/captain/documents/:document_id` | `api/v1/accounts/captain/documents#show` | `routes.rb:82` | exact | +| GET | `/api/v1/accounts/:account_id/captain/preferences/` | `/api/v1/accounts/:account_id/captain/preferences/` | `api/v1/accounts/captain/preferences#show` | `routes.rb:63` | exact | | GET | `/api/v1/accounts/:account_id/companies/` | `/api/v1/accounts/:account_id/companies/` | `api/v1/accounts/companies#index` | `routes.rb:179` | exact | | GET | `/api/v1/accounts/:account_id/companies/:company_id` | `/api/v1/accounts/:account_id/companies/:company_id` | `api/v1/accounts/companies#show` | `routes.rb:179` | exact | | GET | `/api/v1/accounts/:account_id/companies/:company_id/contacts` | `/api/v1/accounts/:account_id/companies/:company_id/contacts` | `api/v1/accounts/companies/contacts#index` | `routes.rb:188` | exact | @@ -93,6 +121,7 @@ These routes exist with equivalent method and path shape but different parameter | GET | `/api/v1/accounts/:account_id/inboxes/:inbox_id` | `/api/v1/accounts/:account_id/inboxes/:inbox_id` | `api/v1/accounts/inboxes#show` | `routes.rb:252` | exact | | GET | `/api/v1/accounts/:account_id/inboxes/:inbox_id/agent_bot` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/agent_bot` | `api/v1/accounts/inboxes#agent_bot` | `routes.rb:255` | exact | | GET | `/api/v1/accounts/:account_id/inboxes/:inbox_id/assignable_agents` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/assignable_agents` | `api/v1/accounts/inboxes#assignable_agents` | `routes.rb:253` | exact | +| GET | `/api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/` | `api/v1/accounts/inboxes/assignment_policy#show` | `routes.rb:311` | exact | | GET | `/api/v1/accounts/:account_id/inboxes/:inbox_id/campaigns` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/campaigns` | `api/v1/accounts/inboxes#campaigns` | `routes.rb:254` | exact | | GET | `/api/v1/accounts/:account_id/inboxes/:inbox_id/health` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/health` | `api/v1/accounts/inboxes#health` | `routes.rb:259` | exact | | GET | `/api/v1/accounts/:account_id/labels/` | `/api/v1/accounts/:account_id/labels/` | `api/v1/accounts/labels#index` | `routes.rb:281` | exact | @@ -112,14 +141,66 @@ These routes exist with equivalent method and path shape but different parameter | GET | `/api/v1/accounts/:account_id/teams/` | `/api/v1/accounts/:account_id/teams/` | `api/v1/accounts/teams#index` | `routes.rb:296` | exact | | GET | `/api/v1/accounts/:account_id/teams/:team_id` | `/api/v1/accounts/:account_id/teams/:team_id` | `api/v1/accounts/teams#show` | `routes.rb:296` | exact | | GET | `/api/v1/accounts/:account_id/teams/:team_id/team_members/` | `/api/v1/accounts/:account_id/teams/:team_id/team_members/` | `api/v1/accounts/teams/team_members#index` | `routes.rb:297` | exact | +| GET | `/api/v1/widget/campaigns` | `/api/v1/widget/campaigns` | `api/v1/widget/campaigns#index` | `routes.rb:445` | exact | +| GET | `/api/v1/widget/contact` | `/api/v1/widget/contact` | `api/v1/widget/contact#show` | `routes.rb:458` | exact | +| GET | `/api/v1/widget/conversations` | `/api/v1/widget/conversations` | `api/v1/widget/conversations#index` | `routes.rb:448` | exact | +| GET | `/api/v1/widget/conversations/toggle_status` | `/api/v1/widget/conversations/toggle_status` | `api/v1/widget/conversations#toggle_status` | `routes.rb:455` | exact | +| GET | `/api/v1/widget/inbox_members` | `/api/v1/widget/inbox_members` | `api/v1/widget/inbox_members#index` | `routes.rb:463` | exact | +| GET | `/api/v1/widget/messages` | `/api/v1/widget/messages` | `api/v1/widget/messages#index` | `routes.rb:447` | exact | +| GET | `/api/v2/accounts/:account_id/live_reports/conversation_metrics` | `/api/v2/accounts/:account_id/live_reports/conversation_metrics` | `api/v2/accounts/live_reports#conversation_metrics` | `routes.rb:508` | exact | +| GET | `/api/v2/accounts/:account_id/live_reports/grouped_conversation_metrics` | `/api/v2/accounts/:account_id/live_reports/grouped_conversation_metrics` | `api/v2/accounts/live_reports#grouped_conversation_metrics` | `routes.rb:509` | exact | +| GET | `/api/v2/accounts/:account_id/reports` | `/api/v2/accounts/:account_id/reports` | `api/v2/accounts/reports#index` | `routes.rb:488` | exact | +| GET | `/api/v2/accounts/:account_id/reports/agents` | `/api/v2/accounts/:account_id/reports/agents` | `api/v2/accounts/reports#agents` | `routes.rb:493` | exact | +| GET | `/api/v2/accounts/:account_id/reports/bot_metrics` | `/api/v2/accounts/:account_id/reports/bot_metrics` | `api/v2/accounts/reports#bot_metrics` | `routes.rb:500` | exact | +| GET | `/api/v2/accounts/:account_id/reports/bot_summary` | `/api/v2/accounts/:account_id/reports/bot_summary` | `api/v2/accounts/reports#bot_summary` | `routes.rb:492` | exact | +| GET | `/api/v2/accounts/:account_id/reports/conversation_traffic` | `/api/v2/accounts/:account_id/reports/conversation_traffic` | `api/v2/accounts/reports#conversation_traffic` | `routes.rb:499` | exact | +| GET | `/api/v2/accounts/:account_id/reports/conversations` | `/api/v2/accounts/:account_id/reports/conversations` | `api/v2/accounts/reports#conversations` | `routes.rb:497` | exact | +| GET | `/api/v2/accounts/:account_id/reports/conversations_summary` | `/api/v2/accounts/:account_id/reports/conversations_summary` | `api/v2/accounts/reports#conversations_summary` | `routes.rb:498` | exact | +| GET | `/api/v2/accounts/:account_id/reports/first_response_time_distribution` | `/api/v2/accounts/:account_id/reports/first_response_time_distribution` | `api/v2/accounts/reports#first_response_time_distribution` | `routes.rb:502` | exact | +| GET | `/api/v2/accounts/:account_id/reports/inbox_label_matrix` | `/api/v2/accounts/:account_id/reports/inbox_label_matrix` | `api/v2/accounts/reports#inbox_label_matrix` | `routes.rb:501` | exact | +| GET | `/api/v2/accounts/:account_id/reports/inboxes` | `/api/v2/accounts/:account_id/reports/inboxes` | `api/v2/accounts/reports#inboxes` | `routes.rb:494` | exact | +| GET | `/api/v2/accounts/:account_id/reports/labels` | `/api/v2/accounts/:account_id/reports/labels` | `api/v2/accounts/reports#labels` | `routes.rb:495` | exact | +| GET | `/api/v2/accounts/:account_id/reports/outgoing_messages_count` | `/api/v2/accounts/:account_id/reports/outgoing_messages_count` | `api/v2/accounts/reports#outgoing_messages_count` | `routes.rb:503` | exact | +| GET | `/api/v2/accounts/:account_id/reports/summary` | `/api/v2/accounts/:account_id/reports/summary` | `api/v2/accounts/reports#summary` | `routes.rb:491` | exact | +| GET | `/api/v2/accounts/:account_id/reports/teams` | `/api/v2/accounts/:account_id/reports/teams` | `api/v2/accounts/reports#teams` | `routes.rb:496` | exact | +| GET | `/api/v2/accounts/:account_id/summary_reports/agent` | `/api/v2/accounts/:account_id/summary_reports/agent` | `api/v2/accounts/summary_reports#agent` | `routes.rb:481` | exact | +| GET | `/api/v2/accounts/:account_id/summary_reports/channel` | `/api/v2/accounts/:account_id/summary_reports/channel` | `api/v2/accounts/summary_reports#channel` | `routes.rb:485` | exact | +| GET | `/api/v2/accounts/:account_id/summary_reports/inbox` | `/api/v2/accounts/:account_id/summary_reports/inbox` | `api/v2/accounts/summary_reports#inbox` | `routes.rb:483` | exact | +| GET | `/api/v2/accounts/:account_id/summary_reports/label` | `/api/v2/accounts/:account_id/summary_reports/label` | `api/v2/accounts/summary_reports#label` | `routes.rb:484` | exact | +| GET | `/api/v2/accounts/:account_id/summary_reports/team` | `/api/v2/accounts/:account_id/summary_reports/team` | `api/v2/accounts/summary_reports#team` | `routes.rb:482` | exact | +| GET | `/public/api/v1/csat_survey/:id` | `/public/api/v1/csat_survey/:id` | `public/api/v1/csat_survey#show` | `routes.rb:585` | exact | +| GET | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id` | `public/api/v1/inboxes/contacts#show` | `routes.rb:572` | exact | +| GET | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations` | `public/api/v1/inboxes/conversations#index` | `routes.rb:573` | exact | +| GET | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id` | `public/api/v1/inboxes/conversations#show` | `routes.rb:573` | exact | +| GET | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages` | `public/api/v1/inboxes/messages#index` | `routes.rb:580` | exact | | PATCH | `/api/v1/accounts/:account_id/inbox_members/` | `/api/v1/accounts/:account_id/inbox_members/` | `api/v1/accounts/inbox_members#update` | `routes.rb:278` | exact | | PATCH | `/api/v1/accounts/:account_id/teams/:team_id/team_members/` | `/api/v1/accounts/:account_id/teams/:team_id/team_members/` | `api/v1/accounts/teams/team_members#update` | `routes.rb:300` | exact | +| PATCH | `/api/v1/widget/contact/set_user` | `/api/v1/widget/contact/set_user` | `api/v1/widget/contact#set_user` | `routes.rb:461` | exact | | POST | `/api/v1/accounts/` | `/api/v1/accounts/` | `api/v1/accounts#create` | `routes.rb:47` | exact | | POST | `/api/v1/accounts/:account_id/agents` | `/api/v1/accounts/:account_id/agents` | `api/v1/accounts/agents#create` | `routes.rb:59` | exact | | POST | `/api/v1/accounts/:account_id/agents/bulk_create` | `/api/v1/accounts/:account_id/agents/bulk_create` | `api/v1/accounts/agents#bulk_create` | `routes.rb:60` | exact | +| POST | `/api/v1/accounts/:account_id/assignment_policies/` | `/api/v1/accounts/:account_id/assignment_policies/` | `api/v1/accounts/assignment_policies#create` | `routes.rb:306` | exact | +| POST | `/api/v1/accounts/:account_id/assignment_policies/:policy_id/inboxes` | `/api/v1/accounts/:account_id/assignment_policies/:policy_id/inboxes` | `api/v1/accounts/assignment_policies/inboxes#create` | `routes.rb:307` | exact | | POST | `/api/v1/accounts/:account_id/automation_rules/` | `/api/v1/accounts/:account_id/automation_rules/` | `api/v1/accounts/automation_rules#create` | `routes.rb:115` | exact | | POST | `/api/v1/accounts/:account_id/automation_rules/:automation_id/clone` | `/api/v1/accounts/:account_id/automation_rules/:automation_id/clone` | `api/v1/accounts/automation_rules#clone` | `routes.rb:116` | exact | | POST | `/api/v1/accounts/:account_id/canned_responses/` | `/api/v1/accounts/:account_id/canned_responses/` | `api/v1/accounts/canned_responses#create` | `routes.rb:114` | exact | +| POST | `/api/v1/accounts/:account_id/captain/assistant_responses/` | `/api/v1/accounts/:account_id/captain/assistant_responses/` | `api/v1/accounts/captain/assistant_responses#create` | `routes.rb:74` | exact | +| POST | `/api/v1/accounts/:account_id/captain/assistants/` | `/api/v1/accounts/:account_id/captain/assistants/` | `api/v1/accounts/captain/assistants#create` | `routes.rb:64` | exact | +| POST | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/inboxes` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/inboxes` | `api/v1/accounts/captain/assistants/inboxes#create` | `routes.rb:71` | exact | +| POST | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/playground` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/playground` | `api/v1/accounts/captain/assistants#playground` | `routes.rb:66` | exact | +| POST | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/` | `api/v1/accounts/captain/assistants/scenarios#create` | `routes.rb:72` | exact | +| POST | `/api/v1/accounts/:account_id/captain/bulk_actions/` | `/api/v1/accounts/:account_id/captain/bulk_actions/` | `api/v1/accounts/captain/bulk_actions#create` | `routes.rb:75` | exact | +| POST | `/api/v1/accounts/:account_id/captain/copilot_threads/` | `/api/v1/accounts/:account_id/captain/copilot_threads/` | `api/v1/accounts/captain/copilot_threads#create` | `routes.rb:76` | exact | +| POST | `/api/v1/accounts/:account_id/captain/copilot_threads/:thread_id/copilot_messages/` | `/api/v1/accounts/:account_id/captain/copilot_threads/:thread_id/copilot_messages/` | `api/v1/accounts/captain/copilot_messages#create` | `routes.rb:77` | exact | +| POST | `/api/v1/accounts/:account_id/captain/custom_tools/` | `/api/v1/accounts/:account_id/captain/custom_tools/` | `api/v1/accounts/captain/custom_tools#create` | `routes.rb:79` | exact | +| POST | `/api/v1/accounts/:account_id/captain/custom_tools/test` | `/api/v1/accounts/:account_id/captain/custom_tools/test` | `api/v1/accounts/captain/custom_tools#test` | `routes.rb:80` | exact | +| POST | `/api/v1/accounts/:account_id/captain/documents/` | `/api/v1/accounts/:account_id/captain/documents/` | `api/v1/accounts/captain/documents#create` | `routes.rb:82` | exact | +| POST | `/api/v1/accounts/:account_id/captain/documents/:document_id/sync` | `/api/v1/accounts/:account_id/captain/documents/:document_id/sync` | `api/v1/accounts/captain/documents#sync` | `routes.rb:83` | exact | +| POST | `/api/v1/accounts/:account_id/captain/tasks/follow_up` | `/api/v1/accounts/:account_id/captain/tasks/follow_up` | `api/v1/accounts/captain/tasks#follow_up` | `routes.rb:90` | exact | +| POST | `/api/v1/accounts/:account_id/captain/tasks/label_suggestion` | `/api/v1/accounts/:account_id/captain/tasks/label_suggestion` | `api/v1/accounts/captain/tasks#label_suggestion` | `routes.rb:89` | exact | +| POST | `/api/v1/accounts/:account_id/captain/tasks/reply_suggestion` | `/api/v1/accounts/:account_id/captain/tasks/reply_suggestion` | `api/v1/accounts/captain/tasks#reply_suggestion` | `routes.rb:88` | exact | +| POST | `/api/v1/accounts/:account_id/captain/tasks/rewrite` | `/api/v1/accounts/:account_id/captain/tasks/rewrite` | `api/v1/accounts/captain/tasks#rewrite` | `routes.rb:86` | exact | +| POST | `/api/v1/accounts/:account_id/captain/tasks/summarize` | `/api/v1/accounts/:account_id/captain/tasks/summarize` | `api/v1/accounts/captain/tasks#summarize` | `routes.rb:87` | exact | | POST | `/api/v1/accounts/:account_id/companies/` | `/api/v1/accounts/:account_id/companies/` | `api/v1/accounts/companies#create` | `routes.rb:179` | exact | | POST | `/api/v1/accounts/:account_id/companies/:company_id/contacts/:contact_id` | `/api/v1/accounts/:account_id/companies/:company_id/contacts/:contact_id` | `api/v1/accounts/companies/contacts#create` | `routes.rb:188` | exact | | POST | `/api/v1/accounts/:account_id/contacts/` | `/api/v1/accounts/:account_id/contacts/` | `api/v1/accounts/contacts#create` | `routes.rb:197` | exact | @@ -141,6 +222,7 @@ These routes exist with equivalent method and path shape but different parameter | POST | `/api/v1/accounts/:account_id/custom_filters/` | `/api/v1/accounts/:account_id/custom_filters/` | `api/v1/accounts/custom_filters#create` | `routes.rb:251` | exact | | POST | `/api/v1/accounts/:account_id/inbox_members/` | `/api/v1/accounts/:account_id/inbox_members/` | `api/v1/accounts/inbox_members#create` | `routes.rb:275` | exact | | POST | `/api/v1/accounts/:account_id/inboxes/` | `/api/v1/accounts/:account_id/inboxes/` | `api/v1/accounts/inboxes#create` | `routes.rb:252` | exact | +| POST | `/api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/` | `api/v1/accounts/inboxes/assignment_policy#create` | `routes.rb:311` | exact | | POST | `/api/v1/accounts/:account_id/inboxes/:inbox_id/register_webhook` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/register_webhook` | `api/v1/accounts/inboxes#register_webhook` | `routes.rb:260` | exact | | POST | `/api/v1/accounts/:account_id/inboxes/:inbox_id/reset_secret` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/reset_secret` | `api/v1/accounts/inboxes#reset_secret` | `routes.rb:262` | exact | | POST | `/api/v1/accounts/:account_id/inboxes/:inbox_id/set_agent_bot` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/set_agent_bot` | `api/v1/accounts/inboxes#set_agent_bot` | `routes.rb:256` | exact | @@ -156,10 +238,36 @@ These routes exist with equivalent method and path shape but different parameter | POST | `/api/v1/accounts/:account_id/teams/` | `/api/v1/accounts/:account_id/teams/` | `api/v1/accounts/teams#create` | `routes.rb:296` | exact | | POST | `/api/v1/accounts/:account_id/teams/:team_id/team_members/` | `/api/v1/accounts/:account_id/teams/:team_id/team_members/` | `api/v1/accounts/teams/team_members#create` | `routes.rb:297` | exact | | POST | `/api/v1/accounts/:account_id/update_active_at` | `/api/v1/accounts/:account_id/update_active_at` | `api/v1/accounts#update_active_at` | `routes.rb:49` | exact | +| POST | `/api/v1/widget/config` | `/api/v1/widget/config` | `api/v1/widget/config#create` | `routes.rb:444` | exact | +| POST | `/api/v1/widget/contact/destroy_custom_attributes` | `/api/v1/widget/contact/destroy_custom_attributes` | `api/v1/widget/contact#destroy_custom_attributes` | `routes.rb:460` | exact | +| POST | `/api/v1/widget/conversations` | `/api/v1/widget/conversations` | `api/v1/widget/conversations#create` | `routes.rb:448` | exact | +| POST | `/api/v1/widget/conversations/destroy_custom_attributes` | `/api/v1/widget/conversations/destroy_custom_attributes` | `api/v1/widget/conversations#destroy_custom_attributes` | `routes.rb:450` | exact | +| POST | `/api/v1/widget/conversations/set_custom_attributes` | `/api/v1/widget/conversations/set_custom_attributes` | `api/v1/widget/conversations#set_custom_attributes` | `routes.rb:451` | exact | +| POST | `/api/v1/widget/conversations/toggle_typing` | `/api/v1/widget/conversations/toggle_typing` | `api/v1/widget/conversations#toggle_typing` | `routes.rb:453` | exact | +| POST | `/api/v1/widget/conversations/transcript` | `/api/v1/widget/conversations/transcript` | `api/v1/widget/conversations#transcript` | `routes.rb:454` | exact | +| POST | `/api/v1/widget/conversations/update_last_seen` | `/api/v1/widget/conversations/update_last_seen` | `api/v1/widget/conversations#update_last_seen` | `routes.rb:452` | exact | +| POST | `/api/v1/widget/direct_uploads` | `/api/v1/widget/direct_uploads` | `api/v1/widget/direct_uploads#create` | `routes.rb:443` | exact | +| POST | `/api/v1/widget/events` | `/api/v1/widget/events` | `api/v1/widget/events#create` | `routes.rb:446` | exact | +| POST | `/api/v1/widget/integrations/dyte/add_participant_to_meeting` | `/api/v1/widget/integrations/dyte/add_participant_to_meeting` | `api/v1/widget/integrations/dyte#add_participant_to_meeting` | `routes.rb:469` | exact | +| POST | `/api/v1/widget/labels` | `/api/v1/widget/labels` | `api/v1/widget/labels#create` | `routes.rb:464` | exact | +| POST | `/api/v1/widget/messages` | `/api/v1/widget/messages` | `api/v1/widget/messages#create` | `routes.rb:447` | exact | +| POST | `/api/v2/accounts/` | `/api/v2/accounts/` | `api/v2/accounts#create` | `routes.rb:478` | exact | +| POST | `/public/api/v1/inboxes/:inbox_id/contacts` | `/public/api/v1/inboxes/:inbox_id/contacts` | `public/api/v1/inboxes/contacts#create` | `routes.rb:572` | exact | +| POST | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations` | `public/api/v1/inboxes/conversations#create` | `routes.rb:573` | exact | +| POST | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages` | `public/api/v1/inboxes/messages#create` | `routes.rb:580` | exact | +| POST | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/toggle_status` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/toggle_status` | `public/api/v1/inboxes/conversations#toggle_status` | `routes.rb:575` | exact | +| POST | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/toggle_typing` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/toggle_typing` | `public/api/v1/inboxes/conversations#toggle_typing` | `routes.rb:576` | exact | +| POST | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/update_last_seen` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/update_last_seen` | `public/api/v1/inboxes/conversations#update_last_seen` | `routes.rb:577` | exact | | PUT | `/api/v1/accounts/:account_id` | `/api/v1/accounts/:account_id` | `api/v1/accounts#update` | `routes.rb:47` | exact | | PUT | `/api/v1/accounts/:account_id/agents/:agent_id` | `/api/v1/accounts/:account_id/agents/:agent_id` | `api/v1/accounts/agents#update` | `routes.rb:59` | exact | +| PUT | `/api/v1/accounts/:account_id/assignment_policies/:policy_id` | `/api/v1/accounts/:account_id/assignment_policies/:policy_id` | `api/v1/accounts/assignment_policies#update` | `routes.rb:306` | exact | | PUT | `/api/v1/accounts/:account_id/automation_rules/:automation_id` | `/api/v1/accounts/:account_id/automation_rules/:automation_id` | `api/v1/accounts/automation_rules#update` | `routes.rb:115` | exact | | PUT | `/api/v1/accounts/:account_id/canned_responses/:id` | `/api/v1/accounts/:account_id/canned_responses/:id` | `api/v1/accounts/canned_responses#update` | `routes.rb:114` | exact | +| PUT | `/api/v1/accounts/:account_id/captain/assistant_responses/:response_id` | `/api/v1/accounts/:account_id/captain/assistant_responses/:response_id` | `api/v1/accounts/captain/assistant_responses#update` | `routes.rb:74` | exact | +| PUT | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id` | `api/v1/accounts/captain/assistants#update` | `routes.rb:64` | exact | +| PUT | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/:scenario_id` | `/api/v1/accounts/:account_id/captain/assistants/:assistant_id/scenarios/:scenario_id` | `api/v1/accounts/captain/assistants/scenarios#update` | `routes.rb:72` | exact | +| PUT | `/api/v1/accounts/:account_id/captain/custom_tools/:tool_id` | `/api/v1/accounts/:account_id/captain/custom_tools/:tool_id` | `api/v1/accounts/captain/custom_tools#update` | `routes.rb:79` | exact | +| PUT | `/api/v1/accounts/:account_id/captain/preferences/` | `/api/v1/accounts/:account_id/captain/preferences/` | `api/v1/accounts/captain/preferences#update` | `routes.rb:63` | exact | | PUT | `/api/v1/accounts/:account_id/companies/:company_id` | `/api/v1/accounts/:account_id/companies/:company_id` | `api/v1/accounts/companies#update` | `routes.rb:179` | exact | | PUT | `/api/v1/accounts/:account_id/contacts/:contact_id` | `/api/v1/accounts/:account_id/contacts/:contact_id` | `api/v1/accounts/contacts#update` | `routes.rb:197` | exact | | PUT | `/api/v1/accounts/:account_id/conversations/:conversation_id` | `/api/v1/accounts/:account_id/conversations/:conversation_id` | `api/v1/accounts/conversations#update` | `routes.rb:134` | exact | @@ -173,3 +281,8 @@ These routes exist with equivalent method and path shape but different parameter | PUT | `/api/v1/accounts/:account_id/notifications/:notification_id` | `/api/v1/accounts/:account_id/notifications/:notification_id` | `api/v1/accounts/notifications#update` | `routes.rb:283` | exact | | PUT | `/api/v1/accounts/:account_id/sla_policies/:id` | `/api/v1/accounts/:account_id/sla_policies/:id` | `api/v1/accounts/sla_policies#update` | `routes.rb:121` | exact | | PUT | `/api/v1/accounts/:account_id/teams/:team_id` | `/api/v1/accounts/:account_id/teams/:team_id` | `api/v1/accounts/teams#update` | `routes.rb:296` | exact | +| PUT | `/api/v1/widget/contact` | `/api/v1/widget/contact` | `api/v1/widget/contact#update` | `routes.rb:458` | exact | +| PUT | `/api/v1/widget/messages/:message_id` | `/api/v1/widget/messages/:message_id` | `api/v1/widget/messages#update` | `routes.rb:447` | exact | +| PUT | `/public/api/v1/csat_survey/:id` | `/public/api/v1/csat_survey/:id` | `public/api/v1/csat_survey#update` | `routes.rb:585` | exact | +| PUT | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id` | `public/api/v1/inboxes/contacts#update` | `routes.rb:572` | exact | +| PUT | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages/:message_id` | `/public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages/:message_id` | `public/api/v1/inboxes/messages#update` | `routes.rb:580` | exact | diff --git a/internal/handler/api/v1/csat_survey_handler.go b/internal/handler/api/v1/csat_survey_handler.go index b5b844a6..7a24e315 100644 --- a/internal/handler/api/v1/csat_survey_handler.go +++ b/internal/handler/api/v1/csat_survey_handler.go @@ -138,6 +138,9 @@ func (h *CsatSurveyHandler) Update(c *gin.Context) { // GET /public/api/v1/conversations/:conversation_uuid/csats func (h *CsatSurveyHandler) PublicGet(c *gin.Context) { conversationUUID := c.Param("conversation_uuid") + if conversationUUID == "" { + conversationUUID = c.Param("id") + } if conversationUUID == "" { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "conversation_uuid is required") return @@ -156,6 +159,9 @@ func (h *CsatSurveyHandler) PublicGet(c *gin.Context) { // POST /public/api/v1/conversations/:conversation_uuid/csats func (h *CsatSurveyHandler) PublicUpdate(c *gin.Context) { conversationUUID := c.Param("conversation_uuid") + if conversationUUID == "" { + conversationUUID = c.Param("id") + } if conversationUUID == "" { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "conversation_uuid is required") return diff --git a/internal/router/router.go b/internal/router/router.go index b18580fa..be0dff84 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -228,6 +228,12 @@ func RegisterRoutes( // Reference: Chatwoot POST /widget/direct_uploads widget.POST("/direct_uploads", handlers.Upload.DirectUpload) + // Chatwoot widget API routes — public + CORS for reused Chatwoot frontend/widget. + // Reference: Chatwoot namespace :api/:v1/:widget at /api/v1/widget/*. + widgetV1 := engine.Group("/api/v1/widget") + widgetV1.Use(middleware.CORS(corsCfg)) + registerChatwootWidgetRoutes(widgetV1, handlers) + // Public CSAT survey routes — no auth required (customer submits rating) // Reference: Chatwoot /public/api/v1/conversations/:uuid/csats publicAPI := engine.Group("/public/api/v1") @@ -235,8 +241,45 @@ func RegisterRoutes( { publicAPI.GET("/conversations/:conversation_uuid/csats", handlers.CsatSurvey.PublicGet) publicAPI.POST("/conversations/:conversation_uuid/csats", handlers.CsatSurvey.PublicUpdate) + publicAPI.GET("/csat_survey/:id", handlers.CsatSurvey.PublicGet) + publicAPI.PUT("/csat_survey/:id", handlers.CsatSurvey.PublicUpdate) + publicAPI.PATCH("/csat_survey/:id", handlers.CsatSurvey.PublicUpdate) + + // Public API compatibility routes used by Chatwoot public inbox/contact flows. + publicInboxes := publicAPI.Group("/inboxes") + { + publicInboxes.GET("/:inbox_id", chatwootParityStub) + contacts := publicInboxes.Group("/:inbox_id/contacts") + { + contacts.POST("", chatwootParityStub) + contacts.GET("/:contact_id", chatwootParityStub) + contacts.PUT("/:contact_id", chatwootParityStub) + contacts.PATCH("/:contact_id", chatwootParityStub) + conversations := contacts.Group("/:contact_id/conversations") + { + conversations.GET("", chatwootParityStub) + conversations.POST("", chatwootParityStub) + conversations.GET("/:conversation_id", chatwootParityStub) + conversations.POST("/:conversation_id/toggle_status", chatwootParityStub) + conversations.POST("/:conversation_id/toggle_typing", chatwootParityStub) + conversations.POST("/:conversation_id/update_last_seen", chatwootParityStub) + messages := conversations.Group("/:conversation_id/messages") + { + messages.GET("", chatwootParityStub) + messages.POST("", chatwootParityStub) + messages.PUT("/:message_id", chatwootParityStub) + messages.PATCH("/:message_id", chatwootParityStub) + } + } + } + } } + // API v2 routes — authenticated, account-scoped report APIs. + apiV2 := engine.Group("/api/v2") + apiV2.Use(middleware.AuthMiddleware(jwtCfg)) + registerV2Routes(apiV2, handlers) + // Webhook callback routes — channel-specific auth // Reference: Chatwoot webhook routes for FB/WhatsApp/Telegram webhookGroup := engine.Group("/webhooks") @@ -1081,6 +1124,8 @@ func registerV1Routes(g *gin.RouterGroup, h *Handlers) { tasks.POST("/reply_suggestion", h.CaptainTask.ReplySuggestion) tasks.POST("/summarize", h.CaptainTask.Summarize) tasks.POST("/rewrite", h.CaptainTask.Rewrite) + tasks.POST("/label_suggestion", h.CaptainTaskExtended.LabelSuggestion) + tasks.POST("/follow_up", h.CaptainTaskExtended.FollowUp) // M12: SSE streaming variants of Captain task endpoints tasks.POST("/reply_suggestion/stream", h.CaptainTask.StreamReplySuggestion) tasks.POST("/summarize/stream", h.CaptainTask.StreamSummarize) @@ -1427,8 +1472,9 @@ func registerV1Routes(g *gin.RouterGroup, h *Handlers) { // Inbox-level assignment policy overrides (nested under inboxes) inboxAssignment := accountScoped.Group("/inboxes/:inbox_id/assignment_policy") { - inboxAssignment.GET("/", h.AssignmentPolicy.GetInboxPolicy) - inboxAssignment.POST("/", h.AssignmentPolicy.CreateInboxPolicy) + inboxAssignment.GET("/", h.AssignmentPolicyV2.GetInboxPolicy) + inboxAssignment.POST("/", h.AssignmentPolicyV2.SetInboxPolicy) + inboxAssignment.DELETE("/", h.AssignmentPolicyV2.DeleteInboxPolicy) inboxAssignment.PUT("/:policy_id", h.AssignmentPolicy.UpdateInboxPolicy) inboxAssignment.DELETE("/:policy_id", h.AssignmentPolicy.DeleteInboxPolicy) } @@ -1523,6 +1569,44 @@ func registerV1Routes(g *gin.RouterGroup, h *Handlers) { } } +// registerV2Routes maps Chatwoot API v2 account-scoped report routes. +// Reference: Chatwoot config/routes.rb namespace :api/:v2. +func registerV2Routes(g *gin.RouterGroup, h *Handlers) { + accounts := g.Group("/accounts") + accounts.Use(middleware.AccountScope()) + { + accounts.POST("/", h.Account.Create) + accountScoped := accounts.Group("/:account_id") + { + v1.RegisterSummaryReportRoutes(accountScoped, h.SummaryReport) + + reports := accountScoped.Group("/reports") + { + reports.GET("", h.Analytics.Summary) + reports.GET("/summary", h.Analytics.Summary) + reports.GET("/bot_summary", h.Analytics.BotSummary) + reports.GET("/agents", h.Analytics.AgentMetrics) + reports.GET("/inboxes", h.Analytics.InboxMetrics) + reports.GET("/labels", h.Analytics.LabelMetrics) + reports.GET("/teams", h.Analytics.TeamMetrics) + reports.GET("/conversations", h.Analytics.Conversations) + reports.GET("/conversations_summary", h.Analytics.ConversationsSummary) + reports.GET("/conversation_traffic", h.Analytics.ConversationTraffic) + reports.GET("/bot_metrics", h.Analytics.BotMetrics) + reports.GET("/inbox_label_matrix", h.Analytics.InboxLabelMatrix) + reports.GET("/first_response_time_distribution", h.Analytics.FirstResponseTimeDistribution) + reports.GET("/outgoing_messages_count", h.Analytics.OutgoingMessagesCount) + } + + liveReports := accountScoped.Group("/live_reports") + { + liveReports.GET("/conversation_metrics", h.LiveReport.ConversationMetrics) + liveReports.GET("/grouped_conversation_metrics", h.LiveReport.GroupedConversationMetrics) + } + } + } +} + // registerPlatformRoutes maps super-admin platform routes. // Reference: Chatwoot namespace :platform_app (super_admin only) func registerPlatformRoutes(g *gin.RouterGroup, h *Handlers) { @@ -1614,6 +1698,40 @@ func registerPlatformTokenRoutes(g *gin.RouterGroup, h *Handlers) { g.DELETE("/accounts/:account_id/account_users/:user_id", h.PlatformAccountUser.Destroy) } +// registerChatwootWidgetRoutes maps Chatwoot's /api/v1/widget namespace. +// Some behavior still delegates to tracked stubs until widget serializer parity is completed. +func registerChatwootWidgetRoutes(g *gin.RouterGroup, h *Handlers) { + g.POST("/direct_uploads", h.Upload.DirectUpload) + g.POST("/config", h.Widget.Init) + g.GET("/campaigns", chatwootParityStub) + g.POST("/events", chatwootParityStub) + + g.GET("/messages", chatwootParityStub) + g.POST("/messages", h.Widget.SendMessage) + g.PUT("/messages/:message_id", chatwootParityStub) + g.PATCH("/messages/:message_id", chatwootParityStub) + + g.GET("/conversations", h.Widget.GetConversations) + g.POST("/conversations", chatwootParityStub) + g.POST("/conversations/destroy_custom_attributes", chatwootParityStub) + g.POST("/conversations/set_custom_attributes", chatwootParityStub) + g.POST("/conversations/update_last_seen", chatwootParityStub) + g.POST("/conversations/toggle_typing", chatwootParityStub) + g.POST("/conversations/transcript", chatwootParityStub) + g.GET("/conversations/toggle_status", chatwootParityStub) + + g.GET("/contact", chatwootParityStub) + g.PUT("/contact", h.Widget.UpdateContact) + g.PATCH("/contact", h.Widget.UpdateContact) + g.POST("/contact/destroy_custom_attributes", chatwootParityStub) + g.PATCH("/contact/set_user", chatwootParityStub) + + g.GET("/inbox_members", chatwootParityStub) + g.POST("/labels", chatwootParityStub) + g.DELETE("/labels/:label_id", chatwootParityStub) + g.POST("/integrations/dyte/add_participant_to_meeting", chatwootParityStub) +} + // registerWidgetRoutes maps widget API routes (public, for embed). // Reference: Chatwoot namespace :widget_api — no authentication required func registerWidgetRoutes(g *gin.RouterGroup, h *widget.WidgetHandler) { @@ -1638,6 +1756,13 @@ func registerWidgetRoutes(g *gin.RouterGroup, h *widget.WidgetHandler) { widget.POST("/:website_token/offline_message", h.SubmitOfflineMessage) } +func chatwootParityStub(c *gin.Context) { + c.JSON(501, gin.H{ + "error": "not implemented", + "message": "Chatwoot parity route is registered; behavior is tracked for implementation", + }) +} + // --- Placeholder handlers for webhooks --- // Full implementation deferred to P7 (channels) diff --git a/internal/router/router_test.go b/internal/router/router_test.go new file mode 100644 index 00000000..2570ea58 --- /dev/null +++ b/internal/router/router_test.go @@ -0,0 +1,48 @@ +package router + +import ( + "testing" + + "github.com/gin-gonic/gin" + "github.com/gochat/gochat/internal/config" + "github.com/gochat/gochat/internal/middleware" +) + +func TestRegisterRoutesBootsWithChatwootParityConflictGroups(t *testing.T) { + gin.SetMode(gin.TestMode) + engine := gin.New() + + RegisterRoutes( + engine, + nil, + nil, + nil, + &Handlers{}, + nil, + nil, + &config.JWTConfig{}, + middleware.CORSConfig{}, + nil, + ) + + routes := map[string]bool{} + for _, route := range engine.Routes() { + routes[route.Method+" "+route.Path] = true + } + + expected := []string{ + "GET /api/v1/accounts/:account_id/captain/assistants/tools", + "GET /api/v1/accounts/:account_id/captain/assistants/:assistant_id", + "GET /api/v1/widget/conversations", + "GET /api/v1/widget/conversations/toggle_status", + "PUT /public/api/v1/inboxes/:inbox_id/contacts/:contact_id/conversations/:conversation_id/messages/:message_id", + "GET /api/v2/accounts/:account_id/reports/summary", + "GET /api/v2/accounts/:account_id/live_reports/grouped_conversation_metrics", + } + + for _, key := range expected { + if !routes[key] { + t.Fatalf("expected route %s to be registered", key) + } + } +}