<file_sep>const restify = require('restify'),
  jwt = require('express-jwt'),
  jwks = require('jwks-rsa'),
  dotenv = require('dotenv'),
  corsMiddleware = require('restify-cors-middleware'),
  server = restify.createServer({
    name: 'rage-meter',
    version: '1.0.0',
  }),
  auth = require('./middleware/Auth'),
  cors = corsMiddleware({
    origins: [
      'http://localhost:4200',
    ],
  }),
  Rage = require('./model/Rage'),
  RageConfig = require('./model/RageConfig'),
  Person = require('./model/Person');

dotenv.config();

const jwtCheck = jwt({
  secret: jwks.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: process.env.AUTH0_JWKS_URI,
  }),
  audience: process.env.AUTH0_API_AUDIENCE,
  issuer: '',
  algorithms: ['RS256'],
});

server.use(restify.plugins.bodyParser());
server.pre(cors.preflight);
server.use(cors.actual);
server.use(auth);

const rageConfig = new RageConfig({
  maxRage: 10,
  minRage: 0,
  baseRoute: '/rage',
  jwtConfig: jwtCheck,
});

let arrayPerson = [
    {name: 'person1'},
    {name: 'person2'},
    {name: 'person3'},
    {name: 'person4'},
    {name: 'person5'},
    {name: 'person6'},
    {name: 'person7'},
];

arrayPerson = arrayPerson.map((p) => {
    return new Person(p.name, 2);
});

const rageMeter = new Rage(arrayPerson, 0, 10, '/rage');

const routes = require('./routes/Routes');
routes(server, rageMeter, rageConfig);

server.listen(8005);
console.log('RAGE API started on port 8005');

module.exports = server;
<file_sep># Rage Meter API

