feat(crm): validate imported contact labels

This commit is contained in:
2026-06-05 04:23:16 +08:00
parent b5f4431437
commit 653db81ef2
3 changed files with 75 additions and 0 deletions
@@ -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")
+48
View File
@@ -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)