updated format and lint scripts and applied them
This commit is contained in:
@@ -1,34 +1,34 @@
|
||||
// Application hooks that run for every service
|
||||
const logger = require('./hooks/logger');
|
||||
const logger = require('./hooks/logger')
|
||||
|
||||
module.exports = {
|
||||
before: {
|
||||
all: [ logger() ],
|
||||
all: [logger()],
|
||||
find: [],
|
||||
get: [],
|
||||
create: [],
|
||||
update: [],
|
||||
patch: [],
|
||||
remove: []
|
||||
remove: [],
|
||||
},
|
||||
|
||||
after: {
|
||||
all: [ logger() ],
|
||||
all: [logger()],
|
||||
find: [],
|
||||
get: [],
|
||||
create: [],
|
||||
update: [],
|
||||
patch: [],
|
||||
remove: []
|
||||
remove: [],
|
||||
},
|
||||
|
||||
error: {
|
||||
all: [ logger() ],
|
||||
all: [logger()],
|
||||
find: [],
|
||||
get: [],
|
||||
create: [],
|
||||
update: [],
|
||||
patch: [],
|
||||
remove: []
|
||||
}
|
||||
};
|
||||
remove: [],
|
||||
},
|
||||
}
|
||||
|
||||
195
src/app.js
195
src/app.js
@@ -1,131 +1,126 @@
|
||||
const favicon = require('serve-favicon');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const compress = require('compression');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const cookieParser = require('cookie-parser')();
|
||||
const RateLimit = require('express-rate-limit');
|
||||
const trustIPs = require('./trustIPs');
|
||||
const logger = require('winston');
|
||||
const favicon = require('serve-favicon')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const compress = require('compression')
|
||||
const cors = require('cors')
|
||||
const helmet = require('helmet')
|
||||
const cookieParser = require('cookie-parser')()
|
||||
const RateLimit = require('express-rate-limit')
|
||||
const trustIPs = require('./trustIPs')
|
||||
const logger = require('winston')
|
||||
|
||||
const feathers = require('@feathersjs/feathers');
|
||||
const express = require('@feathersjs/express');
|
||||
const configuration = require('@feathersjs/configuration');
|
||||
const hostConfig = require('../config/host.json');
|
||||
const feathers = require('@feathersjs/feathers')
|
||||
const express = require('@feathersjs/express')
|
||||
const configuration = require('@feathersjs/configuration')
|
||||
const hostConfig = require('../config/host.json')
|
||||
|
||||
const middleware = require('./middleware');
|
||||
const services = require('./services');
|
||||
const appHooks = require('./app.hooks');
|
||||
const channels = require('./channels');
|
||||
const authentication = require('./authentication');
|
||||
const middleware = require('./middleware')
|
||||
const services = require('./services')
|
||||
const appHooks = require('./app.hooks')
|
||||
const channels = require('./channels')
|
||||
const authentication = require('./authentication')
|
||||
|
||||
const dev = process.env.NODE_ENV !== 'production';
|
||||
const basePath = require('../util/basePath');
|
||||
const stripBase = require('../util/stripBase');
|
||||
const getUrl = require('../util/getUrl');
|
||||
const { parse } = require('url');
|
||||
const nxt = require('next')({ dev, quiet: true });
|
||||
const nxtHandler = nxt.getRequestHandler();
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
const basePath = require('../util/basePath')
|
||||
const stripBase = require('../util/stripBase')
|
||||
const getUrl = require('../util/getUrl')
|
||||
const { parse } = require('url')
|
||||
const nxt = require('next')({ dev, quiet: true })
|
||||
const nxtHandler = nxt.getRequestHandler()
|
||||
|
||||
const app = express(feathers());
|
||||
const app = express(feathers())
|
||||
|
||||
app.run = async port => {
|
||||
const server = app.listen(port);
|
||||
await nxt.prepare();
|
||||
const server = app.listen(port)
|
||||
await nxt.prepare()
|
||||
|
||||
if(dev) {
|
||||
if (dev) {
|
||||
server.on('upgrade', (req, socket) => {
|
||||
nxtHandler(req, socket, parse(stripBase(req.url), true));
|
||||
});
|
||||
nxtHandler(req, socket, parse(stripBase(req.url), true))
|
||||
})
|
||||
}
|
||||
return server;
|
||||
};
|
||||
return server
|
||||
}
|
||||
|
||||
// Load app configuration
|
||||
app.configure(configuration());
|
||||
app.configure(configuration())
|
||||
|
||||
// load host config
|
||||
Object.keys(hostConfig).forEach(key => (
|
||||
app.set(key, hostConfig[key])
|
||||
));
|
||||
app.set('didSetup', false);
|
||||
Object.keys(hostConfig).forEach(key => app.set(key, hostConfig[key]))
|
||||
app.set('didSetup', false)
|
||||
|
||||
try {
|
||||
fs.statSync(path.join(__dirname, '..', 'db', '.didSetup'));
|
||||
app.set('didSetup', true);
|
||||
}
|
||||
catch(err) {
|
||||
fs.statSync(path.join(__dirname, '..', 'db', '.didSetup'))
|
||||
app.set('didSetup', true)
|
||||
} catch (err) {
|
||||
app.use((req, res, next) => {
|
||||
req.doSetup = !app.get('didSetup');
|
||||
next();
|
||||
});
|
||||
}
|
||||
req.doSetup = !app.get('didSetup')
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
const authLimit = new RateLimit({
|
||||
windowMs: 5 * 60 * 1000, // 5 minutes
|
||||
max: 5, // 5 attempts then block
|
||||
delayAfter: 3, // slow down after 3 fails
|
||||
delayMs: 2 * 1000
|
||||
});
|
||||
app.authLimit = authLimit;
|
||||
app.use(getUrl('auth'), authLimit);
|
||||
app.patch(getUrl('users/*'), authLimit);
|
||||
delayMs: 2 * 1000,
|
||||
})
|
||||
app.authLimit = authLimit
|
||||
app.use(getUrl('auth'), authLimit)
|
||||
app.patch(getUrl('users/*'), authLimit)
|
||||
|
||||
// Enable CORS, security, compression, favicon and body parsing
|
||||
trustIPs(app);
|
||||
app.use(cors());
|
||||
app.use(helmet({
|
||||
hidePoweredBy: { setTo: 'hamsters' }
|
||||
}));
|
||||
trustIPs(app)
|
||||
app.use(cors())
|
||||
app.use(
|
||||
helmet({
|
||||
hidePoweredBy: { setTo: 'hamsters' },
|
||||
})
|
||||
)
|
||||
|
||||
if(!dev) app.use(compress());
|
||||
app.use(express.json()); // use { limit } option to increase max post size
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
app.use(getUrl('/'), favicon('favicon.ico'));
|
||||
app.configure(express.rest()); // Set up Plugins and providers
|
||||
app.configure(middleware); // middleware/index.js
|
||||
app.configure(authentication); // Set up authentication
|
||||
app.configure(services); // Set up our services (see `services/index.js`)
|
||||
app.configure(channels); // Set up event channels (see channels.js)
|
||||
if (!dev) app.use(compress())
|
||||
app.use(express.json()) // use { limit } option to increase max post size
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
app.use(getUrl('/'), favicon('favicon.ico'))
|
||||
app.configure(express.rest()) // Set up Plugins and providers
|
||||
app.configure(middleware) // middleware/index.js
|
||||
app.configure(authentication) // Set up authentication
|
||||
app.configure(services) // Set up our services (see `services/index.js`)
|
||||
app.configure(channels) // Set up event channels (see channels.js)
|
||||
|
||||
const checkJWT = async (req, res, next) => {
|
||||
const result = await req.app.authenticate('jwt', {})(req);
|
||||
if(result.success) {
|
||||
req.jwt = req.cookies.jwt;
|
||||
delete result.data.user.password;
|
||||
req.user = result.data.user;
|
||||
const result = await req.app.authenticate('jwt', {})(req)
|
||||
if (result.success) {
|
||||
req.jwt = req.cookies.jwt
|
||||
delete result.data.user.password
|
||||
req.user = result.data.user
|
||||
}
|
||||
next();
|
||||
};
|
||||
nxt.setAssetPrefix(basePath); // setup next.js routes
|
||||
next()
|
||||
}
|
||||
nxt.setAssetPrefix(basePath) // setup next.js routes
|
||||
;['/', '/logout', '/new', '/settings'].forEach(route => {
|
||||
app.get(getUrl(route), cookieParser, checkJWT, (req, res) => {
|
||||
const { query } = parse(req.url, true)
|
||||
nxt.render(req, res, route, query)
|
||||
})
|
||||
})
|
||||
;['/k', '/edit'].forEach(route => {
|
||||
app.get(getUrl(route + '/:id'), cookieParser, checkJWT, (req, res) => {
|
||||
nxt.render(req, res, route, { id: req.params.id })
|
||||
})
|
||||
})
|
||||
|
||||
[ '/', '/logout', '/new', '/settings' ]
|
||||
.forEach(route => {
|
||||
app.get(getUrl(route), cookieParser, checkJWT, (req, res) => {
|
||||
const { query } = parse(req.url, true);
|
||||
nxt.render(req, res, route, query);
|
||||
});
|
||||
});
|
||||
|
||||
[ '/k', '/edit' ]
|
||||
.forEach(route => {
|
||||
app.get(getUrl(route + '/:id'), cookieParser, checkJWT, (req, res) => {
|
||||
nxt.render(req, res, route, { id: req.params.id });
|
||||
});
|
||||
});
|
||||
|
||||
const notFound = express.notFound();
|
||||
const notFound = express.notFound()
|
||||
app.use((req, res, next) => {
|
||||
let accept = req.get('accept');
|
||||
if(accept && accept.toLowerCase() === 'application/json')
|
||||
return notFound(req, res, next);
|
||||
if(req.url.substr(0, basePath.length) !== basePath)
|
||||
return nxt.render404(req, res);
|
||||
nxtHandler(req, res, parse(stripBase(req.url), true));
|
||||
});
|
||||
let accept = req.get('accept')
|
||||
if (accept && accept.toLowerCase() === 'application/json')
|
||||
return notFound(req, res, next)
|
||||
if (req.url.substr(0, basePath.length) !== basePath)
|
||||
return nxt.render404(req, res)
|
||||
nxtHandler(req, res, parse(stripBase(req.url), true))
|
||||
})
|
||||
|
||||
app.use(express.errorHandler({ logger }));
|
||||
app.hooks(appHooks);
|
||||
app.use(express.errorHandler({ logger }))
|
||||
app.hooks(appHooks)
|
||||
|
||||
module.exports = app;
|
||||
module.exports = app
|
||||
|
||||
@@ -1,26 +1,28 @@
|
||||
const authentication = require('@feathersjs/authentication');
|
||||
const jwt = require('@feathersjs/authentication-jwt');
|
||||
const local = require('@feathersjs/authentication-local');
|
||||
const getUrl = require('../util/getUrl');
|
||||
const authentication = require('@feathersjs/authentication')
|
||||
const jwt = require('@feathersjs/authentication-jwt')
|
||||
const local = require('@feathersjs/authentication-local')
|
||||
const getUrl = require('../util/getUrl')
|
||||
|
||||
module.exports = function (app) {
|
||||
const config = app.get('authentication');
|
||||
config.path = getUrl(config.path);
|
||||
config.service = getUrl('users');
|
||||
module.exports = function(app) {
|
||||
const config = app.get('authentication')
|
||||
config.path = getUrl(config.path)
|
||||
config.service = getUrl('users')
|
||||
|
||||
// Set up authentication with the secret
|
||||
app.configure(authentication(
|
||||
Object.assign({}, config, {
|
||||
cookie: {
|
||||
enabled: true,
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
name: 'jwt',
|
||||
}
|
||||
})
|
||||
));
|
||||
app.configure(jwt());
|
||||
app.configure(local());
|
||||
app.configure(
|
||||
authentication(
|
||||
Object.assign({}, config, {
|
||||
cookie: {
|
||||
enabled: true,
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
name: 'jwt',
|
||||
},
|
||||
})
|
||||
)
|
||||
)
|
||||
app.configure(jwt())
|
||||
app.configure(local())
|
||||
|
||||
// The `authentication` service is used to create a JWT.
|
||||
// The before `create` hook registers strategies that can be used
|
||||
@@ -30,13 +32,11 @@ module.exports = function (app) {
|
||||
create: [
|
||||
authentication.hooks.authenticate(config.strategies),
|
||||
ctx => {
|
||||
ctx.app.authLimit.resetKey(ctx.params.ip);
|
||||
return ctx;
|
||||
}
|
||||
ctx.app.authLimit.resetKey(ctx.params.ip)
|
||||
return ctx
|
||||
},
|
||||
],
|
||||
remove: [
|
||||
authentication.hooks.authenticate('jwt')
|
||||
]
|
||||
}
|
||||
});
|
||||
};
|
||||
remove: [authentication.hooks.authenticate('jwt')],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,56 +1,59 @@
|
||||
module.exports = function(app) {
|
||||
if(typeof app.channel !== 'function') {
|
||||
if (typeof app.channel !== 'function') {
|
||||
// If no real-time functionality has been configured just return
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
app.on('connection', connection => {
|
||||
// On a new real-time connection, add it to the anonymous channel
|
||||
app.channel('anonymous').join(connection);
|
||||
});
|
||||
app.channel('anonymous').join(connection)
|
||||
})
|
||||
|
||||
app.on('login', (authResult, { connection }) => {
|
||||
// connection can be undefined if there is no
|
||||
// real-time connection, e.g. when logging in via REST
|
||||
if(connection) {
|
||||
if (connection) {
|
||||
// Obtain the logged in user from the connection
|
||||
// const user = connection.user;
|
||||
|
||||
|
||||
// The connection is no longer anonymous, remove it
|
||||
app.channel('anonymous').leave(connection);
|
||||
app.channel('anonymous').leave(connection)
|
||||
|
||||
// Add it to the authenticated user channel
|
||||
app.channel('authenticated').join(connection);
|
||||
app.channel('authenticated').join(connection)
|
||||
|
||||
// Channels can be named anything and joined on any condition
|
||||
|
||||
// Channels can be named anything and joined on any condition
|
||||
|
||||
// E.g. to send real-time events only to admins use
|
||||
// if(user.isAdmin) { app.channel('admins').join(connection); }
|
||||
|
||||
// If the user has joined e.g. chat rooms
|
||||
// if(Array.isArray(user.rooms)) user.rooms.forEach(room => app.channel(`rooms/${room.id}`).join(channel));
|
||||
|
||||
|
||||
// Easily organize users by email and userid for things like messaging
|
||||
// app.channel(`emails/${user.email}`).join(channel);
|
||||
// app.channel(`userIds/$(user.id}`).join(channel);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
app.publish((data, hook) => {
|
||||
// Here you can add event publishers to channels set up in `channels.js`
|
||||
// To publish only for a specific event use `app.publish(eventname, () => {})`
|
||||
|
||||
console.log('Publishing all events to all authenticated users. See `channels.js` and https://docs.feathersjs.com/api/channels.html for more information.'); // eslint-disable-line
|
||||
// eslint-disable-next-line
|
||||
console.log(
|
||||
'Publishing all events to all authenticated users. See `channels.js` and https://docs.feathersjs.com/api/channels.html for more information.'
|
||||
)
|
||||
|
||||
// e.g. to publish all service events to all authenticated users use
|
||||
return app.channel('authenticated');
|
||||
});
|
||||
return app.channel('authenticated')
|
||||
})
|
||||
|
||||
// Here you can also add service specific event publishers
|
||||
// e..g the publish the `users` service `created` event to the `admins` channel
|
||||
// app.service('users').publish('created', () => app.channel('admins'));
|
||||
|
||||
|
||||
// With the userid and email organization from above you can easily select involved users
|
||||
// app.service('messages').publish(() => {
|
||||
// return [
|
||||
@@ -58,4 +61,4 @@ module.exports = function(app) {
|
||||
// app.channel(`emails/${data.recipientEmail}`)
|
||||
// ];
|
||||
// });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
// A hook that logs service method before, after and error
|
||||
// See https://github.com/winstonjs/winston for documentation
|
||||
// about the logger.
|
||||
const logger = require('winston');
|
||||
const logger = require('winston')
|
||||
|
||||
// To see more detailed messages, uncomment the following line
|
||||
// logger.level = 'debug';
|
||||
|
||||
module.exports = function () {
|
||||
module.exports = function() {
|
||||
return context => {
|
||||
// This debugs the service call and a stringified version of the hook context
|
||||
// You can customize the mssage (and logger) to your needs
|
||||
logger.debug(`${context.type} app.service('${context.path}').${context.method}()`);
|
||||
|
||||
if(typeof context.toJSON === 'function') {
|
||||
logger.debug('Hook Context', JSON.stringify(context, null, ' '));
|
||||
logger.debug(
|
||||
`${context.type} app.service('${context.path}').${context.method}()`
|
||||
)
|
||||
|
||||
if (typeof context.toJSON === 'function') {
|
||||
logger.debug('Hook Context', JSON.stringify(context, null, ' '))
|
||||
}
|
||||
|
||||
|
||||
if (context.error) {
|
||||
logger.error(context.error);
|
||||
logger.error(context.error)
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const logger = require('winston');
|
||||
const app = require('./app');
|
||||
const port = app.get('port');
|
||||
const logger = require('winston')
|
||||
const app = require('./app')
|
||||
const port = app.get('port')
|
||||
|
||||
app.run(port).then(() => {
|
||||
logger.info('MYKB listening at http://%s:%d', app.get('host'), port)
|
||||
@@ -8,4 +8,4 @@ app.run(port).then(() => {
|
||||
|
||||
process.on('unhandledRejection', (reason, p) =>
|
||||
logger.error('Unhandled Rejection at: Promise ', p, reason)
|
||||
);
|
||||
)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = function (app) {
|
||||
module.exports = function(app) {
|
||||
// Add your custom middleware here. Remember, that
|
||||
// in Express the order matters
|
||||
|
||||
|
||||
// add req.ip to feathers
|
||||
app.use((req, res, next) => {
|
||||
req.feathers.ip = req.ip;
|
||||
next();
|
||||
});
|
||||
};
|
||||
req.feathers.ip = req.ip
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
const NeDB = require('nedb');
|
||||
const path = require('path');
|
||||
const NeDB = require('nedb')
|
||||
const path = require('path')
|
||||
|
||||
module.exports = function (app) {
|
||||
const dbPath = app.get('nedb');
|
||||
module.exports = function(app) {
|
||||
const dbPath = app.get('nedb')
|
||||
const Model = new NeDB({
|
||||
filename: path.join(dbPath, 'users.db'),
|
||||
autoload: true
|
||||
});
|
||||
autoload: true,
|
||||
})
|
||||
|
||||
Model.ensureIndex({ fieldName: 'email', unique: true });
|
||||
|
||||
return Model;
|
||||
};
|
||||
Model.ensureIndex({ fieldName: 'email', unique: true })
|
||||
|
||||
return Model
|
||||
}
|
||||
|
||||
@@ -1,225 +1,233 @@
|
||||
const errors = require('@feathersjs/errors');
|
||||
const gitP = require('simple-git/promise');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const loadDocs = require('./loadDocs');
|
||||
const comparableFields = [ 'name', 'dir' ];
|
||||
let git;
|
||||
const errors = require('@feathersjs/errors')
|
||||
const gitP = require('simple-git/promise')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const loadDocs = require('./loadDocs')
|
||||
const comparableFields = ['name', 'dir']
|
||||
let git
|
||||
|
||||
class Service {
|
||||
constructor (options) {
|
||||
this.options = options || {};
|
||||
this.docsDir = this.options.docsDir;
|
||||
this.$limit = this.options.paginate.default;
|
||||
this.useGit = this.options.useGit;
|
||||
this.maxDocSize = 1024 * 100; // 100 kb (don't try sending if bigger)
|
||||
this.docs = {};
|
||||
this.updTimeouts = {};
|
||||
loadDocs.bind(this)();
|
||||
constructor(options) {
|
||||
this.options = options || {}
|
||||
this.docsDir = this.options.docsDir
|
||||
this.$limit = this.options.paginate.default
|
||||
this.useGit = this.options.useGit
|
||||
this.maxDocSize = 1024 * 100 // 100 kb (don't try reading if bigger)
|
||||
this.docs = {}
|
||||
this.updTimeouts = {}
|
||||
loadDocs.bind(this)()
|
||||
|
||||
if(this.useGit) {
|
||||
git = gitP(this.docsDir);
|
||||
if (this.useGit) {
|
||||
git = gitP(this.docsDir)
|
||||
fs.stat(path.join(this.docsDir, '.git'), async err => {
|
||||
if(err && err.code === 'ENOENT') {
|
||||
if (err && err.code === 'ENOENT') {
|
||||
git.init().then(() => {
|
||||
if(this.numInitDocs === 0) return;
|
||||
git.add('./*').then(() => git.commit('initial commit'));
|
||||
});
|
||||
if (this.numInitDocs === 0) return
|
||||
git.add('./*').then(() => git.commit('initial commit'))
|
||||
})
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async getMd (id) {
|
||||
const doc = this.docs[id];
|
||||
if(doc.md) return doc.md;
|
||||
const docPath = path.join(this.docsDir, doc.dir, doc.name);
|
||||
const { size } = await fs.stat(docPath);
|
||||
if(size > this.maxDocSize) return 'Document is too big to display...';
|
||||
const buff = await fs.readFile(docPath);
|
||||
return buff.toString();
|
||||
async getMd(id) {
|
||||
const doc = this.docs[id]
|
||||
if (doc.md) return doc.md
|
||||
const docPath = path.join(this.docsDir, doc.dir, doc.name)
|
||||
const { size } = await fs.stat(docPath)
|
||||
if (size > this.maxDocSize) return 'Document is too big to display...'
|
||||
const buff = await fs.readFile(docPath)
|
||||
return buff.toString()
|
||||
}
|
||||
|
||||
async isMatch (id, $search) {
|
||||
const doc = this.docs[id];
|
||||
const name = doc.name.toLowerCase();
|
||||
async isMatch(id, $search) {
|
||||
const doc = this.docs[id]
|
||||
const name = doc.name.toLowerCase()
|
||||
|
||||
if(name.indexOf($search) > -1) return true;
|
||||
const dir = doc.dir.toLowerCase();
|
||||
|
||||
if(dir.indexOf($search) > -1) return true;
|
||||
const relPath = dir + (dir.length > 0 ? '/' : '') + name;
|
||||
|
||||
if(relPath.toLowerCase().indexOf($search) > -1) return true;
|
||||
let md = await this.getMd(id);
|
||||
md = md.toLowerCase();
|
||||
if (name.indexOf($search) > -1) return true
|
||||
const dir = doc.dir.toLowerCase()
|
||||
|
||||
if(md.indexOf($search) > -1) return true;
|
||||
return false;
|
||||
if (dir.indexOf($search) > -1) return true
|
||||
const relPath = dir + (dir.length > 0 ? '/' : '') + name
|
||||
|
||||
if (relPath.toLowerCase().indexOf($search) > -1) return true
|
||||
let md = await this.getMd(id)
|
||||
md = md.toLowerCase()
|
||||
|
||||
if (md.indexOf($search) > -1) return true
|
||||
return false
|
||||
}
|
||||
|
||||
async checkRmDir (dir) {
|
||||
if(dir === '') return;
|
||||
const parts = dir.split('/');
|
||||
const n = parts.length;
|
||||
for(let i = 0; i < n; i++) {
|
||||
const dir = parts.filter((p, pIdx) => pIdx < (n - i)).join('/');
|
||||
const docsInDir = await this.find({ query: { dir } });
|
||||
if(docsInDir.total === 0) {
|
||||
async checkRmDir(dir) {
|
||||
if (dir === '') return
|
||||
const parts = dir.split('/')
|
||||
const n = parts.length
|
||||
for (let i = 0; i < n; i++) {
|
||||
const dir = parts.filter((p, pIdx) => pIdx < n - i).join('/')
|
||||
const docsInDir = await this.find({ query: { dir } })
|
||||
if (docsInDir.total === 0) {
|
||||
try {
|
||||
await fs.rmdir(path.join(this.docsDir, dir));
|
||||
} catch(err) { return; }
|
||||
} else return;
|
||||
await fs.rmdir(path.join(this.docsDir, dir))
|
||||
} catch (err) {
|
||||
return
|
||||
}
|
||||
} else return
|
||||
}
|
||||
}
|
||||
|
||||
async find (params) {
|
||||
const { query } = params;
|
||||
let { $limit, $skip, $search, $select, $sort } = query;
|
||||
if($skip < 0) $skip = 0;
|
||||
$limit = $limit || this.$limit;
|
||||
if($search) {
|
||||
$search = $search.toLowerCase().trim();
|
||||
async find(params) {
|
||||
const { query } = params
|
||||
let { $limit, $skip, $search, $select, $sort } = query
|
||||
if ($skip < 0) $skip = 0
|
||||
$limit = $limit || this.$limit
|
||||
if ($search) {
|
||||
$search = $search.toLowerCase().trim()
|
||||
}
|
||||
let ids = Object.keys(this.docs);
|
||||
const data = [];
|
||||
const toComp = comparableFields.filter(f => typeof query[f] !== 'undefined');
|
||||
if(toComp.length > 0) {
|
||||
let ids = Object.keys(this.docs)
|
||||
const data = []
|
||||
const toComp = comparableFields.filter(f => typeof query[f] !== 'undefined')
|
||||
if (toComp.length > 0) {
|
||||
ids = ids.filter(id => {
|
||||
const doc = this.docs[id];
|
||||
return !toComp.some(f => doc[f] !== query[f]);
|
||||
});
|
||||
const doc = this.docs[id]
|
||||
return !toComp.some(f => doc[f] !== query[f])
|
||||
})
|
||||
}
|
||||
if($search) {
|
||||
if ($search) {
|
||||
ids = await Promise.all(
|
||||
ids.map(async id => await this.isMatch(id, $search) ? id : null)
|
||||
);
|
||||
ids = ids.filter(Boolean);
|
||||
ids.map(async id => ((await this.isMatch(id, $search)) ? id : null))
|
||||
)
|
||||
ids = ids.filter(Boolean)
|
||||
}
|
||||
if(ids.length === 0) return { total: 0, data };
|
||||
if($sort) {
|
||||
const sortKey = Object.keys($sort).pop();
|
||||
const ascDesc = $sort[sortKey];
|
||||
if(this.docs[ids[0]][sortKey] || sortKey === 'dirName') { // make sure valid key
|
||||
if (ids.length === 0) return { total: 0, data }
|
||||
if ($sort) {
|
||||
const sortKey = Object.keys($sort).pop()
|
||||
const ascDesc = $sort[sortKey]
|
||||
|
||||
if (this.docs[ids[0]][sortKey] || sortKey === 'dirName') {
|
||||
ids.sort((a, b) => {
|
||||
let val1, val2, parseVals;
|
||||
const docA = this.docs[a],
|
||||
docB = this.docs[b];
|
||||
if(sortKey === 'dirName') {
|
||||
parseVals = doc => path.join(doc.dir, doc.name);
|
||||
a = this.docs[a]
|
||||
b = this.docs[b]
|
||||
let parseVal
|
||||
if (sortKey === 'dirName') {
|
||||
parseVal = doc => path.join(doc.dir, doc.name)
|
||||
} else {
|
||||
parseVal = doc => doc[sortKey]
|
||||
}
|
||||
else {
|
||||
parseVals = doc => doc[sortKey];
|
||||
}
|
||||
[val1, val2] = [docA, docB].map(parseVals);
|
||||
let c = 0;
|
||||
if(val1 < val2) c = -1;
|
||||
else if(val1 > val2) c = 1;
|
||||
return c * ascDesc;
|
||||
});
|
||||
a = parseVal(a)
|
||||
b = parseVal(b)
|
||||
let c = 0
|
||||
if (a < b) c = -1
|
||||
else if (a > b) c = 1
|
||||
return c * ascDesc
|
||||
})
|
||||
}
|
||||
}
|
||||
for(let i = $skip || 0; i < ids.length && data.length < $limit; i++) {
|
||||
const id = ids[i];
|
||||
let doc = $select && !$select.md ? this.docs[id] : this.get(id);
|
||||
if($select) {
|
||||
let _doc = {};
|
||||
$select.forEach(k => _doc[k] = doc[k]);
|
||||
doc = _doc;
|
||||
for (let i = $skip || 0; i < ids.length && data.length < $limit; i++) {
|
||||
const id = ids[i]
|
||||
let doc = $select && !$select.md ? this.docs[id] : this.get(id)
|
||||
if ($select) {
|
||||
let _doc = {}
|
||||
$select.forEach(k => (_doc[k] = doc[k]))
|
||||
doc = _doc
|
||||
}
|
||||
data.push(doc);
|
||||
data.push(doc)
|
||||
}
|
||||
return { total: ids.length, data };
|
||||
return { total: ids.length, data }
|
||||
}
|
||||
|
||||
async get (id, params) { // eslint-disable-line no-unused-vars
|
||||
const doc = this.docs[id];
|
||||
if(!doc) throw new errors.NotFound(`No record found for id '${id}'`);
|
||||
if(!doc.md) doc.md = await this.getMd(id);
|
||||
return this.docs[id];
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async get(id, params) {
|
||||
const doc = this.docs[id]
|
||||
if (!doc) throw new errors.NotFound(`No record found for id '${id}'`)
|
||||
if (!doc.md) doc.md = await this.getMd(id)
|
||||
return this.docs[id]
|
||||
}
|
||||
|
||||
async create (data, params) { // eslint-disable-line no-unused-vars
|
||||
if(Array.isArray(data)) {
|
||||
return await Promise.all(data.map(current => this.create(current)));
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async create(data, params) {
|
||||
if (Array.isArray(data)) {
|
||||
return await Promise.all(data.map(current => this.create(current)))
|
||||
}
|
||||
const { name, dir, md } = data;
|
||||
const { name, dir, md } = data
|
||||
try {
|
||||
const rPath = path.join(dir, name);
|
||||
const docPath = path.join(this.docsDir, rPath);
|
||||
await fs.outputFile(docPath, md);
|
||||
|
||||
if(this.useGit) {
|
||||
git.add(rPath).then(() => git.commit(`added doc ${rPath}`));
|
||||
const rPath = path.join(dir, name)
|
||||
const docPath = path.join(this.docsDir, rPath)
|
||||
await fs.outputFile(docPath, md)
|
||||
|
||||
if (this.useGit) {
|
||||
git.add(rPath).then(() => git.commit(`added doc ${rPath}`))
|
||||
}
|
||||
const ts = new Date().toJSON();
|
||||
return this.setDoc(path.join(dir, name), ts, md, ts);
|
||||
}
|
||||
catch (err) {
|
||||
throw new errors.GeneralError('could not create doc');
|
||||
const ts = new Date().toJSON()
|
||||
return this.setDoc(path.join(dir, name), ts, md, ts)
|
||||
} catch (err) {
|
||||
throw new errors.GeneralError('could not create doc')
|
||||
}
|
||||
}
|
||||
|
||||
async update (id, data, params) { // eslint-disable-line no-unused-vars
|
||||
throw new errors.MethodNotAllowed('can not update on docs service');
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async update(id, data, params) {
|
||||
throw new errors.MethodNotAllowed('can not update on docs service')
|
||||
}
|
||||
|
||||
async patch (id, data, params) { // eslint-disable-line no-unused-vars
|
||||
const doc = this.docs[id];
|
||||
if(!doc) throw new errors.NotFound(`No record found for id '${id}'`);
|
||||
let { name, dir, md } = data;
|
||||
let diffDir = Boolean(doc.dir !== dir);
|
||||
if(!name) name = doc.name;
|
||||
if(typeof dir !== 'string') {
|
||||
dir = doc.dir;
|
||||
diffDir = false;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async patch(id, data, params) {
|
||||
const doc = this.docs[id]
|
||||
if (!doc) throw new errors.NotFound(`No record found for id '${id}'`)
|
||||
let { name, dir, md } = data
|
||||
let diffDir = Boolean(doc.dir !== dir)
|
||||
if (!name) name = doc.name
|
||||
if (typeof dir !== 'string') {
|
||||
dir = doc.dir
|
||||
diffDir = false
|
||||
}
|
||||
const rPath = path.join(dir, name);
|
||||
const docPath = path.join(this.docsDir, rPath);
|
||||
if(name !== doc.name || diffDir) {
|
||||
const oldRPath = path.join(doc.dir, doc.name);
|
||||
const oldPath = path.join(this.docsDir, oldRPath);
|
||||
await fs.ensureDir(path.join(this.docsDir, dir));
|
||||
await fs.move(oldPath, docPath);
|
||||
|
||||
if(this.useGit) {
|
||||
git.rm(oldRPath)
|
||||
const rPath = path.join(dir, name)
|
||||
const docPath = path.join(this.docsDir, rPath)
|
||||
if (name !== doc.name || diffDir) {
|
||||
const oldRPath = path.join(doc.dir, doc.name)
|
||||
const oldPath = path.join(this.docsDir, oldRPath)
|
||||
await fs.ensureDir(path.join(this.docsDir, dir))
|
||||
await fs.move(oldPath, docPath)
|
||||
|
||||
if (this.useGit) {
|
||||
git
|
||||
.rm(oldRPath)
|
||||
.then(() => git.add(rPath))
|
||||
.then(() => git.commit(`renamed doc ${oldRPath} ${rPath}`));
|
||||
.then(() => git.commit(`renamed doc ${oldRPath} ${rPath}`))
|
||||
}
|
||||
id = this.getId(path.join(dir, name));
|
||||
this.docs[id] = Object.assign({}, doc, { id, name, dir });
|
||||
delete this.docs[doc.id];
|
||||
if(diffDir) this.checkRmDir(doc.dir);
|
||||
id = this.getId(path.join(dir, name))
|
||||
this.docs[id] = Object.assign({}, doc, { id, name, dir })
|
||||
delete this.docs[doc.id]
|
||||
if (diffDir) this.checkRmDir(doc.dir)
|
||||
}
|
||||
if(md) {
|
||||
await fs.writeFile(docPath, md);
|
||||
if(this.useGit) {
|
||||
git.add(rPath).then(() => git.commit(`updated doc ${rPath}`));
|
||||
if (md) {
|
||||
await fs.writeFile(docPath, md)
|
||||
if (this.useGit) {
|
||||
git.add(rPath).then(() => git.commit(`updated doc ${rPath}`))
|
||||
}
|
||||
this.docs[id].md = md;
|
||||
this.docs[id].md = md
|
||||
}
|
||||
return this.docs[id];
|
||||
return this.docs[id]
|
||||
}
|
||||
|
||||
async remove (id, params) { // eslint-disable-line no-unused-vars
|
||||
const doc = this.docs[id];
|
||||
if(!id) throw new errors.NotFound(`No record found for id '${id}'`);
|
||||
const rPath = path.join(doc.dir, doc.name);
|
||||
const docPath = path.join(this.docsDir, rPath);
|
||||
await fs.unlink(docPath);
|
||||
|
||||
if(this.useGit) {
|
||||
git.rm(rPath).then(() => git.commit(`removed doc ${rPath}`));
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async remove(id, params) {
|
||||
const doc = this.docs[id]
|
||||
if (!id) throw new errors.NotFound(`No record found for id '${id}'`)
|
||||
const rPath = path.join(doc.dir, doc.name)
|
||||
const docPath = path.join(this.docsDir, rPath)
|
||||
await fs.unlink(docPath)
|
||||
|
||||
if (this.useGit) {
|
||||
git.rm(rPath).then(() => git.commit(`removed doc ${rPath}`))
|
||||
}
|
||||
delete this.docs[id];
|
||||
this.checkRmDir(doc.dir);
|
||||
return doc;
|
||||
delete this.docs[id]
|
||||
this.checkRmDir(doc.dir)
|
||||
return doc
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function (options) {
|
||||
return new Service(options);
|
||||
};
|
||||
module.exports = function(options) {
|
||||
return new Service(options)
|
||||
}
|
||||
|
||||
module.exports.Service = Service;
|
||||
module.exports.Service = Service
|
||||
|
||||
@@ -1,72 +1,81 @@
|
||||
const { authenticate } = require('@feathersjs/authentication').hooks;
|
||||
const { checkDir, checkName } = require('../../../util/checkDirParts');
|
||||
const { disable, invalid, adminOnly } = require('../hooksUtil');
|
||||
const getUrl = require('../../../util/getUrl');
|
||||
const { authenticate } = require('@feathersjs/authentication').hooks
|
||||
const { checkDir, checkName } = require('../../../util/checkDirParts')
|
||||
const { disable, invalid, adminOnly } = require('../hooksUtil')
|
||||
const getUrl = require('../../../util/getUrl')
|
||||
const nameIsValid = name => {
|
||||
name = checkName(name);
|
||||
if(!name) return invalid('name');
|
||||
if(name.substr(name.length - 3).toLowerCase() !== '.md') {
|
||||
name += '.md';
|
||||
name = checkName(name)
|
||||
if (!name) return invalid('name')
|
||||
if (name.substr(name.length - 3).toLowerCase() !== '.md') {
|
||||
name += '.md'
|
||||
}
|
||||
return name;
|
||||
};
|
||||
return name
|
||||
}
|
||||
const dirIsValid = dir => {
|
||||
dir = checkDir(dir);
|
||||
if(!dir && dir !== 0) return invalid('dir');
|
||||
else if(dir === 0) return '';
|
||||
return dir;
|
||||
};
|
||||
dir = checkDir(dir)
|
||||
if (!dir && dir !== 0) return invalid('dir')
|
||||
else if (dir === 0) return ''
|
||||
return dir
|
||||
}
|
||||
const mdIsValid = md => {
|
||||
if(typeof md !== 'string' || md.trim().length === 0) {
|
||||
return invalid('md');
|
||||
if (typeof md !== 'string' || md.trim().length === 0) {
|
||||
return invalid('md')
|
||||
}
|
||||
return md;
|
||||
};
|
||||
return md
|
||||
}
|
||||
const pathTaken = async (name, dir, app) => {
|
||||
const matches = await app.service(getUrl('docs')).find({ query: { name, dir } });
|
||||
if(matches.total > 0) {
|
||||
return invalid(null, 'filename is taken');
|
||||
const matches = await app
|
||||
.service(getUrl('docs'))
|
||||
.find({ query: { name, dir } })
|
||||
if (matches.total > 0) {
|
||||
return invalid(null, 'filename is taken')
|
||||
}
|
||||
};
|
||||
}
|
||||
module.exports = {
|
||||
before: {
|
||||
all: [ authenticate('jwt') ],
|
||||
all: [authenticate('jwt')],
|
||||
find: [],
|
||||
get: [],
|
||||
create: [ async ctx => {
|
||||
const { app, data } = ctx;
|
||||
let { name, dir, md } = data;
|
||||
const k = {};
|
||||
k.name = nameIsValid(name);
|
||||
k.dir = dirIsValid(dir);
|
||||
k.md = mdIsValid(md);
|
||||
await pathTaken(k.name, k.dir, app);
|
||||
ctx.data = k;
|
||||
return ctx;
|
||||
}],
|
||||
update: [ disable ],
|
||||
patch: [ async ctx => {
|
||||
const { data, app } = ctx;
|
||||
const { name, dir, md } = data;
|
||||
const k = {};
|
||||
if(name) k.name = nameIsValid(name);
|
||||
if(typeof dir === 'string') k.dir = dirIsValid(dir); // allow empty string
|
||||
if(name || typeof dir === 'string') {
|
||||
let checkName, checkDir;
|
||||
if(!name || typeof dir !== 'string') {
|
||||
const doc = await app.service(getUrl('docs')).get(ctx.id);
|
||||
if(!name) checkName = doc.name;
|
||||
if(typeof dir !== 'string') checkDir = doc.dir;
|
||||
create: [
|
||||
async ctx => {
|
||||
const { app, data } = ctx
|
||||
let { name, dir, md } = data
|
||||
const k = {}
|
||||
k.name = nameIsValid(name)
|
||||
k.dir = dirIsValid(dir)
|
||||
k.md = mdIsValid(md)
|
||||
await pathTaken(k.name, k.dir, app)
|
||||
ctx.data = k
|
||||
return ctx
|
||||
},
|
||||
],
|
||||
update: [disable],
|
||||
patch: [
|
||||
async ctx => {
|
||||
const { data, app } = ctx
|
||||
const { name, dir, md } = data
|
||||
const k = {}
|
||||
if (name) k.name = nameIsValid(name)
|
||||
if (typeof dir === 'string') k.dir = dirIsValid(dir) // allow empty string
|
||||
if (name || typeof dir === 'string') {
|
||||
let checkName, checkDir
|
||||
if (!name || typeof dir !== 'string') {
|
||||
const doc = await app.service(getUrl('docs')).get(ctx.id)
|
||||
if (!name) checkName = doc.name
|
||||
if (typeof dir !== 'string') checkDir = doc.dir
|
||||
}
|
||||
await pathTaken(
|
||||
k.name || checkName,
|
||||
typeof k.dir === 'string' ? k.dir : checkDir,
|
||||
app
|
||||
)
|
||||
}
|
||||
await pathTaken(k.name || checkName,
|
||||
typeof k.dir === 'string' ? k.dir : checkDir, app);
|
||||
}
|
||||
if(md) k.md = mdIsValid(md);
|
||||
if(Object.keys(k).length === 0) invalid(null, 'nothing to update');
|
||||
ctx.data = k;
|
||||
return ctx;
|
||||
}],
|
||||
remove: [ adminOnly ]
|
||||
if (md) k.md = mdIsValid(md)
|
||||
if (Object.keys(k).length === 0) invalid(null, 'nothing to update')
|
||||
ctx.data = k
|
||||
return ctx
|
||||
},
|
||||
],
|
||||
remove: [adminOnly],
|
||||
},
|
||||
|
||||
after: {
|
||||
@@ -76,7 +85,7 @@ module.exports = {
|
||||
create: [],
|
||||
update: [],
|
||||
patch: [],
|
||||
remove: []
|
||||
remove: [],
|
||||
},
|
||||
|
||||
error: {
|
||||
@@ -86,6 +95,6 @@ module.exports = {
|
||||
create: [],
|
||||
update: [],
|
||||
patch: [],
|
||||
remove: []
|
||||
}
|
||||
};
|
||||
remove: [],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
// Initializes the `docs` service on path `/docs`
|
||||
const createService = require('./docs.class.js');
|
||||
const hooks = require('./docs.hooks');
|
||||
const getUrl = require('../../../util/getUrl');
|
||||
const createService = require('./docs.class.js')
|
||||
const hooks = require('./docs.hooks')
|
||||
const getUrl = require('../../../util/getUrl')
|
||||
|
||||
module.exports = function (app) {
|
||||
|
||||
const paginate = app.get('paginate');
|
||||
const docsDir = app.get('docsDir');
|
||||
const cacheSize = app.get('cacheSize');
|
||||
const useGit = app.get('useGit');
|
||||
module.exports = function(app) {
|
||||
const paginate = app.get('paginate')
|
||||
const docsDir = app.get('docsDir')
|
||||
const cacheSize = app.get('cacheSize')
|
||||
const useGit = app.get('useGit')
|
||||
|
||||
const options = {
|
||||
name: 'docs',
|
||||
paginate,
|
||||
docsDir,
|
||||
cacheSize,
|
||||
useGit
|
||||
};
|
||||
const url = getUrl('docs');
|
||||
useGit,
|
||||
}
|
||||
const url = getUrl('docs')
|
||||
|
||||
// Initialize our service with any options it requires
|
||||
app.use(url, createService(options));
|
||||
app.use(url, createService(options))
|
||||
|
||||
// Get our initialized service so that we can register hooks and filters
|
||||
const service = app.service(url);
|
||||
const service = app.service(url)
|
||||
|
||||
service.hooks(hooks);
|
||||
};
|
||||
service.hooks(hooks)
|
||||
}
|
||||
|
||||
@@ -1,64 +1,78 @@
|
||||
const chokidar = require('chokidar');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const glob = require('glob');
|
||||
const crypto = require('crypto');
|
||||
const chokidar = require('chokidar')
|
||||
const fs = require('fs-extra')
|
||||
const path = require('path')
|
||||
const glob = require('glob')
|
||||
const crypto = require('crypto')
|
||||
|
||||
async function loadDocs() {
|
||||
const { docsDir, cacheSize } = this.options;
|
||||
this.numInitDocs = glob.sync(path.join(docsDir, '**/*.md')).length;
|
||||
this.cached = 0;
|
||||
this.loaded = false;
|
||||
const { docsDir, cacheSize } = this.options
|
||||
this.numInitDocs = glob.sync(path.join(docsDir, '**/*.md')).length
|
||||
this.cached = 0
|
||||
this.loaded = false
|
||||
|
||||
const getId = relPath =>
|
||||
crypto
|
||||
.createHash('sha1')
|
||||
.update(relPath)
|
||||
.digest()
|
||||
.toString('base64')
|
||||
.substr(0, 16)
|
||||
.split('/')
|
||||
.join('_')
|
||||
this.getId = getId
|
||||
|
||||
const getId = relPath => (crypto.createHash('sha1')
|
||||
.update(relPath).digest().toString('base64')
|
||||
.substr(0, 16).split('/').join('_')
|
||||
);
|
||||
this.getId = getId;
|
||||
|
||||
const setDoc = (relPath, created, md, updated) => {
|
||||
const id = getId(relPath);
|
||||
let dir = relPath.split('/');
|
||||
const name = dir.pop();
|
||||
dir = dir.join('/');
|
||||
const id = getId(relPath)
|
||||
let dir = relPath.split('/')
|
||||
const name = dir.pop()
|
||||
dir = dir.join('/')
|
||||
const doc = {
|
||||
id, name, dir, created, md, updated
|
||||
};
|
||||
this.docs[id] = doc;
|
||||
return doc;
|
||||
};
|
||||
this.setDoc = setDoc;
|
||||
id,
|
||||
name,
|
||||
dir,
|
||||
created,
|
||||
md,
|
||||
updated,
|
||||
}
|
||||
this.docs[id] = doc
|
||||
return doc
|
||||
}
|
||||
this.setDoc = setDoc
|
||||
|
||||
const watcher = chokidar.watch(path.join(docsDir, '/**/*.md'), { persistent: true });
|
||||
const watcher = chokidar.watch(path.join(docsDir, '/**/*.md'), {
|
||||
persistent: true,
|
||||
})
|
||||
const relPath = path => {
|
||||
path = path.split(docsDir).pop();
|
||||
if(path.substr(0, 1) === '/') path = path.substr(1);
|
||||
return path;
|
||||
};
|
||||
path = path.split(docsDir).pop()
|
||||
if (path.substr(0, 1) === '/') path = path.substr(1)
|
||||
return path
|
||||
}
|
||||
const handleDoc = async (path, stats) => {
|
||||
const rPath = relPath(path);
|
||||
if(!stats) stats = await fs.stat(path);
|
||||
else { // stats was set so it's a change event
|
||||
const changedDoc = this.docs[getId(rPath)];
|
||||
if(changedDoc && changedDoc.md) this.cached -= changedDoc.md.length;
|
||||
const rPath = relPath(path)
|
||||
if (!stats) stats = await fs.stat(path)
|
||||
else {
|
||||
// stats was set so it's a change event
|
||||
const changedDoc = this.docs[getId(rPath)]
|
||||
if (changedDoc && changedDoc.md) this.cached -= changedDoc.md.length
|
||||
}
|
||||
const { birthtime, size, mtime } = stats;
|
||||
let md = null;
|
||||
if(size < this.maxDocSize && (this.cached + size) < cacheSize) {
|
||||
md = await fs.readFile(path);
|
||||
md = md.toString();
|
||||
this.cached += md.length;
|
||||
const { birthtime, size, mtime } = stats
|
||||
let md = null
|
||||
if (size < this.maxDocSize && this.cached + size < cacheSize) {
|
||||
md = await fs.readFile(path)
|
||||
md = md.toString()
|
||||
this.cached += md.length
|
||||
}
|
||||
setDoc(rPath, birthtime, md, mtime);
|
||||
};
|
||||
watcher.on('add', handleDoc); // file added
|
||||
watcher.on('change', handleDoc); // file changed (rename triggers unlink then add)
|
||||
watcher.on('unlink', path => { // file removed
|
||||
const id = getId(relPath(path));
|
||||
if(this.docs[id] && this.docs[id].md) {
|
||||
this.cached -= this.docs[id].md.length;
|
||||
setDoc(rPath, birthtime, md, mtime)
|
||||
}
|
||||
watcher.on('add', handleDoc) // file added
|
||||
watcher.on('change', handleDoc) // file changed (rename triggers unlink then add)
|
||||
watcher.on('unlink', path => {
|
||||
// file removed
|
||||
const id = getId(relPath(path))
|
||||
if (this.docs[id] && this.docs[id].md) {
|
||||
this.cached -= this.docs[id].md.length
|
||||
}
|
||||
delete this.docs[id];
|
||||
});
|
||||
delete this.docs[id]
|
||||
})
|
||||
}
|
||||
module.exports = loadDocs;
|
||||
module.exports = loadDocs
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
const { BadRequest, Forbidden } = require('@feathersjs/errors');
|
||||
const { BadRequest, Forbidden } = require('@feathersjs/errors')
|
||||
|
||||
const isAdmin = ctx => {
|
||||
const { params } = ctx;
|
||||
return Boolean(params.user && params.user.admin);
|
||||
};
|
||||
const { params } = ctx
|
||||
return Boolean(params.user && params.user.admin)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
disable: () => {
|
||||
throw new BadRequest('method not allowed');
|
||||
throw new BadRequest('method not allowed')
|
||||
},
|
||||
|
||||
invalid: (field, msg) => {
|
||||
throw new BadRequest(msg || `invalid ${field} value`);
|
||||
throw new BadRequest(msg || `invalid ${field} value`)
|
||||
},
|
||||
|
||||
isAdmin: isAdmin,
|
||||
|
||||
adminOnly: ctx => {
|
||||
if(!isAdmin(ctx)) throw new Forbidden('invalid permission');
|
||||
return ctx;
|
||||
}
|
||||
};
|
||||
if (!isAdmin(ctx)) throw new Forbidden('invalid permission')
|
||||
return ctx
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const users = require('./users/users.service.js');
|
||||
const docs = require('./docs/docs.service.js');
|
||||
module.exports = function (app) {
|
||||
app.configure(users);
|
||||
app.configure(docs);
|
||||
};
|
||||
const users = require('./users/users.service.js')
|
||||
const docs = require('./docs/docs.service.js')
|
||||
module.exports = function(app) {
|
||||
app.configure(users)
|
||||
app.configure(docs)
|
||||
}
|
||||
|
||||
@@ -1,71 +1,76 @@
|
||||
const { authenticate } = require('@feathersjs/authentication').hooks;
|
||||
const { Forbidden } = require('@feathersjs/errors');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { disable, invalid, isAdmin, adminOnly } = require('../hooksUtil');
|
||||
const {
|
||||
hashPassword, protect
|
||||
} = require('@feathersjs/authentication-local').hooks;
|
||||
const { authenticate } = require('@feathersjs/authentication').hooks
|
||||
const { Forbidden } = require('@feathersjs/errors')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const { disable, invalid, isAdmin, adminOnly } = require('../hooksUtil')
|
||||
const {
|
||||
hashPassword,
|
||||
protect,
|
||||
} = require('@feathersjs/authentication-local').hooks
|
||||
|
||||
const invalidStr = str => Boolean(
|
||||
typeof str !== 'string' || str.trim().length === 0);
|
||||
const invalidStr = str =>
|
||||
Boolean(typeof str !== 'string' || str.trim().length === 0)
|
||||
|
||||
module.exports = {
|
||||
before: {
|
||||
all: [],
|
||||
find: [ authenticate('jwt') ],
|
||||
get: [ authenticate('jwt') ],
|
||||
create: [
|
||||
find: [authenticate('jwt')],
|
||||
get: [authenticate('jwt')],
|
||||
create: [
|
||||
async ctx => {
|
||||
const { data, app } = ctx;
|
||||
if(app.get('didSetup') && !isAdmin(ctx)) {
|
||||
throw new Forbidden('invalid permission');
|
||||
const { data, app } = ctx
|
||||
if (app.get('didSetup') && !isAdmin(ctx)) {
|
||||
throw new Forbidden('invalid permission')
|
||||
}
|
||||
const { email, password, admin } = data;
|
||||
if(invalidStr(email)) invalid('email');
|
||||
if(invalidStr(password)) invalid('password');
|
||||
if(typeof admin !== 'boolean') invalid('admin');
|
||||
|
||||
ctx.data = { email, password, admin };
|
||||
return ctx;
|
||||
const { email, password, admin } = data
|
||||
if (invalidStr(email)) invalid('email')
|
||||
if (invalidStr(password)) invalid('password')
|
||||
if (typeof admin !== 'boolean') invalid('admin')
|
||||
|
||||
ctx.data = { email, password, admin }
|
||||
return ctx
|
||||
},
|
||||
hashPassword()
|
||||
hashPassword(),
|
||||
],
|
||||
update: [ disable ],
|
||||
patch: [ authenticate('local'),
|
||||
update: [disable],
|
||||
patch: [
|
||||
authenticate('local'),
|
||||
async ctx => {
|
||||
const { newPassword } = ctx.data;
|
||||
if(invalidStr(newPassword)) invalid('newPassword');
|
||||
ctx.data = { password: newPassword };
|
||||
await hashPassword()(ctx);
|
||||
ctx.app.authLimit.resetKey(ctx.params.ip);
|
||||
return ctx;
|
||||
}
|
||||
const { newPassword } = ctx.data
|
||||
if (invalidStr(newPassword)) invalid('newPassword')
|
||||
ctx.data = { password: newPassword }
|
||||
await hashPassword()(ctx)
|
||||
ctx.app.authLimit.resetKey(ctx.params.ip)
|
||||
return ctx
|
||||
},
|
||||
],
|
||||
remove: [ authenticate('jwt'), adminOnly ]
|
||||
remove: [authenticate('jwt'), adminOnly],
|
||||
},
|
||||
|
||||
after: {
|
||||
all: [
|
||||
all: [
|
||||
// Make sure the password field is never sent to the client
|
||||
// Always must be the last hook
|
||||
protect('password')
|
||||
protect('password'),
|
||||
],
|
||||
find: [],
|
||||
get: [],
|
||||
create: [
|
||||
async ctx => {
|
||||
const { app } = ctx;
|
||||
if(app.get('didSetup')) return ctx;
|
||||
app.set('didSetup', true);
|
||||
fs.writeFileSync( // create empty file so we cant stat it
|
||||
path.join(__dirname, '..', '..', '..', 'db', '.didSetup'), '');
|
||||
return ctx;
|
||||
}
|
||||
const { app } = ctx
|
||||
if (app.get('didSetup')) return ctx
|
||||
app.set('didSetup', true)
|
||||
fs.writeFileSync(
|
||||
// create empty file so we cant stat it
|
||||
path.join(__dirname, '..', '..', '..', 'db', '.didSetup'),
|
||||
''
|
||||
)
|
||||
return ctx
|
||||
},
|
||||
],
|
||||
update: [],
|
||||
patch: [],
|
||||
remove: []
|
||||
remove: [],
|
||||
},
|
||||
|
||||
error: {
|
||||
@@ -75,6 +80,6 @@ module.exports = {
|
||||
create: [],
|
||||
update: [],
|
||||
patch: [],
|
||||
remove: []
|
||||
}
|
||||
};
|
||||
remove: [],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
// Initializes the `users` service on path `/users`
|
||||
const createService = require('feathers-nedb');
|
||||
const createModel = require('../../models/users.model');
|
||||
const hooks = require('./users.hooks');
|
||||
const getUrl = require('../../../util/getUrl');
|
||||
const createService = require('feathers-nedb')
|
||||
const createModel = require('../../models/users.model')
|
||||
const hooks = require('./users.hooks')
|
||||
const getUrl = require('../../../util/getUrl')
|
||||
|
||||
module.exports = function (app) {
|
||||
const Model = createModel(app);
|
||||
const paginate = app.get('paginate');
|
||||
module.exports = function(app) {
|
||||
const Model = createModel(app)
|
||||
const paginate = app.get('paginate')
|
||||
|
||||
const options = {
|
||||
name: 'users',
|
||||
Model,
|
||||
paginate
|
||||
};
|
||||
const url = getUrl('users');
|
||||
paginate,
|
||||
}
|
||||
const url = getUrl('users')
|
||||
|
||||
// Initialize our service with any options it requires
|
||||
app.use(url, createService(options));
|
||||
app.use(url, createService(options))
|
||||
|
||||
// Get our initialized service so that we can register hooks and filters
|
||||
const service = app.service(url);
|
||||
const service = app.service(url)
|
||||
|
||||
service.hooks(hooks);
|
||||
};
|
||||
service.hooks(hooks)
|
||||
}
|
||||
|
||||
@@ -1,50 +1,49 @@
|
||||
/* eslint-disable no-console */
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const fetch = require('isomorphic-unfetch');
|
||||
const ips = require('../config/trustIPs.json');
|
||||
ips.push('loopback');
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const fetch = require('isomorphic-unfetch')
|
||||
const ips = require('../config/trustIPs.json')
|
||||
ips.push('loopback')
|
||||
|
||||
const cfv4 = 'https://www.cloudflare.com/ips-v4';
|
||||
const cfv6 = 'https://www.cloudflare.com/ips-v6';
|
||||
const cfConf = path.join(__dirname, '../config/cfIPs.json');
|
||||
const refreshInterval = 24 * 60 * 60 * 1000;
|
||||
const cfv4 = 'https://www.cloudflare.com/ips-v4'
|
||||
const cfv6 = 'https://www.cloudflare.com/ips-v6'
|
||||
const cfConf = path.join(__dirname, '../config/cfIPs.json')
|
||||
const refreshInterval = 24 * 60 * 60 * 1000
|
||||
|
||||
const getIps = str => {
|
||||
str = str.split('\n').map(ip => ip.trim());
|
||||
str = str.filter(ip => ip.length !== 0);
|
||||
return str;
|
||||
};
|
||||
str = str.split('\n').map(ip => ip.trim())
|
||||
str = str.filter(ip => ip.length !== 0)
|
||||
return str
|
||||
}
|
||||
|
||||
const getCfIps = async app => {
|
||||
const cfIps = [];
|
||||
let res = await fetch(cfv4);
|
||||
if(res.ok) {
|
||||
cfIps.push(...getIps(await res.text()));
|
||||
}
|
||||
res = await fetch(cfv6);
|
||||
if(res.ok) {
|
||||
cfIps.push(...getIps(await res.text()));
|
||||
const cfIps = []
|
||||
let res = await fetch(cfv4)
|
||||
if (res.ok) {
|
||||
cfIps.push(...getIps(await res.text()))
|
||||
}
|
||||
res = await fetch(cfv6)
|
||||
if (res.ok) {
|
||||
cfIps.push(...getIps(await res.text()))
|
||||
}
|
||||
fs.writeFile(cfConf, JSON.stringify(cfIps, null, 2), err => {
|
||||
if(err) console.error(err);
|
||||
});
|
||||
app.set('trust proxy', [...ips, ...cfIps]);
|
||||
};
|
||||
if (err) console.error(err)
|
||||
})
|
||||
app.set('trust proxy', [...ips, ...cfIps])
|
||||
}
|
||||
|
||||
module.exports = app => {
|
||||
if(!app.get('trustCloudflare')) {
|
||||
return app.set('trust proxy', ips);
|
||||
if (!app.get('trustCloudflare')) {
|
||||
return app.set('trust proxy', ips)
|
||||
}
|
||||
fs.readFile(cfConf, async (err, buff) => {
|
||||
if(err) {
|
||||
if(err.code === 'ENOENT') getCfIps(app);
|
||||
else return console.error(err);
|
||||
if (err) {
|
||||
if (err.code === 'ENOENT') getCfIps(app)
|
||||
else return console.error(err)
|
||||
} else {
|
||||
const cfIps = JSON.parse(buff.toString())
|
||||
app.set('trust proxy', [...ips, ...cfIps])
|
||||
}
|
||||
else {
|
||||
const cfIps = JSON.parse(buff.toString());
|
||||
app.set('trust proxy', [...ips, ...cfIps]);
|
||||
}
|
||||
setInterval(() => getCfIps(app), refreshInterval);
|
||||
});
|
||||
};
|
||||
setInterval(() => getCfIps(app), refreshInterval)
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user