feat(repos): 添加提交日志查询和分支查询

This commit is contained in:
Ivan Li 2021-02-19 13:11:03 +08:00
parent c2c5340278
commit dfaee1fb56
20 changed files with 279 additions and 5 deletions

5
.gitignore vendored
View File

@ -31,4 +31,7 @@ lerna-debug.log*
!.vscode/settings.json !.vscode/settings.json
!.vscode/tasks.json !.vscode/tasks.json
!.vscode/launch.json !.vscode/launch.json
!.vscode/extensions.json !.vscode/extensions.json
workspaces/*
!workspaces/.gitkeep

5
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"cSpell.words": [
"Repos"
]
}

View File

@ -4,7 +4,7 @@ http:
db: db:
postgres: postgres:
host: 192.168.31.195 host: 192.168.31.194
port: 5432 port: 5432
database: fennec database: fennec
username: fennec username: fennec

53
package-lock.json generated
View File

@ -2056,6 +2056,34 @@
"chalk": "^4.0.0" "chalk": "^4.0.0"
} }
}, },
"@kwsites/file-exists": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz",
"integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==",
"requires": {
"debug": "^4.1.1"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"@kwsites/promise-deferred": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz",
"integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw=="
},
"@nestjs/cli": { "@nestjs/cli": {
"version": "7.5.4", "version": "7.5.4",
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-7.5.4.tgz", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-7.5.4.tgz",
@ -10753,6 +10781,31 @@
"resolved": "https://registry.npmjs.org/signedsource/-/signedsource-1.0.0.tgz", "resolved": "https://registry.npmjs.org/signedsource/-/signedsource-1.0.0.tgz",
"integrity": "sha1-HdrOSYF5j5O9gzlzgD2A1S6TrWo=" "integrity": "sha1-HdrOSYF5j5O9gzlzgD2A1S6TrWo="
}, },
"simple-git": {
"version": "2.35.0",
"resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.35.0.tgz",
"integrity": "sha512-VuXs2/HyZmZm43Z5IjvU+ahTmURh/Hmb/egmgNdFZuu8OEnW2emCalnL/4jRQkXeJvfzCTnev6wo5jtDmWw0Dw==",
"requires": {
"@kwsites/file-exists": "^1.1.1",
"@kwsites/promise-deferred": "^1.1.1",
"debug": "^4.3.1"
},
"dependencies": {
"debug": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"sisteransi": { "sisteransi": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",

View File

@ -39,6 +39,7 @@
"reflect-metadata": "^0.1.13", "reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rxjs": "^6.6.3", "rxjs": "^6.6.3",
"simple-git": "^2.35.0",
"typeorm": "^0.2.30" "typeorm": "^0.2.30"
}, },
"devDependencies": { "devDependencies": {

View File

@ -6,6 +6,7 @@ import { AppController } from './app.controller';
import { AppResolver } from './app.resolver'; import { AppResolver } from './app.resolver';
import { AppService } from './app.service'; import { AppService } from './app.service';
import { ProjectsModule } from './projects/projects.module'; import { ProjectsModule } from './projects/projects.module';
import { ReposModule } from './repos/repos.module';
import configuration from './commons/config/configuration'; import configuration from './commons/config/configuration';
@Module({ @Module({
@ -37,6 +38,7 @@ import configuration from './commons/config/configuration';
inject: [ConfigService], inject: [ConfigService],
}), }),
ProjectsModule, ProjectsModule,
ReposModule,
], ],
controllers: [AppController], controllers: [AppController],
providers: [AppService, AppResolver], providers: [AppService, AppResolver],

View File

@ -19,7 +19,7 @@ export class CreateProjectInput {
@MinLength(2) @MinLength(2)
comment: string; comment: string;
@IsUrl({ protocols: ['ssh'], require_protocol: false }) @IsUrl({ protocols: ['ssh'] })
@MaxLength(256) @MaxLength(256)
sshUrl: string; sshUrl: string;

View File

@ -1,6 +1,6 @@
import { ObjectType } from '@nestjs/graphql'; import { ObjectType } from '@nestjs/graphql';
import { AppBaseEntity } from 'src/commons/entities/app-base-entity';
import { Entity, Column, DeleteDateColumn } from 'typeorm'; import { Entity, Column, DeleteDateColumn } from 'typeorm';
import { AppBaseEntity } from '../commons/entities/app-base-entity';
@ObjectType() @ObjectType()
@Entity() @Entity()

View File

@ -7,5 +7,6 @@ import { Project } from './project.entity';
@Module({ @Module({
imports: [TypeOrmModule.forFeature([Project])], imports: [TypeOrmModule.forFeature([Project])],
providers: [ProjectsService, ProjectsResolver], providers: [ProjectsService, ProjectsResolver],
exports: [ProjectsService],
}) })
export class ProjectsModule {} export class ProjectsModule {}

View File

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { BaseDbService } from 'src/commons/services/base-db.service'; import { BaseDbService } from '../commons/services/base-db.service';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { CreateProjectInput } from './dtos/create-project.input'; import { CreateProjectInput } from './dtos/create-project.input';
import { Project } from './project.entity'; import { Project } from './project.entity';

View File

@ -0,0 +1,24 @@
import { ObjectType, Field } from '@nestjs/graphql';
import {
LogResult,
DefaultLogFields,
BranchSummary,
BranchSummaryBranch,
} from 'simple-git';
@ObjectType()
export class Branch implements BranchSummaryBranch {
current: boolean;
name: string;
commit: string;
label: string;
}
@ObjectType()
export class BranchesList {
detached: boolean;
current: string;
all: string[];
@Field(() => [Branch])
branches: Branch[];
}

View File

@ -0,0 +1,8 @@
import { InputType } from '@nestjs/graphql';
import { IsUUID } from 'class-validator';
@InputType()
export class ListBranchesArgs {
@IsUUID()
projectId: string;
}

View File

@ -0,0 +1,12 @@
import { InputType, ObjectType } from '@nestjs/graphql';
import { IsOptional, IsString, IsUUID } from 'class-validator';
@InputType()
export class ListLogsArgs {
@IsUUID()
projectId: string;
@IsString()
@IsOptional()
branch?: string;
}

View File

@ -0,0 +1,21 @@
import { ObjectType, Field } from '@nestjs/graphql';
import { LogResult, DefaultLogFields } from 'simple-git';
@ObjectType()
export class LogFields {
hash: string;
date: string;
message: string;
refs: string;
body: string;
author_name: string;
author_email: string;
}
@ObjectType()
export class LogsList implements LogResult<DefaultLogFields> {
@Field(() => [LogFields])
all: LogFields[];
total: number;
latest: LogFields;
}

11
src/repos/repos.module.ts Normal file
View File

@ -0,0 +1,11 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Project } from '../projects/project.entity';
import { ReposResolver } from './repos.resolver';
import { ReposService } from './repos.service';
@Module({
imports: [TypeOrmModule.forFeature([Project])],
providers: [ReposResolver, ReposService],
})
export class ReposModule {}

View File

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ReposResolver } from './repos.resolver';
describe('ReposResolver', () => {
let resolver: ReposResolver;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ReposResolver],
}).compile();
resolver = module.get<ReposResolver>(ReposResolver);
});
it('should be defined', () => {
expect(resolver).toBeDefined();
});
});

View File

@ -0,0 +1,26 @@
import { Args, Query, Resolver } from '@nestjs/graphql';
import { ListLogsArgs } from './dtos/list-logs.args';
import { ReposService } from './repos.service';
import { LogsList } from './dtos/logs-list.model';
import { ListBranchesArgs } from './dtos/list-branches.args';
import { BranchesList } from './dtos/branches-list.model';
@Resolver()
export class ReposResolver {
con
@Query(() => LogsList)
async listLogs(@Args('listLogsArgs') dto: ListLogsArgs) {
return await this.service.listLogs(dto);
}
@Query(() => BranchesList)
async ListBranchesArgs(
@Args('listBranchesArgs') dto: ListBranchesArgs,
): Promise<BranchesList> {
return await this.service.listBranches(dto).then((data) => {
return {
...data,
branches: Object.values(data.branches),
};
});
}
}

View File

@ -0,0 +1,48 @@
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { Project } from '../projects/project.entity';
import { ReposService } from './repos.service';
describe('ReposService', () => {
let service: ReposService;
const repositoryMockFactory = jest.fn(() => ({
findOneOrFail: jest.fn(
(entity): Project => ({
id: '1',
// sshUrl: 'ssh://gitea@git.ivanli.cc:7018/ivan/test1.git',
sshUrl: 'ssh://gitea@git.ivanli.cc:7018/Fennec/fennec-fe.git',
name: 'test1',
...entity,
}),
),
}));
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
ReposService,
{
provide: getRepositoryToken(Project),
useFactory: repositoryMockFactory,
},
],
}).compile();
service = module.get<ReposService>(ReposService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
describe('listLogs', () => {
it('should be return logs', async () => {
const result = await service.listLogs({ projectId: '1' });
expect(result).toBeDefined();
}, 10_000);
});
describe('listBranch', () => {
it('should be return branches', async () => {
const result = await service.listBranches({ projectId: '1' });
expect(result).toBeDefined();
}, 10_000);
});
});

View File

@ -0,0 +1,41 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { F_OK } from 'constants';
import { access, mkdir } from 'fs/promises';
import { join } from 'path';
import { gitP } from 'simple-git';
import { Repository } from 'typeorm';
import { Project } from '../projects/project.entity';
import { ListBranchesArgs } from './dtos/list-branches.args';
import { ListLogsArgs } from './dtos/list-logs.args';
@Injectable()
export class ReposService {
constructor(
@InjectRepository(Project)
private readonly projectRepository: Repository<Project>,
) {}
async getGit(project: Project) {
const workspacePath = join(__dirname, '../../workspaces', project.name);
await access(workspacePath, F_OK).catch(() => mkdir(workspacePath));
return gitP(workspacePath);
}
async listLogs(dto: ListLogsArgs) {
const project = await this.projectRepository.findOneOrFail({
id: dto.projectId,
});
const git = await this.getGit(project);
await git.fetch();
return git.log();
}
async listBranches(dto: ListBranchesArgs) {
const project = await this.projectRepository.findOneOrFail({
id: dto.projectId,
});
const git = await this.getGit(project);
return git.branch();
}
}

0
workspaces/.gitkeep Normal file
View File