# Introduction
Remove the v2 feature flag for view-field field-metadata and
object-metadata metadata entities
## Some details
- Disabled nestjs-query for object metadata creation and explicitly
calling it
- removed all v1 integration tests files
## Remarks
Not remove v2 referencing in both filenaming right now will handle that
globally later
## Breaking change
Due to object metadata resolver createOne standardization had to rename
the input from `CreateObjectInput` to `CreateOneObjectInput`
# Introduction
While removing the v1 https://github.com/twentyhq/twenty/pull/15823 I've
encountered the method `objectMetadataService.deleteObjectsMetadata`
that I didn't wanted to migrate as it is and if it's not challenging its
existence legitimacy.
## Motivations
In a nutshell on a workspace deletion object metadata and field metadata
cascading deletion is correclty handled
But that's not the case for all of a workspaces entities ( roles,
workspaceMigrations ) I suspect that we did not defined the foreignKey
explicitly through `typeorm`
## Battle testing v2
I still decided to give a try to a complex operation in the v2 such as a
workspace all object metadata deletion.
Spoiler it failed due to object being interdependent between them and
not being topologically sorted ( morph relation can introduce circular
dep anw ).
Note: Even after removing the delete field on delete object aggregator
we end up with an equivalent circular dep error which an object and its
field metadata identifier connection.
## Elegant `DEFERRED` and `DEFERRABLE` foreign keys
The most safe, low level solution would be to make all field relations
deferrable and start the runner transaction as deferred. But this
requires a quite invasive migration of existing FK
## On cascade solution
I've opted for the quick fix, on object or field deletion spread
cacasde.
It's pretty safe as the builder priorly validates the deletion and and
its related entities integrity
Only for both field and object deletion action types in v2 runner
## Integration coverage
Added a test that will scan a new workspace database core schema tables
and expect now result after workspace deletion through its only user
deletion
## Context
inferDeletionFromEntities only accepts a set of keys for each entities
that needs deletion. This could be error-prone if tmr we want to add a
new side effect and forget to add the entity when in practice you want
to delete all entities that are in the fromToAllFlatEntityMaps (this is
the case for applications for example)
# Introduction
related to https://github.com/twentyhq/core-team-issues/issues/1833
In this PR we're starting the sync-metadata and standardIds deprecation
by introducing `twenty-standard` application that will regroup every
standard object such as company and opportunities. But also the
`custom-workspace-application` which is an app created at the same time
as a workspace and that will regroup everything configure within the
workspace ( custom objects fields etc )
## What's done
On both new workspace and seeded workspace creation:
- Creating a custom workspace app
- Creating a twenty standard app
- Refactored the seed core schema and workspace creation to be run
within a transaction in order to handle circular dependency foreignkey
requirements ( which is deferred for app toward workspace )
- Updated workspace entity to have a custom workspace relation (
nullable for the moment until we implem an upgrade command to handle
retro comp )
- Integration testing on user, workspace creation deletion and expected
default apps creation
- ~~Soft deleted user on `deleteUser`~~ Done by marie and rebased on it
## What's next
- Update seeder to propagate the `twenty-standard` workspace
`applicationId` to every standard synchronized entities ( cheap and fast
iteration through the about to be deprecated sync-metadata as an easy
way to synchronize standards metadata entities ).
- Update seeder to propagate the `custom-workspace-application`
workspace `applicationId` to anything custom ( `pets` and `rockets` )
- Prepend `custom-workspace-application` `applicationId` to every
metadata API operations ( create a specific cache etc )
- Upgrade command on all existing workspace to create a custom app and
associate its applicationId to any existing custom entities
- Make `universalIdentifier` and `applicationId` required for any
syncable entity
**Root Cause**
Many-to-one relation filters were being exposed under the relation name
(e.g., `company`) instead of the corresponding foreign-key attribute
(e.g., `companyId`).
**Change**
- Detect many-to-one relation metadata and remap those filter keys to
`<fieldName>Id`.
- Removed some unused code unrelated to this fix.
## Overview
This PR strengthens our permission system by introducing more granular
role-based access control across the platform.
## Changes
### New Permissions Added
- **Applications** - Control who can install and manage applications
- **Layouts** - Control who can customize page layouts and UI structure
- **AI** - Control access to AI features and agents
- **Upload File** - Separate permission for file uploads
- **Download File** - Separate permission for file downloads (frontend
visibility)
### Security Enhancements
- Implemented whitelist-based validation for workspace field updates
- Added explicit permission guards to core entity resolvers
- Enhanced ESLint rule to enforce permission checks on all mutations
- Created `CustomPermissionGuard` and `NoPermissionGuard` for better
code documentation
### Affected Components
- Core entity resolvers: webhooks, files, domains, applications,
layouts, postgres credentials
- Workspace update mutations now use whitelist validation
- Settings UI updated with new permission controls
### Developer Experience
- ESLint now catches missing permission guards during development
- Explicit guard markers make permission requirements clear in code
review
- Comprehensive test coverage for new permission logic
## Testing
- ✅ All TypeScript type checks pass
- ✅ ESLint validation passes
- ✅ New permission guards properly enforced
- ✅ Frontend UI displays new permissions correctly
## Migration Notes
Existing workspaces will need to assign the new permissions to roles as
needed. By default, all new permissions are set to `false` for non-admin
roles.
Until this is done: [Make workspaceMembers
non-system](https://github.com/twentyhq/twenty/issues/15688)
Let's make that update to allow us to have workspaces with
workspaceMember not being system object, to allow user to customize
their data model.
**Before**
- any user with workpace_members permission was able to remove a user
from their workspace. This triggered the deletion of workspaceMember +
of userWorkspace, but did not delete the user (even if they had no
workspace left) nor the roleTarget (acts as junction between role and
userWorkspace) which was left with a userWorkspaceId pointing to
nothing. This is because roleTarget points to userWorkspaceId but the
foreign key constraint was not implemented
- any user could delete their own account. This triggered the deletion
of all their workspaceMembers, but not of their userWorkspace nor their
user nor the roleTarget --> we have orphaned userWorkspace, not
technically but product wise - a userWorkspace without a workspaceMember
does not make sense
So the problems are
- we have some roleTargets pointing to non-existing userWorkspaceId
(which caused https://github.com/twentyhq/twenty/issues/14608 )
- we have userWorkspaces that should not exist and that have no
workspaceMember counterpart
- it is not possible for a user to leave a workspace by themselves, they
can only leave all workspaces at once, except if they are being removed
from the workspace by another user
**Now**
- if a user has multiple workspaces, they are given the possibility to
leave one workspace while remaining in the others (we show two buttons:
Leave workspace and Delete account buttons). if a user has just one
workspace, they only see Delete account
- when a user leaves a workspace, we delete their workspaceMember,
userWorkspace and roleTarget. If they don't belong to any other
workspace we also soft-delete their user
- soft-deleted users get hard deleted after 30 days thanks to a cron
- we have two commands to clean the orphans roleTarget and userWorkspace
(TODO: query db to see how many must be run)
**Next**
- once the commands have been run, we can implement and introduce the
foreign key constraint on roleTarget
Fixes https://github.com/twentyhq/twenty/issues/14608
As title
- adds decorators in twenty-sdk
- update twenty-cli load-manifest to it gets @FieldMetadata infos +
testing
- update twenty-server so it CRUD fields properly, using
universalIdentifier
- Fix UI so we can update managed objects records
- move FieldMetadata items from twenty-server to twenty-shared
Prevent users from creating v2 chart types via the api.
Only created unit tests and not integration tests (since it's not that
important, and the v2 will be released soon), but tested via the api
playground.
# Introduction
This PR aims to deprecate having to manually handle optimistic side
effect foreign key addition in the whole v2 experience.
This PR implements the strong basis + builder refactor of the optimistic
computation of a given flat entity maps with its related flat entity
maps ( runner needs a small refactor on actions type definition first )
Flat entity maps updates through mutations are now only scoped to the
generic entity builder ( very isolated )
## What's next
- Refactor actions v2 type definition to gain grain over `metadataName`
and action operation ( `create` `delete` `update` ).
from `{type: 'create_view_field'}` to `{metadataName: 'view_field',
type: 'create' }`
- Use new optimistic tool computation tools
- Only invalidate impacted flat maps cache
## New tools
Strictly dynamically typed new flat entity maps tools
- `addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
-
`deleteFlatEntityFromFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
## Unit test
Adding basic unit testing coverage to introduced tools
## `FlatEntityValidationArgs`
From
```ts
export type FlatEntityValidationArgs<T extends AllMetadataName> = {
flatEntityToValidate: MetadataFlatEntity<T>;
optimisticFlatEntityMaps: MetadataFlatEntityMaps<T>;
mutableDependencyOptimisticFlatEntityMaps: MetadataValidationRelatedFlatEntityMaps<T>;
workspaceId: string;
remainingFlatEntityMapsToValidate: MetadataFlatEntityMaps<T>;
buildOptions: WorkspaceMigrationBuilderOptions;
};
```
To
```ts
export type FlatEntityValidationArgs<T extends AllMetadataName> = {
flatEntityToValidate: MetadataFlatEntity<T>;
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: MetadataFlatEntityAndRelatedFlatEntityMapsForValidation<T>;
workspaceId: string;
remainingFlatEntityMapsToValidate: MetadataFlatEntityMaps<T>;
buildOptions: WorkspaceMigrationBuilderOptions;
};
```
## Context
Having rollout feature for new workspaces creates bad experience for new
users and doesn't bring as much value as existing ones anyway. Removing
migration v2 from default feature flag and the whole concept of default
feature flag
## Tests
### makeSureDashboardNamingAvailableCommand
Case 1: no dashboard custom object
Case 2: with dashboard custom object
### SeedDashboardViewCommand
Case 1: no existing view
Case 2: with existing view
- Allow users to choose the date granularity of the x axis and the group
by of the y axis on a bar chart
- Display those options conditionally
- Store timezones in graphs: each graph has its own timezone, defaults
to the user timezone. There will be a picker in the v2 to choose the
timezone. For now the timezone is not used by the backend, but it will
be used in filters and in group by queries.
- Store first day of the week
https://github.com/user-attachments/assets/66a5d156-dd93-4ebe-8c8f-d172f93e25be
The removal of views from the workspaces schema implies the deletion of
```
view
viewFilter
viewFilterGroup
viewGroup
viewSort
```
Before it is created again in the core schema.
However, the syncmetadata that executes the pending migration fails
because it will try to
`query failed: DROP TABLE "workspace_xxxx"."view"`
before the removal of the other view related tables that contains a view
foreign key with this kind of message
> constraint FK_c5ab40cd4debb51d588752a4857 on table "viewField" depends
on table view
# Adding MORPH support to the relationDecorator
Extends the @WorkspaceRelation decorator to support morph relations by
adding isMorphRelation and morphId options
The implementation enforces type safety through TypeScript discriminated
unions, ensuring morphId is required when isMorphRelation: true, and
includes runtime validation that throws a descriptive error if morphId
is missing.
The morph relation metadata is properly stored in metadataArgsStorage
and integrates seamlessly with the existing StandardFieldRelationFactory
to convert decorated fields into FieldMetadataType.MORPH_RELATION
entities with appropriate database constraints.
To test :
in person entity, add
```ts
@WorkspaceRelation({
standardId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordCompany,
type: RelationType.MANY_TO_ONE,
label: msg`Test Related Record of Company`,
description: msg`Test relation to company`,
icon: 'IconLink',
inverseSideTarget: () => CompanyWorkspaceEntity,
inverseSideFieldKey: 'testRelatedPeople',
onDelete: RelationOnDeleteAction.CASCADE,
isMorphRelation: true,
morphId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordMorphId,
})
@WorkspaceIsNullable()
@WorkspaceIsSystem()
testRelatedRecordCompany: Relation<CompanyWorkspaceEntity> | null;
@WorkspaceJoinColumn('testRelatedRecordCompany')
testRelatedRecordCompanyId: string | null;
@WorkspaceRelation({
standardId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordOpportunity,
type: RelationType.MANY_TO_ONE,
label: msg`Test Related Record of Opportunity`,
description: msg`Test relation to opportunity`,
icon: 'IconLink',
inverseSideTarget: () => OpportunityWorkspaceEntity,
inverseSideFieldKey: 'testRelatedPeople',
onDelete: RelationOnDeleteAction.CASCADE,
isMorphRelation: true,
morphId: PERSON_STANDARD_FIELD_IDS.testRelatedRecordMorphId,
})
@WorkspaceIsNullable()
@WorkspaceIsSystem()
testRelatedRecordOpportunity: Relation<OpportunityWorkspaceEntity> | null;
@WorkspaceJoinColumn('testRelatedRecordOpportunity')
testRelatedRecordOpportunityId: string | null;
```
in opportunity,
```ts
@WorkspaceRelation({
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.testRelatedPeople,
type: RelationType.ONE_TO_MANY,
label: msg`Test Related People`,
description: msg`People linked to the opportunity via Test Related Record`,
icon: 'IconUsers',
inverseSideTarget: () => PersonWorkspaceEntity,
inverseSideFieldKey: 'testRelatedRecordOpportunity',
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsNullable()
@WorkspaceIsSystem()
testRelatedPeople: Relation<PersonWorkspaceEntity[]>;
```
in company,
```ts
@WorkspaceRelation({
standardId: COMPANY_STANDARD_FIELD_IDS.testRelatedPeople,
type: RelationType.ONE_TO_MANY,
label: msg`Test Related People`,
description: msg`People linked to the company via Test Related Record`,
icon: 'IconUsers',
inverseSideTarget: () => PersonWorkspaceEntity,
inverseSideFieldKey: 'testRelatedRecordCompany',
onDelete: RelationOnDeleteAction.SET_NULL,
})
@WorkspaceIsNullable()
@WorkspaceIsSystem()
testRelatedPeople: Relation<PersonWorkspaceEntity[]>;
```
In constants/standard-field-ids.ts, add the standard fields ids
```ts
// COMPANY_STANDARD_FIELD_IDS
testRelatedPeople: '20202020-7a90-47e9-b31f-916a716fd212',
// OPPORTUNITY_STANDARD_FIELD_IDS
testRelatedPeople: '20202020-7a90-47e9-b31f-916a716fd213',
// PERSON_STANDARD_FIELD_IDS
testRelatedRecordOpportunity: '20202020-5a90-47e9-b31f-916a716fd211',
testRelatedRecordCompany: '20202020-1eb2-4298-910b-66b015b36d72',
testRelatedRecordMorphId: '20202020-6ccf-48e3-bb92-bc9961bc011e',
```
To be done in an other PR, move graphql-query-runner handlers in
common-api-query-runner folder (+ renaming + typing improvments)
Fixes :
- totalCount on findMany (Rest)
- getAllSelectedFields did not handle composite field (Rest)
- issue with endCursor (Rest)
- args processing for updateOne and createOne (Rest + Gql)
- fix findDuplicates (Gql)
# Introduction
A while ago we migrated view from workspace to metadata
Their standard objects workspace entities declaration remained we can
now remove them
## Deprecating commands before 1.5
The view migration command from workspace to metadata was introduced in
`1.5.0`. Removing the `baseWorkspaceEntity` make this command obsolete.
If tomorrow twenty handles auto upgrade in latest and a user having an
instance in `1.3.0` starts auto-upgrading he won't be able to migrate
his views ( that's why we should not support upgrade before 1.5 anymore
here )
We will have the same use case with FavoritesFolders
Now that all existing and new workspaces have the following syncStage:
- CALENDAR_EVENT_LIST_FETCH_PENDING
- MESSAGE_LIST_FETCH_PENDING
We can fully deprecate the old FULL_CALENDAR_EVENT_LIST_FETCH_PENDING
and PARTIAL_CALENDAR_EVENT_LIST_FETCH_PENDING (full vs partial is now
directly inferred from the presence of a cursor)
# Introduction
Fixing composite field update by computing field column type for each of
its properties instead of globally
## Coverage
Added integration tests for each composite field on both successful
`create` and `update`
```ts
Test Suites: 17 passed, 17 total
Tests: 104 passed, 104 total
Snapshots: 14 passed, 14 total
Time: 135.431 s, estimated 143 s
```
## Conlusion
Related to https://github.com/twentyhq/core-team-issues/issues/1753
## Context
When updating both enum options and defaultValue, the old default might
not be in the new options (or vice versa), causing PostgreSQL constraint
violations regardless of update order.
## Solution
Sort updates to process defaultValue last; before updating options,
temporarily set the new defaultValue in metadata so alterEnumValues
creates the column with the correct default, then skip the redundant
defaultValue update handler.
# Introduction
Adding a view field to a view in v2 would be optimistically rendered by
the front but on refresh would not get persisted.
That's because we cache both:
```ts
useCachedMetadata({
cacheGetter: cacheStorageService.get.bind(cacheStorageService),
cacheSetter: cacheStorageService.set.bind(cacheStorageService),
operationsToCache: ['ObjectMetadataItems', 'FindAllCoreViews'],
}),
```
With keys that look like:
```ts
return `graphql:operations:${operationName}:${workspace.id}:${workspaceMetadataVersion}:${locale}:${queryHash}`;
```
It was functional in v1 as we would be incrementing metadata version
often.
In v2 it gets incremented only if implies an interaction to metadata
object or fields ( will be deprecated in the future though, until we
finish the // run )
The fix was to check if an `view` or related has been processed in the
workspace migration or if we incremented the metadata in order to
manually flush the `findAllCoreViews` redis cache.