Ver a proveniência

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
ascendente
cometimento
60a65b078e
14 ficheiros alterados com 40 adições e 345 eliminações
  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 Ver ficheiro

@@ -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 Ver ficheiro

@@ -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 Ver ficheiro

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

+ 0
- 1
src/modules/common/index.ts Ver ficheiro

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

+ 0
- 12
src/modules/common/models.ts Ver ficheiro

@@ -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 Ver ficheiro

@@ -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 Ver ficheiro

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

+ 0
- 38
src/modules/git/models.ts Ver ficheiro

@@ -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 Ver ficheiro

@@ -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 Ver ficheiro

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

+ 0
- 18
src/modules/log/models.ts Ver ficheiro

@@ -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 Ver ficheiro

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

+ 1
- 28
src/utils/process.ts Ver ficheiro

@@ -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 Ver ficheiro

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


Carregando…
Cancelar
Guardar