Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
216 commits
Select commit Hold shift + click to select a range
1723aa4
feat: add group and conversation types to contacts and conversations,…
CayoPOliveira Feb 5, 2026
65cfeb6
chore: add factory and specs for conversation group member model
CayoPOliveira Feb 5, 2026
3da2a7a
chore: add group type checks and associations for contacts and conver…
CayoPOliveira Feb 5, 2026
0999a7f
Merge branch 'main' into Cayo-Oliveira/CU-86af00yvg/2-Backend-Models-…
CayoPOliveira Feb 6, 2026
61e7d64
refactor: remove scopes from ConversationGroupMember model
CayoPOliveira Feb 10, 2026
919f019
refactor: remove scopes from ConversationGroupMember model specs
CayoPOliveira Feb 10, 2026
1cc8158
refactor: enhance conversation type migration with concurrent indexing
CayoPOliveira Feb 10, 2026
190a09c
feat: add is_active index and scopes to ConversationGroupMember model
CayoPOliveira Feb 11, 2026
39ed63d
feat: implement GroupConversationHandler for managing group conversat…
CayoPOliveira Feb 11, 2026
5c73e1f
feat: add group_type attribute to contact creation
CayoPOliveira Feb 12, 2026
a1041aa
fix: update WHATSAPP_CHANNEL_REGEX to allow up to 20 digits to handle…
CayoPOliveira Feb 12, 2026
dc5f8dd
feat: handle group JID format in remote_jid method
CayoPOliveira Feb 12, 2026
da82216
chore: update group contact info when finding or creating group contact
CayoPOliveira Feb 12, 2026
2c6319e
chore: refactor and implement contact message handling and message cr…
CayoPOliveira Feb 12, 2026
d150a07
feat: implement group message handling and metadata fetching in Whats…
CayoPOliveira Feb 12, 2026
b45b0cb
chore: add spec for group type handling in contact creation for indiv…
CayoPOliveira Feb 12, 2026
3015d56
chore: add specs for test scopes in conversation group members
CayoPOliveira Feb 12, 2026
7fa221b
chore: update documentation for sender phone extraction in group conv…
CayoPOliveira Feb 12, 2026
85b91d8
chore: move GroupConversationHandler concern to correct dir
CayoPOliveira Feb 12, 2026
fea412f
chore: implement specs for recipient_id handling to individual and gr…
CayoPOliveira Feb 12, 2026
9401b0d
chore: add group message handling specs for incoming messages
CayoPOliveira Feb 12, 2026
d703338
chore: enhance group message handling to prevent race conditions
CayoPOliveira Feb 13, 2026
00863c0
chore: add group_metadata method to with error handling
CayoPOliveira Feb 13, 2026
889f559
chore: add test for sending messages to group recipients in WhatsappB…
CayoPOliveira Feb 13, 2026
f0b3f59
chore: raise error for unsuccessful response in group_metadata method
CayoPOliveira Feb 13, 2026
d35e629
chore: adds tests for group metadata retrieval and error handling
CayoPOliveira Feb 13, 2026
f923143
chore: refactor build_sender_contact_attributes to avoid double call …
CayoPOliveira Feb 13, 2026
dfd2b12
chore: update error handling for attachment download failure in messa…
CayoPOliveira Feb 13, 2026
376a459
chore: optimize update_contact_info method to use compact hash for up…
CayoPOliveira Feb 13, 2026
ac1f946
chore: simplify find_or_create_sender_contact method return values
CayoPOliveira Feb 19, 2026
b611f0c
chore: rename group and individual contact message handlers
CayoPOliveira Feb 19, 2026
afc4c47
chore: remove pointless comments from group contact message handler m…
CayoPOliveira Feb 19, 2026
a6feebd
chore: refine sender JID extraction logic to remove unnecessary checks
CayoPOliveira Feb 19, 2026
e117933
chore: remove phone number in spec for group contact attributes
CayoPOliveira Feb 19, 2026
2bba624
Merge branch 'main' into Cayo-Oliveira/CU-86af00yvg/2-Backend-Models-…
CayoPOliveira Feb 19, 2026
c7648b8
Merge branch 'Cayo-Oliveira/CU-86af00yvg/2-Backend-Models-Main-PR' in…
CayoPOliveira Feb 19, 2026
bd8eb28
chore: implement sync_group route
CayoPOliveira Feb 20, 2026
1204541
chore: implement get group_members route
CayoPOliveira Feb 20, 2026
bab1e5c
fix: sync_group participants creation handling
CayoPOliveira Feb 20, 2026
a16d384
chore: update contact avatar handling in group message processing
CayoPOliveira Feb 23, 2026
cc98ef3
chore: move sync_group functionality for conversation model
CayoPOliveira Feb 23, 2026
d06285c
feat: add sync_group action to ConversationsController and route
CayoPOliveira Feb 23, 2026
eb6d7db
fix: set contact name to phone in group message processing
CayoPOliveira Feb 23, 2026
d0c5ffb
chore: refine group member retrieval logic in sync_group service and …
CayoPOliveira Feb 23, 2026
d3f9379
feat: implement group participants update handling
CayoPOliveira Feb 24, 2026
cbc83d2
feat: implement group updates handling and localization for group act…
CayoPOliveira Feb 24, 2026
014c077
chore: add handling for group membership requests and icon changes
CayoPOliveira Feb 24, 2026
007656c
chore: add authorization for sync_group action in ContactsController
CayoPOliveira Feb 24, 2026
678b52a
chore: add sync_group endpoint specs for contact management
CayoPOliveira Feb 24, 2026
67f88d0
chore: add authorization for sync_group action in ConversationsContro…
CayoPOliveira Feb 24, 2026
5e253b8
chore: add specs for sync_group endpoint in ConversationsController
CayoPOliveira Feb 24, 2026
86178f1
chore: refactor index action in GroupMembersController for improved c…
CayoPOliveira Feb 24, 2026
5c5b11e
chore: add request specs for group_members endpoint in ContactsContro…
CayoPOliveira Feb 24, 2026
74ba3aa
chore: add specs for sync_group method in Conversation model
CayoPOliveira Feb 24, 2026
23642e9
chore: add specs for sync_group method in Channel::Whatsapp model
CayoPOliveira Feb 24, 2026
5ed3683
chore: remove comments in find_or_create_group_conversation method
CayoPOliveira Feb 24, 2026
ae239f1
chore: add specs for Contacts::SyncGroupService to validate group con…
CayoPOliveira Feb 24, 2026
057f88c
chore: add specs for Whatsapp::BaileysHandlers::GroupsUpdate to valid…
CayoPOliveira Feb 25, 2026
4b020b5
chore: add specs for Whatsapp::BaileysHandlers::GroupParticipantsUpda…
CayoPOliveira Feb 25, 2026
504e1e3
chore: add fallback for identifier when contact has no phone_number i…
CayoPOliveira Feb 25, 2026
953f187
chore: add specs for group membership request and icon change handlin…
CayoPOliveira Feb 25, 2026
097bcca
chore: add specs for sync_group method to handle group metadata and p…
CayoPOliveira Feb 25, 2026
b06121b
chore: update sync_group method to retrieve group members and adjust …
CayoPOliveira Feb 25, 2026
f5067fd
chore: update conversation query to filter by group type in GroupMemb…
CayoPOliveira Feb 25, 2026
778b72f
chore: update conversation creation in group_members_controller_spec …
CayoPOliveira Feb 25, 2026
2d8ccfc
chore: update find_or_create_group_conversation to include pending co…
CayoPOliveira Feb 25, 2026
c78fdc9
chore: refactor sync_group method and enhance specs for group convers…
CayoPOliveira Feb 25, 2026
4963dd9
feat: add GroupEventHelper module for managing group activities and c…
CayoPOliveira Feb 25, 2026
2bcf1a3
chore: refactor group contact inbox and conversation creation methods…
CayoPOliveira Feb 25, 2026
16b50ce
chore: remove unnecessary check for blank participant contacts in syn…
CayoPOliveira Feb 25, 2026
cb72db7
feat: implement message receipt update handling for WhatsApp integration
CayoPOliveira Feb 26, 2026
26a8196
chore: resolve rubocop rule for update_last_seen_at method
CayoPOliveira Feb 26, 2026
fa34ff3
chore: update swagger with endpoints for syncing group information an…
CayoPOliveira Feb 26, 2026
049239d
chore: integrate Contacts::SyncGroupService in group members controll…
CayoPOliveira Feb 26, 2026
e8870a6
chore: include participant information in reaction and quoted message…
CayoPOliveira Feb 26, 2026
7613493
chore: enhance whatsapp_baileys_service with participant handling for…
CayoPOliveira Feb 27, 2026
efd953e
feat: add skill for writing RSpec tests in the project
CayoPOliveira Feb 27, 2026
44d772c
fix: update recipient_id logic to directly use contact identifier for…
CayoPOliveira Feb 27, 2026
b17e9ec
chore: implement group stub message handling for membership requests …
CayoPOliveira Feb 27, 2026
b9494e8
fix: update whatsapp inbox source_id validation regex spec
CayoPOliveira Feb 27, 2026
ea3a94f
chore: fix spec for contact syncing group
CayoPOliveira Feb 27, 2026
d5cb1a9
Merge branch 'Cayo-Oliveira/CU-86af01932/4-Backend-Gerenciamento-dos-…
CayoPOliveira Feb 27, 2026
16b267f
chore: remove readTimestamp handling and related tests for message re…
CayoPOliveira Feb 27, 2026
979264e
Cayo oliveira/cu 86af01932/4 backend gerenciamento dos grupos (#221)
CayoPOliveira Feb 27, 2026
462bbfa
fix: optimize update_last_seen_at method to use update_columns
CayoPOliveira Feb 27, 2026
62c7ac3
Merge branch 'Cayo-Oliveira/CU-86af00yvg/2-Backend-Models-Main-PR' in…
CayoPOliveira Feb 27, 2026
569c780
Merge pull request #227 from fazer-ai/Cayo-Oliveira/CU-86af019hb/5-Ba…
gabrieljablonski Feb 27, 2026
952d53c
feat: Implement full frontend and backend support for group conversat…
gabrieljablonski Feb 27, 2026
e16a7ae
feat: [US-001] - Rename conversation_type to group_type on conversations
gabrieljablonski Feb 27, 2026
540c4ff
chore: mark US-001 complete, update progress log, fix rubocop annotation
gabrieljablonski Feb 27, 2026
341770e
feat: [US-002] - Serialize group_type fields in API responses
gabrieljablonski Feb 27, 2026
e02e329
feat: [US-003] - Add group_type filter to conversations index
gabrieljablonski Feb 27, 2026
ec06aa4
feat: [US-004] - Add group_type to filter_keys.yml and FilterService
gabrieljablonski Feb 27, 2026
baa3afc
feat: US-005 - Backend group creation endpoint
gabrieljablonski Feb 27, 2026
679dbd9
feat: US-006 - Backend add/remove members and role management endpoints
gabrieljablonski Feb 27, 2026
b2fd482
feat: US-007 - Backend group metadata update endpoint
gabrieljablonski Feb 27, 2026
a8b674d
feat: US-008 - Backend invite link management endpoints
gabrieljablonski Feb 27, 2026
85d4747
feat: US-009 - Backend join request management endpoints
gabrieljablonski Feb 27, 2026
327d961
feat: US-010 - Extend MentionService for contact mentions
gabrieljablonski Feb 27, 2026
6996d03
feat: US-011 - Frontend API clients for all group endpoints
gabrieljablonski Feb 27, 2026
6d73e91
feat: US-012 - Frontend Vuex store module groupMembers
gabrieljablonski Feb 27, 2026
ba932c3
feat: US-013 - Frontend i18n keys for group features
gabrieljablonski Feb 27, 2026
453f547
feat: US-014 - Frontend group_type filter in ConversationBasicFilter
gabrieljablonski Feb 27, 2026
60b3128
feat: US-013 - Frontend — i18n keys for group features (en + pt-BR)
gabrieljablonski Feb 27, 2026
dfcfe46
feat: [US-014] - Frontend — add group_type filter to ConversationBasi…
gabrieljablonski Feb 27, 2026
fc2917f
feat: US-015 - Frontend — add group_type to advanced filter system
gabrieljablonski Feb 27, 2026
1e940c9
feat: US-016 - Frontend — GroupContactInfo basic display
gabrieljablonski Feb 27, 2026
96773e6
feat: US-017 - Frontend — GroupContactInfo sync button
gabrieljablonski Feb 27, 2026
33608cf
feat: US-018 - integrate GroupContactInfo in ContactPanel
gabrieljablonski Feb 27, 2026
9250648
feat: US-019 - Frontend — group creation UI modal
gabrieljablonski Feb 27, 2026
3465ff4
feat: US-020 - Frontend — member management UI in GroupContactInfo
gabrieljablonski Feb 27, 2026
011d84e
feat: US-021 - Frontend — group metadata editing UI
gabrieljablonski Feb 27, 2026
3aa1f93
feat: US-022 - Frontend — invite link management UI
gabrieljablonski Feb 27, 2026
f410cff
feat: US-023 - Frontend — join request management UI
gabrieljablonski Feb 27, 2026
ea029ae
feat: US-024 - Frontend — group message bubbles: sender name with color
gabrieljablonski Feb 27, 2026
26fea3b
feat: US-025 - Frontend — group message bubbles: sender avatar
gabrieljablonski Feb 27, 2026
2cc44ba
feat: Add Ralph Wiggum AI agent script for managing tool execution an…
gabrieljablonski Feb 27, 2026
1f70ab5
feat: US-026 - Frontend — @mention dropdown for group conversations
gabrieljablonski Feb 27, 2026
535b7a4
feat: US-027 - Frontend — mention rendering in group message bubbles
gabrieljablonski Feb 27, 2026
7fb5d76
feat: US-028 - Frontend ActionCable handler for contact.group_synced …
gabrieljablonski Feb 27, 2026
b866424
feat: Update progress tracking for group conversations feature - mark…
gabrieljablonski Feb 27, 2026
2062c3d
fix: sender click case mismatch and filter dropdown spacing
CayoPOliveira Feb 28, 2026
46ed109
fix: four bugs found during manual testing review
CayoPOliveira Feb 28, 2026
dfa15f9
docs: update progress with bug fix learnings
CayoPOliveira Feb 28, 2026
02fcf9d
chore: implement group creation functionality in UI components
CayoPOliveira Mar 2, 2026
57ac905
chore: add copy invite link functionality and update UI components
CayoPOliveira Mar 2, 2026
0ed9ea5
feat: US-041 - Backend — ensure group_type is set on existing contact…
CayoPOliveira Mar 3, 2026
3799f35
chore: mark US-041 as complete
CayoPOliveira Mar 3, 2026
9424611
feat: US-029 - i18n keys for You badge and group settings (en + pt-BR)
CayoPOliveira Mar 3, 2026
359c32d
feat: US-030 - fix Baileys API route/method mismatches
CayoPOliveira Mar 3, 2026
b545e54
feat: US-031 - group_leave, group_setting_update, group_join_approval…
CayoPOliveira Mar 3, 2026
5d58aa1
feat: US-032 - persist group settings, invite code, and profile pictu…
CayoPOliveira Mar 3, 2026
1db18bd
feat: US-033 - GroupSettingsController with leave, update, toggle
CayoPOliveira Mar 3, 2026
9008c5f
feat: US-034 - remove inbox_contact_id from provider_config and jbuilder
CayoPOliveira Mar 3, 2026
377fe80
feat: US-035 - refactor TagGroupMembers to phone_number matching
CayoPOliveira Mar 3, 2026
1ea1fa9
feat: US-036 - remove InboxContact.vue and settings tab
CayoPOliveira Mar 3, 2026
ec3c7d4
feat: US-037 - add You badge in GroupContactInfo member list
CayoPOliveira Mar 3, 2026
f877080
feat: US-038 - fix inline edit for group name and description
CayoPOliveira Mar 3, 2026
5d8a84e
feat: US-039 - group settings section UI with toggles
CayoPOliveira Mar 3, 2026
7026005
feat: US-040 - leave group UI with confirmation and auto-resolve
CayoPOliveira Mar 3, 2026
cd17f28
feat: US-050 - Create GroupMember model and migration
CayoPOliveira Mar 3, 2026
7d3cd3d
feat: US-064 - Helper method to find channel from group contact
CayoPOliveira Mar 3, 2026
7ddd5aa
feat: US-052 - Update GroupConversationHandler to use GroupMember
CayoPOliveira Mar 3, 2026
cac19f1
feat: US-057 - Update GroupMembersController to query GroupMember
CayoPOliveira Mar 3, 2026
0f51ac2
feat: US-058 - Update GroupSettingsController to not depend on conver…
CayoPOliveira Mar 3, 2026
4cc377f
feat: US-060 - Update group_members jbuilder views
CayoPOliveira Mar 3, 2026
2979977
feat: US-059 - Remove group_members association from Conversation model
CayoPOliveira Mar 3, 2026
0772baa
feat: US-051 - Remove ConversationGroupMember model and table
CayoPOliveira Mar 3, 2026
935ccd2
chore: mark all stories complete, update progress
CayoPOliveira Mar 3, 2026
2eade24
feat(groups): real-time group panel, avatar refresh on icon change, e…
CayoPOliveira Mar 3, 2026
02a4256
fix(groups): rewrite SyncGroupService and simplify group metadata cha…
CayoPOliveira Mar 3, 2026
dcc76df
feat(groups): resolve conversations when inbox phone leaves or is rem…
CayoPOliveira Mar 3, 2026
3686373
feat(groups): add paginated member list with infinite scroll
CayoPOliveira Mar 3, 2026
31549cc
feat(groups): support Ctrl+Click on group message sender to open in n…
CayoPOliveira Mar 3, 2026
076ad01
chore(i18n): update leave group confirmation text in en and pt_BR
CayoPOliveira Mar 3, 2026
0427265
fix(groups): handle phone format differences in You badge and admin d…
CayoPOliveira Mar 3, 2026
08686c6
feat(groups): auto-sync members on mount, show existing members immed…
CayoPOliveira Mar 3, 2026
c30dcc9
fix(groups): pin own member on first page and return inbox phone in meta
CayoPOliveira Mar 3, 2026
5ca83f0
fix(groups): fix member action dropdown clipped by overflow container
CayoPOliveira Mar 3, 2026
0b2a7ca
fix(groups): keep member action dropdown visible when menu is open
CayoPOliveira Mar 3, 2026
75b95a2
fix(groups): move clickaway to member list wrapper to prevent instant…
CayoPOliveira Mar 3, 2026
fb25741
feat: add WhatsApp mention conversion (incoming + outgoing)
CayoPOliveira Mar 3, 2026
1839d5e
fix: preserve mention display text in WhatsApp renderer
CayoPOliveira Mar 3, 2026
fb3240e
feat: add @everyone mention option in group conversations
CayoPOliveira Mar 3, 2026
5a1be4f
refactor: use Switch component for group settings toggles
CayoPOliveira Mar 3, 2026
98b6db3
feat(whatsapp): add group sync status tracking (group_left, group_las…
CayoPOliveira Mar 4, 2026
579ff88
feat(whatsapp): hide group management UI when group_left is true
CayoPOliveira Mar 4, 2026
0810fa3
fix(groupMembers): include inbox phone number in group members state …
CayoPOliveira Mar 4, 2026
ed9b8fb
feat(whatsapp): wrap group settings and leave in Accordion component
CayoPOliveira Mar 4, 2026
87d443d
feat(groupMembers): handle group creator modification errors and upda…
CayoPOliveira Mar 4, 2026
254f622
feat(groupMembers): enhance invite link functionality and clean up UI…
CayoPOliveira Mar 4, 2026
d1ce873
refactor: remove sync_group functionality from conversations and rela…
CayoPOliveira Mar 4, 2026
fee1140
feat(GroupContactInfo): implement scroll-based loading for group members
CayoPOliveira Mar 4, 2026
2e179ab
docs(swagger): add group API endpoints and remove conversation sync_g…
CayoPOliveira Mar 4, 2026
9c83baa
feat(WhatsappBaileysService): enhance mention handling by replacing @…
CayoPOliveira Mar 4, 2026
865f754
feat(groups): move group sync to background job with 15-min cooldown
CayoPOliveira Mar 4, 2026
7c88704
fix: show group members list even after leaving group\n\nKeep the mem…
CayoPOliveira Mar 5, 2026
f152557
fix: disable group name/description/avatar editing when group_left is…
CayoPOliveira Mar 5, 2026
a3d62c1
fix: remove @all mention and fix Enter key in group mention dropdown\…
CayoPOliveira Mar 5, 2026
0f6ed5b
fix: address PR review feedback for group conversations
CayoPOliveira Mar 5, 2026
5a0d87a
fix: address PR #228 review feedback - strong params, guards, and saf…
CayoPOliveira Mar 5, 2026
903e772
fix: dispatch real-time events for Baileys group participant and meta…
CayoPOliveira Mar 5, 2026
cf919dd
fix: enhance member menu positioning and close behavior on sidebar sc…
CayoPOliveira Mar 5, 2026
6b20272
Merge remote-tracking branch 'origin/main' into ralph/group-conversat…
CayoPOliveira Mar 5, 2026
0e25291
feat: implement group property updates and enhance toast notifications
CayoPOliveira Mar 6, 2026
7ee5794
fix: update WhatsApp channel regex to allow optional hyphenated numbers
CayoPOliveira Mar 6, 2026
505d766
feat: implement group admin functionalities including leave, update p…
CayoPOliveira Mar 6, 2026
ac4f42d
refactor: simplify group message handling by removing metadata fetchi…
CayoPOliveira Mar 6, 2026
a6370f6
chore: remove raph files
CayoPOliveira Mar 6, 2026
9cb77a5
feat: update Portuguese translations for 'Read More' and 'Insert Read…
CayoPOliveira Mar 6, 2026
b87dc2e
feat: enhance group admin functionalities with join approval and memb…
CayoPOliveira Mar 9, 2026
1cb3148
feat: enhance group join request handling by adding removal of handle…
CayoPOliveira Mar 9, 2026
b507eef
feat: restrict message sending in announcement mode groups
CayoPOliveira Mar 9, 2026
afcee89
feat: add group sync job enqueueing and improve avatar update handling
CayoPOliveira Mar 9, 2026
391a47e
feat: add functionality to reset invite link and confirm member addit…
CayoPOliveira Mar 9, 2026
243380b
feat: update group name extraction logic to handle nil values
CayoPOliveira Mar 9, 2026
e2fefe6
feat: add inbox admin status handling and update related components
CayoPOliveira Mar 9, 2026
df97570
feat: remove group conversation resolution on leave action
CayoPOliveira Mar 9, 2026
2819158
feat: enhance group sender avatar interaction with tooltip and cursor…
CayoPOliveira Mar 9, 2026
492f288
feat: add force option to SyncGroupJob and update related specs
CayoPOliveira Mar 9, 2026
25c6eb8
feat: enhance invite link handling and avatar update logic in group c…
CayoPOliveira Mar 9, 2026
1825c59
chore: remove prd.json
CayoPOliveira Mar 9, 2026
c47055c
fix: change group sender name display from block to inline-block for …
CayoPOliveira Mar 9, 2026
cac65a0
feat: add group members loading check and fetch logic in MessagesView…
CayoPOliveira Mar 9, 2026
91d68bd
feat: allow id and firstUnreadId props to accept both Number and Stri…
CayoPOliveira Mar 9, 2026
97df6f7
feat: soft-disabled group conversations with activity tracking
gabrieljablonski Mar 12, 2026
b425495
fix: use method for groups disabled banner action to avoid window sco…
gabrieljablonski Mar 12, 2026
1c95632
fix: broadcast conversation update after groups.activity event
gabrieljablonski Mar 13, 2026
87ef75c
feat: show unread dot for soft-disabled group conversations with acti…
gabrieljablonski Mar 13, 2026
18f927c
fix: clear unread dot when agent opens soft-disabled group conversation
gabrieljablonski Mar 13, 2026
1d1e4e8
feat: group avatar upload to provider and fix icon change sync
gabrieljablonski Mar 15, 2026
eeb6a30
fix: update specs for group conversations feature changes
gabrieljablonski Mar 15, 2026
708193a
chore: add settings file for additional directories configuration
gabrieljablonski Mar 15, 2026
bb4284c
Merge branch 'main' into ralph/group-conversations-full-feature
gabrieljablonski Mar 20, 2026
4718d7b
chore: undo unrelated changes
gabrieljablonski Mar 20, 2026
8ce78cc
chore: remove planning doc, fix migration version, fix swagger param …
gabrieljablonski Mar 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"additionalDirectories": ["../baileys-api"]
}
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -293,5 +293,7 @@ AZURE_APP_SECRET=
BAILEYS_PROVIDER_DEFAULT_CLIENT_NAME=Chatwoot
BAILEYS_PROVIDER_DEFAULT_URL=http://localhost:3025
BAILEYS_PROVIDER_DEFAULT_API_KEY=
# Enable WhatsApp group conversations for Baileys provider (default: false)
BAILEYS_WHATSAPP_GROUPS_ENABLED=false

RESEND_API_KEY=
3 changes: 2 additions & 1 deletion app/builders/contact_inbox_with_contact_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,8 @@ def create_contact
email: contact_attributes[:email],
identifier: contact_attributes[:identifier],
additional_attributes: contact_attributes[:additional_attributes],
custom_attributes: contact_attributes[:custom_attributes]
custom_attributes: contact_attributes[:custom_attributes],
group_type: contact_attributes[:group_type] || :individual
)
end

