Skip to content

Commit f355239

Browse files
AchoArnoldCopilot
andcommitted
perf: load unarchive flag lazily for archived threads
Consult the phone's UnarchiveThread setting inside MessageThreadService.UpdateThread only when an inbound message lands on an archived thread, instead of loading the phone on every received message. Removes the per-message phone read in ReceiveMessage and drops the UnarchiveThread field from the received-message event payload. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: bf78429e-573c-406c-9f7c-1d6e1bddbbb5
1 parent e1c3d1f commit f355239

6 files changed

Lines changed: 56 additions & 60 deletions

File tree

api/pkg/di/container.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1111,6 +1111,7 @@ func (container *Container) MessageThreadService() (service *services.MessageThr
11111111
container.Logger(),
11121112
container.Tracer(),
11131113
container.MessageThreadRepository(),
1114+
container.PhoneRepository(),
11141115
container.EventDispatcher(),
11151116
)
11161117
}

api/pkg/events/message_phone_received_event.go

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,13 @@ const EventTypeMessagePhoneReceived = "message.phone.received"
1313

1414
// MessagePhoneReceivedPayload is the payload of the EventTypeMessagePhoneReceived event
1515
type MessagePhoneReceivedPayload struct {
16-
MessageID uuid.UUID `json:"message_id"`
17-
UserID entities.UserID `json:"user_id"`
18-
Owner string `json:"owner"`
19-
Encrypted bool `json:"encrypted"`
20-
Contact string `json:"contact"`
21-
Timestamp time.Time `json:"timestamp"`
22-
Content string `json:"content"`
23-
SIM entities.SIM `json:"sim"`
24-
Attachments []string `json:"attachments"`
25-
UnarchiveThread bool `json:"unarchive_thread"`
16+
MessageID uuid.UUID `json:"message_id"`
17+
UserID entities.UserID `json:"user_id"`
18+
Owner string `json:"owner"`
19+
Encrypted bool `json:"encrypted"`
20+
Contact string `json:"contact"`
21+
Timestamp time.Time `json:"timestamp"`
22+
Content string `json:"content"`
23+
SIM entities.SIM `json:"sim"`
24+
Attachments []string `json:"attachments"`
2625
}

api/pkg/listeners/message_thread_listener.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -222,14 +222,13 @@ func (listener *MessageThreadListener) OnMessagePhoneReceived(ctx context.Contex
222222
}
223223

224224
updateParams := services.MessageThreadUpdateParams{
225-
Owner: payload.Owner,
226-
Contact: payload.Contact,
227-
Timestamp: payload.Timestamp,
228-
UserID: payload.UserID,
229-
Status: entities.MessageStatusReceived,
230-
Content: payload.Content,
231-
MessageID: payload.MessageID,
232-
UnarchiveThread: payload.UnarchiveThread,
225+
Owner: payload.Owner,
226+
Contact: payload.Contact,
227+
Timestamp: payload.Timestamp,
228+
UserID: payload.UserID,
229+
Status: entities.MessageStatusReceived,
230+
Content: payload.Content,
231+
MessageID: payload.MessageID,
233232
}
234233

