refactor [liberapay] service (#2879)

This commit is contained in:
chris48s
2019-01-28 20:57:11 +00:00
committed by GitHub
parent 97e2ec1e60
commit a9d6809e3f
11 changed files with 339 additions and 214 deletions

View 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,
}

View 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' })
}
}
}

View 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' })

View 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' })
}
}
}

View 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' })

View 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 })
}
}

View 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' })

View 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' })
}
}
}

View 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' })

View File

@@ -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)
}
})
})
)
}
}

View File

@@ -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' })