Expand Down
56 changes: 56 additions & 0 deletions app/controllers/api/v1/accounts/contacts/group_admin_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
class Api::V1::Accounts::Contacts::GroupAdminController < Api::V1::Accounts::Contacts::BaseController
VALID_PROPERTIES = %w[announce restrict join_approval_mode member_add_mode].freeze

def leave
authorize @contact, :update?
channel.group_leave(@contact.identifier)
head :ok
rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
render json: { error: e.message }, status: :unprocessable_entity
end

def update
authorize @contact, :update?
property = property_params[:property]
enabled = ActiveModel::Type::Boolean.new.cast(property_params[:enabled])
return render json: { error: 'invalid_property' }, status: :unprocessable_entity unless property.in?(VALID_PROPERTIES)

apply_property_change(property, enabled)
update_contact_attribute(property, enabled)
head :ok
rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
render json: { error: e.message }, status: :unprocessable_entity
end

private

def apply_property_change(property, enabled)
case property
when 'announce', 'restrict'
channel.group_setting_update(@contact.identifier, property, enabled)
when 'join_approval_mode'
channel.group_join_approval_mode(@contact.identifier, enabled ? 'on' : 'off')
when 'member_add_mode'
channel.group_member_add_mode(@contact.identifier, enabled ? 'all_member_add' : 'admin_add')
end
end

