Skip to content

Commit fc5ed96

Browse files
committed
fix: improve mappings for the fetch
1 parent 8b5acf3 commit fc5ed96

File tree

4 files changed

+158
-46
lines changed

4 files changed

+158
-46
lines changed

docs/example-mappings.json

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,16 @@
11
[
2-
{
3-
"number": "91201",
4-
"matrix_id": "@giacomo:synapse.gs.nethserver.net",
5-
"room_id": "!giacomo-room:synapse.gs.nethserver.net",
6-
"user_name": "Giacomo Rossi"
7-
},
82
{
93
"number": "201",
104
"matrix_id": "@giacomo:synapse.gs.nethserver.net",
115
"room_id": "!giacomo-room:synapse.gs.nethserver.net",
12-
"user_name": "Giacomo Rossi"
13-
},
14-
{
15-
"number": "91202",
16-
"matrix_id": "@mario:synapse.gs.nethserver.net",
17-
"room_id": "!mario-room:synapse.gs.nethserver.net",
18-
"user_name": "Mario Bianchi"
6+
"user_name": "Giacomo Rossi",
7+
"sub_numbers": ["3344", "91201"]
198
},
209
{
2110
"number": "202",
2211
"matrix_id": "@mario:synapse.gs.nethserver.net",
2312
"room_id": "!mario-room:synapse.gs.nethserver.net",
24-
"user_name": "Mario Bianchi"
13+
"user_name": "Mario Bianchi",
14+
"sub_numbers": ["3345", "91202"]
2515
}
2616
]
27-

models/mapping.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ package models
22

33
// MappingRequest defines the payload used by the Message-to-Matrix mapping API.
44
type MappingRequest struct {
5-
Number string `json:"number"`
6-
MatrixID string `json:"matrix_id,omitempty"`
7-
RoomID string `json:"room_id"`
8-
UserName string `json:"user_name,omitempty"`
5+
Number string `json:"number"`
6+
MatrixID string `json:"matrix_id,omitempty"`
7+
RoomID string `json:"room_id"`
8+
UserName string `json:"user_name,omitempty"`
9+
SubNumbers []string `json:"sub_numbers,omitempty"`
910
}
1011

1112
// MappingResponse is returned once a mapping has been created or looked up.
1213
type MappingResponse struct {
13-
Number string `json:"number"`
14-
MatrixID string `json:"matrix_id"`
15-
RoomID string `json:"room_id"`
16-
UserName string `json:"user_name,omitempty"`
17-
UpdatedAt string `json:"updated_at"`
14+
Number string `json:"number"`
15+
MatrixID string `json:"matrix_id"`
16+
RoomID string `json:"room_id"`
17+
UserName string `json:"user_name,omitempty"`
18+
SubNumbers []string `json:"sub_numbers,omitempty"`
19+
UpdatedAt string `json:"updated_at"`
1820
}

service/messages.go

Lines changed: 43 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ type MessageService struct {
3737
}
3838

3939
type mappingEntry struct {
40-
Number string
41-
MatrixID string
42-
RoomID id.RoomID
43-
UserName string
44-
UpdatedAt time.Time
40+
Number string
41+
MatrixID string
42+
RoomID id.RoomID
43+
UserName string
44+
SubNumbers []string
45+
UpdatedAt time.Time
4546
}
4647

4748
// NewMessageService wires the provided Matrix client and push token database into the service layer.
@@ -244,6 +245,13 @@ func (s *MessageService) resolveMatrixUser(identifier string) id.UserID {
244245
}
245246

