Skip to content
This repository has been archived by the owner on Aug 12, 2023. It is now read-only.

Commit

Permalink
Introduce related apps endpoint (#432)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbovis authored Oct 22, 2020
1 parent 25c7dff commit 9791995
Show file tree
Hide file tree
Showing 2 changed files with 227 additions and 1 deletion.
48 changes: 47 additions & 1 deletion src/app/routes/v1/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ const Router = require('koa-router');
const { TIME_PERIOD } = require('../../../constants');
const getAppBySlug = require('../../../apps/get-app-by-slug');
const getAppStatsForPeriod = require('../../../apps/get-app-stats-for-period');
const getRelatedAppsForPeriod = require('../../../apps/get-related-apps-for-period');
const getTokensForAppInPeriod = require('../../../tokens/get-tokens-for-app-in-period');
const middleware = require('../../middleware');
const transformApp = require('./util/transform-app');
const getTokensForAppInPeriod = require('../../../tokens/get-tokens-for-app-in-period');

const createRouter = () => {
const router = new Router({ prefix: '/apps/:slug' });
Expand Down Expand Up @@ -81,6 +82,51 @@ const createRouter = () => {
},
);

router.get(
'/related-apps',
middleware.timePeriod('statsPeriod', TIME_PERIOD.DAY),
middleware.enum('sortBy', ['tradeCount', 'tradeVolume'], 'tradeVolume'),
middleware.pagination({
defaultLimit: 20,
maxLimit: 50,
maxPage: Infinity,
}),
async ({ pagination, params, response }, next) => {
const { slug, sortBy, statsPeriod } = params;
const { limit, page } = pagination;

const app = await getAppBySlug(slug);

if (app === null) {
response.status = 404;
await next();
return;
}

const { apps, resultCount } = await getRelatedAppsForPeriod(
app._id,
statsPeriod,
{
sortBy,
limit,
page,
},
);

response.body = {
apps,
limit,
page,
pageCount: Math.ceil(resultCount / limit),
sortBy,
statsPeriod,
total: resultCount,
};

await next();
},
);

return router;
};

Expand Down
180 changes: 180 additions & 0 deletions src/apps/get-related-apps-for-period.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
const _ = require('lodash');
const { FILL_ATTRIBUTION_TYPE } = require('../constants');

const AttributionEntity = require('../model/attribution-entity');
const elasticsearch = require('../util/elasticsearch');
const getDatesForPeriod = require('../util/get-dates-for-time-period');

const getRelatedAppsForPeriod = async (appId, period, options) => {
const { limit, page, sortBy } = options;
const { dateFrom, dateTo } = getDatesForPeriod(period);

const startIndex = (page - 1) * limit;

const response = await elasticsearch.getClient().search({
index: 'fills',
body: {
query: {
bool: {
filter: [
{
range: {
date: {
gte: dateFrom,
lte: dateTo,
},
},
},
{
nested: {
path: 'attributions',
query: {
term: {
'attributions.id': appId,
},
},
},
},
],
},
},
aggs: {
attributions: {
nested: {
path: 'attributions',
},
aggs: {
apps: {
filter: {
bool: {
filter: {
terms: {
'attributions.type': [0, 1],
},
},
must_not: {
term: {
'attributions.id': appId,
},
},
},
},
aggs: {
stats_by_app: {
terms: {
field: 'attributions.id',
size: limit * page,
order: { [`attribution>${sortBy}`]: 'desc' },
},
aggs: {
attribution: {
reverse_nested: {},
aggs: {
tradeCount: {
sum: {
field: 'tradeCountContribution',
},
},
tradeVolume: {
sum: {
field: 'tradeVolume',
},
},
},
},
by_type: {
terms: {
field: 'attributions.type',
size: 10,
},
aggs: {
attribution: {
reverse_nested: {},
aggs: {
tradeCount: {
sum: {
field: 'tradeCountContribution',
},
},
tradeVolume: {
sum: {
field: 'tradeVolume',
},
},
},
},
},
},
bucket_truncate: {
bucket_sort: {
size: limit,
from: startIndex,
},
},
},
},
appCount: {
cardinality: {
field: 'attributions.id',
},
},
},
},
},
},
},
},
});

const aggregations = response.body.aggregations.attributions.apps;
const { buckets } = aggregations.stats_by_app;
const appIds = buckets.map(bucket => bucket.key);
const apps = await AttributionEntity.find({ _id: { $in: appIds } }).lean();
const appCount = aggregations.appCount.value;

const appsWithStats = buckets.map(bucket => {
const app = apps.find(a => a._id === bucket.key);

const getStatByType = (type, stat) => {
const typeBucket = bucket.by_type.buckets.find(b => b.key === type);

if (typeBucket === undefined) {
return 0;
}

return typeBucket.attribution[stat].value;
};

return {
categories: app.categories,
description: _.get(app, 'description', null),
id: app._id,
logoUrl: _.get(app, 'logoUrl', null),
name: app.name,
stats: {
tradeCount: {
relayer: getStatByType(FILL_ATTRIBUTION_TYPE.RELAYER, 'tradeCount'),
consumer: getStatByType(FILL_ATTRIBUTION_TYPE.CONSUMER, 'tradeCount'),
total: bucket.attribution.tradeCount.value,
},
tradeVolume: {
relayer: getStatByType(FILL_ATTRIBUTION_TYPE.RELAYER, 'tradeVolume'),
consumer: getStatByType(
FILL_ATTRIBUTION_TYPE.CONSUMER,
'tradeVolume',
),
total: bucket.attribution.tradeVolume.value,
},
},
urlSlug: app.urlSlug,
websiteUrl: _.get(app, 'websiteUrl', null),
};
});

return {
apps: appsWithStats,
resultCount: appCount,
};
};

module.exports = getRelatedAppsForPeriod;

0 comments on commit 9791995

Please sign in to comment.