Skip to content

Commit

Permalink
Implement real time notifications
Browse files Browse the repository at this point in the history
  • Loading branch information
Dimi Mikadze committed Nov 10, 2019
1 parent c4cf243 commit f464f61
Show file tree
Hide file tree
Showing 6 changed files with 735 additions and 574 deletions.
3 changes: 3 additions & 0 deletions api/constants/Subscriptions.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ export const MESSAGE_CREATED = 'MESSAGE_CREATED';
export const IS_USER_ONLINE = 'IS_USER_ONLINE';

export const NEW_CONVERSATION = 'NEW_CONVERSATION';

export const NOTIFICATION_CREATED_OR_DELETED =
'NOTIFICATION_CREATED_OR_DELETED';
55 changes: 52 additions & 3 deletions api/resolvers/notification.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
import { withFilter } from 'apollo-server';

import { pubSub } from '../utils/apollo-server';
import { NOTIFICATION_CREATED_OR_DELETED } from '../constants/Subscriptions';

const Query = {
/**
* Gets notifications for specific user
Expand Down Expand Up @@ -44,7 +49,7 @@ const Mutation = {
},
{ Notification, User }
) => {
const newNotification = await new Notification({
let newNotification = await new Notification({
author: authorId,
user: userId,
post: postId,
Expand All @@ -57,6 +62,20 @@ const Mutation = {
{ $push: { notifications: newNotification.id } }
);

// Publish notification created event
newNotification = await newNotification
.populate('author')
.populate('follow')
.populate({ path: 'comment', populate: { path: 'post' } })
.populate({ path: 'like', populate: { path: 'post' } })
.execPopulate();
pubSub.publish(NOTIFICATION_CREATED_OR_DELETED, {
notificationCreatedOrDeleted: {
operation: 'CREATE',
notification: newNotification,
},
});

return newNotification;
},
/**
Expand All @@ -69,14 +88,28 @@ const Mutation = {
{ input: { id } },
{ Notification, User }
) => {
const notification = await Notification.findByIdAndRemove(id);
let notification = await Notification.findByIdAndRemove(id);

// Delete notification from users collection
await User.findOneAndUpdate(
{ _id: notification.user },
{ $pull: { notifications: notification.id } }
);

// Publish notification deleted event
notification = await notification
.populate('author')
.populate('follow')
.populate({ path: 'comment', populate: { path: 'post' } })
.populate({ path: 'like', populate: { path: 'post' } })
.execPopulate();
pubSub.publish(NOTIFICATION_CREATED_OR_DELETED, {
notificationCreatedOrDeleted: {
operation: 'DELETE',
notification,
},
});

return notification;
},
/**
Expand All @@ -103,4 +136,20 @@ const Mutation = {
},
};

export default { Query, Mutation };
const Subscription = {
/**
* Subscribes to notification created or deleted event
*/
notificationCreatedOrDeleted: {
subscribe: withFilter(
() => pubSub.asyncIterator(NOTIFICATION_CREATED_OR_DELETED),
(payload, variables, { authUser }) => {
const userId = payload.notificationCreatedOrDeleted.notification.user.toString();

return authUser && authUser.id === userId;
}
),
},
};

export default { Query, Mutation, Subscription };
18 changes: 18 additions & 0 deletions api/schema/Notification.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,16 @@ const NotificationSchema = gql`
notifications: [NotificationPayload]!
}
enum NotificationOperationType {
CREATE
DELETE
}
type NotificationCreatedOrDeletedPayload {
operation: NotificationOperationType!
notification: NotificationPayload
}
# ---------------------------------------------------------
# Queries
# ---------------------------------------------------------
Expand All @@ -88,6 +98,14 @@ const NotificationSchema = gql`
# Updates notification seen values for user
updateNotificationSeen(input: UpdateNotificationSeenInput!): Boolean
}
# ---------------------------------------------------------
# Subscriptions
# ---------------------------------------------------------
extend type Subscription {
# Subscribes to notification created or deleted event
notificationCreatedOrDeleted: NotificationCreatedOrDeletedPayload
}
`;

export default NotificationSchema;
51 changes: 49 additions & 2 deletions frontend/src/components/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { GlobalStyle } from './GlobalStyles';

import { GET_AUTH_USER } from 'graphql/user';
import { GET_NEW_CONVERSATIONS_SUBSCRIPTION } from 'graphql/messages';
import { NOTIFICATION_CREATED_OR_DELETED } from 'graphql/notification';

import Message from 'components/Message';
import { Loading } from 'components/Loading';
Expand All @@ -19,10 +20,56 @@ import { useStore } from 'store';
* Root component of the app
*/
const App = () => {
const [{ message }, dispatch] = useStore();
const [{ message }] = useStore();

const { loading, subscribeToMore, data, refetch } = useQuery(GET_AUTH_USER);

useEffect(() => {
const unsubscribe = subscribeToMore({
document: NOTIFICATION_CREATED_OR_DELETED,
updateQuery: async (prev, { subscriptionData }) => {
if (!subscriptionData.data) return prev;

const oldNotifications = prev.getAuthUser.newNotifications;
const {
operation,
notification,
} = subscriptionData.data.notificationCreatedOrDeleted;

let newNotifications;

if (operation === 'CREATE') {
// Don't show message notification in Header if user already is on notifications page
if (window.location.href.split('/')[3] === 'notifications') {
return prev;
}

// Add new notification
newNotifications = [notification, ...oldNotifications];
} else {
// Remove from notifications
const notifications = oldNotifications;
const index = notifications.findIndex(n => n.id === notification.id);
if (index > -1) {
notifications.splice(index, 1);
}

newNotifications = notifications;
}

// Attach new notifications to authUser
const authUser = prev.getAuthUser;
authUser.newNotifications = newNotifications;

return { getAuthUser: authUser };
},
});

return () => {
unsubscribe();
};
}, [subscribeToMore]);

useEffect(() => {
const unsubscribe = subscribeToMore({
document: GET_NEW_CONVERSATIONS_SUBSCRIPTION,
Expand Down Expand Up @@ -60,7 +107,7 @@ const App = () => {
return () => {
unsubscribe();
};
}, [subscribeToMore, dispatch]);
}, [subscribeToMore]);

if (loading) return <Loading top="xl" />;

Expand Down
71 changes: 46 additions & 25 deletions frontend/src/graphql/notification.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,36 @@
import gql from 'graphql-tag';

/**
* Records to select from notifications
*/
const notificationPayload = `
id
createdAt
author {
id
fullName
username
image
}
follow {
id
}
comment {
id
post {
id
image
}
}
like {
id
post {
id
image
}
}
`;

/**
* Creates a notification for user
*/
Expand Down Expand Up @@ -30,31 +61,7 @@ export const GET_USER_NOTIFICATION = gql`
getUserNotifications(userId: $userId, skip: $skip, limit: $limit) {
count
notifications {
id
createdAt
author {
id
fullName
username
image
}
follow {
id
}
comment {
id
post {
id
image
}
}
like {
id
post {
id
image
}
}
${notificationPayload}
}
}
}
Expand All @@ -68,3 +75,17 @@ export const UPDATE_NOTIFICATION_SEEN = gql`
updateNotificationSeen(input: $input)
}
`;

/**
* Get user's notifications in real time
*/
export const NOTIFICATION_CREATED_OR_DELETED = gql`
subscription {
notificationCreatedOrDeleted {
operation
notification {
${notificationPayload}
}
}
}
`;
Loading

0 comments on commit f464f61

Please sign in to comment.