mirror of https://github.com/iconify/api.git
chore: split api responses from server, allowing reuse, prepare npm package
This commit is contained in:
parent
2181b8022c
commit
ceb3fc4394
|
|
@ -0,0 +1,18 @@
|
||||||
|
/.idea
|
||||||
|
/.vscode
|
||||||
|
.DS_Store
|
||||||
|
/.env
|
||||||
|
/.editorconfig
|
||||||
|
/.prettierrc
|
||||||
|
*.map
|
||||||
|
/docker.sh
|
||||||
|
/Dockerfile
|
||||||
|
/tsconfig*.*
|
||||||
|
/vitest.config.*
|
||||||
|
/.github
|
||||||
|
/src
|
||||||
|
/node_modules
|
||||||
|
/cache
|
||||||
|
/tmp
|
||||||
|
/icons
|
||||||
|
/tests
|
||||||
|
|
@ -6,6 +6,12 @@ This repository contains Iconify API script. It is a HTTP server, written in Nod
|
||||||
- Generates SVG, which you can link to in HTML or stylesheet.
|
- Generates SVG, which you can link to in HTML or stylesheet.
|
||||||
- Provides search engine for hosted icons, which can be used by icon pickers.
|
- Provides search engine for hosted icons, which can be used by icon pickers.
|
||||||
|
|
||||||
|
## NPM Package
|
||||||
|
|
||||||
|
This package is also available at NPM, allowing using API code in custom wrappers.
|
||||||
|
|
||||||
|
NPM package contains only compiled files, to build custom Docker image you need to use source files from Git repository, not NPM package.
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
|
|
||||||
To build a Docker image, run `./docker.sh`.
|
To build a Docker image, run `./docker.sh`.
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@
|
||||||
"description": "Iconify API",
|
"description": "Iconify API",
|
||||||
"author": "Vjacheslav Trushkin",
|
"author": "Vjacheslav Trushkin",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"private": true,
|
"version": "3.1.0-beta.1",
|
||||||
"version": "3.0.2",
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"tag": "next"
|
||||||
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bugs": "https://github.com/iconify/api/issues",
|
"bugs": "https://github.com/iconify/api/issues",
|
||||||
"homepage": "https://github.com/iconify/api",
|
"homepage": "https://github.com/iconify/api",
|
||||||
|
|
|
||||||
|
|
@ -125,8 +125,9 @@ export function updateIconSets(): number {
|
||||||
/**
|
/**
|
||||||
* Trigger update
|
* Trigger update
|
||||||
*/
|
*/
|
||||||
export function triggerIconSetsUpdate() {
|
export function triggerIconSetsUpdate(done?: (success?: boolean) => void) {
|
||||||
if (!importers) {
|
if (!importers) {
|
||||||
|
done?.();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('Checking for updates...');
|
console.log('Checking for updates...');
|
||||||
|
|
@ -147,6 +148,10 @@ export function triggerIconSetsUpdate() {
|
||||||
.then((updated) => {
|
.then((updated) => {
|
||||||
console.log(updated ? 'Update complete' : 'Nothing to update');
|
console.log(updated ? 'Update complete' : 'Nothing to update');
|
||||||
updateIconSets();
|
updateIconSets();
|
||||||
|
done?.(true);
|
||||||
})
|
})
|
||||||
.catch(console.error);
|
.catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
done?.(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
import { checkJSONPQuery, sendJSONResponse } from './json.js';
|
||||||
|
import { createIconsDataResponse } from '../responses/icons.js';
|
||||||
|
|
||||||
|
type CallbackResult = object | number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle icons data API response
|
||||||
|
*/
|
||||||
|
export function handleIconsDataResponse(
|
||||||
|
prefix: string,
|
||||||
|
wrapJS: boolean,
|
||||||
|
query: FastifyRequest['query'],
|
||||||
|
res: FastifyReply
|
||||||
|
) {
|
||||||
|
const q = (query || {}) as Record<string, string>;
|
||||||
|
|
||||||
|
// Check for JSONP
|
||||||
|
const wrap = checkJSONPQuery(q, wrapJS, 'SimpleSVG._loaderCallback');
|
||||||
|
if (!wrap) {
|
||||||
|
// Invalid JSONP callback
|
||||||
|
res.send(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to send response
|
||||||
|
const respond = (result: CallbackResult) => {
|
||||||
|
if (typeof result === 'number') {
|
||||||
|
res.send(result);
|
||||||
|
} else {
|
||||||
|
sendJSONResponse(result, q, wrap, res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get result
|
||||||
|
const result = createIconsDataResponse(prefix, q);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result.then(respond).catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
respond(500);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
respond(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
import type { FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
import { checkJSONPQuery, sendJSONResponse } from './json.js';
|
||||||
|
|
||||||
|
type CallbackResult = object | number;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle JSON API response generated by a callback
|
||||||
|
*/
|
||||||
|
export function handleJSONResponse(
|
||||||
|
req: FastifyRequest,
|
||||||
|
res: FastifyReply,
|
||||||
|
callback: (query: Record<string, string>) => CallbackResult | Promise<CallbackResult>
|
||||||
|
) {
|
||||||
|
const q = (req.query || {}) as Record<string, string>;
|
||||||
|
|
||||||
|
// Check for JSONP
|
||||||
|
const wrap = checkJSONPQuery(q);
|
||||||
|
if (!wrap) {
|
||||||
|
// Invalid JSONP callback
|
||||||
|
res.send(400);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to send response
|
||||||
|
const respond = (result: CallbackResult) => {
|
||||||
|
if (typeof result === 'number') {
|
||||||
|
res.send(result);
|
||||||
|
} else {
|
||||||
|
sendJSONResponse(result, q, wrap, res);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get result
|
||||||
|
const result = callback(q);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result.then(respond).catch((err) => {
|
||||||
|
console.error(err);
|
||||||
|
respond(500);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
respond(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,17 +3,18 @@ import fastifyFormBody from '@fastify/formbody';
|
||||||
import { appConfig, httpHeaders } from '../config/app.js';
|
import { appConfig, httpHeaders } from '../config/app.js';
|
||||||
import { runWhenLoaded } from '../data/loading.js';
|
import { runWhenLoaded } from '../data/loading.js';
|
||||||
import { iconNameRoutePartialRegEx, iconNameRouteRegEx, splitIconName } from '../misc/name.js';
|
import { iconNameRoutePartialRegEx, iconNameRouteRegEx, splitIconName } from '../misc/name.js';
|
||||||
import { generateAPIv1IconsListResponse } from './responses/collection-v1.js';
|
import { createAPIv1IconsListResponse } from './responses/collection-v1.js';
|
||||||
import { generateAPIv2CollectionResponse } from './responses/collection-v2.js';
|
import { createAPIv2CollectionResponse } from './responses/collection-v2.js';
|
||||||
import { generateCollectionsListResponse } from './responses/collections.js';
|
import { createCollectionsListResponse } from './responses/collections.js';
|
||||||
import { generateIconsDataResponse } from './responses/icons.js';
|
import { handleIconsDataResponse } from './helpers/send-icons.js';
|
||||||
import { generateKeywordsResponse } from './responses/keywords.js';
|
import { createKeywordsResponse } from './responses/keywords.js';
|
||||||
import { generateLastModifiedResponse } from './responses/modified.js';
|
import { createLastModifiedResponse } from './responses/modified.js';
|
||||||
import { generateAPIv2SearchResponse } from './responses/search.js';
|
import { createAPIv2SearchResponse } from './responses/search.js';
|
||||||
import { generateSVGResponse } from './responses/svg.js';
|
import { generateSVGResponse } from './responses/svg.js';
|
||||||
import { generateUpdateResponse } from './responses/update.js';
|
import { generateUpdateResponse } from './responses/update.js';
|
||||||
import { initVersionResponse, versionResponse } from './responses/version.js';
|
import { initVersionResponse, versionResponse } from './responses/version.js';
|
||||||
import { generateIconsStyleResponse } from './responses/css.js';
|
import { generateIconsStyleResponse } from './responses/css.js';
|
||||||
|
import { handleJSONResponse } from './helpers/send.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start HTTP server
|
* Start HTTP server
|
||||||
|
|
@ -88,12 +89,12 @@ export async function startHTTPServer() {
|
||||||
// Icons data: /prefix/icons.json, /prefix.json
|
// Icons data: /prefix/icons.json, /prefix.json
|
||||||
server.get('/:prefix(' + iconNameRoutePartialRegEx + ')/icons.json', (req, res) => {
|
server.get('/:prefix(' + iconNameRoutePartialRegEx + ')/icons.json', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
|
handleIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
server.get('/:prefix(' + iconNameRoutePartialRegEx + ').json', (req, res) => {
|
server.get('/:prefix(' + iconNameRoutePartialRegEx + ').json', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
|
handleIconsDataResponse((req.params as PrefixParams).prefix, false, req.query, res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -107,19 +108,19 @@ export async function startHTTPServer() {
|
||||||
// Icons data: /prefix/icons.js, /prefix.js
|
// Icons data: /prefix/icons.js, /prefix.js
|
||||||
server.get('/:prefix(' + iconNameRoutePartialRegEx + ')/icons.js', (req, res) => {
|
server.get('/:prefix(' + iconNameRoutePartialRegEx + ')/icons.js', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
|
handleIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
server.get('/:prefix(' + iconNameRoutePartialRegEx + ').js', (req, res) => {
|
server.get('/:prefix(' + iconNameRoutePartialRegEx + ').js', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
|
handleIconsDataResponse((req.params as PrefixParams).prefix, true, req.query, res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Last modification time
|
// Last modification time
|
||||||
server.get('/last-modified', (req, res) => {
|
server.get('/last-modified', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateLastModifiedResponse(req.query, res);
|
handleJSONResponse(req, res, createLastModifiedResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -127,26 +128,26 @@ export async function startHTTPServer() {
|
||||||
// Icon sets list
|
// Icon sets list
|
||||||
server.get('/collections', (req, res) => {
|
server.get('/collections', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateCollectionsListResponse(req.query, res);
|
handleJSONResponse(req, res, createCollectionsListResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Icons list, API v2
|
// Icons list, API v2
|
||||||
server.get('/collection', (req, res) => {
|
server.get('/collection', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateAPIv2CollectionResponse(req.query, res);
|
handleJSONResponse(req, res, createAPIv2CollectionResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Icons list, API v1
|
// Icons list, API v1
|
||||||
server.get('/list-icons', (req, res) => {
|
server.get('/list-icons', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateAPIv1IconsListResponse(req.query, res, false);
|
handleJSONResponse(req, res, (q) => createAPIv1IconsListResponse(q, false));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
server.get('/list-icons-categorized', (req, res) => {
|
server.get('/list-icons-categorized', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateAPIv1IconsListResponse(req.query, res, true);
|
handleJSONResponse(req, res, (q) => createAPIv1IconsListResponse(q, true));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -154,14 +155,14 @@ export async function startHTTPServer() {
|
||||||
// Search, currently version 2
|
// Search, currently version 2
|
||||||
server.get('/search', (req, res) => {
|
server.get('/search', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateAPIv2SearchResponse(req.query, res);
|
handleJSONResponse(req, res, createAPIv2SearchResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Keywords
|
// Keywords
|
||||||
server.get('/keywords', (req, res) => {
|
server.get('/keywords', (req, res) => {
|
||||||
runWhenLoaded(() => {
|
runWhenLoaded(() => {
|
||||||
generateKeywordsResponse(req.query, res);
|
handleJSONResponse(req, res, createKeywordsResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
||||||
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
|
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
|
||||||
import type { IconSetAPIv2IconsList } from '../../types/icon-set/extra.js';
|
import type { IconSetAPIv2IconsList } from '../../types/icon-set/extra.js';
|
||||||
import type { StoredIconSet } from '../../types/icon-set/storage.js';
|
import type { StoredIconSet } from '../../types/icon-set/storage.js';
|
||||||
|
|
@ -7,11 +6,13 @@ import type {
|
||||||
APIv1ListIconsCategorisedResponse,
|
APIv1ListIconsCategorisedResponse,
|
||||||
APIv1ListIconsResponse,
|
APIv1ListIconsResponse,
|
||||||
} from '../../types/server/v1.js';
|
} from '../../types/server/v1.js';
|
||||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json.js';
|
|
||||||
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
||||||
|
|
||||||
|
// Response results, depends on `categorised` option
|
||||||
|
type PossibleResults = APIv1ListIconsResponse | APIv1ListIconsCategorisedResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send API v2 response
|
* Create API v1 response
|
||||||
*
|
*
|
||||||
* This response ignores the following parameters:
|
* This response ignores the following parameters:
|
||||||
* - `aliases` -> always enabled
|
* - `aliases` -> always enabled
|
||||||
|
|
@ -19,27 +20,15 @@ import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
||||||
*
|
*
|
||||||
* Those parameters are always requested anyway, so does not make sense to re-create data in case they are disabled
|
* Those parameters are always requested anyway, so does not make sense to re-create data in case they are disabled
|
||||||
*/
|
*/
|
||||||
export function generateAPIv1IconsListResponse(
|
export function createAPIv1IconsListResponse(
|
||||||
query: FastifyRequest['query'],
|
query: Record<string, string>,
|
||||||
res: FastifyReply,
|
|
||||||
categorised: boolean
|
categorised: boolean
|
||||||
) {
|
): PossibleResults | Record<string, PossibleResults> | number {
|
||||||
const q = (query || {}) as Record<string, string>;
|
|
||||||
|
|
||||||
const wrap = checkJSONPQuery(q);
|
|
||||||
if (!wrap) {
|
|
||||||
// Invalid JSONP callback
|
|
||||||
res.send(400);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parse(
|
function parse(
|
||||||
prefix: string,
|
prefix: string,
|
||||||
iconSet: StoredIconSet,
|
iconSet: StoredIconSet,
|
||||||
v2Cache: IconSetAPIv2IconsList
|
v2Cache: IconSetAPIv2IconsList
|
||||||
): APIv1ListIconsResponse | APIv1ListIconsCategorisedResponse {
|
): APIv1ListIconsResponse | APIv1ListIconsCategorisedResponse {
|
||||||
const icons = iconSet.icons;
|
|
||||||
|
|
||||||
// Generate common data
|
// Generate common data
|
||||||
const base: APIv1ListIconsBaseResponse = {
|
const base: APIv1ListIconsBaseResponse = {
|
||||||
prefix,
|
prefix,
|
||||||
|
|
@ -48,13 +37,13 @@ export function generateAPIv1IconsListResponse(
|
||||||
if (v2Cache.title) {
|
if (v2Cache.title) {
|
||||||
base.title = v2Cache.title;
|
base.title = v2Cache.title;
|
||||||
}
|
}
|
||||||
if (q.info && v2Cache.info) {
|
if (query.info && v2Cache.info) {
|
||||||
base.info = v2Cache.info;
|
base.info = v2Cache.info;
|
||||||
}
|
}
|
||||||
if (q.aliases && v2Cache.aliases) {
|
if (query.aliases && v2Cache.aliases) {
|
||||||
base.aliases = v2Cache.aliases;
|
base.aliases = v2Cache.aliases;
|
||||||
}
|
}
|
||||||
if (q.chars && v2Cache.chars) {
|
if (query.chars && v2Cache.chars) {
|
||||||
base.chars = v2Cache.chars;
|
base.chars = v2Cache.chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,22 +70,20 @@ export function generateAPIv1IconsListResponse(
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (q.prefix) {
|
if (query.prefix) {
|
||||||
const prefix = q.prefix;
|
const prefix = query.prefix;
|
||||||
const iconSet = iconSets[prefix]?.item;
|
const iconSet = iconSets[prefix]?.item;
|
||||||
if (!iconSet || !iconSet.apiV2IconsCache) {
|
if (!iconSet || !iconSet.apiV2IconsCache) {
|
||||||
res.send(404);
|
return 404;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
sendJSONResponse(parse(prefix, iconSet, iconSet.apiV2IconsCache), q, wrap, res);
|
return parse(prefix, iconSet, iconSet.apiV2IconsCache);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (q.prefixes) {
|
if (query.prefixes) {
|
||||||
const prefixes = filterPrefixesByPrefix(
|
const prefixes = filterPrefixesByPrefix(
|
||||||
getPrefixes(),
|
getPrefixes(),
|
||||||
{
|
{
|
||||||
prefixes: q.prefixes,
|
prefixes: query.prefixes,
|
||||||
},
|
},
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
@ -125,20 +112,18 @@ export function generateAPIv1IconsListResponse(
|
||||||
|
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
// Empty list
|
// Empty list
|
||||||
res.send(404);
|
return 404;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all items
|
// Get all items
|
||||||
const result = Object.create(null) as Record<string, ReturnType<typeof parse>>;
|
const result = Object.create(null) as Record<string, PossibleResults>;
|
||||||
for (let i = 0; i < items.length; i++) {
|
for (let i = 0; i < items.length; i++) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
result[item.prefix] = parse(item.prefix, item.iconSet, item.v2Cache);
|
result[item.prefix] = parse(item.prefix, item.iconSet, item.v2Cache);
|
||||||
}
|
}
|
||||||
sendJSONResponse(result, q, wrap, res);
|
return result;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalid
|
// Invalid
|
||||||
res.send(400);
|
return 400;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
||||||
import { iconSets } from '../../data/icon-sets.js';
|
import { iconSets } from '../../data/icon-sets.js';
|
||||||
import type { APIv2CollectionResponse } from '../../types/server/v2.js';
|
import type { APIv2CollectionResponse } from '../../types/server/v2.js';
|
||||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send API v2 response
|
* Send API v2 response
|
||||||
|
|
@ -12,29 +10,18 @@ import { checkJSONPQuery, sendJSONResponse } from '../helpers/json.js';
|
||||||
*
|
*
|
||||||
* Those parameters are always requested anyway, so does not make sense to re-create data in case they are disabled
|
* Those parameters are always requested anyway, so does not make sense to re-create data in case they are disabled
|
||||||
*/
|
*/
|
||||||
export function generateAPIv2CollectionResponse(query: FastifyRequest['query'], res: FastifyReply) {
|
export function createAPIv2CollectionResponse(q: Record<string, string>): APIv2CollectionResponse | number {
|
||||||
const q = (query || {}) as Record<string, string>;
|
|
||||||
|
|
||||||
const wrap = checkJSONPQuery(q);
|
|
||||||
if (!wrap) {
|
|
||||||
// Invalid JSONP callback
|
|
||||||
res.send(400);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get icon set
|
// Get icon set
|
||||||
const prefix = q.prefix;
|
const prefix = q.prefix;
|
||||||
if (!prefix || !iconSets[prefix]) {
|
if (!prefix || !iconSets[prefix]) {
|
||||||
res.send(404);
|
return 404;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const iconSet = iconSets[prefix].item;
|
const iconSet = iconSets[prefix].item;
|
||||||
const apiV2IconsCache = iconSet.apiV2IconsCache;
|
const apiV2IconsCache = iconSet.apiV2IconsCache;
|
||||||
if (!apiV2IconsCache) {
|
if (!apiV2IconsCache) {
|
||||||
// Disabled
|
// Disabled
|
||||||
res.send(404);
|
return 404;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate response
|
// Generate response
|
||||||
|
|
@ -52,5 +39,5 @@ export function generateAPIv2CollectionResponse(query: FastifyRequest['query'],
|
||||||
delete response.chars;
|
delete response.chars;
|
||||||
}
|
}
|
||||||
|
|
||||||
sendJSONResponse(response, q, wrap, res);
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
||||||
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
|
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
|
||||||
import type { APIv2CollectionsResponse } from '../../types/server/v2.js';
|
import type { APIv2CollectionsResponse } from '../../types/server/v2.js';
|
||||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json.js';
|
|
||||||
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -12,15 +10,7 @@ import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
||||||
* Ignored parameters:
|
* Ignored parameters:
|
||||||
* - hidden (always enabled)
|
* - hidden (always enabled)
|
||||||
*/
|
*/
|
||||||
export function generateCollectionsListResponse(query: FastifyRequest['query'], res: FastifyReply) {
|
export function createCollectionsListResponse(q: Record<string, string>): APIv2CollectionsResponse {
|
||||||
const q = (query || {}) as Record<string, string>;
|
|
||||||
const wrap = checkJSONPQuery(q);
|
|
||||||
if (!wrap) {
|
|
||||||
// Invalid JSONP callback
|
|
||||||
res.send(400);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter prefixes
|
// Filter prefixes
|
||||||
const prefixes = filterPrefixesByPrefix(getPrefixes('info'), q, false);
|
const prefixes = filterPrefixesByPrefix(getPrefixes('info'), q, false);
|
||||||
const response = Object.create(null) as APIv2CollectionsResponse;
|
const response = Object.create(null) as APIv2CollectionsResponse;
|
||||||
|
|
@ -33,5 +23,5 @@ export function generateCollectionsListResponse(query: FastifyRequest['query'],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendJSONResponse(response, q, wrap, res);
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,59 @@
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
import { IconifyJSON } from '@iconify/types';
|
||||||
import { getStoredIconsData } from '../../data/icon-set/utils/get-icons.js';
|
import { getStoredIconsData } from '../../data/icon-set/utils/get-icons.js';
|
||||||
import { iconSets } from '../../data/icon-sets.js';
|
import { iconSets } from '../../data/icon-sets.js';
|
||||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate icons data
|
* Generate icons data
|
||||||
*/
|
*/
|
||||||
export function generateIconsDataResponse(
|
export function createIconsDataResponse(
|
||||||
prefix: string,
|
prefix: string,
|
||||||
wrapJS: boolean,
|
q: Record<string, string>
|
||||||
query: FastifyRequest['query'],
|
): number | IconifyJSON | Promise<IconifyJSON | number> {
|
||||||
res: FastifyReply
|
|
||||||
) {
|
|
||||||
const q = (query || {}) as Record<string, string>;
|
|
||||||
const names = q.icons?.split(',');
|
const names = q.icons?.split(',');
|
||||||
|
|
||||||
if (!names || !names.length) {
|
if (!names || !names.length) {
|
||||||
// Missing or invalid icons parameter
|
// Missing or invalid icons parameter
|
||||||
res.send(404);
|
return 404;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for JSONP
|
|
||||||
const wrap = checkJSONPQuery(q, wrapJS, 'SimpleSVG._loaderCallback');
|
|
||||||
if (!wrap) {
|
|
||||||
// Invalid JSONP callback
|
|
||||||
res.send(400);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get icon set
|
// Get icon set
|
||||||
const iconSet = iconSets[prefix];
|
const iconSet = iconSets[prefix];
|
||||||
if (!iconSet) {
|
if (!iconSet) {
|
||||||
// No such icon set
|
// No such icon set
|
||||||
res.send(404);
|
return 404;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get icons
|
// Get icons, possibly sync
|
||||||
|
let syncData: IconifyJSON | undefined;
|
||||||
|
let resolveData: undefined | ((data: IconifyJSON) => void);
|
||||||
|
|
||||||
getStoredIconsData(iconSet.item, names, (data) => {
|
getStoredIconsData(iconSet.item, names, (data) => {
|
||||||
// Send data
|
// Send data
|
||||||
sendJSONResponse(data, q, wrap, res);
|
if (resolveData) {
|
||||||
|
resolveData(data);
|
||||||
|
} else {
|
||||||
|
syncData = data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (syncData) {
|
||||||
|
return syncData;
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
resolveData = resolve;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Awaitable version of createIconsDataResponse()
|
||||||
|
*/
|
||||||
|
export function createIconsDataResponseAsync(prefix: string, q: Record<string, string>): Promise<IconifyJSON | number> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const result = createIconsDataResponse(prefix, q);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
result.then(resolve).catch(reject);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,16 @@
|
||||||
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
import { matchIconName } from '@iconify/utils/lib/icon/name';
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
||||||
import { searchIndex } from '../../data/search.js';
|
import { searchIndex } from '../../data/search.js';
|
||||||
import { getPartialKeywords } from '../../data/search/partial.js';
|
import { getPartialKeywords } from '../../data/search/partial.js';
|
||||||
import type { APIv3KeywordsQuery, APIv3KeywordsResponse } from '../../types/server/keywords.js';
|
import type { APIv3KeywordsQuery, APIv3KeywordsResponse } from '../../types/server/keywords.js';
|
||||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate icons data
|
* Find full keywords for partial keyword
|
||||||
*/
|
*/
|
||||||
export function generateKeywordsResponse(query: FastifyRequest['query'], res: FastifyReply) {
|
export function createKeywordsResponse(q: Record<string, string>): number | APIv3KeywordsResponse {
|
||||||
const q = (query || {}) as Record<string, string>;
|
|
||||||
const wrap = checkJSONPQuery(q);
|
|
||||||
if (!wrap) {
|
|
||||||
// Invalid JSONP callback
|
|
||||||
res.send(400);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if search data is available
|
// Check if search data is available
|
||||||
const searchIndexData = searchIndex.data;
|
const searchIndexData = searchIndex.data;
|
||||||
if (!searchIndexData) {
|
if (!searchIndexData) {
|
||||||
res.send(404);
|
return 404;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const keywords = searchIndexData.keywords;
|
const keywords = searchIndexData.keywords;
|
||||||
|
|
||||||
|
|
@ -32,15 +21,16 @@ export function generateKeywordsResponse(query: FastifyRequest['query'], res: Fa
|
||||||
let failed = false;
|
let failed = false;
|
||||||
|
|
||||||
if (typeof q.prefix === 'string') {
|
if (typeof q.prefix === 'string') {
|
||||||
|
// Keywords should start with prefix
|
||||||
test = q.prefix;
|
test = q.prefix;
|
||||||
suffixes = false;
|
suffixes = false;
|
||||||
} else if (typeof q.keyword === 'string') {
|
} else if (typeof q.keyword === 'string') {
|
||||||
|
// All keywords that contain keyword
|
||||||
test = q.keyword;
|
test = q.keyword;
|
||||||
suffixes = true;
|
suffixes = true;
|
||||||
} else {
|
} else {
|
||||||
// Invalid query
|
// Invalid query
|
||||||
res.send(400);
|
return 400;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
test = test.toLowerCase().trim();
|
test = test.toLowerCase().trim();
|
||||||
|
|
||||||
|
|
@ -71,5 +61,5 @@ export function generateKeywordsResponse(query: FastifyRequest['query'], res: Fa
|
||||||
matches: failed || invalid ? [] : getPartialKeywords(test, suffixes, searchIndexData)?.slice(0) || [],
|
matches: failed || invalid ? [] : getPartialKeywords(test, suffixes, searchIndexData)?.slice(0) || [],
|
||||||
};
|
};
|
||||||
|
|
||||||
sendJSONResponse(response, q, wrap, res);
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,11 @@
|
||||||
import type { FastifyReply, FastifyRequest } from 'fastify';
|
|
||||||
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
|
import { getPrefixes, iconSets } from '../../data/icon-sets.js';
|
||||||
import type { APIv3LastModifiedResponse } from '../../types/server/modified.js';
|
import type { APIv3LastModifiedResponse } from '../../types/server/modified.js';
|
||||||
import { checkJSONPQuery, sendJSONResponse } from '../helpers/json.js';
|
|
||||||
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
import { filterPrefixesByPrefix } from '../helpers/prefixes.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate icons data
|
* Get last modified time for all icon sets
|
||||||
*/
|
*/
|
||||||
export function generateLastModifiedResponse(query: FastifyRequest['query'], res: FastifyReply) {
|
export function createLastModifiedResponse(q: Record<string, string>): number | APIv3LastModifiedResponse {
|
||||||
const q = (query || {}) as Record<string, string>;
|
|
||||||
const wrap = checkJSONPQuery(q);
|
|
||||||
if (!wrap) {
|
|
||||||
// Invalid JSONP callback
|
|
||||||
res.send(400);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Filter prefixes
|
// Filter prefixes
|
||||||
const prefixes = filterPrefixesByPrefix(getPrefixes(), q, false);
|
const prefixes = filterPrefixesByPrefix(getPrefixes(), q, false);
|
||||||
|
|
||||||
|
|
@ -36,5 +26,5 @@ export function generateLastModifiedResponse(query: FastifyRequest['query'], res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendJSONResponse(response, q, wrap, res);
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,28 +14,17 @@ const defaultSearchLimit = minSearchLimit * 2;
|
||||||
/**
|
/**
|
||||||
* Send API v2 response
|
* Send API v2 response
|
||||||
*/
|
*/
|
||||||
export function generateAPIv2SearchResponse(query: FastifyRequest['query'], res: FastifyReply) {
|
export function createAPIv2SearchResponse(q: Record<string, string>): number | APIv2SearchResponse {
|
||||||
const q = (query || {}) as Record<string, string>;
|
|
||||||
|
|
||||||
const wrap = checkJSONPQuery(q);
|
|
||||||
if (!wrap) {
|
|
||||||
// Invalid JSONP callback
|
|
||||||
res.send(400);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if search data is available
|
// Check if search data is available
|
||||||
const searchIndexData = searchIndex.data;
|
const searchIndexData = searchIndex.data;
|
||||||
if (!searchIndexData) {
|
if (!searchIndexData) {
|
||||||
res.send(404);
|
return 404;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get query
|
// Get query
|
||||||
const keyword = q.query;
|
const keyword = q.query;
|
||||||
if (!keyword) {
|
if (!keyword) {
|
||||||
res.send(400);
|
return 400;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to params
|
// Convert to params
|
||||||
|
|
@ -49,16 +38,14 @@ export function generateAPIv2SearchResponse(query: FastifyRequest['query'], res:
|
||||||
if (v2Query.limit) {
|
if (v2Query.limit) {
|
||||||
const limit = parseInt(v2Query.limit);
|
const limit = parseInt(v2Query.limit);
|
||||||
if (!limit) {
|
if (!limit) {
|
||||||
res.send(400);
|
return 400;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
params.limit = Math.max(minSearchLimit, Math.min(limit, maxSearchLimit));
|
params.limit = Math.max(minSearchLimit, Math.min(limit, maxSearchLimit));
|
||||||
}
|
}
|
||||||
if (v2Query.min) {
|
if (v2Query.min) {
|
||||||
const limit = parseInt(v2Query.min);
|
const limit = parseInt(v2Query.min);
|
||||||
if (!limit) {
|
if (!limit) {
|
||||||
res.send(400);
|
return 400;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
params.limit = Math.max(minSearchLimit, Math.min(limit, maxSearchLimit));
|
params.limit = Math.max(minSearchLimit, Math.min(limit, maxSearchLimit));
|
||||||
params.softLimit = true;
|
params.softLimit = true;
|
||||||
|
|
@ -68,8 +55,7 @@ export function generateAPIv2SearchResponse(query: FastifyRequest['query'], res:
|
||||||
if (v2Query.start) {
|
if (v2Query.start) {
|
||||||
start = parseInt(v2Query.start);
|
start = parseInt(v2Query.start);
|
||||||
if (isNaN(start) || start < 0 || start >= params.limit) {
|
if (isNaN(start) || start < 0 || start >= params.limit) {
|
||||||
res.send(400);
|
return 400;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,5 +116,5 @@ export function generateAPIv2SearchResponse(query: FastifyRequest['query'], res:
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
sendJSONResponse(response, q, wrap, res);
|
return response;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
17
src/index.ts
17
src/index.ts
|
|
@ -1,30 +1,19 @@
|
||||||
import { config } from 'dotenv';
|
import { config } from 'dotenv';
|
||||||
import { getImporters } from './config/icon-sets.js';
|
|
||||||
import { iconSetsStorage } from './data/icon-set/store/storage.js';
|
|
||||||
import { setImporters, updateIconSets } from './data/icon-sets.js';
|
|
||||||
import { loaded } from './data/loading.js';
|
import { loaded } from './data/loading.js';
|
||||||
import { cleanupStorageCache } from './data/storage/startup.js';
|
|
||||||
import { startHTTPServer } from './http/index.js';
|
import { startHTTPServer } from './http/index.js';
|
||||||
import { loadEnvConfig } from './misc/load-config.js';
|
import { loadEnvConfig } from './misc/load-config.js';
|
||||||
|
import { initAPI } from './init.js';
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
// Configure environment
|
// Configure environment
|
||||||
config();
|
config();
|
||||||
loadEnvConfig();
|
loadEnvConfig();
|
||||||
|
|
||||||
// Reset old cache
|
|
||||||
await cleanupStorageCache(iconSetsStorage);
|
|
||||||
|
|
||||||
// Start HTTP server
|
// Start HTTP server
|
||||||
startHTTPServer();
|
startHTTPServer();
|
||||||
|
|
||||||
// Get all importers and load data
|
// Init API
|
||||||
const importers = await getImporters();
|
await initAPI();
|
||||||
for (let i = 0; i < importers.length; i++) {
|
|
||||||
await importers[i].init();
|
|
||||||
}
|
|
||||||
setImporters(importers);
|
|
||||||
updateIconSets();
|
|
||||||
|
|
||||||
// Loaded
|
// Loaded
|
||||||
loaded();
|
loaded();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { getImporters } from './config/icon-sets.js';
|
||||||
|
import { iconSetsStorage } from './data/icon-set/store/storage.js';
|
||||||
|
import { setImporters, updateIconSets } from './data/icon-sets.js';
|
||||||
|
import { cleanupStorageCache } from './data/storage/startup.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init API
|
||||||
|
*/
|
||||||
|
export async function initAPI() {
|
||||||
|
// Reset old cache
|
||||||
|
await cleanupStorageCache(iconSetsStorage);
|
||||||
|
|
||||||
|
// Get all importers and load data
|
||||||
|
const importers = await getImporters();
|
||||||
|
for (let i = 0; i < importers.length; i++) {
|
||||||
|
await importers[i].init();
|
||||||
|
}
|
||||||
|
setImporters(importers);
|
||||||
|
updateIconSets();
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue