diff --git a/apps/server/src/core/core.module.ts b/apps/server/src/core/core.module.ts index 5dde0e03..c8319c89 100644 --- a/apps/server/src/core/core.module.ts +++ b/apps/server/src/core/core.module.ts @@ -9,6 +9,7 @@ import { EnvironmentModule } from '../environment/environment.module'; import { CommentModule } from './comment/comment.module'; import { SearchModule } from './search/search.module'; import { SpaceModule } from './space/space.module'; +import { GroupModule } from './group/group.module'; @Module({ imports: [ @@ -23,6 +24,7 @@ import { SpaceModule } from './space/space.module'; CommentModule, SearchModule, SpaceModule, + GroupModule, ], }) export class CoreModule {} diff --git a/apps/server/src/core/group/dto/add-group-user.dto.ts b/apps/server/src/core/group/dto/add-group-user.dto.ts new file mode 100644 index 00000000..86ff372f --- /dev/null +++ b/apps/server/src/core/group/dto/add-group-user.dto.ts @@ -0,0 +1,8 @@ +import { IsNotEmpty, IsUUID } from 'class-validator'; +import { GroupIdDto } from './group-id.dto'; + +export class AddGroupUserDto extends GroupIdDto { + @IsNotEmpty() + @IsUUID() + userId: string; +} diff --git a/apps/server/src/core/group/dto/create-group.dto.ts b/apps/server/src/core/group/dto/create-group.dto.ts new file mode 100644 index 00000000..3ab5b4c0 --- /dev/null +++ b/apps/server/src/core/group/dto/create-group.dto.ts @@ -0,0 +1,12 @@ +import { IsOptional, IsString, MaxLength, MinLength } from 'class-validator'; + +export class CreateGroupDto { + @MinLength(2) + @MaxLength(64) + @IsString() + name: string; + + @IsOptional() + @IsString() + description?: string; +} diff --git a/apps/server/src/core/group/dto/group-id.dto.ts b/apps/server/src/core/group/dto/group-id.dto.ts new file mode 100644 index 00000000..d4092acd --- /dev/null +++ b/apps/server/src/core/group/dto/group-id.dto.ts @@ -0,0 +1,7 @@ +import { IsNotEmpty, IsUUID } from 'class-validator'; + +export class GroupIdDto { + @IsNotEmpty() + @IsUUID() + groupId: string; +} diff --git a/apps/server/src/core/group/dto/remove-group-user.dto.ts b/apps/server/src/core/group/dto/remove-group-user.dto.ts new file mode 100644 index 00000000..c6dd1855 --- /dev/null +++ b/apps/server/src/core/group/dto/remove-group-user.dto.ts @@ -0,0 +1,3 @@ +import { AddGroupUserDto } from './add-group-user.dto'; + +export class RemoveGroupUserDto extends AddGroupUserDto {} diff --git a/apps/server/src/core/group/dto/update-group.dto.ts b/apps/server/src/core/group/dto/update-group.dto.ts new file mode 100644 index 00000000..ddaa1b0f --- /dev/null +++ b/apps/server/src/core/group/dto/update-group.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/mapped-types'; +import { CreateGroupDto } from './create-group.dto'; + +export class UpdateGroupDto extends PartialType(CreateGroupDto) {} diff --git a/apps/server/src/core/group/entities/group-user.entity.ts b/apps/server/src/core/group/entities/group-user.entity.ts new file mode 100644 index 00000000..c1cd53d7 --- /dev/null +++ b/apps/server/src/core/group/entities/group-user.entity.ts @@ -0,0 +1,43 @@ +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + Unique, + UpdateDateColumn, +} from 'typeorm'; +import { User } from '../../user/entities/user.entity'; +import { Group } from './group.entity'; + +@Entity('group_users') +@Unique(['groupId', 'userId']) +export class GroupUser { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + userId: string; + + @ManyToOne(() => User, (user) => user.workspaceUsers, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'userId' }) + user: User; + + @Column() + groupId: string; + + @ManyToOne(() => Group, (group) => group.groupUsers, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'groupId' }) + group: Group; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; +} diff --git a/apps/server/src/core/group/entities/group.entity.ts b/apps/server/src/core/group/entities/group.entity.ts new file mode 100644 index 00000000..47f964be --- /dev/null +++ b/apps/server/src/core/group/entities/group.entity.ts @@ -0,0 +1,50 @@ +import { + Column, + CreateDateColumn, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; +import { GroupUser } from './group-user.entity'; +import { Workspace } from '../../workspace/entities/workspace.entity'; +import { User } from '../../user/entities/user.entity'; + +@Entity('groups') +export class Group { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ length: 255 }) + name: string; + + @Column({ type: 'text', nullable: true }) + description: string; + + @Column() + workspaceId: string; + + @ManyToOne(() => Workspace, (workspace) => workspace.groups, { + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'workspaceId' }) + workspace: Workspace; + + @Column() + creatorId: string; + + @ManyToOne(() => User) + @JoinColumn({ name: 'creatorId' }) + creator: User; + + @CreateDateColumn() + createdAt: Date; + + @UpdateDateColumn() + updatedAt: Date; + + @OneToMany(() => GroupUser, (groupUser) => groupUser.group) + groupUsers: GroupUser[]; +} diff --git a/apps/server/src/core/group/group.controller.spec.ts b/apps/server/src/core/group/group.controller.spec.ts new file mode 100644 index 00000000..0a68f0cd --- /dev/null +++ b/apps/server/src/core/group/group.controller.spec.ts @@ -0,0 +1,20 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { GroupController } from './group.controller'; +import { GroupService } from './services/group.service'; + +describe('GroupController', () => { + let controller: GroupController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [GroupController], + providers: [GroupService], + }).compile(); + + controller = module.get(GroupController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/apps/server/src/core/group/group.controller.ts b/apps/server/src/core/group/group.controller.ts new file mode 100644 index 00000000..b8535020 --- /dev/null +++ b/apps/server/src/core/group/group.controller.ts @@ -0,0 +1,121 @@ +import { + Controller, + Post, + Body, + UseGuards, + HttpCode, + HttpStatus, +} from '@nestjs/common'; +import { GroupService } from './services/group.service'; +import { CreateGroupDto } from './dto/create-group.dto'; +import { JwtGuard } from '../auth/guards/jwt.guard'; +import { AuthUser } from '../../decorators/auth-user.decorator'; +import { CurrentWorkspace } from '../../decorators/current-workspace.decorator'; +import { User } from '../user/entities/user.entity'; +import { Workspace } from '../workspace/entities/workspace.entity'; +import { GroupUserService } from './services/group-user.service'; +import { GroupIdDto } from './dto/group-id.dto'; +import { PaginationOptions } from '../../helpers/pagination/pagination-options'; +import { AddGroupUserDto } from './dto/add-group-user.dto'; +import { RemoveGroupUserDto } from './dto/remove-group-user.dto'; +import { UpdateGroupDto } from './dto/update-group.dto'; + +@UseGuards(JwtGuard) +@Controller('groups') +export class GroupController { + constructor( + private readonly groupService: GroupService, + private readonly groupUserService: GroupUserService, + ) {} + + @HttpCode(HttpStatus.OK) + @Post('/') + getWorkspaceGroups( + @Body() pagination: PaginationOptions, + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, + ) { + return this.groupService.getGroupsInWorkspace(workspace.id, pagination); + } + + @HttpCode(HttpStatus.OK) + @Post('/details') + getGroup( + @Body() groupIdDto: GroupIdDto, + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, + ) { + return this.groupService.getGroup(groupIdDto.groupId, workspace.id); + } + + @HttpCode(HttpStatus.OK) + @Post('create') + createGroup( + @Body() createGroupDto: CreateGroupDto, + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, + ) { + return this.groupService.createGroup(user, workspace.id, createGroupDto); + } + + @HttpCode(HttpStatus.OK) + @Post('update') + updateGroup( + @Body() updateGroupDto: UpdateGroupDto, + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, + ) { + return this.groupService.updateGroup(workspace.id, updateGroupDto); + } + + @HttpCode(HttpStatus.OK) + @Post('members') + getGroupMembers( + @Body() groupIdDto: GroupIdDto, + @Body() pagination: PaginationOptions, + @CurrentWorkspace() workspace: Workspace, + ) { + return this.groupUserService.getGroupUsers( + groupIdDto.groupId, + workspace.id, + pagination, + ); + } + + @HttpCode(HttpStatus.OK) + @Post('members/add') + addGroupMember( + @Body() addGroupUserDto: AddGroupUserDto, + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, + ) { + return this.groupUserService.addUserToGroup( + addGroupUserDto.userId, + addGroupUserDto.groupId, + workspace.id, + ); + } + + @HttpCode(HttpStatus.OK) + @Post('members/remove') + removeGroupMember( + @Body() removeGroupUserDto: RemoveGroupUserDto, + //@AuthUser() user: User, + //@CurrentWorkspace() workspace: Workspace, + ) { + return this.groupUserService.removeUserFromGroup( + removeGroupUserDto.userId, + removeGroupUserDto.groupId, + ); + } + + @HttpCode(HttpStatus.OK) + @Post('delete') + deleteGroup( + @Body() groupIdDto: GroupIdDto, + @AuthUser() user: User, + @CurrentWorkspace() workspace: Workspace, + ) { + return this.groupService.deleteGroup(groupIdDto.groupId, workspace.id); + } +} diff --git a/apps/server/src/core/group/group.module.ts b/apps/server/src/core/group/group.module.ts new file mode 100644 index 00000000..8095f709 --- /dev/null +++ b/apps/server/src/core/group/group.module.ts @@ -0,0 +1,22 @@ +import { Module } from '@nestjs/common'; +import { GroupService } from './services/group.service'; +import { GroupController } from './group.controller'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { AuthModule } from '../auth/auth.module'; +import { Group } from './entities/group.entity'; +import { GroupUser } from './entities/group-user.entity'; +import { GroupRepository } from './respositories/group.repository'; +import { GroupUserRepository } from './respositories/group-user.repository'; +import { GroupUserService } from './services/group-user.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Group, GroupUser]), AuthModule], + controllers: [GroupController], + providers: [ + GroupService, + GroupUserService, + GroupRepository, + GroupUserRepository, + ], +}) +export class GroupModule {} diff --git a/apps/server/src/core/group/respositories/group-user.repository.ts b/apps/server/src/core/group/respositories/group-user.repository.ts new file mode 100644 index 00000000..63cbe0b9 --- /dev/null +++ b/apps/server/src/core/group/respositories/group-user.repository.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource, Repository } from 'typeorm'; +import { GroupUser } from '../entities/group-user.entity'; + +@Injectable() +export class GroupUserRepository extends Repository { + constructor(private dataSource: DataSource) { + super(GroupUser, dataSource.createEntityManager()); + } +} diff --git a/apps/server/src/core/group/respositories/group.repository.ts b/apps/server/src/core/group/respositories/group.repository.ts new file mode 100644 index 00000000..f585ec07 --- /dev/null +++ b/apps/server/src/core/group/respositories/group.repository.ts @@ -0,0 +1,10 @@ +import { Injectable } from '@nestjs/common'; +import { DataSource, Repository } from 'typeorm'; +import { Group } from '../entities/group.entity'; + +@Injectable() +export class GroupRepository extends Repository { + constructor(private dataSource: DataSource) { + super(Group, dataSource.createEntityManager()); + } +} diff --git a/apps/server/src/core/group/services/group-user.service.ts b/apps/server/src/core/group/services/group-user.service.ts new file mode 100644 index 00000000..147dce7d --- /dev/null +++ b/apps/server/src/core/group/services/group-user.service.ts @@ -0,0 +1,121 @@ +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common'; +import { DataSource, EntityManager } from 'typeorm'; +import { GroupUserRepository } from '../respositories/group-user.repository'; +import { PaginationOptions } from '../../../helpers/pagination/pagination-options'; +import { + WorkspaceUser, + WorkspaceUserRole, +} from '../../workspace/entities/workspace-user.entity'; +import { transactionWrapper } from '../../../helpers/db.helper'; +import { User } from '../../user/entities/user.entity'; +import { GroupUser } from '../entities/group-user.entity'; +import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto'; +import { PaginatedResult } from '../../../helpers/pagination/paginated-result'; + +@Injectable() +export class GroupUserService { + constructor( + private groupUserRepository: GroupUserRepository, + private dataSource: DataSource, + ) {} + + async getGroupUsers( + groupId, + workspaceId: string, + paginationOptions: PaginationOptions, + ): Promise> { + const [groupUsers, count] = await this.groupUserRepository.findAndCount({ + relations: ['user'], + where: { + group: { + workspaceId: workspaceId, + }, + }, + + take: paginationOptions.limit, + skip: paginationOptions.skip, + }); + + const users = groupUsers.map((groupUser: GroupUser) => groupUser.user); + + const paginationMeta = new PaginationMetaDto({ count, paginationOptions }); + + return new PaginatedResult(users, paginationMeta); + } + + async addUserToGroup( + userId: string, + groupId: string, + workspaceId: string, + manager?: EntityManager, + ): Promise { + let addedUser; + + await transactionWrapper( + async (manager) => { + // TODO: make duplicate code reusable + const userExists = await manager.exists(User, { + where: { id: userId }, + }); + if (!userExists) { + throw new NotFoundException('User not found'); + } + + // only workspace users can be added to workspace groups + const workspaceUser = await manager.findOneBy(WorkspaceUser, { + userId: userId, + workspaceId: workspaceId, + }); + + if (!workspaceUser) { + throw new NotFoundException('User is not a member of this workspace'); + } + + const existingGroupUser = await manager.findOneBy(GroupUser, { + userId: userId, + groupId: groupId, + }); + + if (existingGroupUser) { + throw new BadRequestException( + 'User is already a member of this group', + ); + } + + const groupUser = new GroupUser(); + groupUser.userId = userId; + groupUser.groupId = groupId; + + addedUser = await manager.save(groupUser); + }, + this.dataSource, + manager, + ); + + return addedUser; + } + + async removeUserFromGroup(userId: string, groupId: string): Promise { + const groupUser = await this.findGroupUser(userId, groupId); + + if (!groupUser) { + throw new BadRequestException('Group member not found'); + } + + await this.groupUserRepository.delete({ + userId, + groupId, + }); + } + + async findGroupUser(userId: string, groupId: string): Promise { + return await this.groupUserRepository.findOneBy({ + userId, + groupId, + }); + } +} diff --git a/apps/server/src/core/group/services/group.service.spec.ts b/apps/server/src/core/group/services/group.service.spec.ts new file mode 100644 index 00000000..495dd796 --- /dev/null +++ b/apps/server/src/core/group/services/group.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { GroupService } from './group.service'; + +describe('GroupService', () => { + let service: GroupService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [GroupService], + }).compile(); + + service = module.get(GroupService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/apps/server/src/core/group/services/group.service.ts b/apps/server/src/core/group/services/group.service.ts new file mode 100644 index 00000000..62bbf8f1 --- /dev/null +++ b/apps/server/src/core/group/services/group.service.ts @@ -0,0 +1,82 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { CreateGroupDto } from '../dto/create-group.dto'; +import { GroupRepository } from '../respositories/group.repository'; +import { Group } from '../entities/group.entity'; +import { plainToInstance } from 'class-transformer'; +import { User } from '../../user/entities/user.entity'; +import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto'; +import { PaginatedResult } from '../../../helpers/pagination/paginated-result'; +import { PaginationOptions } from '../../../helpers/pagination/pagination-options'; +import { UpdateGroupDto } from '../dto/update-group.dto'; + +@Injectable() +export class GroupService { + constructor(private groupRepository: GroupRepository) {} + + async createGroup( + authUser: User, + workspaceId: string, + createGroupDto: CreateGroupDto, + ): Promise { + const group = plainToInstance(Group, createGroupDto); + group.creatorId = authUser.id; + group.workspaceId = workspaceId; + + return await this.groupRepository.save(group); + } + + async updateGroup( + workspaceId: string, + updateGroupDto: UpdateGroupDto, + ): Promise { + const group = new Group(); + + if (updateGroupDto.name) { + group.name = updateGroupDto.name; + } + + if (updateGroupDto.description) { + group.description = updateGroupDto.description; + } + + return await this.groupRepository.save(group); + } + + async getGroup(groupId: string, workspaceId: string): Promise { + const group = await this.groupRepository.findOneBy({ + id: groupId, + workspaceId: workspaceId, + }); + + //TODO: get group member count + + if (!group) { + throw new NotFoundException('Group not found'); + } + + return group; + } + + async getGroupsInWorkspace( + workspaceId: string, + paginationOptions: PaginationOptions, + ): Promise> { + const [groupsInWorkspace, count] = await this.groupRepository.findAndCount({ + where: { + workspaceId: workspaceId, + }, + + take: paginationOptions.limit, + skip: paginationOptions.skip, + }); + + const paginationMeta = new PaginationMetaDto({ count, paginationOptions }); + + return new PaginatedResult(groupsInWorkspace, paginationMeta); + } + + async deleteGroup(groupId: string, workspaceId: string) { + await this.getGroup(groupId, workspaceId); + await this.groupRepository.delete(groupId); + } +} diff --git a/apps/server/src/core/space/space.service.ts b/apps/server/src/core/space/space.service.ts index b3ca5bbc..5062f012 100644 --- a/apps/server/src/core/space/space.service.ts +++ b/apps/server/src/core/space/space.service.ts @@ -1,4 +1,8 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { CreateSpaceDto } from './dto/create-space.dto'; import { Space } from './entities/space.entity'; import { plainToInstance } from 'class-transformer'; @@ -7,6 +11,8 @@ import { SpaceUserRepository } from './repositories/space-user.repository'; import { SpaceUser } from './entities/space-user.entity'; import { transactionWrapper } from '../../helpers/db.helper'; import { DataSource, EntityManager } from 'typeorm'; +import { WorkspaceUser } from '../workspace/entities/workspace-user.entity'; +import { User } from '../user/entities/user.entity'; @Injectable() export class SpaceService { @@ -51,14 +57,33 @@ export class SpaceService { userId: string, spaceId: string, role: string, + workspaceId, manager?: EntityManager, ): Promise { let addedUser: SpaceUser; await transactionWrapper( async (manager: EntityManager) => { - const existingSpaceUser = await manager.findOne(SpaceUser, { - where: { userId: userId, spaceId: spaceId }, + const userExists = await manager.exists(User, { + where: { id: userId }, + }); + if (!userExists) { + throw new NotFoundException('User not found'); + } + + // only workspace users can be added to workspace spaces + const workspaceUser = await manager.findOneBy(WorkspaceUser, { + userId: userId, + workspaceId: workspaceId, + }); + + if (!workspaceUser) { + throw new NotFoundException('User is not a member of this workspace'); + } + + const existingSpaceUser = await manager.findOneBy(SpaceUser, { + userId: userId, + spaceId: spaceId, }); if (existingSpaceUser) { diff --git a/apps/server/src/core/workspace/entities/workspace.entity.ts b/apps/server/src/core/workspace/entities/workspace.entity.ts index 02cb6506..a8e30e0f 100644 --- a/apps/server/src/core/workspace/entities/workspace.entity.ts +++ b/apps/server/src/core/workspace/entities/workspace.entity.ts @@ -15,6 +15,7 @@ import { Page } from '../../page/entities/page.entity'; import { WorkspaceInvitation } from './workspace-invitation.entity'; import { Comment } from '../../comment/entities/comment.entity'; import { Space } from '../../space/entities/space.entity'; +import { Group } from '../../group/entities/group.entity'; @Entity('workspaces') export class Workspace { @@ -82,4 +83,7 @@ export class Workspace { @OneToMany(() => Space, (space) => space.workspace) spaces: []; + + @OneToMany(() => Group, (group) => group.workspace) + groups: []; } diff --git a/apps/server/src/core/workspace/services/workspace-user.service.ts b/apps/server/src/core/workspace/services/workspace-user.service.ts index 975996ad..d54a8c2f 100644 --- a/apps/server/src/core/workspace/services/workspace-user.service.ts +++ b/apps/server/src/core/workspace/services/workspace-user.service.ts @@ -34,10 +34,6 @@ export class WorkspaceUserService { await transactionWrapper( async (manager) => { - const existingWorkspaceUser = await manager.findOne(WorkspaceUser, { - where: { userId: userId, workspaceId: workspaceId }, - }); - const userExists = await manager.exists(User, { where: { id: userId }, }); @@ -45,6 +41,11 @@ export class WorkspaceUserService { throw new NotFoundException('User not found'); } + const existingWorkspaceUser = await manager.findOneBy(WorkspaceUser, { + userId: userId, + workspaceId: workspaceId, + }); + if (existingWorkspaceUser) { throw new BadRequestException( 'User is already a member of this workspace', diff --git a/apps/server/src/core/workspace/services/workspace.service.ts b/apps/server/src/core/workspace/services/workspace.service.ts index b7f63878..53baab9a 100644 --- a/apps/server/src/core/workspace/services/workspace.service.ts +++ b/apps/server/src/core/workspace/services/workspace.service.ts @@ -82,6 +82,7 @@ export class WorkspaceService { userId, createdSpace.id, WorkspaceUserRole.OWNER, + createdWorkspace.id, manager, ); @@ -110,6 +111,7 @@ export class WorkspaceService { userId, firstWorkspace[0].defaultSpaceId, WorkspaceUserRole.MEMBER, + firstWorkspace[0].id, manager, ); } diff --git a/apps/server/src/database/migrations/1709644512305-Groups.ts b/apps/server/src/database/migrations/1709644512305-Groups.ts new file mode 100644 index 00000000..8abec0fc --- /dev/null +++ b/apps/server/src/database/migrations/1709644512305-Groups.ts @@ -0,0 +1,24 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Groups1709644512305 implements MigrationInterface { + name = 'Groups1709644512305' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "group_users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid NOT NULL, "groupId" uuid NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_129c2cb846b1f4beedf4c6373b5" UNIQUE ("groupId", "userId"), CONSTRAINT "PK_5df8869cdeffc693bd083153bcf" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE TABLE "groups" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying(255) NOT NULL, "description" text, "workspaceId" uuid NOT NULL, "creatorId" uuid NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_659d1483316afb28afd3a90646e" PRIMARY KEY ("id"))`); + await queryRunner.query(`ALTER TABLE "group_users" ADD CONSTRAINT "FK_ad937045ed48b757293b2011d36" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "group_users" ADD CONSTRAINT "FK_ba2d59b482905354e872896dba8" FOREIGN KEY ("groupId") REFERENCES "groups"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "groups" ADD CONSTRAINT "FK_cce5e5fec33dae0fcc991795b4a" FOREIGN KEY ("workspaceId") REFERENCES "workspaces"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "groups" ADD CONSTRAINT "FK_accb24ba8f4f213f33d08e2a20f" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "groups" DROP CONSTRAINT "FK_accb24ba8f4f213f33d08e2a20f"`); + await queryRunner.query(`ALTER TABLE "groups" DROP CONSTRAINT "FK_cce5e5fec33dae0fcc991795b4a"`); + await queryRunner.query(`ALTER TABLE "group_users" DROP CONSTRAINT "FK_ba2d59b482905354e872896dba8"`); + await queryRunner.query(`ALTER TABLE "group_users" DROP CONSTRAINT "FK_ad937045ed48b757293b2011d36"`); + await queryRunner.query(`DROP TABLE "groups"`); + await queryRunner.query(`DROP TABLE "group_users"`); + } + +}