Feature: Basic repo clone and fetch list branches for a git repo (#3)

This commit is contained in:
Timothee Guerin 2019-05-16 15:55:49 -07:00 коммит произвёл GitHub
Родитель 8a0b0038e5
Коммит ee93476917
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
38 изменённых файлов: 711 добавлений и 24 удалений

3
.gitignore поставляемый
Просмотреть файл

@ -58,4 +58,5 @@ jspm_packages/
.next
bin/
buildcache/
buildcache/
tmp/

3
.vscode/launch.json поставляемый
Просмотреть файл

@ -6,8 +6,9 @@
"request": "launch",
"name": "Run server",
"program": "${workspaceFolder}/src/main.ts",
"outFiles": ["${workspaceFolder}/bin/main.js"],
"outFiles": ["${workspaceFolder}/bin/**/*.js"],
"console": "integratedTerminal",
"sourceMaps": true,
"cwd": "${workspaceFolder}"
}
]

Просмотреть файл

@ -1,4 +1,4 @@
{
"extends": "../tsconfig.json",
"exclude": ["node_modules", "bin", "test", "**/*spec.ts"]
"exclude": ["node_modules", "bin", "test", "**/*.test.ts"]
}

Просмотреть файл

@ -16,6 +16,7 @@ const config = {
tsConfig: "tsconfig.json",
},
},
setupFiles: ["<rootDir>/test/jest-setup.ts"],
testMatch: ["**/*.test.ts"],
verbose: true,
testEnvironment: "node",

91
package-lock.json сгенерированный
Просмотреть файл

@ -417,6 +417,22 @@
"multer": "1.4.1"
}
},
"@nestjs/swagger": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-3.0.2.tgz",
"integrity": "sha512-7cmOqa3MoK3ZXThECa3RCe5s5Bppm66DqDRz+nfTp5k2oGoFHX9t45jNU8P5aANCIEi1nA3TYwvEAGgZdV8WbA==",
"requires": {
"lodash": "4.17.11",
"path-to-regexp": "3.0.0"
},
"dependencies": {
"path-to-regexp": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.0.0.tgz",
"integrity": "sha512-ZOtfhPttCrqp2M1PBBH4X13XlvnfhIwD7yCLx+GoGoXRPQyxGOTdQMpIzPSPKXAJT/JQrdfFrgdJOyAzvgpQ9A=="
}
}
},
"@nuxtjs/opencollective": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.2.1.tgz",
@ -513,6 +529,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA=="
},
"@types/nodegit": {
"version": "0.24.6",
"resolved": "https://registry.npmjs.org/@types/nodegit/-/nodegit-0.24.6.tgz",
"integrity": "sha512-W4HgaAR/eV2JIabXGmfMkad2SxoXVveC+NzTLzRXP8a1/uxIBCdoULAH9N7MWXQjZ9foiykAVd/LZ9cXdx5tog==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@ -3322,6 +3347,22 @@
"supports-color": "^6.1.0"
},
"dependencies": {
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"requires": {
"pify": "^4.0.1",
"semver": "^5.6.0"
}
},
"pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true
},
"supports-color": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
@ -3355,11 +3396,27 @@
"ms": "^2.1.1"
}
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"requires": {
"pify": "^4.0.1",
"semver": "^5.6.0"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
"dev": true
},
"pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true
}
}
},
@ -4010,20 +4067,17 @@
}
},
"make-dir": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
"integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
"dev": true,
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.0.0.tgz",
"integrity": "sha512-grNJDhb8b1Jm1qeqW5R/O63wUo4UXo2v2HMic6YT9i/HBlF93S8jkMgH7yugvY9ABDShH4VZMn8I+U8+fCNegw==",
"requires": {
"pify": "^4.0.1",
"semver": "^5.6.0"
"semver": "^6.0.0"
},
"dependencies": {
"pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"dev": true
"semver": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.0.0.tgz",
"integrity": "sha512-0UewU+9rFapKFnlbirLi3byoOuhrSsli/z/ihNnvM24vgF+8sNBiI1LZPBSH9wJKUwaUbw+s3hToDLCXkrghrQ=="
}
}
},
@ -5689,6 +5743,21 @@
"has-flag": "^3.0.0"
}
},
"swagger-ui-dist": {
"version": "3.22.1",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.22.1.tgz",
"integrity": "sha512-KITbEqXkXrjGH12A0lpVZlH3uODFkwUh8d15My1YD4N0PSZDnIiC1iMFT6ryyuJxDYWZh0qezKpPqa5FRowngw==",
"dev": true
},
"swagger-ui-express": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.0.3.tgz",
"integrity": "sha512-G0anPbcUHCnf84fum1BPZ2s1LG4DP1bYBFgF05YB0ibXx4VMAkGIMSq7Y9yT8hMtTaKkmK+ikJFOCCeA9FcRfA==",
"dev": true,
"requires": {
"swagger-ui-dist": "^3.18.1"
}
},
"symbol-tree": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",

