forked from mochajs/mocha
-
Notifications
You must be signed in to change notification settings - Fork 0
/
supporters.js
153 lines (139 loc) · 4.34 KB
/
supporters.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#!/usr/bin/env node
'use strict';
const {mkdirSync} = require('fs');
const {writeFile} = require('fs').promises;
const {resolve} = require('path');
const debug = require('debug')('mocha:docs:data:supporters');
const needle = require('needle');
const imageSize = require('image-size');
const blacklist = new Set(require('./blacklist.json'));
const API_ENDPOINT = 'https://api.opencollective.com/graphql/v2';
const query = `query account($limit: Int, $offset: Int, $slug: String) {
account(slug: $slug) {
orders(limit: $limit, offset: $offset) {
limit
offset
totalCount
nodes {
fromAccount {
id
name
slug
website
imgUrlMed: imageUrl(height:64)
imgUrlSmall: imageUrl(height:32)
type
}
totalDonations {
value
}
createdAt
}
}
}
}`;
const graphqlPageSize = 1000;
const nodeToSupporter = node => ({
id: node.fromAccount.id,
name: node.fromAccount.name,
slug: node.fromAccount.slug,
website: node.fromAccount.website,
imgUrlMed: node.fromAccount.imgUrlMed,
imgUrlSmall: node.fromAccount.imgUrlSmall,
firstDonation: node.createdAt,
totalDonations: node.totalDonations.value * 100,
type: node.fromAccount.type
});
/**
* Retrieves donation data from OC
*
* Handles pagination
* @param {string} slug - Collective slug to get donation data from
* @returns {Promise<Object[]>} Array of raw donation data
*/
const getAllOrders = async (slug = 'mochajs') => {
let allOrders = [];
const variables = {limit: graphqlPageSize, offset: 0, slug};
// Handling pagination if necessary (2 pages for ~1400 results in May 2019)
while (true) {
const result = await needle(
'post',
API_ENDPOINT,
{query, variables},
{json: true}
);
const orders = result.body.data.account.orders.nodes;
allOrders = [...allOrders, ...orders];
variables.offset += graphqlPageSize;
if (orders.length < graphqlPageSize) {
debug('retrieved %d orders', allOrders.length);
return allOrders;
} else {
debug(
'loading page %d of orders...',
Math.floor(variables.offset / graphqlPageSize)
);
}
}
};
module.exports = async () => {
const orders = await getAllOrders();
// Deduplicating supporters with multiple orders
const uniqueSupporters = new Map();
const supporters = orders
.map(nodeToSupporter)
.filter(supporter => !blacklist.has(supporter.slug))
.reduce((supporters, supporter) => {
if (uniqueSupporters.has(supporter.slug)) {
// aggregate donation totals
uniqueSupporters.get(supporter.slug).totalDonations +=
supporter.totalDonations;
return supporters;
}
uniqueSupporters.set(supporter.slug, supporter);
return [...supporters, supporter];
}, [])
.sort((a, b) => b.totalDonations - a.totalDonations)
.reduce(
(supporters, supporter) => {
if (supporter.type === 'INDIVIDUAL') {
if (supporter.name !== 'anonymous') {
supporters.backers.push({
...supporter,
avatar: supporter.imgUrlSmall
});
}
} else {
supporters.sponsors.push({...supporter, avatar: supporter.imgUrlMed});
}
return supporters;
},
{sponsors: [], backers: []}
);
const supporterImagePath = resolve(__dirname, '../images/supporters');
mkdirSync(supporterImagePath, {recursive: true});
// Fetch images for sponsors and save their image dimensions
await Promise.all(
supporters.sponsors.map(async sponsor => {
const filePath = resolve(supporterImagePath, sponsor.id + '.png');
const {body} = await needle('get', encodeURI(sponsor.avatar));
sponsor.dimensions = imageSize(body);
await writeFile(filePath, body);
})
);
// Fetch images for backers and save their image dimensions
await Promise.all(
supporters.backers.map(async backer => {
const filePath = resolve(supporterImagePath, backer.id + '.png');
const {body} = await needle('get', encodeURI(backer.avatar));
await writeFile(filePath, body);
})
);
debug(
'found %d valid backers and %d valid sponsors (%d total)',
supporters.backers.length,
supporters.sponsors.length,
supporters.backers.length + supporters.sponsors.length
);
return supporters;
};