def property_params
params.permit(:property, :enabled)
end

def channel
@channel ||= @contact.group_channel
end

def resolve_group_conversations
Current.account.conversations
.where(contact_id: @contact.id, group_type: :group, status: %i[open pending])
.find_each { |c| c.update!(status: :resolved) }
end

def update_contact_attribute(key, value)
new_attrs = (@contact.additional_attributes || {}).merge(key => value)
@contact.update!(additional_attributes: new_attrs)
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class Api::V1::Accounts::Contacts::GroupInvitesController < Api::V1::Accounts::Contacts::BaseController
def show
authorize @contact, :show?
code = channel.group_invite_code(@contact.identifier)
render json: invite_response(code)
rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
render json: { error: e.message }, status: :unprocessable_entity
end

def revoke
authorize @contact, :update?
code = channel.revoke_group_invite(@contact.identifier)
render json: invite_response(code)
rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
render json: { error: e.message }, status: :unprocessable_entity
end

private

def channel
@channel ||= @contact.group_channel
end

def invite_response(code)
{ invite_code: code, invite_url: "https://chat.whatsapp.com/#{code}" }
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
class Api::V1::Accounts::Contacts::GroupJoinRequestsController < Api::V1::Accounts::Contacts::BaseController
def index
authorize @contact, :show?
requests = channel.group_join_requests(@contact.identifier)
render json: { payload: requests }
rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
render json: { error: e.message }, status: :unprocessable_entity
end

