mirror of
https://github.com/unraid/api.git
synced 2026-01-07 09:10:05 -06:00
128
app/index.js
Normal file
128
app/index.js
Normal file
@@ -0,0 +1,128 @@
|
||||
const path = require('path');
|
||||
const am = require('am');
|
||||
const camelcase = require('camelcase');
|
||||
const Injector = require('bolus');
|
||||
|
||||
// Set the working directory to this one.
|
||||
process.chdir(__dirname);
|
||||
|
||||
// Create an $injector.
|
||||
const $injector = new Injector();
|
||||
|
||||
// Register the imported modules with default names.
|
||||
$injector.registerImports([
|
||||
'net',
|
||||
'express',
|
||||
'apollo-server-express',
|
||||
'graphql',
|
||||
'deepmerge',
|
||||
'stoppable'
|
||||
], module);
|
||||
|
||||
// Register modules that need require and not import
|
||||
$injector.registerRequires([
|
||||
'graphql'
|
||||
], module);
|
||||
|
||||
// Register the imported modules with custom names.
|
||||
$injector.registerImports({
|
||||
get: 'lodash.get',
|
||||
graphqlDirective: 'graphql-directive',
|
||||
mergeGraphqlSchemas: 'merge-graphql-schemas',
|
||||
GraphQLJSON: 'graphql-type-json',
|
||||
GraphQLLong: 'graphql-type-long',
|
||||
GraphQLUUID: 'graphql-type-uuid'
|
||||
}, module);
|
||||
|
||||
// Register all of the single js files as modules.
|
||||
$injector.registerPath([
|
||||
'*.js',
|
||||
], defaultName => camelcase(defaultName));
|
||||
|
||||
// Register graphql schema
|
||||
$injector.registerPath([
|
||||
'./graphql/schema/**/*.js',
|
||||
], defaultName => camelcase(defaultName));
|
||||
|
||||
// Register core
|
||||
$injector.registerPath(path.resolve(process.env.CORE_CWD || path.join(__dirname, '../node_modules/core')));
|
||||
|
||||
const main = async () => {
|
||||
const core = $injector.resolve('core');
|
||||
|
||||
// Load core
|
||||
await core.load();
|
||||
|
||||
// Must be loaded after core
|
||||
const log = $injector.resolve('log');
|
||||
const config = $injector.resolve('config');
|
||||
|
||||
// Must be loaded after deps above and core
|
||||
const server = $injector.resolve('server');
|
||||
|
||||
// Start the server.
|
||||
await server.start();
|
||||
|
||||
log.info('Listening on port %s.', config.get('port'));
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
log.debug('SIGINT signal received.');
|
||||
server.stop();
|
||||
});
|
||||
}
|
||||
|
||||
// Boot app
|
||||
am(main, error => {
|
||||
try {
|
||||
const corePath = path.resolve(process.env.CORE_CWD || path.join(__dirname, '../node_modules/core'));
|
||||
const errorsRegistered = $injector.isRegistered('FileMissingError');
|
||||
const logRegistered = $injector.isRegistered('log');
|
||||
|
||||
// Register errors if they're not already registered
|
||||
if (!errorsRegistered) {
|
||||
$injector.registerPath([
|
||||
corePath + '/errors/*.js'
|
||||
], defaultName => camelcase(defaultName, { pascalCase: true }));
|
||||
}
|
||||
|
||||
// Register log if it's not already registered
|
||||
if (!logRegistered) {
|
||||
$injector.registerPath([
|
||||
path.join(corePath, 'log.js')
|
||||
]);
|
||||
}
|
||||
|
||||
const log = $injector.resolve('log');
|
||||
const FileMissingError = $injector.resolve('FileMissingError');
|
||||
|
||||
// Allow optional files to throw but keep the app running
|
||||
if (error instanceof FileMissingError) {
|
||||
log.warn(error.message);
|
||||
|
||||
if (!error.filePath.includes('disk-load.ini')) {
|
||||
// Kill applicaiton
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
// Log last error
|
||||
log.error(error);
|
||||
|
||||
// Kill applicaiton
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// We should only end here if errors or log have an issue loading
|
||||
} catch (error) {
|
||||
// Log last error
|
||||
console.error(error);
|
||||
|
||||
// Kill applicaiton
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// If repl exists we're in the repl so attach the injector for debugging
|
||||
// We don't check for the NODE_ENV as we need this to debug all envs
|
||||
if (global.repl) {
|
||||
global.$injector = $injector;
|
||||
}
|
||||
98
app/server.js
Normal file
98
app/server.js
Normal file
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* The Graphql server.
|
||||
*/
|
||||
module.exports = function ($injector, fs, net, express, config, log, getEndpoints, stoppable) {
|
||||
const app = express();
|
||||
const port = config.get('port');
|
||||
let server;
|
||||
|
||||
const ApolloServer = $injector.resolve('apollo-server-express').ApolloServer;
|
||||
const graphql = $injector.resolvePath(__dirname + '/graphql');
|
||||
|
||||
// Mount graph endpoint
|
||||
const graphApp = new ApolloServer(graphql);
|
||||
graphApp.applyMiddleware({ app });
|
||||
|
||||
// List all endpoints at start of server
|
||||
app.get('/', (req, res, next) => {
|
||||
return res.send(getEndpoints(app));
|
||||
});
|
||||
|
||||
// Handle errors by logging them and returning a 500.
|
||||
app.use(function (err, req, res, next) {
|
||||
log.error(err);
|
||||
if (err.stack) {
|
||||
err.stackTrace = err.stack;
|
||||
}
|
||||
res.status(500).send(err);
|
||||
});
|
||||
|
||||
// Return an object with start and stop methods.
|
||||
return {
|
||||
start() {
|
||||
server = stoppable(app.listen(port, () => {
|
||||
// Downgrade process user to owner of this file
|
||||
return fs.stat(__filename, (err, stats) => {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return process.setuid(stats.uid);
|
||||
});
|
||||
}));
|
||||
|
||||
// Port is a UNIX socket file
|
||||
if (isNaN(parseInt(port, 10))) {
|
||||
server.on('listening', () => {
|
||||
// In production this will let pm2 know we're ready
|
||||
if (process.send) {
|
||||
process.send('ready');
|
||||
}
|
||||
|
||||
// Set permissions
|
||||
return fs.chmodSync(port, 777);
|
||||
});
|
||||
|
||||
// Double-check EADDRINUSE
|
||||
server.on('error', error => {
|
||||
if (error.code !== 'EADDRINUSE') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
net.connect({
|
||||
path: port
|
||||
}, () => {
|
||||
// Really in use: re-throw
|
||||
throw error;
|
||||
}).on('error', error => {
|
||||
if (error.code !== 'ECONNREFUSED') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Not in use: delete it and re-listen
|
||||
fs.unlinkSync(port);
|
||||
server.listen(port);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return server;
|
||||
},
|
||||
stop() {
|
||||
// Stop the server from accepting new connections and close existing connections
|
||||
return server.close(err => {
|
||||
if (err) {
|
||||
log.error(err);
|
||||
// Exit with error (code 1)
|
||||
// eslint-disable-next-line
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
log.info('Server shutting down..');
|
||||
|
||||
// Gracefully exit
|
||||
process.exitCode = 0;
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
22
package.json
Normal file
22
package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"am": "^1.0.1",
|
||||
"apollo-datasource-rest": "^0.5.0",
|
||||
"apollo-server-express": "^2.6.2",
|
||||
"bolus": "https://github.com/omgimalexis/bolus",
|
||||
"camelcase": "^5.3.1",
|
||||
"core": "ssh://git@github.com:unraid/core.git",
|
||||
"express": "^4.17.1",
|
||||
"got": "^9.6.0",
|
||||
"graphql": "^14.3.1",
|
||||
"graphql-directive": "^0.2.1",
|
||||
"graphql-type-json": "^0.3.0",
|
||||
"graphql-type-long": "^0.1.1",
|
||||
"graphql-type-uuid": "^0.2.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"merge-graphql-schemas": "^1.5.8",
|
||||
"p-props": "^3.0.1",
|
||||
"stoppable": "^1.1.0",
|
||||
"utils": "https://github.com/unraid/utils"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user