Просмотреть файл

@ -12,7 +12,8 @@
"start:debug": "nodemon --config nodemon-debug.json",
"test": "jest",
"lint": "tslint -p tsconfig.json",
"test:watch": "jest --watch --coverage:false --config ./config/jest.dev.config.js"
"test:watch": "jest --watch --coverage=false --config ./config/jest.dev.config.js",
"swagger:gen": "node ./bin/scripts/generate-swagger-specs.js"
},
"repository": {
"type": "git",
@ -26,9 +27,11 @@
"homepage": "https://github.com/Azure/git-rest-api#readme",
"devDependencies": {
"@types/jest": "^24.0.13",
"@types/nodegit": "^0.24.6",
"concurrently": "^4.1.0",
"jest": "^24.8.0",
"prettier": "^1.17.1",
"swagger-ui-express": "^4.0.3",
"ts-jest": "^24.0.2",
"tslint": "^5.16.0",
"tslint-config-prettier": "^1.18.0",
@ -39,8 +42,10 @@
"@nestjs/common": "^6.2.0",
"@nestjs/core": "^6.2.0",
"@nestjs/platform-express": "^6.2.0",
"@nestjs/swagger": "^3.0.2",
"@types/node": "^12.0.2",
"class-validator": "^0.9.1",
"make-dir": "^3.0.0",
"nodegit": "^0.24.3",
"reflect-metadata": "^0.1.13",
"rxjs": "^6.5.2"

Просмотреть файл

@ -1,11 +1,11 @@
import { Module } from "@nestjs/common";
import { AppController } from "./controllers";
import { AppService } from "./services";
import { AppController, BranchesController, HealthCheckController } from "./controllers";
import { AppService, BranchService, FSService, PermissionService, RepoService } from "./services";
@Module({
imports: [],
controllers: [AppController],
providers: [AppService],
controllers: [AppController, HealthCheckController, BranchesController],
providers: [AppService, RepoService, FSService, BranchService, PermissionService],
})
export class AppModule {}

18
src/app.ts Normal file
Просмотреть файл

@ -0,0 +1,18 @@
import { NestFactory } from "@nestjs/core";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { AppModule } from "./app.module";
export async function createApp() {
const app = await NestFactory.create(AppModule);
const options = new DocumentBuilder()
.setTitle("GIT Rest API")
.setDescription("Rest api to run operation on git repositories")
.setVersion("1.0")
.setSchemes("http", "https")
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup("swagger", app, document);
return { app, document };
}

Просмотреть файл

@ -0,0 +1,35 @@
import { RepoAuth } from "../../core";
import { BranchesController } from "./branches.controller";
const b1 = {
name: "master",
commit: {
sha: "sha1",
},
};
const b2 = {
name: "master",
commit: {
sha: "sha2",
},
};
describe("BranchController", () => {
let controller: BranchesController;
const branchServiceSpy = {
list: jest.fn(() => [b1, b2]),
};
beforeEach(() => {
jest.clearAllMocks();
controller = new BranchesController(branchServiceSpy as any);
});
it("list the branches", async () => {
const auth = new RepoAuth({});
const branches = await controller.list("github.com/Azure/git-rest-api", auth);
expect(branches).toEqual([b1, b2]);
});
});

Просмотреть файл

@ -0,0 +1,20 @@
import { Controller, Get, Param } from "@nestjs/common";
import { ApiNotFoundResponse, ApiOkResponse } from "@nestjs/swagger";
import { ApiHasPassThruAuth, Auth, RepoAuth } from "../../core";
import { GitBranch } from "../../dtos";
import { BranchService } from "../../services";
@Controller("/repos/:remote/branches")
export class BranchesController {
constructor(private branchService: BranchService) {}
@Get()
@ApiHasPassThruAuth()
@ApiOkResponse({ type: GitBranch, isArray: true })
@ApiNotFoundResponse({})
public async list(@Param("remote") remote: string, @Auth() auth: RepoAuth): Promise<GitBranch[]> {
const branches = await this.branchService.list(remote, { auth });
return branches;
}
}

Просмотреть файл

@ -1,2 +1,3 @@
export * from "./app.controller";
export * from "./health-check/health-check.controller";
export * from "./branches/branches.controller";

1
src/core/index.ts Normal file
Просмотреть файл

@ -0,0 +1 @@
export * from "./repo-auth";

Просмотреть файл

@ -0,0 +1 @@
export * from "./repo-auth";

Просмотреть файл

@ -0,0 +1,17 @@
import { Cred } from "nodegit";
import { RepoAuth } from "./repo-auth";
describe("RepoAuth", () => {
describe("Model", () => {
it("doesn't generated any creds when no options are passed", () => {
expect(new RepoAuth({}).toCreds()).toBeUndefined();
});
it("doesn't generate some basic password creds when using oath token", () => {
expect(new RepoAuth({ token: "token-1" }).toCreds()).toEqual(
Cred.userpassPlaintextNew("token-1", "x-oauth-basic"),
);
});
});
});

Просмотреть файл

@ -0,0 +1,45 @@
import { createParamDecorator } from "@nestjs/common";
import { ApiImplicitHeaders } from "@nestjs/swagger";
import { Cred } from "nodegit";
export interface IRepoAuth {
token?: string;
}
export class RepoAuth {
private token: string | undefined;
constructor(obj: IRepoAuth) {
this.token = obj.token;
}
public toCreds(): Cred | undefined {
if (this.token) {
return Cred.userpassPlaintextNew(this.token, "x-oauth-basic");
} else {
return undefined;
}
}
}
const TOKEN_HEADER = "x-oauth-basic";
export const Auth = createParamDecorator(
(_, req): RepoAuth => {
return new RepoAuth({
token: req.headers[TOKEN_HEADER],
});
},
);
/**
* Helper to add on methods using the Auth parameter for the swagger specs to be generated correctly
*/
export function ApiHasPassThruAuth() {
return ApiImplicitHeaders([
{
name: TOKEN_HEADER,
required: false,
},
]);
}

21
src/dtos/git-branch.ts Normal file
Просмотреть файл

@ -0,0 +1,21 @@
import { ApiModelProperty } from "@nestjs/swagger";
import { GitCommit, IGitCommit } from "./git-commit";
export interface IGitBranch {
name: string;
commit: IGitCommit;
}
export class GitBranch implements IGitBranch {
@ApiModelProperty({ type: String })
public name: string;
@ApiModelProperty({ type: GitCommit })
public commit: GitCommit;
constructor(branch: IGitBranch) {
this.name = branch.name;
this.commit = new GitCommit(branch.commit);
}
}

14
src/dtos/git-commit.ts Normal file
Просмотреть файл

@ -0,0 +1,14 @@
import { ApiModelProperty } from "@nestjs/swagger";
export interface IGitCommit {
sha: string;
}
export class GitCommit implements IGitCommit {
@ApiModelProperty({ type: String })
public sha: string;
constructor(commit: { sha: string }) {
this.sha = commit.sha;
}
}

2
src/dtos/index.ts Normal file
Просмотреть файл

@ -0,0 +1,2 @@
export * from "./git-branch";
export * from "./git-commit";

Просмотреть файл

@ -1,9 +1,7 @@
import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { createApp } from "./app";
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { app } = await createApp();
await app.listen(3009);
}

Просмотреть файл

@ -0,0 +1,6 @@
import { saveSwagger } from "./swagger-generation";
void saveSwagger().then(() => {
// tslint:disable-next-line: no-console
console.log("Generated the specs");
});

Просмотреть файл

@ -0,0 +1,24 @@
import fs from "fs";
import { promisify } from "util";
import { createApp } from "../app";
const writeFile = promisify(fs.writeFile);
const readFile = promisify(fs.readFile);
const SWAGGER_FILE_PATH = "./swagger-spec.json";
export async function generateSwagger(): Promise<string> {
const { document } = await createApp();
return JSON.stringify(document, null, 2);
}
export async function saveSwagger() {
const specs = await generateSwagger();
await writeFile(SWAGGER_FILE_PATH, specs);
}
export async function getSavedSwagger(): Promise<string> {
const response = await readFile(SWAGGER_FILE_PATH);
return response.toString();
}

Просмотреть файл

@ -0,0 +1,10 @@
import { generateSwagger, getSavedSwagger } from "./swagger-generation";
describe("Swagger specs", () => {
it("should be up to date", async () => {
const [specs, current] = await Promise.all([generateSwagger(), getSavedSwagger()]);
// Swagger specs saved in the repo should match the ones being generated run `npm run swagger:gen` to regenerate
expect(specs).toEqual(current);
});
});

Просмотреть файл

@ -0,0 +1,50 @@
import { GitBranch } from "../../dtos";
import { BranchService } from "./branch.service";
const b1 = new GitBranch({
name: "master",
commit: {
sha: "sha1",
},
});
const b2 = new GitBranch({
name: "stable",
commit: {
sha: "sha2",
},
});
const refs = [
{ isRemote: () => true, name: () => "refs/remotes/origin/master", target: () => "sha1" },
{ isRemote: () => false, name: () => "refs/head/feat1", target: () => "sha3" },
{ isRemote: () => true, name: () => "refs/remotes/origin/stable", target: () => "sha2" },
];
describe("BranchService", () => {
let service: BranchService;
const mockRepo = {
getReferences: jest.fn(() => {
return refs;
}),
};
const repoServiceSpy = {
get: jest.fn(() => mockRepo),
};
beforeEach(() => {
jest.clearAllMocks();
service = new BranchService(repoServiceSpy as any);
});
it("List the branches", async () => {
const branches = await service.list("github.com/Azure/git-rest-api");
expect(repoServiceSpy.get).toHaveBeenCalledTimes(1);
expect(repoServiceSpy.get).toHaveBeenCalledWith("github.com/Azure/git-rest-api", {});
expect(mockRepo.getReferences).toHaveBeenCalledTimes(1);
expect(branches).toEqual([b1, b2]);
});
});

Просмотреть файл

@ -0,0 +1,37 @@
import { Injectable } from "@nestjs/common";
import { Reference } from "nodegit";
import { GitBranch } from "../../dtos";
import { GitBaseOptions, RepoService } from "../repo";
@Injectable()
export class BranchService {
constructor(private repoService: RepoService) {}
/**
* List the branches
* @param remote
*/
public async list(remote: string, options: GitBaseOptions = {}): Promise<GitBranch[]> {
const repo = await this.repoService.get(remote, options);
const refs = await repo.getReferences(Reference.TYPE.LISTALL);
const branches = refs.filter(x => x.isRemote());
return Promise.all(
branches.map(async ref => {
const target = await ref.target();
return new GitBranch({
name: getRemoteBranchName(ref.name()),
commit: {
sha: target.toString(),
},
});
}),
);
}
}
export function getRemoteBranchName(refName: string) {
const prefix = "refs/remotes/origin/";
return refName.slice(prefix.length);
}

Просмотреть файл

@ -0,0 +1 @@
export * from "./branch.service";

Просмотреть файл

@ -0,0 +1,19 @@
import { Injectable } from "@nestjs/common";
import fs from "fs";
import makeDir from "make-dir";
import util from "util";
const fsPromises = {
exists: util.promisify(fs.exists),
};
@Injectable()
export class FSService {
public async exists(path: string): Promise<boolean> {
return fsPromises.exists(path);
}
public async makeDir(path: string): Promise<string> {
return makeDir(path);
}
}

1
src/services/fs/index.ts Normal file
Просмотреть файл

@ -0,0 +1 @@
export * from "./fs.service";

Просмотреть файл

@ -1 +1,5 @@
export * from "./app.service";
export * from "./branch";
export * from "./fs";
export * from "./repo";
export * from "./permission";

Просмотреть файл

@ -0,0 +1 @@
export * from "./permission.service";

Просмотреть файл

@ -0,0 +1,28 @@
import { PermissionService, TokenPermission } from "./permission.service";
const remote = "github.com/Azure/git-rest-specs";
describe("PermissionService", () => {
let service: PermissionService;
beforeEach(() => {
jest.clearAllTimers();
jest.useFakeTimers();
service = new PermissionService();
});
it("returns undefined when permission has not been set", () => {
expect(service.getTokenPermission("token-1", remote)).toBeUndefined();
});
it("set a permission", () => {
service.setTokenPermission("token-1", remote, TokenPermission.Read);
expect(service.getTokenPermission("token-1", remote)).toBe(TokenPermission.Read);
});
it("clear permission after a timeout", () => {
service.setTokenPermission("token-1", remote, TokenPermission.Read);
expect(service.getTokenPermission("token-1", remote)).toBe(TokenPermission.Read);
jest.runTimersToTime(60_000);
expect(service.getTokenPermission("token-1", remote)).toBeUndefined();
});
});

Просмотреть файл

@ -0,0 +1,54 @@
import { Injectable } from "@nestjs/common";
import { SecureUtils } from "../../utils";
export enum TokenPermission {
None,
Read,
Write,
}
const TOKEN_INVALIDATE_TIMEOUT = 60_000; // 60s
@Injectable()
export class PermissionService {
private tokenPermissions = new Map<string, TokenPermission>();
private timeouts = new Map<string, NodeJS.Timeout>();
public getTokenPermission(token: string, remote: string): TokenPermission | undefined {
return this.tokenPermissions.get(this.getMapKey(token, remote));
}
public setTokenPermission(token: string, remote: string, permission: TokenPermission) {
const key = this.getMapKey(token, remote);
this.tokenPermissions.set(key, permission);
this.timeoutPermission(key);
}
/**
* Remove the cached permission after a timeout
*/
private timeoutPermission(key: string) {
const existingTimeout = this.timeouts.get(key);
if (existingTimeout) {
clearTimeout(existingTimeout);
this.timeouts.delete(key);
}
this.timeouts.set(
key,
setTimeout(() => {
this.tokenPermissions.delete(key);
this.timeouts.delete(key);
}, TOKEN_INVALIDATE_TIMEOUT),
);
}
private getMapKey(token: string, remote: string) {
return `${this.hashToken(token)}/${remote}`;
}
private hashToken(token: string): string {
return SecureUtils.sha512(token);
}
}

Просмотреть файл

@ -0,0 +1 @@
export * from "./repo.service";

Просмотреть файл

@ -0,0 +1,77 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { Clone, Fetch, FetchOptions, Repository } from "nodegit";
import path from "path";
import { RepoAuth } from "../../core";
import { FSService } from "../fs";
const repoCacheFolder = path.join("./tmp", "repos");
export function getRepoMainPath(remote: string) {
return path.join(repoCacheFolder, encodeURIComponent(remote));
}
const defaultFetchOptions: FetchOptions = {
downloadTags: 0,
prune: Fetch.PRUNE.GIT_FETCH_PRUNE,
};
export interface GitBaseOptions {
auth?: RepoAuth;
}
@Injectable()
export class RepoService {
private cacheReady: Promise<string>;
constructor(private fs: FSService) {
this.cacheReady = fs.makeDir(repoCacheFolder);
}
public async get(remote: string, options: GitBaseOptions = {}): Promise<Repository> {
const repoPath = getRepoMainPath(remote);
if (await this.fs.exists(repoPath)) {
const repo = await Repository.open(repoPath);
await this.fetchAll(repo, options);
return repo;
} else {
return this.clone(remote, repoPath, options);
}
}
public async fetchAll(repo: Repository, options: GitBaseOptions) {
try {
await repo.fetchAll({
...defaultFetchOptions,
callbacks: {
credentials: credentialsCallback(options),
},
});
} catch {
throw new NotFoundException();
}
}
public async clone(remote: string, repoPath: string, options: GitBaseOptions): Promise<Repository> {
await this.cacheReady;
try {
return await Clone.clone(`https://${remote}`, repoPath, {
fetchOpts: {
...defaultFetchOptions,
callbacks: {
credentials: credentialsCallback(options),
},
},
});
} catch {
throw new NotFoundException();
}
}
}
function credentialsCallback(options: GitBaseOptions) {
return () => {
if (options.auth) {
return options.auth.toCreds();
}
return undefined;
};
}

1
src/utils/index.ts Normal file
Просмотреть файл

@ -0,0 +1 @@
export * from "./secure-utils";

Просмотреть файл

@ -0,0 +1,9 @@
import crypto from "crypto";
export const SecureUtils = {
sha512: (key: string) => {
const hash = crypto.createHash("sha512");
hash.update(key);
return hash.digest("hex");
},
};

112
swagger-spec.json Normal file
Просмотреть файл

@ -0,0 +1,112 @@
{
"swagger": "2.0",
"info": {
"description": "Rest api to run operation on git repositories",
"version": "1.0",
"title": "GIT Rest API"
},
"basePath": "/",
"tags": [],
"schemes": [
"http",
"https"
],
"paths": {
"/": {
"get": {
"responses": {
"200": {
"description": ""
}
},
"produces": [
"application/json"
],
"consumes": [
"application/json"
]
}
},
"/health/alive": {
"get": {
"responses": {
"200": {
"description": ""
}
},
"produces": [
"application/json"
],
"consumes": [
"application/json"
]
}
},
"/repos/{remote}/branches": {
"get": {
"parameters": [
{
"type": "string",
"name": "remote",
"required": true,
"in": "path"
},
{
"name": "x-oauth-basic",
"required": false,
"in": "header",
"type": "string"
}
],
"responses": {
"200": {
"description": "",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/GitBranch"
}
}
},
"404": {
"description": ""
}
},
"produces": [
"application/json"
],
"consumes": [
"application/json"
]
}
}
},
"definitions": {
"GitCommit": {
"type": "object",
"properties": {
"sha": {
"type": "string"
}
},
"required": [
"sha"
]
},
"GitBranch": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"commit": {
"$ref": "#/definitions/GitCommit"
}
},
"required": [
"name",
"commit"
]
}
}
}

1
test/jest-setup.ts Normal file
Просмотреть файл

@ -0,0 +1 @@
import "reflect-metadata";

Просмотреть файл

@ -11,6 +11,7 @@
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"rootDir": "src",
"outDir": "bin",
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
@ -44,5 +45,5 @@
"emitDecoratorMetadata": true /* Enables experimental support for emitting type metadata for decorators. */
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]
"exclude": ["node_modules", "tmp", "bin"]
}