246247
// resolveMatrixIDToIdentifier resolves a Matrix user ID to a preferred identifier (Number, then UserName).
248+
// The resolution logic:
249+
// - First checks if any sub_number matches the matrix_id; if found, returns the main number
250+
// - Then checks if the main number matches the matrix_id; if found, returns the number
251+
// - Falls back to UserName if available
252+
// - Returns the original Matrix ID if no mapping is found
253+
//
254+
// Sub_numbers are never returned directly; if a sub_number is matched, the main number is returned instead.
247255
func (s *MessageService) resolveMatrixIDToIdentifier(matrixID string) string {
248256
matrixID = strings.TrimSpace(matrixID)
249257

@@ -255,6 +263,16 @@ func (s *MessageService) resolveMatrixIDToIdentifier(matrixID string) string {
255263
if strings.Contains(entry.Number, "|") {
256264
continue
257265
}
266+
267+
// First try to match against sub_numbers
268+
for _, subNum := range entry.SubNumbers {
269+
if strings.EqualFold(strings.TrimSpace(subNum), matrixID) {
270+
// Sub_number matched, return the main number instead
271+
logger.Debug().Str("matrix_id", matrixID).Str("sub_number", subNum).Str("number", entry.Number).Msg("resolved matrix id to number via sub_number")
272+
return entry.Number
273+
}
274+
}
275+
258276
// Prefer Number as the identifier
259277
if entry.Number != "" {
260278
logger.Debug().Str("matrix_id", matrixID).Str("number", entry.Number).Msg("resolved matrix id to number")
@@ -389,11 +407,12 @@ func (s *MessageService) SaveMapping(req *models.MappingRequest) (*models.Mappin
389407
}
390408

391409
entry := mappingEntry{
392-
Number: number,
393-
MatrixID: strings.TrimSpace(req.MatrixID),
394-
RoomID: id.RoomID(roomID),
395-
UserName: strings.TrimSpace(req.UserName),
396-
UpdatedAt: s.now(),
410+
Number: number,
411+
MatrixID: strings.TrimSpace(req.MatrixID),
412+
RoomID: id.RoomID(roomID),
413+
UserName: strings.TrimSpace(req.UserName),
414+
SubNumbers: req.SubNumbers,
415+
UpdatedAt: s.now(),
397416
}
398417
entry = s.setMapping(entry)
399418
return s.buildMappingResponse(entry), nil
@@ -402,8 +421,8 @@ func (s *MessageService) SaveMapping(req *models.MappingRequest) (*models.Mappin
402421
// LoadMappingsFromFile loads mappings from a JSON file in the format:
403422
//
404423
// [
405-
// {"number": "91201", "matrix_id": "@giacomo:synapse.gs.nethserver.net", "room_id": "!giacomo-room:synapse.gs.nethserver.net", "user_name": "Giacomo Rossi"},
406-
// {"number": "91202", "matrix_id": "@mario:synapse.gs.nethserver.net", "room_id": "!mario-room:synapse.gs.nethserver.net", "user_name": "Mario Bianchi"}
424+
// {"number": "201", "matrix_id": "@giacomo:synapse.gs.nethserver.net", "room_id": "!giacomo-room:synapse.gs.nethserver.net", "user_name": "Giacomo Rossi", "sub_numbers": ["3344", "91201"]},
425+
// {"number": "202", "matrix_id": "@mario:synapse.gs.nethserver.net", "room_id": "!mario-room:synapse.gs.nethserver.net", "user_name": "Mario Bianchi", "sub_numbers": ["3345", "91202"]}
407426
// ]
408427
//
409428
// This is typically called at startup if MAPPING_FILE environment variable is set.
@@ -424,11 +443,12 @@ func (s *MessageService) LoadMappingsFromFile(filePath string) error {
424443
continue
425444
}
426445
entry := mappingEntry{
427-
Number: req.Number,
428-
MatrixID: req.MatrixID,
429-
RoomID: id.RoomID(req.RoomID),
430-
UserName: req.UserName,
431-
UpdatedAt: s.now(),
446+
Number: req.Number,
447+
MatrixID: req.MatrixID,
448+
RoomID: id.RoomID(req.RoomID),
449+
UserName: req.UserName,
450+
SubNumbers: req.SubNumbers,
451+
UpdatedAt: s.now(),
432452
}
433453
s.setMapping(entry)
434454
}
@@ -439,11 +459,12 @@ func (s *MessageService) LoadMappingsFromFile(filePath string) error {
439459

440460
func (s *MessageService) buildMappingResponse(entry mappingEntry) *models.MappingResponse {
441461
return &models.MappingResponse{
442-
Number: entry.Number,
443-
MatrixID: entry.MatrixID,
444-
RoomID: string(entry.RoomID),
445-
UserName: entry.UserName,
446-
UpdatedAt: entry.UpdatedAt.UTC().Format(time.RFC3339),
462+
Number: entry.Number,
463+
MatrixID: entry.MatrixID,
464+
RoomID: string(entry.RoomID),
465+
UserName: entry.UserName,
466+
SubNumbers: entry.SubNumbers,
467+
UpdatedAt: entry.UpdatedAt.UTC().Format(time.RFC3339),
447468
}
448469
}
449470

service/messages_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,106 @@ func TestLoadMappingsFromFile_InvalidJSON(t *testing.T) {
364364
assert.Error(t, err)
365365
assert.Contains(t, err.Error(), "failed to parse mapping file")
366366
}
367+
368+
func TestResolveMatrixIDToIdentifier_SubNumbers(t *testing.T) {
369+
// Test case 1: Resolve via sub_number match
370+
// When a matrix_id matches one of the sub_numbers, the main number should be returned (not the sub_number)
371+
t.Run("resolve via sub_number match", func(t *testing.T) {
372+
svc := NewMessageService(nil, nil)
373+
svc.SaveMapping(&models.MappingRequest{
374+
Number: "201",
375+
MatrixID: "@giacomo:example.com",
376+
RoomID: "!room1:example.com",
377+
UserName: "Giacomo Rossi",
378+
SubNumbers: []string{"3344", "91201"},
379+
})
380+
381+
// Resolve using a sub_number - should return the main number
382+
result := svc.resolveMatrixIDToIdentifier("@giacomo:example.com")
383+
assert.Equal(t, "201", result, "should return main number when matrix_id matches via sub_number")
384+
})
385+
386+
// Test case 2: Resolve via main number
387+
// When a matrix_id matches the main number field, return that number
388+
t.Run("resolve via main number", func(t *testing.T) {
389+
svc := NewMessageService(nil, nil)
390+
svc.SaveMapping(&models.MappingRequest{
391+
Number: "202",
392+
MatrixID: "@mario:example.com",
393+
RoomID: "!room2:example.com",
394+
UserName: "Mario Bianchi",
395+
})
396+
397+
// Resolve using the matrix_id - should return the main number
398+
result := svc.resolveMatrixIDToIdentifier("@mario:example.com")
399+
assert.Equal(t, "202", result, "should return main number when matrix_id matches")
400+
})
401+
402+
// Test case 3: Sub_numbers should never be returned directly
403+
// This is ensured by the logic that checks sub_numbers first, then returns the main number
404+
t.Run("sub_numbers never returned directly", func(t *testing.T) {
405+
svc := NewMessageService(nil, nil)
406+
svc.SaveMapping(&models.MappingRequest{
407+
Number: "201",
408+
MatrixID: "@giacomo:example.com",
409+
RoomID: "!room1:example.com",
410+
UserName: "Giacomo Rossi",
411+
SubNumbers: []string{"3344", "91201"},
412+
})
413+
414+
// Try to resolve using the main number
415+
result := svc.resolveMatrixIDToIdentifier("@giacomo:example.com")
416+
assert.Equal(t, "201", result)
417+
assert.NotEqual(t, "3344", result, "should never return sub_number directly")
418+
assert.NotEqual(t, "91201", result, "should never return sub_number directly")
419+
})
420+
421+
// Test case 4: Case insensitivity
422+
// Matrix IDs should be matched case-insensitively
423+
t.Run("case insensitivity", func(t *testing.T) {
424+
svc := NewMessageService(nil, nil)
425+
svc.SaveMapping(&models.MappingRequest{
426+
Number: "201",
427+
MatrixID: "@giacomo:example.com",
428+
RoomID: "!room1:example.com",
429+
UserName: "Giacomo Rossi",
430+
SubNumbers: []string{"3344", "91201"},
431+
})
432+
433+
// Try with uppercase
434+
result := svc.resolveMatrixIDToIdentifier("@GIACOMO:EXAMPLE.COM")
435+
assert.Equal(t, "201", result, "should match case-insensitively")
436+
})
437+
438+
// Test case 5: Fallback to UserName when no sub_numbers or main number
439+
// If matrix_id doesn't match main number but UserName is set, return UserName
440+
t.Run("fallback to user name", func(t *testing.T) {
441+
svc := NewMessageService(nil, nil)
442+
// Create a mapping with matrix_id and username but no number
443+
entry := mappingEntry{
444+
Number: "internal|key",
445+
MatrixID: "@test:example.com",
446+
UserName: "Test User",
447+
SubNumbers: []string{},
448+
UpdatedAt: svc.now(),
449+
}
450+
svc.setMapping(entry)
451+
452+
// The matrix_id should match and we should get the number since it's available
453+
// So this test actually verifies that internal mappings are skipped
454+
result := svc.resolveMatrixIDToIdentifier("@test:example.com")
455+
// Internal mappings (with |) should be skipped, so no match expected
456+
assert.Equal(t, "@test:example.com", result, "internal mappings should be skipped")
457+
})
458+
459+
// Test case 6: No mapping found, return original matrix_id
460+
t.Run("no mapping returns original matrix_id", func(t *testing.T) {
461+
svc := NewMessageService(nil, nil)
462+
result := svc.resolveMatrixIDToIdentifier("@unknown:example.com")
463+
assert.Equal(t, "@unknown:example.com", result, "should return original matrix_id when no mapping found")
464+
})
465+
}
466+
367467
func TestReportPushToken(t *testing.T) {
368468
// Test with nil request
369469
t.Run("nil request", func(t *testing.T) {

0 commit comments

Comments
 (0)