Skip to content

Commit

Permalink
Add Notification
Browse files Browse the repository at this point in the history
Read only
  • Loading branch information
dready92 committed Nov 25, 2020
1 parent 2e80e8e commit 58f5949
Show file tree
Hide file tree
Showing 35 changed files with 897 additions and 62 deletions.
38 changes: 38 additions & 0 deletions src/backend/core/commands/user-notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ObjectId } from 'bson';
import UserNotification from '../../../shared/user-notification';
import sendEvent from '../../kafka/events/producer';
import KafkaMessage from '../../kafka/kafka-message';
import logger from '../logger';
/**
* A user notification is associated with an activity
*
*
* Should have:
* - activity: Object notification activity
* - user: Actor notification user target
*
* may have:
* - emailId(String) and email(EmailHead)
*
*/

const debug = logger.extend('commands:user-notification');

export async function createUserNotification(notification, sender) {
let userNotification;
const _id = new ObjectId();
try {
userNotification = UserNotification.fromObject({ ...notification, _id });
} catch (e) {
debug('Bad parameter: %O', e.message);
throw e;
}

const kafkaMessage = KafkaMessage.fromObject(userNotification.user._id, {
event: 'user:notification:create',
sender,
payload: userNotification,
});

await sendEvent(kafkaMessage);
}
29 changes: 25 additions & 4 deletions src/backend/core/events/email/share.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ import sendNotification from '../../../kafka/notifications/producer';
import { dbCol } from '../../../mongodb';
import logger from '../../logger';
import EmailShareActivity from '../../../../shared/email-share-activity';
import EmailHead from '../../../../shared/email-head';
import Actor from '../../../../shared/actor';
import { getEmailIfAllowed } from '../../../api-middleware/email-permission';
import KafkaMessage from '../../../kafka/kafka-message';
import UserNotification from '../../../../shared/user-notification';
import { createUserNotification } from '../../commands/user-notification';

const debug = logger.extend('events:email:share');

Expand Down Expand Up @@ -32,6 +36,25 @@ export async function emailShareReceiver(kafkaMessage) {
return false;
}

const updated = await updateEmailDocument(collection, email, activity, target);
if (!updated) {
return;
}
const notificationMessage = kafkaMessage.setEvent(NOTIFICATION_NAME);
await sendNotification(notificationMessage);

const userNotification = UserNotification.fromObject({
activity,
user: target,
seen: false,
email: EmailHead.fromEmail(email.email),
emailId: email._id,
});

await createUserNotification(userNotification, kafkaMessage.sender());
}

