Skip to content

Commit

Permalink
feat: Allow changing host when fetching badge (#457)
Browse files Browse the repository at this point in the history
  • Loading branch information
DenverCoder1 authored Jun 14, 2022
1 parent 920da86 commit 2af9033
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 25 deletions.
36 changes: 24 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ You can find a list of slugs for each brand [here](https://github.com/simple-ico

All [Octicons](https://primer.style/octicons/) from GitHub are supported by Custom Icon Badges.

| Slug | Example |
| ------------------ | ------------------------------------------------------------------------------------------------------ |
| `issue-opened` | ![img](https://custom-icon-badges.herokuapp.com/badge/Issue-red.svg?logo=issue-opened&logoColor=fff) |
| `repo-forked` | ![img](https://custom-icon-badges.herokuapp.com/badge/Fork-orange.svg?logo=fork) |
| `star` | ![img](https://custom-icon-badges.herokuapp.com/badge/Star-yellow.svg?logo=star) |
| `git-commit` | ![img](https://custom-icon-badges.herokuapp.com/badge/Commit-green.svg?logo=git-commit&logoColor=fff) |
| `repo` | ![img](https://custom-icon-badges.herokuapp.com/badge/Repo-blue.svg?logo=repo) |
| `git-pull-request` | ![img](https://custom-icon-badges.herokuapp.com/badge/Pull%20Request-purple.svg?logo=pr) |
| `heart` | ![img](https://custom-icon-badges.herokuapp.com/badge/Heart-D15E9B.svg?logo=heart) |
| `mail` | ![img](https://custom-icon-badges.herokuapp.com/badge/Mail-E61B23.svg?logo=mail) |
| More Octicons | [View all ⇨](https://primer.style/octicons) |
| Slug | Example |
| ------------------ | ----------------------------------------------------------------------------------------------------- |
| `issue-opened` | ![img](https://custom-icon-badges.herokuapp.com/badge/Issue-red.svg?logo=issue-opened&logoColor=fff) |
| `repo-forked` | ![img](https://custom-icon-badges.herokuapp.com/badge/Fork-orange.svg?logo=fork) |
| `star` | ![img](https://custom-icon-badges.herokuapp.com/badge/Star-yellow.svg?logo=star) |
| `git-commit` | ![img](https://custom-icon-badges.herokuapp.com/badge/Commit-green.svg?logo=git-commit&logoColor=fff) |
| `repo` | ![img](https://custom-icon-badges.herokuapp.com/badge/Repo-blue.svg?logo=repo) |
| `git-pull-request` | ![img](https://custom-icon-badges.herokuapp.com/badge/Pull%20Request-purple.svg?logo=pr) |
| `heart` | ![img](https://custom-icon-badges.herokuapp.com/badge/Heart-D15E9B.svg?logo=heart) |
| `mail` | ![img](https://custom-icon-badges.herokuapp.com/badge/Mail-E61B23.svg?logo=mail) |
| More Octicons | [View all ⇨](https://primer.style/octicons) |

### Miscellaneous

Expand All @@ -64,7 +64,7 @@ All [Octicons](https://primer.style/octicons/) from GitHub are supported by Cust
| `trending-down` | ![img](https://custom-icon-badges.herokuapp.com/badge/trending--down-red.svg?logoColor=fff&logo=trending-down) |
| `phone` | ![img](https://custom-icon-badges.herokuapp.com/badge/phone-green.svg?logo=phone&logoColor=white) |
| `swi-prolog` | ![img](https://custom-icon-badges.herokuapp.com/badge/swi--prolog-E61B23.svg?logo=swi-prolog&logoColor=fff) |
| Add your own | [Upload icon ⇨](https://custom-icon-badges.herokuapp.com) |
| Add your own | [Upload icon ⇨](https://custom-icon-badges.herokuapp.com) |

## ➕ Adding a new logo

Expand Down Expand Up @@ -140,6 +140,18 @@ Click to get the URL!
[25]: https://custom-icon-badges.herokuapp.com/badge/dynamic/json?logo=fire&logoColor=fff&color=orange&label=github%20streak&query=%24.currentStreak.length&suffix=%20days&url=https%3A%2F%2Fgithub-readme-streak-stats.herokuapp.com%2F%3Fuser%3DDenverCoder1%26type%3Djson
[26]: https://custom-icon-badges.herokuapp.com/badge/dynamic/json?logo=graph&logoColor=fff&color=blue&label=total%20contributions&query=%24.totalContributions&url=https%3A%2F%2Fgithub-readme-streak-stats.herokuapp.com%2F%3Fuser%3DDenverCoder1%26type%3Djson

## 🖥️ Using a Different Badge Host

By default, fetching a badge from Custom Icon Badges will use [`img.shields.io`](https://img.shields.io) as the badge host.

You can set the `host` parameter to one of the following to override the hostname of the badge URL:

- [`img.shields.io`](https://img.shields.io)
- [`staging.shields.io`](https://staging.shields.io)
- [`formatted-dynamic-badges.herokuapp.com`](https://formatted-dynamic-badges.herokuapp.com)

If you would like to use a different badge host, fork and modify this repository. Create a PR if it may be useful to others.

## 🤗 Contributing

We welcome contributions!
Expand Down
34 changes: 25 additions & 9 deletions server/controllers/controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { Request, Response } from 'express';
import fetchBadge from '../services/fetchBadges';
import {
BadgeError,
fetchBadgeFromRequest,
fetchDefaultBadge,
fetchErrorBadge,
} from '../services/fetchBadges';
import iconDatabase from '../services/iconDatabase';
import octicons from '../services/octicons';

Expand All @@ -20,12 +25,23 @@ async function listIconsJSON(_req: Request, res: Response): Promise<void> {
* @param {Response} res The response object
*/
async function getBadge(req: Request, res: Response): Promise<void> {
// get logo from query as a string, use nothing if multiple or empty
const slug = typeof req.query.logo === 'string' ? req.query.logo : '';
// check if slug exists
const item = slug ? octicons.getIcon(slug) || await iconDatabase.getIcon(slug) : null;
// get badge for item
const response = await fetchBadge.fetchBadgeFromRequest(req, item);
let response = null;
try {
// get logo from query as a string, use nothing if multiple or empty
const slug = typeof req.query.logo === 'string' ? req.query.logo : '';
// check if slug exists
const item = slug ? octicons.getIcon(slug) || await iconDatabase.getIcon(slug) : null;
// get badge for item
response = await fetchBadgeFromRequest(req, item);
} catch (error) {
// set response to error badge
if (error instanceof BadgeError) {
response = await fetchErrorBadge(error.message);
} else {
console.error(error);
response = await fetchErrorBadge('something went wrong');
}
}
// get content type
const contentType = response.headers['content-type'] || 'image/svg+xml';
// send response
Expand Down Expand Up @@ -53,7 +69,7 @@ async function postIcon(req: Request, res: Response): Promise<void> {
console.info(`Received icon for ${slug}`);

// get the badge for item data
const logoBadgeResponse = await fetchBadge.fetchBadgeFromRequest(req, { slug, type, data });
const logoBadgeResponse = await fetchBadgeFromRequest(req, { slug, type, data });
// if the response is 414, the icon is too big
if (logoBadgeResponse.status === 414) {
res.status(logoBadgeResponse.status).json({
Expand All @@ -77,7 +93,7 @@ async function postIcon(req: Request, res: Response): Promise<void> {
const item = octicons.getIcon(slug) || await iconDatabase.getIcon(slug);

// Get default badge with the logo set to the slug
const defaultBadgeResponse = await fetchBadge.fetchDefaultBadge(slug);
const defaultBadgeResponse = await fetchDefaultBadge(slug);

// Check if the slug is reserved
// Slug is reserved if it is in the database or shields.io has an icon for it
Expand Down
52 changes: 48 additions & 4 deletions server/services/fetchBadges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@ import { Request } from 'express';
import { ParsedQs } from 'qs';
import setLogoColor from './setLogoColor';

/**
* Error class for exceptions caused during building and fetching of badges
*/
class BadgeError extends Error {
constructor(message: string) {
super(message);
this.name = 'BadgeError';
Object.setPrototypeOf(this, BadgeError.prototype);
}
}

/**
* Build query string from ParsedQs
* @param {ParsedQs} parsedQs query string possibly with replacements
Expand Down Expand Up @@ -47,10 +58,26 @@ function buildQueryStringFromItem(
}
// replace logo with data url in query
const newQuery = replacedLogoQuery(req, item.type, data);
// remove "host" parameter from query string
delete newQuery.host;
// build url using request params and query
return buildQueryString(newQuery);
}

/**
* Validate hostname is allowed
* @param {string|null} host hostname to validate
* @returns {boolean} True if host is valid, otherwise false
*/
function isValidHost(host: string): boolean {
const validHosts = [
'img.shields.io',
'staging.shields.io',
'formatted-dynamic-badges.herokuapp.com',
];
return validHosts.includes(host);
}

/**
* Build a badge URL given the slug
* @param {Request} req request object
Expand All @@ -63,7 +90,11 @@ function getBadgeUrl(
// build url using request params and query
const params = Object.values(req.params).map((p) => encodeURIComponent(p)).join('/');
const queryString = buildQueryStringFromItem(req, item);
return `https://img.shields.io/${params}?${queryString}`;
const host = typeof req.query.host === 'string' ? req.query.host : 'img.shields.io';
if (!isValidHost(host)) {
throw new BadgeError('invalid host');
}
return `https://${host}/${params}?${queryString}`;
}

/**
Expand Down Expand Up @@ -93,9 +124,22 @@ function fetchDefaultBadge(slug: string): Promise<AxiosResponse<string>> {
return axios.get(url, { validateStatus: () => true });
}

const defaultExport = {
/**
* Fetch badge from shields.io that displays an error message
* @param {string} message message to display
* @returns {AxiosResponse} response from shields.io
*/
function fetchErrorBadge(message: string): Promise<AxiosResponse<string>> {
const encodedMessage = encodeURIComponent(message);
// get shields url
const url = `https://img.shields.io/static/v1?label=custom-icon-badges&message=${encodedMessage}&color=red`;
// get badge from url
return axios.get(url, { validateStatus: () => true });
}

export {
BadgeError,
fetchBadgeFromRequest,
fetchDefaultBadge,
fetchErrorBadge,
};

export default defaultExport;

0 comments on commit 2af9033

Please sign in to comment.