add graphql server

Signed-off-by: Alexis Tyler <xo@wvvw.me>
This commit is contained in:
Alexis Tyler
2019-06-11 00:58:18 +09:30
parent fa75ebba8b
commit 93e1737b34
5 changed files with 4333 additions and 0 deletions

128
app/index.js Normal file
View 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
View 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;
});
}
};
};

1
index.js Normal file
View File

@@ -0,0 +1 @@
require('./app');

22
package.json Normal file
View 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"
}
}

4084
yarn.lock Normal file

File diff suppressed because it is too large Load Diff