Skip to content

Conversation

@etiennejouan
Copy link
Contributor

@etiennejouan etiennejouan commented Sep 17, 2025

Done :

  • migrate standard unique fields (add : -old)
  • deactivate defaultWhereClause
  • restore soft deleted if upserted (same for contact creation in mailing sync)

fixes #14443

@github-actions
Copy link
Contributor

github-actions bot commented Sep 17, 2025

🚀 Preview Environment Ready!

Your preview environment is available at: http://bore.pub:56949

This environment will automatically shut down when the PR is closed or after 5 hours.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Summary

This PR fixes a critical issue with CSV uploads when mapping multiple unique identifiers by updating the unique field constraint logic to include soft-deleted records.

Key Changes:

  • Database indexes: Removed "deletedAt" IS NULL constraint from unique indexes so soft-deleted records are included in uniqueness checks
  • Upsert logic: Enhanced GraphQL resolver to restore soft-deleted records by setting deletedAt: null during upserts
  • Contact/company creation: Added restoration logic for soft-deleted contacts and companies instead of treating them as conflicts
  • Database migration: Added v1.7 upgrade command to deduplicate existing unique field violations
  • Testing: Comprehensive integration tests covering soft-delete restoration scenarios

The changes ensure that when users upload CSV data with existing unique identifiers from soft-deleted records, those records are restored rather than causing constraint violations.

Confidence Score: 4/5

  • This PR is generally safe to merge with minor syntax issues to address
  • Score reflects solid implementation of soft-delete restoration logic with comprehensive testing, but contains syntax errors that should be fixed before merge
  • packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-deduplicate-unique-fields.command.ts needs syntax fixes

Important Files Changed

File Analysis

Filename        Score        Overview
packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/utils/workspace-migration-index.factory.utils.ts 5/5 Removed "deletedAt" IS NULL constraint from unique indexes to include soft-deleted records in uniqueness checks
packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts 4/5 Enhanced upsert logic to restore soft-deleted records by setting deletedAt: null and updated query to include soft-deleted records
packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts 4/5 Added restoration logic for soft-deleted companies, including filtering and updating deleted companies with deletedAt: null
packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-deduplicate-unique-fields.command.ts 3/5 New command to deduplicate unique fields for existing data, handling workspace members, companies and people with duplicate constraints

Sequence Diagram

sequenceDiagram
    participant U as User
    participant API as GraphQL API  
    participant R as CreateManyResolver
    participant DB as Database
    participant CS as ContactService
    participant COS as CompanyService

    U->>API: CSV Upload with unique identifiers
    API->>R: createMany with upsert: true
    
    R->>DB: Query existing records (including soft-deleted)
    DB->>R: Return existing records
    
    alt Record exists and is soft-deleted
        R->>DB: Update record with deletedAt: null
        DB->>R: Restored record
    else Record exists and not deleted
        R->>DB: Update existing record
        DB->>R: Updated record
    else Record doesn't exist
        R->>DB: Insert new record
        DB->>R: New record
    end

    alt Contact creation workflow
        CS->>DB: Find soft-deleted contacts by email
        DB->>CS: Soft-deleted contacts
        CS->>COS: Create/restore companies for contacts
        COS->>DB: Restore soft-deleted companies
        DB->>COS: Restored companies
        CS->>DB: Restore contacts with company associations
        DB->>CS: Restored contacts
    end

    R->>API: Return processed records
    API->>U: Success response
Loading

15 files reviewed, 4 comments

Edit Code Review Bot Settings | Greptile


