Skip to content

Commit

Permalink
feat(api): add api-transaction-pool package (#268)
Browse files Browse the repository at this point in the history
* extract api building blocks into api-common

* update yml

* lint

* style: resolve style guide violations

* fix

* revert lint

* implement transaction-pool api package

* add abstract service provider for api server

* cleanup

* style: resolve style guide violations

* remove unused endpoints

* style: resolve style guide violations

---------

Co-authored-by: oXtxNt9U <[email protected]>
  • Loading branch information
oXtxNt9U and oXtxNt9U authored Oct 6, 2023
1 parent 4e7ee92 commit a68877e
Show file tree
Hide file tree
Showing 33 changed files with 486 additions and 116 deletions.
17 changes: 2 additions & 15 deletions packages/api-common/source/controller.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
import Boom from "@hapi/boom";
import Hapi from "@hapi/hapi";
import { inject, injectable, tagged } from "@mainsail/container";
import { inject, injectable } from "@mainsail/container";
import { Contracts, Identifiers } from "@mainsail/contracts";
import { Providers } from "@mainsail/kernel";

import { Options, Pagination, Resource, ResultsPage, Sorting } from "./contracts";
import { Pagination, Resource, ResultsPage, Sorting } from "./contracts";
import { SchemaObject } from "./schemas";

@injectable()
export abstract class AbstractController {
@inject(Identifiers.Application)
protected readonly app!: Contracts.Kernel.Application;

@inject(Identifiers.PluginConfiguration)
@tagged("plugin", "api-http")
protected readonly apiConfiguration!: Providers.PluginConfiguration;

protected getQueryPagination(query: Hapi.RequestQuery): Pagination {
return {
limit: query.limit,
Expand Down Expand Up @@ -60,14 +55,6 @@ export abstract class AbstractController {
}));
}

protected getListingOptions(): Options {
const estimateTotalCount = this.apiConfiguration.getOptional<boolean>("options.estimateTotalCount", true);

return {
estimateTotalCount,
};
}

protected async respondWithResource(data, transformer, transform = true): Promise<any> {
if (!data) {
return Boom.notFound();
Expand Down
1 change: 1 addition & 0 deletions packages/api-common/source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * as Contracts from "./contracts";
export * from "./controller";
export * as Schemas from "./schemas";
export * from "./server";
export * from "./service-provider";
File renamed without changes.
25 changes: 18 additions & 7 deletions packages/api-common/source/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ import { Contracts, Identifiers } from "@mainsail/contracts";
import { Providers, Utils } from "@mainsail/kernel";
import { readFileSync } from "fs";

export enum ServerType {
Http = "HTTP",
Https = "HTTPS",
}

@injectable()
export abstract class AbstractServer {
@inject(Identifiers.Application)
Expand All @@ -14,16 +19,22 @@ export abstract class AbstractServer {

private server: HapiServer;

private name!: string;
protected abstract baseName(): string;
private serverType!: ServerType;

public get prettyName(): string {
return `${this.baseName()} (${this.serverType})`;
}

public get uri(): string {
return this.server.info.uri;
}

public async initialize(name: string, optionsServer: Contracts.Types.JsonObject): Promise<void> {
this.name = name;
public async initialize(type: ServerType, optionsServer: Contracts.Types.JsonObject): Promise<void> {
this.server = new HapiServer(this.getServerOptions(optionsServer));

this.serverType = type;

const timeout: number = this.pluginConfiguration().getRequired<number>("plugins.socketTimeout");
this.server.listener.timeout = timeout;
this.server.listener.keepAliveTimeout = timeout;
Expand Down Expand Up @@ -57,19 +68,19 @@ export abstract class AbstractServer {
try {
await this.server.start();

this.logger.info(`${this.name} Server started at ${this.server.info.uri}`);
this.logger.info(`${this.prettyName} Server started at ${this.server.info.uri}`);
} catch (error) {
await this.app.terminate(`Failed to start ${this.name} Server!`, error);
await this.app.terminate(`Failed to start ${this.prettyName} Server!`, error);
}
}

public async dispose(): Promise<void> {
try {
await this.server.stop();

this.logger.info(`${this.name} Server stopped at ${this.server.info.uri}`);
this.logger.info(`${this.prettyName} Server stopped at ${this.server.info.uri}`);
} catch (error) {
await this.app.terminate(`Failed to stop ${this.name} Server!`, error);
await this.app.terminate(`Failed to stop ${this.prettyName} Server!`, error);
}
}

Expand Down
95 changes: 95 additions & 0 deletions packages/api-common/source/service-provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { Providers } from "@mainsail/kernel";
import Joi from "joi";

import { preparePlugins } from "./plugins";
// import { preparePlugins } from "./plugins";
import { AbstractServer, ServerType } from "./server";

export type ServerConstructor<T extends AbstractServer> = new (...arguments_: any[]) => T;
export abstract class AbstractServiceProvider<T extends AbstractServer> extends Providers.ServiceProvider {
protected abstract httpIdentifier(): symbol;
protected abstract httpsIdentifier(): symbol;
protected abstract getServerConstructor(): ServerConstructor<T>;
protected abstract getHandlers(): any | any[];

public async register(): Promise<void> {
if (this.config().get("server.http.enabled")) {
await this.buildServer(ServerType.Http, this.httpIdentifier());
}

if (this.config().get("server.https.enabled")) {
await this.buildServer(ServerType.Https, this.httpsIdentifier());
}
}

public async boot(): Promise<void> {
if (this.config().get("server.http.enabled")) {
await this.app.get<T>(this.httpIdentifier()).boot();
}

if (this.config().get("server.https.enabled")) {
await this.app.get<T>(this.httpsIdentifier()).boot();
}
}

public async dispose(): Promise<void> {
if (this.config().get("server.http.enabled")) {
await this.app.get<T>(this.httpIdentifier()).dispose();
}

if (this.config().get("server.https.enabled")) {
await this.app.get<T>(this.httpsIdentifier()).dispose();
}
}

public configSchema(): Joi.ObjectSchema {
return Joi.object({
plugins: Joi.object({
pagination: Joi.object({
limit: Joi.number().integer().min(0).required(),
}).required(),
socketTimeout: Joi.number().integer().min(0).required(),
trustProxy: Joi.bool().required(),
whitelist: Joi.array().items(Joi.string()).required(),
}).required(),

server: Joi.object({
http: Joi.object({
enabled: Joi.bool().required(),
host: Joi.string().required(),
port: Joi.number().integer().min(1).max(65_535).required(),
}).required(),
https: Joi.object({
enabled: Joi.bool().required(),
host: Joi.string().required(),
port: Joi.number().integer().min(1).max(65_535).required(),
tls: Joi.object({
cert: Joi.string().when("...enabled", { is: true, then: Joi.required() }),
key: Joi.string().when("...enabled", { is: true, then: Joi.required() }),
}).required(),
}).required(),
}).required(),
}).unknown(true);
}

protected async buildServer(type: ServerType, id: symbol): Promise<void> {
this.app.bind<T>(id).to(this.getServerConstructor()).inSingletonScope();

const server = this.app.get<T>(id);

await server.initialize(type, {
...this.config().get(`server.${type.toLowerCase()}`),

routes: {
cors: true,
},
});

await server.register(preparePlugins(this.config().get("plugins")));

await server.register({
plugin: this.getHandlers(),
routes: { prefix: "/api" },
});
}
}
File renamed without changes.
File renamed without changes.
1 change: 0 additions & 1 deletion packages/api-http/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
"@mainsail/container": "workspace:*",
"@mainsail/contracts": "workspace:*",
"@mainsail/kernel": "workspace:*",
"@mainsail/transactions": "workspace:*",
"@mainsail/utils": "workspace:*",
"dayjs": "1.10.7",
"joi": "17.9.2",
Expand Down
4 changes: 4 additions & 0 deletions packages/api-http/source/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export class Server extends AbstractServer {
@tagged("plugin", "api-http")
private readonly configuration!: Providers.PluginConfiguration;

protected baseName(): string {
return "Public API";
}

protected pluginConfiguration(): Providers.PluginConfiguration {
return this.configuration;
}
Expand Down
129 changes: 39 additions & 90 deletions packages/api-http/source/service-provider.ts
Original file line number Diff line number Diff line change
@@ -1,109 +1,58 @@
import { Providers } from "@mainsail/kernel";
import { AbstractServiceProvider, ServerConstructor } from "@mainsail/api-common";
import Joi from "joi";

import Handlers from "./handlers";
import { Identifiers as ApiIdentifiers } from "./identifiers";
import { preparePlugins } from "./plugins";
import { Server } from "./server";

export class ServiceProvider extends Providers.ServiceProvider {
public async register(): Promise<void> {
if (this.config().get("server.http.enabled")) {
await this.buildServer("http", ApiIdentifiers.HTTP);
}

if (this.config().get("server.https.enabled")) {
await this.buildServer("https", ApiIdentifiers.HTTPS);
}
export class ServiceProvider extends AbstractServiceProvider<Server> {
protected httpIdentifier(): symbol {
return ApiIdentifiers.HTTP;
}

public async boot(): Promise<void> {
if (this.config().get("server.http.enabled")) {
await this.app.get<Server>(ApiIdentifiers.HTTP).boot();
}

if (this.config().get("server.https.enabled")) {
await this.app.get<Server>(ApiIdentifiers.HTTPS).boot();
}
protected httpsIdentifier(): symbol {
return ApiIdentifiers.HTTPS;
}

public async dispose(): Promise<void> {
if (this.config().get("server.http.enabled")) {
await this.app.get<Server>(ApiIdentifiers.HTTP).dispose();
}

if (this.config().get("server.https.enabled")) {
await this.app.get<Server>(ApiIdentifiers.HTTPS).dispose();
}
protected getServerConstructor(): ServerConstructor<Server> {
return Server;
}

public configSchema(): object {
return Joi.object({
options: Joi.object({
estimateTotalCount: Joi.bool().required(),
}).required(),
protected getHandlers(): any | any[] {
return Handlers;
}

plugins: Joi.object({
cache: Joi.object({
checkperiod: Joi.number().integer().min(0).required(),
enabled: Joi.bool().required(),
stdTTL: Joi.number().integer().min(0).required(),
}).required(),
log: Joi.object({
enabled: Joi.bool().required(),
public configSchema(): Joi.ObjectSchema {
return super.configSchema().concat(
Joi.object({
options: Joi.object({
estimateTotalCount: Joi.bool().required(),
}).required(),
pagination: Joi.object({
limit: Joi.number().integer().min(0).required(),
}).required(),
rateLimit: Joi.object({
blacklist: Joi.array().items(Joi.string()).required(),
duration: Joi.number().integer().min(0).required(),
enabled: Joi.bool().required(),
points: Joi.number().integer().min(0).required(),
whitelist: Joi.array().items(Joi.string()).required(),
}).required(),
socketTimeout: Joi.number().integer().min(0).required(),
trustProxy: Joi.bool().required(),
whitelist: Joi.array().items(Joi.string()).required(),
}).required(),

server: Joi.object({
http: Joi.object({
enabled: Joi.bool().required(),
host: Joi.string().required(),
port: Joi.number().integer().min(1).max(65_535).required(),
}).required(),
https: Joi.object({
enabled: Joi.bool().required(),
host: Joi.string().required(),
port: Joi.number().integer().min(1).max(65_535).required(),
tls: Joi.object({
cert: Joi.string().when("...enabled", { is: true, then: Joi.required() }),
key: Joi.string().when("...enabled", { is: true, then: Joi.required() }),
plugins: Joi.object({
cache: Joi.object({
checkperiod: Joi.number().integer().min(0).required(),
enabled: Joi.bool().required(),
stdTTL: Joi.number().integer().min(0).required(),
}).required(),
log: Joi.object({
enabled: Joi.bool().required(),
}).required(),
pagination: Joi.object({
limit: Joi.number().integer().min(0).required(),
}).required(),
rateLimit: Joi.object({
blacklist: Joi.array().items(Joi.string()).required(),
duration: Joi.number().integer().min(0).required(),
enabled: Joi.bool().required(),
points: Joi.number().integer().min(0).required(),
whitelist: Joi.array().items(Joi.string()).required(),
}).required(),
socketTimeout: Joi.number().integer().min(0).required(),
trustProxy: Joi.bool().required(),
whitelist: Joi.array().items(Joi.string()).required(),
}).required(),
}).required(),
}).unknown(true);
}

private async buildServer(type: string, id: symbol): Promise<void> {
this.app.bind<Server>(id).to(Server).inSingletonScope();

const server: Server = this.app.get<Server>(id);

await server.initialize(`Public API (${type.toUpperCase()})`, {
...this.config().get(`server.${type}`),

routes: {
cors: true,
},
});

await server.register(preparePlugins(this.config().get("plugins")));

await server.register({
plugin: Handlers,
routes: { prefix: "/api" },
});
}).unknown(true),
);
}
}
7 changes: 7 additions & 0 deletions packages/api-transaction-pool/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Path-based git attributes
# https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html

# Ignore all test and documentation with "export-ignore".
/.gitattributes export-ignore
/.gitignore export-ignore
/README.md export-ignore
Loading

0 comments on commit a68877e

Please sign in to comment.