Skip to content

Commit

Permalink
refactor(core-api): integrate hapi-pagination to replace fork (#2994)
Browse files Browse the repository at this point in the history
  • Loading branch information
faustbrian authored and spkjp committed Oct 2, 2019
1 parent ee6fe40 commit 279585b
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 252 deletions.
1 change: 0 additions & 1 deletion packages/core-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"@hapi/joi": "^15.1.0",
"ajv": "^6.10.2",
"dayjs": "^1.8.15",
"hapi-pagination": "https://github.com/faustbrian/hapi-pagination#f4991348ca779b68b8e7139cfcbc601e6d496612",
"hapi-rate-limit": "^4.0.0",
"ip": "^1.1.5",
"lodash.groupby": "^4.6.0",
Expand Down
30 changes: 0 additions & 30 deletions packages/core-api/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,36 +38,6 @@ export const defaults = {
// @see https://github.com/fknop/hapi-pagination
pagination: {
limit: 100,
include: [
"/api/blocks",
"/api/blocks/{id}/transactions",
"/api/blocks/search",
"/api/bridgechains",
"/api/bridgechains/search",
"/api/businesses",
"/api/businesses/{id}/bridgechains",
"/api/businesses/search",
"/api/delegates",
"/api/delegates/{id}/blocks",
"/api/delegates/{id}/voters",
"/api/delegates/search",
"/api/locks",
"/api/locks/search",
"/api/locks/unlocked",
"/api/peers",
"/api/transactions",
"/api/transactions/search",
"/api/transactions/unconfirmed",
"/api/votes",
"/api/wallets",
"/api/wallets/top",
"/api/wallets/{id}/locks",
"/api/wallets/{id}/transactions",
"/api/wallets/{id}/transactions/received",
"/api/wallets/{id}/transactions/sent",
"/api/wallets/{id}/votes",
"/api/wallets/search",
],
},
whitelist: ["*"],
plugins: [],
Expand Down
3 changes: 0 additions & 3 deletions packages/core-api/src/handlers/transactions/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@ export const registerRoutes = (server: Hapi.Server): void => {
handler: controller.store,
options: {
plugins: {
pagination: {
enabled: false,
},
"hapi-ajv": {
payloadSchema: Schema.store,
},
Expand Down
18 changes: 18 additions & 0 deletions packages/core-api/src/plugins/pagination/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Based on https://github.com/fknop/hapi-pagination

import Joi from "@hapi/joi";

export const getConfig = options => {
const { error, value } = Joi.validate(options, {
query: Joi.object({
limit: Joi.object({
default: Joi.number()
.integer()
.positive()
.default(100),
}),
}),
});

return { error: error || undefined, config: error ? undefined : value };
};
37 changes: 37 additions & 0 deletions packages/core-api/src/plugins/pagination/decorate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Based on https://github.com/fknop/hapi-pagination

import { internal } from "@hapi/boom";

export const decorate = () => {
return {
paginate(response, totalCount, options) {
options = options || {};

const key = options.key;

if (Array.isArray(response) && key) {
throw internal("Object required with results key");
}

if (!Array.isArray(response) && !key) {
throw internal("Missing results key");
}

if (key && !response[key]) {
throw internal(`key: ${key} does not exists on response`);
}

const results = key ? response[key] : response;

if (key) {
delete response[key];
}

return this.response({
results,
totalCount,
response: Array.isArray(response) ? undefined : response,
});
},
};
};
157 changes: 157 additions & 0 deletions packages/core-api/src/plugins/pagination/ext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
// Based on https://github.com/fknop/hapi-pagination

import Hoek from "@hapi/hoek";
import { get } from "dottie";
import Qs from "querystring";

interface IRoute {
method: string;
path: string;
}

export class Ext {
private readonly routes: IRoute[] = [
{ method: "get", path: "/api/blocks" },
{ method: "get", path: "/api/blocks/{id}/transactions" },
{ method: "post", path: "/api/blocks/search" },
{ method: "get", path: "/api/bridgechains" },
{ method: "post", path: "/api/bridgechains/search" },
{ method: "get", path: "/api/businesses" },
{ method: "get", path: "/api/businesses/{id}/bridgechains" },
{ method: "post", path: "/api/businesses/search" },
{ method: "get", path: "/api/delegates" },
{ method: "get", path: "/api/delegates/{id}/blocks" },
{ method: "get", path: "/api/delegates/{id}/voters" },
{ method: "post", path: "/api/delegates/search" },
{ method: "get", path: "/api/locks" },
{ method: "post", path: "/api/locks/search" },
{ method: "post", path: "/api/locks/unlocked" },
{ method: "get", path: "/api/peers" },
{ method: "get", path: "/api/transactions" },
{ method: "post", path: "/api/transactions/search" },
{ method: "get", path: "/api/transactions/unconfirmed" },
{ method: "get", path: "/api/votes" },
{ method: "get", path: "/api/wallets" },
{ method: "get", path: "/api/wallets/top" },
{ method: "get", path: "/api/wallets/{id}/locks" },
{ method: "get", path: "/api/wallets/{id}/transactions" },
{ method: "get", path: "/api/wallets/{id}/transactions/received" },
{ method: "get", path: "/api/wallets/{id}/transactions/sent" },
{ method: "get", path: "/api/wallets/{id}/votes" },
{ method: "post", path: "/api/wallets/search" },
];

constructor(private readonly config) {}

public isValidRoute(request) {
if (!this.hasPagination(request)) {
return false;
}

const { method, path } = request.route;

return this.routes.find(route => route.method === method && route.path === path) !== undefined;
}

public onPreHandler(request, h) {
if (this.isValidRoute(request)) {
const setParam = (name, defaultValue) => {
let value;

if (request.query[name]) {
value = parseInt(request.query[name]);

if (Number.isNaN(value)) {
value = defaultValue;
}
}

request.query[name] = value || defaultValue;

return undefined;
};

setParam("page", 1);
setParam("limit", get(this.config, "query.limit.default", 100));
}

return h.continue;
}

public onPostHandler(request, h) {
const { statusCode } = request.response;
const processResponse: boolean =
this.isValidRoute(request) && statusCode >= 200 && statusCode <= 299 && this.hasPagination(request);

if (!processResponse) {
return h.continue;
}

const { source } = request.response;
const results = Array.isArray(source) ? source : source.results;

Hoek.assert(Array.isArray(results), "The results must be an array");

const baseUri = request.url.pathname + "?";
const { query } = request;
const currentPage = query.page;
const currentLimit = query.limit;

const { totalCount } = !!source.totalCount ? source : request;

let pageCount: number;
if (totalCount) {
pageCount = Math.trunc(totalCount / currentLimit) + (totalCount % currentLimit === 0 ? 0 : 1);
}

const getUri = (page: number | null): string =>
// tslint:disable-next-line: no-null-keyword
page ? baseUri + Qs.stringify(Hoek.applyToDefaults({ ...query, ...request.orig.query }, { page })) : null;

const newSource = {
meta: {
...(source.meta || {}),
...{
count: results.length,
pageCount: pageCount || 1,
totalCount: totalCount ? totalCount : 0,

// tslint:disable-next-line: no-null-keyword
next: totalCount && currentPage < pageCount ? getUri(currentPage + 1) : null,
previous:
// tslint:disable-next-line: no-null-keyword
totalCount && currentPage > 1 && currentPage <= pageCount + 1 ? getUri(currentPage - 1) : null,

self: getUri(currentPage),
first: getUri(1),
last: getUri(pageCount),
},
},
data: results,
};

if (source.response) {
const keys = Object.keys(source.response);

for (const key of keys) {
if (key !== "meta" && key !== "data") {
newSource[key] = source.response[key];
}
}
}

request.response.source = newSource;

return h.continue;
}

public hasPagination(request) {
const routeOptions = this.getRouteOptions(request);

return Object.prototype.hasOwnProperty.call(routeOptions, "pagination") ? routeOptions.pagination : true;
}

private getRouteOptions(request) {
return request.route.settings.plugins.pagination || {};
}
}
28 changes: 28 additions & 0 deletions packages/core-api/src/plugins/pagination/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Based on https://github.com/fknop/hapi-pagination

import { getConfig } from "./config";
import { decorate } from "./decorate";
import { Ext } from "./ext";

exports.plugin = {
name: "hapi-pagination",
version: "1.0.0",
register(server, options) {
const { error, config } = getConfig(options);

if (error) {
throw error;
}

try {
server.decorate("toolkit", "paginate", decorate().paginate);
} catch {
//
}

const ext = new Ext(config);

server.ext("onPreHandler", (request, h) => ext.onPreHandler(request, h));
server.ext("onPostHandler", (request, h) => ext.onPostHandler(request, h));
},
};
15 changes: 3 additions & 12 deletions packages/core-api/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { app } from "@arkecosystem/core-container";
import { createServer, mountServer, plugins } from "@arkecosystem/core-http-utils";
import { Logger } from "@arkecosystem/core-interfaces";
import Hapi from "@hapi/hapi";
import { get } from "dottie";

export class Server {
private logger = app.resolvePlugin<Logger.ILogger>("logger");
Expand Down Expand Up @@ -90,23 +91,13 @@ export class Server {
});

await server.register({
plugin: require("hapi-pagination"),
plugin: require("./plugins/pagination"),
options: {
meta: {
baseUri: "",
},
query: {
limit: {
default: this.config.pagination.limit,
default: get(this.config, "pagination.limit", 100),
},
},
results: {
name: "data",
},
routes: {
include: this.config.pagination.include,
exclude: ["*"],
},
},
});

Expand Down
Loading

0 comments on commit 279585b

Please sign in to comment.