<commit_before>var express = require('express');
var router = express.Router();
var auth = require('../config/auth');
var Kifu = require('../models/kifu').Kifu;
var User = require('../models/user').User;
var Notification = require('../models/notification').Notification;
var Comment = require('../models/comment').Comment;
var async = require('async');

router.post('/', auth.ensureAuthenticated, function (req, res) {
	Kifu.findOne({
		_id: req.body._id
	}, function (error, kifu) {
		if (!error && kifu) {

			var comment = new Comment();

			comment.kifu = kifu;
			comment.user = req.user;
			comment.path = req.body.path;
			comment.content.markdown = req.body.content;

			comment.save(function (error) {
				if (!error) {
					kifu.comments.push(comment);
					kifu.save(function (error) {
						if (!error) {
							res.json(201, { message: 'Comment created with id: ' + comment._id });

							// Send notifications
							comment.getRecipients(function (recipients) {
								recipients.forEach(function (recipient) {
									// Send a notification to the kifu owner
									var notification = new Notification();
									notification.cause = 'new comment';
									notification.to = recipient;
									notification.from = comment.user;
									notification.kifu = comment.kifu;
									notification.path = comment.path;
									notification.comment = comment._id;

									notification.save();
								});
							});

						} else {
							res.json(500, {
								message: 'Error pushing reference to parent kifu. Error: ' + error
							});
						}
					});
				} else {
					res.json(500, { message: 'Error saving comment. Error: ' + error });
				}
			});

		} else if (error) {
			res.json(500, { message: 'Error creating comment. ' + error });
		} else {
			res.json(404, { message: 'No kifu found for _id:' + req.params._id });
		}
	});
});

