Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a0e0c49
feat(partners): add partnerUser relation to Partner/Person/Company/Op…
rashad Jun 8, 2026
69a0521
feat(partners): partner role update perms (own records via RLS)
rashad Jun 8, 2026
6581514
feat(partners): RLS predicate config script for partner role
rashad Jun 8, 2026
6534769
feat(partners): cascade partnerUser to company + people on partner as…
rashad Jun 8, 2026
f356743
feat(partners): lock partner Opportunity edits to the stage field
rashad Jun 8, 2026
c9d1c84
chore(partners): bump version to 0.5.0 for partner RLS
rashad Jun 8, 2026
a21d089
feat(partners): grant scoped workspaceMember read to partner role + s…
rashad Jun 9, 2026
4421a18
feat(partners): add Partner User column to Validated Partners view
rashad Jun 9, 2026
b1f72b2
fix(partners): RLS relation predicates use operand IS (CONTAINS rejec…
rashad Jun 9, 2026
481874e
fix(partners): clear opportunity partnerUser on partner unassignment
rashad Jun 9, 2026
3962680
fix(partners): unassign also clears partnerUser on linked company + p…
rashad Jun 9, 2026
4265cec
feat(partners): partner edits limited to own profile + opportunity st…
rashad Jun 10, 2026
81ebf0f
fix(partners): allow partner opportunity stage + amount edits
rashad Jun 10, 2026
1fde014
refactor(partners): address code review on partner RLS cascade + role
rashad Jun 10, 2026
f2a77f5
Merge remote-tracking branch 'origin/main' into rk-partner-rls-app
rashad Jun 10, 2026
e3e7279
Merge branch 'main' into rk-partner-rls-app
rashad Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/twenty-apps/internal/twenty-partners/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "twenty-partners",
"version": "0.4.3",
"version": "0.5.0",
"license": "MIT",
"engines": {
"node": "^24.5.0",
Expand All @@ -25,7 +25,9 @@
"import:apply": "IMPORT_APPLY=1 tsx src/scripts/import-from-tft.ts",
"import:apply:prod": "ENV_FILE=.env.prod IMPORT_APPLY=1 tsx src/scripts/import-from-tft.ts",
"migrate:partner-scope": "tsx src/scripts/migrate-partner-scope.ts",
"migrate:partner-scope:prod": "ENV_FILE=.env.prod tsx src/scripts/migrate-partner-scope.ts"
"migrate:partner-scope:prod": "ENV_FILE=.env.prod tsx src/scripts/migrate-partner-scope.ts",
"rls:configure": "tsx src/scripts/configure-partner-rls.ts",
"rls:configure:prod": "ENV_FILE=.env.prod tsx src/scripts/configure-partner-rls.ts"
},
"dependencies": {
"twenty-client-sdk": "2.10.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

import { COMPANIES_AS_PARTNER_USER_FIELD_ID, PARTNER_USER_ON_COMPANY_FIELD_ID } from './partner-user-on-company.field';

export default defineField({
universalIdentifier: COMPANIES_AS_PARTNER_USER_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier,
type: FieldType.RELATION,
name: 'companiesAsPartnerUser',
label: 'Companies (as partner user)',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: PARTNER_USER_ON_COMPANY_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

import { OPPORTUNITIES_AS_PARTNER_USER_FIELD_ID, PARTNER_USER_ON_OPPORTUNITY_FIELD_ID } from './partner-user-on-opportunity.field';

export default defineField({
universalIdentifier: OPPORTUNITIES_AS_PARTNER_USER_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier,
type: FieldType.RELATION,
name: 'opportunitiesAsPartnerUser',
label: 'Opportunities (as partner user)',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: PARTNER_USER_ON_OPPORTUNITY_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const OPPORTUNITY_DESIGN_DOC_STATUS_FIELD_ID = 'cc6b8a59-f860-493f-8b9a-f138c078fbf1';

export default defineField({
universalIdentifier: 'cc6b8a59-f860-493f-8b9a-f138c078fbf1',
universalIdentifier: OPPORTUNITY_DESIGN_DOC_STATUS_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
type: FieldType.SELECT,
name: 'designDocStatus',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const OPPORTUNITY_DESIGN_DOC_URL_FIELD_ID = '37e5428c-6c8c-4616-b626-f0ea1caa443d';

export default defineField({
universalIdentifier: '37e5428c-6c8c-4616-b626-f0ea1caa443d',
universalIdentifier: OPPORTUNITY_DESIGN_DOC_URL_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
type: FieldType.LINKS,
name: 'designDocUrl',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const OPPORTUNITY_HOSTING_TYPE_FIELD_ID = '7ac7517f-bbca-4b4c-8996-6f864f71219b';

export default defineField({
universalIdentifier: '7ac7517f-bbca-4b4c-8996-6f864f71219b',
universalIdentifier: OPPORTUNITY_HOSTING_TYPE_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
type: FieldType.SELECT,
name: 'hostingType',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const OPPORTUNITY_LOST_REASON_FIELD_ID = '834e233d-b171-409e-825f-77ac49b0f19d';

export default defineField({
universalIdentifier: '834e233d-b171-409e-825f-77ac49b0f19d',
universalIdentifier: OPPORTUNITY_LOST_REASON_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
type: FieldType.TEXT,
name: 'lostReason',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const OPPORTUNITY_NUMBER_OF_SEATS_FIELD_ID = '90c683ec-2365-4533-a187-7b9ae162b753';

export default defineField({
universalIdentifier: '90c683ec-2365-4533-a187-7b9ae162b753',
universalIdentifier: OPPORTUNITY_NUMBER_OF_SEATS_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
type: FieldType.NUMBER,
name: 'numberOfSeats',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const OPPORTUNITY_SUBSCRIPTION_FREQUENCY_FIELD_ID = '59d5de53-202f-4913-a417-8a08970d87cc';

export default defineField({
universalIdentifier: '59d5de53-202f-4913-a417-8a08970d87cc',
universalIdentifier: OPPORTUNITY_SUBSCRIPTION_FREQUENCY_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
type: FieldType.SELECT,
name: 'subscriptionFrequency',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const OPPORTUNITY_SUBSCRIPTION_TYPE_FIELD_ID = 'a58214e9-38f9-4faf-8927-09b3980fd8c3';

export default defineField({
universalIdentifier: 'a58214e9-38f9-4faf-8927-09b3980fd8c3',
universalIdentifier: OPPORTUNITY_SUBSCRIPTION_TYPE_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
type: FieldType.SELECT,
name: 'subscriptionType',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const OPPORTUNITY_TFT_ID_FIELD_ID = '2e3e1d04-2719-4e0d-9a6b-ec73acf896c5';

export default defineField({
universalIdentifier: '2e3e1d04-2719-4e0d-9a6b-ec73acf896c5',
universalIdentifier: OPPORTUNITY_TFT_ID_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
type: FieldType.TEXT,
name: 'tftOpportunityId',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { FieldType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const OPPORTUNITY_USE_CASE_FIELD_ID = '1bc57f52-a621-4243-ae3e-05c3f504b90c';

export default defineField({
universalIdentifier: '1bc57f52-a621-4243-ae3e-05c3f504b90c',
universalIdentifier: OPPORTUNITY_USE_CASE_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
type: FieldType.TEXT,
name: 'useCase',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

import { PARTNER_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
import { PARTNER_PROFILES_AS_USER_FIELD_ID, PARTNER_USER_ON_PARTNER_FIELD_ID } from './partner-user-on-partner.field';

export default defineField({
universalIdentifier: PARTNER_PROFILES_AS_USER_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier,
type: FieldType.RELATION,
name: 'partnerProfilesAsUser',
label: 'Partner Profiles (as user)',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier: PARTNER_OBJECT_UNIVERSAL_IDENTIFIER,
relationTargetFieldMetadataUniversalIdentifier: PARTNER_USER_ON_PARTNER_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FieldType, OnDeleteAction, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const PARTNER_USER_ON_COMPANY_FIELD_ID = 'f8f1dbb6-9f9f-4c13-9dab-a19ea6223801';
export const COMPANIES_AS_PARTNER_USER_FIELD_ID = '6235bf5b-9ecf-482f-b8b0-e6ae37659638';

export default defineField({
universalIdentifier: PARTNER_USER_ON_COMPANY_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.company.universalIdentifier,
type: FieldType.RELATION,
name: 'partnerUser',
label: 'Partner User',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: COMPANIES_AS_PARTNER_USER_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'partnerUserId',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FieldType, OnDeleteAction, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const PARTNER_USER_ON_OPPORTUNITY_FIELD_ID = '7022a40a-a954-4e6b-96b8-1faff0919ec0';
export const OPPORTUNITIES_AS_PARTNER_USER_FIELD_ID = 'b03a26e8-6d9d-4d70-930b-2006929c9869';

export default defineField({
universalIdentifier: PARTNER_USER_ON_OPPORTUNITY_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.opportunity.universalIdentifier,
type: FieldType.RELATION,
name: 'partnerUser',
label: 'Partner User',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: OPPORTUNITIES_AS_PARTNER_USER_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'partnerUserId',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { FieldType, OnDeleteAction, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

import { PARTNER_OBJECT_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';

export const PARTNER_USER_ON_PARTNER_FIELD_ID = '0e49f2e4-1e45-433d-bf49-79acc0b06d0e';
export const PARTNER_PROFILES_AS_USER_FIELD_ID = 'c0791b65-802f-4ae0-85e0-5434459214f1';

export default defineField({
universalIdentifier: PARTNER_USER_ON_PARTNER_FIELD_ID,
objectUniversalIdentifier: PARTNER_OBJECT_UNIVERSAL_IDENTIFIER,
type: FieldType.RELATION,
name: 'partnerUser',
label: 'Partner User',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: PARTNER_PROFILES_AS_USER_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'partnerUserId',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { FieldType, OnDeleteAction, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

export const PARTNER_USER_ON_PERSON_FIELD_ID = '2fc8b812-b7f9-49f2-829a-050be7ee1e5e';
export const PERSONS_AS_PARTNER_USER_FIELD_ID = '94d17fac-3d32-4b13-9fb3-a671d4bf9c46';

export default defineField({
universalIdentifier: PARTNER_USER_ON_PERSON_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
type: FieldType.RELATION,
name: 'partnerUser',
label: 'Partner User',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: PERSONS_AS_PARTNER_USER_FIELD_ID,
universalSettings: {
relationType: RelationType.MANY_TO_ONE,
onDelete: OnDeleteAction.SET_NULL,
joinColumnName: 'partnerUserId',
},
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { FieldType, RelationType, STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS, defineField } from 'twenty-sdk/define';

import { PARTNER_USER_ON_PERSON_FIELD_ID, PERSONS_AS_PARTNER_USER_FIELD_ID } from './partner-user-on-person.field';

export default defineField({
universalIdentifier: PERSONS_AS_PARTNER_USER_FIELD_ID,
objectUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.workspaceMember.universalIdentifier,
type: FieldType.RELATION,
name: 'personsAsPartnerUser',
label: 'People (as partner user)',
isNullable: true,
relationTargetObjectMetadataUniversalIdentifier: STANDARD_OBJECT_UNIVERSAL_IDENTIFIERS.person.universalIdentifier,
relationTargetFieldMetadataUniversalIdentifier: PARTNER_USER_ON_PERSON_FIELD_ID,
universalSettings: {
relationType: RelationType.ONE_TO_MANY,
},
});
Loading
Loading