Skip to content

Commit 4da9c43

Browse files
Merge pull request #50 from PaystackHQ/feature/implement-wrapped-endpoints
create endpoint for fetch wrapped top artists for a contributor
2 parents 8385009 + 0909e0a commit 4da9c43

File tree

4 files changed

+141
-10
lines changed

4 files changed

+141
-10
lines changed

controllers/index.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,39 @@ module.exports = {
154154
return res.status(500).send({ message: 'An error occurred' });
155155
}
156156
},
157+
158+
wrappedGetContributorTopArtists: async (req, res) => {
159+
try {
160+
const limit = req.query.limit || 3;
161+
const { year, name } = req.query;
162+
163+
const contributor = await serverMethods.getContributorByName({ name });
164+
165+
if (!contributor) {
166+
return res.status(400).send({
167+
status: false,
168+
message: 'We could not find any contributor with this name.',
169+
});
170+
}
171+
172+
// eslint-disable-next-line no-underscore-dangle
173+
const results = await serverMethods.getContributorTopArtists(year, contributor._id, limit);
174+
175+
const { about, profile_image: profileImage } = contributor;
176+
177+
const data = {
178+
contributor: { name, about, profileImage },
179+
artists: results,
180+
};
181+
182+
return res.status(200).send({
183+
status: true,
184+
data,
185+
});
186+
} catch (err) {
187+
logger.error(err);
188+
slack.sendMonitorMessage(err);
189+
return res.status(500).send({ message: 'An error occurred' });
190+
}
191+
},
157192
};

helpers/server-methods.js

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const color = require('./color');
77
const image = require('./image');
88
const util = require('./util');
99
const Playlist = require('../models/playlist');
10+
const Contributor = require('../models/contributor');
1011
const Wrapped2020Tracks = require('../models/Wrapped2020Tracks');
1112

1213
const trigger = async ({ day, month, year }) => {
@@ -107,27 +108,110 @@ const trigger = async ({ day, month, year }) => {
107108
};
108109

109110
/**
110-
* This method relies on the existence of the `2020Tracks` collection which is created
111-
* using a special aggregation found in `scripts/customAggregations/get2020Tracks.js`
112-
* Subsequent years can be done by creating the collection, model and adding the mapping
113-
* to this function.
114-
* @param {String} year
115-
* @param {Number} limit
111+
* Used to get the associated model for a particular year's collection of
112+
* tracks.
113+
* @param {string} year
116114
*/
117-
const getTopArtists = async ({ year, limit }) => {
115+
const getYearTracksModel = ({ year }) => {
118116
const years = {
119117
2020: Wrapped2020Tracks,
120118
};
121119

122120
// confirm it's a year we have data for.
123-
if (Object.keys(years).indexOf(year) < 0) {
124-
return [];
121+
if (!years[year]) {
122+
return null;
125123
}
126124

127-
const model = years[year];
125+
return years[year];
126+
};
127+
128+
/**
129+
* This method relies on the existence of the year's tracks collection e.g. `2020Tracks`,
130+
* created using an aggregation you can find in `scripts/customAggregations/get2020Tracks.js`
131+
* Subsequent years can be done by creating the collection, model and adding the mapping
132+
* to the `getYearTracksModel` function.
133+
* @param {String} year
134+
* @param {Number} limit
135+
*/
136+
const getTopArtists = async ({ year, limit }) => {
137+
const model = getYearTracksModel({ year });
138+
139+
if (!model) return [];
140+
141+
const agg = [
142+
{
143+
'$project': {
144+
'artist_names': {
145+
'$split': [
146+
'$artist_names', ', ',
147+
],
148+
},
149+
},
150+
}, {
151+
'$unwind': {
152+
'path': '$artist_names',
153+
'preserveNullAndEmptyArrays': false,
154+
},
155+
}, {
156+
'$sortByCount': '$artist_names',
157+
}, {
158+
'$limit': Number(limit),
159+
}, {
160+
'$lookup': {
161+
'from': 'artists',
162+
'localField': '_id',
163+
'foreignField': 'name',
164+
'as': 'details',
165+
},
166+
}, {
167+
'$project': {
168+
'_id': {
169+
'$arrayElemAt': [
170+
'$details._id', 0,
171+
],
172+
},
173+
'name': '$_id',
174+
'url': {
175+
'$arrayElemAt': [
176+
'$details.url', 0,
177+
],
178+
},
179+
'spotifyId': {
180+
'$arrayElemAt': [
181+
'$details.spotifyId', 0,
182+
],
183+
},
184+
'tracks': '$count',
185+
},
186+
},
187+
];
188+
return model.aggregate(agg);
189+
};
190+
191+
/**
192+
* This method relies on the existence of the year's tracks collection e.g. `2020Tracks`,
193+
* created using an aggregation you can find in `scripts/customAggregations/get2020Tracks.js`
194+
* Subsequent years can be done by creating the collection, model and adding the mapping
195+
* to the `getYearTracksModel` function.
196+
* @param {String} year
197+
* @param {ObjectId} contributorId
198+
* @param {Number} limit
199+
*/
200+
const getContributorTopArtists = async (year, contributorId, limit) => {
201+
const model = getYearTracksModel({ year });
202+
203+
if (!model) return [];
128204

129205
const agg = [
130206
{
207+
'$match': {
208+
'contributors': {
209+
'$in': [
210+
contributorId,
211+
],
212+
},
213+
},
214+
}, {
131215
'$project': {
132216
'artist_names': {
133217
'$split': [
@@ -173,10 +257,15 @@ const getTopArtists = async ({ year, limit }) => {
173257
},
174258
},
175259
];
260+
176261
return model.aggregate(agg);
177262
};
178263

264+
const getContributorByName = async ({ name }) => Contributor.findOne({ name });
265+
179266
module.exports = {
180267
trigger,
181268
getTopArtists,
269+
getContributorByName,
270+
getContributorTopArtists,
182271
};

routes/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ router.post('/reset', validateRequest(validationSchemas.resetBody), controllers.
2424
router.post('/webhook', controllers.webhook);
2525

2626
router.get('/wrapped/top-artists', validateRequest(validationSchemas.wrappedGetTopArtists, 'query'), controllers.wrappedGetTopArtists);
27+
router.get('/wrapped/top-artists/contributor', validateRequest(validationSchemas.wrappedGetContributorTopArtists, 'query'), controllers.wrappedGetContributorTopArtists);
2728

2829
module.exports = router;

validation/schema/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ module.exports = {
1818
limit: Joi.number(),
1919
year: Joi.number().required(),
2020
}),
21+
wrappedGetContributorTopArtists: Joi.object().keys({
22+
limit: Joi.number(),
23+
// matches a string with one space and possibly a hyphen
24+
name: Joi.string().regex(/[a-zA-Z\- ]/).required(),
25+
year: Joi.number().required(),
26+
}),
2127
getPlaylistByIdParams,
2228
resetBody,
2329
getAudioFeaturesParams,

0 commit comments

Comments
 (0)