Vulcan/packages/telescope-migrations/lib/server/migrations.js
Maxime Quandalle 94c6121d91 Improve jsHint consistency
This commit touch a lot of lines of code with the goal to be more
rigorous about JavaScript code conventions defined in the `.jshintrc`.

Some modification:

* Add a list of used global symbols in the corresponding section of
  `.jshintrc`
* Use local variables instead of global in a lot of places where the
  keyword `var` was mistakenly forgotten
* Add missing semi-colons after instructions
* Add new lines at the end of files
* Remove trailing whitespaces
* Use newer name of some Meteor APIs, eg `addFiles` instead of
  `add_files`
* Add missing `break` statements in `switch` blocks
* Use `===` instead of `==` and `!==` instead of `!=`
* Remove unused variables

This commit should also fix a few bugs due to this lack of rigor. One
example of that was the test `typeof navElements === "array"` that was
never true because in JavaScript, `typeof [] === "object"`, we
replaced this test by the `_.isArray` method provided by underscore.
It might also fix some potential collision related to global
variables.

There is still plenty of work until Telescope code base passes jsHint
validation, but at least this commit is a step in the right direction.
2015-05-01 18:38:27 +02:00

563 lines
19 KiB
JavaScript

// TODO: switch over to Tom's migration package.
// database migrations
// http://stackoverflow.com/questions/10365496/meteor-how-to-perform-database-migrations
Migrations = new Meteor.Collection('migrations');
Meteor.startup(function () {
allMigrations = Object.keys(migrationsList);
_.each(allMigrations, function(migrationName){
runMigration(migrationName);
});
});
Meteor.methods({
removeMigration: function (name) {
if (Users.is.admin(Meteor.user())) {
console.log('// removing migration: ' + name);
Migrations.remove({name: name});
}
}
});
// wrapper function for all migrations
var runMigration = function (migrationName) {
var migration = Migrations.findOne({name: migrationName});
if (migration){
if(typeof migration.finishedAt === 'undefined'){
// if migration exists but hasn't finished, remove it and start fresh
console.log('!!! Found incomplete migration "'+migrationName+'", removing and running again.');
Migrations.remove({name: migrationName});
}else{
// do nothing
// console.log('Migration "'+migrationName+'" already exists, doing nothing.')
return;
}
}
console.log("//----------------------------------------------------------------------//");
console.log("//------------// Starting "+migrationName+" Migration //-----------//");
console.log("//----------------------------------------------------------------------//");
Migrations.insert({name: migrationName, startedAt: new Date(), completed: false});
// execute migration function
var itemsAffected = migrationsList[migrationName]() || 0;
Migrations.update({name: migrationName}, {$set: {finishedAt: new Date(), completed: true, itemsAffected: itemsAffected}});
console.log("//----------------------------------------------------------------------//");
console.log("//------------// Ending "+migrationName+" Migration //-----------//");
console.log("//----------------------------------------------------------------------//");
};
var migrationsList = {
updatePostStatus: function () {
var i = 0;
Posts.find({status: {$exists : false}}).forEach(function (post) {
i++;
Posts.update(post._id, {$set: {status: 2}});
console.log("---------------------");
console.log("Post: "+post.title);
console.log("Updating status to approved");
});
return i;
},
updateCategories: function () {
if (typeof Categories === "undefined" || Categories === null) return;
var i = 0;
Categories.find({slug: {$exists : false}}).forEach(function (category) {
i++;
var slug = Telescope.utils.slugify(category.name);
Categories.update(category._id, {$set: {slug: slug}});
console.log("---------------------");
console.log("Category: "+category.name);
console.log("Updating category with new slug: "+slug);
});
return i;
},
updatePostCategories: function () {
if (typeof Categories === "undefined" || Categories === null) return;
var i = 0;
Posts.find().forEach(function (post) {
i++;
var oldCategories = post.categories;
var newCategories = [];
var category = {};
var updating = false; // by default, assume we're not going to do anything
// iterate over the post.categories array
// if the post has no categories then nothing will happen
_.each(oldCategories, function(value){
// make sure the categories are strings
if((typeof value === "string") && (category = Categories.findOne({name: value}))){
// if value is a string, then look for the matching category object
// and if it exists push it to the newCategories array
updating = true; // we're updating at least one category for this post
newCategories.push(category);
}else{
// if category A) is already an object, or B) it's a string but a matching category object doesn't exist
// just keep the current value
newCategories.push(value);
}
});
if(updating){
// update categories property on post
Posts.update(post._id, {$set: {categories: newCategories}});
}
// START CONSOLE LOGS
console.log("---------------------");
console.log("Post: "+post.title);
if(updating){
console.log(oldCategories.length+" categories: "+oldCategories);
console.log("Updating categories array to: ");
console.log(newCategories);
}else{
console.log("No updates");
}
// END CONSOLE LOGS
});
return i;
},
updateUserProfiles: function () {
var i = 0;
var allUsers = Meteor.users.find();
console.log('> Found '+allUsers.count()+' users.\n');
allUsers.forEach(function(user){
i++;
console.log('> Updating user '+user._id+' ('+user.username+')');
var properties = {};
// update user slug
if(Users.getUserName(user))
properties.slug = Telescope.utils.slugify(Users.getUserName(user));
// update user isAdmin flag
if(typeof user.isAdmin === 'undefined')
properties.isAdmin = false;
// update postCount
var postsByUser = Posts.find({userId: user._id});
properties.postCount = postsByUser.count();
// update commentCount
var commentsByUser = Comments.find({userId: user._id});
properties.commentCount = commentsByUser.count();
Meteor.users.update(user._id, {$set:properties});
});
return i;
},
resetUpvotesDownvotes: function () {
var i = 0;
Posts.find().forEach(function (post) {
i++;
var upvotes = 0,
downvotes = 0;
console.log("Post: "+post.title);
if(post.upvoters){
upvotes = post.upvoters.length;
console.log("Found "+upvotes+" upvotes.");
}
if(post.downvoters){
downvotes = post.downvoters.length;
console.log("Found "+downvotes+" downvotes.");
}
Posts.update(post._id, {$set: {upvotes: upvotes, downvotes: downvotes}});
console.log("---------------------");
});
return i;
},
resetCommentsUpvotesDownvotes: function () {
var i = 0;
Comments.find().forEach(function (comment) {
i++;
var upvotes = 0,
downvotes = 0;
console.log("Comment: "+comment._id);
if(comment.upvoters){
upvotes = comment.upvoters.length;
console.log("Found "+upvotes+" upvotes.");
}
if(comment.downvoters){
downvotes = comment.downvoters.length;
console.log("Found "+downvotes+" downvotes.");
}
Comments.update(comment._id, {$set: {upvotes: upvotes, downvotes: downvotes}});
console.log("---------------------");
});
return i;
},
headlineToTitle: function () {
var i = 0;
Posts.find({title: {$exists : false}}).forEach(function (post) {
i++;
console.log("Post: "+post.headline+" "+post.title);
Posts.update(post._id, { $rename: { 'headline': 'title'}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
commentsSubmittedToCreatedAt: function () {
var i = 0;
Comments.find({createdAt: {$exists: false}}).forEach(function (comment) {
i++;
console.log("Comment: "+comment._id);
Comments.update(comment._id, { $rename: { 'submitted': 'createdAt'}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
commentsPostToPostId: function () {
var i = 0;
Comments.find({postId: {$exists : false}}).forEach(function (comment) {
i++;
console.log("Comment: "+comment._id);
Comments.update(comment._id, { $rename: { 'post': 'postId'}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
createdAtSubmittedToDate: function () {
var i = 0;
Posts.find().forEach(function (post) {
if(typeof post.submitted === "number" || typeof post.createdAt === "number"){
i++;
console.log("Posts: "+post.title);
var createdAt = new Date(post.createdAt);
var submitted = new Date(post.submitted);
console.log(createdAt);
Posts.update(post._id, { $set: { 'createdAt': createdAt, submitted: submitted}}, {multi: true, validate: false});
console.log("---------------------");
}
});
return i;
},
commentsCreatedAtToDate: function () {
var i = 0;
Comments.find().forEach(function (comment) {
if(typeof comment.createdAt === "number"){
i++;
console.log("Comment: "+comment._id);
var createdAt = new Date(comment.createdAt);
console.log(createdAt);
Comments.update(comment._id, { $set: { 'createdAt': createdAt}}, {multi: true, validate: false});
console.log("---------------------");
}
});
return i;
},
submittedToPostedAt: function () {
var i = 0;
Posts.find({postedAt: {$exists : false}}).forEach(function (post) {
i++;
console.log("Post: "+post._id);
Posts.update(post._id, { $rename: { 'submitted': 'postedAt'}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
addPostedAtToComments: function () {
var i = 0;
Comments.find({postedAt: {$exists : false}}).forEach(function (comment) {
i++;
console.log("Comment: "+comment._id);
Comments.update(comment._id, { $set: { 'postedAt': comment.createdAt}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
parentToParentCommentId: function () {
var i = 0;
Comments.find({parent: {$exists: true}, parentCommentId: {$exists : false}}).forEach(function (comment) {
i++;
console.log("Comment: "+comment._id);
Comments.update(comment._id, { $set: { 'parentCommentId': comment.parent}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
addLastCommentedAt: function () {
var i = 0;
Posts.find({$and: [
{$or: [{comments: {$gt: 0}}, {commentCount: {$gt: 0}}]},
{lastCommentedAt: {$exists : false}}
]}).forEach(function (post) {
i++;
console.log("Post: "+post._id);
var postComments = Comments.find({$or: [{postId: post._id}, {post: post._id}]}, {sort: {postedAt: -1}}).fetch();
var lastComment;
if (_.isEmpty(postComments)) {
console.log('postComments from post '+post._id+' is empty. Skipping.');
return;
}
lastComment = postComments[0];
Posts.update(post._id, { $set: { lastCommentedAt: lastComment.postedAt}}, {multi: false, validate: false});
console.log("---------------------");
});
return i;
},
commentsToCommentCount: function () {
var i = 0;
Posts.find({comments: {$exists : true}, commentCount: {$exists : false}}).forEach(function (post) {
i++;
console.log("Post: "+post._id);
Posts.update(post._id, { $set: { 'commentCount': post.comments}, $unset: { 'comments': ''}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
addCommentersToPosts: function () {
var i = 0;
Comments.find().forEach(function (comment) {
i++;
console.log("Comment: "+comment._id);
console.log("Post: "+comment.postId);
Posts.update(comment.postId, { $addToSet: { 'commenters': comment.userId}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
createVotes: function () { // create empty user.votes object
var i = 0;
Meteor.users.find({votes: {$exists : false}}).forEach(function (user) {
i++;
console.log("User: "+user._id);
Meteor.users.update(user._id, {$set: {votes: {}}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
moveVotesFromProfile: function () {
var i = 0;
Meteor.users.find().forEach(function (user) {
i++;
console.log("User: "+user._id);
Meteor.users.update(user._id, {
$rename: {
'profile.upvotedPosts': 'votes.upvotedPosts',
'profile.downvotedPosts': 'votes.downvotedPosts',
'profile.upvotedComments': 'votes.upvotedComments',
'profile.downvotedComments': 'votes.downvotedComments'
}
}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
addHTMLBody: function () {
var i = 0;
Posts.find({body: {$exists : true}}).forEach(function (post) {
i++;
var htmlBody = Telescope.utils.sanitize(marked(post.body));
console.log("Post: "+post._id);
Posts.update(post._id, { $set: { 'htmlBody': htmlBody}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
addHTMLComment: function () {
var i = 0;
Comments.find({body: {$exists : true}}).forEach(function (comment) {
i++;
var htmlBody = Telescope.utils.sanitize(marked(comment.body));
console.log("Comment: "+comment._id);
Comments.update(comment._id, { $set: { 'htmlBody': htmlBody}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
clicksToClickCount: function () {
var i = 0;
Posts.find({"clicks": {$exists: true}, "clickCount": {$exists : false}}).forEach(function (post) {
i++;
console.log("Post: " + post._id);
Posts.update(post._id, { $set: { 'clickCount': post.clicks}, $unset: { 'clicks': ''}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
commentsCountToCommentCount: function () {
var i = 0;
Posts.find({"commentCount": {$exists : false}}).forEach(function (post) {
i++;
console.log("Post: " + post._id);
Posts.update({_id: post._id}, { $set: { 'commentCount': post.commentsCount}, $unset: {'commentsCount': ""}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
userDataCommentsCountToCommentCount: function(){
var i = 0;
Meteor.users.find({'commentCount': {$exists: false}}).forEach(function(user){
i++;
var commentCount = Comments.find({userId: user._id}).count();
console.log("User: " + user._id);
Meteor.users.update(user._id, {$unset: {data: ""}, $set: {'commentCount': commentCount}});
console.log("---------------------");
});
return i;
},
clicksToClickCountForRealThisTime: function () { // since both fields might be co-existing, add to clickCount instead of overwriting it
var i = 0;
Posts.find({'clicks': {$exists: true}}).forEach(function (post) {
i++;
console.log("Post: " + post._id);
Posts.update(post._id, { $inc: { 'clickCount': post.clicks}, $unset: {'clicks': ""}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
normalizeCategories: function () {
var i = 0;
Posts.find({'categories': {$exists: true}}).forEach(function (post) {
i++;
console.log("Post: " + post._id);
var justCategoryIds = post.categories.map(function (category){
return category._id;
});
Posts.update(post._id, {$set: {categories: justCategoryIds, oldCategories: post.categories}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
cleanUpStickyProperty: function () {
var i = 0;
Posts.find({'sticky': {$exists: false}}).forEach(function (post) {
i++;
console.log("Post: " + post._id);
Posts.update(post._id, {$set: {sticky: false}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
show0112ReleaseNotes: function () {
var i = 0;
// if this is the 0.11.2 update, the first run event will not exist yet.
// if that's the case, make sure to still show release notes
if (!Events.findOne({name: 'firstRun'})) {
Releases.update({number:'0.11.2'}, {$set: {read:false}});
}
return i;
},
removeThumbnailHTTP: function () {
var i = 0;
Posts.find({thumbnailUrl: {$exists : true}}).forEach(function (post) {
i++;
var newThumbnailUrl = post.thumbnailUrl.replace("http:", "");
console.log("Post: "+post._id);
Posts.update(post._id, { $set: { 'thumbnailUrl': newThumbnailUrl}}, {multi: true, validate: false});
console.log("---------------------");
});
return i;
},
updateUserNames: function () {
var i = 0;
var allUsers = Meteor.users.find({username: {$exists: true}});
console.log('> Found '+allUsers.count()+' users.\n');
allUsers.forEach(function(user){
i++;
// Perform the same transforms done by useraccounts with `lowercaseUsernames` set to `true`
var oldUsername = user.username;
var username = user.username;
username = username.trim().replace(/\s+/gm, ' ');
user.profile.username = user.profile.name || username;
delete user.profile.name;
username = username.toLowerCase().replace(/\s+/gm, '');
user.username = username;
if (user.emails && user.emails.length > 0) {
_.each(user.emails, function(email){
email.address = email.address.toLowerCase().replace(/\s+/gm, '');
});
}
console.log('> Updating user '+user._id+' ('+oldUsername+' -> ' + user.username + ')');
try {
Meteor.users.update(user._id, {
$set: {
emails: user.emails,
profile: user.profile,
username: user.username,
},
});
}
catch (err) {
console.warn('> Unable to convert username ' + user.username + ' to lowercase!');
console.warn('> Please try to fix it by hand!! :(');
}
});
return i;
},
changeColorNames: function () {
var i = 0;
var settings = Settings.findOne();
var set = {};
if (!!settings) {
if (!!settings.buttonColor)
set.accentColor = settings.buttonColor;
if (!!settings.buttonTextColor)
set.accentContrastColor = settings.buttonTextColor;
if (!!settings.buttonColor)
set.secondaryColor = settings.headerColor;
if (!!settings.buttonColor)
set.secondaryContrastColor = settings.headerTextColor;
if (!_.isEmpty(set)) {
Settings.update(settings._id, {$set: set}, {validate: false});
}
}
return i;
},
migrateUserProfiles: function () {
var i = 0;
var allUsers = Meteor.users.find({telescope: {$exists: false}});
console.log('> Found '+allUsers.count()+' users.\n');
allUsers.forEach(function(user){
i++;
console.log('> Updating user '+user._id+' (' + user.username + ')');
var telescopeUserData = {};
// look for user data on root of user object and in user.votes
_.each(Telescope.schemas.userData._schema, function (property, key) {
if (!!user[key]) {
telescopeUserData[key] = user[key];
}
if (!!user.votes[key]) {
telescopeUserData[key] = user.votes[key];
}
});
// console.log(telescopeUserData);
try {
Meteor.users.update(user._id, {
$set: {
telescope: telescopeUserData
}
});
} catch (err) {
console.log(err);
console.warn('> Unable to migrate profile for user ' + user.username);
}
});
return i;
},
};
// TODO: normalize categories?