File

src/workspace/workspace-members.service.ts

Index

Methods

Constructor

constructor(workspaceMemberRepository: Repository, workspaceService: WorkspaceService, userService: UserService)
Parameters :
Name Type Optional
workspaceMemberRepository Repository<DbWorkspaceMember> No
workspaceService WorkspaceService No
userService UserService No

Methods

baseQueryBuilder
baseQueryBuilder()
Returns : SelectQueryBuilder<DbWorkspaceMember>
Async deleteOneWorkspaceMember
deleteOneWorkspaceMember(id: string, memberId: string, userId: number)
Parameters :
Name Type Optional
id string No
memberId string No
userId number No
Returns : unknown
Async deleteWorkspaceMembers
deleteWorkspaceMembers(dto: DeleteWorkspaceMembersDto, id: string, userId: number)
Parameters :
Name Type Optional
dto DeleteWorkspaceMembersDto No
id string No
userId number No
Returns : unknown
Async findAllMembersByWorkspaceIdForUserId
findAllMembersByWorkspaceIdForUserId(pageOptionsDto: PageOptionsDto, id: string, userId: number | undefined)
Parameters :
Name Type Optional
pageOptionsDto PageOptionsDto No
id string No
userId number | undefined No
Async updateOneWorkspaceMember
updateOneWorkspaceMember(dto: UpdateWorkspaceMemberDto, id: string, memberId: string, userId: number)
Parameters :
Name Type Optional
dto UpdateWorkspaceMemberDto No
id string No
memberId string No
userId number No
Async updateWorkspaceMembers
updateWorkspaceMembers(dto: UpdateWorkspaceMembersDto, id: string, userId: number)
Parameters :
Name Type Optional
dto UpdateWorkspaceMembersDto No
id string No
userId number No
import { Injectable, NotFoundException, UnauthorizedException } from "@nestjs/common";
import { Repository, SelectQueryBuilder } from "typeorm";
import { InjectRepository } from "@nestjs/typeorm";

import { PageMetaDto } from "../common/dtos/page-meta.dto";
import { PageDto } from "../common/dtos/page.dto";
import { PageOptionsDto } from "../common/dtos/page-options.dto";
import { UserService } from "../user/services/user.service";
import { DbWorkspaceMember } from "./entities/workspace-member.entity";
import { DbWorkspace } from "./entities/workspace.entity";
import { WorkspaceService } from "./workspace.service";
import { canUserManageWorkspace, canUserViewWorkspace } from "./common/memberAccess";
import { UpdateWorkspaceMemberDto, UpdateWorkspaceMembersDto } from "./dtos/update-workspace-members.dto";
import { DeleteWorkspaceMembersDto } from "./dtos/delete-workspace-member.dto";

@Injectable()
export class WorkspaceMembersService {
  constructor(
    @InjectRepository(DbWorkspaceMember, "ApiConnection")
    private workspaceMemberRepository: Repository<DbWorkspaceMember>,
    private workspaceService: WorkspaceService,
    private userService: UserService
  ) {}

  baseQueryBuilder(): SelectQueryBuilder<DbWorkspaceMember> {
    const builder = this.workspaceMemberRepository.createQueryBuilder("workspace_members");

    return builder;
  }

  async findAllMembersByWorkspaceIdForUserId(
    pageOptionsDto: PageOptionsDto,
    id: string,
    userId: number | undefined
  ): Promise<PageDto<DbWorkspaceMember>> {
    const workspace = await this.workspaceService.findOneById(id);

    /*
     * viewers, editors, and owners can see who belongs to a workspace
     */

    const canView = canUserViewWorkspace(workspace, userId);

    if (!canView) {
      throw new UnauthorizedException();
    }

    const queryBuilder = this.baseQueryBuilder();

    queryBuilder
      .leftJoinAndSelect("workspace_members.member", "users", "users.id = workspace_members.user_id")
      .where("workspace_members.workspace_id = :id", { id })
      .andWhere("users.deleted_at IS NULL")
      .skip(pageOptionsDto.skip)
      .take(pageOptionsDto.limit);

    const itemCount = await queryBuilder.getCount();
    const entities = await queryBuilder.getMany();

    const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto });

    return new PageDto(entities, pageMetaDto);
  }

  async updateOneWorkspaceMember(
    dto: UpdateWorkspaceMemberDto,
    id: string,
    memberId: string,
    userId: number
  ): Promise<DbWorkspace> {
    const workspace = await this.workspaceService.findOneById(id);

    /*
     * owners can update the workspace members
     */

    const canUpdate = canUserManageWorkspace(workspace, userId);

    if (!canUpdate) {
      throw new UnauthorizedException();
    }

    const existingMember = await this.workspaceMemberRepository.findOne({
      where: {
        id: memberId,
        workspace_id: id,
      },
      withDeleted: true,
    });

    if (!existingMember) {
      throw new NotFoundException();
    }

    /*
     * throw an error if the user is attempting to update their own role.
     * this (for now) shouldn't be allowed in order to prevent any sort of "orphaning"
     * scenarios from occurring.
     * In the future, this should be expanded to allow this but prevent an "ownerless" workspace.
     */
    if (existingMember.user_id === userId) {
      throw new UnauthorizedException("cannot update own role in workspace");
    }

    existingMember.role = dto.role!;
    await this.workspaceMemberRepository.save(existingMember);

    // restores previously soft deleted rows
    if (existingMember.deleted_at) {
      await this.workspaceMemberRepository.restore(existingMember.id);
    }

    return this.workspaceService.findOneById(id);
  }

  async updateWorkspaceMembers(dto: UpdateWorkspaceMembersDto, id: string, userId: number): Promise<DbWorkspace> {
    const workspace = await this.workspaceService.findOneById(id);

    /*
     * owners can update the workspace members
     */

    const canUpdate = canUserManageWorkspace(workspace, userId);

    if (!canUpdate) {
      throw new UnauthorizedException();
    }

    const promises = dto.members.map(async (member) => {
      const existingMember = await this.workspaceMemberRepository.findOne({
        where: {
          workspace_id: id,
          user_id: member.id,
        },
        withDeleted: true,
      });

      if (existingMember) {
        /*
         * throw an error if the user is attempting to update their own role.
         * this (for now) shouldn't be allowed in order to prevent any sort of "orphaning"
         * scenarios from occurring.
         * In the future, this should be expanded to allow this but prevent an "ownerless" workspace.
         */
        if (existingMember.user_id === userId) {
          throw new UnauthorizedException("cannot update own role in workspace");
        }

        existingMember.role = member.role;
        await this.workspaceMemberRepository.save(existingMember);

        // restores previously soft deleted rows
        if (existingMember.deleted_at) {
          await this.workspaceMemberRepository.restore(existingMember.id);
        }
      } else {
        const user = await this.userService.tryFindUserOrMakeStub({ userId: member.id });
        const newMember = new DbWorkspaceMember();

        newMember.workspace = workspace;
        newMember.member = user;
        newMember.role = member.role;

        await this.workspaceMemberRepository.save(newMember);
      }
    });

    await Promise.all(promises);
    return this.workspaceService.findOneById(id);
  }

  async deleteOneWorkspaceMember(id: string, memberId: string, userId: number) {
    const workspace = await this.workspaceService.findOneById(id);

    /*
     * owners can delete the workspace members
     */

    const canDelete = canUserManageWorkspace(workspace, userId);

    if (!canDelete) {
      throw new UnauthorizedException();
    }

    const existingMember = await this.workspaceMemberRepository.findOne({
      where: {
        id: memberId,
        workspace_id: id,
      },
    });

    if (!existingMember) {
      throw new NotFoundException();
    }

    /*
     * throw an error if the user is attempting to delete themselves.
     * this (for now) shouldn't be allowed in order to prevent any sort of "orphaning"
     * scenarios from occurring.
     * In the future, this should be expanded to prevent there from never being an owner.
     */
    if (existingMember.user_id === userId) {
      throw new UnauthorizedException("cannot delete self from workspace");
    }

    await this.workspaceMemberRepository.softDelete(existingMember.id);

    return this.workspaceService.findOneById(id);
  }

  async deleteWorkspaceMembers(dto: DeleteWorkspaceMembersDto, id: string, userId: number) {
    const workspace = await this.workspaceService.findOneById(id);

    /*
     * owners can delete the workspace members
     */

    const canDelete = canUserManageWorkspace(workspace, userId);

    if (!canDelete) {
      throw new UnauthorizedException();
    }

    const promises = dto.members.map(async (member) => {
      const existingMember = await this.workspaceMemberRepository.findOne({
        where: {
          workspace_id: id,
          user_id: member.id,
        },
      });

      if (!existingMember) {
        throw new NotFoundException();
      }

      /*
       * throw an error if the user is attempting to delete themselves.
       * this (for now) shouldn't be allowed in order to prevent any sort of "orphaning"
       * scenarios from occurring.
       * In the future, this should be expanded to prevent there from never being an owner.
       */
      if (existingMember.user_id === userId) {
        throw new UnauthorizedException("cannot delete self from workspace");
      }

      return this.workspaceMemberRepository.softDelete(existingMember.id);
    });

    try {
      await Promise.all(promises);
    } catch (error) {
      dto.members.forEach(async (member) => {
        // restore the members who may have been soft deleted
        const existingMember = await this.workspaceMemberRepository.findOne({
          where: {
            workspace_id: id,
            user_id: member.id,
          },
          withDeleted: true,
        });

        if (existingMember) {
          await this.workspaceMemberRepository.restore(existingMember.id);
        }
      });

      // throws the original error
      throw error;
    }

    return this.workspaceService.findOneById(id);
  }
}

results matching ""

    No results matching ""