Raphaël Bosi
2026-01-08 10:13:43 +01:00
committed by GitHub
parent d653b0a168
commit 2e0eeda52e
13 changed files with 81 additions and 29 deletions
@@ -4,7 +4,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DashboardModule } from 'src/engine/metadata-modules/dashboard/dashboard.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FlatPageLayoutTabModule } from 'src/engine/metadata-modules/flat-page-layout-tab/flat-page-layout-tab.module';
import { FlatPageLayoutWidgetModule } from 'src/engine/metadata-modules/flat-page-layout-widget/flat-page-layout-widget.module';
@@ -17,6 +16,7 @@ import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
import { DashboardSyncModule } from 'src/modules/dashboard-sync/dashboard-sync.module';
@Module({
imports: [
@@ -30,7 +30,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
FlatPageLayoutTabModule,
FlatPageLayoutWidgetModule,
ApplicationModule,
DashboardModule,
DashboardSyncModule,
],
controllers: [PageLayoutTabController],
providers: [
@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { DashboardSyncService } from 'src/engine/metadata-modules/dashboard/services/dashboard-sync.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
@@ -30,6 +29,7 @@ import { fromFlatPageLayoutTabToPageLayoutTabDto } from 'src/engine/metadata-mod
import { fromFlatPageLayoutTabWithWidgetsToPageLayoutTabDto } from 'src/engine/metadata-modules/page-layout-tab/utils/from-flat-page-layout-tab-with-widgets-to-page-layout-tab-dto.util';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
import { DashboardSyncService } from 'src/modules/dashboard-sync/services/dashboard-sync.service';
@Injectable()
export class PageLayoutTabService {
@@ -4,7 +4,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DashboardModule } from 'src/engine/metadata-modules/dashboard/dashboard.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FlatPageLayoutWidgetModule } from 'src/engine/metadata-modules/flat-page-layout-widget/flat-page-layout-widget.module';
import { PageLayoutWidgetController } from 'src/engine/metadata-modules/page-layout-widget/controllers/page-layout-widget.controller';
@@ -16,6 +15,7 @@ import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
import { DashboardSyncModule } from 'src/modules/dashboard-sync/dashboard-sync.module';
@Module({
imports: [
@@ -28,7 +28,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
WorkspaceManyOrAllFlatEntityMapsCacheModule,
FlatPageLayoutWidgetModule,
ApplicationModule,
DashboardModule,
DashboardSyncModule,
],
controllers: [PageLayoutWidgetController],
providers: [
@@ -3,7 +3,6 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { DashboardSyncService } from 'src/engine/metadata-modules/dashboard/services/dashboard-sync.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
@@ -29,6 +28,7 @@ import { fromFlatPageLayoutWidgetToPageLayoutWidgetDto } from 'src/engine/metada
import { validateWidgetGridPosition } from 'src/engine/metadata-modules/page-layout-widget/utils/validate-widget-grid-position.util';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
import { DashboardSyncService } from 'src/modules/dashboard-sync/services/dashboard-sync.service';
type WidgetMigrationOperations = {
flatEntityToCreate: FlatPageLayoutWidget[];
@@ -5,7 +5,6 @@ import { ApplicationModule } from 'src/engine/core-modules/application/applicati
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { I18nModule } from 'src/engine/core-modules/i18n/i18n.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DashboardModule } from 'src/engine/metadata-modules/dashboard/dashboard.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { FlatPageLayoutTabModule } from 'src/engine/metadata-modules/flat-page-layout-tab/flat-page-layout-tab.module';
import { FlatPageLayoutWidgetModule } from 'src/engine/metadata-modules/flat-page-layout-widget/flat-page-layout-widget.module';
@@ -23,6 +22,7 @@ import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
import { WorkspaceMigrationBuilderGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration-v2/interceptors/workspace-migration-builder-graphql-api-exception.interceptor';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
import { DashboardSyncModule } from 'src/modules/dashboard-sync/dashboard-sync.module';
@Module({
imports: [
@@ -40,7 +40,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
ApplicationModule,
PageLayoutTabModule,
PageLayoutWidgetModule,
DashboardModule,
DashboardSyncModule,
],
controllers: [PageLayoutController],
providers: [
@@ -4,7 +4,6 @@ import { computeDiffBetweenObjects, isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { DashboardSyncService } from 'src/engine/metadata-modules/dashboard/services/dashboard-sync.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant';
@@ -28,6 +27,7 @@ import {
import { fromFlatPageLayoutWithTabsAndWidgetsToPageLayoutDto } from 'src/engine/metadata-modules/page-layout/utils/from-flat-page-layout-with-tabs-and-widgets-to-page-layout-dto.util';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
import { DashboardSyncService } from 'src/modules/dashboard-sync/services/dashboard-sync.service';
type UpdatePageLayoutWithTabsParams = {
id: string;
@@ -4,7 +4,6 @@ import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { DashboardSyncService } from 'src/engine/metadata-modules/dashboard/services/dashboard-sync.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
@@ -35,6 +34,7 @@ import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspac
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
import { WorkspaceMigrationBuilderExceptionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/exceptions/workspace-migration-builder-exception-v2';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/services/workspace-migration-validate-build-and-run-service';
import { DashboardSyncService } from 'src/modules/dashboard-sync/services/dashboard-sync.service';
@Injectable()
export class PageLayoutService {
@@ -1,12 +1,12 @@
import { Module } from '@nestjs/common';
import { DashboardSyncService } from 'src/engine/metadata-modules/dashboard/services/dashboard-sync.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { DashboardSyncService } from 'src/modules/dashboard-sync/services/dashboard-sync.service';
@Module({
imports: [TwentyORMModule, WorkspaceManyOrAllFlatEntityMapsCacheModule],
providers: [DashboardSyncService],
exports: [DashboardSyncService],
})
export class DashboardModule {}
export class DashboardSyncModule {}
@@ -1,6 +1,11 @@
import { Controller, Param, Post, UseFilters, UseGuards } from '@nestjs/common';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
@@ -21,10 +26,17 @@ export class DashboardController {
async duplicate(
@Param('id') id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
@AuthUser() user: UserEntity,
@AuthWorkspaceMemberId() workspaceMemberId: string,
@AuthUserWorkspaceId() userWorkspaceId: string,
): Promise<DuplicatedDashboardDTO> {
return this.dashboardDuplicationService.duplicateDashboard(
id,
workspace.id,
);
const authContext: AuthContext = {
user,
workspace,
workspaceMemberId,
userWorkspaceId,
};
return this.dashboardDuplicationService.duplicateDashboard(id, authContext);
}
}
@@ -1,5 +1,6 @@
import { Module } from '@nestjs/common';
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
import { PageLayoutModule } from 'src/engine/metadata-modules/page-layout/page-layout.module';
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
@@ -10,6 +11,7 @@ import { DashboardDuplicationService } from 'src/modules/dashboard/services/dash
@Module({
imports: [
ActorModule,
AuthModule,
PageLayoutModule,
TwentyORMModule,
@@ -2,8 +2,13 @@ import { UseFilters, UseGuards, UsePipes } from '@nestjs/common';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
import { UserEntity } from 'src/engine/core-modules/user/user.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
import { AuthWorkspaceMemberId } from 'src/engine/decorators/auth/auth-workspace-member-id.decorator';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@@ -29,10 +34,17 @@ export class DashboardResolver {
async duplicateDashboard(
@Args('id', { type: () => UUIDScalarType }) id: string,
@AuthWorkspace() workspace: WorkspaceEntity,
@AuthUser() user: UserEntity,
@AuthWorkspaceMemberId() workspaceMemberId: string,
@AuthUserWorkspaceId() userWorkspaceId: string,
): Promise<DuplicatedDashboardDTO> {
return this.dashboardDuplicationService.duplicateDashboard(
id,
workspace.id,
);
const authContext: AuthContext = {
user,
workspace,
workspaceMemberId,
userWorkspaceId,
};
return this.dashboardDuplicationService.duplicateDashboard(id, authContext);
}
}
@@ -1,11 +1,19 @@
import { Injectable, Logger } from '@nestjs/common';
import { appendCopySuffix, isDefined } from 'twenty-shared/utils';
import {
appendCopySuffix,
assertIsDefinedOrThrow,
isDefined,
} from 'twenty-shared/utils';
import { WorkspaceAuthContext } from 'src/engine/api/common/interfaces/workspace-auth-context.interface';
import { ActorFromAuthContextService } from 'src/engine/core-modules/actor/services/actor-from-auth-context.service';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
import { PageLayoutDuplicationService } from 'src/engine/metadata-modules/page-layout/services/page-layout-duplication.service';
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util';
import { DuplicatedDashboardDTO } from 'src/modules/dashboard/dtos/duplicated-dashboard.dto';
import {
DashboardException,
@@ -22,14 +30,21 @@ export class DashboardDuplicationService {
constructor(
private readonly pageLayoutDuplicationService: PageLayoutDuplicationService,
private readonly globalWorkspaceOrmManager: GlobalWorkspaceOrmManager,
private readonly actorFromAuthContextService: ActorFromAuthContextService,
) {}
async duplicateDashboard(
dashboardId: string,
workspaceId: string,
authContext: AuthContext,
): Promise<DuplicatedDashboardDTO> {
const { workspace } = authContext;
assertIsDefinedOrThrow(workspace, WorkspaceNotFoundDefaultError);
const workspaceId = workspace.id;
return this.globalWorkspaceOrmManager.executeInWorkspaceContext(
buildSystemAuthContext(workspaceId),
authContext as WorkspaceAuthContext,
async () => {
const dashboardRepository =
await this.globalWorkspaceOrmManager.getRepository<DashboardWorkspaceEntity>(
@@ -73,6 +88,7 @@ export class DashboardDuplicationService {
originalDashboard,
newPageLayout.id,
dashboardRepository,
authContext,
);
return {
@@ -99,14 +115,24 @@ export class DashboardDuplicationService {
originalDashboard: DashboardWorkspaceEntity,
newPageLayoutId: string,
dashboardRepository: WorkspaceRepository<DashboardWorkspaceEntity>,
authContext: AuthContext,
): Promise<DashboardWorkspaceEntity> {
const newTitle = appendCopySuffix(originalDashboard.title ?? '');
const insertResult = await dashboardRepository.insert({
title: newTitle,
pageLayoutId: newPageLayoutId,
position: originalDashboard.position,
});
const [recordWithActor] =
await this.actorFromAuthContextService.injectActorFieldsOnCreate({
records: [
{
title: newTitle,
pageLayoutId: newPageLayoutId,
position: originalDashboard.position,
},
],
objectMetadataNameSingular: 'dashboard',
authContext,
});
const insertResult = await dashboardRepository.insert(recordWithActor);
const newDashboardId = insertResult.identifiers[0].id;