From f4ad2fb3c6b36f10325d2969569f1964fe895ed2 Mon Sep 17 00:00:00 2001 From: Rogee Date: Sun, 7 Jun 2026 03:40:17 +0800 Subject: [PATCH] feat(conversations): align search message types --- docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md | 12 ++++++++---- .../handler/api/v1/conversation_handler_crud_test.go | 1 + internal/handler/api/v1/conversation_serializer.go | 4 ++-- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md b/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md index 2d19847e..4ec34671 100644 --- a/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md +++ b/docs/CHATWOOT_PARITY_DEVELOPMENT_PLAN.md @@ -49,11 +49,11 @@ Hermes task landing checklist: ## Current Baseline -- Current tracking checkpoint: 2026-06-07 P3.59 conversation search finder parity, prepared as `feat(conversations): align search finder payload`. -- Latest implementation checkpoint: this checkpoint, prepared as `feat(conversations): align search finder payload`. -- Latest documentation/tooling checkpoint: this tracker update records Chatwoot `ConversationsController#search`, `ConversationFinder#perform`, and `search.json.jbuilder` behavior for the reused search route. No route artifacts change. +- Current tracking checkpoint: 2026-06-07 P3.60 conversation search message-type parity, prepared as `feat(conversations): align search message types`. +- Latest implementation checkpoint: this checkpoint, prepared as `feat(conversations): align search message types`. +- Latest documentation/tooling checkpoint: this tracker update records Chatwoot search model serializer `message_type_before_type_cast` behavior for conversation search results. No route artifacts change. - 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: conversation search now routes GET `/conversations/search` through the ConversationFinder-style path too, accepts missing `q` like the reference action, returns the Chatwoot root `{ meta, payload }` search shape without the local `data` wrapper or `assigned_count`, and serializes search-specific conversation contact/inbox/message fields. P3.58 index finder parity, P3.57/P3.56/P3.55 conversation meta parity, P3.54 unread-count parity, P5.8b contact import DataImportJob parity, P3.53 first-response distribution parity, and prior report/profile/channel/contact checkpoints remain implemented. Live API/browser/enterprise smoke still needs the full PostgreSQL/Redis/Meilisearch/GoChat/Vite/Chrome stack. +- Worktree status at this implementation checkpoint: conversation search messages now serialize `message_type` as Chatwoot enum integers via `message_type_before_type_cast` semantics instead of GoChat's local string enum. P3.59 search finder payload parity, P3.58 index finder parity, P3.57/P3.56/P3.55 conversation meta parity, P3.54 unread-count parity, P5.8b contact import DataImportJob parity, P3.53 first-response distribution parity, and prior report/profile/channel/contact checkpoints remain implemented. 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 deeper report data-source/metric-builder drift or 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. @@ -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.60 conversation search message-type parity | Implemented for reused dashboard conversation search results: GoChat now follows inspected `api/v1/models/_conversation.json.jbuilder` by serializing each search result message `message_type` as Chatwoot enum integers (`incoming=0`, `outgoing=1`, `activity=2`, `template=3`) instead of local string values. | Keep in Review; reopen from B12 conversation search smoke or fresh reference evidence for sender-name serialization, message ordering, or additional search model serializer fields. | Focused conversation search handler test, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. | | 0 | P3.59 conversation search finder parity | Implemented for reused dashboard conversation search calls: GET `/conversations/search` now follows inspected Chatwoot `ConversationsController#search`, `ConversationFinder#perform`, and `search.json.jbuilder`; it uses the finder filters/counts, accepts missing `q`, returns root `{ meta, payload }`, omits `assigned_count`, and emits search-specific contact/inbox/message payloads. | Keep in Review; reopen from B12 conversation search smoke or fresh reference evidence for exact model search serializer drift, PostgreSQL `ILIKE` case-folding drift, duplicate message join count drift, or sender-name serialization depth. | Focused conversation search handler tests, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. | | 0 | P3.58 conversation index finder parity | Implemented for reused dashboard conversation list calls: GET `/conversations` now follows inspected Chatwoot `ConversationsController#index` and `ConversationFinder#perform` for the frontend query params, including default-open status, status skip while `q` is present, pre-assignee meta counts, post-count `assignee_type` payload filtering, labels/source/query filters, and finder sort/pagination behavior. | Keep in Review; reopen from B12 conversation list smoke or fresh reference evidence for full `Conversations::PermissionFilterService` custom-role depth, PostgreSQL `ILIKE` case-folding drift, duplicate message join count drift, or custom-filter POST `/conversations/filter` behavior. | Focused repository/list handler tests, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. | | 0 | P3.57 conversation meta query/status parity | Implemented for reused conversation sidebar message search counts: GoChat now follows Chatwoot `ConversationFinder#set_up` by skipping status filtering whenever `q` is present, including explicit `status=open`, so query counts span all statuses before the raw `{ meta }` payload is returned. | Keep in Review; reopen from B12 conversation search smoke or fresh reference evidence for PostgreSQL `ILIKE` case-folding drift, duplicate message join count drift, or full conversation list `perform` query/status drift beyond meta. | Focused repository/handler meta query/status tests, full `go test ./...`, and `git diff --check` must pass. No route artifacts change. | @@ -222,6 +223,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.60 conversation search message-type parity | `internal/handler/api/v1/conversation_serializer.go`, conversation search handler tests | `reference/chatwoot/app/views/api/v1/models/_conversation.json.jbuilder`, `reference/chatwoot/app/views/api/v1/accounts/conversations/search.json.jbuilder` | Conversation search result messages now serialize `message_type` using Chatwoot `message_type_before_type_cast` integer semantics, sharing the existing message-type mapping used by normal message serializers. | Review by `feat(conversations): align search message types`; focused search handler tests assert numeric `message_type`; full `go test ./...` and `git diff --check` must pass. No route artifacts change. | | P3.59 conversation search finder parity | `internal/handler/api/v1/conversation_handler.go`, `internal/handler/api/v1/conversation_serializer.go`, `internal/service/conversation_service.go`, `internal/repository/conversation_repo.go`, conversation search handler tests | `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb#search`, `reference/chatwoot/app/finders/conversation_finder.rb`, `reference/chatwoot/app/views/api/v1/accounts/conversations/search.json.jbuilder`, `reference/chatwoot/app/views/api/v1/models/_conversation.json.jbuilder`, `reference/chatwoot/app/javascript/dashboard/api/inbox/conversation.js` | GET `/api/v1/accounts/:account_id/conversations/search` now uses the same finder path as index/search in Chatwoot, accepts missing `q`, preserves query/status skip semantics, and returns the root search payload shape `{ meta: { mine_count, unassigned_count, all_count }, payload }` with search-specific contact/inbox/messages fields instead of the local list/data envelope. | Review by `feat(conversations): align search finder payload`; focused search handler tests cover root payload shape, omitted `assigned_count`, missing-`q` behavior, all-status query search, and search serializer fields; full `go test ./...` and `git diff --check` must pass. No route artifacts change. | | P3.58 conversation index finder parity | `internal/handler/api/v1/conversation_handler.go`, `internal/service/conversation_service.go`, `internal/repository/conversation_repo.go`, conversation CRUD/repository tests | `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb#index`, `reference/chatwoot/app/finders/conversation_finder.rb`, `reference/chatwoot/app/views/api/v1/accounts/conversations/index.json.jbuilder`, `reference/chatwoot/app/javascript/dashboard/api/inbox/conversation.js` | GET `/api/v1/accounts/:account_id/conversations` now uses a ConversationFinder-style path for reused `ConversationApi.get` params: default status is open, `q` skips status filtering, counts are computed before `assignee_type`, payload is filtered by `assignee_type` after counts, labels/source/query filters share the meta base, and finder sort/pagination behavior replaces the old status-only list path. | Review by `feat(conversations): align index finder filters`; focused repository/list handler tests cover default-open, query/status skip, labels, pre-assignee counts, and post-count payload filtering; full `go test ./...` and `git diff --check` must pass. No route artifacts change. | | P3.57 conversation meta query/status parity | `internal/repository/conversation_repo.go`, `internal/service/conversation_service.go`, `internal/handler/api/v1/conversation_handler.go`, conversation repository/handler tests | `reference/chatwoot/app/finders/conversation_finder.rb`, `reference/chatwoot/app/controllers/api/v1/accounts/conversations_controller.rb#meta`, `reference/chatwoot/app/views/api/v1/accounts/conversations/meta.json.jbuilder`, `reference/chatwoot/app/javascript/dashboard/api/inbox/conversation.js` | Conversation meta now mirrors `ConversationFinder#set_up`: `filter_by_status` is skipped whenever `params[:q]` is present, so meta-only message-search counts include matching conversations across all statuses while preserving `q` incoming/outgoing message filtering and `source_id` filtering from P3.56. | Review by `feat(conversations): align meta query status`; focused repository/handler tests cover query counts including resolved/pending conversations despite explicit `status=open`; full `go test ./...` and `git diff --check` must pass. No route artifacts change. | @@ -306,6 +308,7 @@ This ledger records the committed parity checkpoints that future slices should b | Commit | Scope | Verification summary | Follow-up state | | --- | --- | --- | --- | +| `feat(conversations): align search message types` | Advances P3.60 with Chatwoot conversation search message serializer parity. Search result messages now emit integer `message_type` values matching `message_type_before_type_cast`, instead of GoChat string enums, while preserving the P3.59 root search payload and finder behavior. | `go test ./internal/handler/api/v1 -run 'ConversationCrudTestSuite/TestSearch' -count=1`; `go test ./internal/repository ./internal/service ./internal/handler/api/v1 -run 'ConversationRepo_(GetMeta\|ListForFinder)\|Conversation.*(Meta\|List\|Search)\|ConversationCrudTestSuite/Test(Search\|List)' -count=1`; `go test ./...`; `git diff --check`. No route artifacts change. | Move P3.60 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | | `feat(conversations): align search finder payload` | Advances P3.59 with Chatwoot conversation search finder parity. GET `/conversations/search` now calls the same ConversationFinder-style service path as index, accepts missing `q`, preserves query/status-skip semantics, and returns the root search Jbuilder payload with `mine_count`, `unassigned_count`, `all_count`, and search-specific conversation contact/inbox/message fields. | `go test ./internal/handler/api/v1 -run 'ConversationCrudTestSuite/TestSearch' -count=1`; `go test ./internal/repository ./internal/service ./internal/handler/api/v1 -run 'ConversationRepo_(GetMeta\|ListForFinder)\|Conversation.*(Meta\|List\|Search)\|ConversationCrudTestSuite/Test(Search\|List)' -count=1`; `go test ./...`; `git diff --check`. No route artifacts change. | Move P3.59 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | | `feat(conversations): align index finder filters` | Advances P3.58 with Chatwoot conversation index finder parity. Reused frontend GET `/conversations` query params now flow through a ConversationFinder-style service/repository path: default-open status, status skip while `q` is present, labels/source/query filters, pre-assignee meta counts, post-count `assignee_type` payload filtering, and finder sort/pagination replace the previous status-only list behavior. | `go test ./internal/repository ./internal/handler/api/v1 -run 'ConversationRepo_(GetMeta\|ListForFinder)\|ConversationCrudTestSuite/TestList' -count=1`; `go test ./internal/repository ./internal/service ./internal/handler/api/v1 -run 'Conversation.*(Meta\|List)\|ConversationRepo\|ConversationService\|ConversationCrudTestSuite' -count=1`; `go test ./...`; `git diff --check`. No route artifacts change. | Move P3.58 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | | `feat(conversations): align meta query status` | Advances P3.57 with Chatwoot conversation meta query/status parity. `GET /conversations/meta` now follows `ConversationFinder#set_up` by skipping status filtering whenever `q` is present, even if the frontend also sends `status`, so sidebar message-search counts include matching conversations across all statuses before returning raw `{ meta }`. | `go test ./internal/repository ./internal/handler/api/v1 -run 'ConversationRepo_GetMeta\|ConversationHandlerTestSuite/TestMeta' -count=1`; `go test ./internal/repository ./internal/service ./internal/handler/api/v1 -run 'Conversation.*Meta\|ConversationRepo\|ConversationService\|ConversationHandlerTestSuite' -count=1`; `go test ./...`; `git diff --check`. No route artifacts change. | Move P3.57 to Review; continue Phase 2/3 drift audit, Phase 6 placeholder audit, or B12 live smoke. | @@ -2617,3 +2620,4 @@ Verification milestone gates: - 2026-06-07: P3.57 conversation meta query/status checkpoint prepared as `feat(conversations): align meta query status`; audited Chatwoot `ConversationFinder#set_up`, specifically `filter_by_status unless params[:q]`, plus the reused conversation meta route. GoChat meta counts now skip status filtering whenever `q` is present, including explicit `status=open`, so message-search sidebar counts include matching conversations across all statuses while preserving raw `{ meta }`. Focused repository/handler tests passed; full `go test ./...` and `git diff --check` passed. No route artifacts change. - 2026-06-07: P3.58 conversation index finder checkpoint prepared as `feat(conversations): align index finder filters`; audited Chatwoot `ConversationsController#index`, `ConversationFinder#perform`, `index.json.jbuilder`, and reused dashboard `ConversationApi.get`. GoChat GET `/conversations` now uses the finder-style filtered base instead of the old status-only list path: default-open status, `q` status-skip, labels/source/query filters, pre-assignee meta counts, post-count `assignee_type` payload filtering, and finder sort/pagination are covered. Focused repository/list handler tests passed; full `go test ./...` and `git diff --check` passed. No route artifacts change. - 2026-06-07: P3.59 conversation search finder checkpoint prepared as `feat(conversations): align search finder payload`; audited Chatwoot `ConversationsController#search`, `ConversationFinder#perform`, `search.json.jbuilder`, `_conversation` search model serializer, and reused dashboard `ConversationApi.search`. GoChat GET `/conversations/search` now uses the finder-style path, accepts missing `q`, keeps query/status-skip behavior, returns root `{ meta, payload }` without `assigned_count` or `data`, and serializes search-specific contact/inbox/messages fields. Focused search/list handler and finder tests passed; full `go test ./...` and `git diff --check` passed. No route artifacts change. +- 2026-06-07: P3.60 conversation search message-type checkpoint prepared as `feat(conversations): align search message types`; audited Chatwoot search `_conversation` model serializer and its `message_type_before_type_cast` field. GoChat conversation search result messages now emit integer message types (`incoming=0`, `outgoing=1`, `activity=2`, `template=3`) instead of local string enums. Focused search handler tests passed; full `go test ./...` and `git diff --check` passed. No route artifacts change. diff --git a/internal/handler/api/v1/conversation_handler_crud_test.go b/internal/handler/api/v1/conversation_handler_crud_test.go index 1295263d..7a95148c 100644 --- a/internal/handler/api/v1/conversation_handler_crud_test.go +++ b/internal/handler/api/v1/conversation_handler_crud_test.go @@ -935,6 +935,7 @@ func (s *ConversationCrudTestSuite) TestSearch_Success() { assert.Equal(s.T(), s.testContact.Name, resp.Payload[0].Contact["name"]) assert.Equal(s.T(), s.testInbox.Name, resp.Payload[0].Inbox["name"]) assert.NotEmpty(s.T(), resp.Payload[0].Messages) + assert.Equal(s.T(), float64(0), resp.Payload[0].Messages[0]["message_type"]) } func (s *ConversationCrudTestSuite) TestSearch_InvalidAccountID() { diff --git a/internal/handler/api/v1/conversation_serializer.go b/internal/handler/api/v1/conversation_serializer.go index 9bc9ccfc..935a0ca1 100644 --- a/internal/handler/api/v1/conversation_serializer.go +++ b/internal/handler/api/v1/conversation_serializer.go @@ -94,7 +94,7 @@ type chatwootConversationSearchMessage struct { Content string `json:"content"` ID uint `json:"id"` SenderName string `json:"sender_name,omitempty"` - MessageType string `json:"message_type"` + MessageType int `json:"message_type"` CreatedAt int64 `json:"created_at"` } @@ -189,7 +189,7 @@ func serializeConversationSearch(ctx context.Context, db *gorm.DB, conversation payload.Messages = append(payload.Messages, chatwootConversationSearchMessage{ Content: messages[i].Content, ID: messages[i].ID, - MessageType: messages[i].MessageType, + MessageType: messageTypeValue(messages[i].MessageType), CreatedAt: messages[i].CreatedAt.Unix(), }) }