implemented /help route
This commit is contained in:
		@@ -114,6 +114,7 @@ app.use('/', require('./routes/measurement'));
 | 
			
		||||
app.use('/', require('./routes/template'));
 | 
			
		||||
app.use('/', require('./routes/model'));
 | 
			
		||||
app.use('/', require('./routes/user'));
 | 
			
		||||
app.use('/', require('./routes/help'));
 | 
			
		||||
 | 
			
		||||
// static files
 | 
			
		||||
app.use('/static', express.static('static'));
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								src/models/help.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								src/models/help.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
			
		||||
import db from '../db';
 | 
			
		||||
import mongoose from 'mongoose';
 | 
			
		||||
 | 
			
		||||
const HelpSchema = new mongoose.Schema({
 | 
			
		||||
  key: {type: String, index: {unique: true}},
 | 
			
		||||
  level: String,
 | 
			
		||||
  text: String
 | 
			
		||||
}, {minimize: false});
 | 
			
		||||
 | 
			
		||||
// changelog query helper
 | 
			
		||||
HelpSchema.query.log = function <Q extends mongoose.DocumentQuery<any, any>> (req) {
 | 
			
		||||
  db.log(req, this);
 | 
			
		||||
  return this;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default mongoose.model<any, mongoose.Model<any, any>>('help', HelpSchema);
 | 
			
		||||
							
								
								
									
										184
									
								
								src/routes/help.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										184
									
								
								src/routes/help.spec.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,184 @@
 | 
			
		||||
import should from 'should/as-function';
 | 
			
		||||
import TestHelper from "../test/helper";
 | 
			
		||||
import HelpModel from '../models/help';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
describe('/help', () => {
 | 
			
		||||
  let server;
 | 
			
		||||
  before(done => TestHelper.before(done));
 | 
			
		||||
  beforeEach(done => server = TestHelper.beforeEach(server, done));
 | 
			
		||||
  afterEach(done => TestHelper.afterEach(server, done));
 | 
			
		||||
  after(done => TestHelper.after(done));
 | 
			
		||||
 | 
			
		||||
  describe('GET /help/{key}', () => {
 | 
			
		||||
    it('returns the required text', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        res: {text: 'Samples help', level: 'read'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns the required text without authorization if allowed', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/%2Fdocumentation',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        res: {text: 'Documentation help', level: 'none'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns 404 for an invalid key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/documentation/database',
 | 
			
		||||
        httpStatus: 404
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns 404 for an unknown key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/xxx',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 404
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('returns 403 for a text with a higher level than given', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/%2Fmodels',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 403
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {api: 'janedoe'},
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an unauthorized request if a level is given', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'get',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('POST /help/{key}', () => {
 | 
			
		||||
    it('changes the required text', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
        HelpModel.find({key: '/samples'}).lean().exec((err, data) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.lengthOf(1);
 | 
			
		||||
          should(data[0]).have.only.keys('_id', 'key', 'text', 'level');
 | 
			
		||||
          should(data[0]).property('key', '/samples');
 | 
			
		||||
          should(data[0]).property('text', 'New samples help');
 | 
			
		||||
          should(data[0]).property('level', 'write');
 | 
			
		||||
          done();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('saves a new text', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/help/%2Fmaterials',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 200,
 | 
			
		||||
        req: {text: 'Materials help', level: 'dev'}
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
        HelpModel.find({key: '/materials'}).lean().exec((err, data) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.lengthOf(1);
 | 
			
		||||
          should(data[0]).have.only.keys('_id', 'key', 'text', 'level', '__v');
 | 
			
		||||
          should(data[0]).property('key', '/materials');
 | 
			
		||||
          should(data[0]).property('text', 'Materials help');
 | 
			
		||||
          should(data[0]).property('level', 'dev');
 | 
			
		||||
          done();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {key: 'admin'},
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a write user', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 403,
 | 
			
		||||
        req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an unauthorized request', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'post',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        httpStatus: 401,
 | 
			
		||||
        req: {text: 'New samples help', level: 'write'}
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('DELETE /help/{key}', () => {
 | 
			
		||||
    it('deletes the required entry', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {basic: 'admin'},
 | 
			
		||||
        httpStatus: 200
 | 
			
		||||
      }).end((err, res) => {
 | 
			
		||||
        if (err) return done(err);
 | 
			
		||||
        should(res.body).be.eql({status: 'OK'});
 | 
			
		||||
        HelpModel.find({key: '/materials'}).lean().exec((err, data) => {
 | 
			
		||||
          if (err) return done(err);
 | 
			
		||||
          should(data).have.lengthOf(0);
 | 
			
		||||
          done();
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an API key', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {key: 'admin'},
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects a write user', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        auth: {basic: 'janedoe'},
 | 
			
		||||
        httpStatus: 403
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    it('rejects an unauthorized request', done => {
 | 
			
		||||
      TestHelper.request(server, done, {
 | 
			
		||||
        method: 'delete',
 | 
			
		||||
        url: '/help/%2Fsamples',
 | 
			
		||||
        httpStatus: 401
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										55
									
								
								src/routes/help.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/routes/help.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
import express from 'express';
 | 
			
		||||
import HelpModel from '../models/help';
 | 
			
		||||
import HelpValidate from './validate/help';
 | 
			
		||||
import res400 from './validate/res400';
 | 
			
		||||
import globals from '../globals';
 | 
			
		||||
 | 
			
		||||
const router = express.Router();
 | 
			
		||||
 | 
			
		||||
router.get('/help/:key', (req, res, next) => {
 | 
			
		||||
  const {error: paramError, value: key} = HelpValidate.params(req.params);
 | 
			
		||||
  if (paramError) return res400(paramError, res);
 | 
			
		||||
 | 
			
		||||
  HelpModel.findOne(key).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    if (data.level !== 'none') {  // check level
 | 
			
		||||
      if (!req.auth(res,
 | 
			
		||||
        Object.values(globals.levels).slice(Object.values(globals.levels).findIndex(e => e === data.level))
 | 
			
		||||
        , 'basic')) return;
 | 
			
		||||
    }
 | 
			
		||||
    res.json(HelpValidate.output(data));
 | 
			
		||||
  })
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.post('/help/:key', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
  const {error: paramError, value: key} = HelpValidate.params(req.params);
 | 
			
		||||
  if (paramError) return res400(paramError, res);
 | 
			
		||||
  const {error, value: help} = HelpValidate.input(req.body);
 | 
			
		||||
  if (error) return res400(error, res);
 | 
			
		||||
 | 
			
		||||
  HelpModel.findOneAndUpdate(key, help, {upsert: true}).log(req).lean().exec(err => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    res.json({status: 'OK'});
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
router.delete('/help/:key', (req, res, next) => {
 | 
			
		||||
  if (!req.auth(res, ['dev', 'admin'], 'basic')) return;
 | 
			
		||||
  const {error: paramError, value: key} = HelpValidate.params(req.params);
 | 
			
		||||
  if (paramError) return res400(paramError, res);
 | 
			
		||||
 | 
			
		||||
  HelpModel.findOneAndDelete(key).log(req).lean().exec((err, data) => {
 | 
			
		||||
    if (err) return next(err);
 | 
			
		||||
    if (!data) {
 | 
			
		||||
      return res.status(404).json({status: 'Not found'});
 | 
			
		||||
    }
 | 
			
		||||
    res.json({status: 'OK'});
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
module.exports = router;
 | 
			
		||||
							
								
								
									
										34
									
								
								src/routes/validate/help.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/routes/validate/help.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
import Joi from 'joi';
 | 
			
		||||
import globals from '../../globals';
 | 
			
		||||
 | 
			
		||||
export default class HelpValidate {
 | 
			
		||||
  private static help = {
 | 
			
		||||
    text: Joi.string()
 | 
			
		||||
      .allow('')
 | 
			
		||||
      .max(8192),
 | 
			
		||||
 | 
			
		||||
    level: Joi.string()
 | 
			
		||||
      .valid('none', ...Object.values(globals.levels))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static input (data) {
 | 
			
		||||
    return Joi.object({
 | 
			
		||||
      text: this.help.text.required(),
 | 
			
		||||
      level: this.help.level.required()
 | 
			
		||||
    }).validate(data);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static output (data) {
 | 
			
		||||
    const {value, error} = Joi.object({
 | 
			
		||||
      text: this.help.text,
 | 
			
		||||
      level: this.help.level
 | 
			
		||||
    }).validate(data, {stripUnknown: true});
 | 
			
		||||
    return error !== undefined? null : value;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static params(data) {
 | 
			
		||||
    return Joi.object({
 | 
			
		||||
      key: Joi.string().min(1).max(128)
 | 
			
		||||
    }).validate(data);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -893,6 +893,26 @@
 | 
			
		||||
        "user_id" : {"$oid": "000000000000000000000003"},
 | 
			
		||||
        "__v" : 0
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "helps": [
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"150000000000000000000001"},
 | 
			
		||||
        "key": "/documentation",
 | 
			
		||||
        "text": "Documentation help",
 | 
			
		||||
        "level": "none"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"150000000000000000000002"},
 | 
			
		||||
        "key": "/samples",
 | 
			
		||||
        "text": "Samples help",
 | 
			
		||||
        "level": "read"
 | 
			
		||||
      },
 | 
			
		||||
      {
 | 
			
		||||
        "_id": {"$oid":"150000000000000000000003"},
 | 
			
		||||
        "key": "/models",
 | 
			
		||||
        "text": "Models help",
 | 
			
		||||
        "level": "dev"
 | 
			
		||||
      }
 | 
			
		||||
    ]
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user