Migrate page layout services (#16443)

Last step of the page layout migration:
- Migrate services
- Write integration tests
This commit is contained in:
Raphaël Bosi
2025-12-10 14:49:44 +01:00
committed by GitHub
parent 1839f8e946
commit f48adb5f07
157 changed files with 6099 additions and 7586 deletions
@@ -0,0 +1,40 @@
import { trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
import { type CreatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/create-page-layout-tab.input';
export type FromCreatePageLayoutTabInputToFlatPageLayoutTabToCreateArgs = {
createPageLayoutTabInput: CreatePageLayoutTabInput;
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const fromCreatePageLayoutTabInputToFlatPageLayoutTabToCreate = ({
createPageLayoutTabInput: rawCreatePageLayoutTabInput,
workspaceId,
workspaceCustomApplicationId,
}: FromCreatePageLayoutTabInputToFlatPageLayoutTabToCreateArgs): FlatPageLayoutTab => {
const createPageLayoutTabInput =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawCreatePageLayoutTabInput,
['title'],
);
const createdAt = new Date().toISOString();
const pageLayoutTabId = v4();
return {
id: pageLayoutTabId,
title: createPageLayoutTabInput.title,
position: createPageLayoutTabInput.position ?? 0,
pageLayoutId: createPageLayoutTabInput.pageLayoutId,
workspaceId,
createdAt,
updatedAt: createdAt,
deletedAt: null,
universalIdentifier: pageLayoutTabId,
applicationId: workspaceCustomApplicationId,
widgetIds: [],
};
};
@@ -0,0 +1,44 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
import {
PageLayoutTabException,
PageLayoutTabExceptionCode,
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception';
export type DeletePageLayoutTabInput = {
id: string;
};
export const fromDeletePageLayoutTabInputToFlatPageLayoutTabOrThrow = ({
deletePageLayoutTabInput: rawDeletePageLayoutTabInput,
flatPageLayoutTabMaps,
}: {
deletePageLayoutTabInput: DeletePageLayoutTabInput;
flatPageLayoutTabMaps: FlatPageLayoutTabMaps;
}): FlatPageLayoutTab => {
const { id: pageLayoutTabId } = extractAndSanitizeObjectStringFields(
rawDeletePageLayoutTabInput,
['id'],
);
const existingFlatPageLayoutTabToDelete =
flatPageLayoutTabMaps.byId[pageLayoutTabId];
if (!isDefined(existingFlatPageLayoutTabToDelete)) {
throw new PageLayoutTabException(
t`Page layout tab to delete not found`,
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
);
}
return {
...existingFlatPageLayoutTabToDelete,
deletedAt: new Date().toISOString(),
};
};
@@ -0,0 +1,41 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
import {
PageLayoutTabException,
PageLayoutTabExceptionCode,
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception';
export type DestroyPageLayoutTabInput = {
id: string;
};
export const fromDestroyPageLayoutTabInputToFlatPageLayoutTabOrThrow = ({
destroyPageLayoutTabInput,
flatPageLayoutTabMaps,
}: {
destroyPageLayoutTabInput: DestroyPageLayoutTabInput;
flatPageLayoutTabMaps: FlatPageLayoutTabMaps;
}): FlatPageLayoutTab => {
const { id: pageLayoutTabId } = extractAndSanitizeObjectStringFields(
destroyPageLayoutTabInput,
['id'],
);
const existingFlatPageLayoutTabToDestroy =
flatPageLayoutTabMaps.byId[pageLayoutTabId];
if (!isDefined(existingFlatPageLayoutTabToDestroy)) {
throw new PageLayoutTabException(
t`Page layout tab to destroy not found`,
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
);
}
return existingFlatPageLayoutTabToDestroy;
};
@@ -0,0 +1,51 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
import {
PageLayoutTabException,
PageLayoutTabExceptionCode,
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception';
export type RestorePageLayoutTabInput = {
id: string;
};
export const fromRestorePageLayoutTabInputToFlatPageLayoutTabOrThrow = ({
restorePageLayoutTabInput,
flatPageLayoutTabMaps,
}: {
restorePageLayoutTabInput: RestorePageLayoutTabInput;
flatPageLayoutTabMaps: FlatPageLayoutTabMaps;
}): FlatPageLayoutTab => {
const { id: pageLayoutTabId } = extractAndSanitizeObjectStringFields(
restorePageLayoutTabInput,
['id'],
);
const existingFlatPageLayoutTabToRestore =
flatPageLayoutTabMaps.byId[pageLayoutTabId];
if (!isDefined(existingFlatPageLayoutTabToRestore)) {
throw new PageLayoutTabException(
t`Page layout tab to restore not found`,
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
);
}
if (!isDefined(existingFlatPageLayoutTabToRestore.deletedAt)) {
throw new PageLayoutTabException(
t`Page layout tab is not deleted and cannot be restored`,
PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA,
);
}
return {
...existingFlatPageLayoutTabToRestore,
deletedAt: null,
};
};
@@ -0,0 +1,54 @@
import { t } from '@lingui/core/macro';
import {
extractAndSanitizeObjectStringFields,
isDefined,
} from 'twenty-shared/utils';
import { FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant';
import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
import { type UpdatePageLayoutTabInput } from 'src/engine/metadata-modules/page-layout/dtos/inputs/update-page-layout-tab.input';
import {
PageLayoutTabException,
PageLayoutTabExceptionCode,
} from 'src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception';
import { mergeUpdateInExistingRecord } from 'src/utils/merge-update-in-existing-record.util';
export type UpdatePageLayoutTabInputWithId = {
id: string;
update: UpdatePageLayoutTabInput;
};
export const fromUpdatePageLayoutTabInputToFlatPageLayoutTabToUpdateOrThrow = ({
updatePageLayoutTabInput: rawUpdatePageLayoutTabInput,
flatPageLayoutTabMaps,
}: {
updatePageLayoutTabInput: UpdatePageLayoutTabInputWithId;
flatPageLayoutTabMaps: FlatPageLayoutTabMaps;
}): FlatPageLayoutTab => {
const { id: pageLayoutTabToUpdateId } = extractAndSanitizeObjectStringFields(
rawUpdatePageLayoutTabInput,
['id'],
);
const existingFlatPageLayoutTabToUpdate =
flatPageLayoutTabMaps.byId[pageLayoutTabToUpdateId];
if (!isDefined(existingFlatPageLayoutTabToUpdate)) {
throw new PageLayoutTabException(
t`Page layout tab to update not found`,
PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND,
);
}
const updatedEditableFieldProperties = extractAndSanitizeObjectStringFields(
rawUpdatePageLayoutTabInput.update,
FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES,
);
return mergeUpdateInExistingRecord({
existing: existingFlatPageLayoutTabToUpdate,
properties: [...FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES],
update: updatedEditableFieldProperties,
});
};
@@ -0,0 +1,30 @@
import { isDefined } from 'twenty-shared/utils';
import { type FlatPageLayoutTab } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab.type';
import { type FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
export type FlatPageLayoutTabWithWidgets = FlatPageLayoutTab & {
widgets: FlatPageLayoutWidget[];
};
export const reconstructFlatPageLayoutTabWithWidgets = ({
tab,
flatPageLayoutWidgetMaps,
}: {
tab: FlatPageLayoutTab;
flatPageLayoutWidgetMaps: FlatPageLayoutWidgetMaps;
}): FlatPageLayoutTabWithWidgets => {
const widgets = Object.values(flatPageLayoutWidgetMaps.byId).filter(
(widget): widget is FlatPageLayoutWidget =>
isDefined(widget) &&
widget.pageLayoutTabId === tab.id &&
!isDefined(widget.deletedAt),
);
return {
...tab,
widgets,
widgetIds: widgets.map((widget) => widget.id),
};
};