def handle
authorize @contact, :update?
channel.handle_group_join_requests(@contact.identifier, handle_params[:participants], handle_params[:request_action])
remove_handled_requests(handle_params[:participants])
head :ok
rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
render json: { error: e.message }, status: :unprocessable_entity
end

private

def handle_params
params.permit(:request_action, participants: [])
end

def channel
@channel ||= @contact.group_channel
end

def remove_handled_requests(participants)
return if participants.blank?

current_requests = @contact.additional_attributes&.dig('pending_join_requests') || []
updated_requests = current_requests.reject { |r| participants.include?(r['jid']) }
new_attrs = (@contact.additional_attributes || {}).merge('pending_join_requests' => updated_requests)
@contact.update!(additional_attributes: new_attrs)
end
end
155 changes: 155 additions & 0 deletions app/controllers/api/v1/accounts/contacts/group_members_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
class Api::V1::Accounts::Contacts::GroupMembersController < Api::V1::Accounts::Contacts::BaseController
DEFAULT_PER_PAGE = 10

before_action :ensure_group_contact, only: %i[create update destroy]

def index
authorize @contact, :show?

base_query = GroupMember.active
.where(group_contact: @contact)
.includes(:contact)

@total_count = base_query.count
@page = [(params[:page] || 1).to_i, 1].max
@per_page = (params[:per_page] || DEFAULT_PER_PAGE).to_i.clamp(1, 100)
@inbox_phone_number = inbox_phone_number
@is_inbox_admin = inbox_admin?