[![CircleCI](https://circleci.com/gh/L4ngu0r/rage-meter/tree/master.svg?style=svg)](https://circleci.com/gh/L4ngu0r/rage-meter/tree/master) [![Greenkeeper badge](https://badges.greenkeeper.io/L4ngu0r/rage-meter.svg)](https://greenkeeper.io/)

### Description

Used to troll in our team, decide to make a little API to integrate in our dev tools!
Today only in mem and with mocks.

### Features

Someone is on rage ? Vote for him to increase his level !!
Two votes are necessary to make his rage level up, also a cooldown
of 5 minutes between each vote.

### Services

[API](doc/api.html) : generated with raml2html
[RAML](doc/api.raml)

```
npm install
node index.js
```

##### Generate documentation

```
npm run gen-doc
```

### TODO

- reset on daily basis
- widget
- use database
- admin section security
- configuration (rage limits, cooldown, number of votes etc)

### License

MIT<file_sep>/* eslint linebreak-style: ["error", "windows"]*/
'use strict'

const http = require('http')

const ecstatic = require('ecstatic')({
  root: `${__dirname}`,
  showDir: false,
  autoIndex: true,
  defaultExt: 'html',
  contentType: 'text/html',
  cors: true,
});

http.createServer(ecstatic).listen(8080);
<file_sep>const crypto = require('crypto');
let mapIps = {};

/**
 *
 * @param {object} req
 * @param {object} res
 * @param {object} next
 */
module.exports = function Auth(req, res, next) {
  // regex limited to 255
  let idsReg = /\/rage\/([01]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])/,
    reqPath = req.url,
    reqMethod = req.method,
    reqIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress,
    matches;
  // use of regex to identify if we try to get a person
  if ((matches = idsReg.exec(reqPath)) !== null) {
    if (matches.index === idsReg.lastIndex) {
      idsReg.lastIndex++;
    }
  }
  let ip = null;
  if (reqIp.length < 16) {
    // fallback for ipv6 localhost (::1)
    ip = req.headers.host.split(':')[0];
  } else {
    let ipSplit = reqIp.split(':');
    ip = ipSplit[ipSplit.length - 1];
  }

  if (reqMethod !== 'POST') {
    next();
  } else {
    // check if ip already requested something
    let isPresent = Object.keys(mapIps).find((key) => {
      return key === ip;
    });
    let now = new Date().getTime();
    let accessAdmin = reqPath.indexOf('adm') !== -1;
    if (accessAdmin) {
      let ipTrying = isPresent || ip;
      switch (ipTrying) {
        case 'localhost':
        case '::1':
        case '127.0.0.1':
          next();
          break;
        default:
          res.status(401);
          res.json({'Error': 'Unauthorized request!'});
      }
    } else if (!isPresent) {
      // first time call
      mapIps[ip] = {
        hash: crypto.randomBytes(20).toString('hex'),
        timestamp: now,
      };
      next();
    } else if (mapIps[ip].timestamp + 5 * 60 * 1000 > now && matches && reqMethod === 'POST') {
      // we block vote on POST method with regex matches id and for 5 minutes
      // showing the remaining time in the response
      let remainingTime = ((mapIps[ip].timestamp + 5 * 60 * 1000) - now) / 60;
      res.status(401);
      res.json({
        'Warning': 'Too early sorry :)',
        'Time remaining': Math.floor(remainingTime) + 's',
      });
    } else {
      // can get data otherwise, and update timestamp
      mapIps[isPresent].timestamp = now;
      next();
    }
  }
};
<file_sep>/**
 *
 * @param {object} server
 * @param {module.Rage} rage
 * @param {module.RageConfig} rageConfig
 * @param {Router} routerInstance
 */
module.exports = function Routes(server, rage, rageConfig) {
  server.get(rage.baseRoute, rageConfig.jwtConfig, (req, res) => {
    res.header('Content-Type', 'application/json');
    res.json(rage.persons);
  });

  server.get(`${rage.baseRoute}/:id`, rageConfig.jwtConfig, (req, res) => {
    let person = rage.findPersonById(req.params.id);

    if (person) {
      res.json(person);
    } else {
      res.status(404);
      res.json({});
    }
  });


  server.post(`${rage.baseRoute}/:id`, rageConfig.jwtConfig, (req, res) => {
    let person = rage.findPersonById(req.params.id);

    if (person) {
      person.nbVote++;
      switch (person.nbVote) {
        case 1:
          res.json({'Vote': 'added'});
          break;
        case 2:
          person.nbVote = 0;
          person.rageLevel++;
          res.json({'Vote': 'added'});
          break;
        default:
          res.json({'Error': 'too much vote'});
      }
    } else {
      res.status(400);
      res.json({'Error': 'id ' + req.params.id + ' not matching anyone'});
    }
  });

};
<file_sep>/**
 *
 * @type {module.RageConfig}
 */
module.exports = class RageConfig {
  /**
   *
   * @param {object} options
   */
  constructor(options) {
    if (!options) {
      throw new Error('No options found !');
    }
    this.maxRage = options.maxRage ? options.maxRage : 10;
    this.minRage = options.minRage ? options.minRage : 0;
    this.baseRoute = options.baseRoute ? options.baseRoute : '/';
    this.minutesToWait = options.minutesToWait ? options.minutesToWait : 5;
    this.jwtConfig = options.jwtConfig ? options.jwtConfig : null;
  }
};
<file_sep>const chai = require('chai'),
    chaiHttp = require('chai-http'),
    server = require('../index'),
    should = chai.should(),
    dotenv = require('dotenv');

dotenv.config();
chai.use(chaiHttp);

describe('Persons', () => {
  let token = {};
  let persons = [];

  before((done) => {
    chai.request('https://languor.eu.auth0.com/oauth/token')
      .post('/')
      .send({
        'client_id': process.env.AUTH0_API_ID,
        'client_secret': process.env.AUTH0_API_SECRET,
        'audience': process.env.AUTH0_API_AUDIENCE,
        'grant_type': 'client_credentials',
      })
      .end((err, res) => {
        res.should.have.status(200);
        token = res.body.access_token;
        done();
      });
  });

  describe('/GET person', () => {
    it('it should GET all the persons', (done) => {
      chai.request(server)
          .get('/rage')
          .set('Authorization', `Bearer ${token}`)
          .end((err, res) => {
            if (err) {
              throw err;
            }
            persons = res.body;
            res.should.have.status(200);
            res.body.should.be.a('array');
            res.body.length.should.be.eql(7);
            done();
          });
    });
  });

  describe('/GET/:id person', () => {
    it('it should GET a person by the given id', (done) => {
      const person = persons[0];
      chai.request(server)
          .get(`/rage/${person.id}`)
          .set('Authorization', `Bearer ${token}`)
          .end((err, res) => {
            if (err) {
              throw err;
            }
            res.should.have.status(200);
            res.body.should.be.a('object');
            res.body.should.have.property('name');
            res.body.should.have.property('id');
            res.body.should.have.property('id').eql(person.id);
            done();
          });
    });
  });

  describe('/POST/:id', () => {
    it('should POST a vote for a person', (done) => {
      chai.request(server)
          .post(`/rage/${persons[1].id}`)
          .set('Authorization', `Bearer ${token}`)
          .end((err, res) => {
            if (err) {
              throw err;
            }
            res.should.have.status(200);
            res.body.should.be.a('object');
            res.body.should.have.property('Vote');
            res.body.should.have.property('Vote').eql('added');
            done();
          });
    });

    it('should not add a vote for a person before 5 min', (done) => {
      chai.request(server)
          .post(`/rage/${persons[1].id}`)
          .set('Authorization', `Bearer ${token}`)
          .end((err, res) => {
            res.should.have.status(401);
            res.body.should.be.a('object');
            res.body.should.have.property('Warning');
            res.body.should.have.property('Warning').eql('Too early sorry :)');
            res.body.should.have.property('Time remaining');
            done();
          });
    });
  });
});
