chore(server): Migrate workspace (#2530)
* Migrate workspace Co-authored-by: v1b3m <vibenjamin6@gmail.com> * Migrate workspace Co-authored-by: v1b3m <vibenjamin6@gmail.com> * Migrate workspace to TypeORM Co-authored-by: v1b3m <vibenjamin6@gmail.com> * Migrate workspace to TypeORM Co-authored-by: v1b3m <vibenjamin6@gmail.com> --------- Co-authored-by: v1b3m <vibenjamin6@gmail.com> Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
This commit is contained in:
@ -3,6 +3,7 @@ import { Module } from '@nestjs/common';
|
||||
import { WebHookModule } from 'src/core/web-hook/web-hook.module';
|
||||
import { UserModule as UserV2Module } from 'src/coreV2/user/user.module';
|
||||
import { RefreshTokenModule as RefreshTokenV2Module } from 'src/coreV2/refresh-token/refresh-token.module';
|
||||
import { WorkspaceModule as WorkspaceV2Module } from 'src/coreV2/workspace/workspace.module';
|
||||
|
||||
import { UserModule } from './user/user.module';
|
||||
import { CommentModule } from './comment/comment.module';
|
||||
@ -38,6 +39,7 @@ import { ApiKeyModule } from './api-key/api-key.module';
|
||||
WebHookModule,
|
||||
UserV2Module,
|
||||
RefreshTokenV2Module,
|
||||
WorkspaceV2Module,
|
||||
],
|
||||
exports: [
|
||||
AuthModule,
|
||||
@ -54,6 +56,7 @@ import { ApiKeyModule } from './api-key/api-key.module';
|
||||
WebHookModule,
|
||||
UserV2Module,
|
||||
RefreshTokenV2Module,
|
||||
WorkspaceV2Module,
|
||||
],
|
||||
})
|
||||
export class CoreModule {}
|
||||
|
||||
31
server/src/coreV2/workspace/dtos/update-workspace-input.ts
Normal file
31
server/src/coreV2/workspace/dtos/update-workspace-input.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
// FIXME: We might not need this
|
||||
@InputType()
|
||||
export class UpdateWorkspaceInput {
|
||||
@Field()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
domainName?: string;
|
||||
|
||||
@Field()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
displayName?: string;
|
||||
|
||||
@Field()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
logo?: string;
|
||||
|
||||
@Field()
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsOptional()
|
||||
inviteHash?: string;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
||||
import { Workspace } from 'src/coreV2/workspace/workspace.entity';
|
||||
|
||||
import { WorkspaceService } from './workspace.service';
|
||||
|
||||
describe('WorkspaceService', () => {
|
||||
let service: WorkspaceService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
WorkspaceService,
|
||||
{
|
||||
provide: getRepositoryToken(Workspace),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
service = module.get<WorkspaceService>(WorkspaceService);
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
});
|
||||
103
server/src/coreV2/workspace/services/workspace.service.ts
Normal file
103
server/src/coreV2/workspace/services/workspace.service.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { Workspace } from 'src/coreV2/workspace/workspace.entity';
|
||||
import { TenantManagerService } from 'src/tenant-manager/tenant-manager.service';
|
||||
|
||||
export class WorkspaceService extends TypeOrmQueryService<Workspace> {
|
||||
constructor(
|
||||
@InjectRepository(Workspace)
|
||||
private readonly workspaceRepository: Repository<Workspace>,
|
||||
private readonly tenantManagerService: TenantManagerService,
|
||||
) {
|
||||
super(workspaceRepository);
|
||||
}
|
||||
|
||||
async deleteWorkspace(id: string) {
|
||||
const workspace = await this.workspaceRepository.findOneBy({ id });
|
||||
assert(workspace, 'Workspace not found');
|
||||
|
||||
// await this.deleteWorkspaceRelations(id);
|
||||
|
||||
await this.tenantManagerService.delete(id);
|
||||
|
||||
return workspace;
|
||||
}
|
||||
|
||||
// // FIXME: The rest of the entities are not defined so we can't use this
|
||||
// async deleteWorkspaceRelations(workspaceId: string) {
|
||||
// const queryRunner =
|
||||
// this.workspaceRepository.manager.connection.createQueryRunner();
|
||||
// await queryRunner.connect();
|
||||
|
||||
// await queryRunner.startTransaction();
|
||||
|
||||
// try {
|
||||
// await queryRunner.manager.delete(PipelineProgress, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(Company, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(Person, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(PipelineStage, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(WorkspaceMember, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(Attachment, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(Comment, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(ActivityTarget, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(Activity, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(ApiKey, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(Favorite, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(WebHook, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(WebHook, {
|
||||
// workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.manager.delete(Workspace, {
|
||||
// id: workspaceId,
|
||||
// });
|
||||
|
||||
// await queryRunner.commitTransaction();
|
||||
// } catch {
|
||||
// await queryRunner.rollbackTransaction();
|
||||
// } finally {
|
||||
// await queryRunner.release();
|
||||
// }
|
||||
// }
|
||||
}
|
||||
40
server/src/coreV2/workspace/workspace.auto-resolver-opts.ts
Normal file
40
server/src/coreV2/workspace/workspace.auto-resolver-opts.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import {
|
||||
AutoResolverOpts,
|
||||
PagingStrategies,
|
||||
ReadResolverOpts,
|
||||
} from '@ptc-org/nestjs-query-graphql';
|
||||
import { SortDirection } from '@ptc-org/nestjs-query-core';
|
||||
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { UpdateWorkspaceInput } from 'src/coreV2/workspace/dtos/update-workspace-input';
|
||||
|
||||
import { Workspace } from './workspace.entity';
|
||||
|
||||
export const workspaceAutoResolverOpts: AutoResolverOpts<
|
||||
any,
|
||||
any,
|
||||
unknown,
|
||||
unknown,
|
||||
ReadResolverOpts<any>,
|
||||
PagingStrategies
|
||||
>[] = [
|
||||
{
|
||||
EntityClass: Workspace,
|
||||
DTOClass: Workspace,
|
||||
UpdateDTOClass: UpdateWorkspaceInput,
|
||||
enableTotalCount: true,
|
||||
pagingStrategy: PagingStrategies.CURSOR,
|
||||
read: {
|
||||
defaultSort: [{ field: 'id', direction: SortDirection.DESC }],
|
||||
},
|
||||
create: {
|
||||
many: { disabled: true },
|
||||
one: { disabled: true },
|
||||
},
|
||||
update: {
|
||||
many: { disabled: true },
|
||||
},
|
||||
delete: { many: { disabled: true }, one: { disabled: true } },
|
||||
guards: [JwtAuthGuard],
|
||||
},
|
||||
];
|
||||
46
server/src/coreV2/workspace/workspace.entity.ts
Normal file
46
server/src/coreV2/workspace/workspace.entity.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
@Entity('workspaces')
|
||||
@ObjectType('workspace')
|
||||
export class Workspace {
|
||||
@IDField(() => ID)
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
domainName?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
displayName?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
logo?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
inviteHash?: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
@Column({ nullable: true })
|
||||
deletedAt?: Date;
|
||||
|
||||
@Field()
|
||||
@CreateDateColumn({ type: 'timestamp with time zone' })
|
||||
createdAt: Date;
|
||||
|
||||
@Field()
|
||||
@UpdateDateColumn({ type: 'timestamp with time zone' })
|
||||
updatedAt: Date;
|
||||
}
|
||||
31
server/src/coreV2/workspace/workspace.module.ts
Normal file
31
server/src/coreV2/workspace/workspace.module.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { NestjsQueryGraphQLModule } from '@ptc-org/nestjs-query-graphql';
|
||||
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
|
||||
|
||||
import { TenantManagerModule } from 'src/tenant-manager/tenant-manager.module';
|
||||
import { WorkspaceResolver } from 'src/coreV2/workspace/workspace.resolver';
|
||||
import { FileModule } from 'src/core/file/file.module';
|
||||
import { AbilityModule } from 'src/ability/ability.module';
|
||||
|
||||
import { Workspace } from './workspace.entity';
|
||||
import { workspaceAutoResolverOpts } from './workspace.auto-resolver-opts';
|
||||
|
||||
import { WorkspaceService } from './services/workspace.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
NestjsQueryGraphQLModule.forFeature({
|
||||
imports: [
|
||||
NestjsQueryTypeOrmModule.forFeature([Workspace]),
|
||||
TenantManagerModule,
|
||||
FileModule,
|
||||
AbilityModule,
|
||||
],
|
||||
services: [WorkspaceService],
|
||||
resolvers: workspaceAutoResolverOpts,
|
||||
}),
|
||||
],
|
||||
providers: [WorkspaceResolver],
|
||||
})
|
||||
export class WorkspaceModule {}
|
||||
66
server/src/coreV2/workspace/workspace.resolver.ts
Normal file
66
server/src/coreV2/workspace/workspace.resolver.ts
Normal file
@ -0,0 +1,66 @@
|
||||
import { Resolver, Query, Args, Mutation } from '@nestjs/graphql';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
|
||||
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
|
||||
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
import { assert } from 'src/utils/assert';
|
||||
import { JwtAuthGuard } from 'src/guards/jwt.auth.guard';
|
||||
import { DeleteWorkspaceAbilityHandler } from 'src/ability/handlers/workspace.ability-handler';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
|
||||
import { Workspace } from './workspace.entity';
|
||||
|
||||
import { WorkspaceService } from './services/workspace.service';
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Resolver(() => Workspace)
|
||||
export class WorkspaceResolver {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
private readonly fileUploadService: FileUploadService,
|
||||
) {}
|
||||
|
||||
@Query(() => Workspace)
|
||||
async currentWorkspace(@AuthWorkspace() { id }: Workspace) {
|
||||
const workspace = await this.workspaceService.findById(id);
|
||||
assert(workspace, 'User not found');
|
||||
return workspace;
|
||||
}
|
||||
|
||||
@Mutation(() => String)
|
||||
async uploadWorkspaceLogo(
|
||||
@AuthWorkspace() { id }: Workspace,
|
||||
@Args({ name: 'file', type: () => GraphQLUpload })
|
||||
{ createReadStream, filename, mimetype }: FileUpload,
|
||||
): Promise<string> {
|
||||
const stream = createReadStream();
|
||||
const buffer = await streamToBuffer(stream);
|
||||
const fileFolder = FileFolder.WorkspaceLogo;
|
||||
|
||||
const { paths } = await this.fileUploadService.uploadImage({
|
||||
file: buffer,
|
||||
filename,
|
||||
mimeType: mimetype,
|
||||
fileFolder,
|
||||
});
|
||||
|
||||
await this.workspaceService.updateOne(id, {
|
||||
logo: paths[0],
|
||||
});
|
||||
|
||||
return paths[0];
|
||||
}
|
||||
|
||||
@Mutation(() => Workspace)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeleteWorkspaceAbilityHandler)
|
||||
async deleteCurrentWorkspace(@AuthWorkspace() { id }: Workspace) {
|
||||
return this.workspaceService.deleteWorkspace(id);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user