paginated = base_query.order(role: :desc, id: :asc)
.offset((@page - 1) * @per_page)
.limit(@per_page)

@group_members = pin_own_member_on_first_page(paginated)
end

def create
authorize @contact, :update?
participants = create_params[:participants]
return render json: { error: 'participants_required' }, status: :unprocessable_entity if participants.blank?

channel.update_group_participants(@contact.identifier, format_participants(participants), 'add')
add_group_members(participants)
head :ok
rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
render json: { error: e.message }, status: :unprocessable_entity
end

def update
authorize @contact, :update?
role = update_params[:role]
return render json: { error: 'invalid_role' }, status: :unprocessable_entity unless %w[admin member].include?(role)

member = group_members.find(params[:member_id])
action = role == 'admin' ? 'promote' : 'demote'
channel.update_group_participants(@contact.identifier, [jid_for_member(member)], action)
member.update!(role: role)
head :ok
rescue Whatsapp::Providers::WhatsappBaileysService::GroupParticipantNotAllowedError
render json: { error: 'group_creator_not_modifiable' }, status: :unprocessable_entity
rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
render json: { error: e.message }, status: :unprocessable_entity
end

def destroy
authorize @contact, :update?

member = group_members.find(params[:id])
channel.update_group_participants(@contact.identifier, [jid_for_member(member)], 'remove')
member.update!(is_active: false)
head :ok
rescue Whatsapp::Providers::WhatsappBaileysService::GroupParticipantNotAllowedError
render json: { error: 'group_creator_not_modifiable' }, status: :unprocessable_entity
rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
render json: { error: e.message }, status: :unprocessable_entity
end