// Get a list of comments in reverse cron
router.get('/', function (req, res) {
	var offset = req.query.offset || 0;
	var limit = Math.min(req.query.limit, 100) || 20;
	var user;
	var username = req.query.username || '';
	var since = req.query.since || null;

	// Bundle successive comments by the same user about the same kifu into a
	// single comment with an array of paths (moves)
	//
	// Helpful for preventing bursts of activity from wiping out everything else
	// that's interesting from a recent comments feed
	var chunk = req.query.chunk || false;
	var chunkedComments = [];
	var totalComments;
	var lastComment, lastUser, lastKifu;

	// Turn the since param into a date object
	if (since) {
		since = new Date(since);
	}

	function getTotal(callback) {
		if (totalComments) {
			callback();
		} else {
			var criteria;

			if (user) {
				criteria.user = user;
			}

			Comment.count(criteria, function (error, count) {
				totalComments = count;
				callback();
			});
		}
	}

	function getUser(callback) {
		User.findOne({ username: username })
			.exec(function (error, userObject) {
				if (!error && userObject) {
					user = userObject;
					callback();
				} else {
					if (error) {
						res.json('500', { message: error });
					} else if (!user) {
						res.json('500', { message: 'That username does not exist.' });
					}
				}
			});
	}

	function getComments() {
		var comments = Comment.find()
			.sort('-date')
			.skip(offset)
			.limit(limit);

		if (since) {
			comments = comments.where('date').gt(since);
		}

		if (user) {
			comments = comments.where('user').equals(user._id);
		}

		comments
			.populate('user', 'username email gravatar rank')
			.populate('kifu', 'shortid game')
			.select('-kifu.game')
			.exec(function (error, comments) {
				if (!error) {
					if (chunk) {
						// Transform a Mongoose document into a JavaScript object
						comments = comments.map(function (comment) { return comment.toObject(); });
						chunkify(comments);

						if (chunkedComments.length >= limit) {
							res.json('200', chunkedComments);
						} else {
							offset += limit;
							if (offset >= totalComments) {
								res.json('200', chunkedComments);
							} else {
								getComments();
							}
						}
					} else {
						res.json('200', comments);
					}
				} else {
					res.json('500', { message: error });
				}
			});
		}

		// Turn a flat array of comments into one where comments by the same user
		// on the same kifu are combined
		function chunkify(comments) {
			// For each comment...
			for (
				var i = 0, length = comments.length;
				i < length && chunkedComments.length < limit;
				i += 1
			) {
				var comment = comments[i];

				// If this comment has the same user and kifu as the last one...
				if (
					lastComment &&
					lastUser === String(comment.user._id) &&
					lastKifu === String(comment.kifu._id) &&

					// Discard duplicate comments on the same path
					lastComment.path !== comment.path
				) {

					// If the last comment's path isn't already an array, turn it into one
					if (!Array.isArray(lastComment.path)) {
						lastComment.path = [{ _id: lastComment._id, path: lastComment.path }];
					}

					var alreadyPresent = lastComment.path.some(function (path) {
						return path.path === comment.path;
					});

					if (!alreadyPresent) {
						// Push the current comment's path to the last object
						lastComment.path.push({ _id: comment._id, path: comment.path });

						lastComment.path.sort(function (a, b) {
							return a.path - b.path;
						});
					}

				} else {
					// This comment becomes the last comment
					lastComment = comment;
					lastUser = String(comment.user._id);
					lastKifu = String(comment.kifu._id);

					chunkedComments.push(comment);
				}
			}
		}

	// get the total (so we have a hard stop), then get the user if necessary,
	// then get comments
	var funcs = [getTotal];

	if (username) {
		funcs.push(getUser);
	}<commit_msg>Fix comment chunking bug<commit_after>var express = require('express');
var router = express.Router();
var auth = require('../config/auth');
var Kifu = require('../models/kifu').Kifu;
var User = require('../models/user').User;
var Notification = require('../models/notification').Notification;
var Comment = require('../models/comment').Comment;
var async = require('async');

router.post('/', auth.ensureAuthenticated, function (req, res) {
	Kifu.findOne({
		_id: req.body._id
	}, function (error, kifu) {
		if (!error && kifu) {

			var comment = new Comment();

			comment.kifu = kifu;
			comment.user = req.user;
			comment.path = req.body.path;
			comment.content.markdown = req.body.content;

			comment.save(function (error) {
				if (!error) {
					kifu.comments.push(comment);
					kifu.save(function (error) {
						if (!error) {
							res.json(201, { message: 'Comment created with id: ' + comment._id });

							// Send notifications
							comment.getRecipients(function (recipients) {
								recipients.forEach(function (recipient) {
									// Send a notification to the kifu owner
									var notification = new Notification();
									notification.cause = 'new comment';
									notification.to = recipient;
									notification.from = comment.user;
									notification.kifu = comment.kifu;
									notification.path = comment.path;
									notification.comment = comment._id;

									notification.save();
								});
							});

						} else {
							res.json(500, {
								message: 'Error pushing reference to parent kifu. Error: ' + error
							});
						}
					});
				} else {
					res.json(500, { message: 'Error saving comment. Error: ' + error });
				}
			});

		} else if (error) {
			res.json(500, { message: 'Error creating comment. ' + error });
		} else {
			res.json(404, { message: 'No kifu found for _id:' + req.params._id });
		}
	});
});

// Get a list of comments in reverse cron
router.get('/', function (req, res) {
	var offset = req.query.offset || 0;
	var limit = Math.min(req.query.limit, 100) || 20;
	var user;
	var username = req.query.username || '';
	var since = req.query.since || null;

	// Bundle successive comments by the same user about the same kifu into a
	// single comment with an array of paths (moves)
	//
	// Helpful for preventing bursts of activity from wiping out everything else
	// that's interesting from a recent comments feed
	var chunk = req.query.chunk || false;
	var chunkedComments = [];
	var totalComments;
	var lastComment, lastUser, lastKifu;

	// Turn the since param into a date object
	if (since) {
		since = new Date(since);
	}

	function getTotal(callback) {
		if (totalComments) {
			callback();
		} else {
			var criteria;

			if (user) {
				criteria.user = user;
			}

			Comment.count(criteria, function (error, count) {
				totalComments = count;
				callback();
			});
		}
	}

	function getUser(callback) {
		User.findOne({ username: username })
			.exec(function (error, userObject) {
				if (!error && userObject) {
					user = userObject;
					callback();
				} else {
					if (error) {
						res.json('500', { message: error });
					} else if (!user) {
						res.json('500', { message: 'That username does not exist.' });
					}
				}
			});
	}

	function getComments() {
		var comments = Comment.find()
			.sort('-date')
			.skip(offset)
			.limit(limit);

		if (since) {
			comments = comments.where('date').gt(since);
		}

		if (user) {
			comments = comments.where('user').equals(user._id);
		}

		comments
			.populate('user', 'username email gravatar rank')
			.populate('kifu', 'shortid game')
			.select('-kifu.game')
			.exec(function (error, comments) {
				if (!error) {
					if (chunk) {
						// Transform a Mongoose document into a JavaScript object
						comments = comments.map(function (comment) { return comment.toObject(); });
						chunkify(comments);

						if (chunkedComments.length >= limit) {
							res.json('200', chunkedComments);
						} else {
							offset += limit;
							if (offset >= totalComments) {
								res.json('200', chunkedComments);
							} else {
								getComments();
							}
						}
					} else {
						res.json('200', comments);
					}
				} else {
					res.json('500', { message: error });
				}
			});
		}

		// Turn a flat array of comments into one where comments by the same user
		// on the same kifu are combined
		function chunkify(comments) {
			function pathPresent(path) {
				return path.path === comment.path;
			}

			function pathSorter(a, b) {
				return a.path - b.path;
			}

			// For each comment...
			for (
				var i = 0, length = comments.length;
				i < length && chunkedComments.length < limit;
				i += 1
			) {
				var comment = comments[i];

				// If this comment has the same user and kifu as the last one...
				if (
					lastComment &&
					lastUser === String(comment.user._id) &&
					lastKifu === String(comment.kifu._id)
				) {

					// Don't add multiple notifications for multiple comments on the same move
					if (lastComment.path !== comment.path) {
						// If the last comment's path isn't already an array, turn it into one
						if (!Array.isArray(lastComment.path)) {
							lastComment.path = [{ _id: lastComment._id, path: lastComment.path }];
						}

						var alreadyPresent = lastComment.path.some(pathPresent);

						if (!alreadyPresent) {
							// Push the current comment's path to the last object
							lastComment.path.push({ _id: comment._id, path: comment.path });

							lastComment.path.sort(pathSorter);
						}
					}

				} else {
					// This comment becomes the last comment
					lastComment = comment;
					lastUser = String(comment.user._id);
					lastKifu = String(comment.kifu._id);

					chunkedComments.push(comment);
				}
			}
		}

	// get the total (so we have a hard stop), then get the user if necessary,
	// then get comments
	var funcs = [getTotal];

	if (username) {
		funcs.push(getUser);
	}
{'fraction_non_alphanumeric': 0.14076968821680402, 'fraction_numerical': 0.006966273043921502, 'mean_word_length': 2.539386650631389, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, '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': '3274290', 'n_tokens_mistral': 4852, 'n_tokens_neox': 3597, 'n_words': 1012}