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:
gitstart-twenty
2023-11-18 05:41:46 +08:00
committed by GitHub
parent ec4f07eab2
commit a8b6edd4a8
8 changed files with 348 additions and 0 deletions

View File

@ -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 {}

View 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;
}

View File

@ -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();
});
});

View 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();
// }
// }
}

View 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],
},
];

View 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;
}

View 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 {}

View 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);
}
}