private

def ensure_group_contact
return if @contact.group_type_group? && @contact.identifier.present?

render json: { error: 'Contact is not a valid group' }, status: :unprocessable_entity
end

def group_members
GroupMember.where(group_contact: @contact)
end

def create_params
params.permit(participants: [])
end

def update_params
params.permit(:role)
end

def channel
@channel ||= @contact.group_channel
end

def inbox_phone_number
channel&.phone_number
end

def inbox_admin?
return false if @inbox_phone_number.blank?

find_own_member&.role == 'admin'
end

def pin_own_member_on_first_page(paginated)
return paginated unless @page == 1 && @inbox_phone_number.present?

ids = paginated.pluck(:id)
own = find_own_member
return paginated if own.blank? || ids.include?(own.id)

# Prepend own member; drop the last one so total per-page stays consistent
[own] + paginated.where.not(id: own.id).limit(@per_page - 1).to_a
end

def find_own_member
clean = @inbox_phone_number.delete('+')
GroupMember.active
.where(group_contact: @contact)
.joins(:contact)
.where('REPLACE(contacts.phone_number, \'+\', \'\') = ? OR RIGHT(REPLACE(contacts.phone_number, \'+\', \'\'), 8) = RIGHT(?, 8)',
clean, clean)
.includes(:contact)
.first
end

