File

src/workspace/workspace-repos.service.ts

Index

Methods

Constructor

constructor(workspaceRepoRepository: Repository, workspaceService: WorkspaceService, repoService: RepoService, pullRequestGithubEventsService: PullRequestGithubEventsService, issuesGithubEventsService: IssuesGithubEventsService)
Parameters :
Name Type Optional
workspaceRepoRepository Repository<DbWorkspaceRepo> No
workspaceService WorkspaceService No
repoService RepoService No
pullRequestGithubEventsService PullRequestGithubEventsService No
issuesGithubEventsService IssuesGithubEventsService No

Methods

Async addOneWorkspaceRepo
addOneWorkspaceRepo(id: string, owner: string, repo: string, userId: number)
Parameters :
Name Type Optional
id string No
owner string No
repo string No
userId number No
Async addWorkspaceRepos
addWorkspaceRepos(dto: UpdateWorkspaceReposDto, id: string, userId: number)
Parameters :
Name Type Optional
dto UpdateWorkspaceReposDto No
id string No
userId number No
baseQueryBuilder
baseQueryBuilder()
Returns : SelectQueryBuilder<DbWorkspaceRepo>
Async deleteOneWorkspaceRepo
deleteOneWorkspaceRepo(id: string, owner: string, repo: string, userId: number)
Parameters :
Name Type Optional
id string No
owner string No
repo string No
userId number No
Returns : unknown
Async deleteWorkspaceRepos
deleteWorkspaceRepos(dto: DeleteWorkspaceReposDto, id: string, userId: number)
Parameters :
Name Type Optional
dto DeleteWorkspaceReposDto No
id string No
userId number No
Returns : unknown
Private Async executeAddWorkspaceRepo
executeAddWorkspaceRepo(workspace: DbWorkspace, ownerName: string, repoName: string)
Parameters :
Name Type Optional
workspace DbWorkspace No
ownerName string No
repoName string No
Returns : any
Async findAllRepoIssuesByWorkspaceIdForUserId
findAllRepoIssuesByWorkspaceIdForUserId(pageOptionsDto: WorkspaceRepoIssuePageOptionsDto, id: string, userId: number | undefined)
Parameters :
Name Type Optional
pageOptionsDto WorkspaceRepoIssuePageOptionsDto No
id string No
userId number | undefined No
Async findAllRepoPrsByWorkspaceIdForUserId
findAllRepoPrsByWorkspaceIdForUserId(pageOptionsDto: WorkspaceRepoPullRequestPageOptionsDto, id: string, userId: number | undefined)
Parameters :
Name Type Optional
pageOptionsDto WorkspaceRepoPullRequestPageOptionsDto No
id string No
userId number | undefined No
Async findAllReposByFilterInWorkspace
findAllReposByFilterInWorkspace(pageOptionsDto: RepoSearchOptionsDto, id: string, userId: number | undefined)
Parameters :
Name Type Optional
pageOptionsDto RepoSearchOptionsDto No
id string No
userId number | undefined No
Async findAllReposByWorkspaceIdForUserId
findAllReposByWorkspaceIdForUserId(pageOptionsDto: PageOptionsDto, id: string, userId: number | undefined)
Parameters :
Name Type Optional
pageOptionsDto PageOptionsDto No
id string No
userId number | undefined No
Async findAllReposByWorkspaceIdUnguarded
findAllReposByWorkspaceIdUnguarded(id: string)
Parameters :
Name Type Optional
id string No
Returns : Promise<DbWorkspaceRepo[]>
import { Injectable, NotFoundException, UnauthorizedException } from "@nestjs/common";
import { Repository, SelectQueryBuilder } from "typeorm";
import { InjectRepository } from "@nestjs/typeorm";

import { IssuesGithubEventsService } from "../timescale/issues_github_events.service";
import { DbIssuesGitHubEvents } from "../timescale/entities/issues_github_event.entity";
import { PageMetaDto } from "../common/dtos/page-meta.dto";
import { PageDto } from "../common/dtos/page.dto";
import { PageOptionsDto } from "../common/dtos/page-options.dto";
import { RepoService } from "../repo/repo.service";
import { DbRepoWithStats } from "../repo/entities/repo.entity";
import { RepoSearchOptionsDto } from "../repo/dtos/repo-search-options.dto";
import { PullRequestGithubEventsService } from "../timescale/pull_request_github_events.service";
import { DbPullRequestGitHubEvents } from "../timescale/entities/pull_request_github_event.entity";
import { DbWorkspaceRepo } from "./entities/workspace-repos.entity";
import { DbWorkspace } from "./entities/workspace.entity";
import { WorkspaceService } from "./workspace.service";
import { canUserEditWorkspace, canUserViewWorkspace } from "./common/memberAccess";
import { UpdateWorkspaceReposDto } from "./dtos/update-workspace-repos.dto";
import { DeleteWorkspaceReposDto } from "./dtos/delete-workspace-repos.dto";
import { WorkspaceRepoPullRequestPageOptionsDto } from "./dtos/workspace-repo-prs.dto";
import { WorkspaceRepoIssuePageOptionsDto } from "./dtos/workspace-repo-issues.dto";

@Injectable()
export class WorkspaceReposService {
  constructor(
    @InjectRepository(DbWorkspaceRepo, "ApiConnection")
    private workspaceRepoRepository: Repository<DbWorkspaceRepo>,
    private workspaceService: WorkspaceService,
    private repoService: RepoService,
    private pullRequestGithubEventsService: PullRequestGithubEventsService,
    private issuesGithubEventsService: IssuesGithubEventsService
  ) {}

  baseQueryBuilder(): SelectQueryBuilder<DbWorkspaceRepo> {
    const builder = this.workspaceRepoRepository.createQueryBuilder("workspace_repos");

    return builder;
  }

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

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

    const canView = canUserViewWorkspace(workspace, userId);

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

    const queryBuilder = this.baseQueryBuilder();

    queryBuilder
      .withDeleted()
      .leftJoinAndSelect(
        "workspace_repos.repo",
        "workspace_repos_repo",
        "workspace_repos.repo_id = workspace_repos_repo.id"
      )
      .where("workspace_repos.deleted_at IS NULL")
      .andWhere("workspace_repos.workspace_id = :id", { id })
      .orderBy("workspace_repos_repo.pushed_at", "DESC")
      .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 findAllReposByWorkspaceIdUnguarded(id: string): Promise<DbWorkspaceRepo[]> {
    const queryBuilder = this.baseQueryBuilder();

    return queryBuilder
      .withDeleted()
      .leftJoinAndSelect(
        "workspace_repos.repo",
        "workspace_repos_repo",
        "workspace_repos.repo_id = workspace_repos_repo.id"
      )
      .where("workspace_repos.deleted_at IS NULL")
      .andWhere("workspace_repos.workspace_id = :id", { id })
      .orderBy("workspace_repos_repo.pushed_at", "DESC")
      .getMany();
  }

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

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

    const canView = canUserViewWorkspace(workspace, userId);

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

    const queryBuilder = this.baseQueryBuilder();

    queryBuilder
      .withDeleted()
      .leftJoinAndSelect(
        "workspace_repos.repo",
        "workspace_repos_repo",
        "workspace_repos.repo_id = workspace_repos_repo.id"
      )
      .where("workspace_repos.deleted_at IS NULL")
      .andWhere("workspace_repos.workspace_id = :id", { id });

    if (pageOptionsDto.repoIds) {
      queryBuilder.andWhere("workspace_repos_repo.id IN (:...repoIds)", {
        repoIds: pageOptionsDto.repoIds.split(","),
      });
    }

    const entities = await queryBuilder.getMany();

    if (entities.length === 0) {
      const pageMeta = new PageMetaDto({ itemCount: 0, pageOptionsDto });

      return new PageDto([], pageMeta);
    }

