Commit abf68b72 authored by Vadym Gidulian's avatar Vadym Gidulian

Initial commit

parents
/.data
.env
# Dependency directories
node_modules/
# Logs
logs/
*.log
npm-debug.log*
# Optional npm cache directory
.npm/
# Optional REPL history
.node_repl_history/
services:
- docker:dind
variables:
IMAGE_SNAPSHOT: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_NAME
IMAGE_LATEST: $CI_REGISTRY_IMAGE:latest
stages:
- build
- release
before_script:
- docker version
- docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
build:
stage: build
script:
- docker build --pull -t $IMAGE_SNAPSHOT .
- docker push $IMAGE_SNAPSHOT
tags:
- dind
release:
stage: release
variables:
GIT_STRATEGY: none
script:
- docker pull $IMAGE_SNAPSHOT
- docker tag $IMAGE_SNAPSHOT $IMAGE
- docker push $IMAGE
tags:
- dind
release:latest:
stage: release
variables:
GIT_STRATEGY: none
script:
- docker pull $IMAGE_SNAPSHOT
- docker tag $IMAGE_SNAPSHOT $IMAGE_LATEST
- docker push $IMAGE_LATEST
when: manual
tags:
- dind
only:
- master
FROM node:10-alpine
MAINTAINER User <email>
COPY package.json yarn.lock /app/
COPY src/ /app/src/
WORKDIR /app
RUN yarn install --production &&\
yarn cache clean
CMD ["node", "src"]
EXPOSE 80
version: '3'
services:
server:
build: .
volumes:
- .:/app
entrypoint: sh
command: -c "yarn install --production=false && npx nodemon --signal SIGHUP src"
environment:
NODE_ENV: $NODE_ENV
CORS_ALLOW_ORIGIN: $CORS_ALLOW_ORIGIN
MONGODB_URI: $MONGODB_URI
SEQID_GEN_API: $SEQID_GEN_API
networks:
default:
aliases:
- api
mongo:
image: mongo
volumes:
- ./.data/mongo:/data/db
seqid-gen:
image: registry.git.gvia.group/microservices/seqid-gen:1.0
volumes:
- ./.data/seqid-gen:/data
networks:
default:
external:
name: $DOCKER_NETWORK
{
"name": "project",
"version": "version",
"dependencies": {
"axios": "^0.18.0",
"express": "^4.16.4",
"mongodb": "^3.1.13",
"response-time": "^2.3.2",
"@gviagroup/seqid-gen-api": "^1.0.0"
},
"devDependencies": {
"nodemon": "^1.18.9"
}
}
'use strict';
const {Router} = require('express');
const router = module.exports = Router();
router.use('/status', require('./status'));
'use strict';
const {Router} = require('express');
const {handle, methodNotAllowed} = require('../utils/api');
const db = require('../utils/db');
const router = module.exports = Router();
const methodNotAllowedHandler = methodNotAllowed(router);
router.route('/')
.get(handle(undefined, () => ({
isAlive: true,
dependencies: {},
modules: {
mongo: {
isConnected: db.isConnected()
}
}
})))
.all(methodNotAllowedHandler);
'use strict';
const NODE_ENV = process.env.NODE_ENV || 'development';
const CORS_ALLOW_ORIGIN = process.env.CORS_ALLOW_ORIGIN || '*';
const express = require('express');
const responseTime = require('response-time');
const grace = require('./utils/grace');
const {logError, logInfo, logWarn} = require('./utils/log');
logInfo(`Running in ${NODE_ENV} environment`);
const app = express();
app.use(express.json({
limit: '1mb'
}));
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', CORS_ALLOW_ORIGIN);
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Accept, Content-Type');
res.header('Access-Control-Allow-Methods', 'OPTIONS, HEAD, GET');
res.header('Access-Control-Expose-Headers', 'Content-Length');
next();
});
app.use(responseTime());
app.use(require('./apis/index'));
switch (NODE_ENV) {
case 'production':
app.use((req, res) => res.status(404).send());
app.use((err, req, res, next) => {
logError(err);
if (!res.finished) res.status(500).send();
});
break;
default:
app.use((req, res) => {
logWarn(`${req.url} was not handled`);
res.status(404).set('Content-Type', 'text/plain').send(`${req.url} was not handled`);
});
app.use((err, req, res, next) => {
logError(err);
if (!res.finished) res.status(500).set('Content-Type', 'text/plain').send(err.stack);
});
}
const server = app.listen(80, () => {
logInfo('Server is started.');
});
grace.registerShutdownHandler(() => new Promise((resolve, reject) => server.close(err => err ? reject(err) : resolve())),
{description: 'Closing server connections'});
'use strict';
module.exports = {
};
'use strict';
module.exports = {
};
'use strict';
module.exports = {
};
'use strict';
const IS_EMPTY = Symbol('isEmpty');
module.exports = {
SYMBOLS: {
IS_EMPTY
},
handle(requestValidator, handler, responseValidator) {
return async function (req, res, next) {
const nextWatcher = new NextWatcher(next);
const _next = nextWatcher.watcher.bind(nextWatcher);
let response;
if (requestValidator) {
try {
await requestValidator(req, res, _next);
if (nextWatcher.wasCalled) return nextWatcher.doNext();
} catch (e) { // Probably bad request
return res.status(400).send(e.message);
}
}
if (handler) {
try {
response = await handler(req, res, _next);
if (nextWatcher.wasCalled) return nextWatcher.doNext();
} catch (e) { // Operation error
const code = e.code || (e.response && e.response.code);
if (/^4\d\d/.test(code)) return res.status(code).send(e.message);
return next(e);
}
}
if (responseValidator) {
try {
response = await responseValidator(response, req, res, _next);
if (nextWatcher.wasCalled) return nextWatcher.doNext();
} catch (e) { // Probably bad response
return next(e);
}
}
if (!response && !res[IS_EMPTY]) return res.status(404).send(response);
res.status(res.statusCode || 200).send(response);
}
},
methodNotAllowed(router) {
return function (req, res) {
const layer = router.stack.filter(layer => layer.path === req.path)[0];
if (!layer) return res.status(404).send();
const routeMethods = Object.keys(layer.route.methods);
const allowedMethods = routeMethods
.filter(method => method[0] !== '_')
.map(method => method.toUpperCase())
.join(', ');
return res.status(405).set('Allow', allowedMethods).send();
};
}
};
class NextWatcher {
constructor(next) {
this._next = next;
this.wasCalled = false;
}
doNext() {
return this._next(...this._passedArgs);
}
watcher(...args) {
this._passedArgs = args;
this.wasCalled = true;
}
}
'use strict';
const grace = require('./grace');
const {createError, logError, logInfo, logWarn} = require('./log');
['MONGODB_URI', 'SEQID_GEN_API'].forEach(varname => {
if (!process.env[varname]) {
logError(`Environment variable ${varname} is not defined!`);
grace.shutdown(1);
}
});
const {MongoClient} = require('mongodb');
const SeqidGenApi = require('@gviagroup/seqid-gen-api');
let mongoClient = null;
const seqidGen = new SeqidGenApi(process.env.SEQID_GEN_API);
grace.registerShutdownHandler(async () => {
if (mongoClient) await mongoClient.close();
}, {description: 'Closing MongoDB connection'});
module.exports = {
async getConnection() {
if (!mongoClient) {
logInfo('Connecting to MongoDB...');
await connectToMongo();
if (!mongoClient) return void grace.shutdown(2);
}
if (!mongoClient.isConnected()) {
throw createError('MongoDB client is not connected', 500);
}
return mongoClient;
},
isConnected() {
return mongoClient ? mongoClient.isConnected() : false;
},
seqidGen
};
async function connectToMongo() {
try {
mongoClient = await MongoClient.connect(process.env.MONGODB_URI, {useNewUrlParser: true});
mongoClient.on('close', () => logWarn('MongoDb client\'s connection was closed'));
mongoClient.on('reconnect', () => logWarn('MongoDB client was reconnected'));
logInfo('Connected.');
} catch (e) {
logError(e);
}
}
'use strict';
const {logError, logInfo} = require('./log');
const shutdownHandlers = {};
['SIGHUP', 'SIGINT', 'SIGTERM'].forEach(signal => {
process.once(signal, async () => {
logInfo(`Received ${signal}. Server is shutting down gracefully...`);
await module.exports.handleShutdown();
logInfo('Shutting down now...');
process.kill(process.pid, signal);
});
});
module.exports = {
async handleShutdown() {
const priorities = Object.keys(shutdownHandlers).sort((p1, p2) => p1 - p2);
for (const priority of priorities) {
for (const h of shutdownHandlers[priority]) {
try {
if (h.description) logInfo(` - ${h.description}`);
await h.handler();
} catch (e) {
logError(e);
}
}
}
},
registerShutdownHandler(handler, options) {
options = Object.assign({}, {priority: 0}, options);
if (!shutdownHandlers[options.priority]) shutdownHandlers[options.priority] = [];
shutdownHandlers[options.priority].push({
description: options.description,
handler
});
},
async shutdown(code) {
logInfo('Server is shutting down gracefully...');
await this.handleShutdown();
process.exit(code || 0);
}
};
'use strict';
module.exports = {
createError(message, code, cause) {
const e = new Error(message);
if (code) e.code = code;
if (cause) e.cause = cause;
return e;
},
log() {
console.log(`[${getCurrentDateString()}]`, ...arguments);
},
logDebug() {
console.debug(`[${getCurrentDateString()}][🔹DEBUG]`, ...arguments);
},
logError() {
console.error(`[${getCurrentDateString()}][‼️ ERROR]`, ...arguments);
},
logInfo() {
console.info(`[${getCurrentDateString()}][ℹ️ INFO]`, ...arguments);
},
logWarn() {
console.warn(`[${getCurrentDateString()}][⚠️ WARN]`, ...arguments);
}
};
function getCurrentDateString() {
return new Date().toISOString();
}
'use strict';
module.exports = {
};
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment