Browse Source

Implement methods for creation and deletion

Add methods for creation and deletion, covering metadata and file
system.
master
TheoryOfNekomata 1 year ago
parent
commit
1b8b26b68e
16 changed files with 363 additions and 36 deletions
  1. +1
    -0
      .gitignore
  2. +5
    -0
      package.json
  3. +70
    -0
      prisma/schema.prisma
  4. +0
    -35
      src/git.ts
  5. +1
    -1
      src/index.ts
  6. +1
    -0
      src/modules/common/index.ts
  7. +12
    -0
      src/modules/common/models.ts
  8. +133
    -0
      src/modules/git/Git.service.ts
  9. +2
    -0
      src/modules/git/index.ts
  10. +38
    -0
      src/modules/git/models.ts
  11. +33
    -0
      src/modules/log/Log.service.ts
  12. +2
    -0
      src/modules/log/index.ts
  13. +18
    -0
      src/modules/log/models.ts
  14. +1
    -0
      src/utils/error.ts
  15. +4
    -0
      src/utils/fs.ts
  16. +42
    -0
      yarn.lock

+ 1
- 0
.gitignore View File

@@ -105,3 +105,4 @@ dist
.tern-port

.npmrc
.database/

+ 5
- 0
package.json View File

@@ -80,5 +80,10 @@
"author": "TheoryOfNekomata <allan.crisostomo@outlook.com>",
"publishConfig": {
"access": "public"
},
"dependencies": {
"@prisma/client": "^3.14.0",
"@theoryofnekomata/uuid-buffer": "^0.1.0",
"prisma": "^3.14.0"
}
}

+ 70
- 0
prisma/schema.prisma View File

@@ -0,0 +1,70 @@
// 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
- 35
src/git.ts View File

@@ -1,35 +0,0 @@
import {spawn} from './utils/process';
import {mkdirp} from './utils/fs';

export enum RepoVisibility {
PRIVATE,
INTERNAL,
PUBLIC,
}

export enum OwnerType {
USER,
ORG,
}

type Owner = {
name: string,
type: OwnerType,
}

type CreateOptions = {
name: string,
owner: Owner,
visibility: RepoVisibility,
}

const OWNER_TYPE_DIR_NAMES: Record<OwnerType, string> = {
[OwnerType.USER]: 'users',
[OwnerType.ORG]: 'orgs',
}

export const create = async (options: CreateOptions) => {
const repoBasePath = `${OWNER_TYPE_DIR_NAMES[options.owner.type]}/${options.owner.name}/${options.name}`;
await mkdirp(repoBasePath);
return spawn(repoBasePath, 'git', ['init', '--bare']);
}

+ 1
- 1
src/index.ts View File

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

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

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

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

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

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

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

+ 133
- 0
src/modules/git/Git.service.ts View File

@@ -0,0 +1,133 @@
import * as path from 'path';
import {Uuid} from '@theoryofnekomata/uuid-buffer';
import {PrismaClient} from '@prisma/client';
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>
}

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

constructor() {
this.prismaClient = new PrismaClient();
this.logService = new LogServiceImpl()
}

private static getRepoBasePath(repoIdString: string) {
return path.join('repos', repoIdString)
}

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.')
}

private static async createRepoFiles(repoIdString: string) {
const repoBasePath = GitServiceImpl.getRepoBasePath(repoIdString);
await mkdirp(repoBasePath);
await spawn(repoBasePath, 'git', ['init', '--bare']);
}

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 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);

await this.logService.create(
subject,
models.GitAction.REPO_REMOVED,
{
key: 'repoId',
value: repoIdString,
},
{
key: 'repoName',
value: repo.name,
},
);
}
}

+ 2
- 0
src/modules/git/index.ts View File

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

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

@@ -0,0 +1,38 @@
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 {}

+ 33
- 0
src/modules/log/Log.service.ts View File

@@ -0,0 +1,33 @@
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,
})
}
}

+ 2
- 0
src/modules/log/index.ts View File

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

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

@@ -0,0 +1,18 @@
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'>;

+ 1
- 0
src/utils/error.ts View File

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

+ 4
- 0
src/utils/fs.ts View File

@@ -13,3 +13,7 @@ export const mkdirp = (path: string) => {

return Promise.allSettled(directoriesToCheck.map((d) => fs.mkdir(d)));
}

export const unlink = (path: string) => {
return fs.rm(path, { recursive: true, force: true, })
}

+ 42
- 0
yarn.lock View File

@@ -347,6 +347,31 @@
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"
integrity sha512-DUKQE2UmS9vq+5kNp1f50U+XLdmgTEKWxRgeCgasXCipL7JNVQoYqwwcuDlCb+yNdqQ2/fNbAEWHKs3kRfa6+w==
dependencies:
"@types/uuid" "^8.3.4"
uuid "^8.3.2"
"@types/chai-subset@^1.3.3":
version "1.3.3"
resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94"
@@ -374,6 +399,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.35.tgz#635b7586086d51fb40de0a2ec9d1014a5283ba4a"
integrity sha512-vu1SrqBjbbZ3J6vwY17jBs8Sr/BKA+/a/WtjRG+whKg1iuLFOosq872EXS0eXWILdO36DHQQeku/ZcL6hz2fpg==
"@types/uuid@^8.3.4":
version "8.3.4"
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
"@typescript-eslint/eslint-plugin@^5.9.0":
version "5.25.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.25.0.tgz#e8ce050990e4d36cc200f2de71ca0d3eb5e77a31"
@@ -2405,6 +2435,13 @@ 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"
@@ -2824,6 +2861,11 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
uuid@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2"
integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
v8-compile-cache@^2.0.3:
version "2.3.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee"


Loading…
Cancel
Save