fix(routes): align dashboard app ids

This commit is contained in:
2026-06-07 12:39:23 +08:00
parent 27f927327d
commit 5062acec61
6 changed files with 58 additions and 47 deletions
+10 -6
View File
@@ -49,16 +49,16 @@ Hermes task landing checklist:
## Current Baseline
- Current tracking checkpoint: 2026-06-07 P3.99 conversation destroy async parity, prepared as `feat(conversations): align destroy job`.
- Latest implementation checkpoint: this checkpoint, prepared as `feat(conversations): align destroy job`.
- Latest documentation/tooling checkpoint: this tracker update records Chatwoot conversation destroy `head :ok` and `DeleteObjectJob` parity. No route artifacts change.
- Current tracking checkpoint: 2026-06-07 P3.100 dashboard app route parameter parity, prepared as `fix(routes): align dashboard app ids`.
- Latest implementation checkpoint: this checkpoint, prepared as `fix(routes): align dashboard app ids`.
- Latest documentation/tooling checkpoint: this tracker update records exact Chatwoot `dashboard_apps/:id` route-parameter parity and regenerated route artifacts.
- Plan landing status: complete for the current known Hermes plans and user-confirmed scope. Future work should update this file directly instead of opening a parallel tracker.
- Worktree status at this implementation checkpoint: account conversation destroy now returns empty `200 OK` like Chatwoot `head :ok` and, when a WorkerPool is configured, enqueues a low-priority durable `conversation:delete_object` job matching the inspected `DeleteObjectJob` path before the worker performs the actual soft delete and event/search cleanup. No-worker tests keep a synchronous fallback. This retains P3.98 conversation transcript delivery parity, P3.97 conversation typing event parity, P3.96 conversation last-seen read parity, P3.95 conversation mute contact-block parity, P3.94 conversation toggle-priority empty-body parity, P3.93 conversation toggle-status fallback parity, and prior checkpoints. Live API/browser/enterprise smoke still needs the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack.
- Worktree status at this implementation checkpoint: dashboard app member routes now register the same `:id` path parameter used by Chatwoot `resources :dashboard_apps`, while the handler still accepts the legacy local `:dashboard_app_id` name for focused tests and extension routes. Route parity artifacts are regenerated and the tracked frontend-critical audit improves to 425 exact routes. This retains P3.99 conversation destroy async parity, P3.98 conversation transcript delivery parity, P3.97 conversation typing event parity, and prior checkpoints. Live API/browser/enterprise smoke still needs the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack.
- Next executable implementation checkpoint: continue Phase 2/3 drift audit for the next reused-frontend mismatch, or run B12 live smoke when the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack is available. Re-run Phase 6 placeholder audit after future route/smoke changes.
- `go test ./...` passes when run outside the restricted socket sandbox for the latest implementation baseline; the latest docs/tooling checkpoint verified `scripts/parity_frontend_smoke.sh --check` with workspace-local temp/cache dirs after `/tmp` was full.
- Route dump succeeds with `967` registered routes after profile MFA route tracking.
- Route dump succeeds with `967` registered routes after dashboard app route-parameter tracking.
- Route parity artifacts now exist under `docs/parity/` and are generated by `cmd/route_parity`.
- Tracked frontend-critical route audit covers 439 Chatwoot routes: 421 exact, 0 method-compatible, 18 parameter-compatible, 0 missing. The 18 parameter-compatible routes are Gin-internal parameter-name differences for nested AgentCapacityPolicy users/inbox limits, dashboard app member `:id` names, WhatsApp call `:id` route names, plus the public article `.md`/`.png` suffixes served through the same external article route dispatcher.
- Tracked frontend-critical route audit covers 439 Chatwoot routes: 425 exact, 0 method-compatible, 14 parameter-compatible, 0 missing. The 14 parameter-compatible routes are Gin-internal parameter-name differences for nested AgentCapacityPolicy users/inbox limits, WhatsApp call `:id` route names, plus the public article `.md`/`.png` suffixes served through the same external article route dispatcher.
- `/api/v1/widget` stubs are burned down and public inbox/contact/conversation/message core flows are backed by real handlers.
- 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.
@@ -156,6 +156,7 @@ This table is the shortest authoritative handoff view. If an older lower section
| Priority | Workstream | Current state | Next checkpoint | Commit close rule |
| --- | --- | --- | --- | --- |
| 0 | P3.100 dashboard app route parameter parity | Implemented for reused dashboard app settings routes: GoChat now registers standard dashboard app member routes with Chatwoot's `:id` parameter name for `show/update/destroy`, keeps `PATCH` and `PUT`, and preserves legacy handler compatibility for local focused tests and widget extension routes. Route parity improves from `421 exact / 18 parameter-compatible` to `425 exact / 14 parameter-compatible` with no missing critical routes. | Keep in Review; reopen from B12 dashboard app settings smoke or fresh reference evidence for serializer/request-permit drift beyond the inspected controller/Jbuilder/frontend store contract. | Focused DashboardAppHandler and router tests passed; route dump/parity artifacts regenerated; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. |
| 0 | P3.99 conversation destroy async parity | Implemented for reused conversation delete actions: `DELETE /conversations/:conversation_id` now returns empty `200 OK` like Chatwoot and queues a low-priority durable `conversation:delete_object` job when workers are configured. The job performs the existing soft delete, dispatches `conversation.deleted`, and removes the search index entry; no-worker construction keeps synchronous focused-test behavior. | Keep in Review; reopen from B12 conversation delete smoke or fresh reference evidence for enterprise `DeleteObjectJob#process_post_deletion_tasks`, exact Pundit destroy permission drift, or heavy-association purge behavior beyond conversation objects. | Focused ConversationService delete/job tests and ConversationHandler delete response tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
| 0 | P3.98 conversation transcript delivery parity | Implemented for reused conversation transcript and automation transcript actions: `POST /conversations/:conversation_id/transcript` now checks Chatwoot-style email transcript availability, enforces configured daily email limits, delivers through the SMTP transcript boundary, increments account outbound-email counters, and scopes transcript content to public incoming/outgoing chat messages. Automation `send_email_transcript` no-ops when disabled and stops recipient fan-out at the rate limit before sync delivery or durable enqueue. | Keep in Review; reopen from B12 transcript smoke or fresh reference evidence for exact Redis TTL behavior, cloud-only global default limit handling, mailer template HTML body drift, or provider-specific SMTP availability checks beyond the inspected mailer gate. | Focused ConversationService, ConversationHandler, ActionService, and Account model transcript tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. `accounts.limits` migration is included; no route artifacts change. |
| 0 | P3.97 conversation typing event parity | Implemented for reused conversation typing action: `POST /conversations/:conversation_id/toggle_typing_status` now accepts empty bodies and `{}` as Chatwoot no-op `200 OK`, rejects malformed JSON, and emits `conversation.typing_on` / `conversation.typing_off` events with conversation/contact/user context and `is_private` metadata for valid statuses. | Keep in Review; reopen from B12 conversation typing smoke or fresh reference evidence for ActionCable event payload fields beyond the inspected `TypingStatusManager` contract. | Focused ConversationService and ConversationHandler toggle-typing tests passed; full `go test ./...` must pass outside the restricted socket sandbox; `git diff --check` must pass. No route artifacts change. |
@@ -263,6 +264,7 @@ These rows are the executable development plan from this point forward. A checkp
| ID | Owner files | Reference files | Work to land | Exit gate |
| --- | --- | --- | --- | --- |
| P3.100 dashboard app route parameter parity | `internal/router/router.go`, `internal/handler/api/v1/dashboard_app_handler.go`, dashboard app handler/router tests, `docs/parity/gochat_routes.txt`, `docs/parity/route_parity.md` | `reference/chatwoot/config/routes.rb:130`, `reference/chatwoot/app/controllers/api/v1/accounts/dashboard_apps_controller.rb`, dashboard apps Jbuilder views, reused dashboard `api/dashboardApps.js` and `store/modules/dashboardApps.js` | Dashboard app standard member routes now use Chatwoot's external `:id` route shape for `GET/PATCH/PUT/DELETE /dashboard_apps/:id`, while handler parsing keeps the prior `:dashboard_app_id` compatibility path for tests and GoChat-only widget extensions. Route parity artifacts are regenerated and the dashboard app rows move from parameter-compatible to exact. | Review by `fix(routes): align dashboard app ids`; focused tests prove handler support for `:id` routes; route parity proves `425 exact, 14 parameter-compatible, 0 missing out of 439`; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. |
| P3.99 conversation destroy async parity | `internal/handler/api/v1/conversation_handler.go`, `internal/service/conversation_service.go`, `internal/service/conversation_delete_worker.go`, `internal/app/bootstrap.go`, conversation handler/service tests | `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb#destroy`, `reference/chatwoot/app/jobs/delete_object_job.rb`, reused dashboard `ConversationApi.delete` and `conversations/actions.js#deleteConversation` | Account conversation destroy now matches the inspected Chatwoot controller/job path: the handler returns empty `200 OK`, production service wiring enqueues a low-priority durable delete-object job, and the worker performs the existing soft-delete side effects plus event dispatch/search-index deletion. The service keeps synchronous behavior when no WorkerPool is set so focused tests and non-worker local callers still delete immediately. | Review by `feat(conversations): align destroy job`; focused tests prove handler `200` response shape, not-found behavior, synchronous fallback deletion, durable job enqueue, delayed visibility before worker execution, missing conversations do not enqueue phantom jobs, and worker replay deletion; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
| P3.98 conversation transcript delivery parity | `internal/handler/api/v1/conversation_handler.go`, `internal/service/conversation_service.go`, `internal/model/account.go`, `internal/model/account_email.go`, `internal/automation/action_service.go`, migration `000037_add_accounts_limits`, conversation handler/service/action/model tests | `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb#transcript`, `reference/chatwoot/app/models/concerns/account_email_rate_limitable.rb`, `reference/chatwoot/app/mailers/conversation_reply_mailer.rb#conversation_transcript`, `reference/chatwoot/app/models/message.rb#chat`, `reference/chatwoot/app/models/concerns/message_filter_helpers.rb#conversation_transcriptable?`, `reference/chatwoot/app/services/action_service.rb#send_email_transcript`, reused dashboard transcript modal/API/i18n payment-required behavior | Account conversation transcript delivery now matches the inspected Chatwoot controller and mailer contract: missing email returns raw `422`, disabled accounts return raw `402`, configured email limits return empty `429`, success returns empty `200 OK` after delivering through a fakeable SMTP transcript boundary and incrementing account outbound-email count. Transcript bodies include only public incoming/outgoing chat messages, and automation transcript actions respect the same enabled/rate behavior while splitting comma recipients and stopping at the first rate-limit miss. | Review by `feat(conversations): align transcript delivery`; focused tests prove account gate/rate helpers, handler `402/429` shape, service delivery/count/message-scope behavior, and automation disabled/rate/filter behavior; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. |
| P3.97 conversation typing event parity | `internal/handler/api/v1/conversation_handler.go`, `internal/service/conversation_service.go`, `internal/channel/event.go`, conversation handler/service tests | `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb#toggle_typing_status`, `reference/chatwoot/app/services/conversations/typing_status_manager.rb`, `reference/chatwoot/app/javascript/dashboard/api/inbox/conversation.js`, `reference/chatwoot/app/javascript/dashboard/store/modules/conversationTypingStatus.js`, reused dashboard reply-box typing callers | Account conversation `toggle_typing_status` now matches Chatwoot's permissive member action: missing body or missing `typing_status` resolves the conversation and returns empty `200 OK` without dispatching an event, malformed JSON still fails, and `on`/`off` statuses dispatch `conversation.typing_on` / `conversation.typing_off` events with conversation/contact/user context plus `is_private`. | Review by `feat(conversations): align typing events`; focused tests prove service event type/data behavior, blank-status no-op after conversation lookup, handler empty-body and `{}` no-op success, and invalid JSON failure; full `go test ./...` must pass outside the restricted socket sandbox; `git diff --check` must pass. No route artifacts change. |
@@ -386,6 +388,7 @@ This ledger records the committed parity checkpoints that future slices should b
| Commit | Scope | Verification summary | Follow-up state |
| --- | --- | --- | --- |
| `fix(routes): align dashboard app ids` | Advances P3.100 with exact Chatwoot dashboard app member route parameter parity. GoChat now registers `GET/PATCH/PUT/DELETE /api/v1/accounts/:account_id/dashboard_apps/:id`, keeps handler compatibility with legacy local `:dashboard_app_id`, and regenerates route parity artifacts. | Focused DashboardAppHandler and router tests passed; route dump/parity regenerated to `TOTAL: 967` and `425 exact, 0 method-compatible, 14 parameter-compatible, 0 missing out of 439`; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. | Move P3.100 to Review; continue reducing remaining parameter-compatible rows or run B12 live smoke. |
| `feat(conversations): align destroy job` | Advances P3.99 with Chatwoot conversation destroy parity. GoChat now returns empty `200 OK` for `DELETE /conversations/:conversation_id`, wires a durable low-priority `conversation:delete_object` job into the WorkerPool, and lets that job perform the existing soft-delete/event/search cleanup while retaining a synchronous fallback without workers. | Focused ConversationService delete/job tests and ConversationHandler delete response tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | Move P3.99 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
| `feat(conversations): align transcript delivery` | Advances P3.98 with Chatwoot conversation transcript delivery parity. GoChat now adds the Chatwoot `accounts.limits` field, uses account email transcript availability/rate helpers, maps dashboard transcript disabled/rate-limited responses to raw `402`/empty `429`, delivers transcript emails through the fakeable SMTP boundary, increments outbound-email counters, and filters transcript content to public incoming/outgoing chat messages. Automation `send_email_transcript` now no-ops when disabled and stops recipient fan-out at the configured rate limit. | Focused Account model, ConversationService, ConversationHandler, and ActionService transcript tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | Move P3.98 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
| `feat(conversations): align typing events` | Advances P3.97 with Chatwoot conversation `toggle_typing_status` parity. GoChat now treats empty or missing `typing_status` as a no-op `200 OK`, keeps malformed JSON as a validation error, and dispatches Chatwoot-style `conversation.typing_on` / `conversation.typing_off` events with user/contact/conversation context and `is_private` metadata. | Focused ConversationService and ConversationHandler toggle-typing tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change. | Move P3.97 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. |
@@ -2778,3 +2781,4 @@ Verification milestone gates:
- 2026-06-07: P3.97 conversation typing checkpoint prepared as `feat(conversations): align typing events`; audited Chatwoot `ConversationsController#toggle_typing_status`, `Conversations::TypingStatusManager`, and reused dashboard conversation typing API/store/reply-box callers. GoChat account conversation `toggle_typing_status` now accepts empty bodies and missing `typing_status` as no-op `200 OK`, still rejects malformed JSON, and dispatches Chatwoot-style `conversation.typing_on` / `conversation.typing_off` events with current user, contact, conversation, and `is_private` metadata for valid `on`/`off` statuses. Focused ConversationService and ConversationHandler toggle-typing tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change.
- 2026-06-07: P3.98 conversation transcript checkpoint prepared as `feat(conversations): align transcript delivery`; audited Chatwoot `ConversationsController#transcript`, `AccountEmailRateLimitable`, `ConversationReplyMailer#conversation_transcript`, `Message.chat`, `MessageFilterHelpers#conversation_transcriptable?`, and automation `ActionService#send_email_transcript`. GoChat account conversation transcript now honors email transcript availability, configured email limits, raw `402`/empty `429` response shapes, SMTP transcript delivery, outbound-email count increments, and public incoming/outgoing chat-message scoping; automation `send_email_transcript` no-ops when disabled and stops recipient fan-out at the rate limit before sync delivery or durable enqueue. Focused Account model, ConversationService, ConversationHandler, and ActionService transcript tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change.
- 2026-06-07: P3.99 conversation destroy checkpoint prepared as `feat(conversations): align destroy job`; audited Chatwoot `ConversationsController#destroy`, `DeleteObjectJob`, and reused dashboard `ConversationApi.delete` / `deleteConversation` action. GoChat account conversation delete now returns empty `200 OK` instead of local `204`, queues a low-priority durable `conversation:delete_object` job when WorkerPool is configured, and the worker performs the existing soft delete, `conversation.deleted` dispatch, and search-index deletion; no-worker construction keeps synchronous deletion for focused tests/local callers. Focused ConversationService delete/job tests and ConversationHandler delete tests passed; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed. No route artifacts change.
- 2026-06-07: P3.100 dashboard app route-parameter checkpoint prepared as `fix(routes): align dashboard app ids`; audited Chatwoot `routes.rb:130`, `DashboardAppsController`, dashboard app Jbuilder views, and reused dashboard `dashboardApps.js` API/store calls. GoChat now registers dashboard app member CRUD routes with Chatwoot's `:id` parameter name, keeps `PATCH` and `PUT`, and preserves legacy local `:dashboard_app_id` parsing for focused tests and GoChat-only widget extensions. Focused DashboardAppHandler and router tests passed; route dump/parity regenerated to `TOTAL: 967` and `425 exact, 0 method-compatible, 14 parameter-compatible, 0 missing out of 439`; full `go test ./...` passed outside the restricted socket sandbox; `git diff --check` passed.
+8 -8
View File
@@ -48,8 +48,8 @@ DELETE /api/v1/accounts/:account_id/conversations/:conversation_id/whatsapp_call
DELETE /api/v1/accounts/:account_id/custom_attribute_definitions/:id
DELETE /api/v1/accounts/:account_id/custom_filters/:id
DELETE /api/v1/accounts/:account_id/custom_roles/:id
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/dashboard_apps/:id
DELETE /api/v1/accounts/:account_id/dashboard_apps/: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/
@@ -249,8 +249,8 @@ GET /api/v1/accounts/:account_id/custom_filters/:id
GET /api/v1/accounts/:account_id/custom_roles/
GET /api/v1/accounts/:account_id/custom_roles/:id
GET /api/v1/accounts/:account_id/dashboard_apps
GET /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id
GET /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets
GET /api/v1/accounts/:account_id/dashboard_apps/:id
GET /api/v1/accounts/:account_id/dashboard_apps/:id/widgets
GET /api/v1/accounts/:account_id/dashboard_apps/search
GET /api/v1/accounts/:account_id/draft_messages/count
GET /api/v1/accounts/:account_id/draft_messages/search
@@ -528,7 +528,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/custom_roles/:id
PATCH /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id
PATCH /api/v1/accounts/:account_id/dashboard_apps/:id
PATCH /api/v1/accounts/:account_id/inbox_members/
PATCH /api/v1/accounts/:account_id/inboxes/:inbox_id
PATCH /api/v1/accounts/:account_id/inboxes/:inbox_id/email_channels/:em_id
@@ -685,7 +685,7 @@ POST /api/v1/accounts/:account_id/custom_attribute_definitions/
POST /api/v1/accounts/:account_id/custom_filters/
POST /api/v1/accounts/:account_id/custom_roles/
POST /api/v1/accounts/:account_id/dashboard_apps
POST /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets
POST /api/v1/accounts/:account_id/dashboard_apps/:id/widgets
POST /api/v1/accounts/:account_id/direct_uploads
POST /api/v1/accounts/:account_id/email_channel_migrations/
POST /api/v1/accounts/:account_id/email_channels/
@@ -914,8 +914,8 @@ PUT /api/v1/accounts/:account_id/conversations/:conversation_id/whatsapp_calls/:
PUT /api/v1/accounts/:account_id/custom_attribute_definitions/:id
PUT /api/v1/accounts/:account_id/custom_filters/:id
PUT /api/v1/accounts/:account_id/custom_roles/:id
PUT /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id
PUT /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets/:widget_index
PUT /api/v1/accounts/:account_id/dashboard_apps/:id
PUT /api/v1/accounts/:account_id/dashboard_apps/:id/widgets/:widget_index
PUT /api/v1/accounts/:account_id/hooks/:id
PUT /api/v1/accounts/:account_id/inboxes/:inbox_id
PUT /api/v1/accounts/:account_id/inboxes/:inbox_id/assignment_policy/:policy_id
+5 -5
View File
@@ -7,7 +7,7 @@ Generated from:
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: 421 exact, 0 method-compatible, 18 parameter-compatible, 0 missing out of 439 tracked critical routes.
Summary: 425 exact, 0 method-compatible, 14 parameter-compatible, 0 missing out of 439 tracked critical routes.
## Missing Critical Routes
@@ -31,14 +31,11 @@ These routes exist with equivalent method and path shape but different parameter
| --- | --- | --- | --- | --- | --- |
| DELETE | `/api/v1/accounts/:account_id/agent_capacity_policies/:agent_capacity_policy_id/inbox_limits/:id` | `/api/v1/accounts/:account_id/agent_capacity_policies/:id/inbox_limits/:limit_id` | `api/v1/accounts/agent_capacity_policies/inbox_limits#destroy` | `routes.rb:126` | parameter-compatible |
| DELETE | `/api/v1/accounts/:account_id/agent_capacity_policies/:agent_capacity_policy_id/users/:id` | `/api/v1/accounts/:account_id/agent_capacity_policies/:id/users/:user_id` | `api/v1/accounts/agent_capacity_policies/users#destroy` | `routes.rb:125` | parameter-compatible |
| DELETE | `/api/v1/accounts/:account_id/dashboard_apps/:id` | `/api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id` | `api/v1/accounts/dashboard_apps#destroy` | `routes.rb:130` | parameter-compatible |
| GET | `/api/v1/accounts/:account_id/agent_capacity_policies/:agent_capacity_policy_id/users` | `/api/v1/accounts/:account_id/agent_capacity_policies/:id/users` | `api/v1/accounts/agent_capacity_policies/users#index` | `routes.rb:125` | parameter-compatible |
| GET | `/api/v1/accounts/:account_id/dashboard_apps/:id` | `/api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id` | `api/v1/accounts/dashboard_apps#show` | `routes.rb:130` | parameter-compatible |
| GET | `/api/v1/accounts/:account_id/whatsapp_calls/:id` | `/api/v1/accounts/:account_id/whatsapp_calls/:call_id` | `api/v1/accounts/whatsapp_calls#show` | `routes.rb:237` | parameter-compatible |
| GET | `/hc/:slug/articles/:article_slug.md` | `/hc/:slug/articles/:article_slug` | `public/api/v1/portals/articles#show_markdown` | `routes.rb:599` | parameter-compatible |
| GET | `/hc/:slug/articles/:article_slug.png` | `/hc/:slug/articles/:article_slug` | `public/api/v1/portals/articles#tracking_pixel` | `routes.rb:598` | parameter-compatible |
| PATCH | `/api/v1/accounts/:account_id/agent_capacity_policies/:agent_capacity_policy_id/inbox_limits/:id` | `/api/v1/accounts/:account_id/agent_capacity_policies/:id/inbox_limits/:limit_id` | `api/v1/accounts/agent_capacity_policies/inbox_limits#update` | `routes.rb:126` | parameter-compatible |
| PATCH | `/api/v1/accounts/:account_id/dashboard_apps/:id` | `/api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id` | `api/v1/accounts/dashboard_apps#update` | `routes.rb:130` | parameter-compatible |
| POST | `/api/v1/accounts/:account_id/agent_capacity_policies/:agent_capacity_policy_id/inbox_limits` | `/api/v1/accounts/:account_id/agent_capacity_policies/:id/inbox_limits` | `api/v1/accounts/agent_capacity_policies/inbox_limits#create` | `routes.rb:126` | parameter-compatible |
| POST | `/api/v1/accounts/:account_id/agent_capacity_policies/:agent_capacity_policy_id/users` | `/api/v1/accounts/:account_id/agent_capacity_policies/:id/users` | `api/v1/accounts/agent_capacity_policies/users#create` | `routes.rb:125` | parameter-compatible |
| POST | `/api/v1/accounts/:account_id/whatsapp_calls/:id/accept` | `/api/v1/accounts/:account_id/whatsapp_calls/:call_id/accept` | `api/v1/accounts/whatsapp_calls#accept` | `routes.rb:239` | parameter-compatible |
@@ -46,7 +43,6 @@ These routes exist with equivalent method and path shape but different parameter
| POST | `/api/v1/accounts/:account_id/whatsapp_calls/:id/terminate` | `/api/v1/accounts/:account_id/whatsapp_calls/:call_id/terminate` | `api/v1/accounts/whatsapp_calls#terminate` | `routes.rb:241` | parameter-compatible |
| POST | `/api/v1/accounts/:account_id/whatsapp_calls/:id/upload_recording` | `/api/v1/accounts/:account_id/whatsapp_calls/:call_id/upload_recording` | `api/v1/accounts/whatsapp_calls#upload_recording` | `routes.rb:242` | parameter-compatible |
| PUT | `/api/v1/accounts/:account_id/agent_capacity_policies/:agent_capacity_policy_id/inbox_limits/:id` | `/api/v1/accounts/:account_id/agent_capacity_policies/:id/inbox_limits/:limit_id` | `api/v1/accounts/agent_capacity_policies/inbox_limits#update` | `routes.rb:126` | parameter-compatible |
| PUT | `/api/v1/accounts/:account_id/dashboard_apps/:id` | `/api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id` | `api/v1/accounts/dashboard_apps#update` | `routes.rb:130` | parameter-compatible |
## Exact Critical Routes
@@ -77,6 +73,7 @@ These routes exist with equivalent method and path shape but different parameter
| DELETE | `/api/v1/accounts/:account_id/conversations/:conversation_id/participants` | `/api/v1/accounts/:account_id/conversations/:conversation_id/participants` | `api/v1/accounts/conversations/participants#destroy` | `routes.rb:150` | 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/dashboard_apps/:id` | `/api/v1/accounts/:account_id/dashboard_apps/:id` | `api/v1/accounts/dashboard_apps#destroy` | `routes.rb:130` | 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 |
@@ -174,6 +171,7 @@ These routes exist with equivalent method and path shape but different parameter
| 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/dashboard_apps` | `/api/v1/accounts/:account_id/dashboard_apps` | `api/v1/accounts/dashboard_apps#index` | `routes.rb:130` | exact |
| GET | `/api/v1/accounts/:account_id/dashboard_apps/:id` | `/api/v1/accounts/:account_id/dashboard_apps/:id` | `api/v1/accounts/dashboard_apps#show` | `routes.rb:130` | 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 |
@@ -279,6 +277,7 @@ These routes exist with equivalent method and path shape but different parameter
| PATCH | `/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 |
| PATCH | `/api/v1/accounts/:account_id/conversations/:conversation_id/draft_messages` | `/api/v1/accounts/:account_id/conversations/:conversation_id/draft_messages` | `api/v1/accounts/conversations/draft_messages#update` | `routes.rb:152` | exact |
| PATCH | `/api/v1/accounts/:account_id/conversations/:conversation_id/participants` | `/api/v1/accounts/:account_id/conversations/:conversation_id/participants` | `api/v1/accounts/conversations/participants#update` | `routes.rb:150` | exact |
| PATCH | `/api/v1/accounts/:account_id/dashboard_apps/:id` | `/api/v1/accounts/:account_id/dashboard_apps/:id` | `api/v1/accounts/dashboard_apps#update` | `routes.rb:130` | 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/integrations/hooks/:id` | `/api/v1/accounts/:account_id/integrations/hooks/:id` | `api/v1/accounts/integrations/hooks#update` | `routes.rb:346` | exact |
| PATCH | `/api/v1/accounts/:account_id/integrations/slack` | `/api/v1/accounts/:account_id/integrations/slack` | `api/v1/accounts/integrations/slack#update` | `routes.rb:350` | exact |
@@ -453,6 +452,7 @@ These routes exist with equivalent method and path shape but different parameter
| PUT | `/api/v1/accounts/:account_id/conversations/:conversation_id/participants` | `/api/v1/accounts/:account_id/conversations/:conversation_id/participants` | `api/v1/accounts/conversations/participants#update` | `routes.rb:150` | 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/dashboard_apps/:id` | `/api/v1/accounts/:account_id/dashboard_apps/:id` | `api/v1/accounts/dashboard_apps#update` | `routes.rb:130` | 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/integrations/hooks/:id` | `/api/v1/accounts/:account_id/integrations/hooks/:id` | `api/v1/accounts/integrations/hooks#update` | `routes.rb:346` | exact |
| PUT | `/api/v1/accounts/:account_id/integrations/slack` | `/api/v1/accounts/:account_id/integrations/slack` | `api/v1/accounts/integrations/slack#update` | `routes.rb:350` | exact |
@@ -70,9 +70,9 @@ func (h *DashboardAppHandler) Create(c *gin.Context) {
}
// Get retrieves a dashboard app by ID.
// GET /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id
// GET /api/v1/accounts/:account_id/dashboard_apps/:id
func (h *DashboardAppHandler) Get(c *gin.Context) {
id, err := parseUintParam(c, "dashboard_app_id")
id, err := parseDashboardAppIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id")
return
@@ -95,10 +95,10 @@ func (h *DashboardAppHandler) Get(c *gin.Context) {
}
// Update modifies an existing dashboard app.
// PUT /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id
// PUT /api/v1/accounts/:account_id/dashboard_apps/:id
// Chatwoot: @dashboard_app.update!(permitted_payload)
func (h *DashboardAppHandler) Update(c *gin.Context) {
id, err := parseUintParam(c, "dashboard_app_id")
id, err := parseDashboardAppIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id")
return
@@ -127,10 +127,10 @@ func (h *DashboardAppHandler) Update(c *gin.Context) {
}
// Delete removes a dashboard app.
// DELETE /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id
// DELETE /api/v1/accounts/:account_id/dashboard_apps/:id
// Chatwoot: @dashboard_app.destroy!; head :no_content (204)
func (h *DashboardAppHandler) Delete(c *gin.Context) {
id, err := parseUintParam(c, "dashboard_app_id")
id, err := parseDashboardAppIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id")
return
@@ -155,10 +155,10 @@ func (h *DashboardAppHandler) Delete(c *gin.Context) {
response.NoContent(c)
}
// Patch partially updates a dashboard app (GoChat extension — Chatwoot has no PATCH).
// PATCH /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id
// Patch partially updates a dashboard app through Rails resource update.
// PATCH /api/v1/accounts/:account_id/dashboard_apps/:id
func (h *DashboardAppHandler) Patch(c *gin.Context) {
id, err := parseUintParam(c, "dashboard_app_id")
id, err := parseDashboardAppIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id")
return
@@ -230,6 +230,13 @@ func bindDashboardAppCreate(c *gin.Context) (*service.CreateDashboardAppRequest,
return &req, nil
}
func parseDashboardAppIDParam(c *gin.Context) (uint, error) {
if c.Param("id") != "" {
return parseUintParam(c, "id")
}
return parseUintParam(c, "dashboard_app_id")
}
func bindDashboardAppUpdate(c *gin.Context) (*service.UpdateDashboardAppRequest, error) {
var raw map[string]json.RawMessage
if err := c.ShouldBindJSON(&raw); err != nil {
@@ -294,9 +301,9 @@ func (h *DashboardAppHandler) Search(c *gin.Context) {
}
// GetWidgets returns all widgets in a dashboard app's content.
// GET /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets
// GET /api/v1/accounts/:account_id/dashboard_apps/:id/widgets
func (h *DashboardAppHandler) GetWidgets(c *gin.Context) {
id, err := parseUintParam(c, "dashboard_app_id")
id, err := parseDashboardAppIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id")
return
@@ -313,9 +320,9 @@ func (h *DashboardAppHandler) GetWidgets(c *gin.Context) {
}
// AddWidget adds a widget to a dashboard app's content array.
// POST /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets
// POST /api/v1/accounts/:account_id/dashboard_apps/:id/widgets
func (h *DashboardAppHandler) AddWidget(c *gin.Context) {
id, err := parseUintParam(c, "dashboard_app_id")
id, err := parseDashboardAppIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id")
return
@@ -338,9 +345,9 @@ func (h *DashboardAppHandler) AddWidget(c *gin.Context) {
}
// RemoveWidget removes a widget from a dashboard app's content array by index.
// DELETE /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets/:widget_index
// DELETE /api/v1/accounts/:account_id/dashboard_apps/:id/widgets/:widget_index
func (h *DashboardAppHandler) RemoveWidget(c *gin.Context) {
id, err := parseUintParam(c, "dashboard_app_id")
id, err := parseDashboardAppIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id")
return
@@ -363,10 +370,10 @@ func (h *DashboardAppHandler) RemoveWidget(c *gin.Context) {
}
// UpdateWidget updates a widget in a dashboard app's content array by index.
// PUT /api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id/widgets/:widget_index
// PUT /api/v1/accounts/:account_id/dashboard_apps/:id/widgets/:widget_index
// GoChat extension — Chatwoot has no widget sub-resource CRUD.
func (h *DashboardAppHandler) UpdateWidget(c *gin.Context) {
id, err := parseUintParam(c, "dashboard_app_id")
id, err := parseDashboardAppIDParam(c)
if err != nil {
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid dashboard_app_id")
return
@@ -137,7 +137,7 @@ func (s *DashboardAppHandlerTestSuite) TestCreate_Success() {
func (s *DashboardAppHandlerTestSuite) TestGet_Success() {
r := gin.New()
r.GET("/api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id", s.handler.Get)
r.GET("/api/v1/accounts/:account_id/dashboard_apps/:id", s.handler.Get)
// Seed via service (same DB path as handler)
seedApp, err := s.handler.svc.Create(context.Background(), s.account.ID, &s.user.ID, &service.CreateDashboardAppRequest{
@@ -163,7 +163,7 @@ func (s *DashboardAppHandlerTestSuite) TestGet_Success() {
func (s *DashboardAppHandlerTestSuite) TestPatch_RawPayloadAndAccountScope() {
r := gin.New()
r.PATCH("/api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id", s.handler.Patch)
r.PATCH("/api/v1/accounts/:account_id/dashboard_apps/:id", s.handler.Patch)
seedApp, err := s.handler.svc.Create(context.Background(), s.account.ID, &s.user.ID, &service.CreateDashboardAppRequest{
Title: "Before",
@@ -192,7 +192,7 @@ func (s *DashboardAppHandlerTestSuite) TestPatch_RawPayloadAndAccountScope() {
func (s *DashboardAppHandlerTestSuite) TestDelete_Success() {
r := gin.New()
r.DELETE("/api/v1/accounts/:account_id/dashboard_apps/:dashboard_app_id", s.handler.Delete)
r.DELETE("/api/v1/accounts/:account_id/dashboard_apps/:id", s.handler.Delete)
// Seed via service
seedApp, err := s.handler.svc.Create(context.Background(), s.account.ID, &s.user.ID, &service.CreateDashboardAppRequest{
+8 -8
View File
@@ -1490,15 +1490,15 @@ func registerV1Routes(g *gin.RouterGroup, h *Handlers) {
dashboardApps.GET("", h.DashboardApp.List)
dashboardApps.POST("", h.DashboardApp.Create)
dashboardApps.GET("/search", h.DashboardApp.Search)
dashboardApps.GET("/:dashboard_app_id", h.DashboardApp.Get)
dashboardApps.PUT("/:dashboard_app_id", h.DashboardApp.Update)
dashboardApps.PATCH("/:dashboard_app_id", h.DashboardApp.Patch)
dashboardApps.DELETE("/:dashboard_app_id", h.DashboardApp.Delete)
dashboardApps.GET("/:id", h.DashboardApp.Get)
dashboardApps.PUT("/:id", h.DashboardApp.Update)
dashboardApps.PATCH("/:id", h.DashboardApp.Patch)
dashboardApps.DELETE("/:id", h.DashboardApp.Delete)
// Widget management (CRUD on content jsonb array)
dashboardApps.GET("/:dashboard_app_id/widgets", h.DashboardApp.GetWidgets)
dashboardApps.POST("/:dashboard_app_id/widgets", h.DashboardApp.AddWidget)
dashboardApps.PUT("/:dashboard_app_id/widgets/:widget_index", h.DashboardApp.UpdateWidget)
dashboardApps.DELETE("/:dashboard_app_id/widgets/:widget_index", h.DashboardApp.RemoveWidget)
dashboardApps.GET("/:id/widgets", h.DashboardApp.GetWidgets)
dashboardApps.POST("/:id/widgets", h.DashboardApp.AddWidget)
dashboardApps.PUT("/:id/widgets/:widget_index", h.DashboardApp.UpdateWidget)
dashboardApps.DELETE("/:id/widgets/:widget_index", h.DashboardApp.RemoveWidget)
}
// Knowledge Base / Help Center routes (M9)