refactor [liberapay] service (#2879)
This commit is contained in:
76
services/liberapay/liberapay-base.js
Normal file
76
services/liberapay/liberapay-base.js
Normal file
@@ -0,0 +1,76 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { metric } = require('../../lib/text-formatters')
|
||||
const { BaseJsonService } = require('..')
|
||||
const { colorScale } = require('../../lib/color-formatters')
|
||||
const { nonNegativeInteger } = require('../validators')
|
||||
|
||||
const schema = Joi.object({
|
||||
npatrons: nonNegativeInteger,
|
||||
giving: Joi.object()
|
||||
.keys({
|
||||
amount: Joi.string().required(),
|
||||
currency: Joi.string().required(),
|
||||
})
|
||||
.allow(null)
|
||||
.required(),
|
||||
receiving: Joi.object()
|
||||
.keys({
|
||||
amount: Joi.string().required(),
|
||||
currency: Joi.string().required(),
|
||||
})
|
||||
.allow(null)
|
||||
.required(),
|
||||
goal: Joi.object()
|
||||
.keys({
|
||||
amount: Joi.string().required(),
|
||||
})
|
||||
.allow(null)
|
||||
.required(),
|
||||
}).required()
|
||||
|
||||
const isCurrencyOverTime = Joi.string().regex(
|
||||
/^([0-9]*[1-9][0-9]*(\.[0-9]+)?|[0]+\.[0-9]*[1-9][0-9]*)[ A-Za-z]{4}\/week/
|
||||
)
|
||||
|
||||
function renderCurrencyBadge({ label, amount, currency }) {
|
||||
return {
|
||||
label,
|
||||
message: `${metric(amount)} ${currency}/week`,
|
||||
color: colorScale([0, 10, 100])(amount),
|
||||
}
|
||||
}
|
||||
|
||||
class LiberapayBase extends BaseJsonService {
|
||||
static get category() {
|
||||
return 'funding'
|
||||
}
|
||||
|
||||
static get defaultBadgeData() {
|
||||
return {
|
||||
label: 'liberapay',
|
||||
namedLogo: 'liberapay',
|
||||
}
|
||||
}
|
||||
|
||||
async fetch({ entity }) {
|
||||
return this._requestJson({
|
||||
schema,
|
||||
url: `https://liberapay.com/${entity}/public.json`,
|
||||
})
|
||||
}
|
||||
|
||||
static buildRoute(badgeName) {
|
||||
return {
|
||||
base: `liberapay/${badgeName}`,
|
||||
pattern: ':entity',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
renderCurrencyBadge,
|
||||
LiberapayBase,
|
||||
isCurrencyOverTime,
|
||||
}
|
||||
37
services/liberapay/liberapay-gives.service.js
Normal file
37
services/liberapay/liberapay-gives.service.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
const { InvalidResponse } = require('..')
|
||||
const { renderCurrencyBadge, LiberapayBase } = require('./liberapay-base')
|
||||
|
||||
module.exports = class LiberapayGives extends LiberapayBase {
|
||||
static get route() {
|
||||
return this.buildRoute('gives')
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Liberapay giving',
|
||||
namedParams: { entity: 'Changaco' },
|
||||
staticPreview: renderCurrencyBadge({
|
||||
label: 'gives',
|
||||
amount: '2.58',
|
||||
currency: 'EUR',
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async handle({ entity }) {
|
||||
const data = await this.fetch({ entity })
|
||||
if (data.giving) {
|
||||
return renderCurrencyBadge({
|
||||
label: 'gives',
|
||||
amount: data.giving.amount,
|
||||
currency: data.giving.currency,
|
||||
})
|
||||
} else {
|
||||
throw new InvalidResponse({ prettyMessage: 'no public giving stats' })
|
||||
}
|
||||
}
|
||||
}
|
||||
32
services/liberapay/liberapay-gives.tester.js
Normal file
32
services/liberapay/liberapay-gives.tester.js
Normal file
@@ -0,0 +1,32 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { isCurrencyOverTime } = require('./liberapay-base')
|
||||
const t = (module.exports = require('..').createServiceTester())
|
||||
|
||||
t.create('Giving (valid)')
|
||||
.get('/Changaco.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'gives',
|
||||
value: isCurrencyOverTime,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('Giving (not found)')
|
||||
.get('/does-not-exist.json')
|
||||
.expectJSON({ name: 'liberapay', value: 'not found' })
|
||||
|
||||
t.create('Giving (null)')
|
||||
.get('/Liberapay.json')
|
||||
.intercept(nock =>
|
||||
nock('https://liberapay.com')
|
||||
.get('/Liberapay/public.json')
|
||||
.reply(200, {
|
||||
npatrons: 0,
|
||||
giving: null,
|
||||
receiving: null,
|
||||
goal: null,
|
||||
})
|
||||
)
|
||||
.expectJSON({ name: 'liberapay', value: 'no public giving stats' })
|
||||
41
services/liberapay/liberapay-goal.service.js
Normal file
41
services/liberapay/liberapay-goal.service.js
Normal file
@@ -0,0 +1,41 @@
|
||||
'use strict'
|
||||
|
||||
const { InvalidResponse } = require('..')
|
||||
const { LiberapayBase } = require('./liberapay-base')
|
||||
const { colorScale } = require('../../lib/color-formatters')
|
||||
|
||||
module.exports = class LiberapayGoal extends LiberapayBase {
|
||||
static get route() {
|
||||
return this.buildRoute('goal')
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Liberapay goal progress',
|
||||
namedParams: { entity: 'Changaco' },
|
||||
staticPreview: this.render({ percentAcheived: 33 }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ percentAcheived }) {
|
||||
return {
|
||||
label: 'goal progress',
|
||||
message: `${percentAcheived}%`,
|
||||
color: colorScale([0, 10, 100])(percentAcheived),
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ entity }) {
|
||||
const data = await this.fetch({ entity })
|
||||
if (data.goal) {
|
||||
const percentAcheived = Math.round(
|
||||
(data.receiving.amount / data.goal.amount) * 100
|
||||
)
|
||||
return this.constructor.render({ percentAcheived })
|
||||
} else {
|
||||
throw new InvalidResponse({ prettyMessage: 'no public goals' })
|
||||
}
|
||||
}
|
||||
}
|
||||
32
services/liberapay/liberapay-goal.tester.js
Normal file
32
services/liberapay/liberapay-goal.tester.js
Normal file
@@ -0,0 +1,32 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { isIntegerPercentage } = require('../test-validators')
|
||||
const t = (module.exports = require('..').createServiceTester())
|
||||
|
||||
t.create('Goal Progress (valid)')
|
||||
.get('/Liberapay.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'goal progress',
|
||||
value: isIntegerPercentage,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('Goal Progress (not found)')
|
||||
.get('/does-not-exist.json')
|
||||
.expectJSON({ name: 'liberapay', value: 'not found' })
|
||||
|
||||
t.create('Goal Progress (no goal set)')
|
||||
.get('/Liberapay.json')
|
||||
.intercept(nock =>
|
||||
nock('https://liberapay.com')
|
||||
.get('/Liberapay/public.json')
|
||||
.reply(200, {
|
||||
npatrons: 0,
|
||||
giving: null,
|
||||
receiving: null,
|
||||
goal: null,
|
||||
})
|
||||
)
|
||||
.expectJSON({ name: 'liberapay', value: 'no public goals' })
|
||||
34
services/liberapay/liberapay-patrons.service.js
Normal file
34
services/liberapay/liberapay-patrons.service.js
Normal file
@@ -0,0 +1,34 @@
|
||||
'use strict'
|
||||
|
||||
const { LiberapayBase } = require('./liberapay-base')
|
||||
const { metric } = require('../../lib/text-formatters')
|
||||
const { colorScale } = require('../../lib/color-formatters')
|
||||
|
||||
module.exports = class LiberapayPatrons extends LiberapayBase {
|
||||
static get route() {
|
||||
return this.buildRoute('patrons')
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Liberapay patrons',
|
||||
namedParams: { entity: 'Changaco' },
|
||||
staticPreview: this.render({ patrons: 10 }),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static render({ patrons }) {
|
||||
return {
|
||||
label: 'patrons',
|
||||
message: metric(patrons),
|
||||
color: colorScale([0, 10, 100])(patrons),
|
||||
}
|
||||
}
|
||||
|
||||
async handle({ entity }) {
|
||||
const data = await this.fetch({ entity })
|
||||
return this.constructor.render({ patrons: data.npatrons })
|
||||
}
|
||||
}
|
||||
18
services/liberapay/liberapay-patrons.tester.js
Normal file
18
services/liberapay/liberapay-patrons.tester.js
Normal file
@@ -0,0 +1,18 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { isMetric } = require('../test-validators')
|
||||
const t = (module.exports = require('..').createServiceTester())
|
||||
|
||||
t.create('Patrons (valid)')
|
||||
.get('/Liberapay.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'patrons',
|
||||
value: isMetric,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('Patrons (not found)')
|
||||
.get('/does-not-exist.json')
|
||||
.expectJSON({ name: 'liberapay', value: 'not found' })
|
||||
37
services/liberapay/liberapay-receives.service.js
Normal file
37
services/liberapay/liberapay-receives.service.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict'
|
||||
|
||||
const { InvalidResponse } = require('..')
|
||||
const { renderCurrencyBadge, LiberapayBase } = require('./liberapay-base')
|
||||
|
||||
module.exports = class LiberapayReceives extends LiberapayBase {
|
||||
static get route() {
|
||||
return this.buildRoute('receives')
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Liberapay receiving',
|
||||
namedParams: { entity: 'Changaco' },
|
||||
staticPreview: renderCurrencyBadge({
|
||||
label: 'receives',
|
||||
amount: '98.32',
|
||||
currency: 'EUR',
|
||||
}),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async handle({ entity }) {
|
||||
const data = await this.fetch({ entity })
|
||||
if (data.receiving) {
|
||||
return renderCurrencyBadge({
|
||||
label: 'receives',
|
||||
amount: data.receiving.amount,
|
||||
currency: data.receiving.currency,
|
||||
})
|
||||
} else {
|
||||
throw new InvalidResponse({ prettyMessage: 'no public receiving stats' })
|
||||
}
|
||||
}
|
||||
}
|
||||
32
services/liberapay/liberapay-receives.tester.js
Normal file
32
services/liberapay/liberapay-receives.tester.js
Normal file
@@ -0,0 +1,32 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { isCurrencyOverTime } = require('./liberapay-base')
|
||||
const t = (module.exports = require('..').createServiceTester())
|
||||
|
||||
t.create('Receiving (valid)')
|
||||
.get('/Changaco.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'receives',
|
||||
value: isCurrencyOverTime,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('Receiving (not found)')
|
||||
.get('/does-not-exist.json')
|
||||
.expectJSON({ name: 'liberapay', value: 'not found' })
|
||||
|
||||
t.create('Receiving (null)')
|
||||
.get('/Liberapay.json')
|
||||
.intercept(nock =>
|
||||
nock('https://liberapay.com')
|
||||
.get('/Liberapay/public.json')
|
||||
.reply(200, {
|
||||
npatrons: 0,
|
||||
giving: null,
|
||||
receiving: null,
|
||||
goal: null,
|
||||
})
|
||||
)
|
||||
.expectJSON({ name: 'liberapay', value: 'no public receiving stats' })
|
||||
@@ -1,145 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const LegacyService = require('../legacy-service')
|
||||
const { makeBadgeData: getBadgeData } = require('../../lib/badge-data')
|
||||
const { metric } = require('../../lib/text-formatters')
|
||||
const { makeLogo: getLogo } = require('../../lib/logos')
|
||||
const { colorScale } = require('../../lib/color-formatters')
|
||||
|
||||
// This legacy service should be rewritten to use e.g. BaseJsonService.
|
||||
//
|
||||
// Tips for rewriting:
|
||||
// https://github.com/badges/shields/blob/master/doc/rewriting-services.md
|
||||
//
|
||||
// Do not base new services on this code.
|
||||
module.exports = class Liberapay extends LegacyService {
|
||||
static get category() {
|
||||
return 'funding'
|
||||
}
|
||||
|
||||
static get route() {
|
||||
return {
|
||||
base: 'liberapay',
|
||||
pattern: '',
|
||||
}
|
||||
}
|
||||
|
||||
static get examples() {
|
||||
return [
|
||||
{
|
||||
title: 'Liberapay receiving',
|
||||
pattern: 'receives/:entity',
|
||||
namedParams: { entity: 'Changaco' },
|
||||
staticPreview: {
|
||||
label: 'receives',
|
||||
message: '98.32 EUR/week',
|
||||
color: 'green',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Liberapay giving',
|
||||
pattern: 'gives/:entity',
|
||||
namedParams: { entity: 'Changaco' },
|
||||
staticPreview: {
|
||||
label: 'gives',
|
||||
message: '2.58 EUR/week',
|
||||
color: 'yellow',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Liberapay patrons',
|
||||
pattern: 'patrons/:entity',
|
||||
namedParams: { entity: 'Changaco' },
|
||||
staticPreview: {
|
||||
label: 'patrons',
|
||||
message: '10',
|
||||
color: 'green',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Liberapay goal progress',
|
||||
pattern: 'goal/:entity',
|
||||
namedParams: { entity: 'Changaco' },
|
||||
staticPreview: {
|
||||
label: 'goal progress',
|
||||
message: '33%',
|
||||
color: 'green',
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
static registerLegacyRouteHandler({ camp, cache }) {
|
||||
camp.route(
|
||||
/^\/liberapay\/(receives|gives|patrons|goal)\/(.*)\.(svg|png|gif|jpg|json)$/,
|
||||
cache((data, match, sendBadge, request) => {
|
||||
const type = match[1] // e.g., 'gives'
|
||||
const entity = match[2] // e.g., 'Changaco'
|
||||
const format = match[3]
|
||||
const apiUrl = `https://liberapay.com/${entity}/public.json`
|
||||
// Lock down type
|
||||
const label = {
|
||||
receives: 'receives',
|
||||
gives: 'gives',
|
||||
patrons: 'patrons',
|
||||
goal: 'goal progress',
|
||||
}[type]
|
||||
const badgeData = getBadgeData(label, data)
|
||||
if (badgeData.template === 'social') {
|
||||
badgeData.logo = getLogo('liberapay', data)
|
||||
}
|
||||
request(apiUrl, (err, res, buffer) => {
|
||||
if (err != null) {
|
||||
badgeData.text[1] = 'inaccessible'
|
||||
sendBadge(format, badgeData)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const data = JSON.parse(buffer)
|
||||
let value
|
||||
let currency
|
||||
switch (type) {
|
||||
case 'receives':
|
||||
if (data.receiving) {
|
||||
value = data.receiving.amount
|
||||
currency = data.receiving.currency
|
||||
badgeData.text[1] = `${metric(value)} ${currency}/week`
|
||||
}
|
||||
break
|
||||
case 'gives':
|
||||
if (data.giving) {
|
||||
value = data.giving.amount
|
||||
currency = data.giving.currency
|
||||
badgeData.text[1] = `${metric(value)} ${currency}/week`
|
||||
}
|
||||
break
|
||||
case 'patrons':
|
||||
value = data.npatrons
|
||||
badgeData.text[1] = metric(value)
|
||||
break
|
||||
case 'goal':
|
||||
if (data.goal) {
|
||||
value = Math.round(
|
||||
(data.receiving.amount / data.goal.amount) * 100
|
||||
)
|
||||
badgeData.text[1] = `${value}%`
|
||||
}
|
||||
break
|
||||
}
|
||||
if (value != null) {
|
||||
badgeData.colorscheme = colorScale([0, 10, 100])(value)
|
||||
sendBadge(format, badgeData)
|
||||
} else {
|
||||
badgeData.text[1] = 'anonymous'
|
||||
badgeData.colorscheme = 'blue'
|
||||
sendBadge(format, badgeData)
|
||||
}
|
||||
} catch (e) {
|
||||
badgeData.text[1] = 'invalid'
|
||||
sendBadge(format, badgeData)
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
const { ServiceTester } = require('..')
|
||||
const { isMetric } = require('../test-validators')
|
||||
|
||||
// Values must be greater than zero.
|
||||
const isLiberapayTestValues = Joi.string().regex(
|
||||
/^([0-9]*[1-9][0-9]*(\.[0-9]+)?|[0]+\.[0-9]*[1-9][0-9]*)[ A-Za-z]{4}\/week/
|
||||
)
|
||||
|
||||
const t = (module.exports = new ServiceTester({
|
||||
id: 'liberapay',
|
||||
title: 'Liberapay',
|
||||
}))
|
||||
|
||||
t.create('Receiving')
|
||||
.get('/receives/Liberapay.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'receives',
|
||||
value: isLiberapayTestValues,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('Giving')
|
||||
.get('/gives/Changaco.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'gives',
|
||||
value: isLiberapayTestValues,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('Patrons')
|
||||
.get('/patrons/Liberapay.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'patrons',
|
||||
value: isMetric,
|
||||
})
|
||||
)
|
||||
|
||||
t.create('Goal Progress')
|
||||
.get('/goal/Liberapay.json')
|
||||
.expectJSONTypes(
|
||||
Joi.object().keys({
|
||||
name: 'goal progress',
|
||||
value: Joi.string().regex(/^[0-9]+%/),
|
||||
})
|
||||
)
|
||||
|
||||
t.create('No Goal')
|
||||
.get('/goal/Liberapay.json')
|
||||
.intercept(nock =>
|
||||
nock('https://liberapay.com')
|
||||
.get('/Liberapay/public.json')
|
||||
.reply(200, { goal: null })
|
||||
)
|
||||
.expectJSON({ name: 'goal progress', value: 'anonymous' })
|
||||
|
||||
t.create('Empty')
|
||||
.get('/receives/Liberapay.json')
|
||||
.intercept(nock =>
|
||||
nock('https://liberapay.com')
|
||||
.get('/Liberapay/public.json')
|
||||
.reply(200, { receiving: 0.0 })
|
||||
)
|
||||
.expectJSON({ name: 'receives', value: 'anonymous' })
|
||||
Reference in New Issue
Block a user