Commit 5061c411 authored by Andrew Newdigate's avatar Andrew Newdigate

Feature: delete messages.

With a huge dollop of thanks to @trevorah for saving this commit from the
.git trash-heap!
parent 6256413f
......@@ -84,8 +84,9 @@ function attachNotificationListenersToSchema(schema, options) {
}
if(options.onRemove) {
schema.post('remove', function(model) {
schema.post('remove', function(model, next) {
options.onRemove(model);
next();
});
}
......
......@@ -112,4 +112,32 @@ describe('mongoose-utils', function() {
return Promise.each(_.range(20), iter);
});
describe('attachNotificationListenersToSchema', function() {
it('should generate remove events', function() {
var p = new Promise(function(resolve) {
mongooseUtils.attachNotificationListenersToSchema(persistence.schemas.UserSchema, {
onRemove: function(model) {
resolve(model);
}
});
});
return mongooseUtils.upsert(persistence.User, { username: username }, { $setOnInsert: { displayName: x } })
.spread(function() {
return persistence.User.findOne({ username: username }).exec();
})
.then(function(user) {
return user.remove();
})
.then(function() {
return p;
})
.then(function(user) {
assert.strictEqual(user.username, username);
})
});
});
});
......@@ -6,12 +6,13 @@ var cypher = require("cypher-promise");
var neo4jClient = cypher(env.config.get('neo4j:endpoint'));
var debug = require('debug')('gitter:app:graph-suggestions');
var _ = require('lodash');
var Promise = require('bluebird');
function query(text, params) {
debug("neo4j query: %s %j", text, params);
var start = Date.now();
return neo4jClient.query(text, params)
return Promise.resolve(neo4jClient.query(text, params))
.then(function(response) {
stats.responseTime('suggestions.graph.query', Date.now() - start);
debug("Neo4j response: %j", response);
......
......@@ -11,5 +11,10 @@
},
"private": true,
"dependencies": {
"bluebird": "^3.2.1",
"cypher-promise": "^1.0.1",
"debug": "^2.2.0",
"gitter-web-env": "file:../env",
"lodash": "^3.2.0"
}
}
......@@ -109,12 +109,16 @@ var ChatCollection = LiveCollection.extend({
},
initialize: function() {
this.listenTo(this, 'add remove', function(model, collection) {
this.listenTo(this, 'add', function(model, collection) {
collection.once('sort', function() {
burstCalculator.calc.call(this, model);
});
});
this.listenTo(this, 'remove', function() {
burstCalculator.parse(this);
});
this.listenTo(this, 'sync', function(model) {
// Sync is for collections and models
if (!(model instanceof Backbone.Model)) return;
......
......@@ -426,6 +426,10 @@ module.exports = (function() {
return this.model.id && this.isOwnMessage() && this.isInEditablePeriod() && !this.isEmbedded();
},
canDelete: function() {
return this.model.id && !this.isEmbedded() && (this.isOwnMessage() || context.isTroupeAdmin());
},
hasBeenEdited: function() {
return !!this.model.get('editedAt');
},
......@@ -859,8 +863,7 @@ module.exports = (function() {
},
delete: function() {
this.model.set('text', '');
this.model.save();
this.model.destroy();
},
retry: function() {
......@@ -891,6 +894,7 @@ module.exports = (function() {
var canCollapse = !deleted && this.model.get('isCollapsible');
var isPersisted = !!this.model.id;
var canEdit = !deleted && this.chatItemView.canEdit() && isPersisted;
var canDelete = this.chatItemView.canDelete() && isPersisted;
var data = {
actions: [
......@@ -903,7 +907,7 @@ module.exports = (function() {
}
data.actions.push({ name: 'edit', description: 'Edit', disabled: !canEdit });
data.actions.push({ name: 'delete', description: 'Delete', disabled: !canEdit });
data.actions.push({ name: 'delete', description: 'Delete', disabled: !canDelete });
if (canCollapse) {
var action;
......
......@@ -66,7 +66,7 @@ function main(uri, dryRun) {
return Promise.map(missingIds, function(itemId) {
console.log('REMOVING ', itemId);
// Remove the items, slowly
return unreadItemService.testOnly.removeItem(room.id, itemId)
return unreadItemService.removeItem(room.id, itemId)
.delay(10);
}, { concurrency: 1 });
});
......
......@@ -8,6 +8,7 @@ var restSerializer = require('../../../serializers/rest-serializer');
var userAgentTagger = require('../../../web/user-agent-tagger');
var loadTroupeFromParam = require('./load-troupe-param');
var mongoUtils = require('gitter-web-persistence-utils/lib/mongo-utils');
var RoomWithPolicyService = require('../../../services/room-with-policy-service');
function parseLookups(lookups) {
......@@ -115,6 +116,22 @@ module.exports = {
});
},
destroy: function(req, res) {
return Promise.join(
chatService.findById(req.params.chatMessageId),
loadTroupeFromParam(req),
function(chatMessage, troupe) {
if (!chatMessage) throw new StatusError(404);
var roomWithPolicyService = new RoomWithPolicyService(troupe, req.user, req.userRoomPolicy);
return roomWithPolicyService.deleteMessageFromRoom(chatMessage);
})
.then(function() {
res.status(204);
return null;
})
},
subresources: {
'readBy': require('./chat-read-by')
}
......
......@@ -13,7 +13,7 @@ var userService = require("./user-service");
var processText = require('gitter-web-text-processor');
var Promise = require('bluebird');
var StatusError = require('statuserror');
var _ = require('underscore');
var _ = require('lodash');
var mongooseUtils = require('gitter-web-persistence-utils/lib/mongoose-utils');
var groupResolver = require('./group-resolver');
var chatSearchService = require('./chat-search-service');
......@@ -552,6 +552,16 @@ exports.removeAllMessagesForUserIdInRoomId = function(userId, roomId) {
});
};
function deleteMessageFromRoom(troupeId, chatMessage) {
return unreadItemService.removeItem(troupeId, chatMessage._id)
.then(function() {
return chatMessage.remove();
})
.return(null);
}
exports.deleteMessageFromRoom = deleteMessageFromRoom;
exports.testOnly = {
setUseHints: function(value) {
useHints = value;
......
......@@ -53,6 +53,7 @@ function allowAddUser() {
return this.policy.canAddUser();
}
/**
* Allow staff or admins to update the tags for a room
* @return {Promise} Promise of room
......@@ -393,4 +394,23 @@ RoomWithPolicyService.prototype.autoConfigureHooks = secureMethod([allowAdmin],
});
});
function deleteMessageFromRoomEnsureRoomMatch(chatMessage) {
if (!chatMessage || !mongoUtils.objectIDsEqual(chatMessage.toTroupeId, this.room._id)) {
throw new StatusError(404);
}
}
function deleteMessageFromRoomAllowSender(chatMessage) {
if (!this.user || !chatMessage) return false;
return mongoUtils.objectIDsEqual(chatMessage.fromUserId, this.user._id);
}
/**
* Delete a message, if it's your own or you're a room admin
*/
RoomWithPolicyService.prototype.deleteMessageFromRoom = secureMethod([deleteMessageFromRoomEnsureRoomMatch, allowAdmin, deleteMessageFromRoomAllowSender], function(chatMessage) {
return chatService.deleteMessageFromRoom(this.room._id, chatMessage);
});
module.exports = RoomWithPolicyService;
......@@ -40,7 +40,7 @@ function sinceFilter(since) {
/**
* Item removed
*/
var removeItem = Promise.method(function (troupeId, itemId) {
function removeItem(troupeId, itemId) {
if(!troupeId) throw new Error("removeItem failed. Troupe cannot be null");
if(!itemId) throw new Error("removeItem failed. itemId cannot be null");
......@@ -81,7 +81,9 @@ var removeItem = Promise.method(function (troupeId, itemId) {
});
});
});
}
exports.removeItem = Promise.method(removeItem);
/*
This ensures that if all else fails, we clear out the unread items
......@@ -440,7 +442,6 @@ exports.testOnly = {
},
getOldestId: getOldestId,
sinceFilter: sinceFilter,
removeItem: removeItem,
getTroupeIdsCausingBadgeCount: getTroupeIdsCausingBadgeCount,
processResultsForNewItemWithMentions: processResultsForNewItemWithMentions
};
......@@ -212,7 +212,7 @@ describe('unread-item-service', function() {
mockito.when(roomMembershipServiceMock).findMembersForRoomWithLurk(troupeId1).thenReturn(Promise.resolve(usersWithLurkHash));
return unreadItemService.testOnly.removeItem(troupeId1, chatId)
return unreadItemService.removeItem(troupeId1, chatId)
.then(function() {
// Two calls here, not three
mockito.verify(appEvents, once).unreadItemsRemoved(userId1, troupeId1);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment