Compare commits

..

6 Commits

39 changed files with 2926 additions and 984 deletions

View File

@ -1,9 +1,9 @@
{
"env":{
"es2021":true,
"node":true
"env": {
"es2021": true,
"node": true
},
"extends":[
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended",
@ -11,161 +11,168 @@
"plugin:import/recommended",
"plugin:import/typescript"
],
"parser":"@typescript-eslint/parser",
"parserOptions":{
"project":"./tsconfig.json",
"ecmaVersion":"latest",
"sourceType":"module"
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./tsconfig.json",
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins":[
"@typescript-eslint",
"unused-imports",
"promise"
],
"rules":{
"no-console":[
"error"
],
"semi":"off",
"@typescript-eslint/semi":[
"error",
"always"
],
"quotes":[
"error",
"double"
],
"no-multiple-empty-lines":[
"plugins": ["@typescript-eslint", "unused-imports", "promise"],
"rules": {
"no-console": ["error"],
"semi": "off",
"@typescript-eslint/semi": ["error", "always"],
"quotes": ["error", "double"],
"no-multiple-empty-lines": [
"error",
{
"max":1,
"maxEOF":1
"max": 1,
"maxEOF": 1
}
],
"spaced-comment":[
"error",
"always"
],
"padding-line-between-statements":[
"spaced-comment": ["error", "always"],
"padding-line-between-statements": [
"error",
{
"blankLine":"always",
"prev":"import",
"next":"*"
"blankLine": "always",
"prev": "import",
"next": "*"
},
{
"blankLine":"never",
"prev":"import",
"next":"import"
"blankLine": "never",
"prev": "import",
"next": "import"
},
{
"blankLine":"always",
"prev":"class",
"next":"*"
"blankLine": "always",
"prev": "class",
"next": "*"
}
],
"lines-between-class-members":"off",
"@typescript-eslint/lines-between-class-members":[
"lines-between-class-members": "off",
"@typescript-eslint/lines-between-class-members": [
"error",
"always",
{
"exceptAfterSingleLine":true
"exceptAfterSingleLine": true
}
],
"@typescript-eslint/await-thenable":"error",
"@typescript-eslint/no-for-in-array":"error",
"@typescript-eslint/no-misused-promises":"error",
"@typescript-eslint/explicit-function-return-type":"error",
"@typescript-eslint/no-unnecessary-type-assertion":"error",
"@typescript-eslint/prefer-includes":"error",
"@typescript-eslint/prefer-regexp-exec":"error",
"@typescript-eslint/prefer-string-starts-ends-with":"error",
"@typescript-eslint/explicit-member-accessibility":"error",
"require-await":"off",
"@typescript-eslint/require-await":"error",
"@typescript-eslint/unbound-method":"error",
"no-var":"error",
"prefer-const":"error",
"prefer-rest-params":"error",
"prefer-spread":"error",
"no-use-before-define":"off",
"@typescript-eslint/no-use-before-define":"error",
"no-useless-constructor":"off",
"@typescript-eslint/no-useless-constructor":"error",
"@typescript-eslint/no-var-requires":"error",
"@typescript-eslint/no-misused-new":"error",
"@typescript-eslint/no-non-null-asserted-optional-chain":"error",
"@typescript-eslint/no-non-null-assertion":"error",
"@typescript-eslint/no-parameter-properties":"error",
"@typescript-eslint/no-require-imports":"error",
"@typescript-eslint/no-this-alias":"error",
"@typescript-eslint/no-throw-literal":"error",
"@typescript-eslint/no-unsafe-call":"error",
"@typescript-eslint/no-unsafe-member-access":"error",
"@typescript-eslint/no-unsafe-return":"error",
"no-unused-expressions":"off",
"@typescript-eslint/no-unused-expressions":"error",
"no-extra-semi":"off",
"@typescript-eslint/no-extra-semi":"error",
"@typescript-eslint/no-extraneous-class":"error",
"@typescript-eslint/no-floating-promises":"error",
"@typescript-eslint/no-implied-eval":"error",
"comma-spacing":"off",
"@typescript-eslint/comma-spacing":"error",
"func-call-spacing":"off",
"@typescript-eslint/func-call-spacing":"error",
"space-before-function-paren":"off",
"@typescript-eslint/space-before-function-paren":"error",
"@typescript-eslint/type-annotation-spacing":"error",
"no-magic-numbers":"off",
"@typescript-eslint/no-magic-numbers":[
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/no-for-in-array": "error",
"@typescript-eslint/no-misused-promises": "error",
"@typescript-eslint/explicit-function-return-type": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-regexp-exec": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/explicit-member-accessibility": "error",
"require-await": "off",
"@typescript-eslint/require-await": "error",
"@typescript-eslint/unbound-method": "error",
"no-var": "error",
"prefer-const": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"no-use-before-define": "off",
"@typescript-eslint/no-use-before-define": "error",
"no-useless-constructor": "off",
"@typescript-eslint/no-useless-constructor": "error",
"@typescript-eslint/no-var-requires": "error",
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
"@typescript-eslint/no-non-null-assertion": "error",
"@typescript-eslint/no-parameter-properties": "error",
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/no-this-alias": "error",
"@typescript-eslint/no-throw-literal": "error",
"@typescript-eslint/no-unsafe-call": "error",
"@typescript-eslint/no-unsafe-member-access": "error",
"@typescript-eslint/no-unsafe-return": "error",
"no-unused-expressions": "off",
"@typescript-eslint/no-unused-expressions": "error",
"no-extra-semi": "off",
"@typescript-eslint/no-extra-semi": "error",
"@typescript-eslint/no-extraneous-class": "error",
"@typescript-eslint/no-floating-promises": "error",
"@typescript-eslint/no-implied-eval": "error",
"comma-spacing": "off",
"@typescript-eslint/comma-spacing": "error",
"func-call-spacing": "off",
"@typescript-eslint/func-call-spacing": "error",
"space-before-function-paren": "off",
"@typescript-eslint/space-before-function-paren": "error",
"@typescript-eslint/type-annotation-spacing": "error",
"no-magic-numbers": "off",
"@typescript-eslint/no-magic-numbers": [
"error",
{
"ignoreArrayIndexes":true,
"ignore":[
-1,
1,
0,
2,
3
]
"ignoreArrayIndexes": true,
"ignore": [-1, 1, 0, 2, 3]
}
],
"@typescript-eslint/no-unnecessary-boolean-literal-compare":"warn",
"@typescript-eslint/no-unnecessary-condition":"warn",
"@typescript-eslint/no-unnecessary-qualifier":"warn",
"@typescript-eslint/no-unnecessary-type-arguments":"warn",
"@typescript-eslint/member-ordering":"warn",
"@typescript-eslint/strict-boolean-expressions":"off",
"@typescript-eslint/prefer-as-const":"warn",
"@typescript-eslint/prefer-for-of":"warn",
"@typescript-eslint/prefer-function-type":"warn",
"@typescript-eslint/prefer-namespace-keyword":"warn",
"@typescript-eslint/prefer-nullish-coalescing":"warn",
"@typescript-eslint/prefer-optional-chain":"warn",
"@typescript-eslint/prefer-readonly":"warn",
"@typescript-eslint/prefer-readonly-parameter-types":"off",
"@typescript-eslint/no-explicit-any":1,
"@typescript-eslint/no-inferrable-types":[
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "warn",
"@typescript-eslint/no-unnecessary-condition": "warn",
"@typescript-eslint/no-unnecessary-qualifier": "warn",
"@typescript-eslint/no-unnecessary-type-arguments": "warn",
"@typescript-eslint/member-ordering": [
"error",
{
"default": {
"memberTypes": [
"public-static-field",
"public-field",
"protected-static-field",
"protected-field",
"private-static-field",
"private-field",
"public-constructor",
"protected-constructor",
"private-constructor",
["public-get", "public-set"],
["protected-get", "protected-set"],
["private-get", "private-set"],
"signature",
"public-static-method",
"public-method",
"protected-static-method",
"protected-method",
"private-static-method",
"private-method"
],
"order": "as-written"
}
}
],
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/prefer-as-const": "warn",
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-namespace-keyword": "warn",
"@typescript-eslint/prefer-nullish-coalescing": "warn",
"@typescript-eslint/prefer-optional-chain": "warn",
"@typescript-eslint/prefer-readonly": "warn",
"@typescript-eslint/prefer-readonly-parameter-types": "off",
"@typescript-eslint/no-explicit-any": 1,
"@typescript-eslint/no-inferrable-types": [
"warn",
{
"ignoreParameters":true
"ignoreParameters": true
}
],
"no-unused-vars":"off",
"no-unused-vars": "off",
"unused-imports/no-unused-vars": "off",
"unused-imports/no-unused-imports":"error",
"@typescript-eslint/no-unused-vars":[
"unused-imports/no-unused-imports": "error",
"@typescript-eslint/no-unused-vars": [
"warn",
{
"vars":"all",
"varsIgnorePattern":"^_",
"args":"after-used",
"argsIgnorePattern":"^_"
"vars": "all",
"varsIgnorePattern": "^_",
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"indent": "off",
"@typescript-eslint/indent": ["error", 2],
"import/no-unresolved": ["off"]
"import/no-unresolved": [2, { "ignore": ["^#.+$"] }]
}
}

6
.prettierrc Normal file
View File

@ -0,0 +1,6 @@
{
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": false
}

19
.vscode/launch.json vendored
View File

@ -1,11 +1,18 @@
{
"version": "0.2.0",
"configurations": [{
"configurations": [
{
"name": "Node Inspector",
"type": "node",
"request": "launch",
"args": ["${workspaceRoot}/src/server.ts"],
"runtimeArgs": ["--loader", "./src/myloader.mjs", "--experimental-top-level-await", "--experimental-specifier-resolution=node", "--experimental-specifier-resolution=node"],
"args": ["${workspaceRoot}/src/app.ts", "start", "-p", "1234"],
"runtimeArgs": [
"--no-warnings",
"--loader",
"./src/myloader.mjs",
"--experimental-modules",
"--es-module-specifier-resolution=node"
],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"sourceMaps": true,
@ -14,7 +21,9 @@
"internalConsoleOptions": "openOnSessionStart",
"env": {
"TS_NODE_IGNORE": "false",
"NODE_ENV": "development"
"NODE_ENV": "development",
"TS_NODE_COMPILER": "ttypescript"
}
}]
}
]
}

27
.vscode/settings.json vendored
View File

@ -11,7 +11,7 @@
"editor.formatOnSave": false
},
"[markdown]": {
"editor.formatOnSave": false
"editor.formatOnSave": true
},
"search.exclude": {
"**/node_modules": true,
@ -21,5 +21,26 @@
},
"typescript.referencesCodeLens.enabled": true,
"appService.zipIgnorePattern": [".vscode{,/**}"],
"appService.deploySubpath": ""
}
"appService.deploySubpath": "",
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#2f7c47",
"activityBar.activeBorder": "#422c74",
"activityBar.background": "#2f7c47",
"activityBar.foreground": "#e7e7e7",
"activityBar.inactiveForeground": "#e7e7e799",
"activityBarBadge.background": "#422c74",
"activityBarBadge.foreground": "#e7e7e7",
"sash.hoverBorder": "#2f7c47",
"statusBar.background": "#215732",
"statusBar.foreground": "#e7e7e7",
"statusBarItem.hoverBackground": "#2f7c47",
"statusBarItem.remoteBackground": "#215732",
"statusBarItem.remoteForeground": "#e7e7e7",
"titleBar.activeBackground": "#215732",
"titleBar.activeForeground": "#e7e7e7",
"titleBar.inactiveBackground": "#21573299",
"titleBar.inactiveForeground": "#e7e7e799"
},
"peacock.color": "#215732",
"docwriter.hotkey.windows": "Alt + ."
}

View File

@ -5,11 +5,13 @@
"main": "server.ts",
"type": "module",
"scripts": {
"start": "node --no-warnings --loader ./src/myloader.mjs --experimental-modules --es-module-specifier-resolution=node ./dist/server.js",
"dev": "node --no-warnings --loader ./src/myloader.mjs --experimental-modules --es-module-specifier-resolution=node ./src/server.ts",
"build": "tsc -p tsconfig.json",
"start": "node --no-warnings --trace-warnings --es-module-specifier-resolution=node ./dist/app.js",
"dev": "cross-env TS_NODE_COMPILER=ttypescript node --no-warnings --loader ./src/myloader.mjs --es-module-specifier-resolution=node ./src/app.ts",
"build": "ttsc -p tsconfig.json && tsc-alias -p tsconfig.json",
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint --fix --ext ts src/"
"lint": "eslint --fix --ext ts src/",
"upgrade": "yarn upgrade --latest",
"xxx": "node --loader ts-node/esm ./src/test.ts"
},
"repository": {
"type": "git",
@ -23,26 +25,44 @@
"author": "Joshua Schnabel",
"license": "MIT",
"dependencies": {
"@gquittet/graceful-server": "^2.5.2",
"fastify": "^3.25.3",
"@automapper/classes": "^8.5.0",
"@automapper/core": "^8.5.0",
"@fastify/helmet": "9.1.0",
"@fastify/sensible": "^5.1.0",
"@gquittet/graceful-server": "^3.0.2",
"@nestjs/common": "^9.0.2",
"@nestjs/core": "^9.0.2",
"@nestjs/passport": "^9.0.0",
"@nestjs/platform-fastify": "^9.0.2",
"args-command-parser": "^1.2.4",
"cli-color": "^2.0.3",
"fastify-helmet": "^7.1.0",
"fastify-plugin": "^3.0.0",
"fastify-sensible": "^3.1.2",
"winston": "^3.4.0"
"passport": "^0.6.0",
"passport-http": "^0.3.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.5.5",
"typescript-rtti": "^0.8.0",
"winston": "^3.8.1"
},
"devDependencies": {
"@tsconfig/node16": "^1.0.2",
"@types/node": "^17.0.9",
"@tsconfig/node16": "^1.0.3",
"@types/cli-color": "^2.0.2",
"@types/node": "^18.0.3",
"@types/passport": "^1.0.9",
"@types/passport-http": "^0.3.9",
"@types/triple-beam": "^1.3.2",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"eslint": "^8.7.0",
"@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.30.5",
"cross-env": "^7.0.3",
"eslint": "^8.19.0",
"eslint-import-resolver-alias": "^1.1.2",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-promise": "^6.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"ts-node": "^10.4.0",
"typescript": "^4.5.4"
},
"_moduleAliases": {
"@logger/*": "./dist/logger/*"
"ts-node": "^10.8.2",
"tsc-alias": "^1.6.11",
"ttypescript": "^1.5.13",
"typescript": "^4.7.4"
}
}

View File

@ -1,5 +0,0 @@
class InfrastructureAutoLoadable {
public load (): void {console.log("TEST");}
}
export default new InfrastructureAutoLoadable();

10
src/app.module.ts Normal file
View File

@ -0,0 +1,10 @@
import { FrameworkModule } from "#framework/framework.module";
import { Module } from "@nestjs/common";
import { PersistenceModule } from "./application/persistance.module";
import { RestModule } from "./application/rest.module";
@Module({
imports: [RestModule, FrameworkModule, PersistenceModule]
})
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class AppModule {}

80
src/app.ts Normal file
View File

@ -0,0 +1,80 @@
/* eslint-disable @typescript-eslint/no-magic-numbers */
/* eslint-disable no-console */
import "reflect-metadata";
import clc from "cli-color";
import App, { AppCommand } from "#app";
import ConfigurationDefinition, { DefinitionType } from "#configuration/configurationDefinition";
import definitionValidators from "#configuration/configurationValidatior";
import Server from "#framework/server/server";
import getlogger, { defaultLogger } from "#logger";
import {Builder} from "#framework/builder/builder";
const x = Builder<App>(Server);
const app: App = new (class extends App {
public options (setDefinitions: (...definitions: ConfigurationDefinition[]) => void): void {
setDefinitions(new ConfigurationDefinition()
.withName("port")
.withType(DefinitionType.Number)
.withArgName("port", "p")
.withEnvName("port")
.withDescription("Port of the server.")
.isRequired()
.relevantToCommands("start")
.withValidator(definitionValidators.validatePort()),
new ConfigurationDefinition()
.withName("host")
.withType(DefinitionType.String)
.withArgName("host", "h")
.withEnvName("host")
.withDefault("127.0.0.1")
.withDescription("Host name under which the server should listen for requests.")
.relevantToCommands("start")
.withValidator(definitionValidators.validateHost()));
}
public commmands (setCommands: (...commands: AppCommand[]) => void): void {
setCommands(new (class implements AppCommand {
public getName (): string {
return "start";
}
public getDescriptions (): string {
return "Start the server.";
}
public run (): void {
const server = new Server();
const logger = getlogger("server");
server.setup().then(() => {
return server.start();
}).catch((err) => logger.error(err));
}
}
));
}
public printBanner (): void {
const crot = clc.xterm(9);
const cyellow = clc.xterm(11);
const cwhite = clc.xterm(15);
const cpurple = clc.xterm(13);
console.log(crot(" ") + cwhite("_ _ _ _ _ _ ") + cpurple(" ____ _ "));
console.log(crot(" \\ \\ ") + cwhite(" / \\ _ __ | |_| |__ (_) | | ") + cpurple("| __ ) __ _ ___| | ___ _ _ __ "));
console.log(crot(" |__/ _ .-. ") + cwhite(" / _ \\ | '_ \\| __| '_ \\| | | | ") + cpurple("| _ \\ / _` |/ __| |/ / | | | '_ \\ "));
console.log(crot("(") + cyellow("o_o") + crot(")(_`>( ) ") + cwhite(" / ___ \\| | | | |_| | | | | | |") + cpurple(" | |_) | (_| | (__| <| |_| | |_) |"));
console.log(crot(" { }//||\\\\`-' ") + cwhite(" /_/ \\_\\_| |_|\\__|_| |_|_|_|_| ") + cpurple("|____/ \\__,_|\\___|_|\\_\\\\__,_| .__/ "));
console.log(cpurple(" |_| "));
}
});
try {
app.execute();
} catch (error: unknown) {
if (error instanceof Error) {
defaultLogger.error(error.message);
} else {
defaultLogger.error("ssss" + error);
}
}

View File

@ -0,0 +1,68 @@
import { Aggregate, Identifier, IdentifierGenerator } from "#ddd";
import { randomUUID } from "crypto";
import { UserAssignment } from "./userAssignment.entity";
export class RepoId extends Identifier<string> {
private readonly _value: string;
public constructor (value: string) {
super();
this._value = value;
}
public get value (): string {
return this._value;
}
}
export default class Repo extends Aggregate<RepoId>{
private readonly _id?: RepoId;
private _name = "";
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
private readonly _count = 1;
private readonly _userAssignment: UserAssignment[] = [];
public constructor (id: RepoId | undefined) {
super();
this._id = id;
}
public get id (): RepoId | undefined {
return this._id;
}
public get name (): string {
return this._name;
}
public set name (name: string) {
this._name = name;
}
public get path (): string {
return "/" + this._name;
}
public get userAssignment (): UserAssignment[] {
return this._userAssignment;
}
public addUserAssignment (ua: UserAssignment): UserAssignment[] {
this._userAssignment.push(ua);
return this._userAssignment;
}
}
export interface IRepoBuider {
name: string;
id: RepoId;
addUserAssignment: UserAssignment;
}
export class RepoIdGenerator implements IdentifierGenerator<RepoId> {
public generate (): RepoId {
return new RepoId(randomUUID());
}
}

View File

@ -0,0 +1,6 @@
import {Repository} from "#ddd";
import Repo, { RepoId } from "./repo.entity";
export const RepoRepository = "RepoRepository";
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export default interface RepoRepository extends Repository<RepoId, Repo> {}

View File

@ -0,0 +1,30 @@
import { ValueObject } from "#ddd";
import { UserId } from "../user/user.entity";
// import { Builder, IBuilder } from "builder-pattern";
export enum UserRight {
Read = "read",
AppendOnly = "append",
Write = "write"
}
export class UserAssignment extends ValueObject {
private readonly _right: UserRight;
private readonly _userId: UserId;
public constructor (userId: UserId, right: UserRight) {
super();
this._userId = userId;
this._right = right;
}
public get id (): UserId {
return this._userId;
}
public get right (): UserRight {
return this._right;
}
}

View File

@ -0,0 +1,42 @@
import { Identifier, Aggregate} from "#ddd";
export default class User extends Aggregate<UserId> {
private readonly _id: UserId | undefined;
private readonly _name: string;
private readonly _password: string;
public constructor (id: UserId | undefined, name: string, password: string) {
super();
this._id = id;
this._name = name;
this._password = password;
}
public get id (): UserId | undefined {
return this._id;
}
public get name (): string {
return this._name;
}
public get password (): string {
return this._password;
}
}
export class UserId extends Identifier<string> {
private readonly _value: string;
public constructor (value: string) {
super();
this._value = value;
}
public get value (): string {
return this.value;
}
}

View File

@ -0,0 +1,6 @@
import User, { UserId } from "./user.entity";
import {Repository} from "#ddd";
export const UserRepositoryToken = "UserRepository";
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface UserRepository extends Repository<UserId, User> {}

View File

@ -0,0 +1,47 @@
import Repo, { RepoId } from "#domain/repo/repo.entity";
import RepoRepository from "#domain/repo/repo.repo";
import { Injectable } from "@nestjs/common";
@Injectable()
export default class RepoFilePersistence implements RepoRepository {
private readonly values: Map<RepoId, Repo> = new Map();
public constructor () {
const x = new RepoId("xxxx");
// const y = Repo.builder().id(x).name("dddd").build();
// this.values.set(x, y);
}
public deleteAggregate (aggregate: Repo): void {
if(aggregate.id)
this.values.delete(aggregate.id);
}
public deleteAggregateById (id: RepoId): void{
this.values.delete(id);
}
public getAggregate (identitiy: RepoId): Repo {
const v: Repo | undefined = this.values.get(identitiy);
if(v !== undefined)
return v;
throw new Error();
}
public getAggregates (): Repo[] {
return Array.from(this.values.values());
}
public storeAggregate (aggregate: Repo): Repo {
let id = aggregate.id;
if(id) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
id = new RepoId(Math.random().toString(16).substr(2, 8));
// aggregate = Repo.builder().id(id).name(aggregate.name).build();
}
this.values.set(id as RepoId, aggregate);
return aggregate;
}
}

View File

@ -0,0 +1,42 @@
import User, { UserId } from "#domain/user/user.entity";
import { UserRepository } from "#domain/user/user.repo";
import { Injectable } from "@nestjs/common";
@Injectable()
export default class UserFilePersistence implements UserRepository {
public xxx = "";
private readonly values: Map<UserId, User> = new Map();
public deleteAggregate (aggregate: User): void {
if(aggregate.id)
this.values.delete(aggregate.id);
}
public deleteAggregateById (identitiy: UserId): void {
this.values.delete(identitiy);
}
public getAggregate (identitiy: UserId): User {
const v: User | undefined = this.values.get(identitiy);
if(v !== undefined)
return v;
throw new Error();
}
public getAggregates (): User[] {
return Array.from(this.values.values());
}
public storeAggregate (aggregate: User): User {
let id = aggregate.id;
if(id) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
id = new UserId(Math.random().toString(16).substr(2, 8));
// aggregate = User.builder().id(id).name(aggregate.name).password(aggregate.password).build();
}
this.values.set(id as UserId, aggregate);
return aggregate;
}
}

View File

@ -0,0 +1,18 @@
import { constructUsing, createMap, forMember, mapFrom } from "@automapper/core";
import Repo, { RepoId } from "#domain/repo/repo.entity";
import { mapper } from "#framework/mapper/mapper";
export class RepoDto {
public id!: string;
public name!: string;
}
createMap(mapper, Repo, RepoDto, forMember(
(destination: RepoDto) => destination.id, mapFrom(s => s.id?.value)
));
createMap(mapper, RepoDto, Repo,
constructUsing((sourceObject, _destinationIdentifier): Repo => {
return new Repo(new RepoId(sourceObject.id));
})
);

View File

@ -0,0 +1,30 @@
import Repo from "#domain/repo/repo.entity";
import Repository, { RepoRepository } from "#domain/repo/repo.repo";
import { mapper } from "#framework/mapper/mapper";
import { Body, Controller, Get, Inject, Post, UseGuards } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { RepoDto } from "./dtos/repo.dto";
@Controller("api/management")
export class ManagementController {
@Inject(RepoRepository)
private readonly repos!: Repository;
@Post()
@UseGuards(AuthGuard("basic"))
public create (@Body() repo: RepoDto): RepoDto {
console.log(repo);
console.log(mapper.map(repo, RepoDto, Repo));
return new RepoDto();
}
@Get()
@UseGuards(AuthGuard("basic"))
public findAll (): RepoDto[] {
console.log(this.repos.getAggregates());
console.log(mapper.mapArray<Repo, RepoDto>(this.repos.getAggregates(), Repo, RepoDto));
return mapper.mapArray<Repo, RepoDto>(this.repos.getAggregates(), Repo, RepoDto);
}
}

View File

@ -0,0 +1,11 @@
import { Controller, Get } from "@nestjs/common";
@Controller("repo")
export class RepoController {
@Get()
public findAll (): string {
return "This action returns all cats";
}
}

View File

@ -0,0 +1,22 @@
/* eslint-disable @typescript-eslint/no-extraneous-class */
import {RepoRepository} from "#domain/repo/repo.repo";
import {UserRepositoryToken} from "#domain/user/user.repo";
import { Module } from "@nestjs/common";
import RepoFilePersistence from "./infrastructure/persistence/file/repo.persistence";
import UserFilePersistence from "./infrastructure/persistence/file/user.persistence";
@Module({
providers: [
{
useClass: RepoFilePersistence,
provide: RepoRepository
},
{
useClass: UserFilePersistence,
provide: UserRepositoryToken
}
],
exports: [RepoRepository]
})
export class PersistenceModule {}

View File

@ -0,0 +1,12 @@
/* eslint-disable @typescript-eslint/no-extraneous-class */
import { Module } from "@nestjs/common";
import { ManagementController } from "./infrastructure/presentation/rest/management.controller";
import { RepoController } from "./infrastructure/presentation/rest/repo.controller";
import { PersistenceModule } from "./persistance.module";
@Module({
controllers: [ManagementController, RepoController],
imports: [PersistenceModule]
})
export class RestModule {}

82
src/framework/app/app.ts Normal file
View File

@ -0,0 +1,82 @@
/* eslint-disable @typescript-eslint/no-magic-numbers */
/* eslint-disable no-console */
import configuration, {initConfiguration} from "#configuration";
import ConfigurationDefinition from "#configuration/configurationDefinition";
import getLogger from "#logger";
import { Logger } from "winston";
import AppHelp from "./appHelp";
// TODO: move to App
const ENV_PREFIX = "ANT";
export abstract class AppCommand {
public abstract getDescriptions (): string;
public abstract getName (): string;
public abstract run (): void;
}
export default abstract class App {
private readonly appHelp: AppHelp;
private readonly commands: Map<string, AppCommand> = new Map();
private readonly logger: Logger;
public constructor () {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const that = this;
this.appHelp = new AppHelp();
this.logger = getLogger("app");
this.addCommmand(new (class implements AppCommand {
public getDescriptions (): string {
return "Show help for this application.";
}
public getName (): string {
return "help";
}
public run (): void {
that.appHelp.printHelp(that.commands, ENV_PREFIX);
}
}));
}
public abstract commmands (setCommands: (...commands: AppCommand[]) => void): void;
/**
* It executes the commands and options.
*/
public execute (): void {
this.commmands((...definitions): void => {
for (const definition of definitions) {
this.addCommmand(definition);
}
console.log();
});
this.options((...defs): void => {
initConfiguration(ENV_PREFIX, ...defs);
});
this.printBanner();
configuration().validate();
const name = configuration().getCommand();
const command = this.commands.get(name);
this.logger.info("Starting App with command '" + name + "'");
if(command) {
command.run();
} else {
throw new Error(name + " is not implemented");
}
}
public abstract options (setDefinitions: (...definitions: ConfigurationDefinition[]) => void): void;
public abstract printBanner (): void;
private addCommmand (command: AppCommand): void {
this.commands.set(command.getName(), command);
}
}

View File

@ -0,0 +1,113 @@
/* eslint-disable @typescript-eslint/no-magic-numbers */
/* eslint-disable no-console */
import configuration from "#configuration";
import clc from "cli-color";
import ConfigurationDefinition from "#configuration/configurationDefinition";
import { AppCommand } from "#app";
/* The `AppHelp` class is responsible for printing out the help message */
export default class AppHelp {
/**
* Prints the help message
* @param commands - A map of all the commands that the program can run.
*/
public printHelp (commands: Map<string, AppCommand>, envPrefix: string): void {
const programm = process.argv.slice(0, 2).join(" ");
const underline = clc.underline;
console.log(underline("Usage") + "\n");
console.log("\t" + programm + " [COMMAND] [OPTIONS]" + "\n");
console.log(underline("Commands") + "\n");
this.printCommands(commands);
console.log();
console.log(underline("Options"));
console.log();
this.printOptions(envPrefix, (def) => def.getCommands().length >= 1);
console.log();
console.log(underline("Rules & Behavior"));
console.log();
this.printOptions(envPrefix, (def) => def.getCommands().length == 0);
}
/**
* Generate the parameters for the command line help
* @param {ConfigurationDefinition} def - ConfigurationDefinition
* @returns The `generateParameters` function returns a string that represents the parameters that are
* being passed to the `CommandLine` object.
*/
private generateParameters (def: ConfigurationDefinition): string {
const args: string[] = [];
for (const argParam of def.getArgName()) {
if (argParam.length === 1)
args.push("-" + argParam + " <" + def.getName() + ">");
else
args.push("--" + argParam + " <" + def.getName() + ">");
}
let paramater = args.join(" | ");
if (def.getArgName().length > 1)
paramater = "(" + paramater + ")";
if (!def.getRequired())
paramater = "[" + paramater + "]";
return paramater;
}
/**
* Get the configuration definitions from the configuration object
* @returns The configuration definitions are being returned.
*/
private getConfigurationDefinitions (): ConfigurationDefinition[] {
return configuration().getDefinitions().sort((a, b) => {
if (a.getRequired() && !b.getRequired())
return -1;
else if (!a.getRequired() && b.getRequired())
return 1;
else
return a.getName() < b.getName() ? -1 : 1;
});
}
/**
* Prints all commands and their descriptions
* @param commands - Map<string, AppCommand>
*/
private printCommands (commands: Map<string, AppCommand>): void {
commands.forEach((v, k, _m) => {
console.log("\t" + k + " - " + v.getDescriptions());
const paramaters: string[] = [];
// Find parameters for current command
for (const def of this.getConfigurationDefinitions()) {
if (def.getCommands().includes(k)) {
paramaters.push(this.generateParameters(def));
}
}
console.log("\t↳ " + k + " " + paramaters.join(" "));
});
}
/**
* Prints out all the options that are available to the user
* @param condition - Select witch options are printed.
*/
private printOptions (envPrefix: string, condition: (a: ConfigurationDefinition) => boolean): void {
let maxLength = 0;
/* Find longest String */
for (const def of this.getConfigurationDefinitions()) {
if (condition(def)) {
let option = " -" + def.getArgName().sort((a, b) => a.length - b.length).join(" -");
option += " (" + envPrefix + "_" + def.getEnvName().toUpperCase()+")";
maxLength =
Math.max(maxLength, option.length);
}
}
for (const def of this.getConfigurationDefinitions()) {
if (condition(def)) {
let option = " -" + def.getArgName().sort((a, b) => a.length - b.length).join(" -");
option += " (" + envPrefix + "_" + def.getEnvName().toUpperCase()+")";
console.log("\t↳ " + (option.padEnd(maxLength, " ")) + " - " + def.getDescription());
}
}
}
}

View File

@ -0,0 +1,23 @@
import { BasicStrategy as Strategy } from "passport-http";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { FastifyRequest } from "fastify";
@Injectable()
export class BasicStrategy extends PassportStrategy(Strategy) {
public constructor () {
super({
passReqToCallback: true,
realm: "XXXXX"
});
}
// eslint-disable-next-line @typescript-eslint/require-await
public async validate (req: FastifyRequest, username: string, password: string): Promise<boolean> {
if (username === "test" && password === "abc") {
return true;
}
throw new UnauthorizedException();
}
}

View File

@ -0,0 +1,105 @@
import Repo, { IRepoBuider, RepoId } from "#domain/repo/repo.entity";
import { UserAssignment, UserRight } from "#domain/repo/userAssignment.entity";
import { UserId } from "#domain/user/user.entity";
import { reflect, ReflectedArrayRef, ReflectedClassRef, ReflectedUnionRef } from "typescript-rtti";
export type IBuilder<T> = {
[k in keyof T]-?: (arg: T[k]) => IBuilder<T>
}
& {
build(): T;
};
type Clazz<T> = new (...args: unknown[]) => T;
type Constructor<Params extends readonly any[] = readonly any[], Result = any> = new (...params: Params) => Result;
interface Foo {
[key: string]: unknown;
}
export function Builder<Type> (type: Constructor): IBuilder<Type> {
const parameters: { name: string; class: any }[] = [];
const properties = new Map<string, { name: string; class: any, aggregation?: string, parameter: boolean }>();
for (const property of reflect(type).properties) {
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (!property.name.startsWith("_") && property.class.super !== null) {
if (property.type instanceof ReflectedClassRef) {
properties.set(property.name, { name: property.name, class: property.type.class, parameter: false });
}
else if (property.type instanceof ReflectedUnionRef) {
properties.set(property.name, { name: property.name, class: (<ReflectedClassRef<unknown>>property.type.types.filter(t => t instanceof ReflectedClassRef)[0]).class, parameter: false });
}
else if (property.type instanceof ReflectedArrayRef) {
properties.set(property.name, { name: property.name, class: (<ReflectedClassRef<unknown>>property.type.elementType).class, aggregation: "array", parameter: false });
}
}
}
for (const parameter of reflect(type).parameters) {
const name = parameter.rawMetadata.n;
parameters.push({ name: name, class: properties.get(parameter.name) });
if (properties.get(name) !== undefined)
(properties.get(name) as { parameter: boolean }).parameter = true;
}
const values = new Map<string, unknown>();
const builder = new Proxy(
{},
{
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
get (_target, prop) {
if (typeof prop === "string" && prop.startsWith("add")) {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
const tprop = prop.charAt(3).toLocaleLowerCase() + prop.substring(4);
return (x: unknown): unknown => {
if (properties.get(tprop)?.aggregation === "array") {
if (!values.has(tprop))
values.set(tprop, []);
(values.get(tprop) as Array<unknown>).push(x);
} else {
values.set(tprop, x);
}
return builder;
};
} else if ("build" === prop) {
const params: unknown[] = [];
for (const param of parameters) {
params.push(values.get(param.name));
}
const obj: Foo = new type(params);
for (const propertyname of properties.keys()) {
const prop = properties.get(propertyname);
if (values.has(propertyname) && !prop?.parameter) {
if (prop?.aggregation === "array") {
const desc = Object.getOwnPropertyDescriptor(obj, propertyname);
if (desc?.set === undefined) {
const methodeName = "add" + propertyname.charAt(0).toUpperCase() + propertyname.slice(1);
for (const value of (values.get(propertyname) as Array<unknown>)) {
(obj[methodeName] as ((t: unknown) => unknown))(value);
}
} else {
obj[propertyname] = values.get(propertyname);
}
} else {
obj[propertyname] = values.get(propertyname);
}
}
}
return () => obj;
}
return (x: unknown): unknown => {
values.set(prop.toString(), x);
return builder;
};
}
}
);
return builder as IBuilder<Type>;
}
const ua1 = new UserAssignment(new UserId("test"), UserRight.Read);
const ua2 = new UserAssignment(new UserId("test2"), UserRight.Read);
const builder = Builder<IRepoBuider>(Repo);
const x = builder.id(new RepoId("test")).name("name").addUserAssignment(ua1).addUserAssignment(ua2).build();
console.log(x);

View File

@ -0,0 +1,116 @@
import { DefinitionValidator } from "./configurationValidatior";
export enum DefinitionType {
String = "string",
Boolean = "boolean",
File = "file",
Number = "number"
}
class ConfigurationDefinition implements ConfigurationDefinition {
private name!: string;
private envName!: string;
private argName!: string[];
private validator!: DefinitionValidator;
private description!: string;
private type!: DefinitionType;
private multiple = false;
private required = false;
private commands: string[] = [];
private defaultValue: string | undefined = undefined;
public getName (): string {
return this.name;
}
public getEnvName (): string {
return this.envName;
}
public getArgName (): string[] {
return this.argName;
}
public getValidator (): DefinitionValidator {
return this.validator;
}
public getDescription (): string {
return this.description;
}
public getType (): DefinitionType {
return this.type;
}
public getRequired (): boolean {
return this.required;
}
public getMultiple (): boolean {
return this.multiple;
}
public getCommands (): string[] {
return this.commands;
}
public getDefaultValue (): string[] | undefined {
if(this.defaultValue) {
return [this.defaultValue];
}
return undefined;
}
public withName (name: string): ConfigurationDefinition {
this.name = name;
return this;
}
public withEnvName (envName: string): ConfigurationDefinition {
this.envName = envName;
return this;
}
public withArgName (...argName: string[]): ConfigurationDefinition {
this.argName = argName;
return this;
}
public withValidator (validator: DefinitionValidator): ConfigurationDefinition {
this.validator = validator;
return this;
}
public withDescription (description: string): ConfigurationDefinition {
this.description = description;
return this;
}
public withDefault (defaultValue: string): ConfigurationDefinition {
this.defaultValue = defaultValue;
return this;
}
public withType (type: DefinitionType): ConfigurationDefinition {
this.type = type;
return this;
}
public relevantToCommands (...commands: string[]): ConfigurationDefinition {
this.commands = commands;
return this;
}
public isRequired (): ConfigurationDefinition {
this.required = true;
return this;
}
public isMultiple (): ConfigurationDefinition {
this.multiple = true;
return this;
}
}
export default ConfigurationDefinition;

View File

@ -0,0 +1,192 @@
import ConfigurationDefinition, {DefinitionType} from "./configurationDefinition";
import {parser, ArgCollection} from "args-command-parser";
class Configuration {
private readonly arguments: ArgCollection;
private readonly defaultCommand: string;
private readonly definitions: Map<string, ConfigurationDefinition> = new Map();
private readonly envPrefix: string;
private readonly values: Map<string, string[] | undefined> = new Map();
public constructor (defaultCommand: string, envPrefix: string, ...definitions: ConfigurationDefinition[]) {
this.arguments = parser();
for (const definition of definitions) {
this.definitions.set(definition.getName(), definition);
this.values.set(definition.getName(), this.getInternalValue(definition));
}
this.defaultCommand = defaultCommand;
this.envPrefix = envPrefix;
}
/**
* Get the value of a boolean parameter
* @param {string} name - The name of the parameter.
* @returns The value of the parameter.
*/
public getBooleanValue (name: string): boolean | boolean[] | undefined {
const def = this.definitions.get(name);
if(def?.getType() == DefinitionType.Boolean) {
const value = this.values.get(name) ?? [];
const result: boolean[] = this.toBoolean(value);
if(def.getMultiple()) {
return result;
}
return result[0];
}
return undefined;
}
/**
* Get the command from the arguments
* @returns The command that was passed in the arguments.
*/
public getCommand (): string {
if(this.arguments.data.commands.length >= 1)
return this.arguments.data.commands[0];
else
return this.defaultCommand;
}
public getDefinitions (): ConfigurationDefinition[] {
return Array.from(this.definitions.values());
}
public getNumberValue (name: string): number | number[] | undefined {
const def = this.definitions.get(name);
if(def?.getType() == DefinitionType.Number) {
const value = this.values.get(name) ?? [];
const result: number[] = this.toInteger(value);
if(def.getMultiple()) {
return result;
}
return result[0];
}
return undefined;
}
public getStringValue (name: string): string | string[] | undefined {
const def = this.definitions.get(name);
if(def?.getType() == DefinitionType.String) {
const value = this.values.get(name) ?? [];
if(def.getMultiple()) {
return value;
}
return value[0];
}
return undefined;
}
/**
* Get the value of a parameter
* @param {string} name - The name of the parameter.
* @returns The value of the parameter.
*/
public getValue (name: string): string | string[] | boolean | boolean[] | number | number[] | undefined {
const def = this.definitions.get(name);
const value = this.values.get(name) ?? [];
switch ( def?.getType() ) {
case DefinitionType.Boolean: {
const result = this.toBoolean(value);
if(def.getMultiple()) {
return result;
}
return result[0];
}
case DefinitionType.Number: {
const result: number[] = this.toInteger(value);
if(def.getMultiple()) {
return result;
}
return result[0];
}
case DefinitionType.String: {
if(def.getMultiple()) {
return value;
}
return value[0];
}
}
}
public validate (): void {
this.definitions.forEach((v, _k, _m) => {
const def = v;
const values = this.values.get(def.getName()) ?? [];
if(!def.getMultiple() && values.length > 1)
throw new Error("No more than one value may be assigned to parameter '" + def.getName() + "'.");
if(def.getRequired() && (def.getCommands().length === 0 || def.getCommands().includes(this.getCommand())) && values.length === 0)
throw new Error("Parameter '" + def.getName() + "' is required.");
for (const value of values) {
if(!def.getValidator().validate(value))
throw new Error("Parameter '" + def.getName() + "' does not meet the requirements: " + def.getValidator().description());
}
});
}
private getArgValue (def: ConfigurationDefinition): string[] | undefined{
for (const arg of def.getArgName()) {
if(arg.length >= 2 && this.arguments.hasLongSwitch(arg)) {
return this.arguments.getLongSwitch(arg).values;
} else if (this.arguments.hasShortSwitch(arg)) {
return this.arguments.getShortSwitch(arg).values;
}
}
return undefined;
}
/**
* Convert a string array to a boolean array
* @param {string[]} value - The value of the parameter.
* @returns The `toBoolean` function returns an array of booleans.
*/
private getEnvValue (def: ConfigurationDefinition): string[] | undefined {
const value = process.env[this.envPrefix + "_" + def.getEnvName().toUpperCase()];
if(value !== undefined)
return [value];
else
return undefined;
}
private getInternalValue (def: ConfigurationDefinition): string[] | undefined {
let value = this.getArgValue(def);
value = (value === undefined)?this.getEnvValue(def):value;
value = (value === undefined)?def.getDefaultValue():value;
if(value !== undefined) {
return value;
}
return undefined;
}
private toBoolean (value: string[]): boolean[] {
const result: boolean[] = [];
for (const v of value) {
result.push(v === "true" || v === "1" || v === "y");
}
return result;
}
/**
* It takes an array of strings and returns an array of numbers.
* @param {string[]} value - The value of the parameter.
* @returns The `toInteger` function returns an array of numbers.
*/
private toInteger (value: string[]): number[] {
const result: number[] = [];
for (const v of value) {
const f = parseFloat(v); const i = parseInt(v);
result.push((f == i) ? i : f);
}
return result;
}
}
let configInstance: Configuration;
export function initConfiguration (envPrefix: string, ...definitions: ConfigurationDefinition[]): void {
configInstance = new Configuration("help", envPrefix, ...definitions);
}
export default function config (): Configuration {
return configInstance;
}

View File

@ -0,0 +1,56 @@
export abstract class DefinitionValidator {
public abstract validate (value: string): boolean;
public abstract description (): string;
}
const definitionValidators = {
validateBoolean (): DefinitionValidator {
return new (class implements DefinitionValidator {
public description (): string {
return "Parameter must be a boolean value.";
}
public validate (value: string): boolean {
return (value === "true" || value === "1" || value === "y" || value === "false" || value === "0" || value === "n");
}
});
},
validateNumber (): DefinitionValidator {
return new (class implements DefinitionValidator {
public description (): string {
return "Parameter must be a numeric value.";
}
public validate (value: string): boolean {
return /^[+-]?([0-9]*[.])?[0-9]+$/.exec(value) !== null;
}
});
},
validatePort (): DefinitionValidator {
return new (class implements DefinitionValidator {
public description (): string {
return "Parameter must be valid port (1-65535).";
}
public validate (value: string): boolean {
const maxPort = 65535;
return (/^[0-9]*$/.exec(value) !== null) && parseInt(value) >= 1 && parseInt(value) <= maxPort;
}
});
},
validateHost (): DefinitionValidator {
return new (class implements DefinitionValidator {
public description (): string {
return "Hostname musst be a IP-Adress or a valid Hostname.";
}
public validate (value: string): boolean {
const regexIPv6 = /(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/gi;
const regexIPv4 = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
const regexHost = /^(([a-zA-Z0-9]*)|([a-zA-Z0-9]*\.)*([a-zA-Z]+))$/;
return (regexHost.exec(value) !== null) || (regexIPv4.exec(value) !== null) || (regexIPv6.exec(value) !== null);
}
});
}
};
export default definitionValidators;

View File

@ -0,0 +1,41 @@
export abstract class Entity<I extends Identifier<unknown>> {
public abstract get id (): I | undefined;
}
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export abstract class ValueObject {}
export abstract class Identifier<Type> {
public abstract get value (): Type;
}
export abstract class IdentifierGenerator<I extends Identifier<unknown>> {
public abstract generate (): I;
}
export abstract class Aggregate<I extends Identifier<unknown>> extends Entity<I> {}
export interface ReadRepository<I extends Identifier<unknown>, A extends Aggregate<I>> {
getAggregate(identitiy: I): A;
getAggregates(): Array<A>;
}
export interface WriteRepository<I extends Identifier<unknown>, A extends Aggregate<I>> {
storeAggregate(aggregate: A): A;
}
export interface DeleteRepository<I extends Identifier<unknown>, A extends Aggregate<I>> {
deleteAggregate(aggregate: A): void;
deleteAggregateById(identitiy: I): void;
}
export interface Repository<I extends Identifier<unknown>, A extends Aggregate<I>> extends ReadRepository<I, A>, WriteRepository<I, A>, DeleteRepository<I, A>{}

View File

@ -0,0 +1,10 @@
import { Module } from "@nestjs/common";
import { BasicStrategy } from "./auth/auth-basic.strategy";
import { PassportModule } from "@nestjs/passport";
@Module({
imports: [PassportModule],
providers: [BasicStrategy],
})
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class FrameworkModule {}

View File

@ -1,55 +1,10 @@
/* eslint-disable @typescript-eslint/require-await */
import { FastifyLogFn, FastifyLoggerInstance, FastifyPluginAsync, FastifyRequest} from "fastify";
import { FastifyPluginAsync, FastifyRequest} from "fastify";
import fastifyPlugin from "fastify-plugin";
import { Bindings } from "fastify/types/logger";
import getlogger from "./logger";
import chalk from "chalk";
import logadapter from "./pinoLoggerAdapter";
const logger = getlogger("fastify");
const customLogger: FastifyLoggerInstance = {
info: function (obj: unknown, msg?: string, ...args: unknown[]): void {
if(typeof obj === "string")
logger.info(obj, msg, ...args);
else if(msg)
logger.info(msg, obj, ...args);
} as FastifyLogFn,
warn: function (obj: unknown, msg?: string, ...args: unknown[]): void {
if(typeof obj === "string")
logger.warning(obj, msg, ...args);
else if(msg)
logger.warning(msg, obj, ...args);
} as FastifyLogFn,
error: function (obj: unknown, msg?: string, ...args: unknown[]): void {
if(typeof obj === "string")
logger.error(obj, msg, ...args);
else if(msg)
logger.error(msg, obj, ...args);
} as FastifyLogFn,
fatal: function (obj: unknown, msg?: string, ...args: unknown[]): void {
if(typeof obj === "string")
logger.fatal(obj, msg, ...args);
else if(msg)
logger.fatal(msg, obj, ...args);
} as FastifyLogFn,
trace: function (obj: unknown, msg?: string, ...args: unknown[]): void {
if(typeof obj === "string")
logger.trace(obj, msg, ...args);
else if(msg)
logger.trace(msg, obj, ...args);
} as FastifyLogFn,
debug: function (obj: unknown, msg?: string, ...args: unknown[]): void {
if(typeof obj === "string")
logger.debug(obj, msg, ...args);
else if(msg)
logger.debug(msg, obj, ...args);
} as FastifyLogFn,
child: function (_bindings: Bindings): FastifyLoggerInstance {
return customLogger;
},
setBindings: function (_bindings: Bindings): void {
// OK
}
};
// const logger = getlogger("fastify");
export type FastifyRequestLoggerOptions = {
logBody?: boolean;
@ -70,24 +25,36 @@ export const plugin: FastifyPluginAsync<FastifyRequestLoggerOptions> = async (fa
return false;
};
const ifExists = (prefix: string, value: string): string => {
if(value.length >= 1) {
if(prefix.length >= 1) {
return prefix + " " + value;
}
return value;
}
return "";
};
fastify.addHook("onRequest", async (request) => {
if (isIgnoredRequest(request)) {
return;
}
const id = request.id;
const method = request.method;
const referrer = (request.headers["Referer"] ?? (request.headers["referer"]) ?? request.headers["Refferer"]) ?? request.headers["refferer"];
const referrer = (request.headers["Referer"] ?? (request.headers["referer"]) ?? request.headers["Refferer"]) ?? request.headers["refferer"] ?? "none";
const remoteAddr = request.ip;
const remoteUser = "???";
const url = request.url;
const httpVersion = request.raw.httpVersion;
const userAgent = request.headers["User-Agents"];
const contentLength = request.headers["content-length"];
const contentLength = request.headers["content-length"] ?? "";
request.log.info(`${chalk.bold.yellow("→")} ${chalk.yellow(method)} ${chalk.green(url)} HTTP/${httpVersion} - ${chalk.blue(remoteAddr)} ${remoteUser} - ${referrer} - ${contentLength} bytes [${id}]`);
request.log.trace(`${chalk.bold.yellow("→")} ${chalk.yellow(method)} ${chalk.green(url)} ${userAgent} [${id}]`);
console.log(JSON.stringify(request.headers));
request.log.info(`${chalk.bold.yellow("→")} ${chalk.yellow(method)} ${chalk.green(url)} HTTP/${httpVersion} - ${chalk.blue(remoteAddr)} ${ifExists("", remoteUser)} - ${referrer} ${ifExists("-", contentLength+" bytes")} [${chalk.greenBright(id)}]`);
request.log.trace(`${chalk.bold.yellow("→")} ${chalk.yellow(method)} ${chalk.green(url)} ${userAgent} [${chalk.greenBright(id)}]`);
request.log.info(
`${chalk.bold.yellow("←")}${chalk.yellow(request.method)}:${chalk.green(
`${chalk.bold.yellow("← ")}${chalk.yellow(request.method)}:${chalk.green(
request.url
)} request from ip ${chalk.blue(request.ip)}${
contentLength ? ` with a ${chalk.yellow(contentLength)}-length body` : ""
@ -141,15 +108,9 @@ export const plugin: FastifyPluginAsync<FastifyRequestLoggerOptions> = async (fa
});
};
declare module "fastify" {
interface FastifyLoggerInstance {
setBindings(bindings: Bindings): void;
}
}
export const fastifyRequestLogger = fastifyPlugin(plugin, {
fastify: "3.x",
fastify: "4.x",
name: "fastify-request-logger",
});
export const fastifyLogger = customLogger;
export const fastifyLogger = logadapter;

View File

@ -5,7 +5,7 @@ const { combine, timestamp, label, printf, errors, splat } = format;
// Custom logging format
const customFormat = printf(({ level, message, label, timestamp, stack }) => {
return `${level}\t ${timestamp} · ${label || "-"}: ${message} ${stack || ""}`;
return `${level}\t ${timestamp} · ${label || "-"} \ ${message} ${stack || ""}`;
});
const myCustomLevels = {

View File

@ -0,0 +1,27 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { LoggerService } from "@nestjs/common";
import getlogger from "./logger";
const logger = getlogger("nest");
export default class NestLogger implements LoggerService {
public log (message: any, ...optionalParams: any[]): void {
logger.info(message, optionalParams);
}
public error (message: any, ...optionalParams: any[]): void {
logger.error(message, optionalParams);
}
public warn (message: any, ...optionalParams: any[]): void {
logger.warning(message, optionalParams);
}
public debug (message: any, ...optionalParams: any[]): void {
logger.debug(message, optionalParams);
}
public verbose (message: any, ...optionalParams: any[]): void {
logger.verbose(message, optionalParams);
}
}

View File

@ -0,0 +1,37 @@
import { FastifyLogFn, FastifyLoggerInstance } from "fastify";
import getlogger from "./logger";
import pino, { ChildLoggerOptions, LoggerOptions } from "pino";
const pinoLogger = pino();
const logger = getlogger("fastify");
const customLogger: FastifyLoggerInstance = pinoLogger;
customLogger.info = function (obj: unknown, msg?: string, ...args: unknown[]): void {
if (typeof obj === "string") logger.info(obj, msg, ...args);
else if (msg) logger.info(msg, obj, ...args);
} as FastifyLogFn;
customLogger.warn = function (obj: unknown, msg?: string, ...args: unknown[]): void {
if (typeof obj === "string") logger.warning(obj, msg, ...args);
else if (msg) logger.warning(msg, obj, ...args);
} as FastifyLogFn;
customLogger.error = function (obj: unknown, msg?: string, ...args: unknown[]): void {
if (typeof obj === "string") logger.error(obj, msg, ...args);
else if (msg) logger.error(msg, obj, ...args);
} as FastifyLogFn;
customLogger.fatal = function (obj: unknown, msg?: string, ...args: unknown[]): void {
if (typeof obj === "string") logger.fatal(obj, msg, ...args);
else if (msg) logger.fatal(msg, obj, ...args);
} as FastifyLogFn;
customLogger.trace = function (obj: unknown, msg?: string, ...args: unknown[]): void {
if (typeof obj === "string") logger.trace(obj, msg, ...args);
else if (msg) logger.trace(msg, obj, ...args);
} as FastifyLogFn;
customLogger.debug = function (obj: unknown, msg?: string, ...args: unknown[]): void {
if (typeof obj === "string") logger.debug(obj, msg, ...args);
else if (msg) logger.debug(msg, obj, ...args);
} as FastifyLogFn;
customLogger.child = function <ChildOptions extends pino.ChildLoggerOptions>(_bindings: pino.Bindings, _options?: ChildLoggerOptions): pino.Logger<LoggerOptions & ChildOptions> {
return <pino.Logger<LoggerOptions & ChildOptions>>customLogger;
};
export default customLogger;

View File

@ -0,0 +1,7 @@
import { CamelCaseNamingConvention, createMapper } from "@automapper/core";
import { classes } from "@automapper/classes";
export const mapper = createMapper({
strategyInitializer: classes(),
namingConventions: new CamelCaseNamingConvention()
});

View File

@ -0,0 +1,74 @@
import GracefulServer from "@gquittet/graceful-server";
import getlogger from "#logger";
import {fastifyRequestLogger, fastifyLogger} from "#logger/fastifyLogger";
import { Logger } from "winston";
import configuration from "#configuration";
import { NestFactory } from "@nestjs/core";
import { FastifyAdapter, NestFastifyApplication } from "@nestjs/platform-fastify";
import { AppModule } from "../../app.module";
import NestLogger from "#framework/logger/nestLogger";
import { EventEmitter } from "events";
import fastifySensible from "@fastify/sensible";
interface IGracefulServer {
isReady: () => boolean;
setReady: () => void;
on: (name: string, callback: (...args: any[]) => void) => EventEmitter;
}
export default class Server {
private readonly logger: Logger;
private gracefulServer: IGracefulServer | undefined;
private nest: NestFastifyApplication | undefined;
public constructor () {
this.logger = getlogger("fastify");
}
public async start (): Promise<void> {
const start = async (): Promise<void> => {
try {
const port = <number>configuration().getNumberValue("port");
const host = <string>configuration().getStringValue("host");
this.logger.info("Start server on port " + host + ":" + port);
await this.nest?.listen(port, host);
this.gracefulServer?.setReady();
} catch (err) {
this.logger.error(err);
process.exit(1);
}
};
await start();
}
public async setup (): Promise<void> {
this.nest = await NestFactory.create<NestFastifyApplication>(
AppModule,
new FastifyAdapter({
logger: fastifyLogger,
disableRequestLogging: true
}),
{
logger: new NestLogger(),
}
);
await this.nest.register(fastifySensible);
await this.nest.register(fastifyRequestLogger);
this.gracefulServer = GracefulServer(this.nest.getHttpServer());
this.gracefulServer.on(GracefulServer.READY, () => {
this.logger.info("Server is ready");
});
this.gracefulServer.on(GracefulServer.SHUTTING_DOWN, () => {
this.logger.info("Server is shutting down");
});
this.gracefulServer.on(GracefulServer.SHUTDOWN, () => {
this.logger.info("Server is down");
process.exit();
});
}
}

View File

@ -1,4 +1,4 @@
import tsconfig from '../tsconfig.json';
import tsconfig from '../tsconfig.json' assert {type: "json"};;
import * as path from 'path';
import { fileURLToPath } from 'url'
import { createRequire } from 'module'

View File

@ -1,45 +0,0 @@
import Fastify from "fastify";
import fastifySensible from "fastify-sensible";
import GracefulServer from "@gquittet/graceful-server";
import getlogger from "@logger";
import {fastifyLogger, fastifyRequestLogger} from "@logger/fastifyLogger";
const fastify = Fastify({
logger: fastifyLogger,
disableRequestLogging: true
});
await fastify.register(fastifyRequestLogger, {logBody: true});
await fastify.register(fastifySensible);
const gracefulServer = GracefulServer(fastify.server);
gracefulServer.on(GracefulServer.READY, () => {
getlogger("server").info("Server is ready");
});
gracefulServer.on(GracefulServer.SHUTTING_DOWN, () => {
getlogger("server").info("Server is shutting down");
});
gracefulServer.on(GracefulServer.SHUTDOWN, (error: {message: string}) => {
getlogger("server").info("Server is down because of", error.message);
process.exit();
});
// Declare a route
fastify.get("/", function (request, reply) {
void reply.send({ hello: "world" });
});
// Run the server!
const start = async (): Promise<void> => {
try {
const port = 3000;
await fastify.listen(port);
gracefulServer.setReady();
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
};
await start();

View File

@ -8,11 +8,30 @@
"esModuleInterop": true,
"outDir": "./dist/",
"baseUrl": "./src/",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"noImplicitAny": true,
"paths": {
"@abc/*": ["abc/*"],
"@logger": ["framework/logger/logger"],
"@logger/*": ["framework/logger/*"]
}
"#ddd": ["framework/ddd/ddd.types"],
"#framework/*": ["framework/*"],
"#logger": ["framework/logger/logger"],
"#logger/*": ["framework/logger/*"],
"#configuration": ["framework/configuration/configuration"],
"#configuration/*": ["framework/configuration/*"],
"#app": ["framework/app/app"],
"#domain/*": ["application/domain/*"]
},
"plugins": [
{
"transform": "@automapper/classes/transformer-plugin",
"modelFileNameSuffix": [".types.js", ".entity.ts", ".dto.ts"]
},
{ "transform": "typescript-rtti/dist/transformer" }
]
},
"tsc-alias": {
"verbose": true,
"resolveFullPaths": true
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules"]

1906
yarn.lock

File diff suppressed because it is too large Load Diff