diff --git a/services/atn/atn-base.js b/services/atn/atn-base.js new file mode 100644 index 0000000000000..197d2acd0c898 --- /dev/null +++ b/services/atn/atn-base.js @@ -0,0 +1,34 @@ +import Joi from 'joi' +import { nonNegativeInteger } from '../validators.js' +import { BaseJsonService } from '../index.js' + +const description = + '[addons.thunderbird.net](https://addons.thunderbird.net) (ATN) publishes extensions for Mozilla Thunderbird' + +const schema = Joi.object({ + average_daily_users: nonNegativeInteger, + current_version: Joi.object({ + version: Joi.string().required(), + }).required(), + ratings: Joi.object({ + average: Joi.number().required(), + }).required(), + weekly_downloads: nonNegativeInteger, +}).required() + +class BaseAtnService extends BaseJsonService { + static defaultBadgeData = { label: 'thunderbird add-on' } + + addonUrl({ addonId }) { + return `https://addons.thunderbird.net/api/v3/addons/addon/${addonId}/` + } + + async fetch({ addonId }) { + return this._requestJson({ + schema, + url: this.addonUrl({ addonId }), + }) + } +} + +export { BaseAtnService, description } diff --git a/services/atn/atn-downloads.service.js b/services/atn/atn-downloads.service.js new file mode 100644 index 0000000000000..f40ae960f4731 --- /dev/null +++ b/services/atn/atn-downloads.service.js @@ -0,0 +1,36 @@ +import { renderDownloadsBadge } from '../downloads.js' +import { pathParams } from '../index.js' +import { BaseAtnService, description } from './atn-base.js' + +export default class AtnWeeklyDownloads extends BaseAtnService { + static category = 'downloads' + static route = { base: 'atn/dw', pattern: ':addonId' } + + static openApi = { + '/atn/dw/{addonId}': { + get: { + summary: 'Thunderbird Add-on Downloads', + description, + parameters: pathParams({ + name: 'addonId', + example: 'unicodify-text-transformer', + }), + }, + }, + } + + static _cacheLength = 21600 + + static defaultBadgeData = { label: 'downloads' } + + static render({ downloads }) { + return renderDownloadsBadge({ downloads, interval: 'week' }) + } + + async handle({ addonId }) { + const data = await this.fetch({ addonId }) + return this.constructor.render({ + downloads: data.weekly_downloads, + }) + } +} diff --git a/services/atn/atn-downloads.tester.js b/services/atn/atn-downloads.tester.js new file mode 100644 index 0000000000000..b6c78a845c03e --- /dev/null +++ b/services/atn/atn-downloads.tester.js @@ -0,0 +1,11 @@ +import { createServiceTester } from '../tester.js' +import { isMetricOverTimePeriod } from '../test-validators.js' +export const t = await createServiceTester() + +t.create('Weekly Downloads') + .get('/dictionary-german.json') + .expectBadge({ label: 'downloads', message: isMetricOverTimePeriod }) + +t.create('Weekly Downloads (not found)') + .get('/not-a-real-plugin.json') + .expectBadge({ label: 'downloads', message: 'not found' }) diff --git a/services/atn/atn-rating.service.js b/services/atn/atn-rating.service.js new file mode 100644 index 0000000000000..a6df5c97eb4b5 --- /dev/null +++ b/services/atn/atn-rating.service.js @@ -0,0 +1,50 @@ +import { starRating } from '../text-formatters.js' +import { floorCount as floorCountColor } from '../color-formatters.js' +import { pathParams } from '../index.js' +import { BaseAtnService, description } from './atn-base.js' + +export default class AtnRating extends BaseAtnService { + static category = 'rating' + static route = { base: 'atn', pattern: ':format(stars|rating)/:addonId' } + + static openApi = { + '/atn/rating/{addonId}': { + get: { + summary: 'Thunderbird Add-on Rating', + description, + parameters: pathParams({ + name: 'addonId', + example: 'unicodify-text-transformer', + }), + }, + }, + '/atn/stars/{addonId}': { + get: { + summary: 'Thunderbird Add-on Stars', + description, + parameters: pathParams({ + name: 'addonId', + example: 'unicodify-text-transformer', + }), + }, + }, + } + + static _cacheLength = 7200 + + static render({ format, rating }) { + return { + label: format, + message: + format === 'stars' + ? starRating(rating) + : `${Math.round(rating * 10) / 10}/5`, + color: floorCountColor(rating, 2, 3, 4), + } + } + + async handle({ format, addonId }) { + const data = await this.fetch({ addonId }) + return this.constructor.render({ format, rating: data.ratings.average }) + } +} diff --git a/services/atn/atn-rating.tester.js b/services/atn/atn-rating.tester.js new file mode 100644 index 0000000000000..3c73e0098f976 --- /dev/null +++ b/services/atn/atn-rating.tester.js @@ -0,0 +1,19 @@ +import Joi from 'joi' +import { isStarRating } from '../test-validators.js' +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() + +t.create('Rating') + .get('/rating/unicodify-text-transformer.json') + .expectBadge({ + label: 'rating', + message: Joi.string().regex(/^\d(\.\d)?\/\d$/), + }) + +t.create('Stars') + .get('/stars/unicodify-text-transformer.json') + .expectBadge({ label: 'stars', message: isStarRating }) + +t.create('Rating (not found)') + .get('/rating/not-a-real-plugin.json') + .expectBadge({ label: 'thunderbird add-on', message: 'not found' }) diff --git a/services/atn/atn-version.service.js b/services/atn/atn-version.service.js new file mode 100644 index 0000000000000..301eceaba3f87 --- /dev/null +++ b/services/atn/atn-version.service.js @@ -0,0 +1,26 @@ +import { renderVersionBadge } from '../version.js' +import { pathParams } from '../index.js' +import { BaseAtnService, description } from './atn-base.js' + +export default class AtnVersion extends BaseAtnService { + static category = 'version' + static route = { base: 'atn/v', pattern: ':addonId' } + + static openApi = { + '/atn/v/{addonId}': { + get: { + summary: 'Thunderbird Add-on Version', + description, + parameters: pathParams({ + name: 'addonId', + example: 'unicodify-text-transformer', + }), + }, + }, + } + + async handle({ addonId }) { + const data = await this.fetch({ addonId }) + return renderVersionBadge({ version: data.current_version.version }) + } +} diff --git a/services/atn/atn-version.tester.js b/services/atn/atn-version.tester.js new file mode 100644 index 0000000000000..b7f80fcaec35e --- /dev/null +++ b/services/atn/atn-version.tester.js @@ -0,0 +1,12 @@ +import { isVPlusDottedVersionAtLeastOne } from '../test-validators.js' +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() + +t.create('Version').get('/unicodify-text-transformer.json').expectBadge({ + label: 'thunderbird add-on', + message: isVPlusDottedVersionAtLeastOne, +}) + +t.create('Version (not found)') + .get('/not-a-real-plugin.json') + .expectBadge({ label: 'thunderbird add-on', message: 'not found' }) diff --git a/services/atn/atn.service.js b/services/atn/atn.service.js new file mode 100644 index 0000000000000..14f7c62fc245c --- /dev/null +++ b/services/atn/atn.service.js @@ -0,0 +1,34 @@ +import { renderDownloadsBadge } from '../downloads.js' +import { pathParams } from '../index.js' +import { BaseAtnService, description } from './atn-base.js' + +export default class AtnUsers extends BaseAtnService { + static category = 'downloads' + static route = { base: 'atn/users', pattern: ':addonId' } + + static openApi = { + '/atn/users/{addonId}': { + get: { + summary: 'Thunderbird Add-on Users', + description, + parameters: pathParams({ + name: 'addonId', + example: 'unicodify-text-transformer', + }), + }, + }, + } + + static _cacheLength = 21600 + + static defaultBadgeData = { label: 'users' } + + static render({ users: downloads }) { + return renderDownloadsBadge({ downloads, colorOverride: 'blue' }) + } + + async handle({ addonId }) { + const data = await this.fetch({ addonId }) + return this.constructor.render({ users: data.average_daily_users }) + } +} diff --git a/services/atn/atn.service.spec.js b/services/atn/atn.service.spec.js new file mode 100644 index 0000000000000..b0b167592a480 --- /dev/null +++ b/services/atn/atn.service.spec.js @@ -0,0 +1,10 @@ +import { test, given } from 'sazerac' +import { BaseAtnService } from './atn-base.js' + +describe('ATN service', function () { + test(BaseAtnService.prototype.addonUrl, () => { + given({ addonId: 'unicodify-text-transformer' }).expect( + 'https://addons.thunderbird.net/api/v3/addons/addon/unicodify-text-transformer/', + ) + }) +}) diff --git a/services/atn/atn.tester.js b/services/atn/atn.tester.js new file mode 100644 index 0000000000000..15c69e6e94b91 --- /dev/null +++ b/services/atn/atn.tester.js @@ -0,0 +1,11 @@ +import { isMetric } from '../test-validators.js' +import { createServiceTester } from '../tester.js' +export const t = await createServiceTester() + +t.create('Users') + .get('/unicodify-text-transformer.json') + .expectBadge({ label: 'users', message: isMetric }) + +t.create('Users (not found)') + .get('/not-a-real-plugin.json') + .expectBadge({ label: 'users', message: 'not found' })