diff --git a/packages/das/src/api/repos/repos.controller.ts b/packages/das/src/api/repos/repos.controller.ts index 98c001c..6f173ad 100644 --- a/packages/das/src/api/repos/repos.controller.ts +++ b/packages/das/src/api/repos/repos.controller.ts @@ -24,4 +24,23 @@ export class ReposController { ): Promise { return this.repos.getMaintainers(owner, repo); } + + @Get(":owner/:repo/installation") + @ApiOperation({ + summary: "GitHub App installation status for a repo", + description: + "Returns whether the Gittensor Mirror GitHub App is installed on the " + + "repo (installation_id present), independent of whether the repo is " + + "registered. The registration UI uses this to verify the install step " + + "before a repo is registered. An unknown repo returns installed=false, " + + "not a 404.", + }) + @ApiParam({ name: "owner", description: "Repository owner (org or user)" }) + @ApiParam({ name: "repo", description: "Repository name" }) + async getInstallationStatus( + @Param("owner") owner: string, + @Param("repo") repo: string, + ): Promise { + return this.repos.getInstallationStatus(owner, repo); + } } diff --git a/packages/das/src/api/repos/repos.service.ts b/packages/das/src/api/repos/repos.service.ts index a16e496..d2a4f9d 100644 --- a/packages/das/src/api/repos/repos.service.ts +++ b/packages/das/src/api/repos/repos.service.ts @@ -1,10 +1,16 @@ /* eslint-disable @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-assignment */ import { Injectable } from "@nestjs/common"; -import { DataSource } from "typeorm"; +import { InjectRepository } from "@nestjs/typeorm"; +import { DataSource, IsNull, Not, Raw, Repository } from "typeorm"; +import { Repo } from "../../entities"; @Injectable() export class ReposService { - constructor(private readonly dataSource: DataSource) {} + constructor( + private readonly dataSource: DataSource, + @InjectRepository(Repo) + private readonly repoRepo: Repository, + ) {} async getMaintainers( owner: string, @@ -38,4 +44,26 @@ export class ReposService { maintainers: rows, }; } + + async getInstallationStatus( + owner: string, + repo: string, + ): Promise<{ repo_full_name: string; installed: boolean }> { + const repoFullName = `${owner}/${repo}`; + + // Case-insensitive match (#120); installed regardless of `registered`. + const count = await this.repoRepo.count({ + where: { + repoFullName: Raw((alias) => `LOWER(${alias}) = LOWER(:repoFullName)`, { + repoFullName, + }), + installationId: Not(IsNull()), + }, + }); + + return { + repo_full_name: repoFullName.toLowerCase(), + installed: count > 0, + }; + } }