Refactor WorkspaceMemberDto transpilation (#12110)
# Introduction In a nutshell this PR introduces a `workspaceMemberEntity` to `workspaceMemberDto` transpilation which was not done but commented as `// TODO` across the `user resolver`. Also passed on the `Roles` and `UserWorkspacePermissions` transpilation We now also compute the roles for the `workspaceMember` resolver ( not only the `workspaceMembers` ) Some refactor In the following days about to create a PR that introduces integration testing on the user resolver ## Conclusion As always any suggestions are more than welcomed ! Please let me know ! ## Misc Following https://github.com/twentyhq/twenty/pull/11914 closing https://github.com/twentyhq/core-team-issues/issues/1011
This commit is contained in:
@ -1,63 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { DeletedWorkspaceMember } from 'src/engine/core-modules/user/dtos/deleted-workspace-member.dto';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
export class DeletedWorkspaceMemberTranspiler {
|
||||
constructor(private readonly fileService: FileService) {}
|
||||
|
||||
generateSignedAvatarUrl({
|
||||
workspaceId,
|
||||
workspaceMember,
|
||||
}: {
|
||||
workspaceMember: Pick<WorkspaceMemberWorkspaceEntity, 'avatarUrl' | 'id'>;
|
||||
workspaceId: string;
|
||||
}): string {
|
||||
return this.fileService.signFileUrl({
|
||||
url: workspaceMember.avatarUrl,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
toDeletedWorkspaceMemberDto(
|
||||
workspaceMember: WorkspaceMemberWorkspaceEntity,
|
||||
userWorkspaceId?: string,
|
||||
): DeletedWorkspaceMember {
|
||||
const {
|
||||
avatarUrl: avatarUrlFromEntity,
|
||||
id,
|
||||
name,
|
||||
userEmail,
|
||||
} = workspaceMember;
|
||||
|
||||
const avatarUrl =
|
||||
userWorkspaceId && avatarUrlFromEntity
|
||||
? this.generateSignedAvatarUrl({
|
||||
workspaceId: userWorkspaceId,
|
||||
workspaceMember: {
|
||||
avatarUrl: avatarUrlFromEntity,
|
||||
id,
|
||||
},
|
||||
})
|
||||
: null;
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
userEmail,
|
||||
avatarUrl,
|
||||
userWorkspaceId: userWorkspaceId ?? null,
|
||||
} satisfies DeletedWorkspaceMember;
|
||||
}
|
||||
|
||||
toDeletedWorkspaceMemberDtos(
|
||||
workspaceMembers: WorkspaceMemberWorkspaceEntity[],
|
||||
userWorkspaceId?: string,
|
||||
): DeletedWorkspaceMember[] {
|
||||
return workspaceMembers.map((workspaceMember) =>
|
||||
this.toDeletedWorkspaceMemberDto(workspaceMember, userWorkspaceId),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,136 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { DeletedWorkspaceMember } from 'src/engine/core-modules/user/dtos/deleted-workspace-member.dto';
|
||||
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { fromRoleEntitiesToRoleDtos } from 'src/engine/metadata-modules/role/utils/fromRoleEntityToRoleDto.util';
|
||||
import {
|
||||
WorkspaceMemberDateFormatEnum,
|
||||
WorkspaceMemberTimeFormatEnum,
|
||||
WorkspaceMemberWorkspaceEntity,
|
||||
} from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
export type ToWorkspaceMemberDtoArgs = {
|
||||
workspaceMemberEntity: WorkspaceMemberWorkspaceEntity;
|
||||
userWorkspaceRoles: RoleEntity[];
|
||||
userWorkspace: UserWorkspace;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMemberTranspiler {
|
||||
constructor(private readonly fileService: FileService) {}
|
||||
|
||||
generateSignedAvatarUrl({
|
||||
workspaceId,
|
||||
workspaceMember,
|
||||
}: {
|
||||
workspaceMember: Pick<WorkspaceMemberWorkspaceEntity, 'avatarUrl' | 'id'>;
|
||||
workspaceId: string;
|
||||
}): string {
|
||||
if (
|
||||
!isDefined(workspaceMember.avatarUrl) ||
|
||||
!isNonEmptyString(workspaceMember.avatarUrl)
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return this.fileService.signFileUrl({
|
||||
url: workspaceMember.avatarUrl,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
toWorkspaceMemberDto({
|
||||
userWorkspace,
|
||||
workspaceMemberEntity,
|
||||
userWorkspaceRoles,
|
||||
}: ToWorkspaceMemberDtoArgs): WorkspaceMember {
|
||||
const {
|
||||
avatarUrl: avatarUrlFromEntity,
|
||||
id,
|
||||
name,
|
||||
userEmail,
|
||||
colorScheme,
|
||||
locale,
|
||||
timeFormat,
|
||||
timeZone,
|
||||
dateFormat,
|
||||
} = workspaceMemberEntity;
|
||||
|
||||
const avatarUrl = this.generateSignedAvatarUrl({
|
||||
workspaceId: userWorkspace.id,
|
||||
workspaceMember: {
|
||||
avatarUrl: avatarUrlFromEntity,
|
||||
id,
|
||||
},
|
||||
});
|
||||
|
||||
const roles = fromRoleEntitiesToRoleDtos(userWorkspaceRoles);
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
userEmail,
|
||||
avatarUrl,
|
||||
userWorkspaceId: userWorkspace.id,
|
||||
colorScheme,
|
||||
dateFormat: dateFormat as WorkspaceMemberDateFormatEnum,
|
||||
locale,
|
||||
timeFormat: timeFormat as WorkspaceMemberTimeFormatEnum,
|
||||
timeZone,
|
||||
roles,
|
||||
} satisfies WorkspaceMember;
|
||||
}
|
||||
|
||||
toWorkspaceMemberDtos(
|
||||
allWorkspaceEntitiesBundles: ToWorkspaceMemberDtoArgs[],
|
||||
) {
|
||||
return allWorkspaceEntitiesBundles.map((bundle) =>
|
||||
this.toWorkspaceMemberDto(bundle),
|
||||
);
|
||||
}
|
||||
|
||||
toDeletedWorkspaceMemberDto(
|
||||
workspaceMember: WorkspaceMemberWorkspaceEntity,
|
||||
userWorkspaceId?: string,
|
||||
): DeletedWorkspaceMember {
|
||||
const {
|
||||
avatarUrl: avatarUrlFromEntity,
|
||||
id,
|
||||
name,
|
||||
userEmail,
|
||||
} = workspaceMember;
|
||||
|
||||
const avatarUrl = userWorkspaceId
|
||||
? this.generateSignedAvatarUrl({
|
||||
workspaceId: userWorkspaceId,
|
||||
workspaceMember: {
|
||||
avatarUrl: avatarUrlFromEntity,
|
||||
id,
|
||||
},
|
||||
})
|
||||
: null;
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
userEmail,
|
||||
avatarUrl,
|
||||
userWorkspaceId: userWorkspaceId ?? null,
|
||||
} satisfies DeletedWorkspaceMember;
|
||||
}
|
||||
|
||||
toDeletedWorkspaceMemberDtos(
|
||||
workspaceMembers: WorkspaceMemberWorkspaceEntity[],
|
||||
userWorkspaceId?: string,
|
||||
): DeletedWorkspaceMember[] {
|
||||
return workspaceMembers.map((workspaceMember) =>
|
||||
this.toDeletedWorkspaceMemberDto(workspaceMember, userWorkspaceId),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ import { KeyValuePair } from 'src/engine/core-modules/key-value-pair/key-value-p
|
||||
import { OnboardingModule } from 'src/engine/core-modules/onboarding/onboarding.module';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
|
||||
import { DeletedWorkspaceMemberTranspiler } from 'src/engine/core-modules/user/services/deleted-workspace-member-transpiler.service';
|
||||
import { WorkspaceMemberTranspiler } from 'src/engine/core-modules/user/services/workspace-member-transpiler.service';
|
||||
import { UserVarsModule } from 'src/engine/core-modules/user/user-vars/user-vars.module';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { UserResolver } from 'src/engine/core-modules/user/user.resolver';
|
||||
@ -54,12 +54,12 @@ import { UserService } from './services/user.service';
|
||||
PermissionsModule,
|
||||
UserWorkspaceModule,
|
||||
],
|
||||
exports: [UserService, DeletedWorkspaceMemberTranspiler],
|
||||
exports: [UserService, WorkspaceMemberTranspiler],
|
||||
providers: [
|
||||
UserService,
|
||||
UserResolver,
|
||||
TypeORMService,
|
||||
DeletedWorkspaceMemberTranspiler,
|
||||
WorkspaceMemberTranspiler,
|
||||
],
|
||||
})
|
||||
export class UserModule {}
|
||||
|
||||
@ -13,7 +13,7 @@ import crypto from 'crypto';
|
||||
|
||||
import { GraphQLJSONObject } from 'graphql-type-json';
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { In, Repository } from 'typeorm';
|
||||
|
||||
@ -24,12 +24,10 @@ import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
} from 'src/engine/core-modules/auth/auth.exception';
|
||||
import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service';
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { SignedFileDTO } from 'src/engine/core-modules/file/file-upload/dtos/signed-file.dto';
|
||||
import { FileUploadService } from 'src/engine/core-modules/file/file-upload/services/file-upload.service';
|
||||
import { FileService } from 'src/engine/core-modules/file/services/file.service';
|
||||
import { OnboardingStatus } from 'src/engine/core-modules/onboarding/enums/onboarding-status.enum';
|
||||
import {
|
||||
OnboardingService,
|
||||
@ -37,10 +35,14 @@ import {
|
||||
} from 'src/engine/core-modules/onboarding/onboarding.service';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
||||
import { DeletedWorkspaceMember } from 'src/engine/core-modules/user/dtos/deleted-workspace-member.dto';
|
||||
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
|
||||
import { DeletedWorkspaceMemberTranspiler } from 'src/engine/core-modules/user/services/deleted-workspace-member-transpiler.service';
|
||||
import { UserService } from 'src/engine/core-modules/user/services/user.service';
|
||||
import {
|
||||
ToWorkspaceMemberDtoArgs,
|
||||
WorkspaceMemberTranspiler,
|
||||
} from 'src/engine/core-modules/user/services/workspace-member-transpiler.service';
|
||||
import { UserVarsService } from 'src/engine/core-modules/user/user-vars/services/user-vars.service';
|
||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||
@ -48,11 +50,10 @@ import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { ObjectPermissionDTO } from 'src/engine/metadata-modules/object-permission/dtos/object-permission.dto';
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
import { UserWorkspacePermissions } from 'src/engine/metadata-modules/permissions/types/user-workspace-permissions';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
import { fromUserWorkspacePermissionsToUserWorkspacePermissionsDto } from 'src/engine/metadata-modules/role/utils/fromUserWorkspacePermissionsToUserWorkspacePermissionsDto';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { AccountsToReconnectKeys } from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
@ -77,16 +78,50 @@ export class UserResolver {
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
private readonly onboardingService: OnboardingService,
|
||||
private readonly userVarService: UserVarsService,
|
||||
private readonly fileService: FileService,
|
||||
private readonly domainManagerService: DomainManagerService,
|
||||
@InjectRepository(UserWorkspace, 'core')
|
||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||
private readonly userRoleService: UserRoleService,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
private readonly deletedWorkspaceMemberTranspiler: DeletedWorkspaceMemberTranspiler,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceMemberTranspiler: WorkspaceMemberTranspiler,
|
||||
private readonly userWorkspaceService: UserWorkspaceService,
|
||||
) {}
|
||||
|
||||
private async getUserWorkspacePermissions({
|
||||
currentUserWorkspace,
|
||||
workspace,
|
||||
}: {
|
||||
workspace: Workspace;
|
||||
currentUserWorkspace: UserWorkspace;
|
||||
}): Promise<UserWorkspacePermissions> {
|
||||
const workspaceIsPendingOrOngoingCreation = [
|
||||
WorkspaceActivationStatus.PENDING_CREATION,
|
||||
WorkspaceActivationStatus.ONGOING_CREATION,
|
||||
].includes(workspace.activationStatus);
|
||||
|
||||
if (workspaceIsPendingOrOngoingCreation) {
|
||||
return this.permissionsService.getDefaultUserWorkspacePermissions();
|
||||
}
|
||||
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (!isPermissionsV2Enabled) {
|
||||
return await this.permissionsService.getUserWorkspacePermissions({
|
||||
userWorkspaceId: currentUserWorkspace.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
return await this.permissionsService.getUserWorkspacePermissionsV2({
|
||||
userWorkspaceId: currentUserWorkspace.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
|
||||
@Query(() => User)
|
||||
async currentUser(
|
||||
@AuthUser() { id: userId }: User,
|
||||
@ -108,75 +143,24 @@ export class UserResolver {
|
||||
(userWorkspace) => userWorkspace.workspace.id === workspace.id,
|
||||
);
|
||||
|
||||
if (!currentUserWorkspace) {
|
||||
if (!isDefined(currentUserWorkspace)) {
|
||||
throw new Error('Current user workspace not found');
|
||||
}
|
||||
let settingsPermissions = {};
|
||||
let objectRecordsPermissions = {};
|
||||
let objectPermissions: ObjectPermissionDTO[] = [];
|
||||
|
||||
if (
|
||||
![
|
||||
WorkspaceActivationStatus.PENDING_CREATION,
|
||||
WorkspaceActivationStatus.ONGOING_CREATION,
|
||||
].includes(workspace.activationStatus)
|
||||
) {
|
||||
const isPermissionsV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_PERMISSIONS_V2_ENABLED,
|
||||
workspace.id,
|
||||
);
|
||||
|
||||
if (isPermissionsV2Enabled) {
|
||||
const permissions =
|
||||
await this.permissionsService.getUserWorkspacePermissionsV2({
|
||||
userWorkspaceId: currentUserWorkspace.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
settingsPermissions = permissions.settingsPermissions;
|
||||
objectPermissions = Object.entries(permissions.objectPermissions).map(
|
||||
([objectMetadataId, permissions]) => ({
|
||||
objectMetadataId,
|
||||
canReadObjectRecords: permissions.canRead,
|
||||
canUpdateObjectRecords: permissions.canUpdate,
|
||||
canSoftDeleteObjectRecords: permissions.canSoftDelete,
|
||||
canDestroyObjectRecords: permissions.canDestroy,
|
||||
}),
|
||||
);
|
||||
objectRecordsPermissions = permissions.objectRecordsPermissions;
|
||||
} else {
|
||||
const permissions =
|
||||
await this.permissionsService.getUserWorkspacePermissions({
|
||||
userWorkspaceId: currentUserWorkspace.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
settingsPermissions = permissions.settingsPermissions;
|
||||
objectRecordsPermissions = permissions.objectRecordsPermissions;
|
||||
}
|
||||
}
|
||||
|
||||
const grantedSettingsPermissions: SettingPermissionType[] = (
|
||||
Object.keys(settingsPermissions) as SettingPermissionType[]
|
||||
)
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
.filter((feature) => settingsPermissions[feature] === true);
|
||||
|
||||
const grantedObjectRecordsPermissions = (
|
||||
Object.keys(objectRecordsPermissions) as PermissionsOnAllObjectRecords[]
|
||||
)
|
||||
// @ts-expect-error legacy noImplicitAny
|
||||
.filter((permission) => objectRecordsPermissions[permission] === true);
|
||||
|
||||
currentUserWorkspace.settingsPermissions = grantedSettingsPermissions;
|
||||
currentUserWorkspace.objectRecordsPermissions =
|
||||
grantedObjectRecordsPermissions;
|
||||
currentUserWorkspace.objectPermissions = objectPermissions;
|
||||
user.currentUserWorkspace = currentUserWorkspace;
|
||||
const userWorkspacePermissions =
|
||||
fromUserWorkspacePermissionsToUserWorkspacePermissionsDto(
|
||||
await this.getUserWorkspacePermissions({
|
||||
currentUserWorkspace,
|
||||
workspace,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...user,
|
||||
currentUserWorkspace: {
|
||||
...currentUserWorkspace,
|
||||
...userWorkspacePermissions,
|
||||
},
|
||||
currentWorkspace: workspace,
|
||||
};
|
||||
}
|
||||
@ -185,18 +169,17 @@ export class UserResolver {
|
||||
async userVars(
|
||||
@Parent() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
): Promise<Record<string, any>> {
|
||||
): Promise<Record<string, unknown>> {
|
||||
const userVars = await this.userVarService.getAll({
|
||||
userId: user.id,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
const userVarAllowList = [
|
||||
const userVarAllowList: string[] = [
|
||||
OnboardingStepKeys.ONBOARDING_CONNECT_ACCOUNT_PENDING,
|
||||
AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
|
||||
AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_EMAIL_ALIASES,
|
||||
] as string[];
|
||||
];
|
||||
|
||||
const filteredMap = new Map(
|
||||
[...userVars].filter(([key]) => userVarAllowList.includes(key)),
|
||||
@ -212,20 +195,39 @@ export class UserResolver {
|
||||
@Parent() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
): Promise<WorkspaceMember | null> {
|
||||
const workspaceMember = await this.userService.loadWorkspaceMember(
|
||||
const workspaceMemberEntity = await this.userService.loadWorkspaceMember(
|
||||
user,
|
||||
workspace,
|
||||
);
|
||||
|
||||
if (workspaceMember && workspaceMember.avatarUrl) {
|
||||
workspaceMember.avatarUrl = this.fileService.signFileUrl({
|
||||
url: workspaceMember.avatarUrl,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
if (!isDefined(workspaceMemberEntity)) {
|
||||
throw new Error('Workspace member not found');
|
||||
}
|
||||
|
||||
// TODO Refactor to be transpiled to WorkspaceMember instead
|
||||
return workspaceMember as WorkspaceMember | null;
|
||||
const workspaceId = workspace.id;
|
||||
const userWorkspace =
|
||||
await this.userWorkspaceService.getUserWorkspaceForUserOrThrow({
|
||||
userId: workspaceMemberEntity.userId,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
const roleOfUserWorkspace =
|
||||
await this.userRoleService.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: [userWorkspace.id],
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const userWorkspaceRoles = roleOfUserWorkspace.get(userWorkspace.id);
|
||||
|
||||
if (!isDefined(userWorkspaceRoles)) {
|
||||
throw new Error('User workspace roles not found');
|
||||
}
|
||||
|
||||
return this.workspaceMemberTranspiler.toWorkspaceMemberDto({
|
||||
workspaceMemberEntity,
|
||||
userWorkspace,
|
||||
userWorkspaceRoles,
|
||||
});
|
||||
}
|
||||
|
||||
@ResolveField(() => [WorkspaceMember], {
|
||||
@ -240,7 +242,6 @@ export class UserResolver {
|
||||
false,
|
||||
);
|
||||
|
||||
const workspaceMembers: WorkspaceMember[] = [];
|
||||
const userWorkspaces = await this.userWorkspaceRepository.find({
|
||||
where: {
|
||||
userId: In(workspaceMemberEntities.map((entity) => entity.userId)),
|
||||
@ -248,14 +249,14 @@ export class UserResolver {
|
||||
},
|
||||
});
|
||||
|
||||
const userWorkspacesByUserId = new Map<string, UserWorkspace>(
|
||||
const userWorkspacesByUserIdMap = new Map<string, UserWorkspace>(
|
||||
userWorkspaces.map((userWorkspace) => [
|
||||
userWorkspace.userId,
|
||||
userWorkspace,
|
||||
]),
|
||||
);
|
||||
|
||||
const rolesByUserWorkspaces: Map<string, RoleDTO[]> =
|
||||
const rolesByUserWorkspacesMap =
|
||||
await this.userRoleService.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: userWorkspaces.map(
|
||||
(userWorkspace) => userWorkspace.id,
|
||||
@ -263,54 +264,36 @@ export class UserResolver {
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
|
||||
for (const workspaceMemberEntity of workspaceMemberEntities) {
|
||||
if (workspaceMemberEntity.avatarUrl) {
|
||||
workspaceMemberEntity.avatarUrl = this.fileService.signFileUrl({
|
||||
url: workspaceMemberEntity.avatarUrl,
|
||||
workspaceId: workspace.id,
|
||||
});
|
||||
}
|
||||
const toWorkspaceMemberDtoArgs =
|
||||
workspaceMemberEntities.map<ToWorkspaceMemberDtoArgs>(
|
||||
(workspaceMemberEntity) => {
|
||||
const userWorkspace = userWorkspacesByUserIdMap.get(
|
||||
workspaceMemberEntity.userId,
|
||||
);
|
||||
|
||||
// TODO Refactor to be transpiled to WorkspaceMember instead
|
||||
const workspaceMember = workspaceMemberEntity as WorkspaceMember;
|
||||
if (!isDefined(userWorkspace)) {
|
||||
throw new Error('User workspace not found');
|
||||
}
|
||||
|
||||
const userWorkspace = userWorkspacesByUserId.get(
|
||||
workspaceMemberEntity.userId,
|
||||
const userWorkspaceRoles = rolesByUserWorkspacesMap.get(
|
||||
userWorkspace.id,
|
||||
);
|
||||
|
||||
if (!isDefined(userWorkspaceRoles)) {
|
||||
throw new Error('User workspace roles not found');
|
||||
}
|
||||
|
||||
return {
|
||||
userWorkspace,
|
||||
userWorkspaceRoles,
|
||||
workspaceMemberEntity,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
// TODO Refactor should not throw ? typed as nullable ?
|
||||
if (!userWorkspace) {
|
||||
throw new Error('User workspace not found');
|
||||
}
|
||||
|
||||
workspaceMember.userWorkspaceId = userWorkspace.id;
|
||||
|
||||
const workspaceMemberRoles = (
|
||||
rolesByUserWorkspaces.get(userWorkspace.id) ?? []
|
||||
).map((roleEntity) => {
|
||||
return {
|
||||
id: roleEntity.id,
|
||||
label: roleEntity.label,
|
||||
canUpdateAllSettings: roleEntity.canUpdateAllSettings,
|
||||
description: roleEntity.description,
|
||||
icon: roleEntity.icon,
|
||||
isEditable: roleEntity.isEditable,
|
||||
userWorkspaceRoles: roleEntity.userWorkspaceRoles,
|
||||
canReadAllObjectRecords: roleEntity.canReadAllObjectRecords,
|
||||
canUpdateAllObjectRecords: roleEntity.canUpdateAllObjectRecords,
|
||||
canSoftDeleteAllObjectRecords:
|
||||
roleEntity.canSoftDeleteAllObjectRecords,
|
||||
canDestroyAllObjectRecords: roleEntity.canDestroyAllObjectRecords,
|
||||
};
|
||||
});
|
||||
|
||||
workspaceMember.roles = workspaceMemberRoles;
|
||||
|
||||
workspaceMembers.push(workspaceMember);
|
||||
}
|
||||
|
||||
// TODO: Fix typing disrepency between Entity and DTO
|
||||
return workspaceMembers;
|
||||
return this.workspaceMemberTranspiler.toWorkspaceMemberDtos(
|
||||
toWorkspaceMemberDtoArgs,
|
||||
);
|
||||
}
|
||||
|
||||
@ResolveField(() => [DeletedWorkspaceMember], {
|
||||
@ -323,7 +306,7 @@ export class UserResolver {
|
||||
const workspaceMemberEntities =
|
||||
await this.userService.loadDeletedWorkspaceMembersOnly(workspace);
|
||||
|
||||
return this.deletedWorkspaceMemberTranspiler.toDeletedWorkspaceMemberDtos(
|
||||
return this.workspaceMemberTranspiler.toDeletedWorkspaceMemberDtos(
|
||||
workspaceMemberEntities,
|
||||
workspace.id,
|
||||
);
|
||||
@ -375,7 +358,6 @@ export class UserResolver {
|
||||
|
||||
@Mutation(() => User)
|
||||
async deleteUser(@AuthUser() { id: userId }: User) {
|
||||
// Proceed with user deletion
|
||||
return this.userService.deleteUser(userId);
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
@ -16,6 +15,7 @@ import {
|
||||
PermissionsExceptionCode,
|
||||
PermissionsExceptionMessage,
|
||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||
import { UserWorkspacePermissions } from 'src/engine/metadata-modules/permissions/types/user-workspace-permissions';
|
||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||
|
||||
@ -33,11 +33,7 @@ export class PermissionsService {
|
||||
}: {
|
||||
userWorkspaceId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<{
|
||||
settingsPermissions: Record<SettingPermissionType, boolean>;
|
||||
objectRecordsPermissions: Record<PermissionsOnAllObjectRecords, boolean>;
|
||||
objectPermissions: ObjectRecordsPermissions;
|
||||
}> {
|
||||
}): Promise<UserWorkspacePermissions> {
|
||||
const [roleOfUserWorkspace] = await this.userRoleService
|
||||
.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: [userWorkspaceId],
|
||||
@ -60,7 +56,9 @@ export class PermissionsService {
|
||||
|
||||
const settingPermissions = roleOfUserWorkspace.settingPermissions ?? [];
|
||||
|
||||
const settingsPermissionsMap = Object.keys(SettingPermissionType).reduce(
|
||||
const defaultSettingsPermissions =
|
||||
this.getDefaultUserWorkspacePermissions().settingsPermissions;
|
||||
const settingsPermissions = Object.keys(SettingPermissionType).reduce(
|
||||
(acc, feature) => ({
|
||||
...acc,
|
||||
[feature]:
|
||||
@ -69,7 +67,7 @@ export class PermissionsService {
|
||||
(settingPermission) => settingPermission.setting === feature,
|
||||
),
|
||||
}),
|
||||
{} as Record<SettingPermissionType, boolean>,
|
||||
defaultSettingsPermissions,
|
||||
);
|
||||
|
||||
const { data: rolesPermissions } =
|
||||
@ -79,37 +77,53 @@ export class PermissionsService {
|
||||
|
||||
const objectPermissions = rolesPermissions[roleOfUserWorkspace.id] ?? {};
|
||||
|
||||
const objectRecordsPermissionsMap: Record<
|
||||
PermissionsOnAllObjectRecords,
|
||||
boolean
|
||||
> = {
|
||||
[PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canReadAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canUpdateAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canSoftDeleteAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canDestroyAllObjectRecords ?? false,
|
||||
};
|
||||
const objectRecordsPermissions: UserWorkspacePermissions['objectRecordsPermissions'] =
|
||||
{
|
||||
[PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canReadAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canUpdateAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canSoftDeleteAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canDestroyAllObjectRecords ?? false,
|
||||
};
|
||||
|
||||
return {
|
||||
settingsPermissions: settingsPermissionsMap,
|
||||
objectRecordsPermissions: objectRecordsPermissionsMap,
|
||||
settingsPermissions,
|
||||
objectRecordsPermissions,
|
||||
objectPermissions,
|
||||
};
|
||||
}
|
||||
|
||||
public getDefaultUserWorkspacePermissions = () =>
|
||||
({
|
||||
objectRecordsPermissions: {
|
||||
[PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]: false,
|
||||
[PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]: false,
|
||||
[PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]: false,
|
||||
[PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]: false,
|
||||
},
|
||||
settingsPermissions: {
|
||||
[SettingPermissionType.API_KEYS_AND_WEBHOOKS]: false,
|
||||
[SettingPermissionType.WORKSPACE]: false,
|
||||
[SettingPermissionType.WORKSPACE_MEMBERS]: false,
|
||||
[SettingPermissionType.ROLES]: false,
|
||||
[SettingPermissionType.DATA_MODEL]: false,
|
||||
[SettingPermissionType.ADMIN_PANEL]: false,
|
||||
[SettingPermissionType.SECURITY]: false,
|
||||
[SettingPermissionType.WORKFLOWS]: false,
|
||||
},
|
||||
objectPermissions: {},
|
||||
}) as const satisfies UserWorkspacePermissions;
|
||||
|
||||
public async getUserWorkspacePermissions({
|
||||
userWorkspaceId,
|
||||
workspaceId,
|
||||
}: {
|
||||
userWorkspaceId: string;
|
||||
workspaceId: string;
|
||||
}): Promise<{
|
||||
settingsPermissions: Record<SettingPermissionType, boolean>;
|
||||
objectRecordsPermissions: Record<PermissionsOnAllObjectRecords, boolean>;
|
||||
}> {
|
||||
}): Promise<UserWorkspacePermissions> {
|
||||
const [roleOfUserWorkspace] = await this.userRoleService
|
||||
.getRolesByUserWorkspaces({
|
||||
userWorkspaceIds: [userWorkspaceId],
|
||||
@ -132,7 +146,9 @@ export class PermissionsService {
|
||||
|
||||
const settingPermissions = roleOfUserWorkspace.settingPermissions ?? [];
|
||||
|
||||
const settingsPermissionsMap = Object.keys(SettingPermissionType).reduce(
|
||||
const defaultSettingsPermissions =
|
||||
this.getDefaultUserWorkspacePermissions().settingsPermissions;
|
||||
const settingsPermissions = Object.keys(SettingPermissionType).reduce(
|
||||
(acc, feature) => ({
|
||||
...acc,
|
||||
[feature]:
|
||||
@ -141,26 +157,25 @@ export class PermissionsService {
|
||||
(settingPermission) => settingPermission.setting === feature,
|
||||
),
|
||||
}),
|
||||
{} as Record<SettingPermissionType, boolean>,
|
||||
defaultSettingsPermissions,
|
||||
);
|
||||
|
||||
const objectRecordsPermissionsMap: Record<
|
||||
PermissionsOnAllObjectRecords,
|
||||
boolean
|
||||
> = {
|
||||
[PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canReadAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canUpdateAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canSoftDeleteAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canDestroyAllObjectRecords ?? false,
|
||||
};
|
||||
const objectRecordsPermissions: UserWorkspacePermissions['objectRecordsPermissions'] =
|
||||
{
|
||||
[PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canReadAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.UPDATE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canUpdateAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.SOFT_DELETE_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canSoftDeleteAllObjectRecords ?? false,
|
||||
[PermissionsOnAllObjectRecords.DESTROY_ALL_OBJECT_RECORDS]:
|
||||
roleOfUserWorkspace.canDestroyAllObjectRecords ?? false,
|
||||
};
|
||||
|
||||
return {
|
||||
settingsPermissions: settingsPermissionsMap,
|
||||
objectRecordsPermissions: objectRecordsPermissionsMap,
|
||||
settingsPermissions,
|
||||
objectRecordsPermissions,
|
||||
objectPermissions: {},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,10 @@
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
|
||||
export type UserWorkspacePermissions = {
|
||||
settingsPermissions: Record<SettingPermissionType, boolean>;
|
||||
objectRecordsPermissions: Record<PermissionsOnAllObjectRecords, boolean>;
|
||||
objectPermissions: ObjectRecordsPermissions;
|
||||
};
|
||||
@ -0,0 +1,6 @@
|
||||
import { UserWorkspace } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
|
||||
export type UserWorkspacePermissionsDto = Pick<
|
||||
UserWorkspace,
|
||||
'objectPermissions' | 'settingsPermissions' | 'objectRecordsPermissions'
|
||||
>;
|
||||
@ -0,0 +1,33 @@
|
||||
import { RoleDTO } from 'src/engine/metadata-modules/role/dtos/role.dto';
|
||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
|
||||
export const fromRoleEntityToRoleDto = ({
|
||||
id,
|
||||
label,
|
||||
canUpdateAllSettings,
|
||||
description,
|
||||
icon,
|
||||
isEditable,
|
||||
userWorkspaceRoles,
|
||||
canReadAllObjectRecords,
|
||||
canUpdateAllObjectRecords,
|
||||
canSoftDeleteAllObjectRecords,
|
||||
canDestroyAllObjectRecords,
|
||||
}: RoleEntity): RoleDTO => {
|
||||
return {
|
||||
id,
|
||||
label,
|
||||
canUpdateAllSettings,
|
||||
description,
|
||||
icon,
|
||||
isEditable,
|
||||
userWorkspaceRoles,
|
||||
canReadAllObjectRecords,
|
||||
canUpdateAllObjectRecords,
|
||||
canSoftDeleteAllObjectRecords,
|
||||
canDestroyAllObjectRecords,
|
||||
};
|
||||
};
|
||||
|
||||
export const fromRoleEntitiesToRoleDtos = (roleEntities: RoleEntity[]) =>
|
||||
roleEntities.map(fromRoleEntityToRoleDto);
|
||||
@ -0,0 +1,35 @@
|
||||
import { PermissionsOnAllObjectRecords } from 'twenty-shared/constants';
|
||||
|
||||
import { SettingPermissionType } from 'src/engine/metadata-modules/permissions/constants/setting-permission-type.constants';
|
||||
import { UserWorkspacePermissions } from 'src/engine/metadata-modules/permissions/types/user-workspace-permissions';
|
||||
import { UserWorkspacePermissionsDto } from 'src/engine/metadata-modules/role/dtos/user-workspace-permissions.dto';
|
||||
|
||||
export const fromUserWorkspacePermissionsToUserWorkspacePermissionsDto = ({
|
||||
objectPermissions: rawObjectPermissions,
|
||||
objectRecordsPermissions: rawObjectRecordsPermissions,
|
||||
settingsPermissions: rawSettingsPermissions,
|
||||
}: UserWorkspacePermissions): UserWorkspacePermissionsDto => {
|
||||
const objectPermissions = Object.entries(rawObjectPermissions).map(
|
||||
([objectMetadataId, permissions]) => ({
|
||||
objectMetadataId,
|
||||
canReadObjectRecords: permissions.canRead,
|
||||
canUpdateObjectRecords: permissions.canUpdate,
|
||||
canSoftDeleteObjectRecords: permissions.canSoftDelete,
|
||||
canDestroyObjectRecords: permissions.canDestroy,
|
||||
}),
|
||||
);
|
||||
|
||||
const settingsPermissions = (
|
||||
Object.keys(rawSettingsPermissions) as SettingPermissionType[]
|
||||
).filter((feature) => rawSettingsPermissions[feature] === true);
|
||||
|
||||
const objectRecordsPermissions = (
|
||||
Object.keys(rawObjectRecordsPermissions) as PermissionsOnAllObjectRecords[]
|
||||
).filter((feature) => rawObjectRecordsPermissions[feature] === true);
|
||||
|
||||
return {
|
||||
objectPermissions,
|
||||
objectRecordsPermissions,
|
||||
settingsPermissions,
|
||||
};
|
||||
};
|
||||
@ -1,8 +1,7 @@
|
||||
export type ObjectRecordsPermissions = {
|
||||
[objectMetadataId: string]: {
|
||||
type ObjectMetadataId = string;
|
||||
export type ObjectRecordsPermissions = Record<ObjectMetadataId, {
|
||||
canRead: boolean;
|
||||
canUpdate: boolean;
|
||||
canSoftDelete: boolean;
|
||||
canDestroy: boolean;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { ObjectRecordsPermissions } from '@/types';
|
||||
import { ObjectRecordsPermissions } from "@/types/ObjectRecordsPermissions";
|
||||
|
||||
export type ObjectRecordsPermissionsByRoleId = {
|
||||
[roleId: string]: ObjectRecordsPermissions;
|
||||
};
|
||||
type RoleId = string;
|
||||
export type ObjectRecordsPermissionsByRoleId = Record<
|
||||
RoleId,
|
||||
ObjectRecordsPermissions
|
||||
>;
|
||||
|
||||
Reference in New Issue
Block a user