    return this.pullRequestGithubEventsService.findAllWithFilters({
      ...pageOptionsDto,
      skip: pageOptionsDto.skip,
      repoIds: entities.map((entity) => entity.repo_id.toString()).join(","),
    });
  }

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

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

    const canView = canUserViewWorkspace(workspace, userId);

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

    const queryBuilder = this.baseQueryBuilder();

    queryBuilder
      .withDeleted()
      .leftJoinAndSelect(
        "workspace_repos.repo",
        "workspace_repos_repo",
        "workspace_repos.repo_id = workspace_repos_repo.id"
      )
      .where("workspace_repos.deleted_at IS NULL")
      .andWhere("workspace_repos.workspace_id = :id", { id });

    if (pageOptionsDto.repoIds) {
      queryBuilder.andWhere("workspace_repos_repo.id IN (:...repoIds)", {
        repoIds: pageOptionsDto.repoIds.split(","),
      });
    }

    const entities = await queryBuilder.getMany();

    if (entities.length === 0) {
      const pageMeta = new PageMetaDto({ itemCount: 0, pageOptionsDto });

      return new PageDto([], pageMeta);
    }

    return this.issuesGithubEventsService.findAllWithFilters({
      ...pageOptionsDto,
      skip: pageOptionsDto.skip,
      repoIds: entities.map((entity) => entity.repo_id.toString()).join(","),
    });
  }

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

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

    const canView = canUserViewWorkspace(workspace, userId);

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

    return this.repoService.findAllWithFiltersInWorkspace(pageOptionsDto, id);
  }

  async addOneWorkspaceRepo(id: string, owner: string, repo: string, userId: number): Promise<DbWorkspace> {
    const workspace = await this.workspaceService.findOneById(id);

    /*
     * owners and editors can update the workspace repos
     */

    const canUpdate = canUserEditWorkspace(workspace, userId);

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

    await this.executeAddWorkspaceRepo(workspace, owner, repo);

    return this.workspaceService.findOneById(id);
  }

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

    /*
     * owners and editors can update the workspace repos
     */

    const canUpdate = canUserEditWorkspace(workspace, userId);

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

    const promises = dto.repos.map(async (dtoRepo) => {
      const parts = dtoRepo.full_name.split("/");

      if (parts.length !== 2) {
        throw new NotFoundException("invalid repo input: must be of form 'owner/name'");
      }

      await this.executeAddWorkspaceRepo(workspace, parts[0], parts[1]);
    });

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

  private async executeAddWorkspaceRepo(workspace: DbWorkspace, ownerName: string, repoName: string) {
    const repo = await this.repoService.tryFindRepoOrMakeStub({ repoOwner: ownerName, repoName });

    const existingWorkspaceRepo = await this.workspaceRepoRepository.findOne({
      where: {
        workspace_id: workspace.id,
        repo_id: repo.id,
      },
      withDeleted: true,
    });

    if (existingWorkspaceRepo) {
      await this.workspaceRepoRepository.restore(existingWorkspaceRepo.id);
    } else {
      const newWorkspaceRepo = new DbWorkspaceRepo();

      newWorkspaceRepo.workspace = workspace;
      newWorkspaceRepo.repo = repo;

      await this.workspaceRepoRepository.save(newWorkspaceRepo);
    }
  }

  async deleteOneWorkspaceRepo(id: string, owner: string, repo: string, userId: number) {
    const workspace = await this.workspaceService.findOneById(id);

    /*
     * owners and editors can delete the workspace repos
     */

    const canDelete = canUserEditWorkspace(workspace, userId);

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

    const existingRepo = await this.repoService.findOneByOwnerAndRepo(owner, repo);
    const existingWorkspaceRepo = await this.workspaceRepoRepository.findOne({
      where: {
        workspace_id: id,
        repo_id: existingRepo.id,
      },
    });

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

    await this.workspaceRepoRepository.softDelete(existingWorkspaceRepo.id);

    return this.workspaceService.findOneById(id);
  }

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

    /*
     * owners and editors can delete the workspace repos
     */

    const canDelete = canUserEditWorkspace(workspace, userId);

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

    const promises = dto.repos.map(async (dtoRepo) => {
      const parts = dtoRepo.full_name.split("/");

      if (parts.length !== 2) {
        throw new NotFoundException("invalid repo input: must be of form 'owner/name'");
      }

      const repo = await this.repoService.findOneByOwnerAndRepo(parts[0], parts[1]);
      const existingWorkspaceRepo = await this.workspaceRepoRepository.findOne({
        where: {
          workspace_id: id,
          repo_id: repo.id,
        },
      });

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

      return this.workspaceRepoRepository.softDelete(existingWorkspaceRepo.id);
    });

    try {
      await Promise.all(promises);
    } catch (error) {
      dto.repos.forEach(async (dtoRepo) => {
        // restore the members who may have been soft deleted
        const parts = dtoRepo.full_name.split("/");

        // if there is malformed input, this will have already been caught in the promises catch
        if (parts.length === 2) {
          const repo = await this.repoService.findOneByOwnerAndRepo(parts[0], parts[1]);

          const existingRepo = await this.workspaceRepoRepository.findOne({
            where: {
              workspace_id: id,
              repo_id: repo.id,
            },
            withDeleted: true,
          });

          if (existingRepo) {
            await this.workspaceRepoRepository.restore(existingRepo.id);
          }
        }
      });

      // throws the original error
      throw error;
    }

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

results matching ""

    No results matching ""