return this.createContactService.createPeople(
const restoredContacts =
await this.restorePeopleAndRestoreOrCreateCompanies(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have kept one function that's doing both

@prastoin
Copy link
Contributor

Hey @etiennejouan is this still up to date ?
Closing this for the moment feel free to re-open

@prastoin prastoin closed this Sep 30, 2025
@etiennejouan
Copy link
Contributor Author

Still up to date Mr @prastoin , @charlesBochet could you give a look, please ?

@etiennejouan etiennejouan reopened this Sep 30, 2025
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Overview

Summary

This review covers only the changes made since the last review, not the entire PR.

The latest changes focus on consolidating and renaming contact creation services to align with a broader refactoring effort that addresses unique field constraints for soft-deleted records. The major update involves renaming CreateContactService to CreatePersonService and CreateCompanyAndContactService to CreateCompanyAndPersonService throughout the codebase, reflecting the actual data model where contacts are stored as Person entities.

Key architectural changes include:

  • Complete removal of the CreateContactService class, with its functionality migrated to the new CreatePersonService that handles both creation and restoration of Person entities
  • Enhanced contact creation logic that can now identify whether to create new Person records or restore existing soft-deleted ones, preventing unique constraint violations during CSV imports
  • Updates to unique index generation to remove the deletedAt IS NULL condition, allowing unique constraints to consider all records including soft-deleted ones
  • Implementation of a database migration command (DeduplicateUniqueFieldsCommand) that handles existing duplicate unique values by appending -old-{index} suffixes to soft-deleted records
  • Modification of upsert operations to automatically restore soft-deleted records by setting deletedAt: null

The changes integrate with the existing workspace management, messaging sync, and calendar sync workflows while maintaining backward compatibility through proper service injection and method renaming patterns.

Important Files Changed

Changed Files
Filename Score Overview
packages/twenty-server/src/modules/contact-creation-manager/services/create-contact.service.ts 5/5 Complete file deletion - functionality migrated to CreatePersonService
packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts 5/5 Added version 1.7.0 upgrade with DeduplicateUniqueFieldsCommand
packages/twenty-server/src/modules/contact-creation-manager/services/tests/create-company.service.spec.ts 4/5 Updated tests for createOrRestoreCompanies method with soft-delete handling
packages/twenty-server/test/integration/graphql/suites/upsert/upsert.integration-spec.ts 4/5 Added comprehensive upsert test for restoring soft-deleted records
packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-upgrade-version-command.module.ts 5/5 New module for version 1.7 database upgrade with required dependencies
packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/utils/workspace-migration-index.factory.utils.ts 4/5 Removed deletedAt IS NULL from unique index where clauses
packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts 3/5 Renamed function for better clarity with legacy type suppressions
packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/calendar-event-participant-manager.module.ts 3/5 Updated import to use CalendarCreateCompanyAndPersonAfterSyncJob
packages/twenty-server/src/modules/contact-creation-manager/jobs/create-company-and-contact.job.ts 4/5 Updated service imports and method calls to use Person terminology
packages/twenty-server/src/database/commands/upgrade-version-command/1-7/1-7-deduplicate-unique-fields.command.ts 4/5 New migration command to deduplicate unique fields with comprehensive logic
packages/twenty-server/src/modules/match-participant/utils/add-person-email-filters-to-query-builder.ts 4/5 Added soft-deleted record support with redundant withDeleted() call
packages/twenty-server/src/modules/contact-creation-manager/contact-creation-manager.module.ts 5/5 Module updated with new service names maintaining same structure
packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts 4/5 Enhanced to handle soft-deleted company restoration via createOrRestoreCompanies
packages/twenty-server/src/modules/contact-creation-manager/listeners/auto-companies-and-contacts-creation-calendar-channel.listener.ts 4/5 Updated job types and imports for Person terminology consistency
packages/twenty-server/src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-create-many-resolver.service.ts 4/5 Added deletedAt: null to restore soft-deleted records during upserts
packages/twenty-server/src/modules/messaging/message-participant-manager/jobs/messaging-create-company-and-contact-after-sync.job.ts 4/5 Service and method name updates with inconsistent logging message
packages/twenty-server/src/engine/metadata-modules/index-metadata/index-metadata.service.ts 5/5 Improved type safety by narrowing objectMetadata parameter type
packages/twenty-server/src/database/commands/upgrade-version-command/upgrade-version-command.module.ts 4/5 Added V1_7_UpgradeVersionCommandModule to support unique field migration
packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts 4/5 Major refactor to CreateCompanyAndPersonService with restore functionality
packages/twenty-server/src/modules/contact-creation-manager/services/tests/create-company-and-contact.service.spec.ts 4/5 New test file for CreateCompanyAndPersonService focusing on soft-delete handling
packages/twenty-server/src/engine/workspace-manager/workspace-migration-builder/factories/utils/tests/workspace-migration-index.factory.util.spec.ts 3/5 Updated test expectations to remove deletedAt condition from unique indexes
packages/twenty-server/src/engine/twenty-orm/decorators/workspace-is-unique.decorator.ts 4/5 Modified unique constraint generation by removing prefix and setting whereClause to null
packages/twenty-server/src/modules/contact-creation-manager/services/create-person.service.ts 4/5 New service providing person creation and restoration functionality
packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/jobs/calendar-create-company-and-contact-after-sync.job.ts 4/5 Systematic rename from Contact to Person terminology with method updates

Confidence score: 4/5

  • This PR involves complex architectural changes with proper testing but requires careful review due to the broad impact on unique constraint handling
  • Score reflects good implementation of the soft-delete restoration logic but concerns about potential unintended data restoration during upserts
  • Pay close attention to the database migration command and unique constraint modifications as they affect data integrity across the entire system

Additional Comments (6)

  1. packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts, line 25 (link)

    syntax: Using object instead of Map for boolean tracking

  2. packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts, line 30 (link)

    syntax: Inconsistent data structure - declaring Map but using as object

  3. packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts, line 40 (link)

    syntax: Accessing Map as object - should use .has() method

  4. packages/twenty-server/src/modules/contact-creation-manager/jobs/create-company-and-contact.job.ts, line 8 (link)

    style: Type and class names still use 'Contact' terminology while the service uses 'Person'. Consider renaming to maintain consistency throughout the codebase.

  5. packages/twenty-server/src/modules/match-participant/utils/add-person-email-filters-to-query-builder.ts, line 62 (link)

    syntax: Duplicate .withDeleted() call - already called on line 38

  6. packages/twenty-server/src/modules/messaging/message-participant-manager/jobs/messaging-create-company-and-contact-after-sync.job.ts, line 111 (link)

    syntax: Inconsistent terminology - should use 'people and companies' to match the updated service name and line 39

24 files reviewed, 10 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +109 to +120
const restoredCompanies = await companyRepository.updateMany(
companiesToRestore.map((company) => {
return {
criteria: company.id,
partialEntity: {
deletedAt: null,
},
};
}),
undefined,
['domainNamePrimaryLinkUrl', 'id'],
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider checking if companiesToRestore array is empty before calling updateMany to avoid unnecessary database operations

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/twenty-server/src/modules/contact-creation-manager/services/create-company.service.ts
Line: 109:120

Comment:
style: Consider checking if companiesToRestore array is empty before calling updateMany to avoid unnecessary database operations

How can I resolve this? If you propose a fix, please make it concise.

type CalendarCreateCompanyAndContactAfterSyncJobData,
CalendarCreateCompanyAndPersonAfterSyncJob,
CalendarCreateCompanyAndPersonAfterSyncJobData,
} from 'src/modules/calendar/calendar-event-participant-manager/jobs/calendar-create-company-and-contact-after-sync.job';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Import path references 'calendar-create-company-and-contact-after-sync.job' but the imported types use 'Person' terminology - consider updating the filename to match the new naming convention

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/twenty-server/src/modules/contact-creation-manager/listeners/auto-companies-and-contacts-creation-calendar-channel.listener.ts
Line: 14:14

Comment:
style: Import path references 'calendar-create-company-and-contact-after-sync.job' but the imported types use 'Person' terminology - consider updating the filename to match the new naming convention

How can I resolve this? If you propose a fix, please make it concise.

handle.toLowerCase(),
)?.existingPerson;

if (!isDefined(existingPerson) || isNull(existingPerson.deletedAt))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Logic error - condition is inverted. Should check if deletedAt is NOT null for restoration

Suggested change
if (!isDefined(existingPerson) || isNull(existingPerson.deletedAt))
if (!isDefined(existingPerson) || !isNull(existingPerson.deletedAt))
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/twenty-server/src/modules/contact-creation-manager/services/create-company-and-contact.service.ts
Line: 367:367

Comment:
logic: Logic error - condition is inverted. Should check if deletedAt is NOT null for restoration

```suggestion
      if (!isDefined(existingPerson) || !isNull(existingPerson.deletedAt))
```

How can I resolve this? If you propose a fix, please make it concise.

import { type CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import { type CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity';
import { CreateCompanyAndContactService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service';
import { CreateCompanyAndPersonService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Import path still references 'create-company-and-contact.service' but imports 'CreateCompanyAndPersonService' - consider updating the file path to match the service name

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/jobs/calendar-create-company-and-contact-after-sync.job.ts
Line: 12:12

Comment:
style: Import path still references 'create-company-and-contact.service' but imports 'CreateCompanyAndPersonService' - consider updating the file path to match the service name

How can I resolve this? If you propose a fix, please make it concise.

Copy link
Member

@charlesBochet charlesBochet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, it seems that deleted contacts are not restored though. We can check this in a followup

@charlesBochet charlesBochet merged commit 560e24f into main Oct 11, 2025
42 checks passed
@charlesBochet charlesBochet deleted the ej/14443 branch October 11, 2025 13:50
ashwintemkar pushed a commit to ashwintemkar/twenty that referenced this pull request Oct 13, 2025
…twentyhq#14562)

Done : 
- migrate standard unique fields (add : -old)
- deactivate defaultWhereClause
- restore soft deleted if upserted (same for contact creation in mailing
sync)

fixes twentyhq#14443

---------

Co-authored-by: Charles Bochet <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Cannot upload a csv mapping 2 unique identifiers

3 participants