feat(crm): validate imported contact labels
This commit is contained in:
@@ -614,6 +614,8 @@ func (s *ContactHandlerCRUDTestSuite) TestMerge_ChatwootActionsPathReturnsRawCon
|
||||
}
|
||||
|
||||
func (s *ContactHandlerCRUDTestSuite) TestImport_CreatesDataImportAndReturnsOK() {
|
||||
s.Require().NoError(s.db.Create(&model.Tag{AccountID: s.account.ID, Name: "vip"}).Error)
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
part, err := writer.CreateFormFile("import_file", "contacts.csv")
|
||||
|
||||
@@ -801,6 +801,15 @@ func (s *ContactService) ImportCSV(ctx context.Context, accountID uint, r io.Rea
|
||||
if idx, ok := colIndex["labels"]; ok && idx < len(row) {
|
||||
labels = splitImportLabels(row[idx])
|
||||
}
|
||||
labels, invalidLabels, err := s.resolveApprovedImportLabels(ctx, accountID, labels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(invalidLabels) > 0 {
|
||||
applogger.L().Warnf("Skipping imported contact row with unknown labels: %s", strings.Join(invalidLabels, ", "))
|
||||
result.Failed++
|
||||
continue
|
||||
}
|
||||
known := map[string]struct{}{"name": {}, "email": {}, "phone_number": {}, "identifier": {}, "country_code": {}, "location": {}, "city": {}, "company_name": {}, "contact_type": {}, "labels": {}}
|
||||
for i, col := range header {
|
||||
col = strings.TrimSpace(col)
|
||||
@@ -849,6 +858,45 @@ func (s *ContactService) ImportCSV(ctx context.Context, accountID uint, r io.Rea
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *ContactService) resolveApprovedImportLabels(ctx context.Context, accountID uint, labels []string) ([]string, []string, error) {
|
||||
if len(labels) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
approved := map[string]string{}
|
||||
var tags []model.Tag
|
||||
if err := s.repo.DB().WithContext(ctx).Where("account_id = ?", accountID).Find(&tags).Error; err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
for _, tag := range tags {
|
||||
approved[strings.ToLower(strings.TrimSpace(tag.Name))] = tag.Name
|
||||
}
|
||||
|
||||
seen := map[string]struct{}{}
|
||||
resolved := make([]string, 0, len(labels))
|
||||
invalid := make([]string, 0)
|
||||
for _, label := range labels {
|
||||
label = strings.TrimSpace(label)
|
||||
if label == "" {
|
||||
continue
|
||||
}
|
||||
key := strings.ToLower(label)
|
||||
canonical, ok := approved[key]
|
||||
if !ok {
|
||||
if _, exists := seen["invalid:"+key]; !exists {
|
||||
invalid = append(invalid, key)
|
||||
seen["invalid:"+key] = struct{}{}
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[key]; exists {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
resolved = append(resolved, canonical)
|
||||
}
|
||||
return resolved, invalid, nil
|
||||
}
|
||||
|
||||
func (s *ContactService) findImportContact(ctx context.Context, accountID uint, contact *model.Contact) *model.Contact {
|
||||
if contact.Identifier != "" {
|
||||
if existing, err := s.repo.FindByIdentifier(ctx, accountID, contact.Identifier); err == nil {
|
||||
|
||||
@@ -331,6 +331,7 @@ func TestContactService_ImportContacts_CreatesCompletedDataImport(t *testing.T)
|
||||
db, _, svc := setupContactService(t)
|
||||
account := createTestAccount(t, db)
|
||||
userID := uint(42)
|
||||
require.NoError(t, db.Create(&model.Tag{AccountID: account.ID, Name: "vip"}).Error)
|
||||
|
||||
csvData := "name,email,labels\nAlice,alice@test.com,vip\n"
|
||||
dataImport, err := svc.ImportContacts(context.Background(), account.ID, userID, strings.NewReader(csvData))
|
||||
@@ -347,6 +348,30 @@ func TestContactService_ImportContacts_CreatesCompletedDataImport(t *testing.T)
|
||||
assert.Equal(t, int64(1), labelCount)
|
||||
}
|
||||
|
||||
func TestContactService_ImportContacts_RejectsUnknownLabels(t *testing.T) {
|
||||
db, _, svc := setupContactService(t)
|
||||
account := createTestAccount(t, db)
|
||||
require.NoError(t, db.Create(&model.Tag{AccountID: account.ID, Name: "vip"}).Error)
|
||||
|
||||
csvData := "name,email,labels\nAlice,alice@test.com,vip\nBob,bob@test.com,unknown\n"
|
||||
dataImport, err := svc.ImportContacts(context.Background(), account.ID, 0, strings.NewReader(csvData))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, string(model.DataImportStatusCompleted), dataImport.Status)
|
||||
assert.Equal(t, 2, dataImport.TotalRecords)
|
||||
assert.Equal(t, 1, dataImport.ProcessedRecords)
|
||||
assert.Equal(t, 1, dataImport.FailedRecords)
|
||||
|
||||
var alice model.Contact
|
||||
require.NoError(t, db.Where("account_id = ? AND email = ?", account.ID, "alice@test.com").First(&alice).Error)
|
||||
var bobCount int64
|
||||
db.Model(&model.Contact{}).Where("account_id = ? AND email = ?", account.ID, "bob@test.com").Count(&bobCount)
|
||||
assert.Equal(t, int64(0), bobCount)
|
||||
|
||||
var tagCount int64
|
||||
db.Model(&model.Tag{}).Where("account_id = ? AND name = ?", account.ID, "unknown").Count(&tagCount)
|
||||
assert.Equal(t, int64(0), tagCount)
|
||||
}
|
||||
|
||||
func TestContactService_ImportCSV_FailsOnBadCSV(t *testing.T) {
|
||||
db, _, svc := setupContactService(t)
|
||||
account := createTestAccount(t, db)
|
||||
|
||||
Reference in New Issue
Block a user