浏览代码

Turn methods to thin wrappers

Make methods as thin wrappers to Git executable. Also pass the child
process for easy stream operations to consumers.
master
父节点
当前提交
60a65b078e
共有 14 个文件被更改,包括 40 次插入345 次删除
  1. +1
    -3
      package.json
  2. +0
    -70
      prisma/schema.prisma
  3. +0
    -1
      src/index.ts
  4. +0
    -1
      src/modules/common/index.ts
  5. +0
    -12
      src/modules/common/models.ts
  6. +38
    -113
      src/modules/git/Git.service.ts
  7. +0
    -1
      src/modules/git/index.ts
  8. +0
    -38
      src/modules/git/models.ts
  9. +0
    -33
      src/modules/log/Log.service.ts
  10. +0
    -2
      src/modules/log/index.ts
  11. +0
    -18
      src/modules/log/models.ts
  12. +0
    -1
      src/utils/error.ts
  13. +1
    -28
      src/utils/process.ts
  14. +0
    -24
      yarn.lock

+ 1
- 3
package.json 查看文件

@@ -82,8 +82,6 @@
"access": "public"
},
"dependencies": {
"@prisma/client": "^3.14.0",
"@theoryofnekomata/uuid-buffer": "^0.1.0",
"prisma": "^3.14.0"
"@theoryofnekomata/uuid-buffer": "^0.1.0"
}
}

+ 0
- 70
prisma/schema.prisma 查看文件

@@ -1,70 +0,0 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}

model User {
id Bytes @id
username String @unique
password String
createdAt DateTime @default(now())
properties Property[]
userOrgs UserOrg[]
}

model Property {
id Bytes @id
userId Bytes
user User @relation(fields: [userId], references: [id])
name String
value String
}

model Org {
id Bytes @id
name String @unique
description String
createdAt DateTime @default(now())
orgUsers UserOrg[]
}

model UserOrg {
id Int @id @default(autoincrement())
userId Bytes
user User @relation(fields: [userId], references: [id])
orgId Bytes
org Org @relation(fields: [orgId], references: [id])
}

model Repo {
id Bytes @id
name String
visibility Int
ownerId Bytes
ownerType Int
createdAt DateTime @default(now())
}

model Log {
id Int @id @default(autoincrement())
subjectUserId Bytes
subjectUsername String
action String
createdAt DateTime @default(now())
parameters LogParameter[]
}

model LogParameter {
id Int @id @default(autoincrement())
logId Int
log Log @relation(fields: [logId], references: [id])
key String
value String
}

+ 0
- 1
src/index.ts 查看文件

@@ -1,2 +1 @@
export * as common from './modules/common';
export * as git from './modules/git';

+ 0
- 1
src/modules/common/index.ts 查看文件

@@ -1 +0,0 @@
export * from './models';

+ 0
- 12
src/modules/common/models.ts 查看文件

@@ -1,12 +0,0 @@
import {Uuid} from '@theoryofnekomata/uuid-buffer';

export type User = {
id: Uuid,
username: string,
}

export type Org = {
id: Uuid,
name: string,
description: string,
}

+ 38
- 113
src/modules/git/Git.service.ts 查看文件

@@ -1,133 +1,58 @@
import * as path from 'path';
import {Uuid} from '@theoryofnekomata/uuid-buffer';
import {PrismaClient} from '@prisma/client';
import {ChildProcess} from 'child_process';
import {spawn} from '../../utils/process';
import {mkdirp, unlink} from '../../utils/fs';
import {User} from '../common';
import {LogService, LogServiceImpl} from '../log';
import * as models from './models';
import {notFoundFactory} from '../../utils/error';

export interface GitService {
create(data: models.CreateRepoData, subject: User): Promise<models.Repo>
delete(repoId: models.Repo['id'], subject: User): Promise<void>
create(repoBasePath: string): Promise<ChildProcess>
delete(repoBasePath: string): Promise<void>
advertiseReceivePackRefs(repoBasePath: string): Promise<ChildProcess>
advertiseUploadPackRefs(repoBasePath: string): Promise<ChildProcess>
receivePack(repoBasePath: string): Promise<ChildProcess>
uploadPack(repoBasePath: string): Promise<ChildProcess>
}

