mirror of
https://github.com/docmost/docmost.git
synced 2025-11-22 04:01:12 +10:00
Refactoring
* Refactor workspace membership system * Create setup endpoint * Use Passport.js * Several updates and fixes
This commit is contained in:
@ -7,18 +7,14 @@ import {
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { WorkspaceService } from '../services/workspace.service';
|
||||
import { JwtGuard } from '../../auth/guards/jwt.guard';
|
||||
import { UpdateWorkspaceDto } from '../dto/update-workspace.dto';
|
||||
import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto';
|
||||
import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto';
|
||||
import { RemoveWorkspaceUserDto } from '../dto/remove-workspace-user.dto';
|
||||
import { AddWorkspaceUserDto } from '../dto/add-workspace-user.dto';
|
||||
import { AuthUser } from '../../../decorators/auth-user.decorator';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { CurrentWorkspace } from '../../../decorators/current-workspace.decorator';
|
||||
import { AuthWorkspace } from '../../../decorators/auth-workspace.decorator';
|
||||
import { Workspace } from '../entities/workspace.entity';
|
||||
import { PaginationOptions } from '../../../helpers/pagination/pagination-options';
|
||||
import { WorkspaceUserService } from '../services/workspace-user.service';
|
||||
import { WorkspaceInvitationService } from '../services/workspace-invitation.service';
|
||||
import { Public } from '../../../decorators/public.decorator';
|
||||
import {
|
||||
@ -27,14 +23,15 @@ import {
|
||||
RevokeInviteDto,
|
||||
} from '../dto/invitation.dto';
|
||||
import { Action } from '../../casl/ability.action';
|
||||
import { WorkspaceUser } from '../entities/workspace-user.entity';
|
||||
import { WorkspaceInvitation } from '../entities/workspace-invitation.entity';
|
||||
import { CheckPolicies } from '../../casl/decorators/policies.decorator';
|
||||
import { AppAbility } from '../../casl/abilities/casl-ability.factory';
|
||||
import { PoliciesGuard } from '../../casl/guards/policies.guard';
|
||||
import { WorkspaceUserService } from '../services/workspace-user.service';
|
||||
import { JwtAuthGuard } from '../../../guards/jwt-auth.guard';
|
||||
|
||||
@UseGuards(JwtGuard)
|
||||
@Controller('workspaces')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@Controller('workspace')
|
||||
export class WorkspaceController {
|
||||
constructor(
|
||||
private readonly workspaceService: WorkspaceService,
|
||||
@ -43,33 +40,21 @@ export class WorkspaceController {
|
||||
) {}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('/')
|
||||
async getUserWorkspaces(
|
||||
@Body()
|
||||
pagination: PaginationOptions,
|
||||
@Post('/info')
|
||||
async getWorkspace(
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.workspaceService.getUserWorkspaces(user.id, pagination);
|
||||
return this.workspaceService.getWorkspaceInfo(workspace.id);
|
||||
}
|
||||
|
||||
/*
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('create')
|
||||
async createWorkspace(
|
||||
@Body() createWorkspaceDto: CreateWorkspaceDto,
|
||||
@AuthUser() user: User,
|
||||
) {
|
||||
return this.workspaceService.create(user.id, createWorkspaceDto);
|
||||
}
|
||||
*/
|
||||
|
||||
@UseGuards(PoliciesGuard)
|
||||
@CheckPolicies((ability: AppAbility) => ability.can(Action.Manage, Workspace))
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('update')
|
||||
async updateWorkspace(
|
||||
@Body() updateWorkspaceDto: UpdateWorkspaceDto,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.workspaceService.update(workspace.id, updateWorkspaceDto);
|
||||
}
|
||||
@ -79,19 +64,19 @@ export class WorkspaceController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('delete')
|
||||
async deleteWorkspace(@Body() deleteWorkspaceDto: DeleteWorkspaceDto) {
|
||||
return this.workspaceService.delete(deleteWorkspaceDto);
|
||||
// return this.workspaceService.delete(deleteWorkspaceDto);
|
||||
}
|
||||
|
||||
@UseGuards(PoliciesGuard)
|
||||
@CheckPolicies((ability: AppAbility) =>
|
||||
ability.can(Action.Read, WorkspaceUser),
|
||||
ability.can(Action.Read, 'workspaceUser'),
|
||||
)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('members')
|
||||
async getWorkspaceMembers(
|
||||
@Body()
|
||||
pagination: PaginationOptions,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.workspaceUserService.getWorkspaceUsers(
|
||||
workspace.id,
|
||||
@ -100,48 +85,25 @@ export class WorkspaceController {
|
||||
}
|
||||
|
||||
@UseGuards(PoliciesGuard)
|
||||
@CheckPolicies((ability: AppAbility) =>
|
||||
ability.can(Action.Manage, WorkspaceUser),
|
||||
)
|
||||
// @CheckPolicies((ability: AppAbility) =>
|
||||
// ability.can(Action.Manage, 'WorkspaceUser'),
|
||||
// )
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('members/add')
|
||||
async addWorkspaceMember(
|
||||
@Body() addWorkspaceUserDto: AddWorkspaceUserDto,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.workspaceUserService.addUserToWorkspace(
|
||||
addWorkspaceUserDto.userId,
|
||||
workspace.id,
|
||||
addWorkspaceUserDto.role,
|
||||
);
|
||||
@Post('members/deactivate')
|
||||
async deactivateWorkspaceMember() {
|
||||
return this.workspaceUserService.deactivateUser();
|
||||
}
|
||||
|
||||
@UseGuards(PoliciesGuard)
|
||||
@CheckPolicies((ability: AppAbility) =>
|
||||
ability.can(Action.Manage, WorkspaceUser),
|
||||
)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('members/remove')
|
||||
async removeWorkspaceMember(
|
||||
@Body() removeWorkspaceUserDto: RemoveWorkspaceUserDto,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.workspaceUserService.removeUserFromWorkspace(
|
||||
removeWorkspaceUserDto.userId,
|
||||
workspace.id,
|
||||
);
|
||||
}
|
||||
|
||||
@UseGuards(PoliciesGuard)
|
||||
@CheckPolicies((ability: AppAbility) =>
|
||||
ability.can(Action.Manage, WorkspaceUser),
|
||||
ability.can(Action.Manage, 'workspaceUser'),
|
||||
)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('members/role')
|
||||
async updateWorkspaceMemberRole(
|
||||
@Body() workspaceUserRoleDto: UpdateWorkspaceUserRoleDto,
|
||||
@AuthUser() authUser: User,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.workspaceUserService.updateWorkspaceUserRole(
|
||||
authUser,
|
||||
@ -159,7 +121,7 @@ export class WorkspaceController {
|
||||
async inviteUser(
|
||||
@Body() inviteUserDto: InviteUserDto,
|
||||
@AuthUser() authUser: User,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.workspaceInvitationService.createInvitation(
|
||||
authUser,
|
||||
@ -172,9 +134,9 @@ export class WorkspaceController {
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('invite/accept')
|
||||
async acceptInvite(@Body() acceptInviteDto: AcceptInviteDto) {
|
||||
return this.workspaceInvitationService.acceptInvitation(
|
||||
acceptInviteDto.invitationId,
|
||||
);
|
||||
// return this.workspaceInvitationService.acceptInvitation(
|
||||
// acceptInviteDto.invitationId,
|
||||
//);
|
||||
}
|
||||
|
||||
// TODO: authorize permission with guards
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { IsEmail, IsEnum, IsOptional, IsString, IsUUID } from 'class-validator';
|
||||
import { WorkspaceUserRole } from '../entities/workspace-user.entity';
|
||||
import { UserRole } from '../../../helpers/types/permission';
|
||||
|
||||
export class InviteUserDto {
|
||||
@IsString()
|
||||
@ -9,7 +9,7 @@ export class InviteUserDto {
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@IsEnum(WorkspaceUserRole)
|
||||
@IsEnum(UserRole)
|
||||
role: string;
|
||||
}
|
||||
|
||||
|
||||
@ -1,52 +0,0 @@
|
||||
import {
|
||||
Entity,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
Unique,
|
||||
} from 'typeorm';
|
||||
import { Workspace } from './workspace.entity';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
|
||||
@Entity('workspace_users')
|
||||
@Unique(['workspaceId', 'userId'])
|
||||
export class WorkspaceUser {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column()
|
||||
userId: string;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.workspaceUsers, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'userId' })
|
||||
user: User;
|
||||
|
||||
@Column()
|
||||
workspaceId: string;
|
||||
|
||||
@ManyToOne(() => Workspace, (workspace) => workspace.workspaceUsers, {
|
||||
onDelete: 'CASCADE',
|
||||
})
|
||||
@JoinColumn({ name: 'workspaceId' })
|
||||
workspace: Workspace;
|
||||
|
||||
@Column({ length: 100, nullable: true })
|
||||
role: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt: Date;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export enum WorkspaceUserRole {
|
||||
OWNER = 'owner',
|
||||
ADMIN = 'admin',
|
||||
MEMBER = 'member',
|
||||
}
|
||||
@ -4,18 +4,18 @@ import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
JoinColumn,
|
||||
OneToOne,
|
||||
DeleteDateColumn,
|
||||
} from 'typeorm';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { WorkspaceUser } from './workspace-user.entity';
|
||||
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';
|
||||
import { UserRole } from '../../../helpers/types/permission';
|
||||
|
||||
@Entity('workspaces')
|
||||
export class Workspace {
|
||||
@ -46,12 +46,15 @@ export class Workspace {
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
settings: any;
|
||||
|
||||
@Column()
|
||||
@Column({ default: UserRole.MEMBER })
|
||||
defaultRole: string;
|
||||
|
||||
@Column({ nullable: true, type: 'uuid' })
|
||||
creatorId: string;
|
||||
|
||||
@ManyToOne(() => User, (user) => user.workspaces)
|
||||
@JoinColumn({ name: 'creatorId' })
|
||||
creator: User;
|
||||
//@ManyToOne(() => User, (user) => user.workspaces)
|
||||
// @JoinColumn({ name: 'creatorId' })
|
||||
// creator: User;
|
||||
|
||||
@Column({ nullable: true })
|
||||
defaultSpaceId: string;
|
||||
@ -66,8 +69,11 @@ export class Workspace {
|
||||
@UpdateDateColumn()
|
||||
updatedAt: Date;
|
||||
|
||||
@OneToMany(() => WorkspaceUser, (workspaceUser) => workspaceUser.workspace)
|
||||
workspaceUsers: WorkspaceUser[];
|
||||
@DeleteDateColumn()
|
||||
deletedAt: Date;
|
||||
|
||||
@OneToMany(() => User, (user) => user.workspace)
|
||||
users: [];
|
||||
|
||||
@OneToMany(
|
||||
() => WorkspaceInvitation,
|
||||
@ -87,5 +93,5 @@ export class Workspace {
|
||||
@OneToMany(() => Group, (group) => group.workspace)
|
||||
groups: [];
|
||||
|
||||
workspaceUser?: WorkspaceUser;
|
||||
// workspaceUser?: WorkspaceUser;
|
||||
}
|
||||
|
||||
@ -1,10 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
import { WorkspaceUser } from '../entities/workspace-user.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceUserRepository extends Repository<WorkspaceUser> {
|
||||
constructor(private dataSource: DataSource) {
|
||||
super(WorkspaceUser, dataSource.createEntityManager());
|
||||
}
|
||||
}
|
||||
@ -8,7 +8,24 @@ export class WorkspaceRepository extends Repository<Workspace> {
|
||||
super(Workspace, dataSource.createEntityManager());
|
||||
}
|
||||
|
||||
async findById(workspaceId: string) {
|
||||
return this.findOneBy({ id: workspaceId });
|
||||
async findById(workspaceId: string): Promise<Workspace> {
|
||||
// see: https://github.com/typeorm/typeorm/issues/9316
|
||||
const queryBuilder = this.dataSource.createQueryBuilder(
|
||||
Workspace,
|
||||
'workspace',
|
||||
);
|
||||
return await queryBuilder
|
||||
.where('workspace.id = :id', { id: workspaceId })
|
||||
.getOne();
|
||||
}
|
||||
|
||||
async findFirst(): Promise<Workspace> {
|
||||
const createdWorkspace = await this.find({
|
||||
order: {
|
||||
createdAt: 'ASC',
|
||||
},
|
||||
take: 1,
|
||||
});
|
||||
return createdWorkspace[0];
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,10 +3,11 @@ import { WorkspaceInvitationRepository } from '../repositories/workspace-invitat
|
||||
import { WorkspaceInvitation } from '../entities/workspace-invitation.entity';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { WorkspaceService } from './workspace.service';
|
||||
import { WorkspaceUserService } from './workspace-user.service';
|
||||
import { WorkspaceUserRole } from '../entities/workspace-user.entity';
|
||||
import { UserService } from '../../user/user.service';
|
||||
import { InviteUserDto } from '../dto/invitation.dto';
|
||||
import { WorkspaceUserService } from './workspace-user.service';
|
||||
import { UserRole } from '../../../helpers/types/permission';
|
||||
import { UserRepository } from '../../user/repositories/user.repository';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceInvitationService {
|
||||
@ -15,6 +16,7 @@ export class WorkspaceInvitationService {
|
||||
private workspaceService: WorkspaceService,
|
||||
private workspaceUserService: WorkspaceUserService,
|
||||
private userService: UserService,
|
||||
private userRepository: UserRepository,
|
||||
) {}
|
||||
|
||||
async findInvitedUserByEmail(
|
||||
@ -32,37 +34,17 @@ export class WorkspaceInvitationService {
|
||||
workspaceId: string,
|
||||
inviteUserDto: InviteUserDto,
|
||||
): Promise<WorkspaceInvitation> {
|
||||
const authUserMembership =
|
||||
await this.workspaceUserService.findWorkspaceUser(
|
||||
authUser.id,
|
||||
// check if invited user is already a workspace member
|
||||
const invitedUser =
|
||||
await this.workspaceUserService.findWorkspaceUserByEmail(
|
||||
inviteUserDto.email,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!authUserMembership) {
|
||||
throw new BadRequestException('Inviting user must be a workspace member');
|
||||
}
|
||||
|
||||
if (authUserMembership.role != WorkspaceUserRole.OWNER) {
|
||||
throw new BadRequestException(
|
||||
'Only workspace owners can invite new members',
|
||||
);
|
||||
}
|
||||
|
||||
const invitedUser = await this.userService.findByEmail(inviteUserDto.email);
|
||||
|
||||
// check if invited user is already a workspace member
|
||||
if (invitedUser) {
|
||||
const invitedUserMembership =
|
||||
await this.workspaceUserService.findWorkspaceUser(
|
||||
invitedUser.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (invitedUserMembership) {
|
||||
throw new BadRequestException(
|
||||
'This user already a member of this workspace',
|
||||
);
|
||||
}
|
||||
throw new BadRequestException(
|
||||
'User is already a member of this workspace',
|
||||
);
|
||||
}
|
||||
|
||||
// check if user was already invited
|
||||
@ -72,7 +54,7 @@ export class WorkspaceInvitationService {
|
||||
);
|
||||
|
||||
if (existingInvitation) {
|
||||
throw new BadRequestException('This user has already been invited');
|
||||
throw new BadRequestException('User has already been invited');
|
||||
}
|
||||
|
||||
const invitation = new WorkspaceInvitation();
|
||||
@ -97,14 +79,22 @@ export class WorkspaceInvitationService {
|
||||
|
||||
// TODO: to be completed
|
||||
|
||||
// check if user is in the system already
|
||||
const invitedUser = await this.userService.findByEmail(invitation.email);
|
||||
// check if user is already a member
|
||||
const invitedUser =
|
||||
await this.workspaceUserService.findWorkspaceUserByEmail(
|
||||
invitation.email,
|
||||
invitation.workspaceId,
|
||||
);
|
||||
|
||||
if (invitedUser) {
|
||||
// fetch the workspace
|
||||
// add the user to the workspace
|
||||
throw new BadRequestException(
|
||||
'User is already a member of this workspace',
|
||||
);
|
||||
}
|
||||
return invitation;
|
||||
// add create account for user
|
||||
// add the user to the workspace
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async revokeInvitation(invitationId: string): Promise<void> {
|
||||
|
||||
@ -1,69 +1,34 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { WorkspaceUserRepository } from '../repositories/workspace-user.repository';
|
||||
import {
|
||||
WorkspaceUser,
|
||||
WorkspaceUserRole,
|
||||
} from '../entities/workspace-user.entity';
|
||||
import { Workspace } from '../entities/workspace.entity';
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { UpdateWorkspaceUserRoleDto } from '../dto/update-workspace-user-role.dto';
|
||||
import { PaginationOptions } from '../../../helpers/pagination/pagination-options';
|
||||
import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto';
|
||||
import { PaginatedResult } from '../../../helpers/pagination/paginated-result';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { DataSource, EntityManager } from 'typeorm';
|
||||
import { transactionWrapper } from '../../../helpers/db.helper';
|
||||
import { WorkspaceRepository } from '../repositories/workspace.repository';
|
||||
import { UserRepository } from '../../user/repositories/user.repository';
|
||||
import { UserRole } from '../../../helpers/types/permission';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceUserService {
|
||||
constructor(
|
||||
private workspaceUserRepository: WorkspaceUserRepository,
|
||||
private dataSource: DataSource,
|
||||
private workspaceRepository: WorkspaceRepository,
|
||||
private userRepository: UserRepository,
|
||||
) {}
|
||||
|
||||
async addUserToWorkspace(
|
||||
userId: string,
|
||||
async getWorkspaceUsers(
|
||||
workspaceId: string,
|
||||
role: string,
|
||||
manager?: EntityManager,
|
||||
): Promise<WorkspaceUser> {
|
||||
let addedUser;
|
||||
|
||||
await transactionWrapper(
|
||||
async (manager) => {
|
||||
const userExists = await manager.exists(User, {
|
||||
where: { id: userId },
|
||||
});
|
||||
if (!userExists) {
|
||||
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',
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceUser = new WorkspaceUser();
|
||||
workspaceUser.userId = userId;
|
||||
workspaceUser.workspaceId = workspaceId;
|
||||
workspaceUser.role = role;
|
||||
|
||||
addedUser = await manager.save(workspaceUser);
|
||||
paginationOptions: PaginationOptions,
|
||||
): Promise<PaginatedResult<User>> {
|
||||
const [workspaceUsers, count] = await this.userRepository.findAndCount({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
this.dataSource,
|
||||
manager,
|
||||
);
|
||||
take: paginationOptions.limit,
|
||||
skip: paginationOptions.skip,
|
||||
});
|
||||
|
||||
return addedUser;
|
||||
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
||||
return new PaginatedResult(workspaceUsers, paginationMeta);
|
||||
}
|
||||
|
||||
async updateWorkspaceUserRole(
|
||||
@ -80,16 +45,14 @@ export class WorkspaceUserService {
|
||||
return workspaceUser;
|
||||
}
|
||||
|
||||
const workspaceOwnerCount = await this.workspaceUserRepository.count({
|
||||
const workspaceOwnerCount = await this.userRepository.count({
|
||||
where: {
|
||||
role: WorkspaceUserRole.OWNER,
|
||||
role: UserRole.OWNER,
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
workspaceUser.role === WorkspaceUserRole.OWNER &&
|
||||
workspaceOwnerCount === 1
|
||||
) {
|
||||
if (workspaceUser.role === UserRole.OWNER && workspaceOwnerCount === 1) {
|
||||
throw new BadRequestException(
|
||||
'There must be at least one workspace owner',
|
||||
);
|
||||
@ -97,105 +60,26 @@ export class WorkspaceUserService {
|
||||
|
||||
workspaceUser.role = workspaceUserRoleDto.role;
|
||||
|
||||
return this.workspaceUserRepository.save(workspaceUser);
|
||||
return this.userRepository.save(workspaceUser);
|
||||
}
|
||||
|
||||
async removeUserFromWorkspace(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
const workspaceUser = await this.findAndValidateWorkspaceUser(
|
||||
userId,
|
||||
workspaceId,
|
||||
);
|
||||
async deactivateUser(): Promise<any> {
|
||||
return 'todo';
|
||||
}
|
||||
|
||||
const workspaceOwnerCount = await this.workspaceUserRepository.count({
|
||||
where: {
|
||||
role: WorkspaceUserRole.OWNER,
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
workspaceUser.role === WorkspaceUserRole.OWNER &&
|
||||
workspaceOwnerCount === 1
|
||||
) {
|
||||
throw new BadRequestException(
|
||||
'There must be at least one workspace owner',
|
||||
);
|
||||
}
|
||||
|
||||
await this.workspaceUserRepository.delete({
|
||||
userId,
|
||||
async findWorkspaceUser(userId: string, workspaceId: string): Promise<User> {
|
||||
return await this.userRepository.findOneBy({
|
||||
id: userId,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
|
||||
async getUserWorkspaces(
|
||||
userId: string,
|
||||
paginationOptions: PaginationOptions,
|
||||
): Promise<PaginatedResult<Workspace>> {
|
||||
const [workspaces, count] = await this.workspaceUserRepository.findAndCount(
|
||||
{
|
||||
where: { userId: userId },
|
||||
relations: ['workspace'],
|
||||
take: paginationOptions.limit,
|
||||
skip: paginationOptions.skip,
|
||||
},
|
||||
);
|
||||
|
||||
const userWorkspaces = workspaces.map(
|
||||
(userWorkspace: WorkspaceUser) => userWorkspace.workspace,
|
||||
);
|
||||
|
||||
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
||||
return new PaginatedResult(userWorkspaces, paginationMeta);
|
||||
}
|
||||
|
||||
async getWorkspaceUsers(
|
||||
async findWorkspaceUserByEmail(
|
||||
email: string,
|
||||
workspaceId: string,
|
||||
paginationOptions: PaginationOptions,
|
||||
): Promise<PaginatedResult<any>> {
|
||||
const [workspaceUsers, count] =
|
||||
await this.workspaceUserRepository.findAndCount({
|
||||
relations: ['user'],
|
||||
where: {
|
||||
workspace: {
|
||||
id: workspaceId,
|
||||
},
|
||||
},
|
||||
take: paginationOptions.limit,
|
||||
skip: paginationOptions.skip,
|
||||
});
|
||||
|
||||
const users = workspaceUsers.map((workspaceUser) => {
|
||||
workspaceUser.user.password = '';
|
||||
return {
|
||||
...workspaceUser.user,
|
||||
role: workspaceUser.role,
|
||||
};
|
||||
});
|
||||
|
||||
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
||||
return new PaginatedResult(users, paginationMeta);
|
||||
}
|
||||
|
||||
async getUserRoleInWorkspace(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
): Promise<string> {
|
||||
const workspaceUser = await this.findAndValidateWorkspaceUser(
|
||||
userId,
|
||||
workspaceId,
|
||||
);
|
||||
return workspaceUser.role ? workspaceUser.role : null;
|
||||
}
|
||||
|
||||
async findWorkspaceUser(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
): Promise<WorkspaceUser> {
|
||||
return await this.workspaceUserRepository.findOneBy({
|
||||
userId,
|
||||
): Promise<User> {
|
||||
return await this.userRepository.findOneBy({
|
||||
email: email,
|
||||
workspaceId,
|
||||
});
|
||||
}
|
||||
@ -203,13 +87,13 @@ export class WorkspaceUserService {
|
||||
async findAndValidateWorkspaceUser(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
): Promise<WorkspaceUser> {
|
||||
const workspaceUser = await this.findWorkspaceUser(userId, workspaceId);
|
||||
): Promise<User> {
|
||||
const user = await this.findWorkspaceUser(userId, workspaceId);
|
||||
|
||||
if (!workspaceUser) {
|
||||
if (!user) {
|
||||
throw new BadRequestException('Workspace member not found');
|
||||
}
|
||||
|
||||
return workspaceUser;
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,32 +1,31 @@
|
||||
import { Injectable, NotFoundException } from '@nestjs/common';
|
||||
import {
|
||||
BadRequestException,
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { CreateWorkspaceDto } from '../dto/create-workspace.dto';
|
||||
import { WorkspaceRepository } from '../repositories/workspace.repository';
|
||||
import { WorkspaceUserRepository } from '../repositories/workspace-user.repository';
|
||||
import {
|
||||
WorkspaceUser,
|
||||
WorkspaceUserRole,
|
||||
} from '../entities/workspace-user.entity';
|
||||
import { Workspace } from '../entities/workspace.entity';
|
||||
import { plainToInstance } from 'class-transformer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { UpdateWorkspaceDto } from '../dto/update-workspace.dto';
|
||||
import { DeleteWorkspaceDto } from '../dto/delete-workspace.dto';
|
||||
import { SpaceService } from '../../space/space.service';
|
||||
import { PaginationOptions } from '../../../helpers/pagination/pagination-options';
|
||||
import { PaginationMetaDto } from '../../../helpers/pagination/pagination-meta-dto';
|
||||
import { PaginatedResult } from '../../../helpers/pagination/paginated-result';
|
||||
import { DataSource, EntityManager } from 'typeorm';
|
||||
import { transactionWrapper } from '../../../helpers/db.helper';
|
||||
import { CreateSpaceDto } from '../../space/dto/create-space.dto';
|
||||
import { WorkspaceUserService } from './workspace-user.service';
|
||||
import { UserRepository } from '../../user/repositories/user.repository';
|
||||
import { SpaceRole, UserRole } from '../../../helpers/types/permission';
|
||||
import { User } from '../../user/entities/user.entity';
|
||||
import { EnvironmentService } from '../../../environment/environment.service';
|
||||
import { Space } from '../../space/entities/space.entity';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceService {
|
||||
constructor(
|
||||
private workspaceRepository: WorkspaceRepository,
|
||||
private workspaceUserRepository: WorkspaceUserRepository,
|
||||
private userRepository: UserRepository,
|
||||
private spaceService: SpaceService,
|
||||
private workspaceUserService: WorkspaceUserService,
|
||||
private environmentService: EnvironmentService,
|
||||
|
||||
private dataSource: DataSource,
|
||||
) {}
|
||||
@ -35,115 +34,118 @@ export class WorkspaceService {
|
||||
return this.workspaceRepository.findById(workspaceId);
|
||||
}
|
||||
|
||||
async save(workspace: Workspace) {
|
||||
return this.workspaceRepository.save(workspace);
|
||||
}
|
||||
async getWorkspaceInfo(workspaceId: string): Promise<Workspace> {
|
||||
const space = await this.workspaceRepository
|
||||
.createQueryBuilder('workspace')
|
||||
.where('workspace.id = :workspaceId', { workspaceId })
|
||||
.loadRelationCountAndMap(
|
||||
'workspace.userCount',
|
||||
'workspace.users',
|
||||
'workspaceUsers',
|
||||
)
|
||||
.getOne();
|
||||
|
||||
async createOrJoinWorkspace(
|
||||
userId,
|
||||
createWorkspaceDto?: CreateWorkspaceDto,
|
||||
manager?: EntityManager,
|
||||
) {
|
||||
await transactionWrapper(
|
||||
async (manager: EntityManager) => {
|
||||
const workspaceCount = await manager
|
||||
.createQueryBuilder(Workspace, 'workspace')
|
||||
.getCount();
|
||||
if (!space) {
|
||||
throw new NotFoundException('Workspace not found');
|
||||
}
|
||||
|
||||
if (workspaceCount === 0) {
|
||||
// create first workspace and add user to workspace as owner
|
||||
const createdWorkspace = await this.create(
|
||||
userId,
|
||||
createWorkspaceDto ?? null,
|
||||
manager,
|
||||
);
|
||||
await this.workspaceUserService.addUserToWorkspace(
|
||||
userId,
|
||||
createdWorkspace.id,
|
||||
WorkspaceUserRole.OWNER,
|
||||
manager,
|
||||
);
|
||||
|
||||
// create default space and add user to it too.
|
||||
const createdSpace = await this.spaceService.create(
|
||||
userId,
|
||||
createdWorkspace.id,
|
||||
{ name: 'General' } as CreateSpaceDto,
|
||||
manager,
|
||||
);
|
||||
|
||||
await this.spaceService.addUserToSpace(
|
||||
userId,
|
||||
createdSpace.id,
|
||||
WorkspaceUserRole.OWNER,
|
||||
createdWorkspace.id,
|
||||
manager,
|
||||
);
|
||||
|
||||
createdWorkspace.defaultSpaceId = createdSpace.id;
|
||||
await manager.save(createdWorkspace);
|
||||
} else {
|
||||
// limited to single workspace
|
||||
// fetch the oldest workspace and add user to it
|
||||
const firstWorkspace = await manager.find(Workspace, {
|
||||
order: {
|
||||
createdAt: 'ASC',
|
||||
},
|
||||
take: 1,
|
||||
});
|
||||
|
||||
// add user to workspace and default space
|
||||
|
||||
await this.workspaceUserService.addUserToWorkspace(
|
||||
userId,
|
||||
firstWorkspace[0].id,
|
||||
WorkspaceUserRole.MEMBER,
|
||||
manager,
|
||||
);
|
||||
|
||||
await this.spaceService.addUserToSpace(
|
||||
userId,
|
||||
firstWorkspace[0].defaultSpaceId,
|
||||
WorkspaceUserRole.MEMBER,
|
||||
firstWorkspace[0].id,
|
||||
manager,
|
||||
);
|
||||
}
|
||||
},
|
||||
this.dataSource,
|
||||
manager,
|
||||
);
|
||||
return space;
|
||||
}
|
||||
|
||||
async create(
|
||||
userId: string,
|
||||
createWorkspaceDto?: CreateWorkspaceDto,
|
||||
user: User,
|
||||
createWorkspaceDto: CreateWorkspaceDto,
|
||||
manager?: EntityManager,
|
||||
): Promise<Workspace> {
|
||||
let workspace: Workspace;
|
||||
|
||||
await transactionWrapper(
|
||||
return await transactionWrapper(
|
||||
async (manager) => {
|
||||
if (createWorkspaceDto) {
|
||||
workspace = plainToInstance(Workspace, createWorkspaceDto);
|
||||
} else {
|
||||
workspace = new Workspace();
|
||||
}
|
||||
|
||||
workspace.inviteCode = uuid();
|
||||
workspace.creatorId = userId;
|
||||
|
||||
//if (workspace.name && !workspace.hostname?.trim()) {
|
||||
// workspace.hostname = generateHostname(createWorkspaceDto.name);
|
||||
// }
|
||||
let workspace = new Workspace();
|
||||
|
||||
workspace.name = createWorkspaceDto.name;
|
||||
workspace.hostname = createWorkspaceDto?.hostname;
|
||||
workspace.description = createWorkspaceDto.description;
|
||||
workspace.inviteCode = uuidv4();
|
||||
workspace.creatorId = user.id;
|
||||
workspace = await manager.save(workspace);
|
||||
|
||||
user.workspaceId = workspace.id;
|
||||
user.role = UserRole.OWNER;
|
||||
await manager.save(user);
|
||||
|
||||
// create default space
|
||||
const spaceData: CreateSpaceDto = {
|
||||
name: 'General',
|
||||
};
|
||||
|
||||
// create default space
|
||||
const createdSpace = await this.spaceService.create(
|
||||
user.id,
|
||||
workspace.id,
|
||||
spaceData,
|
||||
manager,
|
||||
);
|
||||
|
||||
// and add user to it too.
|
||||
await this.spaceService.addUserToSpace(
|
||||
user.id,
|
||||
createdSpace.id,
|
||||
SpaceRole.OWNER,
|
||||
workspace.id,
|
||||
manager,
|
||||
);
|
||||
|
||||
workspace.defaultSpaceId = createdSpace.id;
|
||||
await manager.save(workspace);
|
||||
return workspace;
|
||||
},
|
||||
this.dataSource,
|
||||
manager,
|
||||
);
|
||||
}
|
||||
|
||||
return workspace;
|
||||
async addUserToWorkspace(
|
||||
user: User,
|
||||
workspaceId,
|
||||
assignedRole?: UserRole,
|
||||
manager?: EntityManager,
|
||||
): Promise<Workspace> {
|
||||
return await transactionWrapper(
|
||||
async (manager: EntityManager) => {
|
||||
const workspace = await manager.findOneBy(Workspace, {
|
||||
id: workspaceId,
|
||||
});
|
||||
|
||||
if (!workspace) {
|
||||
throw new BadRequestException('Workspace does not exist');
|
||||
}
|
||||
|
||||
user.role = assignedRole ?? workspace.defaultRole;
|
||||
user.workspaceId = workspace.id;
|
||||
await manager.save(user);
|
||||
|
||||
const space = await manager.findOneBy(Space, {
|
||||
id: workspace.defaultSpaceId,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
if (!space) {
|
||||
throw new NotFoundException('Space not found');
|
||||
}
|
||||
|
||||
// add user to default space
|
||||
await this.spaceService.addUserToSpace(
|
||||
user.id,
|
||||
space.id,
|
||||
space.defaultRole,
|
||||
workspace.id,
|
||||
manager,
|
||||
);
|
||||
|
||||
return workspace;
|
||||
},
|
||||
this.dataSource,
|
||||
manager,
|
||||
);
|
||||
}
|
||||
|
||||
async update(
|
||||
@ -178,42 +180,4 @@ export class WorkspaceService {
|
||||
// remove all existing users from workspace
|
||||
// delete workspace
|
||||
}
|
||||
|
||||
async getUserCurrentWorkspace(userId: string): Promise<Workspace> {
|
||||
const userWorkspace = await this.workspaceUserRepository.findOne({
|
||||
relations: ['workspace'],
|
||||
where: { userId: userId },
|
||||
order: {
|
||||
createdAt: 'ASC',
|
||||
},
|
||||
});
|
||||
|
||||
if (!userWorkspace) {
|
||||
throw new NotFoundException('No workspace found for this user');
|
||||
}
|
||||
|
||||
const { workspace, ...workspaceUser } = userWorkspace;
|
||||
return { ...workspace, workspaceUser } as Workspace;
|
||||
}
|
||||
|
||||
async getUserWorkspaces(
|
||||
userId: string,
|
||||
paginationOptions: PaginationOptions,
|
||||
): Promise<PaginatedResult<Workspace>> {
|
||||
const [workspaces, count] = await this.workspaceUserRepository.findAndCount(
|
||||
{
|
||||
where: { userId: userId },
|
||||
relations: ['workspace'],
|
||||
take: paginationOptions.limit,
|
||||
skip: paginationOptions.skip,
|
||||
},
|
||||
);
|
||||
|
||||
const userWorkspaces = workspaces.map(
|
||||
(userWorkspace: WorkspaceUser) => userWorkspace.workspace,
|
||||
);
|
||||
|
||||
const paginationMeta = new PaginationMetaDto({ count, paginationOptions });
|
||||
return new PaginatedResult(userWorkspaces, paginationMeta);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,20 +4,17 @@ import { WorkspaceController } from './controllers/workspace.controller';
|
||||
import { WorkspaceRepository } from './repositories/workspace.repository';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { Workspace } from './entities/workspace.entity';
|
||||
import { WorkspaceUser } from './entities/workspace-user.entity';
|
||||
import { WorkspaceInvitation } from './entities/workspace-invitation.entity';
|
||||
import { WorkspaceUserRepository } from './repositories/workspace-user.repository';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { SpaceModule } from '../space/space.module';
|
||||
import { WorkspaceUserService } from './services/workspace-user.service';
|
||||
import { WorkspaceInvitationService } from './services/workspace-invitation.service';
|
||||
import { WorkspaceInvitationRepository } from './repositories/workspace-invitation.repository';
|
||||
import { WorkspaceUserService } from './services/workspace-user.service';
|
||||
import { UserModule } from '../user/user.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Workspace, WorkspaceUser, WorkspaceInvitation]),
|
||||
AuthModule,
|
||||
SpaceModule,
|
||||
TypeOrmModule.forFeature([Workspace, WorkspaceInvitation]),
|
||||
SpaceModule, UserModule
|
||||
],
|
||||
controllers: [WorkspaceController],
|
||||
providers: [
|
||||
@ -25,9 +22,8 @@ import { WorkspaceInvitationRepository } from './repositories/workspace-invitati
|
||||
WorkspaceUserService,
|
||||
WorkspaceInvitationService,
|
||||
WorkspaceRepository,
|
||||
WorkspaceUserRepository,
|
||||
WorkspaceInvitationRepository,
|
||||
],
|
||||
exports: [WorkspaceService, WorkspaceRepository, WorkspaceUserRepository],
|
||||
exports: [WorkspaceService, WorkspaceRepository],
|
||||
})
|
||||
export class WorkspaceModule {}
|
||||
|
||||
@ -1,5 +0,0 @@
|
||||
export function generateHostname(name: string): string {
|
||||
let hostname = name.replace(/[^a-z0-9]/gi, '').toLowerCase();
|
||||
hostname = hostname.substring(0, 30);
|
||||
return hostname;
|
||||
}
|
||||
Reference in New Issue
Block a user