const updateEmailDocument = async (collection, email, activity, target) => {
try {
const { modifiedCount } = await collection.updateOne(
{ _id: email._id },
Expand All @@ -51,10 +74,8 @@ export async function emailShareReceiver(kafkaMessage) {
debug('MongoDB document update failed: %s %s', e.message, e.stack);
return false;
}

const notificationMessage = kafkaMessage.setEvent(NOTIFICATION_NAME);
sendNotification(notificationMessage);
}
return true;
};

export const EVENTS = {
'email:share': emailShareReceiver,
Expand Down
2 changes: 2 additions & 0 deletions src/backend/core/events/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { EVENTS as EVENTS9 } from './email/label-add';
import { EVENTS as EVENTS10 } from './chat-message/last-seen-pointer-update';
import { EVENTS as EVENTS11 } from './task';
import { EVENTS as EVENTS12 } from './email/user-state';
import { EVENTS as EVENTS13 } from './user-notification';

export const eventsListeners = {};

Expand All @@ -25,3 +26,4 @@ Object.keys(EVENTS9).forEach((k) => (eventsListeners[k] = EVENTS9[k]));
Object.keys(EVENTS10).forEach((k) => (eventsListeners[k] = EVENTS10[k]));
Object.keys(EVENTS11).forEach((k) => (eventsListeners[k] = EVENTS11[k]));
Object.keys(EVENTS12).forEach((k) => (eventsListeners[k] = EVENTS12[k]));
Object.keys(EVENTS13).forEach((k) => (eventsListeners[k] = EVENTS13[k]));
13 changes: 13 additions & 0 deletions src/backend/core/events/task.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ import { ObjectId } from 'bson';
import Task from '../../../shared/task';
import TaskCreatedActivity from '../../../shared/task-created-activity';
import TaskDoneStatusUpdatedActivity from '../../../shared/task-done-status-updated-activity';
import UserNotification from '../../../shared/user-notification';
import { dbCol } from '../../mongodb';
import { recordActivity } from '../activity';
import { createUserNotification } from '../commands/user-notification';
import logger from '../logger';

const debugF = logger.extend('events:task');
Expand Down Expand Up @@ -35,6 +37,17 @@ export async function taskCreateReceiver(kafkaMessage) {
const activity = TaskCreatedActivity.fromKafkaMessage(kafkaMessage);

await recordActivity(activity, task.emailId, true);

if (activity.actor._id !== activity.target._id) {
debug('creating user notification for user %s, task %s', activity.target.email, task.description);
const userNotification = UserNotification.fromObject({
activity,
user: activity.target,
seen: false,
emailId: task.emailId,
});
await createUserNotification(userNotification, kafkaMessage.sender());
}
}

export async function taskDoneStatusUpdateReceiver(kafkaMessage) {
Expand Down
35 changes: 35 additions & 0 deletions src/backend/core/events/user-notification.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import logger from '../logger';
import UserNotification from '../../../shared/user-notification';
import { dbCol } from '../../mongodb';
import { ObjectId } from 'bson';
import sendNotification from '../../kafka/notifications/producer';

const debug = logger.extend('events:user-notifications');

export async function userNotificationCreateReceiver(kafkaMessage) {
let userNotification;
try {
userNotification = UserNotification.fromObject(kafkaMessage.payload());
} catch (e) {
debug('Invalid payload %s: %O', e.message, kafkaMessage.payload());
}

try {
const collection = await dbCol('userNotifications');
const { insertedCount } = await collection.insertOne({ ...userNotification, _id: new ObjectId(userNotification._id) });
if (insertedCount !== 1) {
throw new Error('No document have been inserted in the datastore.');
}
} catch (e) {
debug('Can not insert new user notification: %s', e.message);
return;
}

const notification = kafkaMessage.setEvent('user:notification:created');

await sendNotification(notification);
}

export const EVENTS = {
'user:notification:create': userNotificationCreateReceiver,
};
6 changes: 5 additions & 1 deletion src/components/Modal.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,12 @@
}
};
const isOpened = () => {
return !!Component;
}
setContext(key, { open, close });
registerModal(open, close);
registerModal(open, close, isOpened);
</script>

<style>
Expand Down
2 changes: 2 additions & 0 deletions src/components/Nav.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script>
import { user } from '../libs/users';
import UserInline from '../routes/_components/User/Inline.svelte';
import NotificationNavButton from '../routes/_components/Notification/NavButton.svelte';
</script>

<style>
Expand Down Expand Up @@ -36,6 +37,7 @@ nav {
<i class="fas fa-cog fa-lg"></i>
</span>
</a>
<NotificationNavButton />
<UserInline user={$user} bgclass="has-background-white-bis is-large" />
</div>
</div>
Expand Down
8 changes: 7 additions & 1 deletion src/libs/modal/modalService.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
let openModalFn;
let closeModalFn;
let isOpenedFn = () => {};

export function registerModal(open, close) {
export function registerModal(open, close, isOpened) {
openModalFn = open;
closeModalFn = close;
isOpenedFn = isOpened;
}

export function openModal() {
Expand All @@ -13,3 +15,7 @@ export function openModal() {
export function closeModal() {
return closeModalFn;
}

export function isOpened() {
return isOpenedFn();
}
62 changes: 62 additions & 0 deletions src/libs/notificationProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { writable } from 'svelte/store';
import { get } from 'api';
import { registerEvent } from './sse';
import { isAfter, isBefore } from 'date-fns';
import EmailHead from '../shared/email-head';

export const unreadNotifications = writable([]);

export const start = () => {
const unregister = registerEvent('user:notification:created', (payload) => {
updateStore([payload]);
});
loadFromServer();
};

const loadFromServer = async () => {
try {
const unreads = await get('/api/notifications');
updateStore(unreads);
} catch (e) {
console.log('Error loading notifications from store:', e.message, e);
throw e;
}
};

const updateStore = (notifications) => {
const toUpdate = notifications.filter(n => !n.seen);
if (!toUpdate.length) {
return;
}
unreadNotifications.update((notifs) => {
const newStoreContents = [...notifs];

toUpdate.forEach(n => {
const hydratedNotif = hydrateNotification(n);
const index = newStoreContents.findIndex(notification => hydratedNotif._id === notification._id);
if (index >= 0) {
newStoreContents.splice(index, 1, hydratedNotif);
} else {
const previousNotifIndex = newStoreContents.findIndex(notification => {
console.log(new Date(hydratedNotif.activity.date), new Date(notification.activity.date), isAfter(new Date(hydratedNotif.activity.date), new Date(notification.activity.date)));
return isAfter(new Date(hydratedNotif.activity.date), new Date(notification.activity.date));
});
if (previousNotifIndex < 0) {
newStoreContents.push(hydratedNotif);
} else {
newStoreContents.splice(previousNotifIndex, 0, hydratedNotif);
}
}
});

return newStoreContents;
});
};

const hydrateNotification = (n) => {
const hydratedNotification = { ...n };
if (hydratedNotification.email) {
hydratedNotification.email = EmailHead.fromObject(n.email);
}
return hydratedNotification;
};
Loading

0 comments on commit 58f5949

Please sign in to comment.