export class GitServiceImpl implements GitService {
private readonly prismaClient: PrismaClient
private readonly logService: LogService

constructor() {
this.prismaClient = new PrismaClient();
this.logService = new LogServiceImpl()
private static isWindows() {
return /^win/.test(process.platform);
}

private static getRepoBasePath(repoIdString: string) {
return path.join('repos', repoIdString)
async create(repoBasePath: string): Promise<ChildProcess> {
await mkdirp(repoBasePath);
return spawn(
repoBasePath,
'git', ['init', '--bare'],
);
}

private async getOwnerName(ownerId: Uuid, ownerType: models.OwnerType) {
let owner: models.Owner
if (ownerType === models.OwnerType.USER) {
owner = await this.prismaClient.user.findUnique({
where: {
id: ownerId,
},
rejectOnNotFound: notFoundFactory(models.UserNotFoundError),
})
return owner.username;
}

if (ownerType === models.OwnerType.ORG) {
owner = await this.prismaClient.org.findUnique({
where: {
id: ownerId,
},
rejectOnNotFound: notFoundFactory(models.OrgNotFoundError),
})
return owner.name;
}

throw new models.UnknownOwnerTypeError('Unknown owner type.')
async delete(repoBasePath: string): Promise<void> {
await unlink(repoBasePath);
}

private static async createRepoFiles(repoIdString: string) {
const repoBasePath = GitServiceImpl.getRepoBasePath(repoIdString);
await mkdirp(repoBasePath);
await spawn(repoBasePath, 'git', ['init', '--bare']);
async advertiseReceivePackRefs(repoBasePath: string): Promise<ChildProcess> {
const command = GitServiceImpl.isWindows() ? 'git' : 'git-receive-pack';
const commonArgs = ['--stateless-rpc', '--advertise-refs', '.'];
const args = GitServiceImpl.isWindows() ? ['receive-pack', ...commonArgs] : commonArgs;
return spawn(repoBasePath, command, args);
}

async create(data: models.CreateRepoData, subject: User): Promise<models.Repo> {
const repoId = Uuid.v4();
const repoIdString = repoId.toString()

await GitServiceImpl.createRepoFiles(repoIdString);
const repoMetadata = await this.prismaClient.repo.create({
data: {
id: repoId,
name: data.name,
visibility: data.visibility,
ownerId: data.ownerId,
ownerType: data.ownerType,
},
});

const ownerName = await this.getOwnerName(data.ownerId, data.ownerType);
await this.logService.create(
subject,
models.GitAction.REPO_CREATED,
{
key: 'repoId',
value: repoIdString,
},
{
key: 'repoName',
value: data.name,
},
{
key: 'ownerId',
value: data.ownerId.toString(),
},
{
key: 'ownerName',
value: ownerName,
},
);

return {
...repoMetadata,
id: Uuid.from(repoMetadata.id),
};
async advertiseUploadPackRefs(repoBasePath: string): Promise<ChildProcess> {
const command = GitServiceImpl.isWindows() ? 'git' : 'git-upload-pack';
const commonArgs = ['--stateless-rpc', '--advertise-refs', '.'];
const args = GitServiceImpl.isWindows() ? ['upload-pack', ...commonArgs] : commonArgs;
return spawn(repoBasePath, command, args);
}

async delete(repoId: models.Repo['id'], subject: User): Promise<void> {
const repoIdString = repoId.toString();
const repo = await this.prismaClient.repo.findUnique({
where: {
id: repoId,
},
rejectOnNotFound: notFoundFactory(models.RepoNotFoundError),
});
await this.prismaClient.repo.delete({
where: {
id: repoId,
},
});

const repoBasePath = GitServiceImpl.getRepoBasePath(repoIdString);
await unlink(repoBasePath);
async receivePack(repoBasePath: string): Promise<ChildProcess> {
const command = GitServiceImpl.isWindows() ? 'git' : 'git-receive-pack';
const commonArgs = ['--stateless-rpc', '.'];
const args = GitServiceImpl.isWindows() ? ['receive-pack', ...commonArgs] : commonArgs;
return spawn(repoBasePath, command, args);
}

await this.logService.create(
subject,
models.GitAction.REPO_REMOVED,
{
key: 'repoId',
value: repoIdString,
},
{
key: 'repoName',
value: repo.name,
},
);
async uploadPack(repoBasePath: string): Promise<ChildProcess> {
const command = GitServiceImpl.isWindows() ? 'git' : 'git-upload-pack';
const commonArgs = ['--stateless-rpc', '.'];
const args = GitServiceImpl.isWindows() ? ['upload-pack', ...commonArgs] : commonArgs;
return spawn(repoBasePath, command, args);
}
}

+ 0
- 1
src/modules/git/index.ts 查看文件

@@ -1,2 +1 @@
export * from './Git.service'
export * from './models'

+ 0
- 38
src/modules/git/models.ts 查看文件

@@ -1,38 +0,0 @@
import {Uuid} from '@theoryofnekomata/uuid-buffer';
import {Org, User} from '../common';

export enum RepoVisibility {
PRIVATE,
INTERNAL,
PUBLIC,
}

export enum OwnerType {
USER,
ORG,
}

export type Owner = User | Org;

export enum GitAction {
REPO_CREATED = 'REPO_CREATED',
REPO_REMOVED = 'REPO_REMOVED',
}

export type Repo = {
id: Uuid,
name: string,
ownerId: Uuid,
ownerType: OwnerType,
visibility: RepoVisibility,
}

export type CreateRepoData = Omit<Repo, 'id'>;

export class UnknownOwnerTypeError extends Error {}

export class UserNotFoundError extends Error {}

export class OrgNotFoundError extends Error {}

export class RepoNotFoundError extends Error {}

+ 0
- 33
src/modules/log/Log.service.ts 查看文件

@@ -1,33 +0,0 @@
import { Prisma, PrismaClient } from '@prisma/client';
import {User} from '../common';
import {Log, LogParameterData} from './models';

export interface LogService {
create(subject: User, action: string, ...parameters: LogParameterData[]): Promise<Log>
}

export class LogServiceImpl implements LogService {
private readonly prismaClient: PrismaClient

constructor() {
this.prismaClient = new PrismaClient()
}

async create(subject: User, action: string, ...parameters: LogParameterData[]): Promise<Log> {
const createData: Prisma.LogCreateInput = {
subjectUsername: subject.username,
subjectUserId: subject.id,
action,
}

if (parameters.length > 0) {
createData['parameters'] = {
create: parameters,
}
}

return this.prismaClient.log.create({
data: createData,
})
}
}

+ 0
- 2
src/modules/log/index.ts 查看文件

@@ -1,2 +0,0 @@
export * from './Log.service'
export * from './models'

+ 0
- 18
src/modules/log/models.ts 查看文件

@@ -1,18 +0,0 @@
import {Uuid} from '@theoryofnekomata/uuid-buffer';

export type LogParameter = {
id: number,
logId: number,
key: string,
value: string,
}

export type Log = {
id: number,
subjectUserId: Uuid,
subjectUsername: string,
action: string,
createdAt: Date,
}

export type LogParameterData = Pick<LogParameter, 'key' | 'value'>;

+ 0
- 1
src/utils/error.ts 查看文件

@@ -1 +0,0 @@
export const notFoundFactory = <T extends Error>(ErrorClass: { new(): T }) => () => new ErrorClass()

+ 1
- 28
src/utils/process.ts 查看文件

@@ -4,31 +4,4 @@ export const spawn = (
cwd: string,
command: string,
args: string[],
parentProcess = process,
) => new Promise<string>((resolve, reject) => {
let stdout = '';
let stderr = '';

const theChildProcess = childProcess.spawn(command, args, {
cwd,
});

theChildProcess.stdout.on('data', (data) => {
parentProcess.stdout.write(data);
stdout += data;
});

theChildProcess.stderr.on('data', (data) => {
parentProcess.stderr.write(data);
stderr += data;
});

theChildProcess.on('close', (code) => {
if (code !== 0) {
reject(new Error(stderr));
return;
}

resolve(stdout);
})
})
) => childProcess.spawn(command, args, { cwd });

+ 0
- 24
yarn.lock 查看文件

@@ -347,23 +347,6 @@
resolved "https://registry.yarnpkg.com/@ovyerus/licenses/-/licenses-6.4.4.tgz#596e3ace46ab7c70bcf0e2b17f259796a4bedf9f"
integrity sha512-IHjc31WXciQT3hfvdY+M59jBkQp70Fpr04tNDVO5rez2PNv4u8tE6w//CkU+GeBoO9k2ahneSqzjzvlgjyjkGw==
"@prisma/client@^3.14.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.14.0.tgz#bb90405c012fcca11f4647d91153ed4c58f3bd48"
integrity sha512-atb41UpgTR1MCst0VIbiHTMw8lmXnwUvE1KyUCAkq08+wJyjRE78Due+nSf+7uwqQn+fBFYVmoojtinhlLOSaA==
dependencies:
"@prisma/engines-version" "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a"
"@prisma/engines-version@3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a":
version "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a"
resolved "https://registry.yarnpkg.com/@prisma/engines-version/-/engines-version-3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a.tgz#4edae57cf6527f35e22cebe75e49214fc0e99ac9"
integrity sha512-D+yHzq4a2r2Rrd0ZOW/mTZbgDIkUkD8ofKgusEI1xPiZz60Daks+UM7Me2ty5FzH3p/TgyhBpRrfIHx+ha20RQ==
"@prisma/engines@3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a":
version "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a"
resolved "https://registry.yarnpkg.com/@prisma/engines/-/engines-3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a.tgz#7fa11bc26a51d450185c816cc0ab8cac673fb4bf"
integrity sha512-LwZvI3FY6f43xFjQNRuE10JM5R8vJzFTSmbV9X0Wuhv9kscLkjRlZt0BEoiHmO+2HA3B3xxbMfB5du7ZoSFXGg==
"@theoryofnekomata/uuid-buffer@^0.1.0":
version "0.1.0"
resolved "https://js.pack.modal.sh/@theoryofnekomata%2fuuid-buffer/-/uuid-buffer-0.1.0.tgz#0917314e8230ce1a2047172b3277512bc0fd73a3"
@@ -2435,13 +2418,6 @@ pridepack@1.1.1:
resolve.exports "^1.1.0"
yargs "^17.2.1"
prisma@^3.14.0:
version "3.14.0"
resolved "https://registry.yarnpkg.com/prisma/-/prisma-3.14.0.tgz#dd67ece37d7b5373e9fd9588971de0024b49be81"
integrity sha512-l9MOgNCn/paDE+i1K2fp9NZ+Du4trzPTJsGkaQHVBufTGqzoYHuNk8JfzXuIn0Gte6/ZjyKj652Jq/Lc1tp2yw==
dependencies:
"@prisma/engines" "3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a"
prompts@^2.3.2, prompts@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"


正在加载...
取消
保存