Skip to content
Snippets Groups Projects
Verified Commit fa7201b5 authored by Konstantin Tsabolov's avatar Konstantin Tsabolov
Browse files

feat: add devtools

parent b44a2764
No related branches found
No related tags found
1 merge request!40End to end run preparation
Showing
with 354 additions and 3 deletions
......@@ -11,6 +11,7 @@
# ... in these directories
!apps/**/src/*
!devtools/**/src/*
# Explicitly ignore these locations
node_modules
......
......@@ -20,7 +20,7 @@ module.exports = {
},
'import/resolver': {
typescript: {
project: 'packages/*/tsconfig.json',
project: ['apps/*/tsconfig.json', 'devtools/tsconfig.json'],
alwaysTryTypes: true,
},
},
......@@ -75,7 +75,7 @@ module.exports = {
},
overrides: [
{
files: ['*.spec.ts', '*.e2e-spec.ts', '**/tests/**', 'scripts/*.ts', 'scripts/*.mts'],
files: ['*.spec.ts', '*.e2e-spec.ts', '**/tests/**'],
env: {
jest: true,
node: true,
......@@ -89,5 +89,11 @@ module.exports = {
],
},
},
{
files: ['devtools/**/*.ts'],
rules: {
'no-console': 'off',
}
}
],
};
......@@ -4,6 +4,7 @@
# Except for these files
!*.ts
!*.d.ts
!*.mts
!jest.config.js
# .. also in subdirectories
......@@ -11,6 +12,7 @@
# ... in these ones
!apps/**/src/*
!devtools/**/src/*
# Explicitly ignore these locations
node_modules
......
{
"name": "@ocm/devtools",
"version": "1.0.0",
"description": "Developers tools for OCM",
"contributors": [
"Konstantin Tsabolov <konstantin.tsabolov@spherity.com>"
],
"private": true,
"license": "Apache-2.0",
"type": "module",
"scripts": {
"exec": "nest start --entryFile=cli.js --",
"start": "nest start --entryFile=server.js"
},
"dependencies": {
"@nestjs/cli": "^10.3.1",
"@nestjs/common": "^10.3.0",
"@nestjs/config": "^3.1.1",
"@nestjs/core": "^10.3.0",
"@nestjs/microservices": "^10.3.0",
"@ocm/shared": "workspace:*",
"joi": "^17.12.1",
"nest-commander": "^3.12.5",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"vite-node": "^1.2.1"
},
"devDependencies": {}
}
import { CommandFactory } from 'nest-commander';
import { Commander } from './commander.js';
await CommandFactory.run(Commander, ['warn', 'error']);
import { Body, Controller, Get, Post } from '@nestjs/common';
import { CommanderService } from './commander.service.js';
@Controller()
export class CommanderController {
public constructor(private readonly service: CommanderService) {}
@Get('list-tenants')
public listTenants() {
return this.service.listTenants();
}
@Post('create-tenant')
public createTenant(@Body() { label }: { label?: string }) {
return this.service.createTenant(label);
}
@Post('register-endorser-did')
public registerEndorserDID() {
return this.service.registerEndorserDID();
}
@Post('register-did')
public registerDID(
@Body() { tenantId, seed }: { tenantId: string; seed?: string },
) {
return this.service.registerDID(tenantId, seed);
}
@Post('resolve-did')
public resolveDID(
@Body() { tenantId, did }: { tenantId: string; did: string },
) {
return this.service.resolveDID(tenantId, did);
}
}
import { Inject, Injectable } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import {
EventDidsRegisterEndorserDid,
EventDidsRegisterIndyFromSeed,
EventDidsResolve,
EventTenantsCreate,
EventTenantsGetAllTenantIds,
} from '@ocm/shared';
import { randomBytes } from 'node:crypto';
import { map } from 'rxjs';
import { NATS_CLIENT_NAME } from './constants.js';
import { requestTimeout } from './utils.js';
@Injectable()
export class CommanderService {
public constructor(
@Inject(NATS_CLIENT_NAME) private readonly client: ClientProxy,
) {}
public listTenants() {
return this.client.send(EventTenantsGetAllTenantIds.token, {}).pipe(
requestTimeout,
map(({ data }) => data),
);
}
public createTenant(label = 'tenant_' + randomBytes(4).toString('hex')) {
return this.client.send(EventTenantsCreate.token, { label }).pipe(
requestTimeout,
map(({ data }) => data),
);
}
public registerEndorserDID() {
return this.client.send(EventDidsRegisterEndorserDid.token, {}).pipe(
requestTimeout,
map(({ data }) => data),
);
}
public resolveDID(tenantId: string, did: string) {
return this.client.send(EventDidsResolve.token, { tenantId, did }).pipe(
requestTimeout,
map(({ data }) => data),
);
}
public registerDID(
tenantId: string,
seed: string = randomBytes(16).toString('hex'),
) {
return this.client
.send(EventDidsRegisterIndyFromSeed.token, {
tenantId,
seed,
})
.pipe(
requestTimeout,
map(({ data }) => data),
);
}
}
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { ClientsModule, Transport } from '@nestjs/microservices';
import Joi from 'joi';
import { CommanderController } from './commander.controller.js';
import { CommanderService } from './commander.service.js';
import { CreateTenantCommand } from './commands/create-tenant.command.js';
import { ListTenantsCommand } from './commands/list-tenants.command.js';
import { RegisterDIDCommand } from './commands/register-did.command.js';
import { RegisterEndorserDIDCommand } from './commands/register-endorser-did.command.js';
import { ResolveDIDCommand } from './commands/resolve-did.command.js';
import { NATS_CLIENT_NAME } from './constants.js';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
validationSchema: Joi.object({
HTTP_PORT: Joi.number().default(4100),
NATS_URL: Joi.string().default('nats://localhost:4222'),
NATS_USER: Joi.string().default('nats_user'),
NATS_PASSWORD: Joi.string().default('nats_password'),
}),
}),
ClientsModule.registerAsync([
{
name: NATS_CLIENT_NAME,
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
transport: Transport.NATS,
options: {
servers: configService.get('NATS_URL'),
user: configService.get('NATS_USER'),
pass: configService.get('NATS_PASSWORD'),
},
}),
},
]),
],
providers: [
CommanderService,
ListTenantsCommand,
CreateTenantCommand,
RegisterDIDCommand,
RegisterEndorserDIDCommand,
ResolveDIDCommand,
],
controllers: [CommanderController],
})
export class Commander {}
import { Command, CommandRunner } from 'nest-commander';
import { firstValueFrom, tap } from 'rxjs';
import { CommanderService } from '../commander.service.js';
@Command({
name: 'create-tenant',
description: 'Create a new tenant',
arguments: '[label]',
})
export class CreateTenantCommand extends CommandRunner {
public constructor(private readonly service: CommanderService) {
super();
}
public async run([label]: string[]) {
await firstValueFrom(
this.service.createTenant(label).pipe(
tap((data) => {
console.log(`Tenant "${label}" created with id: ${data.id}`);
}),
),
);
}
}
import { Command, CommandRunner } from 'nest-commander';
import { firstValueFrom, tap } from 'rxjs';
import { CommanderService } from '../commander.service.js';
@Command({
name: 'list-tenants',
description: 'List all tenants',
})
export class ListTenantsCommand extends CommandRunner {
public constructor(private readonly service: CommanderService) {
super();
}
public async run(): Promise<void> {
await firstValueFrom(this.service.listTenants().pipe(tap(console.log)));
}
}
import { Command, CommandRunner } from 'nest-commander';
import { firstValueFrom, tap } from 'rxjs';
import { CommanderService } from '../commander.service.js';
@Command({
name: 'register-did',
description: 'Register a new DID for a tenant',
arguments: '<tenantId> [seed]',
})
export class RegisterDIDCommand extends CommandRunner {
public constructor(private readonly service: CommanderService) {
super();
}
public async run([tenantId, seed]: string[]): Promise<void> {
await firstValueFrom(
this.service.registerDID(tenantId, seed).pipe(
tap((data) => {
console.log(`Registered DID "${data[0]}" for tenant "${tenantId}"`);
}),
),
);
}
}
import { Command, CommandRunner } from 'nest-commander';
import { firstValueFrom, tap } from 'rxjs';
import { CommanderService } from '../commander.service.js';
@Command({
name: 'register-endorser-did',
description: 'Register an endorser DID',
})
export class RegisterEndorserDIDCommand extends CommandRunner {
public constructor(private readonly service: CommanderService) {
super();
}
public async run(): Promise<void> {
await firstValueFrom(
this.service.registerEndorserDID().pipe(
tap(() => {
console.log(`Successfully registered an endorser DID`);
}),
),
);
}
}
import { Command, CommandRunner } from 'nest-commander';
import { firstValueFrom, tap } from 'rxjs';
import { CommanderService } from '../commander.service.js';
@Command({
name: 'resolve-did',
arguments: '<tenantId> <did>',
})
export class ResolveDIDCommand extends CommandRunner {
public constructor(private readonly service: CommanderService) {
super();
}
public async run([tenantId, did]: string[]): Promise<void> {
await firstValueFrom(
this.service.resolveDID(tenantId, did).pipe(
tap((data) => {
console.log(JSON.stringify(data, null, 2));
}),
),
);
}
}
export const NATS_CLIENT_NAME = 'DEVTOOLS';
export const REQUEST_TIMEOUT_MS = 10000;
import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { Commander } from './commander.js';
const app = await NestFactory.create(Commander);
const config = app.get(ConfigService);
const port = config.get('HTTP_PORT');
await app.listen(port);
import { timeout, throwError } from 'rxjs';
import { REQUEST_TIMEOUT_MS } from './constants.js';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const requestTimeout = timeout<any, any>({
each: REQUEST_TIMEOUT_MS,
with: () => throwError(() => new Error('Request timed out')),
});
{
"extends": "../tsconfig.build.json",
"compilerOptions": {
"baseUrl": ".",
"outDir": "./dist",
"rootDir": "./src"
},
"exclude": ["node_modules", "**/test", "**/dist", "**/*spec.ts"]
}
{
"extends": "../tsconfig.json",
}
......@@ -16,7 +16,12 @@
"format:all": "pnpm format -- .",
"lint-staged": "lint-staged",
"prepare": "husky install",
"createTenant": "vite-node scripts/create_tenant.mts"
"devtools": "pnpm -F devtools run exec",
"listTenants": "pnpm devtools list-tenants",
"createTenant": "pnpm devtools create-tenant",
"resolveDID": "pnpm devtools resolve-did",
"registerDID": "pnpm devtools register-did",
"registerEndorserDID": "pnpm devtools register-endorser-did"
},
"devDependencies": {
"@commitlint/cli": "^18.4.2",
......
packages:
- apps/*
- devtools
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment