Skip to content

Commit 3cd7bba

Browse files
authored
feat: Check octicons library before database (#432)
1 parent 1f572b7 commit 3cd7bba

File tree

9 files changed

+105
-24
lines changed

9 files changed

+105
-24
lines changed

client/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
"react-dom": "^18.0.0",
1717
"react-github-btn": "^1.3.0",
1818
"react-scripts": "^5.0.1",
19-
"typescript": "^4.6.4",
19+
"typescript": "^4.7.2",
2020
"web-vitals": "^2.1.4"
2121
},
2222
"devDependencies": {
@@ -54,4 +54,4 @@
5454
"resolutions": {
5555
"autoprefixer": "10.4.5"
5656
}
57-
}
57+
}

client/yarn.lock

+4-4
Original file line numberDiff line numberDiff line change
@@ -9296,10 +9296,10 @@ typedarray-to-buffer@^3.1.5:
92969296
dependencies:
92979297
is-typedarray "^1.0.0"
92989298

9299-
typescript@^4.6.4:
9300-
version "4.6.4"
9301-
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
9302-
integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
9299+
typescript@^4.7.2:
9300+
version "4.7.2"
9301+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4"
9302+
integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==
93039303

93049304
unbox-primitive@^1.0.2:
93059305
version "1.0.2"

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"author": "Jonah Lawrence",
66
"license": "MIT",
77
"dependencies": {
8+
"@primer/octicons": "^17.2.0",
89
"axios": "^0.27.2",
910
"body-parser": "^1.20.0",
1011
"concurrently": "^7.2.1",
@@ -13,13 +14,14 @@
1314
"express": "^4.18.1",
1415
"express-rate-limit": "^6.4.0",
1516
"monk": "^7.3.4",
16-
"typescript": "^4.6.4",
17+
"typescript": "^4.7.2",
1718
"web-vitals": "^2.1.4"
1819
},
1920
"devDependencies": {
2021
"@babel/core": "^7.18.0",
2122
"@babel/eslint-parser": "^7.17.0",
2223
"@types/express": "^4.17.3",
24+
"@types/primer__octicons": "^17.0.0",
2325
"@typescript-eslint/eslint-plugin": "^4.33.0",
2426
"@typescript-eslint/parser": "^4.33.0",
2527
"eslint": "^7.32.0",
@@ -53,4 +55,4 @@
5355
"bugs": {
5456
"url": "https://github.com/DenverCoder1/custom-icon-badges/issues"
5557
}
56-
}
58+
}

server/controllers/controller.ts

+22-6
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,29 @@
11
import { Request, Response } from 'express';
22
import fetchBadge from '../services/fetchBadges';
33
import iconDatabase from '../services/iconDatabase';
4+
import octicons from '../services/octicons';
45

5-
async function listIconsJSON(req: Request, res: Response): Promise<void> {
6+
/**
7+
* List all icons in the database
8+
* @param {Request} _req The request object
9+
* @param {Response} res The response object
10+
*/
11+
async function listIconsJSON(_req: Request, res: Response): Promise<void> {
612
res.status(200).json({
713
icons: await iconDatabase.getIcons(),
814
});
915
}
1016

17+
/**
18+
* Display a badge for the given parameters
19+
* @param {Request} req The request object
20+
* @param {Response} res The response object
21+
*/
1122
async function getBadge(req: Request, res: Response): Promise<void> {
1223
// get logo from query as a string, use nothing if multiple or empty
1324
const slug = typeof req.query.logo === 'string' ? req.query.logo : '';
1425
// check if slug exists
15-
const item = slug ? await iconDatabase.checkSlugExists(slug) : null;
26+
const item = slug ? await octicons.getIcon(slug) || await iconDatabase.getIcon(slug) : null;
1627
// get badge for item
1728
const response = await fetchBadge.fetchBadgeFromRequest(req, item);
1829
// get content type
@@ -21,6 +32,11 @@ async function getBadge(req: Request, res: Response): Promise<void> {
2132
res.status(response.status).type(contentType).send(response.data);
2233
}
2334

35+
/**
36+
* Add a new icon to the database
37+
* @param {Request} req The request object
38+
* @param {Response} res The response object
39+
*/
2440
async function postIcon(req: Request, res: Response): Promise<void> {
2541
const { slug, type, data }: { slug: string, type: string, data: string } = req.body;
2642

@@ -34,7 +50,7 @@ async function postIcon(req: Request, res: Response): Promise<void> {
3450
return;
3551
}
3652

37-
console.log(`Received icon for ${slug}`);
53+
console.info(`Received icon for ${slug}`);
3854

3955
// get the badge for item data
4056
const logoBadgeResponse = await fetchBadge.fetchBadgeFromRequest(req, { slug, type, data });
@@ -58,15 +74,15 @@ async function postIcon(req: Request, res: Response): Promise<void> {
5874
}
5975

6076
// check for slug in the database
61-
const item = await iconDatabase.checkSlugExists(slug);
77+
const item = await octicons.getIcon(slug) || await iconDatabase.getIcon(slug);
6278

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

6682
// Check if the slug is reserved
6783
// Slug is reserved if it is in the database or shields.io has an icon for it
6884
if (item !== null || defaultBadgeResponse.data.match(/<image[^>]*>/) !== null) {
69-
console.log('Slug is already in use');
85+
console.info(`The slug ${slug} is already in use`);
7086
// slug already exists
7187
res.status(409).json({
7288
type: 'error',
@@ -77,7 +93,7 @@ async function postIcon(req: Request, res: Response): Promise<void> {
7793
}
7894

7995
// All checks passed, add the icon to the database
80-
console.log(`Creating new icon for ${slug}`);
96+
console.info(`Creating new icon for ${slug}`);
8197
// create item
8298
const body = await iconDatabase.insertIcon(slug, type, data);
8399
// return success response

server/services/fetchBadges.ts

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import axios, { AxiosResponse } from 'axios';
22
import { Request } from 'express';
3-
import setLogoColor from './logoColor';
3+
import setLogoColor from './setLogoColor';
44

55
/**
66
* Build query string from ParsedQs
7-
* TODO: replace parsedQs with new query object
87
* @param {QueryString.ParsedQs} parsedQs query string possibly with replacements
98
* @returns {string} query string
109
*/
@@ -52,7 +51,10 @@ function buildQueryStringFromItem(
5251
}
5352

5453
/**
55-
* Build a badge query given the slug
54+
* Build a badge URL given the slug
55+
* @param {Request} req request object
56+
* @param {Object} item data about the icon or null if not found
57+
* @returns {string} url to badge
5658
*/
5759
function getBadgeUrl(
5860
req: Request, item: { slug: string, type: string, data: string } | null,
@@ -65,6 +67,9 @@ function getBadgeUrl(
6567

6668
/**
6769
* Fetch badge from shields.io for item with same params as request
70+
* @param {Request} req request object
71+
* @param {Object} item data about the icon or null if not found
72+
* @returns {AxiosResponse} response from shields.io
6873
*/
6974
async function fetchBadgeFromRequest(
7075
req: Request, item: { slug: string, type: string, data: string } | null,
@@ -77,6 +82,8 @@ async function fetchBadgeFromRequest(
7782

7883
/**
7984
* Fetch badge from shields.io given just a slug
85+
* @param {string} slug slug of the icon
86+
* @returns {AxiosResponse} response from shields.io
8087
*/
8188
async function fetchDefaultBadge(slug: string): Promise<AxiosResponse<any>> {
8289
// get shields url

server/services/iconDatabase.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,24 @@ const db = monk(DB_URL);
66
const icons = db.get('icons');
77
icons.createIndex({ slug: 1 }, { unique: true });
88

9-
async function checkSlugExists(slug: string):
9+
/**
10+
* Check if a slug exists in the database and return the icon if it does
11+
* @param {string} slug The slug to look for
12+
* @returns {Object} The icon data if it exists, null otherwise
13+
*/
14+
async function getIcon(slug: string):
1015
Promise<{ slug: string, type: string, data: string } | null> {
1116
// find slug in database, returns null if not found
1217
return icons.findOne({ slug: slug.toLowerCase() });
1318
}
1419

20+
/**
21+
* Insert a new icon into the database
22+
* @param {string} slug The slug to use for the icon
23+
* @param {string} type The type of icon to use (eg. 'png', 'svg+xml')
24+
* @param {string} data The base64 encoded data for the icon
25+
* @returns {Object} The icon data
26+
*/
1527
async function insertIcon(slug: string, type: string, data: string):
1628
Promise<{ slug: string, type: string, data: string }> {
1729
// create item
@@ -22,15 +34,19 @@ async function insertIcon(slug: string, type: string, data: string):
2234
return item;
2335
}
2436

37+
/**
38+
* Get all icons from the database
39+
* @returns {FindResult} The icons in the database
40+
*/
2541
async function getIcons(): Promise<FindResult<{ slug: string, type: string, data: string }>> {
2642
// return all items
2743
return icons.find({}, { sort: { _id: -1 } });
2844
}
2945

3046
const defaultExport = {
31-
checkSlugExists,
32-
insertIcon,
47+
getIcon,
3348
getIcons,
49+
insertIcon,
3450
};
3551

3652
export default defaultExport;

server/services/octicons.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import octicons, { IconName } from '@primer/octicons';
2+
3+
/**
4+
* Check if a slug exists in Octicons and return the icon if it does
5+
* @param {string} slug The slug to look for
6+
* @returns {Object} The icon data if it exists, null otherwise
7+
*/
8+
async function getIcon(slug: string):
9+
Promise<{ slug: string, type: string, data: string } | null> {
10+
const normalized = slug.toLowerCase();
11+
if (!(normalized in octicons)) {
12+
return null;
13+
}
14+
const icon = octicons[normalized as IconName];
15+
// add 'xmlns' attribute to the svg
16+
const svg = icon.toSVG().replace('<svg', '<svg xmlns="http://www.w3.org/2000/svg"');
17+
return {
18+
slug: icon.symbol,
19+
type: 'svg+xml',
20+
data: Buffer.from(svg, 'utf8').toString('base64'),
21+
};
22+
}
23+
24+
const defaultExport = {
25+
getIcon,
26+
};
27+
28+
export default defaultExport;
File renamed without changes.

yarn.lock

+16-4
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,13 @@
306306
"@nodelib/fs.scandir" "2.1.5"
307307
fastq "^1.6.0"
308308

309+
"@primer/octicons@^17.2.0":
310+
version "17.2.0"
311+
resolved "https://registry.yarnpkg.com/@primer/octicons/-/octicons-17.2.0.tgz#6e9be44484100e069006958f28dc75eaba55b1ed"
312+
integrity sha512-A3YetVbJX5J4JGa2AfVrEl1lE3FtT+7HdSzphFRs1QF5OgsUpkNbyjvVxeCxVZaG6zXYbbbPWsPF06PtsIyUWQ==
313+
dependencies:
314+
object-assign "^4.1.1"
315+
309316
"@types/body-parser@*":
310317
version "1.19.2"
311318
resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0"
@@ -375,6 +382,11 @@
375382
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef"
376383
integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA==
377384

385+
"@types/primer__octicons@^17.0.0":
386+
version "17.0.0"
387+
resolved "https://registry.yarnpkg.com/@types/primer__octicons/-/primer__octicons-17.0.0.tgz#b065b6dfb36ad8f1ab27a9f7f59d1ceb448221d5"
388+
integrity sha512-o7EF2Zvih38CT02oH+HxawZ/Wr6TS3lngcc0cq/WDVlfuPi08Nk3o3Rv78BUhhZefwMpiln81L9X1S4JEKi3jg==
389+
378390
"@types/qs@*":
379391
version "6.9.7"
380392
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
@@ -2527,10 +2539,10 @@ type-is@~1.6.18:
25272539
media-typer "0.3.0"
25282540
mime-types "~2.1.24"
25292541

2530-
typescript@^4.6.4:
2531-
version "4.6.4"
2532-
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
2533-
integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
2542+
typescript@^4.7.2:
2543+
version "4.7.2"
2544+
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.2.tgz#1f9aa2ceb9af87cca227813b4310fff0b51593c4"
2545+
integrity sha512-Mamb1iX2FDUpcTRzltPxgWMKy3fhg0TN378ylbktPGPK/99KbDtMQ4W1hwgsbPAsG3a0xKa1vmw4VKZQbkvz5A==
25342546

25352547
unbox-primitive@^1.0.2:
25362548
version "1.0.2"

0 commit comments

Comments
 (0)