diff --git a/.gitignore b/.gitignore index 4a659d9..bf3c7e1 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,7 @@ Temporary Items ### Node ### # Logs logs +Logs *.log npm-debug.log* yarn-debug.log* diff --git a/Server/API/API.js b/Server/API/API.js index a5ef665..e327dcd 100644 --- a/Server/API/API.js +++ b/Server/API/API.js @@ -1,12 +1,14 @@ const express = require("express"); const router = express.Router(); const bodyParser = require('body-parser'); +const Logger = require('../Utils/Logger'); //Routers const UpdateRouter = require('./Routers/UpdateRouter'); const AccountRouter = require('./Routers/AccountRouter'); const FrameRouter = require('./Routers/FrameRouter'); const UserRouter = require('./Routers/UserRouter'); +const FrameViewerRouter = require('./Routers/FrameViewerRouter'); // parse application/x-www-form-urlencoded @@ -23,5 +25,6 @@ router.use(bodyParser.json()) router.use('/update', UpdateRouter); router.use('/account', AccountRouter); router.use('/frame', FrameRouter); +router.use('/frameviewer', FrameViewerRouter); router.use('/user', UserRouter); module.exports = router; diff --git a/Server/API/Routers/AccountRouter.js b/Server/API/Routers/AccountRouter.js index 369e701..bdc1072 100644 --- a/Server/API/Routers/AccountRouter.js +++ b/Server/API/Routers/AccountRouter.js @@ -1,12 +1,17 @@ const express = require("express"); const DBUtils = require('../../Utils/DBUtil'); const Config = require('../../Config/Config'); +const AuthUtil = require('../../Utils/AuthUtil'); +// Logger +const Logger = require('../../Utils/Logger'); +const TAG = '[AccountRouter]' const router = express.Router(); router.post('/create/', (req, res) => { const body = req.body; + Logger.debug(TAG, 'POST: /create/'); if (!body.username || !body.password) { res.status(400).json({ message: 'username and password are required' @@ -46,7 +51,7 @@ router.post('/create/', (req, res) => { }); //FIXME - Lies!!! } else { res.status(201).json({ - user: user, + user: doc.toObject(), token: account.auth_token }); } @@ -59,26 +64,51 @@ router.post('/create/', (req, res) => { }); router.post('/login/', (req, res) => { + Logger.debug(TAG, 'POST: /login/'); const body = req.body; DBUtils.Models.Account.findOne({ username: body.username }, (err, account) => { - if (err) throw err; - if (account) { + if (err) { + Logger.error(TAG, 'Failed to query DB. ERROR:', err); + res.status(500).json({ + message: err.message + }); + } + else if (account) { // test a matching password account.comparePassword(body.password, account.password, (err, isMatch) => { - if (err) throw err; - if (!isMatch) { + if (err) { + Logger.error(TAG, 'Failed to query DB. ERROR:', err); + res.status(500).json({ + message: err.message + }); + } + else if (!isMatch) { + Logger.warn(TAG, 'Authentication Fail'); res.status(401).json({ message: 'Authentication Fail' }); return; + }else { + AuthUtil.getUserByAccountId(account._id).then(user => { + let responseObj = { + user: user.toObject(), + token: account.auth_token + } + Logger.debug(TAG, 'Authentication success', JSON.stringify(responseObj, null, 2)); + res.json(responseObj); + }) + .catch(reason=>{ + res.status(400).json({ + message: reason + }); + }) + } - res.json({ - token: account.auth_token - }); }); } else { + Logger.warn(TAG, 'Authentication Fail'); res.status(401).json({ message: 'Authentication Fail' }); diff --git a/Server/API/Routers/FrameRouter.js b/Server/API/Routers/FrameRouter.js index 76aa9c6..0ac5dc1 100644 --- a/Server/API/Routers/FrameRouter.js +++ b/Server/API/Routers/FrameRouter.js @@ -2,180 +2,238 @@ const express = require("express"); const DBUtils = require('../../Utils/DBUtil') const AuthUtil = require('../../Utils/AuthUtil') const bodyParser = require('body-parser'); +const FrameLinker = require('../../FrameLinker/FrameLinker') +const uuidv4 = require('uuid/v4'); +// Logger +const Logger = require('../../Utils/Logger'); + +const TAG = '[AccountRouter]' const router = express.Router(); router.use(bodyParser.raw({ - uploadDir: '/tmp/uploads', - keepExtensions: true, - limit: '5mb', - type: 'image/*' - })) + uploadDir: '/tmp/uploads', + keepExtensions: true, + limit: '5mb', + type: 'image/*' +})) -router.get('/:frameId', (req, res) => { // by Frame Id +router.use((req, res, next) => { const token = req.get('token'); - const frameId = req.params.frameId; + Logger.debug(TAG, 'Auth middleware check'); AuthUtil.getAccountByToken(token) - .then((account) => { - if(account.frames.indexOf(frameId) >= 0) { - DBUtils.Models.Frame.findOne({_id: frameId}, (err, doc) => { - if(err) { - res.status(400).json({ - message: err.message - }); - return; - } else if(!doc) { - res.status(400).json({ - message: 'Unable to find a Frame with id: ' + frameId - }); - return; - } - const frame = doc.toObject(); - /// lets get all images ids... - DBUtils.Models.Photo.find({frame_id: frameId}, (err, docs)=>{ - if(err) { - res.status(400).json({ - message: err.message - }); - return; - } - console.log(docs); - frame.photos = docs.map(p => p._id); - res.json(frame); - }) - }); - }else{ - res.status(403).json({ - message: 'Account has no access to frame with id of: ' + frameId - }); - } + .then(account => { + req.account = account + Logger.debug(TAG, 'Auth middleware check - Success'); + next() }) .catch((reason) => { + Logger.debug(TAG, 'Auth middleware check - Fail'); res.status(401).json({ message: reason }); }) +}); + +router.get('/:frameId', (req, res) => { // by Frame Id + Logger.debug(TAG, 'GET: /:frameId'); + const token = req.get('token'); + const frameId = req.params.frameId; + const account = req.account; + if (account.frames.indexOf(frameId) >= 0) { + DBUtils.Models.Frame.findOne({ + _id: frameId + }, (err, doc) => { + if (err) { + res.status(400).json({ + message: err.message + }); + return; + } else if (!doc) { + res.status(400).json({ + message: 'Unable to find a Frame with id: ' + frameId + }); + return; + } + const frame = doc.toObject(); + delete frame.viewerKeys + /// lets get all images ids... + DBUtils.Models.Photo.find({ + frame_id: frameId + }) + .populate('user') + .exec((err, docs) => { + if (err) { + res.status(400).json({ + message: err.message + }); + return; + } + frame.photos = docs.map((p) => { + return { + photo_id: p._id, + user: p.user, + timestamp: p.timestamp + }; + }) + res.json(frame); + }) + }); + } else { + res.status(403).json({ + message: 'Account has no access to frame with id of: ' + frameId + }); + } }) router.post('/create', (req, res) => { + Logger.debug(TAG, 'GET: /create'); const token = req.get('token'); const body = req.body; - if(!body.name) { + if (!body.name) { res.status(400).json({ message: 'Must provide a name for your new frame' }); return; } - AuthUtil.getAccountByToken(token) - .then((account)=>{ - // If account valid - create new frame - const frame = new DBUtils.Models.Frame({ - name: body.name, - admin: account._id, - members: [account._id] - }); - // save frame - frame.save((err, doc)=>{ - if(err) { - res.status(400).json({ - message: err.message - }); - return; - } - // frame created - now add its id to the account object - account.frames.push(doc._id); - account.save((err)=>{ - if( err ) throw err; - res.status(201).json(frame); - }) - }) - }) - .catch((reason)=>{ - res.status(401).json({ - message: reason + const account = req.account; + // If account valid - create new frame + const frame = new DBUtils.Models.Frame({ + name: body.name, + admin: account._id, + members: [account._id] + }); + // save frame + frame.save((err, doc) => { + if (err) { + res.status(400).json({ + message: err.message }); + return; + } + // frame created - now add its id to the account object + account.frames.push(doc._id); + account.save((err) => { + if (err) throw err; + res.status(201).json(frame); }) + }) }); router.post('/:frameId/upload/photo', (req, res) => { + Logger.debug(TAG, 'POST: /:frameId/upload/photo'); const token = req.get('token'); - - AuthUtil.getAccountByToken(token) - .then(account => { - const frameId = req.params.frameId; - if(account.frames.indexOf(frameId) >= 0) { - // User can upload image to the frame - AuthUtil.getUserByAccountId(account._id) - .then((user)=>{ - // Upload Photo... - const photo = new DBUtils.Models.Photo({ - frame_id: frameId, - photo: req.body, - timestamp: Date.now(), - contentType: req.get('Content-Type'), - user:user.id}); - - // Save photo - photo.save((err) => { - if(err) { - res.status(400).json({ - message: err.message - }); - return; - } - res.status(201).json(photo) - }); - }) - .catch(reason => { - res.status(500).json({ - message: 'Unexpected error: ' + reason - }); - }) - } else { - res.status(403).json({ - message: 'Account has no access to frame with id of: ' + frameId - }); - } - }) - .catch(reason => { - res.status(401).json({ - message: reason - }); - }) -}); - -router.get('/:frameId/download/photo/:photoId', (req, res) => { - const token = req.get('token'); - const photoId = req.params.photoId; + const account = req.account; const frameId = req.params.frameId; + if (account.frames.indexOf(frameId) >= 0) { + // User can upload image to the frame + AuthUtil.getUserByAccountId(account._id) + .then((user) => { + // Upload Photo... + const photo = new DBUtils.Models.Photo({ + frame_id: frameId, + photo: req.body, + timestamp: Date.now(), + contentType: req.get('Content-Type'), + user: user._id + }); - AuthUtil.getAccountByToken(token) - .then((account) => { - if(account.frames.indexOf(frameId) >= 0) { - DBUtils.Models.Photo.findOne({_id: photoId}, (err, doc) => { - if(err) { + // Save photo + photo.save((err) => { + if (err) { res.status(400).json({ message: err.message }); return; } - if(doc) res.contentType(doc.contentType).send(doc.photo); - else res.status(400).json({ - message: 'Photo not found' - }); + res.status(201).json(photo) }); - } else { - res.status(403).json({ - message: 'Account has no access to frame with id of: ' + photoId + }) + .catch(reason => { + res.status(500).json({ + message: 'Unexpected error: ' + reason }); - } - }) - .catch(reason => { - res.status(401).json({ - message: reason + }) + } else { + res.status(403).json({ + message: 'Account has no access to frame with id of: ' + frameId + }); + } +}); +router.post('/:frameId/link', (req, res) => { + Logger.debug(TAG, 'POST: /:frameId/link'); + const token = req.get('token'); + const frameId = req.params.frameId; + const account = req.account; + const frameViewerKey = req.body.key; + if(FrameLinker.isKeyValid(frameViewerKey)){ + if(account.frames.indexOf(frameId) == -1){ + res.status(403).json({ + message: 'Account has no access to this frame' }); + return; + } + // lets create an access token; + const accessToken = uuidv4(); + DBUtils.Models.Frame.findOne({_id: frameId}, (err, frame)=>{ + if (err) { + res.status(400).json({ + message: err.message + }); + return; + } + frame.viewerKeys.push(accessToken); + FrameLinker.linkFrame(frameId, accessToken ,frameViewerKey) + .then(()=>{ + frame.save((err)=>{ + if(err){ + res.status(400).json({ + message: 'Something went wrong...' + }); + return; + } + res.status(201).json({message: 'Success'}); + }) + }) + .catch((reason)=>{ + res.status(400).json({message:reason}); + }) }) + + }else{ + res.status(404).json({ + message: 'Unable to find a frame viewer with key: ' + frameViewerKey + }); + } +}); +router.get('/:frameId/download/photo/:photoId', (req, res) => { + Logger.debug(TAG, 'GET: /:frameId/download/photo/:photoId'); + const token = req.get('token'); + const photoId = req.params.photoId; + const frameId = req.params.frameId; + const account = req.account; + + if (account.frames.indexOf(frameId) >= 0) { + DBUtils.Models.Photo.findOne({ + _id: photoId + }, (err, doc) => { + if (err) { + res.status(400).json({ + message: err.message + }); + return; + } + if (doc) res.contentType(doc.contentType).send(doc.photo); + else res.status(400).json({ + message: 'Photo not found' + }); + }); + } else { + res.status(403).json({ + message: 'Account has no access to frame with id of: ' + photoId + }); + } }) diff --git a/Server/API/Routers/FrameViewerRouter.js b/Server/API/Routers/FrameViewerRouter.js new file mode 100644 index 0000000..b91bd59 --- /dev/null +++ b/Server/API/Routers/FrameViewerRouter.js @@ -0,0 +1,119 @@ +const express = require("express"); +const DBUtils = require('../../Utils/DBUtil'); +const Config = require('../../Config/Config'); +const AuthUtil = require('../../Utils/AuthUtil'); +// Logger +const Logger = require('../../Utils/Logger'); + +const TAG = '[FrameViewerRouter]' + +const router = express.Router(); + +router.use((req, res, next) => { + Logger.debug(TAG, 'Auth middleware'); + const token = req.get('token'); + Logger.debug(TAG, `Params ${JSON.stringify(req.params)}`) + const frameId = req.params.frameId; + if (!token) { + res.status(403).json({ + message: 'Invalid access token' + }); + } else { + next(); + } +}); + +router.get('/frame/:frameId', (req, res) => { + Logger.debug(TAG, 'GET: /frame/:frameId'); + const token = req.get('token'); + const frameId = req.params.frameId; + DBUtils.Models.Frame.findOne({ + _id: frameId + }, (err, doc) => { + if (err) { + res.status(400).json({ + message: err.message + }); + return; + } else if (!doc) { + res.status(400).json({ + message: 'Unable to find a Frame with id: ' + frameId + }); + return; + } + if(doc.viewerKeys.indexOf(token) == -1){ + res.status(403).json({ + message: 'Frame Viewer has no access to frame with id of: ' + frameId + }); + return; + } + const frame = doc.toObject(); + /// lets get all images ids... + DBUtils.Models.Photo.find({ + frame_id: frameId + }) + .populate('user') + .exec((err, docs) => { + if (err) { + res.status(400).json({ + message: err.message + }); + return; + } + frame.photos = docs.map((p) => { + return { + photo_id: p._id, + user: p.user, + timestamp: p.timestamp + }; + }) + res.json(frame); + }) + }); +}); + +router.get('/frame/:frameId/photo/:photoId', (req, res) => { + Logger.debug(TAG, 'GET: /frame/:frameId/photo/:photoId'); + const token = req.get('token'); + const photoId = req.params.photoId; + const frameId = req.params.frameId; + DBUtils.Models.Frame.findOne({ + _id: frameId + }, (err, frame) => { + if (err) { + res.status(500).json({ + message: 'DB error' + }) + return; + } + if (frame) { + if (frame.viewerKeys.indexOf(token) != -1) { + req.frame = frame; + DBUtils.Models.Photo.findOne({ + _id: photoId + }, (err, doc) => { + if (err) { + res.status(400).json({ + message: err.message + }); + return; + } + if (doc) res.contentType(doc.contentType).send(doc.photo); + else res.status(400).json({ + message: 'Photo not found' + }); + }); + } else { + res.status(403).json({ + message: 'Frame Viewer has no access to frame with id of: ' + frameId + }); + } + }else{ + res.status(404).json({ + message: 'Unable to find frame with id of ' + frameId + }); + } + }); +}); + +module.exports = router; diff --git a/Server/API/Routers/UserRouter.js b/Server/API/Routers/UserRouter.js index b6bf044..22b980f 100644 --- a/Server/API/Routers/UserRouter.js +++ b/Server/API/Routers/UserRouter.js @@ -1,8 +1,41 @@ const express = require("express"); const DBUtils = require('../../Utils/DBUtil'); const AuthUtil = require('../../Utils/AuthUtil'); +const Logger = require('../../Utils/Logger'); const router = express.Router(); +const TAG = '[UserRouter]' + +router.get('/framez', (req, res) => { + const token = req.get('token'); + Logger.debug(TAG, 'GET: /framez'); + AuthUtil.getAccountByToken(token) + .then(account => { + DBUtils.Models.Frame.find({admin:account._id}, (err, docs) => { + if (err) { + Logger.error(TAG, 'Failed to query DB. ERROR:', err); + res.status(500).json({ + message: err.message + }); + } + else{ + Logger.debug(TAG, 'responding with frames'); + let frames = docs.map(f=>{ + let frame = f.toObject() + delete frame.viewerKeys + return frame + }) + res.json({frames:frames}); + } + }); + }) + .catch(reason => { + Logger.warn(TAG, 'Invalid token'); + res.status(401).json({ + message: reason + }); + }) +}) router.get('/:accountId', (req, res) => { const token = req.get('token'); diff --git a/Server/Config/Config.js b/Server/Config/Config.js index 2b25438..09014ab 100644 --- a/Server/Config/Config.js +++ b/Server/Config/Config.js @@ -7,7 +7,7 @@ const DBAuth = require('../../private/DBAuth'); module.exports = { server: { enableSSL: false, - ssl:{ + ssl: { cert: '', key: '', passphrase: '' @@ -30,6 +30,12 @@ module.exports = { } }, + logging: { + logsPath: 'Logs', + fileLogLevel: 'silly', + consoleLogLevel: 'silly' + }, + // Password hash salt_work_factor: 10 }; diff --git a/Server/FrameLinker/FrameLinker.js b/Server/FrameLinker/FrameLinker.js new file mode 100644 index 0000000..df3263e --- /dev/null +++ b/Server/FrameLinker/FrameLinker.js @@ -0,0 +1,45 @@ +const Logger = require('../Utils/Logger') +const frameViewers = {} + +const TAG = '[FrameLinker]'; +module.exports = { + init: (io) => { + io.sockets.on('connection', (socket) => { + let key = null + Logger.log(TAG, 'New connection from client: ', socket.id) + socket.on('disconnect', () => { + Logger.log(TAG, 'Frame Viewer disconnected disconnected') + if(key){ + delete frameViewers[key]; + } + }); + + socket.on('register_key', (data) => { + if(frameViewers[key]){ + Logger.warn(TAG, 'Key in use - sending register_fail') + socket.emit('register_fail') + return; + } + Logger.debug(TAG, 'Registering key ' + data.key) + key = data.key + frameViewers[key] = socket + Logger.debug(TAG, 'is key', key, 'valid:', frameViewers.hasOwnProperty(key)) + }) + + }); + }, + isKeyValid: (key)=>{ + Logger.debug(TAG, 'is key', key, 'valid:', frameViewers.hasOwnProperty(key)) + if(frameViewers.hasOwnProperty(key)) return true; + return false; + }, + linkFrame: (frameId, accessToken, key)=>{ + return new Promise((resolve, reject) => { + if(!frameViewers[key]) reject('No Frame Viewer Found') + else{ + frameViewers[key].emit('consume_frame', {frame_id: frameId, accessToken: accessToken}) + resolve() + } + }); + } +}; diff --git a/Server/Schemas/Account.js b/Server/Schemas/Account.js index 46840da..32fa1c0 100644 --- a/Server/Schemas/Account.js +++ b/Server/Schemas/Account.js @@ -9,7 +9,7 @@ const Account = new Schema({ username: { type: String, required: true, index: { unique: true } }, password: { type: String, required: true }, auth_token: {type: String, require: false, index: { unique: true }}, - frames: {type: [Schema.ObjectId], default: []} + frames: {type: [Schema.Types.ObjectId], default: [], ref: 'Frame'} }); diff --git a/Server/Schemas/Frame.js b/Server/Schemas/Frame.js index 5a320c7..0ac1f9f 100644 --- a/Server/Schemas/Frame.js +++ b/Server/Schemas/Frame.js @@ -5,9 +5,9 @@ const Schema = mongoose.Schema; const Frame = new Schema({ name: String, icon: {type: String, default: 'https://image.flaticon.com/icons/png/128/847/847930.png'}, - admin: Schema.ObjectId, - members: {type: [Schema.ObjectId], default: []}, - tv_clients: {type: [Schema.ObjectId], default: []}, + admin: {type: Schema.Types.ObjectId, ref: 'Account'}, + members: {type: [Schema.Types.ObjectId], default: [], ref: 'Account'}, + viewerKeys: {type: [String], default: []}, }); module.exports = mongoose.model('Frame', Frame); diff --git a/Server/Schemas/Photo.js b/Server/Schemas/Photo.js index d3d5858..6d91e0a 100644 --- a/Server/Schemas/Photo.js +++ b/Server/Schemas/Photo.js @@ -3,9 +3,9 @@ const mongoose = require('mongoose'); const Schema = mongoose.Schema; const Photo = new Schema({ - frame_id: Schema.ObjectId, + frame_id: {type: Schema.Types.ObjectId, ref: 'Frame'}, contentType: String, - user: Schema.ObjectId, + user: {type: Schema.Types.ObjectId, ref: 'User'}, photo: Buffer, // thumbnail: Buffer, timestamp: Number diff --git a/Server/Schemas/User.js b/Server/Schemas/User.js index 3002307..dfdfe3e 100644 --- a/Server/Schemas/User.js +++ b/Server/Schemas/User.js @@ -3,10 +3,9 @@ const Config = require('../Config/Config') const Schema = mongoose.Schema; const User = new Schema({ - account_id: Schema.ObjectId, + account_id: {type: Schema.Types.ObjectId, ref: 'Account'}, nickname: { type: String, required: true, index: { unique: true } }, - avatar: { type: String, default: Config.defaults.user.avatar }, - auth_token: {type: String, default: null} + avatar: { type: String, default: Config.defaults.user.avatar } }); module.exports = mongoose.model('User', User); diff --git a/Server/Utils/Logger.js b/Server/Utils/Logger.js new file mode 100644 index 0000000..9b3fc5f --- /dev/null +++ b/Server/Utils/Logger.js @@ -0,0 +1,55 @@ +/** + * NPM Modules + */ +const fs = require('fs'); +const path = require('path'); +const winston = require('winston'); +/** + * Internal Modules + */ +const Config = require('../Config/Config'); +/** +* Module Variables +*/ +const tsFormat = () => (new Date()).toISOString().replace(/T/, ' '); +let logger; +/** +* Module Functions +*/ + +let _init = ()=>{ + if (!fs.existsSync(Config.logging.logsPath)) { + fs.mkdirSync(Config.logging.logsPath); + } + + // { error: 0, warn: 1, info: 2, verbose: 3, debug: 4, silly: 5 } + logger = new (winston.Logger)({ + transports: [ + //Console Logging + new (winston.transports.Console)({ + timestamp: tsFormat, + colorize: true, + json: false, + level: Config.logging.consoleLogLevel + }), + // File Logging + new (require('winston-daily-rotate-file'))({ + filename: `${Config.logging.logsPath}/framez-server-%DATE%.log`, + timestamp: tsFormat, + datePattern: 'YYYY-MM-DD', + prepend: true, + json: false, + level: Config.logging.fileLogLevel + }) + ] + }); +}; + + +_init(); + + +/** +* Module exported Interface +*/ +module.exports = logger; diff --git a/framez-server.js b/framez-server.js index 26b982e..17cd093 100644 --- a/framez-server.js +++ b/framez-server.js @@ -3,15 +3,19 @@ const https = require('https'); // // Config const Config = require('./Server/Config/Config'); +// Logger +const Logger = require('./Server/Utils/Logger'); // Routers const APIRouter = require('./Server/API/API'); +// Soket.io +const io = require('socket.io'); +// TV linker listener +const TVListener = require('./Server/FrameLinker/FrameLinker'); // App const app = express() - - -app.use('/api', APIRouter); +const TAG = '[FramezServer]' if(Config.server.enableSSL){ // HTTPS server @@ -21,6 +25,13 @@ if(Config.server.enableSSL){ passphrase: fs.readFileSync(Config.server.ssl.passphrase) }, app); } -app.listen(Config.server.port,function(){ - console.log(`Running an ${(Config.server.enableSSL) ? 'HTTPS' : 'HTTP'}, and listening on port ${Config.server.port}`); +const server = app.listen(Config.server.port,function(){ + Logger.info(TAG, `Running an ${(Config.server.enableSSL) ? 'HTTPS' : 'HTTP'}, and listening on port ${Config.server.port}`); }); + +TVListener.init(io(server)); + + + + +app.use('/api', APIRouter); diff --git a/package.json b/package.json new file mode 100644 index 0000000..b613a24 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "framez-back", + "version": "1.0.0", + "description": "", + "main": "framez-server.js", + "dependencies": { + "bcrypt": "^2.0.1", + "body-parser": "^1.18.2", + "express": "^4.16.3", + "mongoose": "^5.0.16", + "socket.io": "^2.1.0", + "uuid": "^3.2.1", + "winston": "^2.4.2", + "winston-daily-rotate-file": "^3.1.3" + }, + "devDependencies": {}, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +}