<commit_before>// TODO: add hotness meter
// TODO: Add avatar vs avatar fights (8hr rounds)
// TODO: Display current round vote history on stats page
// TODO: Remote fat footer, and add static links like on SpinKit
// TODO: FOCUS ON PERFORMANCE
// TODO: mongoose error handling middleware
// TODO: scheduler to remove lowest ranked every day
// TODO: make a new collections for storing Previous Votes for each character
// TODO: add characteristic to profile page that user can select from dropdown:
         // http://ideonomy.mit.edu/essays/traits.html

// TODO: jquery wait until image loaded on profile page
// TODO: set minimum width/height on homepage thumbnails to prevent sliding of DOM
// TODO: reset every 200 rounds
// TODO: socket.io real time number of characters

var async = require('async');
var express = require('express');
var mongoose = require('mongoose');
var path = require('path');
var request = require('request');
var xml2js = require('xml2js');
var _ = require('underscore');
var config = require('./config.json');

// OpenShift configuration
var IP_ADDRESS = process.env.OPENSHIFT_NODEJS_IP || '127.0.0.1';
var PORT = process.env.OPENSHIFT_NODEJS_PORT || 8080;

var app = express();

// Express configuration
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'app')));
app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.send(500, { message: err.message });
});

// MongoDB configuration
mongoose.connect(config.db, {
  server: {
    auto_reconnect: true,
    poolSize: 10,
    socketOptions: {
      keepAlive: 1
    }
  },
  db: {
    numberOfRetries: 1000,
    retryMiliSeconds: 1000
  }
});

// Character schema
var Character = mongoose.model('Character', {
  characterId: { type: String, unique: true, index: true },
  name: String,
  race: String,
  gender: String,
  wrongGender: Boolean,
  bloodline: String,
  wins: { type: Number, default: 0, index: true },
  losses: { type: Number, default: 0 },
  reports: { type: Number, default: 0 },
  random: { type: [Number], index: '2d' },
  voted: { type: Boolean, default: false }
});

/**
* GET /api/characters
* Retrieves 2 characters per user and increments global counter.
*/
app.get('/api/characters', function(req, res, next) {
  var choices = { 0: 'female', 1: 'male' };
  var randomGender = choices[Math.round(Math.random())];
  Character
    .find({ random: { $near: [Math.random(), 0] } })
    .where('voted', false)
    .where('gender', randomGender)
    .limit(2)
    .exec(function(err, characters) {
      if (err) return next(err);

      // When there are no more character pairs given by randomGender,
      // check if there are character pairs of the opposite gender
      if (characters.length < 2) {
        var oppositeRandomGender = randomGender === 'female' ? 'male' : 'female';
        Character
          .find({ random: { $near: [Math.random(), 0] } })
          .where('voted', false)
          .where('gender', oppositeRandomGender)
          .limit(2)
          .exec(function(err, characters) {
            if (err) return next(err);

            // When there are no character pairs left of either gender,
            // reset voted flags, and start the next round
            if (characters.length < 2) {
              Character.update({}, { $set: { voted: false } }, { multi: true }, function(err) {
                if (err) return next(err);
                return res.send([]);
              });
            }

            // Send two characters of oppositeRandomGender
            return res.send(characters);
          });
      }

      // Send two characters of randomGender
      res.send(characters);
    });
});

/**
* POST /report
* Increment character's report count. After (5) successive strikes,
* that character gets deleted from the database.
*/
app.post('/api/report', function(req, res, next) {
  var characterId = req.body.characterId;
  Character.findOne({ characterId: '123' }, function(err, character) {
    if (err) return next(err);
    if (!character) return res.send(404, { message: 'Character Not Found' });

    if (character) {
      character.reports++;
      if (character.reports >= 5) {
        var url = req.protocol + '://' + req.host + ':' + PORT +
          '/api/characters/' + characterId + '?secretCode=' + config.secretCode;
        request.del(url);
        res.send(200);
      } else {
        character.save(function(err) {
          if (err) return res.send(err);
          res.send(200, character.name + ' has been reported');
        });
      }
    }
  });
});

/**
* POST /report/gender
* Marks a character as being an invalid gender,
* e.g. Actual "male" avatar has been added as "female"
*/
app.post('/api/report/gender', function(req, res, next) {
  var characterId = req.body.characterId;
  Character.findOne({ characterId: characterId }, function(err, user) {
    if (err) return res.send(err);
    if (user) {
      user.wrongGender = true;
      user.save(function(err) {
        if (err) return res.send(err);
        res.send(200);
      });
    } else {
      res.send(404);
    }
  });
});

