Skip to content
This repository has been archived by the owner on Jul 6, 2021. It is now read-only.

[WIP] User messages #1127

Open
wants to merge 1 commit into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 30 additions & 0 deletions imports/api/conversations/conversations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';

// ======== DB-Model: ========
// "_id" -> ID
// "participants" -> [{
// "id" -> User ID
// "unread" -> Integer (Counter for unread messages)
// "readAt" -> Date
// "muted" -> Boolean
// }]
// "timeUpdated" -> Date
// "latestMessage" -> UserMessage ID
// ===========================

class Conversation {
constructor(dbDocument) { Object.assign(this, dbDocument); }

otherParticipants() {
return this.participants.filter((p) => p.id !== Meteor.userId());
}

isMutedByUser(userId = Meteor.userId()) {
return this.participants.find((p) => p.id === userId).muted;
}
}

export default Conversations = new Mongo.Collection('Conversations', {
transform: (dbDocument) => new Conversation(dbDocument)
});
62 changes: 62 additions & 0 deletions imports/api/conversations/methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import Conversations from './conversations.js';

Meteor.methods({
'conversation.add'(sender, recipients, latestMessage) {
check(sender, String);
check(recipients, [String]);
check(latestMessage, String);


// create the participant objects
const participants = recipients.map((id) => ({ id, unread: 1 }));
// and add the sender too but with no unread messages
participants.push({ id: sender, unread: 0 });

const conversationId = Conversations.insert(
{ participants
, latestMessage
, timeUpdated: new Date()
}
);

return conversationId;
},

'conversation.remove'(conversationId) {
check(conversationId, String);

Conversations.remove(conversationId);
},

'conversation.resetUnread'(conversationId) {
check(conversationId, String);

Conversations.update(
{ _id: conversationId, 'participants.id': Meteor.userId() },
{ $set: { 'participants.$.unread': 0 } }
);
},

'conversation.markAsRead'(conversationId) {
check(conversationId, String);

Conversations.update(
{ _id: conversationId, 'participants.id': Meteor.userId() },
{ $set:
{ 'participants.$.readAt': new Date()
, 'participants.$.unread': 0
}
}
);
},

'conversation.mute'(conversationId, mute = true) {
check(conversationId, String);
check(mute, Boolean);

Conversations.update(
{ _id: conversationId, 'participants.id': Meteor.userId() },
{ $set: { 'participants.$.muted': mute } }
);
}
});
5 changes: 5 additions & 0 deletions imports/api/conversations/server/publications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Conversations from '../conversations.js';

Meteor.publish('conversations', () => (
Conversations.find({ 'participants.id': { $in: [ Meteor.userId() ] } })
));
72 changes: 72 additions & 0 deletions imports/api/user-messages/methods.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Meteor } from 'meteor/meteor';

import Conversations from '../conversations/conversations.js';
import UserMessages from './user-messages.js';

Meteor.methods({
'userMessage.add'(recipients, content) {
check(recipients, Match.OneOf(String, [String]));
check(content, String);

// if a single recipient has been passed, put recipient into an array
if (typeof recipients === 'string') recipients = [recipients];

const sender = Meteor.userId();
const participantIds = recipients.concat([sender]);
const now = new Date();

// start building the message document
const set = { sender, content, timeCreated: now };

// Check if there's already a conversation with these users.
// If not create one.
const existingConversation = Conversations.findOne({ $and:
participantIds.map((id) => (
{ 'participants.id': { $in: [id] } }
))
});

if (existingConversation) {
set.conversation = existingConversation._id;
}

const messageId = UserMessages.insert(set);

if (existingConversation) {
Conversations.update(
{ _id: existingConversation._id
, 'participants.id': { $in: recipients }
},
{ $set: { latestMessage: messageId, timeUpdated: now }
, $inc: { 'participants.$.unread': 1 }
}
);
} else {
Meteor.call(
'conversation.add',
sender,
recipients,
messageId,
(err, conversationId) => {
if (err) {
console.error(err);
} else {
UserMessages.update(
messageId,
{ $set: { conversation: conversationId } }
);
}
}
);
}

let usersToUpdate = recipients;
if (existingConversation) {
usersToUpdate = usersToUpdate.filter((userId) => {
return !existingConversation.isMutedByUser(userId);
});
}

Meteor.call('user.updateInbox', usersToUpdate);
}
});
13 changes: 13 additions & 0 deletions imports/api/user-messages/server/publications.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Meteor } from 'meteor/meteor';