def format_participants(phone_numbers)
Array(phone_numbers).map { |phone| "#{phone.to_s.delete('+')}@s.whatsapp.net" }
end

def jid_for_member(member)
"#{member.contact.phone_number.to_s.delete('+')}@s.whatsapp.net"
end

def add_group_members(phone_numbers)
inbox = @contact.contact_inboxes.first&.inbox
Array(phone_numbers).each do |phone|
normalized = normalize_phone(phone)
next if normalized.blank?

contact_inbox = ::ContactInboxWithContactBuilder.new(
source_id: normalized.delete('+'),
inbox: inbox,
contact_attributes: { name: normalized, phone_number: normalized }
).perform
next if contact_inbox.blank?

member = GroupMember.find_or_initialize_by(group_contact: @contact, contact: contact_inbox.contact)
member.update!(role: :member, is_active: true) unless member.persisted? && member.is_active?
end
end

def normalize_phone(phone)
cleaned = phone.to_s.strip
return nil if cleaned.blank?

cleaned.start_with?('+') ? cleaned : "+#{cleaned}"
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
class Api::V1::Accounts::Contacts::GroupMetadataController < Api::V1::Accounts::Contacts::BaseController
def update
authorize @contact, :update?
update_subject if metadata_params[:subject].present?
update_description if metadata_params[:description].present?
update_picture if metadata_params[:avatar].present?
render json: { id: @contact.id, name: @contact.name, additional_attributes: @contact.additional_attributes }
rescue Whatsapp::Providers::WhatsappBaileysService::ProviderUnavailableError => e
render json: { error: e.message }, status: :unprocessable_entity
end

