Use polymorphic table for space membership

This commit is contained in:
Philipinho
2024-03-22 00:40:13 +00:00
parent 639842182c
commit 51baf30f0d
25 changed files with 471 additions and 452 deletions

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

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SpaceService } from './space.service';
describe('SpaceService', () => {
let service: SpaceService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [SpaceService],
}).compile();
service = module.get<SpaceService>(SpaceService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

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