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

Commit 9791995

Browse files
authored
Introduce related apps endpoint (#432)
1 parent 25c7dff commit 9791995

File tree

2 files changed

+227
-1
lines changed

2 files changed

+227
-1
lines changed

src/app/routes/v1/app.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ const Router = require('koa-router');
33
const { TIME_PERIOD } = require('../../../constants');
44
const getAppBySlug = require('../../../apps/get-app-by-slug');
55
const getAppStatsForPeriod = require('../../../apps/get-app-stats-for-period');
6+
const getRelatedAppsForPeriod = require('../../../apps/get-related-apps-for-period');
7+
const getTokensForAppInPeriod = require('../../../tokens/get-tokens-for-app-in-period');
68
const middleware = require('../../middleware');
79
const transformApp = require('./util/transform-app');
8-
const getTokensForAppInPeriod = require('../../../tokens/get-tokens-for-app-in-period');
910

1011
const createRouter = () => {
1112
const router = new Router({ prefix: '/apps/:slug' });
@@ -81,6 +82,51 @@ const createRouter = () => {
8182
},
8283
);
8384

85+
router.get(
86+
'/related-apps',
87+
middleware.timePeriod('statsPeriod', TIME_PERIOD.DAY),
88+
middleware.enum('sortBy', ['tradeCount', 'tradeVolume'], 'tradeVolume'),
89+
middleware.pagination({
90+
defaultLimit: 20,
91+
maxLimit: 50,
92+
maxPage: Infinity,
93+
}),
94+
async ({ pagination, params, response }, next) => {
95+
const { slug, sortBy, statsPeriod } = params;
96+
const { limit, page } = pagination;
97+
98+
const app = await getAppBySlug(slug);
99+
100+
if (app === null) {
101+
response.status = 404;
102+
await next();
103+
return;
104+
}
105+
106+
const { apps, resultCount } = await getRelatedAppsForPeriod(
107+
app._id,
108+
statsPeriod,
109+
{
110+
sortBy,
111+
limit,
112+
page,
113+
},
114+
);
115+
116+
response.body = {
117+
apps,
118+
limit,
119+
page,
120+
pageCount: Math.ceil(resultCount / limit),
121+
sortBy,
122+
statsPeriod,
123+
total: resultCount,
124+
};
125+
126+
await next();
127+
},
128+
);
129+
84130
return router;
85131
};
86132

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
const _ = require('lodash');
2+
const { FILL_ATTRIBUTION_TYPE } = require('../constants');
3+
4+
const AttributionEntity = require('../model/attribution-entity');
5+
const elasticsearch = require('../util/elasticsearch');
6+
const getDatesForPeriod = require('../util/get-dates-for-time-period');
7+
8+
const getRelatedAppsForPeriod = async (appId, period, options) => {
9+
const { limit, page, sortBy } = options;
10+
const { dateFrom, dateTo } = getDatesForPeriod(period);
11+
12+
const startIndex = (page - 1) * limit;
13+
14+
const response = await elasticsearch.getClient().search({
15+
index: 'fills',
16+
body: {
17+
query: {
18+
bool: {
19+
filter: [
20+
{
21+
range: {
22+
date: {
23+
gte: dateFrom,
24+
lte: dateTo,
25+
},
26+
},
27+
},
28+
{
29+
nested: {
30+
path: 'attributions',
31+
query: {
32+
term: {
33+
'attributions.id': appId,
34+
},
35+
},
36+
},
37+
},
38+
],
39+
},
40+
},
41+
aggs: {
42+
attributions: {
43+
nested: {
44+
path: 'attributions',
45+
},
46+
aggs: {
47+
apps: {
48+
filter: {
49+
bool: {
50+
filter: {
51+
terms: {
52+
'attributions.type': [0, 1],
53+
},
54+
},
55+
must_not: {
56+
term: {
57+
'attributions.id': appId,
58+
},
59+
},
60+
},
61+
},
62+
aggs: {
63+
stats_by_app: {
64+
terms: {
65+
field: 'attributions.id',
66+
size: limit * page,
67+
order: { [`attribution>${sortBy}`]: 'desc' },
68+
},
69+
aggs: {
70+
attribution: {
71+
reverse_nested: {},
72+
aggs: {
73+
tradeCount: {
74+
sum: {
75+
field: 'tradeCountContribution',
76+
},
77+
},
78+
tradeVolume: {
79+
sum: {
80+
field: 'tradeVolume',
81+
},
82+
},
83+
},
84+
},
85+
by_type: {
86+
terms: {
87+
field: 'attributions.type',
88+
size: 10,
89+
},
90+
aggs: {
91+
attribution: {
92+
reverse_nested: {},
93+
aggs: {
94+
tradeCount: {
95+
sum: {
96+
field: 'tradeCountContribution',
97+
},
98+
},
99+
tradeVolume: {
100+
sum: {
101+
field: 'tradeVolume',
102+
},
103+
},
104+
},
105+
},
106+
},
107+
},
108+
bucket_truncate: {
109+
bucket_sort: {
110+
size: limit,
111+
from: startIndex,
112+
},
113+
},
114+
},
115+
},
116+
appCount: {
117+
cardinality: {
118+
field: 'attributions.id',
119+
},
120+
},
121+
},
122+
},
123+
},
124+
},
125+
},
126+
},
127+
});
128+
129+
const aggregations = response.body.aggregations.attributions.apps;
130+
const { buckets } = aggregations.stats_by_app;
131+
const appIds = buckets.map(bucket => bucket.key);
132+
const apps = await AttributionEntity.find({ _id: { $in: appIds } }).lean();
133+
const appCount = aggregations.appCount.value;
134+
135+
const appsWithStats = buckets.map(bucket => {
136+
const app = apps.find(a => a._id === bucket.key);
137+
138+
const getStatByType = (type, stat) => {
139+
const typeBucket = bucket.by_type.buckets.find(b => b.key === type);
140+
141+
if (typeBucket === undefined) {
142+
return 0;
143+
}
144+
145+
return typeBucket.attribution[stat].value;
146+
};
147+
148+
return {
149+
categories: app.categories,
150+
description: _.get(app, 'description', null),
151+
id: app._id,
152+
logoUrl: _.get(app, 'logoUrl', null),
153+
name: app.name,
154+
stats: {
155+
tradeCount: {
156+
relayer: getStatByType(FILL_ATTRIBUTION_TYPE.RELAYER, 'tradeCount'),
157+
consumer: getStatByType(FILL_ATTRIBUTION_TYPE.CONSUMER, 'tradeCount'),
158+
total: bucket.attribution.tradeCount.value,
159+
},
160+
tradeVolume: {
161+
relayer: getStatByType(FILL_ATTRIBUTION_TYPE.RELAYER, 'tradeVolume'),
162+
consumer: getStatByType(
163+
FILL_ATTRIBUTION_TYPE.CONSUMER,
164+
'tradeVolume',
165+
),
166+
total: bucket.attribution.tradeVolume.value,
167+
},
168+
},
169+
urlSlug: app.urlSlug,
170+
websiteUrl: _.get(app, 'websiteUrl', null),
171+
};
172+
});
173+
174+
return {
175+
apps: appsWithStats,
176+
resultCount: appCount,
177+
};
178+
};
179+
180+
module.exports = getRelatedAppsForPeriod;

0 commit comments

Comments
 (0)