235234
if err := listener.service.UpdateThread(ctx, updateParams); err != nil {

api/pkg/services/message_service.go

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -348,25 +348,16 @@ func (service *MessageService) ReceiveMessage(ctx context.Context, params *Messa
348348

349349
owner := phonenumbers.Format(&params.Owner, phonenumbers.E164)
350350

351-
unarchiveThread := false
352-
phone, err := service.phoneService.Load(ctx, params.UserID, owner)
353-
if err != nil {
354-
ctxLogger.Warn(stacktrace.Propagate(err, "cannot load phone [%s] for user [%s] to resolve UnarchiveThread; defaulting to false", owner, params.UserID))
355-
} else {
356-
unarchiveThread = phone.UnarchiveThread
357-
}
358-
359351
eventPayload := events.MessagePhoneReceivedPayload{
360-
MessageID: messageID,
361-
UserID: params.UserID,
362-
Encrypted: params.Encrypted,
363-
Owner: owner,
364-
Contact: params.Contact,
365-
Timestamp: params.Timestamp,
366-
Content: params.Content,
367-
SIM: params.SIM,
368-
Attachments: attachmentURLs,
369-
UnarchiveThread: unarchiveThread,
352+
MessageID: messageID,
353+
UserID: params.UserID,
354+
Encrypted: params.Encrypted,
355+
Owner: owner,
356+
Contact: params.Contact,
357+
Timestamp: params.Timestamp,
358+
Content: params.Content,
359+
SIM: params.SIM,
360+
Attachments: attachmentURLs,
370361
}
371362

372363
ctxLogger.Info(fmt.Sprintf("creating cloud event for received with ID [%s]", eventPayload.MessageID))

api/pkg/services/message_thread_service.go

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ type MessageThreadService struct {
2121
logger telemetry.Logger
2222
tracer telemetry.Tracer
2323
repository repositories.MessageThreadRepository
24+
phoneRepository repositories.PhoneRepository
2425
eventDispatcher *EventDispatcher
2526
}
2627

@@ -29,33 +30,35 @@ func NewMessageThreadService(
2930
logger telemetry.Logger,
3031
tracer telemetry.Tracer,
3132
repository repositories.MessageThreadRepository,
33+
phoneRepository repositories.PhoneRepository,
3234
eventDispatcher *EventDispatcher,
3335
) (s *MessageThreadService) {
3436
return &MessageThreadService{
3537
logger: logger.WithService(fmt.Sprintf("%T", s)),
3638
tracer: tracer,
3739
eventDispatcher: eventDispatcher,
3840
repository: repository,
41+
phoneRepository: phoneRepository,
3942
}
4043
}
4144

4245
// MessageThreadUpdateParams are parameters for updating a thread
4346
type MessageThreadUpdateParams struct {
44-
Owner string
45-
Status entities.MessageStatus
46-
Contact string
47-
Content string
48-
UserID entities.UserID
49-
MessageID uuid.UUID
50-
Timestamp time.Time
51-
UnarchiveThread bool
47+
Owner string
48+
Status entities.MessageStatus
49+
Contact string
50+
Content string
51+
UserID entities.UserID
52+
MessageID uuid.UUID
53+
Timestamp time.Time
5254
}
5355

54-
// shouldUnarchive reports whether an archived thread should be moved back to
55-
// the inbox because a new inbound message was received and the phone has the
56-
// UnarchiveThread setting enabled.
57-
func (service *MessageThreadService) shouldUnarchive(thread *entities.MessageThread, params MessageThreadUpdateParams) bool {
58-
return thread.IsArchived && params.UnarchiveThread && params.Status == entities.MessageStatusReceived
56+
// shouldCheckUnarchive reports whether a thread update is a new inbound message
57+
// landing on an archived thread. Only in that case is the phone's
58+
// UnarchiveThread setting consulted, so the phone is not loaded on the common
59+
// path where the thread is not archived.
60+
func (service *MessageThreadService) shouldCheckUnarchive(thread *entities.MessageThread, params MessageThreadUpdateParams) bool {
61+
return thread.IsArchived && params.Status == entities.MessageStatusReceived
5962
}
6063

6164
// DeleteAllForUser deletes all entities.MessageThread for an entities.UserID.
@@ -100,9 +103,14 @@ func (service *MessageThreadService) UpdateThread(ctx context.Context, params Me
100103
return nil
101104
}
102105

103-
if service.shouldUnarchive(thread, params) {
104-
thread.UpdateArchive(false)
105-
ctxLogger.Info(fmt.Sprintf("unarchiving thread [%s] after inbound message [%s]", thread.ID, params.MessageID))
106+
if service.shouldCheckUnarchive(thread, params) {
107+
phone, phoneErr := service.phoneRepository.Load(ctx, params.UserID, params.Owner)
108+
if phoneErr != nil {
109+
ctxLogger.Warn(stacktrace.Propagate(phoneErr, "cannot load phone [%s] for user [%s] to resolve UnarchiveThread; leaving thread [%s] archived", params.Owner, params.UserID, thread.ID))
110+
} else if phone.UnarchiveThread {
111+
thread.UpdateArchive(false)
112+
ctxLogger.Info(fmt.Sprintf("unarchiving thread [%s] after inbound message [%s]", thread.ID, params.MessageID))
113+
}
106114
}
107115

108116
if err = service.repository.Update(ctx, thread.Update(params.Timestamp, params.MessageID, params.Content, params.Status)); err != nil {

api/pkg/services/message_thread_service_test.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,16 @@ import (
77
"github.com/stretchr/testify/assert"
88
)
99

10-
func TestShouldUnarchive(t *testing.T) {
10+
func TestShouldCheckUnarchive(t *testing.T) {
1111
service := &MessageThreadService{}
1212

1313
archived := &entities.MessageThread{IsArchived: true}
1414
notArchived := &entities.MessageThread{IsArchived: false}
1515

16-
received := MessageThreadUpdateParams{Status: entities.MessageStatusReceived, UnarchiveThread: true}
17-
receivedFlagOff := MessageThreadUpdateParams{Status: entities.MessageStatusReceived, UnarchiveThread: false}
18-
sentFlagOn := MessageThreadUpdateParams{Status: entities.MessageStatusSent, UnarchiveThread: true}
16+
received := MessageThreadUpdateParams{Status: entities.MessageStatusReceived}
17+
sent := MessageThreadUpdateParams{Status: entities.MessageStatusSent}
1918

20-
assert.True(t, service.shouldUnarchive(archived, received), "archived + inbound + flag on -> unarchive")
21-
assert.False(t, service.shouldUnarchive(archived, receivedFlagOff), "flag off -> no unarchive")
22-
assert.False(t, service.shouldUnarchive(archived, sentFlagOn), "outbound status -> no unarchive")
23-
assert.False(t, service.shouldUnarchive(notArchived, received), "already unarchived -> no change")
19+
assert.True(t, service.shouldCheckUnarchive(archived, received), "archived + inbound -> consult phone setting")
20+
assert.False(t, service.shouldCheckUnarchive(archived, sent), "outbound status -> no check")
21+
assert.False(t, service.shouldCheckUnarchive(notArchived, received), "already unarchived -> no check")
2422
}

0 commit comments

Comments
 (0)