import UserMessages from '../user-messages.js';

Meteor.publish('userMessages.latest', (messageId) => {
check(messageId, String);
return UserMessages.find({ _id: messageId });
});

Meteor.publish('userMessages.fullConversation', (conversationId) => {
check(conversationId, String);
return UserMessages.find({ conversation: conversationId });
});
24 changes: 24 additions & 0 deletions imports/api/user-messages/user-messages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Meteor } from 'meteor/meteor';
import { Mongo } from 'meteor/mongo';

// ======== DB-Model: ========
// "_id" -> ID
// "sender" -> User ID
// "content" -> String
// "timeCreated" -> Date
// "conversation" -> Conversation ID
// ===========================

class UserMessage {
constructor(dbDocument) { Object.assign(this, dbDocument); }

/** Check if it has been sent by the currently logged in user
*
* @returns {Boolean}
*/
sentByOwnUser() { return this.sender === Meteor.userId(); }
}

export default UserMessages = new Mongo.Collection('UserMessages', {
transform: (dbDocument) => new UserMessage(dbDocument)
});
18 changes: 18 additions & 0 deletions imports/api/users/methods.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,23 @@ Meteor.methods({
Meteor.users.update(Meteor.userId(), {
$set: { 'profile.locale': locale }
});
},

'user.updateInbox'(userIds) {
check(userIds, [String]);

Meteor.users.update(
{ _id: { $in: userIds } },
{ $inc: { 'inbox.unseen': 1, 'inbox.unread': 1 } },
{ multi: true }
);
},

'user.resetInbox'(resetUnread = false) {
const modifier = { $set: { 'inbox.unseen': 0 } };

if (resetUnread) modifier.$set['inbox.unread'] = 0;

Meteor.users.update({ _id: Meteor.userId() }, modifier);
}
});
1 change: 1 addition & 0 deletions imports/api/users/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import IdTools from '/imports/utils/id-tools.js';
// "privileges" -> [admin]
// "lastLogin" -> Date
// notificactions: True if the user wants notification mails sent to them
// "inbox" -> {unseen: Integer, unread: Integer}

// Calculated fields
// badges: union of user's id and group ids for permission checking, calculated by updateBadges()
Expand Down
5 changes: 5 additions & 0 deletions imports/startup/both/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,11 @@ Router.map(function () {
}
});

this.route('messenger', {
path: '/messenger',
template: 'messenger'
});

this.route('pages', { ///////// static /////////
path: 'page/:page_name',
action: function() {
Expand Down
4 changes: 4 additions & 0 deletions imports/startup/server/register-api.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@

import '/imports/api/conversations/methods.js';
import '/imports/api/conversations/server/publications.js';
import '/imports/api/courses/methods.js';
import '/imports/api/courses/server/publications.js';
import '/imports/api/course-discussions/methods.js';
Expand All @@ -12,6 +14,8 @@ import '/imports/api/log/server/publications.js';
import '/imports/api/regions/methods.js';
import '/imports/api/regions/server/publications.js';
import '/imports/api/users/users.js';
import '/imports/api/user-messages/methods.js';
import '/imports/api/user-messages/server/publications.js';
import '/imports/api/users/methods.js';
import '/imports/api/users/server/publications.js';
import '/imports/api/venues/methods.js';
Expand Down
29 changes: 0 additions & 29 deletions imports/ui/components/account-tasks/account-tasks.scss
Original file line number Diff line number Diff line change
@@ -1,32 +1,3 @@
.navbar .navbar-nav > .open > .login-dropdown,
.navbar .navbar-nav > .dropdown > .login-dropdown {
@media (min-width: $screen-sm) {
min-width: 300px;
}
}

.user-frame {
@media (max-width: $grid-float-breakpoint) {
@include clearfix;
background-color: $dropdown-bg;
padding-top: 15px;
}
}

.ownuser-frame {
padding: 0 ($grid-gutter-width / 2);
}

.ownuser-frame-welcome {
font-size: $font-size-h3;
margin-top: 10px;
text-align: center;
}

.login {
padding-top: 10px;
}

.account-tasks .help-block {
margin-bottom: 0;
}
Expand Down