fix(server): type WasRemovedInUpgrade columns with WasRemovedInUpgrade<> (#21785)
## Context WasRemovedInUpgrade type brand was introduced in https://github.com/twentyhq/twenty/pull/21228/changes#diff-1b6d688610669a46b3ee8e3a41b1c7eb0ee03e19146d0d249f95df6e56164a92R15 for the `isCustom` property deprecation. The @WasRemovedInUpgrade decorator and the WasRemovedInUpgrade<T> type are meant to go together: the type brand makes the property optional in every derived flat-entity type, so the column only needs to be declared on the entity itself. RolePermissionFlagEntity.flag had the decorator but was typed as a plain PermissionFlagType, forcing the property to be supplied everywhere. This PR: - Types flag as WasRemovedInUpgrade<PermissionFlagType> (matching the isCustom reference impl on object/field metadata). - Removes the now-redundant flag from the flat-entity construction sites, the create input, and the service call site — leaving it only on the entity. The GraphQL RolePermissionFlagDTO.flag is kept (it's an API field derived from permissionFlag.key, not the removed column). - Fixes a latent brand-leak in the flat-entity config type: toStringify is computed via object-detection, and a branded type reads as an object. This was harmless for boolean but wrongly forced toStringify: true for enum/string columns. Added UnwrapWasRemovedInUpgrade<T> and applied it so the brand is transparent making the pattern work for any type, not just booleans. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/21785?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
-14
@@ -1,7 +1,3 @@
|
||||
import {
|
||||
PermissionFlagType,
|
||||
SystemPermissionFlag,
|
||||
} from 'twenty-shared/constants';
|
||||
import { v5 } from 'uuid';
|
||||
|
||||
import { type UniversalFlatRolePermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-role-permission-flag.type';
|
||||
@@ -9,10 +5,6 @@ import { type UniversalFlatRolePermissionFlag } from 'src/engine/workspace-manag
|
||||
export const ROLE_PERMISSION_FLAG_UUID_NAMESPACE =
|
||||
'b9a3b3b3-58a3-4f6c-9c1f-3a4f6c9c1f3a';
|
||||
|
||||
const SYSTEM_PERMISSION_FLAG_BY_UNIVERSAL_IDENTIFIER = Object.fromEntries(
|
||||
Object.entries(SystemPermissionFlag).map(([key, uuid]) => [uuid, key]),
|
||||
) as Record<string, PermissionFlagType | undefined>;
|
||||
|
||||
export const fromPermissionFlagToUniversalFlatRolePermissionFlag = ({
|
||||
permissionFlagUniversalIdentifier,
|
||||
roleUniversalIdentifier,
|
||||
@@ -29,17 +21,11 @@ export const fromPermissionFlagToUniversalFlatRolePermissionFlag = ({
|
||||
ROLE_PERMISSION_FLAG_UUID_NAMESPACE,
|
||||
);
|
||||
|
||||
const resolvedFlag =
|
||||
SYSTEM_PERMISSION_FLAG_BY_UNIVERSAL_IDENTIFIER[
|
||||
permissionFlagUniversalIdentifier
|
||||
];
|
||||
|
||||
return {
|
||||
universalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
roleUniversalIdentifier,
|
||||
permissionFlagUniversalIdentifier,
|
||||
flag: resolvedFlag as PermissionFlagType,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
+6
@@ -12,6 +12,12 @@ export type WasRemovedInUpgrade<T> = T & {
|
||||
readonly [wasRemovedInUpgradeBrand]?: true;
|
||||
};
|
||||
|
||||
export type UnwrapWasRemovedInUpgrade<T> = [T] extends [
|
||||
WasRemovedInUpgrade<infer TUnwrapped>,
|
||||
]
|
||||
? TUnwrapped
|
||||
: T;
|
||||
|
||||
type WasRemovedInUpgradeKeys<TEntity> = {
|
||||
[K in keyof TEntity]: typeof wasRemovedInUpgradeBrand extends keyof TEntity[K]
|
||||
? K
|
||||
|
||||
+7
-2
@@ -1,5 +1,6 @@
|
||||
import { type AllMetadataName } from 'twenty-shared/metadata';
|
||||
|
||||
import { type UnwrapWasRemovedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-removed-in-upgrade.decorator';
|
||||
import { type MetadataEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-entity.type';
|
||||
import { type MetadataManyToOneJoinColumn } from 'src/engine/metadata-modules/flat-entity/types/metadata-many-to-one-join-column.type';
|
||||
import { type ScalarFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/scalar-flat-entity.type';
|
||||
@@ -29,9 +30,13 @@ type MetadataEntityPropertyConfiguration<
|
||||
toStringify: K extends ExtractJsonbProperties<MetadataEntity<TMetadataName>>
|
||||
? true
|
||||
: K extends keyof MetadataEntity<TMetadataName>
|
||||
? NonNullable<MetadataEntity<TMetadataName>[K]> extends Date
|
||||
? NonNullable<
|
||||
UnwrapWasRemovedInUpgrade<MetadataEntity<TMetadataName>[K]>
|
||||
> extends Date
|
||||
? false
|
||||
: HasObjectInUnion<MetadataEntity<TMetadataName>[K]>
|
||||
: HasObjectInUnion<
|
||||
UnwrapWasRemovedInUpgrade<MetadataEntity<TMetadataName>[K]>
|
||||
>
|
||||
: boolean;
|
||||
toCompare: boolean;
|
||||
isOverridable?: boolean;
|
||||
|
||||
+1
-2
@@ -21,7 +21,7 @@ export const fromCreateRolePermissionFlagInputToFlatRolePermissionFlagToCreate =
|
||||
>): UniversalFlatRolePermissionFlag & {
|
||||
id: string;
|
||||
} => {
|
||||
const { roleId, permissionFlagId, flag, universalIdentifier } =
|
||||
const { roleId, permissionFlagId, universalIdentifier } =
|
||||
createRolePermissionFlagInput;
|
||||
const now = new Date().toISOString();
|
||||
|
||||
@@ -38,7 +38,6 @@ export const fromCreateRolePermissionFlagInputToFlatRolePermissionFlagToCreate =
|
||||
applicationUniversalIdentifier: flatApplication.universalIdentifier,
|
||||
permissionFlagUniversalIdentifier,
|
||||
roleUniversalIdentifier,
|
||||
flag,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
|
||||
-3
@@ -1,8 +1,5 @@
|
||||
import { type PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
export type CreateRolePermissionFlagInput = {
|
||||
roleId: string;
|
||||
permissionFlagId: string;
|
||||
flag: PermissionFlagType;
|
||||
universalIdentifier?: string;
|
||||
};
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ export class RolePermissionFlagEntity extends SyncableEntity {
|
||||
'2.7.0_FinalizeRolePermissionFlagCutoverFastInstanceCommand_1779600000000',
|
||||
})
|
||||
@Column({ nullable: false, type: 'varchar' })
|
||||
flag: PermissionFlagType;
|
||||
flag: WasRemovedInUpgrade<PermissionFlagType>;
|
||||
|
||||
@WasIntroducedInUpgrade({
|
||||
upgradeCommandName:
|
||||
|
||||
-2
@@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { type PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
@@ -118,7 +117,6 @@ export class RolePermissionFlagService {
|
||||
createRolePermissionFlagInput: {
|
||||
roleId: input.roleId,
|
||||
permissionFlagId: permissionFlag.id,
|
||||
flag: permissionFlag.key as PermissionFlagType,
|
||||
},
|
||||
flatApplication: workspaceCustomFlatApplication,
|
||||
flatPermissionFlagMaps,
|
||||
|
||||
+1
-1
@@ -294,7 +294,7 @@ export class WorkspaceRolesPermissionsCacheService extends WorkspaceCacheProvide
|
||||
// cursor (@WasIntroducedInUpgrade), so fall back to the legacy `flag` column.
|
||||
return (
|
||||
rolePermissionFlag.permissionFlag?.universalIdentifier ??
|
||||
SystemPermissionFlag[rolePermissionFlag.flag]
|
||||
SystemPermissionFlag[rolePermissionFlag.flag as PermissionFlagType]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user