diff --git a/.gitignore b/.gitignore index d6a1ede..524f5a6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ build/ node_modules/ .idea/ .env -**/*.js \ No newline at end of file +**/*.js +yarn-error.log diff --git a/package.json b/package.json index 8cf0cc5..69e4e68 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,8 @@ "cors": "^2.8.5", "dotenv": "^5.0.1", "express": "^4.16.3", + "express-async-handler": "^1.1.4", + "http-status-codes": "^1.4.0", "morgan": "^1.9.0", "redis": "^2.8.0", "sse-channel": "^3.0.1", @@ -39,6 +41,7 @@ "@types/mocha": "^5.2.0", "chai": "^4.1.2", "dockerode": "^2.5.5", - "mocha": "^5.2.0" + "mocha": "^5.2.0", + "sleep-promise": "^8.0.1" } } diff --git a/src/config.ts b/src/config.ts index a6ebea5..fc1191f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -19,4 +19,4 @@ if (!config.redisPassword) { throw new Error("No redis password found in env!"); } -export default config; \ No newline at end of file +export default config; diff --git a/src/controllers/ClientController.ts b/src/controllers/ClientController.ts index 1bdd362..996b379 100644 --- a/src/controllers/ClientController.ts +++ b/src/controllers/ClientController.ts @@ -6,14 +6,14 @@ import pushServiceRetriever from "../services/PushService"; const clientRestController = Router(); const pushService = pushServiceRetriever(); -clientRestController.get("/", (req:WinstonRequest, res:Response) => { +clientRestController.get("/", (req: WinstonRequest, res: Response) => { log.info("Hit base url.", req.winstonMetadata); res.status(200).send("Hello world!").end(); }); -clientRestController.get("/register", (req:WinstonRequest, res:Response) => { +clientRestController.get("/register", (req: WinstonRequest, res: Response) => { log.info("Registered new client.", req.winstonMetadata); pushService.addClient(req, res); }); -export default clientRestController; \ No newline at end of file +export default clientRestController; diff --git a/src/controllers/HealthController.ts b/src/controllers/HealthController.ts new file mode 100644 index 0000000..6f76c9b --- /dev/null +++ b/src/controllers/HealthController.ts @@ -0,0 +1,21 @@ +import {Response, Router} from "express"; +import redisServiceRetriever from "../services/RedisUpdaterService"; +import config from "../config"; +import * as asyncHandler from "express-async-handler"; +import {WinstonRequest} from "../middleware/logging-metadata"; +import * as HttpStatus from "http-status-codes"; + +const healthRestController = Router(); +const redisService = redisServiceRetriever(config); + +healthRestController.get("/", asyncHandler(async (req: WinstonRequest, res: Response) => { + const connected = await redisService.sendTestMessage(); + + if (connected) { + res.sendStatus(HttpStatus.OK); + } else { + res.sendStatus(HttpStatus.INTERNAL_SERVER_ERROR); + } +})); + +export default healthRestController; diff --git a/src/index.ts b/src/index.ts index bb0046d..fe62e8f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,11 +2,12 @@ import * as express from "express"; import {Response} from "express"; import * as morgan from "morgan"; import log from "./logger"; -import config from "./config"; import addLoggingInfo from "./middleware/logging-metadata"; import jsonParse from "./middleware/json-parse"; import PushServiceClientController from "./controllers/ClientController"; +import HealthController from "./controllers/HealthController"; import * as cors from "cors"; +import config from "./config"; const app = express(); @@ -18,6 +19,7 @@ app.use(jsonParse); app.use(addLoggingInfo); app.use("/push/", PushServiceClientController); +app.use("/healthz/", HealthController); app.get("/", (_, res:Response) => { res.status(200).send(`Quest tracker notification service -- version ${process.env.npm_package_version}`); diff --git a/src/services/PushService.ts b/src/services/PushService.ts index 8a1bc69..070e317 100644 --- a/src/services/PushService.ts +++ b/src/services/PushService.ts @@ -1,9 +1,9 @@ import {Request, Response} from "express"; import {v4 as uuid} from "uuid"; import redisService, {RedisUpdaterService} from "./RedisUpdaterService"; -import config from "../config"; import {GenericAdd, GenericDeletion, GenericUpdate} from "common-interfaces/QuestInterfaces"; import {MessageType} from "common-interfaces/NotificationInterfaces"; +import config from "../config"; import SseChannel = require("sse-channel"); let serviceInstance: PushService | null = null; @@ -11,7 +11,7 @@ let serviceInstance: PushService | null = null; export class PushService { private channel:SseChannel; - constructor(redisService:RedisUpdaterService) { + constructor(redisService: RedisUpdaterService) { this.channel = new SseChannel({jsonEncode: true, cors: {origins: "*"}}); redisService.toNotifyOnAdd.push(this.addNewItem.bind(this)); @@ -19,7 +19,7 @@ export class PushService { redisService.toNotifyOnRemove.push(this.deleteItem.bind(this)); } - addClient(req:Request, res:Response) { + addClient(req: Request, res: Response) { this.channel.addClient(req, res); } diff --git a/src/services/RedisUpdaterService.ts b/src/services/RedisUpdaterService.ts index 82c7312..cca8325 100644 --- a/src/services/RedisUpdaterService.ts +++ b/src/services/RedisUpdaterService.ts @@ -1,23 +1,39 @@ -import {createClient, RedisClient} from "redis"; +import {createClient, RedisClient, RetryStrategyOptions} from "redis"; import {Configuration} from "../config"; import log from "../logger"; import {GenericAdd, GenericDeletion, GenericUpdate} from "common-interfaces/QuestInterfaces"; +import {RedisTestPayload} from "./customTypes/RedisTestTypes"; type QuestAddedCallback = (update: GenericAdd) => void; type QuestUpdatedCallback = (update: GenericUpdate) => void; type QuestDeletedCallback = (update: GenericDeletion) => void; +type TestListenerRegistration = { + id: number; + callbackFn: (input: RedisTestPayload) => void; +}; let serviceSingleton: RedisUpdaterService | null = null; export class RedisUpdaterService { - private subscription:RedisClient; - public readonly toNotifyOnAdd:Array; - public readonly toNotifyOnUpdate:Array; - public readonly toNotifyOnRemove:Array; + public readonly toNotifyOnAdd: Array; + public readonly toNotifyOnUpdate: Array; + public readonly toNotifyOnRemove: Array; + + /** + * Base client is used for typical redis stuff. Needed because the client goes into "subscriber mode" on subscribe. + * + * @see https://github.com/noderedis/node_redis#publish--subscribe + */ + private baseClient: RedisClient; + /** Subscription is used for listening to posted channel messages */ + private subscription: RedisClient; + + private toNotifyOnTest: Array; private ADD_CHANNEL = "new-quests"; private UPDATE_CHANNEL = "quest-updates"; private REMOVE_CHANNEL = "removed-quests"; + private TEST_CHANNEL = "test-connectivity"; private RECONNECT_WAIT_TIME = 10000; constructor(config:Configuration) { @@ -28,18 +44,24 @@ export class RedisUpdaterService { this.toNotifyOnAdd = []; this.toNotifyOnUpdate = []; this.toNotifyOnRemove = []; + this.toNotifyOnTest = []; - this.subscription = createClient(config.redisUrl, { - password: config.redisPassword, - retry_strategy: (options) => { - - if (options.attempt > 12) { - log.error("Lost connection with redis after 12 attempts! Shutting down server."); - return Error("Could not connect to redis after 12 attempts."); - } + let strategy = (options: RetryStrategyOptions) => { - return this.RECONNECT_WAIT_TIME; + if (options.attempt > 12) { + log.error("Lost connection with redis after 12 attempts! Shutting down server."); + return Error("Could not connect to redis after 12 attempts."); } + + return this.RECONNECT_WAIT_TIME; + }; + this.baseClient = createClient(config.redisUrl, { + password: config.redisPassword, + retry_strategy: strategy + }); + this.subscription = createClient(config.redisUrl, { + password: config.redisPassword, + retry_strategy: strategy }); this.subscribeToRedisTopics(); @@ -51,13 +73,65 @@ export class RedisUpdaterService { // This is really only used in testing public disconnect() { this.subscription.quit(); + this.baseClient.quit(); + } + + /** + * Sends a test message through redis pubsub with a unique value and waits 3 seconds to receive the value back. + * If we don't see the value within 3 seconds, we time out and say we can't reach redis. + * + * @return A promise which resolves true if we can talk to redis or false if we can't + */ + public sendTestMessage(): Promise { + return new Promise((resolve) => { + const randomNum = Math.floor(1000 * Math.random()); + const payload: RedisTestPayload = { + testValue: randomNum + }; + let receivedInput = false; + log.debug("Launching promise"); + + // Option 1 - Receive response before timeout and our number matches. Resolve with true, our connection to redis is working + const listnenerFn = (response: RedisTestPayload) => { + // If we got a different value than the one we sent, ignore + log.debug("Got callback"); + if (response.testValue !== randomNum) return; + receivedInput = true; + this.removeTestListenerByID(randomNum); + resolve(true); + }; + // Option 2 - We don't receive a response after 3 seconds, we assume the redis connection isn't working and resolve with false + setTimeout(() => { + log.debug("Timed out"); + if (receivedInput) return; + log.error(`Health test timeout, did not receive value (${randomNum}).`); + this.removeTestListenerByID(randomNum); + resolve(false); + }, 3000); + + log.debug("Adding listener"); + // Add the listener and send the message + this.toNotifyOnTest.push({ + id: randomNum, + callbackFn: listnenerFn, + }); + + try { + log.debug("Publishing"); + this.baseClient.publish(this.TEST_CHANNEL, JSON.stringify(payload)); + } catch (err) { + log.error(`Failed to send health test message. Will time out shortly. Problem: ${err.message}`); + resolve(false); + } + log.debug("Done"); + }) } private subscribeToRedisTopics() { this.subscription.on("message", (channel:string, message:string) => { - let deserializedMessage: GenericAdd|GenericUpdate|GenericDeletion; - + let deserializedMessage: GenericAdd|GenericUpdate|GenericDeletion|RedisTestPayload; try { + log.debug(`Got message: ${message} channel: ${channel}`); deserializedMessage = JSON.parse(message); } catch (e) { @@ -76,14 +150,26 @@ export class RedisUpdaterService { if (channel == this.REMOVE_CHANNEL) { this.toNotifyOnRemove.forEach(async (updateFn) => updateFn( deserializedMessage)); } + + if (channel == this.TEST_CHANNEL) { + this.toNotifyOnTest.forEach(async (registration) => registration.callbackFn( deserializedMessage)); + } }); - this.subscription.subscribe(this.ADD_CHANNEL, this.UPDATE_CHANNEL, this.REMOVE_CHANNEL); + this.subscription.subscribe(this.ADD_CHANNEL, this.UPDATE_CHANNEL, this.REMOVE_CHANNEL, this.TEST_CHANNEL); + } + + private removeTestListenerByID(id: number) { + const listenerIdx = this.toNotifyOnTest.findIndex(registration => registration.id === id); + if (listenerIdx === -1) return; + this.toNotifyOnTest.splice(listenerIdx, 1); } private logErrors() { this.subscription.on("error", (err:Error) => { log.warn(`Got an error from the redis connector. Message: ${err.message}`); + // Kill the process so K8s can restart it + process.exit(1); }) } } @@ -94,4 +180,4 @@ export default (config:Configuration) => { } return serviceSingleton; -}; \ No newline at end of file +}; diff --git a/src/services/customTypes/RedisTestTypes.ts b/src/services/customTypes/RedisTestTypes.ts new file mode 100644 index 0000000..e7f017c --- /dev/null +++ b/src/services/customTypes/RedisTestTypes.ts @@ -0,0 +1,5 @@ + +export interface RedisTestPayload { + testValue: number; +} + diff --git a/test/Util.ts b/test/Util.ts new file mode 100644 index 0000000..01e713d --- /dev/null +++ b/test/Util.ts @@ -0,0 +1,48 @@ +import * as Docker from "dockerode"; +import {ContainerCreateOptions} from "dockerode"; +import {Configuration} from "../src/config"; + +interface HostPortBinding { + HostPort: string +} + +interface CreateOptionsWithPortBindings extends ContainerCreateOptions { + PortBindings: { + [key: string]: HostPortBinding[] + } +} + +const redisPort = "6379"; + +export const testConfig: Configuration = { + applicationPort: 3000, + redisUrl: `redis://localhost:${redisPort}`, + environment: "testing", + redisPassword: "testRedis" +}; + +export function createRedisContainer(docker: Docker): Promise { + return docker.createContainer({ + Image: "bitnami/redis:4.0.9", + Env: [ + `REDIS_PASSWORD=${testConfig.redisPassword}` + ], + PortBindings: { + "6379/tcp": [{HostPort: redisPort}] + } + }); +} + +export function pullRedisImage(docker: Docker): Promise { + return new Promise((resolve, reject) => { + docker.pull("bitnami/redis:4.0.9", {}, (err, stream) => { + if (err) { + console.log("Got an error pulling the image."); + reject(err); + return; + } + + docker.modem.followProgress(stream, (err?: Error) => err ? reject(err) : resolve(), () => {}); + }); + }); +} diff --git a/test/services/RedisUpdaterServiceTest.ts b/test/services/RedisUpdaterServiceTest.ts index 4a8f001..e1dfa49 100644 --- a/test/services/RedisUpdaterServiceTest.ts +++ b/test/services/RedisUpdaterServiceTest.ts @@ -1,8 +1,7 @@ import {afterEach, beforeEach, it, suite} from "mocha"; import {expect} from "chai"; import * as Docker from "dockerode"; -import {Container, ContainerCreateOptions} from "dockerode"; -import {Configuration} from "../../src/config"; +import {Container} from "dockerode"; import {RedisUpdaterService} from "../../src/services/RedisUpdaterService"; import {createClient} from "redis"; import { @@ -13,145 +12,163 @@ import { Objective, ObjectiveUpdate } from "common-interfaces/QuestInterfaces"; - -interface HostPortBinding { - HostPort:string -} -interface CreateOptionsWithPortBindings extends ContainerCreateOptions { - PortBindings: { - [key:string]: HostPortBinding[] - } -} +import {createRedisContainer, pullRedisImage, testConfig} from "../Util"; +import sleep = require("sleep-promise"); suite("Redis updater service test", () => { let container:Container; let docker = new Docker(); let containersToDelete:Container[] = []; - const config:Configuration = { - applicationPort: 3000, - redisUrl: "redis://localhost:6379", - environment: "testing", - redisPassword: "testRedis" - }; - const DOCKER_STARTUP_TIME = 5000; + const DOCKER_STARTUP_TIME = 7000; const MESSAGE_AWAIT_TIME = 500; - const DEFAULT_ASYNC_TIMEOUT = 10000; + const DEFAULT_ASYNC_TIMEOUT = 15000; - before(function (done) { - this.timeout(30000); + suite("With Redis", () => { + before(async function() { + this.timeout(30000); - docker.pull("bitnami/redis:4.0.9", {}, (err, stream) => { - if (err) { - console.log("Got an error pulling the image."); - return; - } + await pullRedisImage(docker); + }); - docker.modem.followProgress(stream, () => done(), () => {}); + beforeEach(async function () { + this.timeout(DEFAULT_ASYNC_TIMEOUT); + + container = await createRedisContainer(docker); + + await container.start(); }); - }); - beforeEach(async function () { - this.timeout(DEFAULT_ASYNC_TIMEOUT); - container = await docker.createContainer({ - Image: "bitnami/redis:4.0.9", - Env: [ - "REDIS_PASSWORD=testRedis" - ], - PortBindings: { - "6379/tcp": [{HostPort: "6379"}] + afterEach(async function () { + this.timeout(DEFAULT_ASYNC_TIMEOUT); + if (container) { + await container.stop(); + containersToDelete.push(container); } }); - await container.start(); - }); - - afterEach(async function () { - this.timeout(DEFAULT_ASYNC_TIMEOUT); - if (container) { - await container.stop(); - containersToDelete.push(container); - } - }); + after(async function() { + this.timeout(DEFAULT_ASYNC_TIMEOUT); + let stopJobs = containersToDelete.map((container) => container.remove()); - after(async function() { - this.timeout(DEFAULT_ASYNC_TIMEOUT); - let stopJobs = containersToDelete.map((container) => container.remove()); - - await Promise.all(stopJobs); - }); + await Promise.all(stopJobs); + }); - it("reacts to things published to its channels", (done) => { - setTimeout(() => { - const service = new RedisUpdaterService(config); - let addPosted = false; - let updatePosted = false; - let deletePosted = false; - - const addMessage: GenericAdd = { - type: HierarchyLevel.OBJECTIVE, - newData: {id: "a", text: "text", completed: true} - }; - const updateMessage: GenericUpdate = { - type: HierarchyLevel.OBJECTIVE, - updateDetail: { - questId: "a", - objectiveId: "b", - text: "text" + it("reacts to things published to its channels", (done) => { + setTimeout(() => { + const service = new RedisUpdaterService(testConfig); + let addPosted = false; + let updatePosted = false; + let deletePosted = false; + + const addMessage: GenericAdd = { + type: HierarchyLevel.OBJECTIVE, + newData: {id: "a", text: "text", completed: true} + }; + const updateMessage: GenericUpdate = { + type: HierarchyLevel.OBJECTIVE, + updateDetail: { + questId: "a", + objectiveId: "b", + text: "text" + } + }; + const deleteMessage: GenericDeletion = { + type: HierarchyLevel.QUEST, + id: "a" + }; + + service.toNotifyOnAdd.push(() => addPosted = true); + service.toNotifyOnUpdate.push(() => updatePosted = true); + service.toNotifyOnRemove.push(() => deletePosted = true); + + const redisClient = createClient(testConfig.redisUrl, { + password: testConfig.redisPassword + }); + + redisClient.publish("new-quests", JSON.stringify(addMessage)); + redisClient.publish("quest-updates", JSON.stringify(updateMessage)); + redisClient.publish("removed-quests", JSON.stringify(deleteMessage)); + + setTimeout(() => { + console.log(`AddPosted value: ${addPosted}`); + console.log(`UpdatePosted value: ${updatePosted}`); + console.log(`DeletePosted value: ${deletePosted}`); + expect(addPosted).to.be.true; + expect(updatePosted).to.be.true; + expect(deletePosted).to.be.true; + redisClient.quit(); + service.disconnect(); + done(); + }, MESSAGE_AWAIT_TIME); + }, DOCKER_STARTUP_TIME); // Give the redis container about 5 seconds to spin up + }).timeout(DEFAULT_ASYNC_TIMEOUT); + + it("does not push messages on malformed JSON", (done) => { + setTimeout(() => { + const service = new RedisUpdaterService(testConfig); + let updateReceived = false; + + service.toNotifyOnAdd.push(() => updateReceived = true); + service.toNotifyOnUpdate.push(() => updateReceived = true); + service.toNotifyOnRemove.push(() => updateReceived = true); + + const redisClient = createClient(testConfig.redisUrl, { + password: testConfig.redisPassword + }); + + redisClient.publish("new-quests", "not json", () => { + console.log(`UpdateReceived value: ${updateReceived}`); + expect(updateReceived).to.be.false; + redisClient.quit(); + service.disconnect(); + done(); + }); + }, DOCKER_STARTUP_TIME); + }).timeout(DEFAULT_ASYNC_TIMEOUT); + + it("can verify its connection to redis", (done) => { + setTimeout(async () => { + const service = new RedisUpdaterService(testConfig); + const connectionStatus = await service.sendTestMessage(); + try { + expect(connectionStatus).to.be.true; + done(); + } catch(err) { + done(err); + } finally { + service.disconnect(); } - }; - const deleteMessage: GenericDeletion = { - type: HierarchyLevel.QUEST, - id: "a" - }; - - service.toNotifyOnAdd.push(() => addPosted = true); - service.toNotifyOnUpdate.push(() => updatePosted = true); - service.toNotifyOnRemove.push(() => deletePosted = true); - - const redisClient = createClient(config.redisUrl, { - password: config.redisPassword - }); - - redisClient.publish("new-quests", JSON.stringify(addMessage)); - redisClient.publish("quest-updates", JSON.stringify(updateMessage)); - redisClient.publish("removed-quests", JSON.stringify(deleteMessage)); + }, DOCKER_STARTUP_TIME); + }).timeout(DEFAULT_ASYNC_TIMEOUT); + }); - setTimeout(() => { - console.log(`AddPosted value: ${addPosted}`); - console.log(`UpdatePosted value: ${updatePosted}`); - console.log(`DeletePosted value: ${deletePosted}`); - expect(addPosted).to.be.true; - expect(updatePosted).to.be.true; - expect(deletePosted).to.be.true; - redisClient.quit(); - service.disconnect(); - done(); - }, MESSAGE_AWAIT_TIME); - }, DOCKER_STARTUP_TIME); // Give the redis container about 5 seconds to spin up - }).timeout(DEFAULT_ASYNC_TIMEOUT); - - it("does not push messages on malformed JSON", (done) => { - setTimeout(() => { - const service = new RedisUpdaterService(config); - let updateReceived = false; - - service.toNotifyOnAdd.push(() => updateReceived = true); - service.toNotifyOnUpdate.push(() => updateReceived = true); - service.toNotifyOnRemove.push(() => updateReceived = true); - - const redisClient = createClient(config.redisUrl, { - password: config.redisPassword - }); - - redisClient.publish("new-quests", "not json", () => { - console.log(`UpdateReceived value: ${updateReceived}`); - expect(updateReceived).to.be.false; - redisClient.quit(); + suite("Without Redis", () => { + it("fails connection test without redis", async () => { + // Get container + await pullRedisImage(docker); + // Start container + const redisContainer = await createRedisContainer(docker); + await redisContainer.start(); + // Wait for container to start + await sleep(DOCKER_STARTUP_TIME); + // Start the redis service + const service = new RedisUpdaterService(testConfig); + // Stop & remove the container + await redisContainer.stop(); + await redisContainer.remove(); + await sleep(1000); + + // Verify the test shows we aren't connected to docker + expect(await service.sendTestMessage()).to.be.false; + + // We know disconnect will throw an error because redis is stopped so we'll just swallow the error + try { service.disconnect(); - done(); - }); - }, DOCKER_STARTUP_TIME); - }).timeout(DEFAULT_ASYNC_TIMEOUT); + } catch (err) { + // It's ok! + } + }).timeout(DEFAULT_ASYNC_TIMEOUT * 2); + }); }); diff --git a/yarn-error.log b/yarn-error.log deleted file mode 100644 index 549ae1a..0000000 --- a/yarn-error.log +++ /dev/null @@ -1,851 +0,0 @@ -Arguments: - /usr/bin/node /usr/bin/yarn add --dev @types/node-docker-api - -PATH: - /home/evan/.config/yarn/global/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/usr/lib/jvm/java-8-oracle/bin:/usr/lib/jvm/java-8-oracle/db/bin:/usr/lib/jvm/java-8-oracle/jre/bin - -Yarn version: - 1.7.0 - -Node version: - 8.11.2 - -Platform: - linux x64 - -Trace: - Error: https://registry.yarnpkg.com/@types%2fnode-docker-api: Not found - at Request.params.callback [as _callback] (/usr/lib/node_modules/yarn/lib/cli.js:65656:18) - at Request.self.callback (/usr/lib/node_modules/yarn/lib/cli.js:134675:22) - at emitTwo (events.js:126:13) - at Request.emit (events.js:214:7) - at Request. (/usr/lib/node_modules/yarn/lib/cli.js:135658:10) - at emitOne (events.js:116:13) - at Request.emit (events.js:211:7) - at IncomingMessage. (/usr/lib/node_modules/yarn/lib/cli.js:135578:12) - at Object.onceWrapper (events.js:313:30) - at emitNone (events.js:111:20) - -npm manifest: - { - "name": "aragashion-pushnotifications", - "version": "1.0.0", - "description": "Push notification service for the Aragashion quest tracker", - "main": "src/index.ts", - "repository": "https://github.com/emanguy/aragashion-pushnotifications", - "author": "Evan Rittenhouse ", - "license": "Apache", - "private": true, - "scripts": { - "start": "tsc && node build/index.js $NODE_DEBUG_OPTION", - "build": "tsc", - "test": "mocha -r ts-node/register test/**/*.ts" - }, - "dependencies": { - "@types/dotenv": "^4.0.3", - "@types/express": "^4.11.1", - "@types/morgan": "^1.7.35", - "@types/redis": "^2.8.6", - "@types/uuid": "^3.4.3", - "@types/winston": "^2.3.9", - "body-parser": "^1.18.3", - "dotenv": "^5.0.1", - "express": "^4.16.3", - "morgan": "^1.9.0", - "redis": "^2.8.0", - "sse-channel": "^3.0.1", - "ts-node": "^6.0.5", - "typescript": "^2.8.3", - "uuid": "^3.2.1", - "winston": "^2.4.2" - }, - "devDependencies": { - "@types/chai": "^4.1.3", - "@types/mocha": "^5.2.0", - "chai": "^4.1.2", - "mocha": "^5.2.0" - } - } - -yarn manifest: - No manifest - -Lockfile: - # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. - # yarn lockfile v1 - - - "@types/body-parser@*": - version "1.17.0" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.17.0.tgz#9f5c9d9bd04bb54be32d5eb9fc0d8c974e6cf58c" - dependencies: - "@types/connect" "*" - "@types/node" "*" - - "@types/chai@^4.1.3": - version "4.1.3" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.3.tgz#b8a74352977a23b604c01aa784f5b793443fb7dc" - - "@types/connect@*": - version "3.4.32" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28" - dependencies: - "@types/node" "*" - - "@types/dotenv@^4.0.3": - version "4.0.3" - resolved "https://registry.yarnpkg.com/@types/dotenv/-/dotenv-4.0.3.tgz#ebcfc40da7bc0728b705945b7db48485ec5b4b67" - dependencies: - "@types/node" "*" - - "@types/events@*": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" - - "@types/express-serve-static-core@*": - version "4.11.1" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.11.1.tgz#f6f7212382d59b19d696677bcaa48a37280f5d45" - dependencies: - "@types/events" "*" - "@types/node" "*" - - "@types/express@*", "@types/express@^4.11.1": - version "4.11.1" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.11.1.tgz#f99663b3ab32d04cb11db612ef5dd7933f75465b" - dependencies: - "@types/body-parser" "*" - "@types/express-serve-static-core" "*" - "@types/serve-static" "*" - - "@types/mime@*": - version "2.0.0" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.0.tgz#5a7306e367c539b9f6543499de8dd519fac37a8b" - - "@types/mocha@^5.2.0": - version "5.2.0" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.2.0.tgz#b3c8e69f038835db1a7fdc0b3d879fc50506e29e" - - "@types/morgan@^1.7.35": - version "1.7.35" - resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.7.35.tgz#6358f502931cc2583d7a94248c41518baa688494" - dependencies: - "@types/express" "*" - - "@types/node@*": - version "10.1.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.1.2.tgz#1b928a0baa408fc8ae3ac012cc81375addc147c6" - - "@types/redis@^2.8.6": - version "2.8.6" - resolved "https://registry.yarnpkg.com/@types/redis/-/redis-2.8.6.tgz#3674d07a13ad76bccda4c37dc3909e4e95757e7e" - dependencies: - "@types/events" "*" - "@types/node" "*" - - "@types/serve-static@*": - version "1.13.2" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.2.tgz#f5ac4d7a6420a99a6a45af4719f4dcd8cd907a48" - dependencies: - "@types/express-serve-static-core" "*" - "@types/mime" "*" - - "@types/uuid@^3.4.3": - version "3.4.3" - resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-3.4.3.tgz#121ace265f5569ce40f4f6d0ff78a338c732a754" - dependencies: - "@types/node" "*" - - "@types/winston@^2.3.9": - version "2.3.9" - resolved "https://registry.yarnpkg.com/@types/winston/-/winston-2.3.9.tgz#1ff9f1bb57952fe3d88c1b21c4899ede316a35bc" - dependencies: - "@types/node" "*" - - accepts@~1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" - dependencies: - mime-types "~2.1.18" - negotiator "0.6.1" - - access-control@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/access-control/-/access-control-1.0.0.tgz#aeba282cee77313e85240163d69e35b29e36d626" - dependencies: - millisecond "0.1.x" - setheader "0.0.x" - vary "1.1.x" - - ansi-styles@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" - dependencies: - color-convert "^1.9.0" - - array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - - arrify@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" - - assertion-error@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - - async@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async/-/async-1.0.0.tgz#f8fc04ca3a13784ade9e1641af98578cfbd647a9" - - balanced-match@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - - basic-auth@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.0.tgz#015db3f353e02e56377755f962742e8981e7bbba" - dependencies: - safe-buffer "5.1.1" - - body-parser@1.18.2: - version "1.18.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" - dependencies: - bytes "3.0.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.1" - http-errors "~1.6.2" - iconv-lite "0.4.19" - on-finished "~2.3.0" - qs "6.5.1" - raw-body "2.3.2" - type-is "~1.6.15" - - body-parser@^1.18.3: - version "1.18.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" - dependencies: - bytes "3.0.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "~1.6.3" - iconv-lite "0.4.23" - on-finished "~2.3.0" - qs "6.5.2" - raw-body "2.3.3" - type-is "~1.6.16" - - brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - dependencies: - balanced-match "^1.0.0" - concat-map "0.0.1" - - browser-stdout@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" - - buffer-from@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.0.0.tgz#4cb8832d23612589b0406e9e2956c17f06fdf531" - - bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - - chai@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.1.2.tgz#0f64584ba642f0f2ace2806279f4f06ca23ad73c" - dependencies: - assertion-error "^1.0.1" - check-error "^1.0.1" - deep-eql "^3.0.0" - get-func-name "^2.0.0" - pathval "^1.0.0" - type-detect "^4.0.0" - - chalk@^2.3.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" - dependencies: - ansi-styles "^3.2.1" - escape-string-regexp "^1.0.5" - supports-color "^5.3.0" - - check-error@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - - color-convert@^1.9.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.1.tgz#c1261107aeb2f294ebffec9ed9ecad529a6097ed" - dependencies: - color-name "^1.1.1" - - color-name@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" - - colors@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.0.3.tgz#0433f44d809680fdeb60ed260f1b0c262e82a40b" - - commander@2.15.1: - version "2.15.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" - - concat-map@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - - content-disposition@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - - content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - - cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - - cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - - cycle@1.0.x: - version "1.0.3" - resolved "https://registry.yarnpkg.com/cycle/-/cycle-1.0.3.tgz#21e80b2be8580f98b468f379430662b046c34ad2" - - debug@0.7.x: - version "0.7.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-0.7.4.tgz#06e1ea8082c2cb14e39806e22e2f6f757f92af39" - - debug@2.6.9: - version "2.6.9" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" - dependencies: - ms "2.0.0" - - debug@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - dependencies: - ms "2.0.0" - - deep-eql@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" - dependencies: - type-detect "^4.0.0" - - depd@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" - - depd@~1.1.1, depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - - destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - - diff@3.5.0, diff@^3.1.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - - dotenv@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-5.0.1.tgz#a5317459bd3d79ab88cff6e44057a6a3fbb1fcef" - - double-ended-queue@^2.1.0-0: - version "2.1.0-0" - resolved "https://registry.yarnpkg.com/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz#103d3527fd31528f40188130c841efdd78264e5c" - - ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - - encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - - escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - - escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - - etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - - express@^4.16.3: - version "4.16.3" - resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" - dependencies: - accepts "~1.3.5" - array-flatten "1.1.1" - body-parser "1.18.2" - content-disposition "0.5.2" - content-type "~1.0.4" - cookie "0.3.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.1.1" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.2" - path-to-regexp "0.1.7" - proxy-addr "~2.0.3" - qs "6.5.1" - range-parser "~1.2.0" - safe-buffer "5.1.1" - send "0.16.2" - serve-static "1.13.2" - setprototypeof "1.1.0" - statuses "~1.4.0" - type-is "~1.6.16" - utils-merge "1.0.1" - vary "~1.1.2" - - eyes@0.1.x: - version "0.1.8" - resolved "https://registry.yarnpkg.com/eyes/-/eyes-0.1.8.tgz#62cf120234c683785d902348a800ef3e0cc20bc0" - - finalhandler@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.2" - statuses "~1.4.0" - unpipe "~1.0.0" - - forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - - fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - - fs.realpath@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - - get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - - glob@7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - - growl@1.10.5: - version "1.10.5" - resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" - - has-flag@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" - - he@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" - - http-errors@1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" - dependencies: - depd "1.1.1" - inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" - - http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: - version "1.6.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - - iconv-lite@0.4.19: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" - - iconv-lite@0.4.23: - version "0.4.23" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" - dependencies: - safer-buffer ">= 2.1.2 < 3" - - inflight@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - dependencies: - once "^1.3.0" - wrappy "1" - - inherits@2, inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - - ipaddr.js@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.6.0.tgz#e3fa357b773da619f26e95f049d055c72796f86b" - - isstream@0.1.x: - version "0.1.2" - resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" - - lodash@^4.0.1: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" - - make-error@^1.1.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.4.tgz#19978ed575f9e9545d2ff8c13e33b5d18a67d535" - - media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - - merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - - methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - - millisecond@0.1.x: - version "0.1.2" - resolved "https://registry.yarnpkg.com/millisecond/-/millisecond-0.1.2.tgz#6cc5ad386241cab8e78aff964f87028eec92dac5" - - mime-db@~1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" - - mime-types@~2.1.18: - version "2.1.18" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" - dependencies: - mime-db "~1.33.0" - - mime@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" - - minimatch@3.0.4, minimatch@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" - dependencies: - brace-expansion "^1.1.7" - - minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - - minimist@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - - mkdirp@0.5.1, mkdirp@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" - - mocha@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-5.2.0.tgz#6d8ae508f59167f940f2b5b3c4a612ae50c90ae6" - dependencies: - browser-stdout "1.3.1" - commander "2.15.1" - debug "3.1.0" - diff "3.5.0" - escape-string-regexp "1.0.5" - glob "7.1.2" - growl "1.10.5" - he "1.1.1" - minimatch "3.0.4" - mkdirp "0.5.1" - supports-color "5.4.0" - - morgan@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.9.0.tgz#d01fa6c65859b76fcf31b3cb53a3821a311d8051" - dependencies: - basic-auth "~2.0.0" - debug "2.6.9" - depd "~1.1.1" - on-finished "~2.3.0" - on-headers "~1.0.1" - - ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - - negotiator@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" - - on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - dependencies: - ee-first "1.1.1" - - on-headers@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" - - once@^1.3.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - dependencies: - wrappy "1" - - parseurl@~1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" - - path-is-absolute@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - - path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - - pathval@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - - proxy-addr@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.3.tgz#355f262505a621646b3130a728eb647e22055341" - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.6.0" - - qs@6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - - qs@6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - - range-parser@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - - raw-body@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" - dependencies: - bytes "3.0.0" - http-errors "1.6.2" - iconv-lite "0.4.19" - unpipe "1.0.0" - - raw-body@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" - dependencies: - bytes "3.0.0" - http-errors "1.6.3" - iconv-lite "0.4.23" - unpipe "1.0.0" - - redis-commands@^1.2.0: - version "1.3.5" - resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.3.5.tgz#4495889414f1e886261180b1442e7295602d83a2" - - redis-parser@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-2.6.0.tgz#52ed09dacac108f1a631c07e9b69941e7a19504b" - - redis@^2.8.0: - version "2.8.0" - resolved "https://registry.yarnpkg.com/redis/-/redis-2.8.0.tgz#202288e3f58c49f6079d97af7a10e1303ae14b02" - dependencies: - double-ended-queue "^2.1.0-0" - redis-commands "^1.2.0" - redis-parser "^2.6.0" - - safe-buffer@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - - "safer-buffer@>= 2.1.2 < 3": - version "2.1.2" - resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" - - send@0.16.2: - version "0.16.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.6.2" - mime "1.4.1" - ms "2.0.0" - on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.4.0" - - serve-static@1.13.2: - version "1.13.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.2" - send "0.16.2" - - setheader@0.0.x: - version "0.0.4" - resolved "https://registry.yarnpkg.com/setheader/-/setheader-0.0.4.tgz#926ed28cf762149620931e7aea3f1b95816ec694" - dependencies: - debug "0.7.x" - - setprototypeof@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" - - setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" - - source-map-support@^0.5.3: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13" - dependencies: - buffer-from "^1.0.0" - source-map "^0.6.0" - - source-map@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - - sse-channel@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/sse-channel/-/sse-channel-3.0.1.tgz#4fb139a5ee003dde06c152ba602d3387a2242943" - dependencies: - access-control "1.0.0" - lodash "^4.0.1" - - stack-trace@0.0.x: - version "0.0.10" - resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" - - "statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - - statuses@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" - - supports-color@5.4.0, supports-color@^5.3.0: - version "5.4.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" - dependencies: - has-flag "^3.0.0" - - ts-node@^6.0.5: - version "6.0.5" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-6.0.5.tgz#977c1c931da7a2b09ae2930101f0104a5c2271e9" - dependencies: - arrify "^1.0.0" - chalk "^2.3.0" - diff "^3.1.0" - make-error "^1.1.1" - minimist "^1.2.0" - mkdirp "^0.5.1" - source-map-support "^0.5.3" - yn "^2.0.0" - - type-detect@^4.0.0: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - - type-is@~1.6.15, type-is@~1.6.16: - version "1.6.16" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.18" - - typescript@^2.8.3: - version "2.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.8.3.tgz#5d817f9b6f31bb871835f4edf0089f21abe6c170" - - unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - - utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - - uuid@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" - - vary@1.1.x, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" - - winston@^2.4.2: - version "2.4.2" - resolved "https://registry.yarnpkg.com/winston/-/winston-2.4.2.tgz#3ca01f763116fc48db61053b7544e750431f8db0" - dependencies: - async "~1.0.0" - colors "1.0.x" - cycle "1.0.x" - eyes "0.1.x" - isstream "0.1.x" - stack-trace "0.0.x" - - wrappy@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - - yn@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" diff --git a/yarn.lock b/yarn.lock index 86a59dc..606dbba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -456,6 +456,10 @@ etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" +express-async-handler@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/express-async-handler/-/express-async-handler-1.1.4.tgz#225a84908df63b35ae9df94b6f0f1af061266426" + express@^4.16.3: version "4.16.3" resolved "https://registry.yarnpkg.com/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" @@ -572,6 +576,10 @@ http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-status-codes@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-1.4.0.tgz#6e4c15d16ff3a9e2df03b89f3a55e1aae05fb477" + iconv-lite@0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" @@ -928,6 +936,10 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" +sleep-promise@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/sleep-promise/-/sleep-promise-8.0.1.tgz#8d795a27ea23953df6b52b91081e5e22665993c5" + source-map-support@^0.5.3: version "0.5.6" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.6.tgz#4435cee46b1aab62b8e8610ce60f788091c51c13"