api.js/app.js

208 lines
6.2 KiB
JavaScript

/**
* This file is part of the @iconify/api package.
*
* (c) Vjacheslav Trushkin <cyberalien@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
"use strict";
// Load required modules
const fs = require('fs'),
util = require('util'),
express = require('express');
// Log uncaught exceptions to stderr
process.on('uncaughtException', function (err) {
console.error('Uncaught exception:', err);
});
// Create application
let app = {
root: __dirname
};
/**
* Load config.json and config-default.json
*/
app.config = JSON.parse(fs.readFileSync(__dirname + '/config-default.json', 'utf8'));
try {
let customConfig = fs.readFileSync(__dirname + '/config.json', 'utf8');
if (typeof customConfig === 'string') {
try {
customConfig = JSON.parse(customConfig);
Object.keys(customConfig).forEach(key => {
if (typeof app.config[key] !== typeof customConfig[key]) {
return;
}
if (typeof app.config[key] === 'object') {
// merge object
Object.assign(app.config[key], customConfig[key]);
} else {
// overwrite scalar variables
app.config[key] = customConfig[key];
}
});
} catch (err) {
console.error('Error parsing config.json', err);
}
}
} catch (err) {
console.log('Missing config.json. Using default API configuration');
}
// Add logging and mail modules
app.mail = require('./src/mail').bind(this, app);
let log = require('./src/log');
app.log = log.bind(this, app, false);
app.error = log.bind(this, app, true);
app.logger = require('./src/logger').bind(this, app);
/**
* Validate configuration
*/
// Port
if (app.config['env-port'] && process.env.PORT) {
app.config.port = process.env.PORT;
}
// Region file to easy identify server in CDN
if (!app.config['env-region'] && process.env.region) {
app.config.region = process.env.region;
}
if (app.config.region.length > 10 || !app.config.region.match(/^[a-z0-9_-]+$/i)) {
app.config.region = '';
app.error('Invalid value for region config variable.');
}
// Reload secret key
if (app.config['reload-secret'] === '') {
// Add reload-secret to config.json to be able to run /reload?key=your-secret-key that will reload collections without restarting server
console.log('reload-secret configuration is empty. You will not be able to update all collections without restarting server.');
}
/**
* Continue loading modules
*/
// Get version
app.version = JSON.parse(fs.readFileSync(__dirname + '/package.json', 'utf8')).version;
// Files helper
app.fs = require('./src/files')(app);
// JSON loader
app.loadJSON = require('./src/json').bind(this, app);
// Add directories storage
app.dirs = require('./src/dirs')(app);
if (!app.dirs.getRepos().length) {
console.error('No repositories found. Make sure either Iconify or custom repository is set in configuration.');
return;
}
// Collections
app.collections = Object.create(null);
app.reload = require('./src/reload').bind(this, app);
// Sync module
app.sync = require('./src/sync').bind(this, app);
// API request and response handlers
app.response = require('./src/response').bind(this, app);
app.iconsRequest = require('./src/request-icons').bind(this, app);
app.miscRequest = require('./src/request').bind(this, app);
// Start application
require('./src/startup')(app).then(() => {
// Create HTTP server
app.server = express();
// Disable X-Powered-By header
app.server.disable('x-powered-by');
// CORS
app.server.options('/*', (req, res) => {
if (app.config.cors) {
res.header('Access-Control-Allow-Origin', app.config.cors.origins);
res.header('Access-Control-Allow-Methods', app.config.cors.methods);
res.header('Access-Control-Allow-Headers', app.config.cors.headers);
res.header('Access-Control-Max-Age', app.config.cors.timeout);
}
res.send(200);
});
// GET 3 part request
app.server.get(/^\/([a-z0-9-]+)\/([a-z0-9-]+)\.(js|json|svg)$/, (req, res) => {
// prefix/icon.svg
// prefix/icons.json
app.iconsRequest(req, res, req.params[0], req.params[1], req.params[2]);
});
// GET 2 part JS/JSON request
app.server.get(/^\/([a-z0-9-]+)\.(js|json)$/, (req, res) => {
// prefix.json
app.iconsRequest(req, res, req.params[0], 'icons', req.params[1]);
});
// GET 2 part SVG request
app.server.get(/^\/([a-z0-9:-]+)\.svg$/, (req, res) => {
let parts = req.params[0].split(':');
if (parts.length === 2) {
// prefix:icon.svg
app.iconsRequest(req, res, parts[0], parts[1], 'svg');
return;
}
if (parts.length === 1) {
parts = parts[0].split('-');
if (parts.length > 1) {
// prefix-icon.svg
app.iconsRequest(req, res, parts.shift(), parts.join('-'), 'svg');
return;
}
}
app.response(req, res, 404);
});
// Send robots.txt that disallows everything
app.server.get('/robots.txt', (req, res) => app.miscRequest(req, res, 'robots'));
app.server.post('/robots.txt', (req, res) => app.miscRequest(req, res, 'robots'));
// API version information
app.server.get('/version', (req, res) => app.miscRequest(req, res, 'version'));
// Reload collections without restarting app
app.server.get('/reload', (req, res) => app.miscRequest(req, res, 'reload'));
app.server.post('/reload', (req, res) => app.miscRequest(req, res, 'reload'));
// Get latest collection from Git repository
app.server.get('/sync', (req, res) => app.miscRequest(req, res, 'sync'));
app.server.post('/sync', (req, res) => app.miscRequest(req, res, 'sync'));
// Redirect home page
app.server.get('/', (req, res) => {
res.redirect(301, app.config['index-page']);
});
// Create server
app.server.listen(app.config.port, () => {
app.log('Listening on port ' + app.config.port);
});
}).catch(err => {
console.error(err);
});