From 14e784c51daf4e8341bc1d5031e353c771d07f32 Mon Sep 17 00:00:00 2001 From: Rogee Date: Thu, 4 Jun 2026 20:44:20 +0800 Subject: [PATCH] feat(routes): expand account parity coverage --- cmd/route_parity/main.go | 66 ++++++++++++++++- docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md | 19 ++++- docs/parity/gochat_routes.txt | 18 ++++- docs/parity/route_parity.md | 61 +++++++++++++++- .../handler/api/v1/inbox_member_handler.go | 70 ++++++++++++++++++- .../handler/api/v1/notification_handler.go | 12 ++-- internal/router/router.go | 27 +++++-- 7 files changed, 253 insertions(+), 20 deletions(-) diff --git a/cmd/route_parity/main.go b/cmd/route_parity/main.go index dfb07a6a..059a7a32 100644 --- a/cmd/route_parity/main.go +++ b/cmd/route_parity/main.go @@ -29,6 +29,19 @@ var criticalRoutes = []route{ {Method: "POST", Path: "/api/v1/accounts/:account_id/update_active_at", Controller: "api/v1/accounts#update_active_at", Source: "routes.rb:49"}, {Method: "GET", Path: "/api/v1/accounts/:account_id/cache_keys", Controller: "api/v1/accounts#cache_keys", Source: "routes.rb:50"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/agents", Controller: "api/v1/accounts/agents#index", Source: "routes.rb:59"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/agents", Controller: "api/v1/accounts/agents#create", Source: "routes.rb:59"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/agents/:agent_id", Controller: "api/v1/accounts/agents#update", Source: "routes.rb:59"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/agents/:agent_id", Controller: "api/v1/accounts/agents#destroy", Source: "routes.rb:59"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/agents/bulk_create", Controller: "api/v1/accounts/agents#bulk_create", Source: "routes.rb:60"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/assignable_agents", Controller: "api/v1/accounts/assignable_agents#index", Source: "routes.rb:104"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/audit_logs/", Controller: "api/v1/accounts/audit_logs#show", Source: "routes.rb:105"}, + + {Method: "GET", Path: "/api/v1/accounts/:account_id/canned_responses/", Controller: "api/v1/accounts/canned_responses#index", Source: "routes.rb:114"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/canned_responses/", Controller: "api/v1/accounts/canned_responses#create", Source: "routes.rb:114"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/canned_responses/:id", Controller: "api/v1/accounts/canned_responses#update", Source: "routes.rb:114"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/canned_responses/:id", Controller: "api/v1/accounts/canned_responses#destroy", Source: "routes.rb:114"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/conversations/", Controller: "api/v1/accounts/conversations#index", Source: "routes.rb:134"}, {Method: "POST", Path: "/api/v1/accounts/:account_id/conversations/", Controller: "api/v1/accounts/conversations#create", Source: "routes.rb:134"}, {Method: "GET", Path: "/api/v1/accounts/:account_id/conversations/:conversation_id", Controller: "api/v1/accounts/conversations#show", Source: "routes.rb:134"}, @@ -74,6 +87,8 @@ var criticalRoutes = []route{ {Method: "GET", Path: "/api/v1/accounts/:account_id/companies/:company_id/contacts", Controller: "api/v1/accounts/companies/contacts#index", Source: "routes.rb:188"}, {Method: "POST", Path: "/api/v1/accounts/:account_id/companies/:company_id/contacts/:contact_id", Controller: "api/v1/accounts/companies/contacts#create", Source: "routes.rb:188"}, {Method: "DELETE", Path: "/api/v1/accounts/:account_id/companies/:company_id/contacts/:contact_id", Controller: "api/v1/accounts/companies/contacts#destroy", Source: "routes.rb:188"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/companies/:company_id/conversations", Controller: "api/v1/accounts/companies/conversations#index", Source: "routes.rb:193"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/companies/:company_id/notes", Controller: "api/v1/accounts/companies/notes#index", Source: "routes.rb:194"}, {Method: "GET", Path: "/api/v1/accounts/:account_id/search", Controller: "api/v1/accounts/search#index", Source: "routes.rb:170"}, {Method: "GET", Path: "/api/v1/accounts/:account_id/search/conversations", Controller: "api/v1/accounts/search#conversations", Source: "routes.rb:173"}, @@ -88,8 +103,57 @@ var criticalRoutes = []route{ {Method: "DELETE", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id", Controller: "api/v1/accounts/inboxes#destroy", Source: "routes.rb:252"}, {Method: "GET", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/assignable_agents", Controller: "api/v1/accounts/inboxes#assignable_agents", Source: "routes.rb:253"}, {Method: "GET", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/campaigns", Controller: "api/v1/accounts/inboxes#campaigns", Source: "routes.rb:254"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/agent_bot", Controller: "api/v1/accounts/inboxes#agent_bot", Source: "routes.rb:255"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/set_agent_bot", Controller: "api/v1/accounts/inboxes#set_agent_bot", Source: "routes.rb:256"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/avatar", Controller: "api/v1/accounts/inboxes#avatar", Source: "routes.rb:257"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/sync_templates", Controller: "api/v1/accounts/inboxes#sync_templates", Source: "routes.rb:258"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/health", Controller: "api/v1/accounts/inboxes#health", Source: "routes.rb:259"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/register_webhook", Controller: "api/v1/accounts/inboxes#register_webhook", Source: "routes.rb:260"}, {Method: "POST", Path: "/api/v1/accounts/:account_id/inboxes/:inbox_id/reset_secret", Controller: "api/v1/accounts/inboxes#reset_secret", Source: "routes.rb:262"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/inbox_members/", Controller: "api/v1/accounts/inbox_members#create", Source: "routes.rb:275"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/inbox_members/:inbox_id", Controller: "api/v1/accounts/inbox_members#show", Source: "routes.rb:275"}, + {Method: "PATCH", Path: "/api/v1/accounts/:account_id/inbox_members/", Controller: "api/v1/accounts/inbox_members#update", Source: "routes.rb:278"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/inbox_members/", Controller: "api/v1/accounts/inbox_members#destroy", Source: "routes.rb:277"}, + + {Method: "GET", Path: "/api/v1/accounts/:account_id/labels/", Controller: "api/v1/accounts/labels#index", Source: "routes.rb:281"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/labels/", Controller: "api/v1/accounts/labels#create", Source: "routes.rb:281"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/labels/:tag_id", Controller: "api/v1/accounts/labels#show", Source: "routes.rb:281"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/labels/:tag_id", Controller: "api/v1/accounts/labels#update", Source: "routes.rb:281"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/labels/:tag_id", Controller: "api/v1/accounts/labels#destroy", Source: "routes.rb:281"}, + + {Method: "GET", Path: "/api/v1/accounts/:account_id/notifications/", Controller: "api/v1/accounts/notifications#index", Source: "routes.rb:283"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/notifications/:notification_id", Controller: "api/v1/accounts/notifications#update", Source: "routes.rb:283"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/notifications/:notification_id", Controller: "api/v1/accounts/notifications#destroy", Source: "routes.rb:283"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/notifications/read_all", Controller: "api/v1/accounts/notifications#read_all", Source: "routes.rb:285"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/notifications/unread_count", Controller: "api/v1/accounts/notifications#unread_count", Source: "routes.rb:286"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/notifications/destroy_all", Controller: "api/v1/accounts/notifications#destroy_all", Source: "routes.rb:287"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/notifications/:notification_id/snooze", Controller: "api/v1/accounts/notifications#snooze", Source: "routes.rb:290"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/notifications/:notification_id/unread", Controller: "api/v1/accounts/notifications#unread", Source: "routes.rb:291"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/notification_settings/", Controller: "api/v1/accounts/notification_settings#show", Source: "routes.rb:293"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/notification_settings/", Controller: "api/v1/accounts/notification_settings#update", Source: "routes.rb:293"}, + + {Method: "GET", Path: "/api/v1/accounts/:account_id/teams/", Controller: "api/v1/accounts/teams#index", Source: "routes.rb:296"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/teams/", Controller: "api/v1/accounts/teams#create", Source: "routes.rb:296"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/teams/:team_id", Controller: "api/v1/accounts/teams#show", Source: "routes.rb:296"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/teams/:team_id", Controller: "api/v1/accounts/teams#update", Source: "routes.rb:296"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/teams/:team_id", Controller: "api/v1/accounts/teams#destroy", Source: "routes.rb:296"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/teams/:team_id/team_members/", Controller: "api/v1/accounts/teams/team_members#index", Source: "routes.rb:297"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/teams/:team_id/team_members/", Controller: "api/v1/accounts/teams/team_members#create", Source: "routes.rb:297"}, + {Method: "PATCH", Path: "/api/v1/accounts/:account_id/teams/:team_id/team_members/", Controller: "api/v1/accounts/teams/team_members#update", Source: "routes.rb:300"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/teams/:team_id/team_members/", Controller: "api/v1/accounts/teams/team_members#destroy", Source: "routes.rb:299"}, + + {Method: "GET", Path: "/api/v1/accounts/:account_id/custom_attribute_definitions/", Controller: "api/v1/accounts/custom_attribute_definitions#index", Source: "routes.rb:250"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/custom_attribute_definitions/", Controller: "api/v1/accounts/custom_attribute_definitions#create", Source: "routes.rb:250"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/custom_attribute_definitions/:id", Controller: "api/v1/accounts/custom_attribute_definitions#show", Source: "routes.rb:250"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/custom_attribute_definitions/:id", Controller: "api/v1/accounts/custom_attribute_definitions#update", Source: "routes.rb:250"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/custom_attribute_definitions/:id", Controller: "api/v1/accounts/custom_attribute_definitions#destroy", Source: "routes.rb:250"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/custom_filters/", Controller: "api/v1/accounts/custom_filters#index", Source: "routes.rb:251"}, + {Method: "POST", Path: "/api/v1/accounts/:account_id/custom_filters/", Controller: "api/v1/accounts/custom_filters#create", Source: "routes.rb:251"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/custom_filters/:id", Controller: "api/v1/accounts/custom_filters#show", Source: "routes.rb:251"}, + {Method: "PUT", Path: "/api/v1/accounts/:account_id/custom_filters/:id", Controller: "api/v1/accounts/custom_filters#update", Source: "routes.rb:251"}, + {Method: "DELETE", Path: "/api/v1/accounts/:account_id/custom_filters/:id", Controller: "api/v1/accounts/custom_filters#destroy", Source: "routes.rb:251"}, + {Method: "GET", Path: "/api/v1/accounts/:account_id/automation_rules/", Controller: "api/v1/accounts/automation_rules#index", Source: "routes.rb:115"}, {Method: "POST", Path: "/api/v1/accounts/:account_id/automation_rules/", Controller: "api/v1/accounts/automation_rules#create", Source: "routes.rb:115"}, {Method: "GET", Path: "/api/v1/accounts/:account_id/automation_rules/:automation_id", Controller: "api/v1/accounts/automation_rules#show", Source: "routes.rb:115"}, @@ -220,7 +284,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 first report covers 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 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(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 823ae521..82e8b43f 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: 709`. +- Route dump succeeds with `TOTAL: 723`. - Route parity artifacts now exist under `docs/parity/` and are generated by `cmd/route_parity`. -- First tracked frontend-critical API v1 route audit covers 81 Chatwoot routes: 81 exact, 0 method-compatible, 0 parameter-compatible, 0 missing. +- First tracked frontend-critical API v1 route audit covers 138 Chatwoot routes: 138 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. @@ -153,10 +153,22 @@ Current Phase 2 route findings: | Type | Count | Required action | | --- | --- | --- | -| Exact tracked critical routes | 81 | Keep covered while expanding audit scope. | +| Exact tracked critical routes | 138 | 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. | +Expanded tracked groups now covered by route parity: + +| Area | Coverage | +| --- | --- | +| Agents and assignable agents | CRUD/bulk-create route coverage for `agents`, plus `assignable_agents#index`. | +| Canned responses | index/create/update/destroy account routes. | +| Custom attributes and custom filters | index/show/create/update/destroy account routes. | +| Labels and team membership | labels CRUD plus teams and team_members collection actions. | +| 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. | + Closed tracked critical route gaps in this slice: | Method | Path | Chatwoot controller | Implementation note | @@ -363,3 +375,4 @@ env GOCACHE=/tmp/gochat-gocache GOMODCACHE=/tmp/gochat-gomodcache go run ./cmd/d - 2026-06-04: Phase 1 indexing hooks wired for conversations, messages, contacts, companies, and articles. Create/update/delete paths now call the service-layer `SearchIndexer` boundary, bootstrap injects the Meilisearch-backed search service, and unit tests cover each entity hook path. - 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`. diff --git a/docs/parity/gochat_routes.txt b/docs/parity/gochat_routes.txt index 628e3c9a..a7979e49 100644 --- a/docs/parity/gochat_routes.txt +++ b/docs/parity/gochat_routes.txt @@ -48,7 +48,7 @@ DELETE /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id DELETE /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets/:widget_index 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/:user_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/:policy_id DELETE /api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy_v2/ @@ -73,6 +73,8 @@ DELETE /api/v1/accounts/:account_id/labels/:tag_id DELETE /api/v1/accounts/:account_id/macros/:macro_id DELETE /api/v1/accounts/:account_id/microsoft_channels/:ms_id DELETE /api/v1/accounts/:account_id/notification_subscriptions/:identifier +DELETE /api/v1/accounts/:account_id/notifications/:notification_id +DELETE /api/v1/accounts/:account_id/notifications/destroy_all DELETE /api/v1/accounts/:account_id/platform_apps/:platform_app_id DELETE /api/v1/accounts/:account_id/platform_apps/:platform_app_id/permissibles/:permissible_id DELETE /api/v1/accounts/:account_id/portals/:portal_id @@ -222,6 +224,7 @@ GET /api/v1/accounts/:account_id/google/webhooks GET /api/v1/accounts/:account_id/google_channels/authorization GET /api/v1/accounts/:account_id/hooks/ GET /api/v1/accounts/:account_id/hooks/:id +GET /api/v1/accounts/:account_id/inbox_members/:inbox_id GET /api/v1/accounts/:account_id/inboxes/ GET /api/v1/accounts/:account_id/inboxes/:inbox_id GET /api/v1/accounts/:account_id/inboxes/:inbox_id/agent_bot @@ -272,6 +275,9 @@ GET /api/v1/accounts/:account_id/microsoft/oauth GET /api/v1/accounts/:account_id/microsoft/webhooks GET /api/v1/accounts/:account_id/microsoft_channels/authorization GET /api/v1/accounts/:account_id/notification_settings/ +GET /api/v1/accounts/:account_id/notifications/ +GET /api/v1/accounts/:account_id/notifications/:notification_id +GET /api/v1/accounts/:account_id/notifications/unread_count GET /api/v1/accounts/:account_id/platform_apps/ GET /api/v1/accounts/:account_id/platform_apps/:platform_app_id GET /api/v1/accounts/:account_id/platform_apps/:platform_app_id/access_tokens @@ -403,6 +409,7 @@ PATCH /api/v1/accounts/:account_id/csat_survey_responses/:id PATCH /api/v1/accounts/:account_id/custom_attribute_definitions/:id PATCH /api/v1/accounts/:account_id/custom_filters/:id PATCH /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id +PATCH /api/v1/accounts/:account_id/inbox_members/ PATCH /api/v1/accounts/:account_id/inboxes/:inbox_id/email_channels/:em_id PATCH /api/v1/accounts/:account_id/inboxes/:inbox_id/inbox_limits/:id PATCH /api/v1/accounts/:account_id/inboxes/:inbox_id/instagram_channels/:ig_id @@ -413,6 +420,7 @@ PATCH /api/v1/accounts/:account_id/inboxes/:inbox_id/tiktok_channels/:tt_id PATCH /api/v1/accounts/:account_id/inboxes/:inbox_id/twilio_sms_channels/:tw_id 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 /platform/api/v1/accounts/:account_id PATCH /platform/api/v1/users/:id @@ -560,6 +568,10 @@ POST /api/v1/accounts/:account_id/macros/:macro_id/toggle_active POST /api/v1/accounts/:account_id/microsoft/webhooks POST /api/v1/accounts/:account_id/microsoft_channels/oauth_callback POST /api/v1/accounts/:account_id/notification_subscriptions/ +POST /api/v1/accounts/:account_id/notifications/:notification_id/snooze +POST /api/v1/accounts/:account_id/notifications/:notification_id/unread +POST /api/v1/accounts/:account_id/notifications/destroy_all +POST /api/v1/accounts/:account_id/notifications/read_all POST /api/v1/accounts/:account_id/platform_apps/ POST /api/v1/accounts/:account_id/platform_apps/:platform_app_id/permissibles POST /api/v1/accounts/:account_id/platform_apps/:platform_app_id/regenerate_access_token @@ -680,6 +692,8 @@ PUT /api/v1/accounts/:account_id/inboxes/:inbox_id/working_hours PUT /api/v1/accounts/:account_id/integrations/hooks/:id PUT /api/v1/accounts/:account_id/labels/:tag_id PUT /api/v1/accounts/:account_id/macros/:macro_id +PUT /api/v1/accounts/:account_id/notification_settings/ +PUT /api/v1/accounts/:account_id/notifications/:notification_id PUT /api/v1/accounts/:account_id/platform_apps/:platform_app_id PUT /api/v1/accounts/:account_id/portals/:portal_id PUT /api/v1/accounts/:account_id/portals/:portal_id/articles/:article_id @@ -707,4 +721,4 @@ PUT /platform/api/v1/banners/:id PUT /platform/api/v1/installation_configs/:id PUT /webhooks/:channel_type/:identifier TRACE /webhooks/:channel_type/:identifier -TOTAL: 709 +TOTAL: 723 diff --git a/docs/parity/route_parity.md b/docs/parity/route_parity.md index 9fc017c7..3bbb3a96 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 first report covers 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 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`. -Summary: 81 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 81 tracked critical routes. +Summary: 138 exact, 0 method-compatible, 0 parameter-compatible, 0 missing out of 138 tracked critical routes. ## Missing Critical Routes @@ -35,23 +35,39 @@ 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/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/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 | | DELETE | `/api/v1/accounts/:account_id/conversations/:conversation_id` | `/api/v1/accounts/:account_id/conversations/:conversation_id` | `api/v1/accounts/conversations#destroy` | `routes.rb:134` | exact | | DELETE | `/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id` | `/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id` | `api/v1/accounts/conversations/messages#destroy` | `routes.rb:142` | exact | +| DELETE | `/api/v1/accounts/:account_id/custom_attribute_definitions/:id` | `/api/v1/accounts/:account_id/custom_attribute_definitions/:id` | `api/v1/accounts/custom_attribute_definitions#destroy` | `routes.rb:250` | exact | +| 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/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 | +| DELETE | `/api/v1/accounts/:account_id/notifications/:notification_id` | `/api/v1/accounts/:account_id/notifications/:notification_id` | `api/v1/accounts/notifications#destroy` | `routes.rb:283` | exact | | 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 | | 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/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/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 | +| GET | `/api/v1/accounts/:account_id/companies/:company_id/conversations` | `/api/v1/accounts/:account_id/companies/:company_id/conversations` | `api/v1/accounts/companies/conversations#index` | `routes.rb:193` | exact | +| GET | `/api/v1/accounts/:account_id/companies/:company_id/notes` | `/api/v1/accounts/:account_id/companies/:company_id/notes` | `api/v1/accounts/companies/notes#index` | `routes.rb:194` | exact | | GET | `/api/v1/accounts/:account_id/companies/search` | `/api/v1/accounts/:account_id/companies/search` | `api/v1/accounts/companies#search` | `routes.rb:181` | exact | | GET | `/api/v1/accounts/:account_id/contacts/` | `/api/v1/accounts/:account_id/contacts/` | `api/v1/accounts/contacts#index` | `routes.rb:197` | exact | | GET | `/api/v1/accounts/:account_id/contacts/:contact_id` | `/api/v1/accounts/:account_id/contacts/:contact_id` | `api/v1/accounts/contacts#show` | `routes.rb:197` | exact | @@ -67,13 +83,25 @@ These routes exist with equivalent method and path shape but different parameter | GET | `/api/v1/accounts/:account_id/conversations/unread_counts` | `/api/v1/accounts/:account_id/conversations/unread_counts` | `api/v1/accounts/conversations/unread_counts#index` | `routes.rb:138` | exact | | GET | `/api/v1/accounts/:account_id/csat_survey_responses/` | `/api/v1/accounts/:account_id/csat_survey_responses/` | `api/v1/accounts/csat_survey_responses#index` | `routes.rb:219` | exact | | GET | `/api/v1/accounts/:account_id/csat_survey_responses/metrics` | `/api/v1/accounts/:account_id/csat_survey_responses/metrics` | `api/v1/accounts/csat_survey_responses#metrics` | `routes.rb:221` | exact | +| GET | `/api/v1/accounts/:account_id/custom_attribute_definitions/` | `/api/v1/accounts/:account_id/custom_attribute_definitions/` | `api/v1/accounts/custom_attribute_definitions#index` | `routes.rb:250` | exact | +| GET | `/api/v1/accounts/:account_id/custom_attribute_definitions/:id` | `/api/v1/accounts/:account_id/custom_attribute_definitions/:id` | `api/v1/accounts/custom_attribute_definitions#show` | `routes.rb:250` | exact | +| GET | `/api/v1/accounts/:account_id/custom_filters/` | `/api/v1/accounts/:account_id/custom_filters/` | `api/v1/accounts/custom_filters#index` | `routes.rb:251` | exact | +| GET | `/api/v1/accounts/:account_id/custom_filters/:id` | `/api/v1/accounts/:account_id/custom_filters/:id` | `api/v1/accounts/custom_filters#show` | `routes.rb:251` | exact | | GET | `/api/v1/accounts/:account_id/custom_roles/` | `/api/v1/accounts/:account_id/custom_roles/` | `api/v1/accounts/custom_roles#index` | `routes.rb:122` | exact | +| GET | `/api/v1/accounts/:account_id/inbox_members/:inbox_id` | `/api/v1/accounts/:account_id/inbox_members/:inbox_id` | `api/v1/accounts/inbox_members#show` | `routes.rb:275` | exact | | GET | `/api/v1/accounts/:account_id/inboxes/` | `/api/v1/accounts/:account_id/inboxes/` | `api/v1/accounts/inboxes#index` | `routes.rb:252` | exact | | 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/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 | +| GET | `/api/v1/accounts/:account_id/labels/:tag_id` | `/api/v1/accounts/:account_id/labels/:tag_id` | `api/v1/accounts/labels#show` | `routes.rb:281` | exact | | GET | `/api/v1/accounts/:account_id/macros/` | `/api/v1/accounts/:account_id/macros/` | `api/v1/accounts/macros#index` | `routes.rb:118` | exact | | GET | `/api/v1/accounts/:account_id/macros/:macro_id` | `/api/v1/accounts/:account_id/macros/:macro_id` | `api/v1/accounts/macros#show` | `routes.rb:118` | exact | +| GET | `/api/v1/accounts/:account_id/notification_settings/` | `/api/v1/accounts/:account_id/notification_settings/` | `api/v1/accounts/notification_settings#show` | `routes.rb:293` | exact | +| GET | `/api/v1/accounts/:account_id/notifications/` | `/api/v1/accounts/:account_id/notifications/` | `api/v1/accounts/notifications#index` | `routes.rb:283` | exact | +| GET | `/api/v1/accounts/:account_id/notifications/unread_count` | `/api/v1/accounts/:account_id/notifications/unread_count` | `api/v1/accounts/notifications#unread_count` | `routes.rb:286` | exact | | GET | `/api/v1/accounts/:account_id/search` | `/api/v1/accounts/:account_id/search` | `api/v1/accounts/search#index` | `routes.rb:170` | exact | | GET | `/api/v1/accounts/:account_id/search/articles` | `/api/v1/accounts/:account_id/search/articles` | `api/v1/accounts/search#articles` | `routes.rb:176` | exact | | GET | `/api/v1/accounts/:account_id/search/contacts` | `/api/v1/accounts/:account_id/search/contacts` | `api/v1/accounts/search#contacts` | `routes.rb:175` | exact | @@ -81,9 +109,17 @@ These routes exist with equivalent method and path shape but different parameter | GET | `/api/v1/accounts/:account_id/search/messages` | `/api/v1/accounts/:account_id/search/messages` | `api/v1/accounts/search#messages` | `routes.rb:174` | exact | | GET | `/api/v1/accounts/:account_id/sla_policies` | `/api/v1/accounts/:account_id/sla_policies` | `api/v1/accounts/sla_policies#index` | `routes.rb:121` | exact | | GET | `/api/v1/accounts/:account_id/sla_policies/:id` | `/api/v1/accounts/:account_id/sla_policies/:id` | `api/v1/accounts/sla_policies#show` | `routes.rb:121` | exact | +| 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 | +| 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 | | 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/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/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 | @@ -101,18 +137,39 @@ These routes exist with equivalent method and path shape but different parameter | POST | `/api/v1/accounts/:account_id/conversations/:conversation_id/toggle_status` | `/api/v1/accounts/:account_id/conversations/:conversation_id/toggle_status` | `api/v1/accounts/conversations#toggle_status` | `routes.rb:158` | exact | | POST | `/api/v1/accounts/:account_id/conversations/:conversation_id/transcript` | `/api/v1/accounts/:account_id/conversations/:conversation_id/transcript` | `api/v1/accounts/conversations#transcript` | `routes.rb:157` | exact | | POST | `/api/v1/accounts/:account_id/conversations/filter` | `/api/v1/accounts/:account_id/conversations/filter` | `api/v1/accounts/conversations#filter` | `routes.rb:139` | exact | +| POST | `/api/v1/accounts/:account_id/custom_attribute_definitions/` | `/api/v1/accounts/:account_id/custom_attribute_definitions/` | `api/v1/accounts/custom_attribute_definitions#create` | `routes.rb:250` | exact | +| 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/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 | +| POST | `/api/v1/accounts/:account_id/inboxes/:inbox_id/sync_templates` | `/api/v1/accounts/:account_id/inboxes/:inbox_id/sync_templates` | `api/v1/accounts/inboxes#sync_templates` | `routes.rb:258` | exact | +| POST | `/api/v1/accounts/:account_id/labels/` | `/api/v1/accounts/:account_id/labels/` | `api/v1/accounts/labels#create` | `routes.rb:281` | exact | | POST | `/api/v1/accounts/:account_id/macros/` | `/api/v1/accounts/:account_id/macros/` | `api/v1/accounts/macros#create` | `routes.rb:118` | exact | | POST | `/api/v1/accounts/:account_id/macros/:macro_id/execute` | `/api/v1/accounts/:account_id/macros/:macro_id/execute` | `api/v1/accounts/macros#execute` | `routes.rb:119` | exact | +| POST | `/api/v1/accounts/:account_id/notifications/:notification_id/snooze` | `/api/v1/accounts/:account_id/notifications/:notification_id/snooze` | `api/v1/accounts/notifications#snooze` | `routes.rb:290` | exact | +| POST | `/api/v1/accounts/:account_id/notifications/:notification_id/unread` | `/api/v1/accounts/:account_id/notifications/:notification_id/unread` | `api/v1/accounts/notifications#unread` | `routes.rb:291` | exact | +| POST | `/api/v1/accounts/:account_id/notifications/destroy_all` | `/api/v1/accounts/:account_id/notifications/destroy_all` | `api/v1/accounts/notifications#destroy_all` | `routes.rb:287` | exact | +| POST | `/api/v1/accounts/:account_id/notifications/read_all` | `/api/v1/accounts/:account_id/notifications/read_all` | `api/v1/accounts/notifications#read_all` | `routes.rb:285` | exact | | POST | `/api/v1/accounts/:account_id/sla_policies` | `/api/v1/accounts/:account_id/sla_policies` | `api/v1/accounts/sla_policies#create` | `routes.rb:121` | exact | +| 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 | | 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/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/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 | | PUT | `/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id` | `/api/v1/accounts/:account_id/conversations/:conversation_id/messages/:message_id` | `api/v1/accounts/conversations/messages#update` | `routes.rb:142` | exact | +| PUT | `/api/v1/accounts/:account_id/custom_attribute_definitions/:id` | `/api/v1/accounts/:account_id/custom_attribute_definitions/:id` | `api/v1/accounts/custom_attribute_definitions#update` | `routes.rb:250` | exact | +| PUT | `/api/v1/accounts/:account_id/custom_filters/:id` | `/api/v1/accounts/:account_id/custom_filters/:id` | `api/v1/accounts/custom_filters#update` | `routes.rb:251` | exact | | PUT | `/api/v1/accounts/:account_id/inboxes/:inbox_id` | `/api/v1/accounts/:account_id/inboxes/:inbox_id` | `api/v1/accounts/inboxes#update` | `routes.rb:252` | exact | +| PUT | `/api/v1/accounts/:account_id/labels/:tag_id` | `/api/v1/accounts/:account_id/labels/:tag_id` | `api/v1/accounts/labels#update` | `routes.rb:281` | exact | | PUT | `/api/v1/accounts/:account_id/macros/:macro_id` | `/api/v1/accounts/:account_id/macros/:macro_id` | `api/v1/accounts/macros#update` | `routes.rb:118` | exact | +| PUT | `/api/v1/accounts/:account_id/notification_settings/` | `/api/v1/accounts/:account_id/notification_settings/` | `api/v1/accounts/notification_settings#update` | `routes.rb:293` | exact | +| 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 | diff --git a/internal/handler/api/v1/inbox_member_handler.go b/internal/handler/api/v1/inbox_member_handler.go index d900be5f..e4bba7d0 100644 --- a/internal/handler/api/v1/inbox_member_handler.go +++ b/internal/handler/api/v1/inbox_member_handler.go @@ -151,4 +151,72 @@ func (h *InboxMemberHandler) UpdateMultiple(c *gin.Context) { "members": members, "meta": gin.H{"count": len(members)}, }) -} \ No newline at end of file +} + +// ShowAccountScoped retrieves all agents assigned to an inbox using Chatwoot's account-level route. +// GET /api/v1/accounts/:account_id/inbox_members/:inbox_id +func (h *InboxMemberHandler) ShowAccountScoped(c *gin.Context) { + inboxID, err := parseUintParam(c, "inbox_id") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid inbox id"}) + return + } + + members, svcErr := h.svc.ListByInbox(c.Request.Context(), inboxID) + if svcErr != nil { + c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to list inbox members"}) + return + } + + c.JSON(http.StatusOK, gin.H{"members": members, "meta": gin.H{"count": len(members)}}) +} + +// CreateAccountScoped adds or replaces inbox members using Chatwoot's account-level create route. +// POST /api/v1/accounts/:account_id/inbox_members +func (h *InboxMemberHandler) CreateAccountScoped(c *gin.Context) { + h.updateAccountScoped(c) +} + +// UpdateAccountScoped replaces all members of an inbox using Chatwoot's account-level update route. +// PATCH /api/v1/accounts/:account_id/inbox_members +func (h *InboxMemberHandler) UpdateAccountScoped(c *gin.Context) { + h.updateAccountScoped(c) +} + +func (h *InboxMemberHandler) updateAccountScoped(c *gin.Context) { + var req service.UpdateMultipleRequest + if bindErr := c.ShouldBindJSON(&req); bindErr != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": bindErr.Error()}) + return + } + + members, svcErr := h.svc.UpdateMultiple(c.Request.Context(), req) + if svcErr != nil { + c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to update members"}) + return + } + + c.JSON(http.StatusOK, gin.H{"members": members, "meta": gin.H{"count": len(members)}}) +} + +// DestroyAccountScoped removes selected users from an inbox using Chatwoot's account-level route. +// DELETE /api/v1/accounts/:account_id/inbox_members +func (h *InboxMemberHandler) DestroyAccountScoped(c *gin.Context) { + var req struct { + InboxID uint `json:"inbox_id" binding:"required"` + UserIDs []uint `json:"user_ids" binding:"required"` + } + if bindErr := c.ShouldBindJSON(&req); bindErr != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": bindErr.Error()}) + return + } + + for _, userID := range req.UserIDs { + if svcErr := h.svc.RemoveMember(c.Request.Context(), req.InboxID, userID); svcErr != nil { + c.JSON(http.StatusUnprocessableEntity, gin.H{"error": "failed to remove member"}) + return + } + } + + c.Status(http.StatusOK) +} diff --git a/internal/handler/api/v1/notification_handler.go b/internal/handler/api/v1/notification_handler.go index 15120a21..d52b79e0 100644 --- a/internal/handler/api/v1/notification_handler.go +++ b/internal/handler/api/v1/notification_handler.go @@ -57,7 +57,7 @@ func (h *NotificationHandler) Get(c *gin.Context) { response.AbortWithStatusError(c, http.StatusServiceUnavailable, response.ErrInternal, "Notification service not available") return } - notificationID, err := parseUintParam(c, "notification_id") + notificationID, err := parseUintAnyParam(c, "notification_id", "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid notification id") return @@ -76,7 +76,7 @@ func (h *NotificationHandler) Get(c *gin.Context) { // PUT /api/v1/accounts/:account_id/notifications/:id // Reference: Chatwoot update — @notification.update(read_at: DateTime.now.utc); render json: @notification func (h *NotificationHandler) Update(c *gin.Context) { - notificationID, err := parseUintParam(c, "notification_id") + notificationID, err := parseUintAnyParam(c, "notification_id", "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid notification id") return @@ -134,7 +134,7 @@ func (h *NotificationHandler) UnreadCount(c *gin.Context) { // POST /api/v1/accounts/:account_id/notifications/:id/snooze // Reference: Chatwoot snooze — update(snoozed_until: ..., meta: ...) func (h *NotificationHandler) Snooze(c *gin.Context) { - notificationID, err := parseUintParam(c, "notification_id") + notificationID, err := parseUintAnyParam(c, "notification_id", "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid notification id") return @@ -170,7 +170,7 @@ func (h *NotificationHandler) Snooze(c *gin.Context) { // POST /api/v1/accounts/:account_id/notifications/:id/unread // Reference: Chatwoot unread — @notification.update(read_at: nil); render json: @notification func (h *NotificationHandler) Unread(c *gin.Context) { - notificationID, err := parseUintParam(c, "notification_id") + notificationID, err := parseUintAnyParam(c, "notification_id", "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid notification id") return @@ -192,7 +192,7 @@ func (h *NotificationHandler) Unread(c *gin.Context) { // DELETE /api/v1/accounts/:account_id/notifications/:id // Reference: Chatwoot destroy — @notification.destroy; head :ok func (h *NotificationHandler) Destroy(c *gin.Context) { - notificationID, err := parseUintParam(c, "notification_id") + notificationID, err := parseUintAnyParam(c, "notification_id", "id") if err != nil { response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid notification id") return @@ -225,4 +225,4 @@ func (h *NotificationHandler) DestroyAll(c *gin.Context) { } c.Status(http.StatusOK) -} \ No newline at end of file +} diff --git a/internal/router/router.go b/internal/router/router.go index 99180325..b18580fa 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -519,13 +519,13 @@ func registerV1Routes(g *gin.RouterGroup, h *Handlers) { inboxes.PATCH("/:inbox_id/members/update_multiple", h.InboxMember.UpdateMultiple) inboxes.DELETE("/:inbox_id/members/:user_id", h.InboxMember.RemoveMember) - // Account-level inbox_members (Chatwoot: resources :inbox_members) - // POST /inbox_members (create), DELETE /inbox_members/:id (destroy) - // These are account-scoped routes for managing inbox assignments + // Account-level inbox_members (Chatwoot: resources :inbox_members, param: :inbox_id) inboxMembers := accountScoped.Group("/inbox_members") { - inboxMembers.POST("/", h.InboxMember.AddMember) - inboxMembers.DELETE("/:user_id", h.InboxMember.RemoveMember) + inboxMembers.POST("/", h.InboxMember.CreateAccountScoped) + inboxMembers.PATCH("/", h.InboxMember.UpdateAccountScoped) + inboxMembers.DELETE("/", h.InboxMember.DestroyAccountScoped) + inboxMembers.GET("/:inbox_id", h.InboxMember.ShowAccountScoped) } // CSAT template (ref: Chatwoot resource :csat_template, singular per inbox) @@ -1338,6 +1338,23 @@ func registerV1Routes(g *gin.RouterGroup, h *Handlers) { { notifSettings.GET("/", h.NotificationSetting.Show) notifSettings.PATCH("/", h.NotificationSetting.Update) + notifSettings.PUT("/", h.NotificationSetting.Update) + } + + // Notifications — account-scoped Chatwoot routes used by the dashboard frontend. + notifications := accountScoped.Group("/notifications") + { + notifications.GET("/", h.Notification.List) + notifications.POST("/read_all", h.Notification.MarkAllRead) + notifications.GET("/unread_count", h.Notification.UnreadCount) + notifications.POST("/destroy_all", h.Notification.DestroyAll) + notifications.DELETE("/destroy_all", h.Notification.DestroyAll) + notifications.GET("/:notification_id", h.Notification.Get) + notifications.PUT("/:notification_id", h.Notification.Update) + notifications.PATCH("/:notification_id", h.Notification.Update) + notifications.DELETE("/:notification_id", h.Notification.Destroy) + notifications.POST("/:notification_id/snooze", h.Notification.Snooze) + notifications.POST("/:notification_id/unread", h.Notification.Unread) } // Notification subscriptions (account-scoped)