/**
* DEL /api/characters/:id
* Delete a character from the database
* Requres the secret code as a querystring to prevent abuse
*/
app.del('/api/characters/:id', function(req, res) {
  var characterId = req.params.id;
  if (req.query.secretCode !== config.secretCode) {
    return res.send(500);
  }
  Character.remove({ characterId: characterId }, function(err) {
    if (err) return res.send(err);
    res.send(200);
  });
});<commit_msg>Simplified report function<commit_after>// TODO: add hotness meter
// TODO: Add avatar vs avatar fights (8hr rounds)
// TODO: Display current round vote history on stats page
// TODO: Remote fat footer, and add static links like on SpinKit
// TODO: FOCUS ON PERFORMANCE
// TODO: mongoose error handling middleware
// TODO: scheduler to remove lowest ranked every day
// TODO: make a new collections for storing Previous Votes for each character
// TODO: add characteristic to profile page that user can select from dropdown:
         // http://ideonomy.mit.edu/essays/traits.html

// TODO: jquery wait until image loaded on profile page
// TODO: set minimum width/height on homepage thumbnails to prevent sliding of DOM
// TODO: reset every 200 rounds
// TODO: socket.io real time number of characters

var async = require('async');
var express = require('express');
var mongoose = require('mongoose');
var path = require('path');
var request = require('request');
var xml2js = require('xml2js');
var _ = require('underscore');
var config = require('./config.json');

// OpenShift configuration
var IP_ADDRESS = process.env.OPENSHIFT_NODEJS_IP || '127.0.0.1';
var PORT = process.env.OPENSHIFT_NODEJS_PORT || 8080;

var app = express();

// Express configuration
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'app')));
app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.send(500, { message: err.message });
});

// MongoDB configuration
mongoose.connect(config.db, {
  server: {
    auto_reconnect: true,
    poolSize: 10,
    socketOptions: {
      keepAlive: 1
    }
  },
  db: {
    numberOfRetries: 1000,
    retryMiliSeconds: 1000
  }
});

// Character schema
var Character = mongoose.model('Character', {
  characterId: { type: String, unique: true, index: true },
  name: String,
  race: String,
  gender: String,
  wrongGender: Boolean,
  bloodline: String,
  wins: { type: Number, default: 0, index: true },
  losses: { type: Number, default: 0 },
  reports: { type: Number, default: 0 },
  random: { type: [Number], index: '2d' },
  voted: { type: Boolean, default: false }
});

/**
* GET /api/characters
* Retrieves 2 characters per user and increments global counter.
*/
app.get('/api/characters', function(req, res, next) {
  var choices = { 0: 'female', 1: 'male' };
  var randomGender = choices[Math.round(Math.random())];
  Character
    .find({ random: { $near: [Math.random(), 0] } })
    .where('voted', false)
    .where('gender', randomGender)
    .limit(2)
    .exec(function(err, characters) {
      if (err) return next(err);

      // When there are no more character pairs given by randomGender,
      // check if there are character pairs of the opposite gender
      if (characters.length < 2) {
        var oppositeRandomGender = randomGender === 'female' ? 'male' : 'female';
        Character
          .find({ random: { $near: [Math.random(), 0] } })
          .where('voted', false)
          .where('gender', oppositeRandomGender)
          .limit(2)
          .exec(function(err, characters) {
            if (err) return next(err);

            // When there are no character pairs left of either gender,
            // reset voted flags, and start the next round
            if (characters.length < 2) {
              Character.update({}, { $set: { voted: false } }, { multi: true }, function(err) {
                if (err) return next(err);
                return res.send([]);
              });
            }

            // Send two characters of oppositeRandomGender
            return res.send(characters);
          });
      }

      // Send two characters of randomGender
      res.send(characters);
    });
});

/**
 * POST /report
 * @param characterId
 * Increment character "reports" count
 */
app.post('/api/report', function(req, res, next) {
  Character.findOne({ characterId: req.body.characterId }, function(err, character) {
    if (err) return next(err);
    if (!character) return res.send(404, { message: 'Character Not Found' });
    character.reports++;
    if (character.reports > 4) {
      character.remove();
      return res.send({ message: 'Character has been deleted'});
    }
    character.save(function(err) {
      if (err) return next(err);
      res.send(200, { message: character.name + ' has been reported' });
    });
  });
});

/**
* POST /report/gender
* Marks a character as being an invalid gender,
* e.g. Actual "male" avatar has been added as "female"
*/
app.post('/api/report/gender', function(req, res, next) {
  var characterId = req.body.characterId;
  Character.findOne({ characterId: characterId }, function(err, user) {
    if (err) return res.send(err);
    if (user) {
      user.wrongGender = true;
      user.save(function(err) {
        if (err) return res.send(err);
        res.send(200);
      });
    } else {
      res.send(404);
    }
  });
});

/**
 * DEL /api/characters/:id
 * @param id
 * Delete a character from the database
 */
app.del('/api/characters/:id', function(req, res, next) {
  if (req.query.secretCode !== config.secretCode) return next(err);
  Character.remove({ characterId: req.params.id }, function(err) {
    if (err) return next(err);
    res.send(200);
  });
});
{'fraction_non_alphanumeric': 0.1450611648712799, 'fraction_numerical': 0.011685229140040169, 'mean_word_length': 2.95916154680159, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 4}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1134202', 'n_tokens_mistral': 3429, 'n_tokens_neox': 3268, 'n_words': 1001}