mirror of
https://github.com/Shadowfita/docmost.git
synced 2025-11-12 15:52:32 +10:00
Use polymorphic table for space membership
This commit is contained in:
@ -8,7 +8,7 @@ import { WorkspaceRepository } from '../../workspace/repositories/workspace.repo
|
|||||||
import { WorkspaceService } from '../../workspace/services/workspace.service';
|
import { WorkspaceService } from '../../workspace/services/workspace.service';
|
||||||
import { CreateWorkspaceDto } from '../../workspace/dto/create-workspace.dto';
|
import { CreateWorkspaceDto } from '../../workspace/dto/create-workspace.dto';
|
||||||
import { Workspace } from '../../workspace/entities/workspace.entity';
|
import { Workspace } from '../../workspace/entities/workspace.entity';
|
||||||
import { SpaceService } from '../../space/space.service';
|
import { SpaceService } from '../../space/services/space.service';
|
||||||
import { CreateAdminUserDto } from '../dto/create-admin-user.dto';
|
import { CreateAdminUserDto } from '../dto/create-admin-user.dto';
|
||||||
import { GroupUserService } from '../../group/services/group-user.service';
|
import { GroupUserService } from '../../group/services/group-user.service';
|
||||||
|
|
||||||
|
|||||||
@ -15,16 +15,16 @@ import { Group } from '../../group/entities/group.entity';
|
|||||||
import { GroupUser } from '../../group/entities/group-user.entity';
|
import { GroupUser } from '../../group/entities/group-user.entity';
|
||||||
import { Attachment } from '../../attachment/entities/attachment.entity';
|
import { Attachment } from '../../attachment/entities/attachment.entity';
|
||||||
import { Space } from '../../space/entities/space.entity';
|
import { Space } from '../../space/entities/space.entity';
|
||||||
import { SpaceUser } from '../../space/entities/space-user.entity';
|
|
||||||
import { Page } from '../../page/entities/page.entity';
|
import { Page } from '../../page/entities/page.entity';
|
||||||
import { Comment } from '../../comment/entities/comment.entity';
|
import { Comment } from '../../comment/entities/comment.entity';
|
||||||
|
import { SpaceMember } from '../../space/entities/space-member.entity';
|
||||||
|
|
||||||
export type Subjects =
|
export type Subjects =
|
||||||
| InferSubjects<
|
| InferSubjects<
|
||||||
| typeof Workspace
|
| typeof Workspace
|
||||||
| typeof WorkspaceInvitation
|
| typeof WorkspaceInvitation
|
||||||
| typeof Space
|
| typeof Space
|
||||||
| typeof SpaceUser
|
| typeof SpaceMember
|
||||||
| typeof Group
|
| typeof Group
|
||||||
| typeof GroupUser
|
| typeof GroupUser
|
||||||
| typeof Attachment
|
| typeof Attachment
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import { GroupUser } from './group-user.entity';
|
|||||||
import { Workspace } from '../../workspace/entities/workspace.entity';
|
import { Workspace } from '../../workspace/entities/workspace.entity';
|
||||||
import { User } from '../../user/entities/user.entity';
|
import { User } from '../../user/entities/user.entity';
|
||||||
import { Unique } from 'typeorm';
|
import { Unique } from 'typeorm';
|
||||||
import { SpaceGroup } from '../../space/entities/space-group.entity';
|
import { SpaceMember } from '../../space/entities/space-member.entity';
|
||||||
|
|
||||||
@Entity('groups')
|
@Entity('groups')
|
||||||
@Unique(['name', 'workspaceId'])
|
@Unique(['name', 'workspaceId'])
|
||||||
@ -54,8 +54,8 @@ export class Group {
|
|||||||
@OneToMany(() => GroupUser, (groupUser) => groupUser.group)
|
@OneToMany(() => GroupUser, (groupUser) => groupUser.group)
|
||||||
groupUsers: GroupUser[];
|
groupUsers: GroupUser[];
|
||||||
|
|
||||||
@OneToMany(() => SpaceGroup, (spaceGroup) => spaceGroup.group)
|
@OneToMany(() => SpaceMember, (spaceMembership) => spaceMembership.group)
|
||||||
spaces: SpaceGroup[];
|
spaces: SpaceMember[];
|
||||||
|
|
||||||
userCount?: number;
|
memberCount?: number;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -89,12 +89,6 @@ export class GroupUserService {
|
|||||||
throw new NotFoundException('Group not found');
|
throw new NotFoundException('Group not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const find = await manager.findOne(User, {
|
|
||||||
where: { id: userId },
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(find);
|
|
||||||
|
|
||||||
const userExists = await manager.exists(User, {
|
const userExists = await manager.exists(User, {
|
||||||
where: { id: userId, workspaceId },
|
where: { id: userId, workspaceId },
|
||||||
});
|
});
|
||||||
|
|||||||
@ -119,7 +119,7 @@ export class GroupService {
|
|||||||
.where('group.id = :groupId', { groupId })
|
.where('group.id = :groupId', { groupId })
|
||||||
.andWhere('group.workspaceId = :workspaceId', { workspaceId })
|
.andWhere('group.workspaceId = :workspaceId', { workspaceId })
|
||||||
.loadRelationCountAndMap(
|
.loadRelationCountAndMap(
|
||||||
'group.userCount',
|
'group.memberCount',
|
||||||
'group.groupUsers',
|
'group.groupUsers',
|
||||||
'groupUsers',
|
'groupUsers',
|
||||||
)
|
)
|
||||||
@ -140,7 +140,7 @@ export class GroupService {
|
|||||||
.createQueryBuilder('group')
|
.createQueryBuilder('group')
|
||||||
.where('group.workspaceId = :workspaceId', { workspaceId })
|
.where('group.workspaceId = :workspaceId', { workspaceId })
|
||||||
.loadRelationCountAndMap(
|
.loadRelationCountAndMap(
|
||||||
'group.userCount',
|
'group.memberCount',
|
||||||
'group.groupUsers',
|
'group.groupUsers',
|
||||||
'groupUsers',
|
'groupUsers',
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,45 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
ManyToOne,
|
|
||||||
JoinColumn,
|
|
||||||
Unique,
|
|
||||||
} from 'typeorm';
|
|
||||||
import { Space } from './space.entity';
|
|
||||||
import { Group } from '../../group/entities/group.entity';
|
|
||||||
|
|
||||||
@Entity('space_groups')
|
|
||||||
@Unique(['spaceId', 'groupId'])
|
|
||||||
export class SpaceGroup {
|
|
||||||
@PrimaryGeneratedColumn('uuid')
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
groupId: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => Group, (group) => group.spaces, {
|
|
||||||
onDelete: 'CASCADE',
|
|
||||||
})
|
|
||||||
@JoinColumn({ name: 'groupId' })
|
|
||||||
group: Group;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
spaceId: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => Space, (space) => space.spaceGroups, {
|
|
||||||
onDelete: 'CASCADE',
|
|
||||||
})
|
|
||||||
space: Space;
|
|
||||||
|
|
||||||
@Column({ length: 100, nullable: true })
|
|
||||||
role: string;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
69
apps/server/src/core/space/entities/space-member.entity.ts
Normal file
69
apps/server/src/core/space/entities/space-member.entity.ts
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
UpdateDateColumn,
|
||||||
|
ManyToOne,
|
||||||
|
JoinColumn,
|
||||||
|
Unique,
|
||||||
|
Check,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { User } from '../../user/entities/user.entity';
|
||||||
|
import { Space } from './space.entity';
|
||||||
|
import { Group } from '../../group/entities/group.entity';
|
||||||
|
|
||||||
|
@Entity('space_members')
|
||||||
|
// allow either userId or groupId
|
||||||
|
@Check(
|
||||||
|
'CHK_allow_userId_or_groupId',
|
||||||
|
`("userId" IS NOT NULL AND "groupId" IS NULL) OR ("userId" IS NULL AND "groupId" IS NOT NULL)`,
|
||||||
|
)
|
||||||
|
@Unique(['spaceId', 'userId'])
|
||||||
|
@Unique(['spaceId', 'groupId'])
|
||||||
|
export class SpaceMember {
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, (user) => user.spaces, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn({ name: 'userId' })
|
||||||
|
user: User;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
groupId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Group, (group) => group.spaces, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
@JoinColumn({ name: 'groupId' })
|
||||||
|
group: Group;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
spaceId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => Space, (space) => space.spaceMembers, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
})
|
||||||
|
space: Space;
|
||||||
|
|
||||||
|
@Column({ length: 100 })
|
||||||
|
role: string;
|
||||||
|
|
||||||
|
@Column({ nullable: true })
|
||||||
|
creatorId: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => User)
|
||||||
|
@JoinColumn({ name: 'creatorId' })
|
||||||
|
creator: User;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@UpdateDateColumn()
|
||||||
|
updatedAt: Date;
|
||||||
|
}
|
||||||
@ -1,45 +0,0 @@
|
|||||||
import {
|
|
||||||
Entity,
|
|
||||||
PrimaryGeneratedColumn,
|
|
||||||
Column,
|
|
||||||
CreateDateColumn,
|
|
||||||
UpdateDateColumn,
|
|
||||||
ManyToOne,
|
|
||||||
JoinColumn,
|
|
||||||
Unique,
|
|
||||||
} from 'typeorm';
|
|
||||||
import { User } from '../../user/entities/user.entity';
|
|
||||||
import { Space } from './space.entity';
|
|
||||||
|
|
||||||
@Entity('space_users')
|
|
||||||
@Unique(['spaceId', 'userId'])
|
|
||||||
export class SpaceUser {
|
|
||||||
@PrimaryGeneratedColumn('uuid')
|
|
||||||
id: string;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
userId: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => User, (user) => user.spaces, {
|
|
||||||
onDelete: 'CASCADE',
|
|
||||||
})
|
|
||||||
@JoinColumn({ name: 'userId' })
|
|
||||||
user: User;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
spaceId: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => Space, (space) => space.spaceUsers, {
|
|
||||||
onDelete: 'CASCADE',
|
|
||||||
})
|
|
||||||
space: Space;
|
|
||||||
|
|
||||||
@Column({ length: 100, nullable: true })
|
|
||||||
role: string;
|
|
||||||
|
|
||||||
@CreateDateColumn()
|
|
||||||
createdAt: Date;
|
|
||||||
|
|
||||||
@UpdateDateColumn()
|
|
||||||
updatedAt: Date;
|
|
||||||
}
|
|
||||||
@ -11,10 +11,9 @@ import {
|
|||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { User } from '../../user/entities/user.entity';
|
import { User } from '../../user/entities/user.entity';
|
||||||
import { Workspace } from '../../workspace/entities/workspace.entity';
|
import { Workspace } from '../../workspace/entities/workspace.entity';
|
||||||
import { SpaceUser } from './space-user.entity';
|
|
||||||
import { Page } from '../../page/entities/page.entity';
|
import { Page } from '../../page/entities/page.entity';
|
||||||
import { SpaceVisibility, SpaceRole } from '../../../helpers/types/permission';
|
import { SpaceVisibility, SpaceRole } from '../../../helpers/types/permission';
|
||||||
import { SpaceGroup } from './space-group.entity';
|
import { SpaceMember } from './space-member.entity';
|
||||||
|
|
||||||
@Entity('spaces')
|
@Entity('spaces')
|
||||||
@Unique(['slug', 'workspaceId'])
|
@Unique(['slug', 'workspaceId'])
|
||||||
@ -56,11 +55,8 @@ export class Space {
|
|||||||
@JoinColumn({ name: 'workspaceId' })
|
@JoinColumn({ name: 'workspaceId' })
|
||||||
workspace: Workspace;
|
workspace: Workspace;
|
||||||
|
|
||||||
@OneToMany(() => SpaceUser, (spaceUser) => spaceUser.space)
|
@OneToMany(() => SpaceMember, (spaceMember) => spaceMember.space)
|
||||||
spaceUsers: SpaceUser[];
|
spaceMembers: SpaceMember[];
|
||||||
|
|
||||||
@OneToMany(() => SpaceGroup, (spaceGroup) => spaceGroup.space)
|
|
||||||
spaceGroups: SpaceGroup[];
|
|
||||||
|
|
||||||
@OneToMany(() => Page, (page) => page.space)
|
@OneToMany(() => Page, (page) => page.space)
|
||||||
pages: Page[];
|
pages: Page[];
|
||||||
|
|||||||
@ -1,10 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { DataSource, Repository } from 'typeorm';
|
|
||||||
import { SpaceGroup } from '../entities/space-group.entity';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class SpaceGroupRepository extends Repository<SpaceGroup> {
|
|
||||||
constructor(private dataSource: DataSource) {
|
|
||||||
super(SpaceGroup, dataSource.createEntityManager());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { DataSource, Repository } from 'typeorm';
|
||||||
|
import { SpaceMember } from '../entities/space-member.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SpaceMemberRepository extends Repository<SpaceMember> {
|
||||||
|
constructor(private dataSource: DataSource) {
|
||||||
|
super(SpaceMember, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { DataSource, Repository } from 'typeorm';
|
|
||||||
import { SpaceUser } from '../entities/space-user.entity';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class SpaceUserRepository extends Repository<SpaceUser> {
|
|
||||||
constructor(private dataSource: DataSource) {
|
|
||||||
super(SpaceUser, dataSource.createEntityManager());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
230
apps/server/src/core/space/services/space-member.service.ts
Normal file
230
apps/server/src/core/space/services/space-member.service.ts
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import {
|
||||||
|
BadRequestException,
|
||||||
|
Injectable,
|
||||||
|
NotFoundException,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { SpaceRepository } from '../repositories/space.repository';
|
||||||
|
import { transactionWrapper } from '../../../helpers/db.helper';
|
||||||
|
import { DataSource, EntityManager, IsNull, Not } from 'typeorm';
|
||||||
|
import { User } from '../../user/entities/user.entity';
|
||||||
|
import { PaginationOptions } from '../../../helpers/pagination/pagination-options';
|
||||||
|
import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto';
|
||||||
|
import { PaginatedResult } from '../../../helpers/pagination/paginated-result';
|
||||||
|
import { Group } from '../../group/entities/group.entity';
|
||||||
|
import { SpaceMemberRepository } from '../repositories/space-member.repository';
|
||||||
|
import { SpaceMember } from '../entities/space-member.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SpaceMemberService {
|
||||||
|
constructor(
|
||||||
|
private spaceRepository: SpaceRepository,
|
||||||
|
private spaceMemberRepository: SpaceMemberRepository,
|
||||||
|
private dataSource: DataSource,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async addUserToSpace(
|
||||||
|
userId: string,
|
||||||
|
spaceId: string,
|
||||||
|
role: string,
|
||||||
|
workspaceId,
|
||||||
|
manager?: EntityManager,
|
||||||
|
): Promise<SpaceMember> {
|
||||||
|
return await transactionWrapper(
|
||||||
|
async (manager: EntityManager) => {
|
||||||
|
const userExists = await manager.exists(User, {
|
||||||
|
where: { id: userId, workspaceId },
|
||||||
|
});
|
||||||
|
if (!userExists) {
|
||||||
|
throw new NotFoundException('User not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingSpaceUser = await manager.findOneBy(SpaceMember, {
|
||||||
|
userId: userId,
|
||||||
|
spaceId: spaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingSpaceUser) {
|
||||||
|
throw new BadRequestException('User already added to this space');
|
||||||
|
}
|
||||||
|
|
||||||
|
const spaceMember = new SpaceMember();
|
||||||
|
spaceMember.userId = userId;
|
||||||
|
spaceMember.spaceId = spaceId;
|
||||||
|
spaceMember.role = role;
|
||||||
|
await manager.save(spaceMember);
|
||||||
|
|
||||||
|
return spaceMember;
|
||||||
|
},
|
||||||
|
this.dataSource,
|
||||||
|
manager,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserSpaces(
|
||||||
|
userId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
paginationOptions: PaginationOptions,
|
||||||
|
) {
|
||||||
|
const [userSpaces, count] = await this.spaceMemberRepository
|
||||||
|
.createQueryBuilder('spaceMember')
|
||||||
|
.leftJoinAndSelect('spaceMember.space', 'space')
|
||||||
|
.where('spaceMember.userId = :userId', { userId })
|
||||||
|
.andWhere('space.workspaceId = :workspaceId', { workspaceId })
|
||||||
|
.loadRelationCountAndMap(
|
||||||
|
'space.memberCount',
|
||||||
|
'space.spaceMembers',
|
||||||
|
'spaceMembers',
|
||||||
|
)
|
||||||
|
.take(paginationOptions.limit)
|
||||||
|
.skip(paginationOptions.skip)
|
||||||
|
.getManyAndCount();
|
||||||
|
|
||||||
|
/*
|
||||||
|
const getUserSpacesViaGroup = this.spaceRepository
|
||||||
|
.createQueryBuilder('space')
|
||||||
|
.leftJoin('space.spaceGroups', 'spaceGroup')
|
||||||
|
.leftJoin('spaceGroup.group', 'group')
|
||||||
|
.leftJoin('group.groupUsers', 'groupUser')
|
||||||
|
.where('groupUser.userId = :userId', { userId })
|
||||||
|
.andWhere('space.workspaceId = :workspaceId', { workspaceId })
|
||||||
|
.getManyAndCount();
|
||||||
|
|
||||||
|
console.log(await getUserSpacesViaGroup);
|
||||||
|
*/
|
||||||
|
|
||||||
|
const spaces = userSpaces.map((userSpace) => userSpace.space);
|
||||||
|
|
||||||
|
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
||||||
|
return new PaginatedResult(spaces, paginationMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSpaceMembers(
|
||||||
|
spaceId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
paginationOptions: PaginationOptions,
|
||||||
|
) {
|
||||||
|
const [spaceMembers, count] = await this.spaceMemberRepository.findAndCount(
|
||||||
|
{
|
||||||
|
relations: ['user', 'group'],
|
||||||
|
where: {
|
||||||
|
space: {
|
||||||
|
id: spaceId,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
createdAt: 'ASC',
|
||||||
|
},
|
||||||
|
take: paginationOptions.limit,
|
||||||
|
skip: paginationOptions.skip,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const members = await Promise.all(
|
||||||
|
spaceMembers.map(async (member) => {
|
||||||
|
let memberInfo = {};
|
||||||
|
|
||||||
|
if (member.user) {
|
||||||
|
memberInfo = {
|
||||||
|
id: member.user.id,
|
||||||
|
name: member.user.name,
|
||||||
|
email: member.user.email,
|
||||||
|
avatarUrl: member.user.avatarUrl,
|
||||||
|
type: 'user',
|
||||||
|
};
|
||||||
|
} else if (member.group) {
|
||||||
|
const memberCount = await this.dataSource.getRepository(Group).count({
|
||||||
|
where: {
|
||||||
|
id: member.groupId,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
memberInfo = {
|
||||||
|
id: member.group.id,
|
||||||
|
name: member.group.name,
|
||||||
|
isDefault: member.group.isDefault,
|
||||||
|
memberCount: memberCount,
|
||||||
|
type: 'group',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...memberInfo,
|
||||||
|
role: member.role,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
||||||
|
return new PaginatedResult(members, paginationMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
async addGroupToSpace(
|
||||||
|
groupId: string,
|
||||||
|
spaceId: string,
|
||||||
|
role: string,
|
||||||
|
workspaceId,
|
||||||
|
manager?: EntityManager,
|
||||||
|
): Promise<SpaceMember> {
|
||||||
|
return await transactionWrapper(
|
||||||
|
async (manager: EntityManager) => {
|
||||||
|
const groupExists = await manager.exists(Group, {
|
||||||
|
where: { id: groupId, workspaceId },
|
||||||
|
});
|
||||||
|
if (!groupExists) {
|
||||||
|
throw new NotFoundException('Group not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingSpaceGroup = await manager.findOneBy(SpaceMember, {
|
||||||
|
groupId: groupId,
|
||||||
|
spaceId: spaceId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingSpaceGroup) {
|
||||||
|
throw new BadRequestException('Group already added to this space');
|
||||||
|
}
|
||||||
|
|
||||||
|
const spaceMember = new SpaceMember();
|
||||||
|
spaceMember.groupId = groupId;
|
||||||
|
spaceMember.spaceId = spaceId;
|
||||||
|
spaceMember.role = role;
|
||||||
|
await manager.save(spaceMember);
|
||||||
|
|
||||||
|
return spaceMember;
|
||||||
|
},
|
||||||
|
this.dataSource,
|
||||||
|
manager,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSpaceGroup(
|
||||||
|
spaceId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
paginationOptions: PaginationOptions,
|
||||||
|
) {
|
||||||
|
const [spaceGroups, count] = await this.spaceMemberRepository.findAndCount({
|
||||||
|
relations: ['group'],
|
||||||
|
where: {
|
||||||
|
groupId: Not(IsNull()),
|
||||||
|
space: {
|
||||||
|
id: spaceId,
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
take: paginationOptions.limit,
|
||||||
|
skip: paginationOptions.skip,
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: add group memberCount
|
||||||
|
const groups = spaceGroups.map((spaceGroup) => {
|
||||||
|
return {
|
||||||
|
...spaceGroup.group,
|
||||||
|
spaceRole: spaceGroup.role,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
||||||
|
return new PaginatedResult(groups, paginationMeta);
|
||||||
|
}
|
||||||
|
}
|
||||||
84
apps/server/src/core/space/services/space.service.ts
Normal file
84
apps/server/src/core/space/services/space.service.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||||
|
import { CreateSpaceDto } from '../dto/create-space.dto';
|
||||||
|
import { Space } from '../entities/space.entity';
|
||||||
|
import { SpaceRepository } from '../repositories/space.repository';
|
||||||
|
import { transactionWrapper } from '../../../helpers/db.helper';
|
||||||
|
import { DataSource, EntityManager } from 'typeorm';
|
||||||
|
import { PaginationOptions } from '../../../helpers/pagination/pagination-options';
|
||||||
|
import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto';
|
||||||
|
import { PaginatedResult } from '../../../helpers/pagination/paginated-result';
|
||||||
|
import { SpaceMemberRepository } from '../repositories/space-member.repository';
|
||||||
|
import slugify from 'slugify';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SpaceService {
|
||||||
|
constructor(
|
||||||
|
private spaceRepository: SpaceRepository,
|
||||||
|
private spaceMemberRepository: SpaceMemberRepository,
|
||||||
|
private dataSource: DataSource,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(
|
||||||
|
userId: string,
|
||||||
|
workspaceId: string,
|
||||||
|
createSpaceDto?: CreateSpaceDto,
|
||||||
|
manager?: EntityManager,
|
||||||
|
): Promise<Space> {
|
||||||
|
return await transactionWrapper(
|
||||||
|
async (manager: EntityManager) => {
|
||||||
|
const space = new Space();
|
||||||
|
space.name = createSpaceDto.name ?? 'untitled space ';
|
||||||
|
space.description = createSpaceDto.description ?? '';
|
||||||
|
space.creatorId = userId;
|
||||||
|
space.workspaceId = workspaceId;
|
||||||
|
|
||||||
|
space.slug = slugify(space.name.toLowerCase()); // TODO: check for duplicate
|
||||||
|
|
||||||
|
await manager.save(space);
|
||||||
|
return space;
|
||||||
|
},
|
||||||
|
this.dataSource,
|
||||||
|
manager,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSpaceInfo(spaceId: string, workspaceId: string): Promise<Space> {
|
||||||
|
const space = await this.spaceRepository
|
||||||
|
.createQueryBuilder('space')
|
||||||
|
.where('space.id = :spaceId', { spaceId })
|
||||||
|
.andWhere('space.workspaceId = :workspaceId', { workspaceId })
|
||||||
|
.loadRelationCountAndMap(
|
||||||
|
'space.memberCount',
|
||||||
|
'space.spaceMembers',
|
||||||
|
'spaceMembers',
|
||||||
|
) // TODO: add groups to memberCount
|
||||||
|
.getOne();
|
||||||
|
|
||||||
|
if (!space) {
|
||||||
|
throw new NotFoundException('Space not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
return space;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getWorkspaceSpaces(
|
||||||
|
workspaceId: string,
|
||||||
|
paginationOptions: PaginationOptions,
|
||||||
|
): Promise<PaginatedResult<Space>> {
|
||||||
|
const [spaces, count] = await this.spaceRepository
|
||||||
|
.createQueryBuilder('space')
|
||||||
|
.where('space.workspaceId = :workspaceId', { workspaceId })
|
||||||
|
.loadRelationCountAndMap(
|
||||||
|
'space.memberCount',
|
||||||
|
'space.spaceMembers',
|
||||||
|
'spaceMembers',
|
||||||
|
) // TODO: add groups to memberCount
|
||||||
|
.take(paginationOptions.limit)
|
||||||
|
.skip(paginationOptions.skip)
|
||||||
|
.getManyAndCount();
|
||||||
|
|
||||||
|
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
||||||
|
|
||||||
|
return new PaginatedResult(spaces, paginationMeta);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { SpaceController } from './space.controller';
|
import { SpaceController } from './space.controller';
|
||||||
import { SpaceService } from './space.service';
|
import { SpaceService } from './services/space.service';
|
||||||
|
|
||||||
describe('SpaceController', () => {
|
describe('SpaceController', () => {
|
||||||
let controller: SpaceController;
|
let controller: SpaceController;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import {
|
|||||||
Post,
|
Post,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { SpaceService } from './space.service';
|
import { SpaceService } from './services/space.service';
|
||||||
import { AuthUser } from '../../decorators/auth-user.decorator';
|
import { AuthUser } from '../../decorators/auth-user.decorator';
|
||||||
import { User } from '../user/entities/user.entity';
|
import { User } from '../user/entities/user.entity';
|
||||||
import { AuthWorkspace } from '../../decorators/auth-workspace.decorator';
|
import { AuthWorkspace } from '../../decorators/auth-workspace.decorator';
|
||||||
@ -14,11 +14,15 @@ import { Workspace } from '../workspace/entities/workspace.entity';
|
|||||||
import { JwtAuthGuard } from '../../guards/jwt-auth.guard';
|
import { JwtAuthGuard } from '../../guards/jwt-auth.guard';
|
||||||
import { SpaceIdDto } from './dto/space-id.dto';
|
import { SpaceIdDto } from './dto/space-id.dto';
|
||||||
import { PaginationOptions } from '../../helpers/pagination/pagination-options';
|
import { PaginationOptions } from '../../helpers/pagination/pagination-options';
|
||||||
|
import { SpaceMemberService } from './services/space-member.service';
|
||||||
|
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@Controller('spaces')
|
@Controller('spaces')
|
||||||
export class SpaceController {
|
export class SpaceController {
|
||||||
constructor(private readonly spaceService: SpaceService) {}
|
constructor(
|
||||||
|
private readonly spaceService: SpaceService,
|
||||||
|
private readonly spaceMemberService: SpaceMemberService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@Post('/')
|
@Post('/')
|
||||||
@ -41,7 +45,11 @@ export class SpaceController {
|
|||||||
@AuthUser() user: User,
|
@AuthUser() user: User,
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.spaceService.getUserSpaces(user.id, workspace.id, pagination);
|
return this.spaceMemberService.getUserSpaces(
|
||||||
|
user.id,
|
||||||
|
workspace.id,
|
||||||
|
pagination,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@HttpCode(HttpStatus.OK)
|
@HttpCode(HttpStatus.OK)
|
||||||
@ -64,7 +72,7 @@ export class SpaceController {
|
|||||||
@AuthUser() user: User,
|
@AuthUser() user: User,
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
) {
|
) {
|
||||||
return this.spaceService.getSpaceUsers(
|
return this.spaceMemberService.getSpaceMembers(
|
||||||
spaceIdDto.spaceId,
|
spaceIdDto.spaceId,
|
||||||
workspace.id,
|
workspace.id,
|
||||||
pagination,
|
pagination,
|
||||||
|
|||||||
@ -1,23 +1,22 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { SpaceService } from './space.service';
|
import { SpaceService } from './services/space.service';
|
||||||
import { SpaceController } from './space.controller';
|
import { SpaceController } from './space.controller';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { Space } from './entities/space.entity';
|
import { Space } from './entities/space.entity';
|
||||||
import { SpaceUser } from './entities/space-user.entity';
|
|
||||||
import { SpaceRepository } from './repositories/space.repository';
|
import { SpaceRepository } from './repositories/space.repository';
|
||||||
import { SpaceUserRepository } from './repositories/space-user.repository';
|
import { SpaceMember } from './entities/space-member.entity';
|
||||||
import { SpaceGroup } from './entities/space-group.entity';
|
import { SpaceMemberRepository } from './repositories/space-member.repository';
|
||||||
import { SpaceGroupRepository } from './repositories/space-group.repository';
|
import { SpaceMemberService } from './services/space-member.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([Space, SpaceUser, SpaceGroup])],
|
imports: [TypeOrmModule.forFeature([Space, SpaceMember])],
|
||||||
controllers: [SpaceController],
|
controllers: [SpaceController],
|
||||||
providers: [
|
providers: [
|
||||||
SpaceService,
|
SpaceService,
|
||||||
|
SpaceMemberService,
|
||||||
SpaceRepository,
|
SpaceRepository,
|
||||||
SpaceUserRepository,
|
SpaceMemberRepository,
|
||||||
SpaceGroupRepository,
|
|
||||||
],
|
],
|
||||||
exports: [SpaceService],
|
exports: [SpaceService, SpaceMemberService],
|
||||||
})
|
})
|
||||||
export class SpaceModule {}
|
export class SpaceModule {}
|
||||||
|
|||||||
@ -1,263 +0,0 @@
|
|||||||
import {
|
|
||||||
BadRequestException,
|
|
||||||
Injectable,
|
|
||||||
NotFoundException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { CreateSpaceDto } from './dto/create-space.dto';
|
|
||||||
import { Space } from './entities/space.entity';
|
|
||||||
import { SpaceRepository } from './repositories/space.repository';
|
|
||||||
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 { User } from '../user/entities/user.entity';
|
|
||||||
import { PaginationOptions } from '../../helpers/pagination/pagination-options';
|
|
||||||
import { PaginationMetaDto } from '../../helpers/pagination/pagination-meta-dto';
|
|
||||||
import { PaginatedResult } from '../../helpers/pagination/paginated-result';
|
|
||||||
import { SpaceGroupRepository } from './repositories/space-group.repository';
|
|
||||||
import { Group } from '../group/entities/group.entity';
|
|
||||||
import { SpaceGroup } from './entities/space-group.entity';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class SpaceService {
|
|
||||||
constructor(
|
|
||||||
private spaceRepository: SpaceRepository,
|
|
||||||
private spaceUserRepository: SpaceUserRepository,
|
|
||||||
private spaceGroupRepository: SpaceGroupRepository,
|
|
||||||
private dataSource: DataSource,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async create(
|
|
||||||
userId: string,
|
|
||||||
workspaceId: string,
|
|
||||||
createSpaceDto?: CreateSpaceDto,
|
|
||||||
manager?: EntityManager,
|
|
||||||
): Promise<Space> {
|
|
||||||
return await transactionWrapper(
|
|
||||||
async (manager: EntityManager) => {
|
|
||||||
const space = new Space();
|
|
||||||
space.name = createSpaceDto.name ?? 'untitled space ';
|
|
||||||
space.description = createSpaceDto.description ?? '';
|
|
||||||
space.creatorId = userId;
|
|
||||||
space.workspaceId = workspaceId;
|
|
||||||
|
|
||||||
space.slug = space.name.toLowerCase(); // TODO: fix
|
|
||||||
|
|
||||||
await manager.save(space);
|
|
||||||
return space;
|
|
||||||
},
|
|
||||||
this.dataSource,
|
|
||||||
manager,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async addUserToSpace(
|
|
||||||
userId: string,
|
|
||||||
spaceId: string,
|
|
||||||
role: string,
|
|
||||||
workspaceId,
|
|
||||||
manager?: EntityManager,
|
|
||||||
): Promise<SpaceUser> {
|
|
||||||
return await transactionWrapper(
|
|
||||||
async (manager: EntityManager) => {
|
|
||||||
const userExists = await manager.exists(User, {
|
|
||||||
where: { id: userId, workspaceId },
|
|
||||||
});
|
|
||||||
if (!userExists) {
|
|
||||||
throw new NotFoundException('User not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingSpaceUser = await manager.findOneBy(SpaceUser, {
|
|
||||||
userId: userId,
|
|
||||||
spaceId: spaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingSpaceUser) {
|
|
||||||
throw new BadRequestException('User already added to this space');
|
|
||||||
}
|
|
||||||
|
|
||||||
const spaceUser = new SpaceUser();
|
|
||||||
spaceUser.userId = userId;
|
|
||||||
spaceUser.spaceId = spaceId;
|
|
||||||
spaceUser.role = role;
|
|
||||||
await manager.save(spaceUser);
|
|
||||||
|
|
||||||
return spaceUser;
|
|
||||||
},
|
|
||||||
this.dataSource,
|
|
||||||
manager,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSpaceInfo(spaceId: string, workspaceId: string): Promise<Space> {
|
|
||||||
const space = await this.spaceRepository
|
|
||||||
.createQueryBuilder('space')
|
|
||||||
.where('space.id = :spaceId', { spaceId })
|
|
||||||
.andWhere('space.workspaceId = :workspaceId', { workspaceId })
|
|
||||||
.loadRelationCountAndMap(
|
|
||||||
'space.userCount',
|
|
||||||
'space.spaceUsers',
|
|
||||||
'spaceUsers',
|
|
||||||
) // TODO: add groups to userCount
|
|
||||||
.getOne();
|
|
||||||
|
|
||||||
if (!space) {
|
|
||||||
throw new NotFoundException('Space not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
return space;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getWorkspaceSpaces(
|
|
||||||
workspaceId: string,
|
|
||||||
paginationOptions: PaginationOptions,
|
|
||||||
): Promise<PaginatedResult<Space>> {
|
|
||||||
const [spaces, count] = await this.spaceRepository
|
|
||||||
.createQueryBuilder('space')
|
|
||||||
.where('space.workspaceId = :workspaceId', { workspaceId })
|
|
||||||
.loadRelationCountAndMap(
|
|
||||||
'space.userCount',
|
|
||||||
'space.spaceUsers',
|
|
||||||
'spaceUsers',
|
|
||||||
) // TODO: add groups to userCount
|
|
||||||
.take(paginationOptions.limit)
|
|
||||||
.skip(paginationOptions.skip)
|
|
||||||
.getManyAndCount();
|
|
||||||
|
|
||||||
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
|
||||||
|
|
||||||
return new PaginatedResult(spaces, paginationMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getUserSpaces(
|
|
||||||
userId: string,
|
|
||||||
workspaceId: string,
|
|
||||||
paginationOptions: PaginationOptions,
|
|
||||||
) {
|
|
||||||
const [userSpaces, count] = await this.spaceUserRepository
|
|
||||||
.createQueryBuilder('spaceUser')
|
|
||||||
.leftJoinAndSelect('spaceUser.space', 'space')
|
|
||||||
.where('spaceUser.userId = :userId', { userId })
|
|
||||||
.andWhere('space.workspaceId = :workspaceId', { workspaceId })
|
|
||||||
.loadRelationCountAndMap(
|
|
||||||
'space.userCount',
|
|
||||||
'space.spaceUsers',
|
|
||||||
'spaceUsers',
|
|
||||||
)
|
|
||||||
.take(paginationOptions.limit)
|
|
||||||
.skip(paginationOptions.skip)
|
|
||||||
.getManyAndCount();
|
|
||||||
|
|
||||||
|
|
||||||
const getUserSpacesViaGroup = this.spaceRepository
|
|
||||||
.createQueryBuilder('space')
|
|
||||||
.leftJoin('space.spaceGroups', 'spaceGroup')
|
|
||||||
.leftJoin('spaceGroup.group', 'group')
|
|
||||||
.leftJoin('group.groupUsers', 'groupUser')
|
|
||||||
.where('groupUser.userId = :userId', { userId })
|
|
||||||
.andWhere('space.workspaceId = :workspaceId', { workspaceId }).getManyAndCount();
|
|
||||||
|
|
||||||
console.log(await getUserSpacesViaGroup);
|
|
||||||
|
|
||||||
const spaces = userSpaces.map((userSpace) => userSpace.space);
|
|
||||||
|
|
||||||
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
|
||||||
return new PaginatedResult(spaces, paginationMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSpaceUsers(
|
|
||||||
spaceId: string,
|
|
||||||
workspaceId: string,
|
|
||||||
paginationOptions: PaginationOptions,
|
|
||||||
) {
|
|
||||||
const [spaceUsers, count] = await this.spaceUserRepository.findAndCount({
|
|
||||||
relations: ['user'],
|
|
||||||
where: {
|
|
||||||
space: {
|
|
||||||
id: spaceId,
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
take: paginationOptions.limit,
|
|
||||||
skip: paginationOptions.skip,
|
|
||||||
});
|
|
||||||
|
|
||||||
const users = spaceUsers.map((spaceUser) => {
|
|
||||||
delete spaceUser.user.password;
|
|
||||||
return {
|
|
||||||
...spaceUser.user,
|
|
||||||
spaceRole: spaceUser.role,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
|
||||||
return new PaginatedResult(users, paginationMeta);
|
|
||||||
}
|
|
||||||
|
|
||||||
async addGroupToSpace(
|
|
||||||
groupId: string,
|
|
||||||
spaceId: string,
|
|
||||||
role: string,
|
|
||||||
workspaceId,
|
|
||||||
manager?: EntityManager,
|
|
||||||
): Promise<SpaceGroup> {
|
|
||||||
return await transactionWrapper(
|
|
||||||
async (manager: EntityManager) => {
|
|
||||||
const groupExists = await manager.exists(Group, {
|
|
||||||
where: { id: groupId, workspaceId },
|
|
||||||
});
|
|
||||||
if (!groupExists) {
|
|
||||||
throw new NotFoundException('Group not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingSpaceGroup = await manager.findOneBy(SpaceGroup, {
|
|
||||||
groupId: groupId,
|
|
||||||
spaceId: spaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingSpaceGroup) {
|
|
||||||
throw new BadRequestException('Group already added to this space');
|
|
||||||
}
|
|
||||||
|
|
||||||
const spaceGroup = new SpaceGroup();
|
|
||||||
spaceGroup.groupId = groupId;
|
|
||||||
spaceGroup.spaceId = spaceId;
|
|
||||||
spaceGroup.role = role;
|
|
||||||
await manager.save(spaceGroup);
|
|
||||||
|
|
||||||
return spaceGroup;
|
|
||||||
},
|
|
||||||
this.dataSource,
|
|
||||||
manager,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSpaceGroups(
|
|
||||||
spaceId: string,
|
|
||||||
workspaceId: string,
|
|
||||||
paginationOptions: PaginationOptions,
|
|
||||||
) {
|
|
||||||
const [spaceGroups, count] = await this.spaceGroupRepository.findAndCount({
|
|
||||||
relations: ['group'],
|
|
||||||
where: {
|
|
||||||
space: {
|
|
||||||
id: spaceId,
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
take: paginationOptions.limit,
|
|
||||||
skip: paginationOptions.skip,
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: add group userCount
|
|
||||||
const groups = spaceGroups.map((spaceGroup) => {
|
|
||||||
return {
|
|
||||||
...spaceGroup.group,
|
|
||||||
spaceRole: spaceGroup.role,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
|
||||||
return new PaginatedResult(groups, paginationMeta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -14,7 +14,7 @@ import { Workspace } from '../../workspace/entities/workspace.entity';
|
|||||||
import { Page } from '../../page/entities/page.entity';
|
import { Page } from '../../page/entities/page.entity';
|
||||||
import { Comment } from '../../comment/entities/comment.entity';
|
import { Comment } from '../../comment/entities/comment.entity';
|
||||||
import { Space } from '../../space/entities/space.entity';
|
import { Space } from '../../space/entities/space.entity';
|
||||||
import { SpaceUser } from '../../space/entities/space-user.entity';
|
import { SpaceMember } from '../../space/entities/space-member.entity';
|
||||||
|
|
||||||
@Entity('users')
|
@Entity('users')
|
||||||
@Unique(['email', 'workspaceId'])
|
@Unique(['email', 'workspaceId'])
|
||||||
@ -78,8 +78,8 @@ export class User {
|
|||||||
@OneToMany(() => Space, (space) => space.creator)
|
@OneToMany(() => Space, (space) => space.creator)
|
||||||
createdSpaces: Space[];
|
createdSpaces: Space[];
|
||||||
|
|
||||||
@OneToMany(() => SpaceUser, (spaceUser) => spaceUser.user)
|
@OneToMany(() => SpaceMember, (spaceMembership) => spaceMembership.user)
|
||||||
spaces: SpaceUser[];
|
spaces: SpaceMember[];
|
||||||
|
|
||||||
toJSON() {
|
toJSON() {
|
||||||
delete this.password;
|
delete this.password;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { Workspace } from '../entities/workspace.entity';
|
|||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { UpdateWorkspaceDto } from '../dto/update-workspace.dto';
|
import { UpdateWorkspaceDto } from '../dto/update-workspace.dto';
|
||||||
import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto';
|
import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto';
|
||||||
import { SpaceService } from '../../space/space.service';
|
import { SpaceService } from '../../space/services/space.service';
|
||||||
import { DataSource, EntityManager } from 'typeorm';
|
import { DataSource, EntityManager } from 'typeorm';
|
||||||
import { transactionWrapper } from '../../../helpers/db.helper';
|
import { transactionWrapper } from '../../../helpers/db.helper';
|
||||||
import { CreateSpaceDto } from '../../space/dto/create-space.dto';
|
import { CreateSpaceDto } from '../../space/dto/create-space.dto';
|
||||||
@ -19,6 +19,7 @@ import { User } from '../../user/entities/user.entity';
|
|||||||
import { EnvironmentService } from '../../../integrations/environment/environment.service';
|
import { EnvironmentService } from '../../../integrations/environment/environment.service';
|
||||||
import { GroupService } from '../../group/services/group.service';
|
import { GroupService } from '../../group/services/group.service';
|
||||||
import { GroupUserService } from '../../group/services/group-user.service';
|
import { GroupUserService } from '../../group/services/group-user.service';
|
||||||
|
import { SpaceMemberService } from '../../space/services/space-member.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkspaceService {
|
export class WorkspaceService {
|
||||||
@ -26,6 +27,7 @@ export class WorkspaceService {
|
|||||||
private workspaceRepository: WorkspaceRepository,
|
private workspaceRepository: WorkspaceRepository,
|
||||||
private userRepository: UserRepository,
|
private userRepository: UserRepository,
|
||||||
private spaceService: SpaceService,
|
private spaceService: SpaceService,
|
||||||
|
private spaceMemberService: SpaceMemberService,
|
||||||
private groupService: GroupService,
|
private groupService: GroupService,
|
||||||
private groupUserService: GroupUserService,
|
private groupUserService: GroupUserService,
|
||||||
private environmentService: EnvironmentService,
|
private environmentService: EnvironmentService,
|
||||||
@ -42,7 +44,7 @@ export class WorkspaceService {
|
|||||||
.createQueryBuilder('workspace')
|
.createQueryBuilder('workspace')
|
||||||
.where('workspace.id = :workspaceId', { workspaceId })
|
.where('workspace.id = :workspaceId', { workspaceId })
|
||||||
.loadRelationCountAndMap(
|
.loadRelationCountAndMap(
|
||||||
'workspace.userCount',
|
'workspace.memberCount',
|
||||||
'workspace.users',
|
'workspace.users',
|
||||||
'workspaceUsers',
|
'workspaceUsers',
|
||||||
)
|
)
|
||||||
@ -105,7 +107,7 @@ export class WorkspaceService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// and add user to space as owner
|
// and add user to space as owner
|
||||||
await this.spaceService.addUserToSpace(
|
await this.spaceMemberService.addUserToSpace(
|
||||||
user.id,
|
user.id,
|
||||||
createdSpace.id,
|
createdSpace.id,
|
||||||
SpaceRole.OWNER,
|
SpaceRole.OWNER,
|
||||||
@ -114,7 +116,7 @@ export class WorkspaceService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// add default group to space as writer
|
// add default group to space as writer
|
||||||
await this.spaceService.addGroupToSpace(
|
await this.spaceMemberService.addGroupToSpace(
|
||||||
group.id,
|
group.id,
|
||||||
createdSpace.id,
|
createdSpace.id,
|
||||||
SpaceRole.WRITER,
|
SpaceRole.WRITER,
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class AddSpacesUsers1708941651476 implements MigrationInterface {
|
|
||||||
name = 'AddSpacesUsers1708941651476'
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`CREATE TABLE "space_users" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid NOT NULL, "spaceId" uuid NOT NULL, "role" character varying(100), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_5819a4f6b83e86596c57c19e39f" UNIQUE ("spaceId", "userId"), CONSTRAINT "PK_8d03fbe7f6bc26f9ac665250e1d" PRIMARY KEY ("id"))`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "space_users" ADD CONSTRAINT "FK_e735cdb3781f344a2dff3083fd5" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "space_users" ADD CONSTRAINT "FK_dae4f7e55306bdcec6ac8f602c1" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE "space_users" DROP CONSTRAINT "FK_dae4f7e55306bdcec6ac8f602c1"`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "space_users" DROP CONSTRAINT "FK_e735cdb3781f344a2dff3083fd5"`);
|
|
||||||
await queryRunner.query(`DROP TABLE "space_users"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
|
||||||
|
|
||||||
export class SpaceGroupsMembership1710892343941 implements MigrationInterface {
|
|
||||||
name = 'SpaceGroupsMembership1710892343941'
|
|
||||||
|
|
||||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`CREATE TABLE "space_groups" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "groupId" uuid NOT NULL, "spaceId" uuid NOT NULL, "role" character varying(100), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_68e59d7b983dfefc7d33febe4c3" UNIQUE ("spaceId", "groupId"), CONSTRAINT "PK_31f9b87a8dced378cb68f04836b" PRIMARY KEY ("id"))`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "space_groups" ADD CONSTRAINT "FK_b3950d22b51148de9e14a1e5020" FOREIGN KEY ("groupId") REFERENCES "groups"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "space_groups" ADD CONSTRAINT "FK_80567cbf54af9e8e8ec469d247d" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
|
||||||
await queryRunner.query(`ALTER TABLE "space_groups" DROP CONSTRAINT "FK_80567cbf54af9e8e8ec469d247d"`);
|
|
||||||
await queryRunner.query(`ALTER TABLE "space_groups" DROP CONSTRAINT "FK_b3950d22b51148de9e14a1e5020"`);
|
|
||||||
await queryRunner.query(`DROP TABLE "space_groups"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class PolymorphicSpaceMembers1711054895950 implements MigrationInterface {
|
||||||
|
name = 'PolymorphicSpaceMembers1711054895950'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`CREATE TABLE "space_members" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "userId" uuid, "groupId" uuid, "spaceId" uuid NOT NULL, "role" character varying(100) NOT NULL, "creatorId" uuid, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "UQ_07add45942b705c4b8c6c88013d" UNIQUE ("spaceId", "groupId"), CONSTRAINT "UQ_e91b442c5a1c7aa13c767c88363" UNIQUE ("spaceId", "userId"), CONSTRAINT "PK_5aaa6440d7f1e8b8c051df43d5e" PRIMARY KEY ("id"))`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "space_members" ADD CONSTRAINT "FK_6b3b64db93d9a721ff7005eb6a3" FOREIGN KEY ("userId") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "space_members" ADD CONSTRAINT "FK_1677eab7e3f6602e13ca23418f5" FOREIGN KEY ("groupId") REFERENCES "groups"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "space_members" ADD CONSTRAINT "FK_25571cab1e221c0278499f4e801" FOREIGN KEY ("spaceId") REFERENCES "spaces"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "space_members" ADD CONSTRAINT "FK_63ce441685d52339875a4a33b7e" FOREIGN KEY ("creatorId") REFERENCES "users"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "space_members" DROP CONSTRAINT "FK_63ce441685d52339875a4a33b7e"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "space_members" DROP CONSTRAINT "FK_25571cab1e221c0278499f4e801"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "space_members" DROP CONSTRAINT "FK_1677eab7e3f6602e13ca23418f5"`);
|
||||||
|
await queryRunner.query(`ALTER TABLE "space_members" DROP CONSTRAINT "FK_6b3b64db93d9a721ff7005eb6a3"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "space_members"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||||
|
|
||||||
|
export class SpaceMemberEntityConstraint1711059108729 implements MigrationInterface {
|
||||||
|
name = 'SpaceMemberEntityConstraint1711059108729'
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "space_members" ADD CONSTRAINT "CHK_allow_userId_or_groupId" CHECK (("userId" IS NOT NULL AND "groupId" IS NULL) OR ("userId" IS NULL AND "groupId" IS NOT NULL))`);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(`ALTER TABLE "space_members" DROP CONSTRAINT "CHK_allow_userId_or_groupId"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user