private

def metadata_params
params.permit(:subject, :description, :avatar)
end

def update_subject
channel.update_group_subject(@contact.identifier, metadata_params[:subject])
@contact.update!(name: metadata_params[:subject])
end

def update_description
channel.update_group_description(@contact.identifier, metadata_params[:description])
attrs = @contact.additional_attributes.merge('description' => metadata_params[:description])
@contact.update!(additional_attributes: attrs)
end

def update_picture
avatar = metadata_params[:avatar]
image_base64 = Base64.strict_encode64(avatar.read)
channel.update_group_picture(@contact.identifier, image_base64)
@contact.avatar.attach(avatar)
end

def channel
@channel ||= @contact.group_channel
end
end
11 changes: 10 additions & 1 deletion app/controllers/api/v1/accounts/contacts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController

before_action :check_authorization
before_action :set_current_page, only: [:index, :active, :search, :filter]
before_action :fetch_contact, only: [:show, :update, :destroy, :avatar, :contactable_inboxes, :destroy_custom_attributes]
before_action :fetch_contact, only: [:show, :update, :destroy, :avatar, :contactable_inboxes, :destroy_custom_attributes, :sync_group]
before_action :set_include_contact_inboxes, only: [:index, :active, :search, :filter, :show, :update]

def index
Expand Down Expand Up @@ -82,6 +82,15 @@ def destroy_custom_attributes
@contact.save!
end

def sync_group
authorize @contact, :sync_group?
raise ActionController::BadRequest, I18n.t('contacts.sync_group.not_a_group') if @contact.group_type_individual?
raise ActionController::BadRequest, I18n.t('contacts.sync_group.no_identifier') if @contact.identifier.blank?

Contacts::SyncGroupJob.perform_later(@contact)
head :accepted
end

def create
ActiveRecord::Base.transaction do
@contact = Current.account.contacts.new(permitted_params.except(:avatar_url))
Expand Down
8 changes: 8 additions & 0 deletions app/controllers/api/v1/accounts/conversations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,15 @@ def update_last_seen_on_conversation(last_seen_at, update_assignee)
# rubocop:enable Rails/SkipsModelValidations
end

def unseen_activity?
@conversation.last_activity_at.present? &&
(@conversation.agent_last_seen_at.blank? || @conversation.last_activity_at > @conversation.agent_last_seen_at)
end

def should_update_last_seen?
# Always update when there's unseen activity (e.g. soft-disabled group conversations that don't create messages)
return true if unseen_activity?

# Update if at least one relevant timestamp is older than 1 hour or not set
# This prevents redundant DB writes when agents repeatedly view the same conversation
agent_needs_update = @conversation.agent_last_seen_at.blank? || @conversation.agent_last_seen_at < 1.hour.ago
Expand Down
Loading
Loading