Sfoglia il codice sorgente

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
parent
commit
60a65b078e
14 ha cambiato i file con 40 aggiunte e 345 eliminazioni
  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 Vedi File

@@ -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 Vedi File

@@ -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 Vedi File

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

+ 0
- 1
src/modules/common/index.ts Vedi File

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

+ 0
- 12
src/modules/common/models.ts Vedi File

@@ -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 Vedi File

@@ -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 Vedi File

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

+ 0
- 38
src/modules/git/models.ts Vedi File

@@ -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 Vedi File

@@ -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 Vedi File

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

+ 0
- 18
src/modules/log/models.ts Vedi File

@@ -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 Vedi File

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

+ 1
- 28
src/utils/process.ts Vedi File

@@ -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 Vedi File

@@ -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"


Caricamento…
Annulla
Salva