Staging implementation (#9)
The creation of a single repository is no longer relevant. Therefore, we have to separate the front and back repo into two distinct repositories. This PR allows to repatriate the work on dev branch
This commit is contained in:
parent
41a8f67da2
commit
ead8f43b7f
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@ -37,5 +37,8 @@
|
|||||||
"rust-client.rustupPath": "${env:HOME}/elrondsdk/vendor-rust/bin/rustup",
|
"rust-client.rustupPath": "${env:HOME}/elrondsdk/vendor-rust/bin/rustup",
|
||||||
"rust-client.rlsPath": "${env:HOME}/elrondsdk/vendor-rust/bin/rls",
|
"rust-client.rlsPath": "${env:HOME}/elrondsdk/vendor-rust/bin/rls",
|
||||||
"rust-client.disableRustup": true,
|
"rust-client.disableRustup": true,
|
||||||
"rust-client.autoStartRls": false
|
"rust-client.autoStartRls": false,
|
||||||
|
"[prisma]": {
|
||||||
|
"editor.defaultFormatter": "Prisma.prisma"
|
||||||
|
}
|
||||||
}
|
}
|
1430
package-lock.json
generated
1430
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
|||||||
"description": "tezosLink project",
|
"description": "tezosLink project",
|
||||||
"_moduleAliases": {
|
"_moduleAliases": {
|
||||||
"@Api": "./dist/api",
|
"@Api": "./dist/api",
|
||||||
"@Front": "./dist/front/*",
|
"@Front": "./dist/front",
|
||||||
"@Assets": "./dist/front/Assets/*",
|
"@Assets": "./dist/front/Assets/*",
|
||||||
"@Components": "./dist/front/Components/*",
|
"@Components": "./dist/front/Components/*",
|
||||||
"@Themes": "./dist/front/Themes/*",
|
"@Themes": "./dist/front/Themes/*",
|
||||||
@ -41,6 +41,9 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/smart-chain-fr/tezosLink#readme",
|
"homepage": "https://github.com/smart-chain-fr/tezosLink#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.10.6",
|
||||||
|
"@emotion/styled": "^11.10.6",
|
||||||
|
"@mui/material": "^5.11.12",
|
||||||
"@prisma/client": "^4.9.0",
|
"@prisma/client": "^4.9.0",
|
||||||
"apexcharts": "^3.36.3",
|
"apexcharts": "^3.36.3",
|
||||||
"axios": "^1.3.3",
|
"axios": "^1.3.3",
|
||||||
@ -52,6 +55,7 @@
|
|||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
"module-alias": "^2.2.2",
|
"module-alias": "^2.2.2",
|
||||||
"next": "^13.1.5",
|
"next": "^13.1.5",
|
||||||
|
"node-schedule": "^2.1.1",
|
||||||
"prisma": "^4.9.0",
|
"prisma": "^4.9.0",
|
||||||
"prisma-query": "^2.0.0",
|
"prisma-query": "^2.0.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@ -71,6 +75,7 @@
|
|||||||
"@types/cors": "^2.8.13",
|
"@types/cors": "^2.8.13",
|
||||||
"@types/express": "^4.17.16",
|
"@types/express": "^4.17.16",
|
||||||
"@types/node": "^18.11.18",
|
"@types/node": "^18.11.18",
|
||||||
|
"@types/node-schedule": "^2.1.0",
|
||||||
"@types/react": "^18.0.27",
|
"@types/react": "^18.0.27",
|
||||||
"@types/react-dom": "^18.0.10",
|
"@types/react-dom": "^18.0.10",
|
||||||
"@types/uuid": "^9.0.0",
|
"@types/uuid": "^9.0.0",
|
||||||
|
@ -3,9 +3,8 @@ import { Controller, Get, Post } from "@ControllerPattern/index";
|
|||||||
import { Service } from "typedi";
|
import { Service } from "typedi";
|
||||||
import { ProjectEntity } from "@Common/ressources";
|
import { ProjectEntity } from "@Common/ressources";
|
||||||
import { IsNotEmpty, IsString, IsUUID, validateOrReject } from "class-validator";
|
import { IsNotEmpty, IsString, IsUUID, validateOrReject } from "class-validator";
|
||||||
import ProjectService from "@Services/project/ProjectService";
|
import ProjectService from "@Services/project/ProjectsService";
|
||||||
import ObjectHydrate from "@Common/helpers/ObjectHydrate";
|
import ObjectHydrate from "@Common/helpers/ObjectHydrate";
|
||||||
import { processFindManyQuery } from "prisma-query";
|
|
||||||
import ApiController from "@Common/system/controller-pattern/ApiController";
|
import ApiController from "@Common/system/controller-pattern/ApiController";
|
||||||
|
|
||||||
class Params {
|
class Params {
|
||||||
@ -24,8 +23,8 @@ export default class ProjectController extends ApiController {
|
|||||||
|
|
||||||
@Get("/projects")
|
@Get("/projects")
|
||||||
protected async get(req: Request, res: Response) {
|
protected async get(req: Request, res: Response) {
|
||||||
const query = processFindManyQuery(req.query);
|
// const query = processFindManyQuery(req.query);
|
||||||
this.httpSuccess(res, await this.projectService.getByCriterias(query));
|
// this.httpSuccess(res, await this.projectService.getByCriterias(query));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get("/projects/:uuid")
|
@Get("/projects/:uuid")
|
||||||
@ -41,10 +40,10 @@ export default class ProjectController extends ApiController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const project = await this.projectService.getByUUID(params);
|
const project = await this.projectService.getByUUID(params);
|
||||||
if (!project) {
|
// if (!project) {
|
||||||
this.httpNotFoundRequest(res);
|
// this.httpNotFoundRequest(res);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
this.httpSuccess(res, project);
|
this.httpSuccess(res, project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
85
src/common/config/variables/Variables.ts
Normal file
85
src/common/config/variables/Variables.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { Service } from "typedi";
|
||||||
|
import { validateOrReject, IsNotEmpty, IsOptional } from "class-validator";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class BackendVariables {
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly DATABASE_PORT!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly DATABASE_HOSTNAME!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly DATABASE_USER!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly DATABASE_PASSWORD!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly DATABASE_NAME!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly API_HOSTNAME!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
public readonly API_LABEL!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly API_PORT!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly API_ROOT_URL!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
public readonly RPC_GATEWAY_LABEL!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly RPC_GATEWAY_PORT!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly RPC_GATEWAY_ROOT_URL!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly ARCHIVE_NODES_URL!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly ARCHIVE_NODES_PORT!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly ROLLING_NODES_URL!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly ROLLING_NODES_PORT!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
public readonly TEZOS_NETWORK!: string;
|
||||||
|
|
||||||
|
public readonly NODE_ENV = process.env.NODE_ENV;
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
dotenv.config();
|
||||||
|
this.DATABASE_PORT = process.env["DATABASE_PORT"]!;
|
||||||
|
this.DATABASE_HOSTNAME = process.env["DATABASE_HOSTNAME"]!;
|
||||||
|
this.DATABASE_USER = process.env["DATABASE_USER"]!;
|
||||||
|
this.DATABASE_PASSWORD = process.env["DATABASE_PASSWORD"]!;
|
||||||
|
this.DATABASE_NAME = process.env["DATABASE_NAME"]!;
|
||||||
|
this.API_HOSTNAME = process.env["API_HOSTNAME"]!;
|
||||||
|
this.API_LABEL = process.env["API_LABEL"]!;
|
||||||
|
this.API_PORT = process.env["API_PORT"]!;
|
||||||
|
this.API_ROOT_URL = process.env["API_ROOT_URL"]!;
|
||||||
|
this.RPC_GATEWAY_LABEL = process.env["RPC_GATEWAY_LABEL"]!;
|
||||||
|
this.RPC_GATEWAY_PORT = process.env["RPC_GATEWAY_PORT"]!;
|
||||||
|
this.RPC_GATEWAY_ROOT_URL = process.env["RPC_GATEWAY_ROOT_URL"]!;
|
||||||
|
this.ARCHIVE_NODES_URL = process.env["ARCHIVE_NODES_URL"]!;
|
||||||
|
this.ARCHIVE_NODES_PORT = process.env["ARCHIVE_NODES_PORT"]!;
|
||||||
|
this.ROLLING_NODES_URL = process.env["ROLLING_NODES_URL"]!;
|
||||||
|
this.ROLLING_NODES_PORT = process.env["ROLLING_NODES_PORT"]!;
|
||||||
|
this.TEZOS_NETWORK = process.env["TEZOS_NETWORK"]!;
|
||||||
|
}
|
||||||
|
public async validate() {
|
||||||
|
await validateOrReject(this);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
67
src/common/cron/Cron.ts
Normal file
67
src/common/cron/Cron.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { Service } from "typedi";
|
||||||
|
import IConfig from "./IConfig";
|
||||||
|
import schedule from "node-schedule";
|
||||||
|
import FunctionBinder, { IFunctionBinder } from "@Common/helpers/FunctionBinder";
|
||||||
|
|
||||||
|
type ICronTimer = {
|
||||||
|
second?: string | "*" | null;
|
||||||
|
minute?: string | "*" | null;
|
||||||
|
hour?: string | "*" | null;
|
||||||
|
dayMonth?: string | "*" | null;
|
||||||
|
month?: string | "*" | null;
|
||||||
|
dayWeek?: string | "*" | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class Cron {
|
||||||
|
private readonly cronJobs: schedule.Job[] = [];
|
||||||
|
private static readonly runningJobs: boolean[] = [];
|
||||||
|
public async run(CronConfig: IConfig) {
|
||||||
|
this.cronJobs.splice(0);
|
||||||
|
this.cancel();
|
||||||
|
this.cronJobs.push(...CronConfig.jobs.filter((job) => job.enabled).map((job, index) => this.buildCronJob(job, index, CronConfig.binders)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public cancel() {
|
||||||
|
this.cronJobs.forEach((job) => job.cancel());
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createTimer(cronTimer: ICronTimer) {
|
||||||
|
return [cronTimer.second ?? "*", cronTimer.minute ?? "*", cronTimer.hour ?? "*", cronTimer.dayMonth ?? "*", cronTimer.month ?? "*", cronTimer.dayWeek ?? "*"].join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildCronJob(job: IConfig["jobs"][number], index: number, binders: IFunctionBinder[]) {
|
||||||
|
return schedule.scheduleJob(job.cronTime, () => Cron.scheduleJob(job, job.onTick, index, binders));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Prevent same jobs superposition
|
||||||
|
*/
|
||||||
|
private static async scheduleJob(jobConfig: IConfig["jobs"][number], cronCommand: () => Promise<any>, index: number, binders: IFunctionBinder[]) {
|
||||||
|
if (Cron.runningJobs[index]) return;
|
||||||
|
Cron.runningJobs[index] = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.info(`${Cron.getDate()} CronJob: ${jobConfig.cronTime} ${jobConfig.name}: started`);
|
||||||
|
|
||||||
|
if (binders.length) {
|
||||||
|
await FunctionBinder.bind(cronCommand, binders);
|
||||||
|
} else {
|
||||||
|
await cronCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.info(`${Cron.getDate()} CronJob: ${jobConfig.name}: end success`);
|
||||||
|
} catch (e) {
|
||||||
|
console.info(`${Cron.getDate()} CronJob: ${jobConfig.name}: end with error`);
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
Cron.runningJobs[index] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getDate() {
|
||||||
|
const d = new Date();
|
||||||
|
return `${d.getDate()}/${d.getMonth() + 1}/${d.getFullYear()} ${d.getHours()}:${d.getMinutes()}:${d.getSeconds()}`;
|
||||||
|
}
|
||||||
|
}
|
12
src/common/cron/IConfig.ts
Normal file
12
src/common/cron/IConfig.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { IFunctionBinder } from "@Common/helpers/FunctionBinder";
|
||||||
|
|
||||||
|
export default interface IConfig {
|
||||||
|
binders: IFunctionBinder[];
|
||||||
|
jobs: {
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
cronTime: string | Date;
|
||||||
|
onTick: () => Promise<any>;
|
||||||
|
enabled?: boolean;
|
||||||
|
}[];
|
||||||
|
}
|
4
src/common/cron/index.ts
Normal file
4
src/common/cron/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
import IConfig from "@Common/cron/IConfig";
|
||||||
|
import Cron from "@Common/cron/Cron";
|
||||||
|
export type { IConfig };
|
||||||
|
export default Cron;
|
@ -1,13 +1,14 @@
|
|||||||
import { Service } from "typedi";
|
import { Service } from "typedi";
|
||||||
import DbProvider from "@Common/system/database";
|
import DbProvider from "@Common/system/database";
|
||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
|
import { BackendVariables } from "@Common/config/variables/Variables";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@Service()
|
@Service()
|
||||||
export default class Database {
|
export default class Database {
|
||||||
protected readonly dbProvider: DbProvider;
|
protected readonly dbProvider: DbProvider;
|
||||||
constructor() {
|
constructor(private variables: BackendVariables) {
|
||||||
this.dbProvider = new DbProvider({
|
this.dbProvider = new DbProvider({
|
||||||
name: this.getDatabaseName(),
|
name: this.getDatabaseName(),
|
||||||
});
|
});
|
||||||
@ -21,7 +22,7 @@ export default class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getDatabaseName(): string {
|
private getDatabaseName(): string {
|
||||||
const name = process.env["DATABASE_NAME"];
|
const name = this.variables.DATABASE_NAME;
|
||||||
if (!name) throw new Error("Database name is undefined!. Add name of db in the url.");
|
if (!name) throw new Error("Database name is undefined!. Add name of db in the url.");
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
-- -- CreateTable
|
-- CreateTable
|
||||||
-- CREATE TABLE "app_projects" (
|
CREATE TABLE "app_projects" (
|
||||||
-- "id" SERIAL NOT NULL,
|
"id" SERIAL NOT NULL,
|
||||||
-- "title" VARCHAR(255) NOT NULL,
|
"title" VARCHAR(255) NOT NULL,
|
||||||
-- "uuid" VARCHAR(255) NOT NULL,
|
"uuid" VARCHAR(255) NOT NULL,
|
||||||
-- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
-- "updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
-- "network" VARCHAR(255) NOT NULL,
|
"network" VARCHAR(255) NOT NULL,
|
||||||
|
|
||||||
-- CONSTRAINT "app_projects_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "app_projects_pkey" PRIMARY KEY ("id")
|
||||||
-- );
|
);
|
||||||
|
|
||||||
-- -- CreateTable
|
-- CreateTable
|
||||||
-- CREATE TABLE "app_metrics" (
|
CREATE TABLE "app_metrics" (
|
||||||
-- "id" SERIAL NOT NULL,
|
"id" SERIAL NOT NULL,
|
||||||
-- "path" VARCHAR(255) NOT NULL,
|
"path" VARCHAR(255) NOT NULL,
|
||||||
-- "uuid" VARCHAR(255) NOT NULL,
|
"uuid" VARCHAR(255) NOT NULL,
|
||||||
-- "remote_address" VARCHAR(255) NOT NULL,
|
"remote_address" VARCHAR(255) NOT NULL,
|
||||||
-- "date_requested" TIMESTAMP(3) NOT NULL,
|
"date_requested" TIMESTAMP(3) NOT NULL,
|
||||||
-- "projectId" INTEGER NOT NULL,
|
"projectId" INTEGER NOT NULL,
|
||||||
-- "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
-- "updatedAt" TIMESTAMP(3) NOT NULL,
|
"updatedAt" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
-- CONSTRAINT "app_metrics_pkey" PRIMARY KEY ("id")
|
CONSTRAINT "app_metrics_pkey" PRIMARY KEY ("id")
|
||||||
-- );
|
);
|
||||||
|
|
||||||
-- -- CreateIndex
|
-- CreateIndex
|
||||||
-- CREATE UNIQUE INDEX "app_projects_uuid_key" ON "app_projects"("uuid");
|
CREATE UNIQUE INDEX "app_projects_uuid_key" ON "app_projects"("uuid");
|
||||||
|
|
||||||
-- -- CreateIndex
|
-- CreateIndex
|
||||||
-- CREATE UNIQUE INDEX "app_metrics_uuid_key" ON "app_metrics"("uuid");
|
CREATE UNIQUE INDEX "app_metrics_uuid_key" ON "app_metrics"("uuid");
|
||||||
|
|
||||||
-- -- AddForeignKey
|
-- AddForeignKey
|
||||||
-- ALTER TABLE "app_metrics" ADD CONSTRAINT "app_metrics_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "app_projects"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
ALTER TABLE "app_metrics" ADD CONSTRAINT "app_metrics_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "app_projects"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
-- DropIndex
|
||||||
|
DROP INDEX "app_metrics_uuid_key";
|
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "app_metrics" ALTER COLUMN "date_requested" SET DATA TYPE DATE;
|
@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- The primary key for the `app_metrics` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- You are about to drop the column `id` on the `app_metrics` table. All the data in the column will be lost.
|
||||||
|
- You are about to drop the column `projectId` on the `app_metrics` table. All the data in the column will be lost.
|
||||||
|
- The primary key for the `app_projects` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- You are about to drop the column `id` on the `app_projects` table. All the data in the column will be lost.
|
||||||
|
- A unique constraint covering the columns `[uuid]` on the table `app_metrics` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
- Added the required column `projectUuid` to the `app_metrics` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- DropForeignKey
|
||||||
|
ALTER TABLE "app_metrics" DROP CONSTRAINT "app_metrics_projectId_fkey";
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "app_metrics" DROP CONSTRAINT "app_metrics_pkey",
|
||||||
|
DROP COLUMN "id",
|
||||||
|
DROP COLUMN "projectId",
|
||||||
|
ADD COLUMN "projectUuid" TEXT NOT NULL,
|
||||||
|
ADD CONSTRAINT "app_metrics_pkey" PRIMARY KEY ("uuid");
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "app_projects" DROP CONSTRAINT "app_projects_pkey",
|
||||||
|
DROP COLUMN "id",
|
||||||
|
ADD CONSTRAINT "app_projects_pkey" PRIMARY KEY ("uuid");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "app_metrics_uuid_key" ON "app_metrics"("uuid");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "app_metrics" ADD CONSTRAINT "app_metrics_projectUuid_fkey" FOREIGN KEY ("projectUuid") REFERENCES "app_projects"("uuid") ON DELETE RESTRICT ON UPDATE CASCADE;
|
42
src/common/helpers/FunctionBinder.ts
Normal file
42
src/common/helpers/FunctionBinder.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
export type IFunctionBinder = (next?: IFunctionBinder) => Promise<void>;
|
||||||
|
/**
|
||||||
|
* @description execute functions on body of another function
|
||||||
|
* @example `
|
||||||
|
* const binders = [fooA, fooB, fooC];
|
||||||
|
*
|
||||||
|
* //Will be something like
|
||||||
|
* function fooA(fooB, fooC, next) {
|
||||||
|
* //code...
|
||||||
|
* fooB(fooC, next);
|
||||||
|
* //code...
|
||||||
|
* }
|
||||||
|
* function fooB(fooC, next) {
|
||||||
|
* //code...
|
||||||
|
* fooC(next);
|
||||||
|
* //code...
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* function fooC(next) {
|
||||||
|
* //code...
|
||||||
|
* next();
|
||||||
|
* //code...
|
||||||
|
* }
|
||||||
|
* `
|
||||||
|
* fooC will be executed in body of fooB and fooB in body of fooA
|
||||||
|
*/
|
||||||
|
export default abstract class FunctionBinder {
|
||||||
|
public static async bind(next: () => Promise<any>, binders: IFunctionBinder[]) {
|
||||||
|
let currentBinder = async function () {
|
||||||
|
await next();
|
||||||
|
};
|
||||||
|
|
||||||
|
let index = binders.length;
|
||||||
|
while (index--) {
|
||||||
|
const localIndex = index;
|
||||||
|
const nextBinder = currentBinder;
|
||||||
|
currentBinder = async () => binders[localIndex]!(nextBinder);
|
||||||
|
}
|
||||||
|
|
||||||
|
await currentBinder();
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,13 @@
|
|||||||
import { plainToClassFromExist } from "class-transformer";
|
import { type ClassTransformOptions, plainToClass, plainToClassFromExist } from "class-transformer";
|
||||||
|
|
||||||
export default abstract class ObjectHydrate {
|
export default abstract class ObjectHydrate {
|
||||||
public static hydrate<T = { [key: string]: any }>(object: { [key: string]: any }, from: { [key: string]: any }): T {
|
public static hydrate<T = {}>(object: T, from: Partial<T>, options?: ClassTransformOptions): T {
|
||||||
return plainToClassFromExist(object, from) as T;
|
return plainToClassFromExist(object, from, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static map<T = {}>(ClassEntity: { new (): T }, fromArray: Partial<T>[], options?: ClassTransformOptions): T[] {
|
||||||
|
return fromArray.map((from) => {
|
||||||
|
return plainToClass(ClassEntity, from, options);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
src/common/helpers/ProxyHelpers.ts
Normal file
13
src/common/helpers/ProxyHelpers.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export function getUUIDFromPath(path: string, re: RegExp): string {
|
||||||
|
let uuid = "";
|
||||||
|
const matches = path.match(re);
|
||||||
|
if (matches !== null) {
|
||||||
|
uuid = matches[matches.length - 1]!;
|
||||||
|
}
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRPCFromPath(basePath: string, path: string, re: RegExp): string {
|
||||||
|
return path.replace("/" + basePath + getUUIDFromPath(path, re), "");
|
||||||
|
}
|
||||||
|
|
4
src/common/repositories/BaseRepository.ts
Normal file
4
src/common/repositories/BaseRepository.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export default abstract class BaseRepository {
|
||||||
|
protected readonly maxFetchRows = 100;
|
||||||
|
protected readonly defaultFetchRows = 50;
|
||||||
|
}
|
@ -1,190 +0,0 @@
|
|||||||
import TezosLink from "@Common/databases/TezosLink";
|
|
||||||
import { MetricEntity } from "@Common/ressources";
|
|
||||||
import { ORMBadQueryError } from "@Common/system/database/exceptions/ORMBadQueryError";
|
|
||||||
import { type Prisma } from "@prisma/client";
|
|
||||||
import { Service } from "typedi";
|
|
||||||
|
|
||||||
type RequestsByDayMetrics = {
|
|
||||||
date_requested: Date;
|
|
||||||
count: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export default class MetricRepository {
|
|
||||||
constructor(private database: TezosLink) {}
|
|
||||||
protected get model() {
|
|
||||||
return this.database.getClient().metric;
|
|
||||||
}
|
|
||||||
protected get instanceDb() {
|
|
||||||
return this.database.getClient();
|
|
||||||
}
|
|
||||||
|
|
||||||
public async findMany(query: any): Promise<MetricEntity[]> {
|
|
||||||
try {
|
|
||||||
return this.model.findMany(query) as Promise<MetricEntity[]>;
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async findOne(metricEntity: Partial<MetricEntity>): Promise<Partial<MetricEntity> | null> {
|
|
||||||
try {
|
|
||||||
const data = { ...metricEntity };
|
|
||||||
return this.model.findUnique({ where: data });
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async create(metricEntity: Partial<MetricEntity>): Promise<MetricEntity> {
|
|
||||||
try {
|
|
||||||
const data = { ...metricEntity };
|
|
||||||
|
|
||||||
return this.model.create({
|
|
||||||
data: {
|
|
||||||
path: data.path!,
|
|
||||||
uuid: data.uuid!,
|
|
||||||
remote_address: data.remote_address!,
|
|
||||||
date_requested: data.date_requested!,
|
|
||||||
project: {
|
|
||||||
connect: {
|
|
||||||
id: data.id!,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}) as Promise<MetricEntity>;
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create many metrics in bulk
|
|
||||||
public async createMany(metricEntity: Partial<MetricEntity[]>): Promise<MetricEntity[]> {
|
|
||||||
try {
|
|
||||||
const data = { ...metricEntity };
|
|
||||||
const result: MetricEntity[] = [];
|
|
||||||
|
|
||||||
this.instanceDb.$transaction(async(transaction: Prisma.TransactionClient) => {
|
|
||||||
for (const item of data) {
|
|
||||||
if (!item) continue;
|
|
||||||
result.push(
|
|
||||||
await transaction.metric.create({
|
|
||||||
data: {
|
|
||||||
path: item.path!,
|
|
||||||
uuid: item.uuid!,
|
|
||||||
remote_address: item.remote_address!,
|
|
||||||
date_requested: item.date_requested!,
|
|
||||||
project: {
|
|
||||||
connect: {
|
|
||||||
id: item.id!,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count Rpc path usage for a specific project
|
|
||||||
public async countRpcPathUsage(projectId: number, from: Date, to: Date): Promise<any> {
|
|
||||||
try {
|
|
||||||
return this.model.groupBy({
|
|
||||||
by: ["path"],
|
|
||||||
_count: {
|
|
||||||
path: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
projectId: projectId,
|
|
||||||
date_requested: {
|
|
||||||
gte: from,
|
|
||||||
lte: to,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Last requests for a specific project
|
|
||||||
public async findLastRequests(projectId: number, limit: number): Promise<MetricEntity[]> {
|
|
||||||
try {
|
|
||||||
return this.model.findMany({
|
|
||||||
where: {
|
|
||||||
projectId: projectId,
|
|
||||||
},
|
|
||||||
take: limit,
|
|
||||||
orderBy: {
|
|
||||||
date_requested: "desc",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find Requests by Day for a specific project
|
|
||||||
public async findRequestsByDay(projectId: number, from: Date, to: Date): Promise<RequestsByDayMetrics[]> {
|
|
||||||
try {
|
|
||||||
const result: RequestsByDayMetrics[] = [];
|
|
||||||
const response = this.model.groupBy({
|
|
||||||
by: ["date_requested"],
|
|
||||||
_count: {
|
|
||||||
date_requested: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
projectId: projectId,
|
|
||||||
date_requested: {
|
|
||||||
gte: from,
|
|
||||||
lte: to,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
for (const item of response as Array<{ date_requested: Date; _count: { date_requested: number } }> | any) {
|
|
||||||
result.push({
|
|
||||||
date_requested: item.date_requested,
|
|
||||||
count: item._count.date_requested,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Count all metrics by criterias for a specific project
|
|
||||||
public async countAll(projectId: number): Promise<number> {
|
|
||||||
try {
|
|
||||||
return this.model.count({
|
|
||||||
where: {
|
|
||||||
projectId: projectId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove Three months old metrics
|
|
||||||
public async removeOldMetricsBymonths(months: number): Promise<void> {
|
|
||||||
try {
|
|
||||||
const date = new Date();
|
|
||||||
date.setMonth(date.getMonth() - months);
|
|
||||||
this.model.deleteMany({
|
|
||||||
where: {
|
|
||||||
date_requested: {
|
|
||||||
lte: date,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
|||||||
import TezosLink from "@Common/databases/TezosLink";
|
|
||||||
import { ProjectEntity } from "@Common/ressources";
|
|
||||||
import { ORMBadQueryError } from "@Common/system/database/exceptions/ORMBadQueryError";
|
|
||||||
import { Service } from "typedi";
|
|
||||||
import { v4 as uuidv4 } from "uuid";
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export default class ProjectRepository {
|
|
||||||
constructor(private database: TezosLink) {}
|
|
||||||
protected get model() {
|
|
||||||
return this.database.getClient().project;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async findMany(query: any): Promise<ProjectEntity[]> {
|
|
||||||
try {
|
|
||||||
return this.model.findMany(query) as Promise<ProjectEntity[]>;
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async findOne(projectEntity: Partial<ProjectEntity>): Promise<Partial<ProjectEntity> | null> {
|
|
||||||
try {
|
|
||||||
const data = { ...projectEntity };
|
|
||||||
return this.model.findUnique({
|
|
||||||
where: data,
|
|
||||||
|
|
||||||
include: {
|
|
||||||
// Include metrics & count
|
|
||||||
Metrics: true,
|
|
||||||
_count: {
|
|
||||||
select: { Metrics: true },
|
|
||||||
},
|
|
||||||
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async create(projectEntity: Partial<ProjectEntity>): Promise<ProjectEntity> {
|
|
||||||
try {
|
|
||||||
const data = { ...projectEntity };
|
|
||||||
data.uuid = uuidv4();
|
|
||||||
return this.model.create({
|
|
||||||
data: {
|
|
||||||
uuid: data.uuid,
|
|
||||||
title: data.title!,
|
|
||||||
network: data.network!,
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
// Include metrics
|
|
||||||
Metrics: true,
|
|
||||||
},
|
|
||||||
}) as Promise<ProjectEntity>;
|
|
||||||
} catch (error) {
|
|
||||||
throw new ORMBadQueryError((error as Error).message, error as Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
220
src/common/repositories/metrics/MetricsRepository.ts
Normal file
220
src/common/repositories/metrics/MetricsRepository.ts
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
// import TezosLink from "@Common/databases/TezosLink";
|
||||||
|
// import ObjectHydrate from "@Common/helpers/ObjectHydrate";
|
||||||
|
// import { MetricEntity } from "@Common/ressources";
|
||||||
|
// import { ORMBadQueryError } from "@Common/system/database/exceptions/ORMBadQueryError";
|
||||||
|
// import { type Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
import { Service } from "typedi";
|
||||||
|
// import { v4 as uuidv4 } from "uuid";
|
||||||
|
import BaseRepository from "../BaseRepository";
|
||||||
|
|
||||||
|
export class RequestsByDayMetrics {
|
||||||
|
date_requested!: Date;
|
||||||
|
count!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CountRpcPathUsage {
|
||||||
|
path!: string;
|
||||||
|
count!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class MetricsRepository extends BaseRepository {
|
||||||
|
// constructor(private database: TezosLink) {
|
||||||
|
// super();
|
||||||
|
// }
|
||||||
|
// protected get model() {
|
||||||
|
// return this.database.getClient().metric;
|
||||||
|
// }
|
||||||
|
// protected get instanceDb() {
|
||||||
|
// return this.database.getClient();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public async findMany(query: Prisma.MetricFindManyArgs): Promise<MetricEntity[]> {
|
||||||
|
// try {
|
||||||
|
// // Use Math.min to limit the number of rows fetched
|
||||||
|
// const limit = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
|
||||||
|
|
||||||
|
// // Update the query with the limited limit
|
||||||
|
// const metrics = await this.model.findMany({ ...query, take: limit });
|
||||||
|
// return ObjectHydrate.map<MetricEntity>(MetricEntity, metrics, { strategy: "exposeAll" });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public async findOne(metricEntity: Partial<MetricEntity>): Promise<Partial<MetricEntity> | null> {
|
||||||
|
// try {
|
||||||
|
// const metric = await this.model.findUnique({ where: metricEntity });
|
||||||
|
// return ObjectHydrate.hydrate<MetricEntity>(new MetricEntity(), metric, { strategy: "exposeAll" });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public async create(metricEntity: Partial<MetricEntity>): Promise<MetricEntity> {
|
||||||
|
// try {
|
||||||
|
// const data = { ...metricEntity };
|
||||||
|
// data.uuid = uuidv4();
|
||||||
|
// const metric = (await this.model.create({
|
||||||
|
// data: {
|
||||||
|
// path: data.path!,
|
||||||
|
// uuid: data.uuid!,
|
||||||
|
// remote_address: data.remote_address!,
|
||||||
|
// date_requested: data.date_requested!,
|
||||||
|
// project: {
|
||||||
|
// connect: {
|
||||||
|
// uuid: data.project!.uuid!,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// })) as MetricEntity;
|
||||||
|
// return ObjectHydrate.hydrate<MetricEntity>(new MetricEntity(), metric, { strategy: "exposeAll" });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Create many metrics in bulk
|
||||||
|
// public async createMany(metricEntity: Partial<MetricEntity[]>): Promise<MetricEntity[]> {
|
||||||
|
// try {
|
||||||
|
// const result: MetricEntity[] = [];
|
||||||
|
|
||||||
|
// this.instanceDb.$transaction(async (transaction: Prisma.TransactionClient) => {
|
||||||
|
// for (const item of metricEntity) {
|
||||||
|
// if (!item) continue;
|
||||||
|
// const data = { ...item };
|
||||||
|
// data.uuid = uuidv4();
|
||||||
|
// result.push(
|
||||||
|
// await transaction.metric.create({
|
||||||
|
// data: {
|
||||||
|
// path: data.path!,
|
||||||
|
// uuid: data.uuid!,
|
||||||
|
// remote_address: data.remote_address!,
|
||||||
|
// date_requested: data.date_requested!,
|
||||||
|
// project: {
|
||||||
|
// connect: {
|
||||||
|
// uuid: data.projectUuid!,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// }),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// return ObjectHydrate.map<MetricEntity>(MetricEntity, result, { strategy: "exposeAll" });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Count Rpc path usage for a specific project
|
||||||
|
// public async countRpcPathUsage(ProjectUuid: string, from: Date, to: Date): Promise<CountRpcPathUsage[]> {
|
||||||
|
// try {
|
||||||
|
// const result: CountRpcPathUsage[] = [];
|
||||||
|
// const response = await this.model.groupBy({
|
||||||
|
// by: ["path"],
|
||||||
|
// _count: {
|
||||||
|
// path: true,
|
||||||
|
// },
|
||||||
|
// where: {
|
||||||
|
// projectUuid: ProjectUuid,
|
||||||
|
// date_requested: {
|
||||||
|
// gte: from,
|
||||||
|
// lte: to,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// response.forEach((item) => {
|
||||||
|
// result.push({
|
||||||
|
// path: item.path,
|
||||||
|
// count: item._count.path,
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// return ObjectHydrate.map<CountRpcPathUsage>(CountRpcPathUsage, response, { strategy: "exposeAll" });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Last requests for a specific project
|
||||||
|
// public async findLastRequests(projectUuid: string, limit: number): Promise<MetricEntity[]> {
|
||||||
|
// try {
|
||||||
|
// // Use Math.min to limit the number of rows fetched
|
||||||
|
// const rows = Math.min(limit || this.defaultFetchRows, this.maxFetchRows);
|
||||||
|
// const metrics = await this.model.findMany({
|
||||||
|
// where: {
|
||||||
|
// projectUuid: projectUuid,
|
||||||
|
// },
|
||||||
|
// take: rows,
|
||||||
|
// orderBy: {
|
||||||
|
// date_requested: "desc",
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// return ObjectHydrate.map<MetricEntity>(MetricEntity, metrics, { strategy: "exposeAll" });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Find Requests by Day for a specific project
|
||||||
|
// public async findRequestsByDay(projectUuid: string, from: Date, to: Date): Promise<RequestsByDayMetrics[]> {
|
||||||
|
// try {
|
||||||
|
// const result: RequestsByDayMetrics[] = [];
|
||||||
|
// const response = await this.model.groupBy({
|
||||||
|
// by: ["date_requested"],
|
||||||
|
// _count: {
|
||||||
|
// date_requested: true,
|
||||||
|
// },
|
||||||
|
// where: {
|
||||||
|
// projectUuid: projectUuid,
|
||||||
|
// date_requested: {
|
||||||
|
// gte: from,
|
||||||
|
// lte: to,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// response.forEach((item) => {
|
||||||
|
// result.push({
|
||||||
|
// date_requested: item.date_requested,
|
||||||
|
// count: item._count.date_requested,
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// return ObjectHydrate.map<RequestsByDayMetrics>(RequestsByDayMetrics, result, { strategy: "exposeAll" });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Count all metrics by criterias for a specific project
|
||||||
|
// public async countAll(projectUuid: string): Promise<number> {
|
||||||
|
// try {
|
||||||
|
// return this.model.count({
|
||||||
|
// where: {
|
||||||
|
// projectUuid: projectUuid,
|
||||||
|
// },
|
||||||
|
// }) as Promise<number>;
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Remove Three months old metrics
|
||||||
|
// public async removeOldMetricsBymonths(months: number): Promise<void> {
|
||||||
|
// try {
|
||||||
|
// const date = new Date();
|
||||||
|
// date.setMonth(date.getMonth() - months);
|
||||||
|
// this.model.deleteMany({
|
||||||
|
// where: {
|
||||||
|
// date_requested: {
|
||||||
|
// lte: date,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
63
src/common/repositories/projects/ProjectsRepository.ts
Normal file
63
src/common/repositories/projects/ProjectsRepository.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// import TezosLink from "@Common/databases/TezosLink";
|
||||||
|
// import ObjectHydrate from "@Common/helpers/ObjectHydrate";
|
||||||
|
import { ProjectEntity } from "@Common/ressources";
|
||||||
|
// import { ORMBadQueryError } from "@Common/system/database/exceptions/ORMBadQueryError";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
// import { v4 as uuidv4 } from "uuid";
|
||||||
|
import BaseRepository from "../BaseRepository";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class ProjectsRepository extends BaseRepository {
|
||||||
|
// constructor(private database: TezosLink) {
|
||||||
|
// super();
|
||||||
|
// }
|
||||||
|
// protected get model() {
|
||||||
|
// return this.database.getClient().project;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// public async findMany(query: Prisma.ProjectFindManyArgs): Promise<ProjectEntity[]> {
|
||||||
|
// try {
|
||||||
|
// // Use Math.min to limit the number of rows fetched
|
||||||
|
// const limit = Math.min(query.take || this.defaultFetchRows, this.maxFetchRows);
|
||||||
|
|
||||||
|
// // Update the query with the limited limit
|
||||||
|
// const projects = await this.model.findMany({ ...query, take: limit });
|
||||||
|
// return ObjectHydrate.map<ProjectEntity>(ProjectEntity, projects, { strategy: "exposeAll" });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
public async findOne(projectEntity: Partial<ProjectEntity>) {
|
||||||
|
// try {
|
||||||
|
// const project = (await this.model.findFirst({
|
||||||
|
// where: projectEntity,
|
||||||
|
// })) as ProjectEntity;
|
||||||
|
// return ObjectHydrate.hydrate<ProjectEntity>(new ProjectEntity(), project, { strategy: "exposeAll" });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create(projectEntity: Partial<ProjectEntity>) {
|
||||||
|
// try {
|
||||||
|
// const data = { ...projectEntity };
|
||||||
|
// data.uuid = uuidv4();
|
||||||
|
// const project = (await this.model.create({
|
||||||
|
// data: {
|
||||||
|
// uuid: data.uuid,
|
||||||
|
// title: data.title!,
|
||||||
|
// network: data.network!,
|
||||||
|
// },
|
||||||
|
// include: {
|
||||||
|
// // Include metrics
|
||||||
|
// Metrics: true,
|
||||||
|
// },
|
||||||
|
// })) as ProjectEntity;
|
||||||
|
// return ObjectHydrate.hydrate<ProjectEntity>(new ProjectEntity(), project, { strategy: "exposeAll" });
|
||||||
|
// } catch (error) {
|
||||||
|
// throw new ORMBadQueryError((error as Error).message, error as Error);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,31 +1,36 @@
|
|||||||
import { IsNotEmpty, IsDate } from "class-validator";
|
|
||||||
import ProjectEntity from "./ProjectEntity";
|
import ProjectEntity from "./ProjectEntity";
|
||||||
|
|
||||||
export default class MetricEntity {
|
export default class MetricEntity {
|
||||||
@IsNotEmpty()
|
|
||||||
public id!: number;
|
|
||||||
|
|
||||||
@IsNotEmpty(({groups: ["create"]}))
|
|
||||||
public path!: string;
|
|
||||||
|
|
||||||
@IsNotEmpty(({groups: ["create"]}))
|
|
||||||
public uuid!: string;
|
public uuid!: string;
|
||||||
|
|
||||||
@IsNotEmpty(({groups: ["create"]}))
|
public path!: string;
|
||||||
|
|
||||||
public remote_address!: string;
|
public remote_address!: string;
|
||||||
|
|
||||||
@IsNotEmpty(({groups: ["create"]}))
|
|
||||||
public date_requested!: Date;
|
public date_requested!: Date;
|
||||||
|
|
||||||
@IsNotEmpty(({groups: ["create"]}))
|
public projectUuid!: string;
|
||||||
public projectId!: number;
|
|
||||||
|
|
||||||
@IsNotEmpty(({groups: ["create"]}))
|
|
||||||
public project!: ProjectEntity;
|
public project!: ProjectEntity;
|
||||||
|
|
||||||
@IsDate()
|
|
||||||
public createdAt?: Date;
|
public createdAt?: Date;
|
||||||
|
|
||||||
@IsDate()
|
|
||||||
public updatedAt?: Date;
|
public updatedAt?: Date;
|
||||||
|
|
||||||
|
set remoteAddress(remote_address: string) {
|
||||||
|
this.remote_address = remote_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
get remoteAddress() {
|
||||||
|
return this.remote_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
set dateRequested(date_requested: Date) {
|
||||||
|
this.date_requested = date_requested;
|
||||||
|
}
|
||||||
|
|
||||||
|
get dateRequested() {
|
||||||
|
return this.date_requested;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,25 +1,19 @@
|
|||||||
import { IsNotEmpty, IsOptional, IsDate } from "class-validator";
|
import { IsNotEmpty } from "class-validator";
|
||||||
import MetricEntity from "./MetricEntity";
|
import MetricEntity from "./MetricEntity";
|
||||||
|
|
||||||
export default class ProjectEntity {
|
export default class ProjectEntity {
|
||||||
@IsNotEmpty()
|
|
||||||
public id!: number;
|
|
||||||
|
|
||||||
@IsNotEmpty({groups: ["create"]})
|
|
||||||
public title!: string;
|
|
||||||
|
|
||||||
@IsNotEmpty()
|
|
||||||
public uuid!: string;
|
public uuid!: string;
|
||||||
|
|
||||||
@IsDate()
|
@IsNotEmpty({ groups: ["create"] })
|
||||||
|
public title!: string;
|
||||||
|
|
||||||
public createdAt!: Date;
|
public createdAt!: Date;
|
||||||
|
|
||||||
@IsDate()
|
|
||||||
public updatedAt!: Date;
|
public updatedAt!: Date;
|
||||||
|
|
||||||
@IsNotEmpty({groups: ["create"]})
|
@IsNotEmpty({ groups: ["create"] })
|
||||||
public network!: string;
|
public network!: string;
|
||||||
|
|
||||||
@IsOptional()
|
|
||||||
public metrics?: MetricEntity[];
|
public metrics?: MetricEntity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,2 +1,11 @@
|
|||||||
export default class BaseService {}
|
import { BackendVariables } from "@Common/config/variables/Variables";
|
||||||
|
import Container from "typedi";
|
||||||
|
|
||||||
|
export default abstract class BaseService {
|
||||||
|
/** @TODO place methods in a config file */
|
||||||
|
public static readonly whitelisted: string[] = ["/chains/main/blocks"];
|
||||||
|
public static readonly blacklisted: string[] = ["/context/contracts", "/monitor", "/network"];
|
||||||
|
public static readonly rollingPatterns: string[] = ["/head", "/injection/operation"];
|
||||||
|
public static readonly network: string = Container.get(BackendVariables).TEZOS_NETWORK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -1,88 +0,0 @@
|
|||||||
import ObjectHydrate from "@Common/helpers/ObjectHydrate";
|
|
||||||
import MetricRepository from "@Common/repositories/MetricsRepository";
|
|
||||||
import { MetricEntity } from "@Common/ressources";
|
|
||||||
import { type processFindManyQuery } from "prisma-query";
|
|
||||||
import { Service } from "typedi";
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export default class MetricService {
|
|
||||||
constructor(private metricRepository: MetricRepository) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws {Error} If metrics are undefined
|
|
||||||
*/
|
|
||||||
public async getByCriterias(query: ReturnType<typeof processFindManyQuery>) {
|
|
||||||
const metrics = await this.metricRepository.findMany(query);
|
|
||||||
return ObjectHydrate.hydrate<Partial<MetricEntity>>(new MetricEntity(), metrics);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @throws {Error} If metric is undefined
|
|
||||||
*/
|
|
||||||
public async getByUUID(metricEntity: Partial<MetricEntity>) {
|
|
||||||
const metric = await this.metricRepository.findOne(metricEntity);
|
|
||||||
if (!metric) return null;
|
|
||||||
return ObjectHydrate.hydrate<Partial<MetricEntity>>(new MetricEntity(), metric);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @throws {Error} If metric cannot be created
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public async create(metricEntity: Partial<MetricEntity>) {
|
|
||||||
const metric = await this.metricRepository.create(metricEntity);
|
|
||||||
if (!metric) return null;
|
|
||||||
return ObjectHydrate.hydrate<Partial<MetricEntity>>(new MetricEntity(), metric);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @throws {Error} If metric is undefined
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public async getCountRpcPath(metricEntity: Partial<MetricEntity>, from: Date, to: Date) {
|
|
||||||
const pathsCount = await this.metricRepository.countRpcPathUsage(metricEntity.projectId!,from,to);
|
|
||||||
if (!pathsCount) return null;
|
|
||||||
return pathsCount;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @throws {Error} If metric is undefined
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public async getCountAllMetrics(metricEntity: Partial<MetricEntity>) {
|
|
||||||
const count = await this.metricRepository.countAll(metricEntity.projectId!);
|
|
||||||
if (isNaN(count)) return null;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @throws {Error} If metric is undefined
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public async getLastMetrics(metricEntity: Partial<MetricEntity>, limit: number){
|
|
||||||
const lastMetric = await this.metricRepository.findLastRequests(metricEntity.projectId!,limit);
|
|
||||||
return ObjectHydrate.hydrate<Partial<MetricEntity>>(new MetricEntity(), lastMetric);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @throws {Error} If metric is undefined
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public async getRequestsByDay(metricEntity: Partial<MetricEntity>, from: Date, to: Date){
|
|
||||||
const requestByDay = await this.metricRepository.findRequestsByDay(metricEntity.projectId!,from,to);
|
|
||||||
return ObjectHydrate.hydrate<Partial<MetricEntity>>(new MetricEntity(), requestByDay);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @throws {Error} If metric is undefined
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public async removeThreeMontsOldMetrics() {
|
|
||||||
const months = 3;
|
|
||||||
await this.metricRepository.removeOldMetricsBymonths(months);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
76
src/common/services/metric/MetricsService.ts
Normal file
76
src/common/services/metric/MetricsService.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import BaseService from "@Services/BaseService";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class MetricsService extends BaseService {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
// /**
|
||||||
|
// * @throws {Error} If metrics are undefined
|
||||||
|
// */
|
||||||
|
// public async getByCriterias(query: ReturnType<typeof processFindManyQuery>): Promise<MetricEntity[]> {
|
||||||
|
// return await this.metricRepository.findMany(query);
|
||||||
|
// }
|
||||||
|
// /**
|
||||||
|
// *
|
||||||
|
// * @throws {Error} If metric cannot be created
|
||||||
|
// * @returns
|
||||||
|
// */
|
||||||
|
// public async create(metricEntity: Partial<MetricEntity>): Promise<Partial<MetricEntity>> {
|
||||||
|
// const metric = await this.metricRepository.create(metricEntity);
|
||||||
|
// if (!metric) return Promise.reject(new Error("Cannot create metric"));
|
||||||
|
// return metric;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws {Error} If metric is undefined
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
// public async getCountRpcPath(uuid: string, from: Date, to: Date): Promise<CountRpcPathUsage[]> {
|
||||||
|
// const pathsCount = await this.metricRepository.countRpcPathUsage(uuid, from, to);
|
||||||
|
// if (!pathsCount) return Promise.reject(new Error("Cannot get count of rpc path"));
|
||||||
|
// return pathsCount;
|
||||||
|
// }
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws {Error} If metric is undefined
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
// public async getCountAllMetrics(metricEntity: Partial<MetricEntity>): Promise<number> {
|
||||||
|
// const count = await this.metricRepository.countAll(metricEntity.uuid!);
|
||||||
|
// if (isNaN(count)) Promise.reject(new Error("Cannot get count of metrics"));
|
||||||
|
// return count;
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws {Error} If metric is undefined
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
// public async getLastMetrics(uuid: string, limit: number): Promise<MetricEntity[]> {
|
||||||
|
// return await this.metricRepository.findLastRequests(uuid, limit);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws {Error} If metric is undefined
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
// public async getRequestsByDay(uuid: string, from: Date, to: Date): Promise<RequestsByDayMetrics[]> {
|
||||||
|
// return await this.metricRepository.findRequestsByDay(uuid, from, to);
|
||||||
|
// }
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws {Error} If metric is undefined
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
// public async removeThreeMontsOldMetrics(): Promise<void> {
|
||||||
|
// const months = 3;
|
||||||
|
// await this.metricRepository.removeOldMetricsBymonths(months);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
@ -1,39 +0,0 @@
|
|||||||
import ObjectHydrate from "@Common/helpers/ObjectHydrate";
|
|
||||||
import ProjectRepository from "@Common/repositories/ProjectRepository";
|
|
||||||
import { ProjectEntity } from "@Common/ressources";
|
|
||||||
import { type processFindManyQuery } from "prisma-query";
|
|
||||||
import { Service } from "typedi";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Service()
|
|
||||||
export default class ProjectService {
|
|
||||||
constructor(private projectRepository: ProjectRepository) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws {Error} If projects are undefined
|
|
||||||
*/
|
|
||||||
public async getByCriterias(query: ReturnType<typeof processFindManyQuery>) {
|
|
||||||
const projects = await this.projectRepository.findMany(query);
|
|
||||||
return ObjectHydrate.hydrate<Partial<ProjectEntity>>(new ProjectEntity(), projects);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* @throws {Error} If project is undefined
|
|
||||||
*/
|
|
||||||
public async getByUUID(projectEntity: Partial<ProjectEntity>) {
|
|
||||||
const project = await this.projectRepository.findOne(projectEntity);
|
|
||||||
if (!project) return null;
|
|
||||||
return ObjectHydrate.hydrate<Partial<ProjectEntity>>(new ProjectEntity(), project);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @throws {Error} If project cannot be created
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
public async create(projectEntity: Partial<ProjectEntity>) {
|
|
||||||
const project = await this.projectRepository.create(projectEntity);
|
|
||||||
if (!project) return null;
|
|
||||||
return ObjectHydrate.hydrate<Partial<ProjectEntity>>(new ProjectEntity(), project);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
37
src/common/services/project/ProjectsService.ts
Normal file
37
src/common/services/project/ProjectsService.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// import ProjectsRepository from "@Common/repositories/projects/ProjectsRepository";
|
||||||
|
import { ProjectEntity } from "@Common/ressources";
|
||||||
|
import BaseService from "@Services/BaseService";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class ProjectsService extends BaseService {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws {Error} If projects are undefined
|
||||||
|
*/
|
||||||
|
// public async getByCriterias(query: ReturnType<typeof processFindManyQuery>): Promise<ProjectEntity[]> {
|
||||||
|
// return this.projectRepository.findMany(query);
|
||||||
|
// }
|
||||||
|
/**
|
||||||
|
* @throws {Error} If project is undefined
|
||||||
|
*/
|
||||||
|
public async getByUUID(projectEntity: Partial<ProjectEntity>){
|
||||||
|
// const project = await this.projectRepository.findOne(projectEntity);
|
||||||
|
// if (!project) Promise.reject(new Error("Cannot get project by uuid"));
|
||||||
|
// return project;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws {Error} If project cannot be created
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public async create(projectEntity: Partial<ProjectEntity>){
|
||||||
|
// const project = await this.projectRepository.create(projectEntity);
|
||||||
|
// if (!project) Promise.reject(new Error("Cannot create project"));
|
||||||
|
// return project;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
119
src/common/services/proxy/ProxyService.ts
Normal file
119
src/common/services/proxy/ProxyService.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import MetricEntity from "@Common/ressources/MetricEntity";
|
||||||
|
import HttpCodes from "@Common/system/controller-pattern/HttpCodes";
|
||||||
|
import { IHttpReponse, IStatusNode } from "@Common/system/interfaces/Interfaces";
|
||||||
|
import BaseService from "@Services/BaseService";
|
||||||
|
import axios from "axios";
|
||||||
|
import { IsNotEmpty, IsUUID, Validate } from "class-validator";
|
||||||
|
import { Service } from "typedi";
|
||||||
|
import IsRpcPathAllowed from "./validators/IsRpcPathAllowed";
|
||||||
|
import IsValidProject from "./validators/IsValidProject";
|
||||||
|
import { BackendVariables } from "@Common/config/variables/Variables";
|
||||||
|
import ProjectsRepository from "@Common/repositories/projects/ProjectsRepository";
|
||||||
|
|
||||||
|
export class RpcRequest {
|
||||||
|
@IsNotEmpty()
|
||||||
|
@Validate(IsRpcPathAllowed)
|
||||||
|
path!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsUUID()
|
||||||
|
@Validate(IsValidProject, [{ network: BaseService.network }])
|
||||||
|
uuid!: string;
|
||||||
|
|
||||||
|
@IsNotEmpty()
|
||||||
|
remoteAddress!: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export default class ProxyService extends BaseService {
|
||||||
|
constructor(private projectRepository: ProjectsRepository, private variables: BackendVariables) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @throws {Error} if url is undefined
|
||||||
|
*/
|
||||||
|
public getHttpServerResponse(): IHttpReponse {
|
||||||
|
return {
|
||||||
|
status: HttpCodes.SUCCESS,
|
||||||
|
reason: null,
|
||||||
|
} as IHttpReponse;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @throws {Error} if url is undefined
|
||||||
|
*/
|
||||||
|
public async getNodesStatus(): Promise<IStatusNode> {
|
||||||
|
const archiveTestURL = new URL(`${this.variables.ARCHIVE_NODES_URL}/chains/main/blocks/head`);
|
||||||
|
const rollingTestURL = new URL(`${this.variables.ROLLING_NODES_URL}/chains/main/blocks/head`);
|
||||||
|
|
||||||
|
const archive_node = {
|
||||||
|
status: HttpCodes.INTERNAL_ERROR,
|
||||||
|
reason: null,
|
||||||
|
} as IHttpReponse;
|
||||||
|
const rolling_node = {
|
||||||
|
status: HttpCodes.INTERNAL_ERROR,
|
||||||
|
reason: null,
|
||||||
|
} as IHttpReponse;
|
||||||
|
|
||||||
|
const [archive, rolling] = await Promise.allSettled([axios.get(archiveTestURL.toString()), axios.get(rollingTestURL.toString())]);
|
||||||
|
|
||||||
|
if (archive.status === "fulfilled") archive_node.status = archive.value.status;
|
||||||
|
if (archive.status === "rejected") archive_node.reason = archive.reason;
|
||||||
|
|
||||||
|
if (rolling.status === "fulfilled") rolling_node.status = rolling.value.status;
|
||||||
|
if (rolling.status === "rejected") rolling_node.reason = rolling.reason;
|
||||||
|
|
||||||
|
return {
|
||||||
|
archive_node,
|
||||||
|
rolling_node,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @throws {Error} If metric cannot be created
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
// async saveMetric(metricEntity: Partial<MetricEntity>) {
|
||||||
|
// const metric = await this.metricsRepository.create(metricEntity);
|
||||||
|
// if (!metric) return null;
|
||||||
|
// return metric;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Proxy proxy an http request to the right repositories
|
||||||
|
public async proxy(request: RpcRequest): Promise<string> {
|
||||||
|
console.info(`Received proxy request for path: ${request.path}`);
|
||||||
|
|
||||||
|
const project = await this.projectRepository.findOne({ uuid: request.uuid, network: BaseService.network });
|
||||||
|
|
||||||
|
// if (!project) {
|
||||||
|
// return Promise.reject(`Project uuid: ${request.uuid} with network: ${BaseService.network} does not exist`);
|
||||||
|
// }
|
||||||
|
|
||||||
|
let response = "";
|
||||||
|
|
||||||
|
if (this.isRollingNodeRedirection(request.path)) {
|
||||||
|
console.info("Forwarding request directly to rolling node (as a reverse proxy)");
|
||||||
|
const rollingURL = new URL(`${this.variables.ROLLING_NODES_URL}/${request.path}`);
|
||||||
|
const { data } = await axios.get(rollingURL.toString());
|
||||||
|
response = data;
|
||||||
|
} else {
|
||||||
|
console.info("Forwarding request directly to archive node (as a reverse proxy)");
|
||||||
|
const archiveURL = new URL(`${this.variables.ARCHIVE_NODES_URL}/${request.path}`);
|
||||||
|
const { data } = await axios.get(archiveURL.toString());
|
||||||
|
response = data;
|
||||||
|
}
|
||||||
|
// Logger les metrics
|
||||||
|
const metric = new MetricEntity();
|
||||||
|
Object.assign(metric, request, { project, dateRequested: new Date() });
|
||||||
|
|
||||||
|
// await this.saveMetric(metric);
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
isRollingNodeRedirection(url: string): boolean {
|
||||||
|
const pureUrl = `/${url!.trim()}`;
|
||||||
|
console.info(`Checking if ${pureUrl} is a rolling node redirection`);
|
||||||
|
return Boolean(BaseService.rollingPatterns.find((rollingpattern) => pureUrl.includes(rollingpattern)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
31
src/common/services/proxy/validators/IsRpcPathAllowed.ts
Normal file
31
src/common/services/proxy/validators/IsRpcPathAllowed.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import BaseService from "@Services/BaseService";
|
||||||
|
import { ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
|
||||||
|
|
||||||
|
@ValidatorConstraint({ name: "IsRpcPathAllowed" })
|
||||||
|
export default class IsRpcPathAllowed implements ValidatorConstraintInterface {
|
||||||
|
public validate(path: string) {
|
||||||
|
return isAllowed(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public defaultMessage() {
|
||||||
|
return `not a valid path!`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isAllowed(path: string): boolean {
|
||||||
|
const pureUrl = `/${path!.trim()}`;
|
||||||
|
let nonWhitelistedPart = "";
|
||||||
|
for (const whitelistPath of BaseService.whitelisted) {
|
||||||
|
if (pureUrl.includes(whitelistPath)) {
|
||||||
|
nonWhitelistedPart = pureUrl.slice(pureUrl.indexOf(whitelistPath) + whitelistPath.length);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const blacklistPath of BaseService.blacklisted) {
|
||||||
|
if (nonWhitelistedPart.includes(blacklistPath) || pureUrl.includes(blacklistPath)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
24
src/common/services/proxy/validators/IsValidProject.ts
Normal file
24
src/common/services/proxy/validators/IsValidProject.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import BaseService from "@Services/BaseService";
|
||||||
|
import { ValidationArguments, ValidatorConstraint, ValidatorConstraintInterface } from "class-validator";
|
||||||
|
import Container from "typedi";
|
||||||
|
import { RpcRequest } from "../ProxyService";
|
||||||
|
import ProjectsRepository from "@Common/repositories/projects/ProjectsRepository";
|
||||||
|
|
||||||
|
@ValidatorConstraint({ name: "IsValidProject" })
|
||||||
|
export default class IsValidProject implements ValidatorConstraintInterface {
|
||||||
|
public async validate(uuid: string, args: ValidationArguments) {
|
||||||
|
const projectRepository = Container.get(ProjectsRepository);
|
||||||
|
|
||||||
|
if (args.constraints?.[0]?.network) {
|
||||||
|
return Boolean(await projectRepository.findOne({ uuid, network: BaseService.network }));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public defaultMessage(args: ValidationArguments) {
|
||||||
|
const network = args.constraints?.[0]!.network;
|
||||||
|
const uuid = (args.object as RpcRequest).uuid;
|
||||||
|
return `Project uuid: ${uuid} with network: ${network} does not exist`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,24 +1,26 @@
|
|||||||
import dotenv from "dotenv";
|
import dotenv from "dotenv";
|
||||||
import { PrismaClient } from "@prisma/client";
|
import { PrismaClient } from "@prisma/client";
|
||||||
import IDatabaseConfig from "../../config/IDatabaseConfig";
|
import IDatabaseConfig from "../../config/database/IDatabaseConfig";
|
||||||
|
import { BackendVariables } from "@Common/config/variables/Variables";
|
||||||
|
import Container from "typedi";
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
export default class DbProvider {
|
export default class DbProvider {
|
||||||
|
protected readonly variables = Container.get(BackendVariables);
|
||||||
protected client = new PrismaClient({
|
protected client = new PrismaClient({
|
||||||
datasources: {
|
datasources: {
|
||||||
db: {
|
db: {
|
||||||
url: `postgres://${process.env["DATABASE_USER"]}:${process.env["DATABASE_PASSWORD"]}@${process.env["DATABASE_HOSTNAME"]}:${process.env["DATABASE_PORT"]}/${process.env["DATABASE_NAME"]}`,
|
url: `postgres://${this.variables.DATABASE_USER}:${this.variables.DATABASE_PASSWORD}@${this.variables.DATABASE_HOSTNAME}:${this.variables.DATABASE_PORT}/${this.variables.DATABASE_NAME}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
constructor(protected config: IDatabaseConfig) {
|
constructor(protected config: IDatabaseConfig) {}
|
||||||
}
|
|
||||||
|
|
||||||
public async connect(): Promise<void> {
|
public async connect(): Promise<void> {
|
||||||
await this.client.$connect();
|
await this.client.$connect();
|
||||||
console.info(`⚡️[Prisma]: Connected to ${this.config.name}`);// A Logger middleware is to be added here
|
console.info(`⚡️[Prisma]: Connected to ${this.config.name}`); // A Logger middleware is to be added here
|
||||||
}
|
}
|
||||||
|
|
||||||
public getClient() {
|
public getClient() {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import IDatabaseConfig from "@Common/config/IDatabaseConfig";
|
import IDatabaseConfig from "@Common/config/database/IDatabaseConfig";
|
||||||
import DbProvider from "./DbProvider";
|
import DbProvider from "./DbProvider";
|
||||||
|
|
||||||
export type { IDatabaseConfig };
|
export type { IDatabaseConfig };
|
||||||
|
9
src/common/system/interfaces/Interfaces.ts
Normal file
9
src/common/system/interfaces/Interfaces.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export interface IHttpReponse {
|
||||||
|
status: number;
|
||||||
|
reason: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IStatusNode {
|
||||||
|
archive_node: IHttpReponse;
|
||||||
|
rolling_node: IHttpReponse;
|
||||||
|
}
|
@ -1,36 +1,34 @@
|
|||||||
import "module-alias/register";
|
import "module-alias/register";
|
||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
import dotenv from "dotenv";
|
|
||||||
import { Container } from "typedi";
|
import { Container } from "typedi";
|
||||||
import ExpressServer from "@Common/system/ExpressServer";
|
import ExpressServer from "@Common/system/ExpressServer";
|
||||||
import routes from "@Api/controllers/index";
|
import routes from "@Api/controllers/index";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import bodyParser from "body-parser";
|
import bodyParser from "body-parser";
|
||||||
import TezosLink from "@Common/databases/TezosLink";
|
// import TezosLink from "@Common/databases/TezosLink";
|
||||||
import errorHandler from "@Api/middlewares/ErrorHandler";
|
import errorHandler from "@Api/middlewares/ErrorHandler";
|
||||||
|
import { BackendVariables } from "@Common/config/variables/Variables";
|
||||||
|
|
||||||
dotenv.config();
|
(async () => {
|
||||||
|
try {
|
||||||
|
const variables = await Container.get(BackendVariables).validate();
|
||||||
|
|
||||||
const port = process.env["NEXT_PUBLIC_API_PORT"];
|
const port = variables.API_PORT;
|
||||||
const rootUrl = process.env["NEXT_PUBLIC_API_ROOT_URL"];
|
const rootUrl = variables.API_ROOT_URL;
|
||||||
const label = process.env["NEXT_PUBLIC_API_LABEL"] ?? "Unknown Service";
|
const label = variables.API_LABEL ?? "Unknown Service";
|
||||||
|
|
||||||
if (!port) throw new Error(`process.env Port is undefined`);
|
// Container.get(TezosLink).connect();
|
||||||
if (!rootUrl) throw new Error(`process.env RootUrl is undefined`);
|
Container.get(ExpressServer).init({
|
||||||
Container.get(TezosLink).connect();
|
|
||||||
Container.get(ExpressServer).init({
|
|
||||||
label,
|
label,
|
||||||
port: parseInt(port),
|
port: parseInt(port),
|
||||||
rootUrl,
|
rootUrl,
|
||||||
middlwares: [
|
middlwares: [cors({ origin: "*" }), bodyParser.urlencoded({ extended: true }), bodyParser.json()],
|
||||||
cors({ origin: "*" }),
|
|
||||||
bodyParser.urlencoded({ extended: true }),
|
|
||||||
bodyParser.json(),
|
|
||||||
],
|
|
||||||
errorHandler,
|
errorHandler,
|
||||||
});
|
});
|
||||||
|
|
||||||
routes.start();
|
|
||||||
|
|
||||||
|
|
||||||
|
routes.start();
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
@ -1,21 +1,27 @@
|
|||||||
import "module-alias/register";
|
import "module-alias/register";
|
||||||
import "reflect-metadata";
|
import "reflect-metadata";
|
||||||
import dotenv from "dotenv";
|
|
||||||
import { Container } from "typedi";
|
import { Container } from "typedi";
|
||||||
import NextServer from "@Common/system/NextJs";
|
import NextServer from "@Common/system/NextJs";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
import { FrontendVariables } from "@Front/Config/VariablesFront";
|
||||||
|
|
||||||
dotenv.config();
|
(async () => {
|
||||||
|
try {
|
||||||
|
dotenv.config();
|
||||||
|
const frontVariables = Container.get(FrontendVariables);
|
||||||
|
|
||||||
const port = process.env["WEB_PORT"];
|
const port = frontVariables.WEB_PORT;
|
||||||
const rootUrl = process.env["WEB_ROOT_URL"];
|
const rootUrl = frontVariables.WEB_ROOT_URL;
|
||||||
const label = process.env["WEB_LABEL"] ?? "Unknown Service";
|
const label = frontVariables.WEB_LABEL ?? "Unknown Service";
|
||||||
|
|
||||||
if (!port) throw new Error(`process.env Port is undefined`);
|
Container.get(NextServer).init({
|
||||||
if (!rootUrl) throw new Error(`process.env RootUrl is undefined`);
|
|
||||||
|
|
||||||
Container.get(NextServer).init({
|
|
||||||
label,
|
label,
|
||||||
isDev: process.env.NODE_ENV !== 'production',
|
isDev: frontVariables.NODE_ENV !== "production",
|
||||||
port: parseInt(port),
|
port: parseInt(port),
|
||||||
rootUrl,
|
rootUrl,
|
||||||
});
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
border: 1px solid $green-flash;
|
border: 1px solid var(green-flash);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -23,7 +23,7 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
background-color: $green-flash;
|
background-color: var(green-flash);
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,16 @@
|
|||||||
|
|
||||||
.root {
|
.root {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
textarea{
|
||||||
|
resize: none;
|
||||||
|
height: auto;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: 'Inter';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 22px;
|
||||||
|
}
|
||||||
.input {
|
.input {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -10,7 +19,6 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
|
|
||||||
width: 530px;
|
width: 530px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
border: 1px solid $grey-medium;
|
border: 1px solid $grey-medium;
|
||||||
@ -18,7 +26,7 @@
|
|||||||
&:focus {
|
&:focus {
|
||||||
~ .fake-placeholder {
|
~ .fake-placeholder {
|
||||||
transform: translateY(-35px);
|
transform: translateY(-35px);
|
||||||
transition: transform 0.5s ease;
|
transition: transform 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:not([value=""]) {
|
&:not([value=""]) {
|
||||||
@ -32,7 +40,7 @@
|
|||||||
&:focus {
|
&:focus {
|
||||||
~ .fake-placeholder {
|
~ .fake-placeholder {
|
||||||
transform: translateY(-35px);
|
transform: translateY(-35px);
|
||||||
transition: transform 0.5s ease;
|
transition: transform 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&:not([value=""]) {
|
&:not([value=""]) {
|
||||||
@ -46,7 +54,7 @@
|
|||||||
&:not([value=""]) {
|
&:not([value=""]) {
|
||||||
~ .fake-placeholder {
|
~ .fake-placeholder {
|
||||||
transform: translateY(-35px);
|
transform: translateY(-35px);
|
||||||
transition: transform 0.5s ease;
|
transition: transform 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import classes from "./classes.module.scss";
|
|||||||
|
|
||||||
export type IProps = IBaseFieldProps & {
|
export type IProps = IBaseFieldProps & {
|
||||||
fakeplaceholder: string;
|
fakeplaceholder: string;
|
||||||
large?: boolean;
|
textarea?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-ignore TODO: typing error on IProps (validator class?? cf Massi 22/02/23)
|
// @ts-ignore TODO: typing error on IProps (validator class?? cf Massi 22/02/23)
|
||||||
@ -30,7 +30,7 @@ export default class InputField extends BaseField<IProps> {
|
|||||||
// we always need to control the input so we need to set the value as "" by default
|
// we always need to control the input so we need to set the value as "" by default
|
||||||
const value = this.state.value ?? "";
|
const value = this.state.value ?? "";
|
||||||
|
|
||||||
if (this.props.large === true) {
|
if (this.props.textarea === true) {
|
||||||
return (
|
return (
|
||||||
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
|
<Typography typo={ITypo.NAV_INPUT_16} color={ITypoColor.GREY}>
|
||||||
<div className={classes["root"]}>
|
<div className={classes["root"]}>
|
||||||
|
@ -11,6 +11,16 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 107px;
|
top: 107px;
|
||||||
right: 56px;
|
right: 56px;
|
||||||
|
animation: smooth-appear 0.2s ease forwards;
|
||||||
|
|
||||||
|
@keyframes smooth-appear {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.notification-header {
|
.notification-header {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -10,6 +10,17 @@
|
|||||||
top: 107px;
|
top: 107px;
|
||||||
right: 66px;
|
right: 66px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
animation: smooth-appear 0.2s ease forwards;
|
||||||
|
|
||||||
|
@keyframes smooth-appear {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
> *:not(:last-child) {
|
> *:not(:last-child) {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,16 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: $modal-background;
|
background-color: $modal-background;
|
||||||
|
animation: smooth-appear 0.2s ease forwards;
|
||||||
|
|
||||||
|
@keyframes smooth-appear {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
@ -28,6 +38,16 @@
|
|||||||
box-shadow: 0px 6px 12px rgba(255, 255, 255, 0.11);
|
box-shadow: 0px 6px 12px rgba(255, 255, 255, 0.11);
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 32px;
|
padding: 32px;
|
||||||
|
animation: smooth-appear 0.2s ease forwards;
|
||||||
|
|
||||||
|
@keyframes smooth-appear {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: $screen-s) {
|
@media (max-width: $screen-s) {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
border: 1px solid $green-flash;
|
border: 1px solid var(green-flash);
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
display: grid;
|
display: grid;
|
||||||
@ -24,7 +24,7 @@
|
|||||||
content: "";
|
content: "";
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
background-color: $green-flash;
|
background-color: var(green-flash);
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@
|
|||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
|
|
||||||
animation-name: slide-left;
|
animation-name: slide-left;
|
||||||
animation-duration: 400ms;
|
animation-duration: 300ms;
|
||||||
animation-timing-function: $custom-easing;
|
animation-timing-function: $custom-easing;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import ToolTipIcon from "@Assets/icons/tool-tip.svg";
|
import ToolTipIcon from "@Assets/icons/tool-tip.svg";
|
||||||
import TooltipContent from "./Content";
|
import TooltipMUI, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip";
|
||||||
import Typography, { ITypo } from "../Typography";
|
import styled from "@emotion/styled";
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
title?: string | JSX.Element;
|
title?: string | JSX.Element;
|
||||||
@ -16,6 +16,19 @@ type IState = {
|
|||||||
event: React.MouseEvent<HTMLElement> | null;
|
event: React.MouseEvent<HTMLElement> | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const LightTooltip = styled(({ className, ...props }: TooltipProps) => (
|
||||||
|
<TooltipMUI {...props} classes={{ popper: className }} />
|
||||||
|
))(({ theme }) => ({
|
||||||
|
[`& .${tooltipClasses.tooltip}`]: {
|
||||||
|
backgroundColor: "var(--colormerdum)",
|
||||||
|
color: "var(--colormerdum)",
|
||||||
|
//boxShadow: theme.shadows[1],
|
||||||
|
fontSize: 11,
|
||||||
|
},[`& .${tooltipClasses.arrow}`]: {
|
||||||
|
// color: theme.palette.common.black,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
export default class Tooltip extends React.Component<IProps, IState> {
|
export default class Tooltip extends React.Component<IProps, IState> {
|
||||||
static defaultProps: Partial<IProps> = {
|
static defaultProps: Partial<IProps> = {
|
||||||
isNotFlex: false,
|
isNotFlex: false,
|
||||||
@ -23,44 +36,17 @@ export default class Tooltip extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
public constructor(props: IProps) {
|
public constructor(props: IProps) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
|
||||||
showContent: false,
|
|
||||||
event: null,
|
|
||||||
};
|
|
||||||
this.togglePopup = this.togglePopup.bind(this);
|
|
||||||
this.moovePopup = this.moovePopup.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override render(): JSX.Element {
|
public override render(): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<>
|
<LightTooltip title={this.props.text} placement="top" arrow>
|
||||||
<span
|
<span
|
||||||
className={this.props.className}
|
className={this.props.className}
|
||||||
style={!this.props.isNotFlex ? { display: "flex" } : {}}
|
style={!this.props.isNotFlex ? { display: "flex" } : {}}>
|
||||||
onMouseEnter={this.togglePopup}
|
|
||||||
onMouseLeave={this.togglePopup}
|
|
||||||
onMouseMove={this.moovePopup}>
|
|
||||||
<Image src={ToolTipIcon} alt="toolTip icon" />
|
<Image src={ToolTipIcon} alt="toolTip icon" />
|
||||||
</span>
|
</span>
|
||||||
<Typography typo={ITypo.CAPTION_14}>
|
</LightTooltip>
|
||||||
<TooltipContent title={this.props.title} event={this.state.event} display={this.state.showContent}>
|
|
||||||
{this.props.text}
|
|
||||||
</TooltipContent>
|
|
||||||
</Typography>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private togglePopup(e: React.MouseEvent<HTMLSpanElement>): void {
|
|
||||||
this.setState({
|
|
||||||
showContent: !this.state.showContent,
|
|
||||||
event: e,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private moovePopup(e: React.MouseEvent<HTMLSpanElement>): void {
|
|
||||||
this.setState({
|
|
||||||
event: e,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import Head from "next/head";
|
import Head from "next/head";
|
||||||
import { ReactNode } from "react";
|
import { ReactNode } from "react";
|
||||||
|
|
||||||
type DefaultLayoutProps = { children: ReactNode };
|
type DefaultLayoutProps = { children: ReactNode };
|
||||||
|
|
||||||
export const DefaultLayout = ({ children }: DefaultLayoutProps) => {
|
export const DefaultLayout = ({ children }: DefaultLayoutProps) => {
|
||||||
|
@ -121,7 +121,7 @@ export default class DesignSystem extends BasePage<IProps, IState> {
|
|||||||
<InputField name="input field" fakeplaceholder="input place hodler" />
|
<InputField name="input field" fakeplaceholder="input place hodler" />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes["sub-section"]}>
|
<div className={classes["sub-section"]}>
|
||||||
<InputField name="input field" fakeplaceholder="text area place hodler" large={true} />
|
<InputField name="input field" fakeplaceholder="text area place hodler" textarea />
|
||||||
</div>
|
</div>
|
||||||
<div className={classes["sub-section"]}>
|
<div className={classes["sub-section"]}>
|
||||||
<InputField name="input field" fakeplaceholder="number place hodler" type="number" />
|
<InputField name="input field" fakeplaceholder="number place hodler" type="number" />
|
||||||
|
37
src/front/Config/VariablesFront.ts
Normal file
37
src/front/Config/VariablesFront.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { Service } from "typedi";
|
||||||
|
|
||||||
|
@Service()
|
||||||
|
export class FrontendVariables {
|
||||||
|
private static instance: FrontendVariables;
|
||||||
|
|
||||||
|
public readonly WEB_LABEL: string;
|
||||||
|
|
||||||
|
public readonly WEB_PORT!: string;
|
||||||
|
|
||||||
|
public readonly WEB_ROOT_URL!: string;
|
||||||
|
|
||||||
|
public readonly NEXT_PUBLIC_API_URL!: string;
|
||||||
|
|
||||||
|
public readonly NEXT_PUBLIC_RPC_GATEWAY_MAINNET_URL!: string;
|
||||||
|
|
||||||
|
public readonly NEXT_PUBLIC_RPC_GATEWAY_TESTNET_URL!: string;
|
||||||
|
|
||||||
|
public readonly NODE_ENV!: string;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.NODE_ENV = process.env["NODE_ENV"]!;
|
||||||
|
this.WEB_LABEL = process.env["WEB_LABEL"]!;
|
||||||
|
this.WEB_PORT = process.env["WEB_PORT"]!;
|
||||||
|
this.WEB_ROOT_URL = process.env["WEB_ROOT_URL"]!;
|
||||||
|
this.NEXT_PUBLIC_API_URL = process.env["NEXT_PUBLIC_API_URL"]!;
|
||||||
|
this.NEXT_PUBLIC_RPC_GATEWAY_MAINNET_URL = process.env["NEXT_PUBLIC_RPC_GATEWAY_MAINNET_URL"]!;
|
||||||
|
this.NEXT_PUBLIC_RPC_GATEWAY_TESTNET_URL = process.env["NEXT_PUBLIC_RPC_GATEWAY_TESTNET_URL"]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getInstance(): FrontendVariables {
|
||||||
|
if (!this.instance) {
|
||||||
|
this.instance = new this();
|
||||||
|
}
|
||||||
|
return this.instance;
|
||||||
|
}
|
||||||
|
}
|
@ -6,5 +6,29 @@
|
|||||||
--root-padding: 64px 120px;
|
--root-padding: 64px 120px;
|
||||||
|
|
||||||
--font-primary: "Inter", sans-serif;
|
--font-primary: "Inter", sans-serif;
|
||||||
--font-secondary: "Source Sans Pro", sans-serif;
|
|
||||||
|
--colormerdum: blue;
|
||||||
|
|
||||||
|
--green-flash: $green-flash;
|
||||||
|
--blue-flash: $blue-flash;
|
||||||
|
--turquoise-flash: $turquoise-flash;
|
||||||
|
--purple-flash: $purple-flash;
|
||||||
|
--purple-hover: $purple-hover;
|
||||||
|
--orange-flash: $orange-flash;
|
||||||
|
--red-flash: $red-flash;
|
||||||
|
--re-hover: $re-hover;
|
||||||
|
--pink-flash: $pink-flash;
|
||||||
|
--pink-hover: $pink-hover;
|
||||||
|
|
||||||
|
--green-soft: $green-soft;
|
||||||
|
--blue-soft: $blue-soft;
|
||||||
|
--turquoise-soft: $turquoise-soft;
|
||||||
|
--purple-soft: $purple-soft;
|
||||||
|
--orange-soft: $orange-soft;
|
||||||
|
--red-soft: $red-soft;
|
||||||
|
--pink-soft: $pink-soft;
|
||||||
|
|
||||||
|
--grey: $grey;
|
||||||
|
--grey-medium: $grey-medium;
|
||||||
|
--grey-soft: $grey-soft;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
@import "@Themes/constants.scss";
|
@import "@Themes/constants.scss";
|
||||||
@import "@Themes/fonts.scss";
|
@import "@Themes/fonts.scss";
|
||||||
|
@import "@Themes/variables.scss";
|
||||||
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user