diff --git a/api/v1/gateway/index.js b/api/v1/gateway/index.js index b2ea919..ede156f 100644 --- a/api/v1/gateway/index.js +++ b/api/v1/gateway/index.js @@ -1,9 +1,11 @@ const User = require('../../../models/User'); const secret = require('../../../secret'); const config = require('../../../config'); +const Category = require('../../../models/Category'); const jwt = require('jsonwebtoken'); -const siolib = require('socket.io') +const siolib = require('socket.io'); +const uuid = require('uuid'); class GatewayServer { constructor(httpServer) { @@ -68,23 +70,37 @@ GatewayServer.prototype.eventSetup = function() { console.log(`[*] [gateway] [handshake] Got yoo from ${socket.username}, connection is finally completed!`); socket.isConnected = true; - socket.on('message', ({ categoryId, content }) => { - // TODO: URGENT: Check if the category exists and if the user has access to it (access coming soon) - const msgConfig = { - username: socket.username, - creatorId: socket.userId, - categoryId: categoryId, - content: content + socket.on('message', ({ category, content }) => { + // TODO: When/if category permissions are added, check if the user has permissions for that category + const categoryTitle = socket.joinedCategories[category._id]; + if (!categoryTitle) return; + + const messageObject = { + author: { + username: socket.username, + _id: socket.userId + }, + category: { + title: categoryTitle, + _id: category._id + }, + content: content, + _id: uuid.v4() }; - socket.to(categoryId).emit('message', msgConfig); - socket.emit('message', msgConfig); + socket.to(category._id).emit('message', messageObject); + socket.emit('message', messageObject); }); socket.on('subscribe', async (categories) => { for (let v of categories) { - // TODO: URGENT: Check if the category exists and if the user has access to it (access coming soon) - socket.join(v); + // TODO: When/if category permissions are added, check if the user has permissions for that category + const category = await Category.findById(v); + if (category && category.title && category._id) { + if (!socket.joinedCategories) socket.joinedCategories = {}; + socket.joinedCategories[v] = category.title; + socket.join(v); + } } }); }); diff --git a/app/app.html b/app/app.html index fb1fde9..cfbf90f 100755 --- a/app/app.html +++ b/app/app.html @@ -109,10 +109,10 @@ {{ button.text }} -
+
- {{ post.username }} + {{ post.author.username }} diff --git a/app/resources/js/app.js b/app/resources/js/app.js index fcf2a37..bd41cea 100755 --- a/app/resources/js/app.js +++ b/app/resources/js/app.js @@ -100,7 +100,12 @@ GatewayConnection.prototype.connect = function(token) { GatewayConnection.prototype.sendMessage = function(categoryId, content) { if (!this.isConnected) return; - this.socket.emit('message', { categoryId, content }); + this.socket.emit('message', { + category: { + _id: categoryId + }, + content + }); }; GatewayConnection.prototype.subscribeToCategoryChat = function(categoryId) { @@ -193,29 +198,7 @@ const app = new Vue({ } }, methods: { - navigateToAccountManager() { - window.location.href = `${window.location.origin}/auth.html`; - }, - snackbarButtonAction: function() { - this.showSnackbarNotification = false; - }, - snackbarButtonClick: function() { - this.snackbarButtonAction(); - }, - snackbarEditButton: function(buttonText="Ok", action) { - this.snackbarButtonText = buttonText; - this.snackbarButtonAction = action; - }, - resetSnackbarButton: function() { - this.snackbarButtonText = 'Ok'; - this.snackbarButtonAction = () => { - this.showSnackbarNotification = false; - }; - }, - notification: function(text) { - this.snackbarNotification = text; - this.showSnackbarNotification = true; - }, + // Gateway and chat performGatewayConnection: function() { // TODO: again, the thing im doing with the token is not very secure, since its being sent by the current user info endpoint and is also being send through query parameters this.gateway.onDisconnect = (e) => { @@ -223,21 +206,99 @@ const app = new Vue({ this.notification('ERROR: You have been disconnected from the gateway. Realtime features such as chat will not work and unexpected errors may occur.'); }; this.gateway.onConnect = () => { - this.gateway.socket.on('message', ({ username, categoryId, content, creatorId }) => { - this.showMessageNotification(categoryId, username, creatorId, content); - }) + this.gateway.socket.on('message', this.processMessage); }; this.gateway.connect(this.loggedInUser.token); }, - showMessageNotification: async function(categoryId, username, creatorId, content) { - if (!this.messages[categoryId]) this.messages[categoryId] = [] - this.messages[categoryId].push({ username, content, creatorId }); + processMessage: async function(messageObject) { + if (!this.messages[messageObject.category._id]) this.$set(this.messages, messageObject.category._id, []); + this.messages[messageObject.category._id].push(messageObject); - this.resetSnackbarButton(); - this.notification(`${username}: ${content}`); + if (messageObject.author.username !== this.loggedInUser.username && messageObject.category._id !== this.selection.category._id) { + this.resetSnackbarButton(); + this.notification(`${messageObject.author.username}: ${messageObject.content}`); + } }, - button: function(text, click) { - this.cardButtons.push({ text, click }); + openChatForCategory: async function(categoryId) { + this.gateway.subscribeToCategoryChat(categoryId); + + this.selection.category.isChatContext = true; + this.selection.category.browsing = true; + this.selection.category.title = 'Chat'; + this.selection.category._id = categoryId; + }, + + // Category and post browsing + browseCategories: async function() { + const res = await fetch(`${window.location.origin}/api/v1/content/category/list?count=50`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + credentials: 'include' + }); + + if (res.status === 200) { + const json = await res.json(); + + this.selection.category.title = 'categories'; + this.selection.category.browsing = true; + this.selection.category.isCategory = false; + this.selection.category.isChatContext = false; + this.selection.posts = []; + + this.cardButtons = []; + + this.button('Chat', (post) => { + if (post._id) { + this.openChatForCategory(post._id); + this.gateway.sendMessage(post._id, 'yoooo'); + } + }); + this.button('View', (post) => { + this.browse(post); + }); + + for (let i = 0; i < json.categories.length; i++) { + const v = json.categories[i]; + this.selection.posts.push({ title: v.title, body: '', _id: v._id, creator: v.creator }); + } + } else { + this.resetSnackbarButton(); + this.notification('Failed to fetch category list'); + } + }, + browse: async function(category) { + const { _id, title } = category; + + const res = await fetch(`${window.location.origin}/api/v1/content/category/${_id}/info`, { + method: 'GET', + headers: { + 'Accept': 'application/json', + }, + credentials: 'include' + }); + + if (res.status === 200) { + const json = await res.json(); + + this.selection.category.title = title; + this.selection.category._id = _id; + this.selection.category.browsing = true; + this.selection.category.isCategory = true; + this.selection.category.isChatContext = false; + this.selection.posts = []; + + this.cardButtons = []; + + for (let i = 0; i < json.category.posts.length; i++) { + const v = json.category.posts[i]; + this.selection.posts.push({ title: v.title, body: v.body, _id: v._id, creator: v.creator }); + } + } else { + this.resetSnackbarButton(); + this.notification('Failed to fetch category'); + } }, refresh: function() { if (this.selection.category.title === 'categories' && this.selection.category.isCategory === false) { @@ -246,6 +307,20 @@ const app = new Vue({ this.browse(this.selection.category); } }, + button: function(text, click) { + this.cardButtons.push({ text, click }); + }, + stopBrowsing: function() { + this.selection.category = { + title: '', + browsing: false, + _id: undefined, + isCategory: false, + isChatContext: false + }; + this.selection.posts = []; + this.cardButtons = []; + }, viewProfile: async function(id) { // TODO: this just returns the username for now const res = await fetch(`${window.location.origin}/api/v1/users/user/${id}/info`, { @@ -267,16 +342,8 @@ const app = new Vue({ this.notification('Failed to fetch user data'); } }, - stopBrowsing: function() { - this.selection.category = { - title: '', - browsing: false, - _id: undefined, - isCategory: false - }; - this.selection.posts = []; - this.cardButtons = []; - }, + + // Content creation showCreatePostDialog: function() { if (!this.selection.category.isCategory) { this.resetSnackbarButton(); @@ -373,84 +440,32 @@ const app = new Vue({ return; } }, - browse: async function(category) { - const { _id, title } = category; - const res = await fetch(`${window.location.origin}/api/v1/content/category/${_id}/info`, { - method: 'GET', - headers: { - 'Accept': 'application/json', - }, - credentials: 'include' - }); - - if (res.status === 200) { - const json = await res.json(); - - this.selection.category.title = title; - this.selection.category._id = _id; - this.selection.category.browsing = true; - this.selection.category.isCategory = true; - this.selection.category.isChatContext = false; - this.selection.posts = []; - - this.cardButtons = []; - - for (let i = 0; i < json.category.posts.length; i++) { - const v = json.category.posts[i]; - this.selection.posts.push({ title: v.title, body: v.body, _id: v._id, creator: v.creator }); - } - } else { - this.resetSnackbarButton(); - this.notification('Failed to fetch category'); - } + // Navigation + navigateToAccountManager() { + window.location.href = `${window.location.origin}/auth.html`; }, - openChatForCategory: async function(categoryId) { - this.gateway.subscribeToCategoryChat(categoryId); - - this.selection.category.isChatContext = true; - this.selection.category.browsing = true; - this.selection.category.title = 'Chat'; - this.selection.category._id = categoryId; + + // Snackbar + snackbarButtonAction: function() { + this.showSnackbarNotification = false; + }, + snackbarButtonClick: function() { + this.snackbarButtonAction(); + }, + snackbarEditButton: function(buttonText="Ok", action) { + this.snackbarButtonText = buttonText; + this.snackbarButtonAction = action; + }, + resetSnackbarButton: function() { + this.snackbarButtonText = 'Ok'; + this.snackbarButtonAction = () => { + this.showSnackbarNotification = false; + }; + }, + notification: function(text) { + this.snackbarNotification = text; + this.showSnackbarNotification = true; }, - browseCategories: async function() { - const res = await fetch(`${window.location.origin}/api/v1/content/category/list?count=50`, { - method: 'GET', - headers: { - 'Accept': 'application/json', - }, - credentials: 'include' - }); - - if (res.status === 200) { - const json = await res.json(); - - this.selection.category.title = 'categories'; - this.selection.category.browsing = true; - this.selection.category.isCategory = false; - this.selection.category.isChatContext = false; - this.selection.posts = []; - - this.cardButtons = []; - - this.button('Chat', (post) => { - if (post._id) { - this.openChatForCategory(post._id); - this.gateway.sendMessage(post._id, 'yoooo'); - } - }); - this.button('View', (post) => { - this.browse(post); - }); - - for (let i = 0; i < json.categories.length; i++) { - const v = json.categories[i]; - this.selection.posts.push({ title: v.title, body: '', _id: v._id, creator: v.creator }); - } - } else { - this.resetSnackbarButton(); - this.notification('Failed to fetch category list'); - } - } } }); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0615da5..fad6d82 100755 --- a/package-lock.json +++ b/package-lock.json @@ -1202,6 +1202,11 @@ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" }, + "uuid": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.1.tgz", + "integrity": "sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg==" + }, "validator": { "version": "13.1.1", "resolved": "https://registry.npmjs.org/validator/-/validator-13.1.1.tgz", diff --git a/package.json b/package.json index 6c3b309..8ba4638 100755 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "express-validator": "^6.6.1", "jsonwebtoken": "^8.5.1", "mongoose": "^5.10.0", - "socket.io": "^3.0.1" + "socket.io": "^3.0.1", + "uuid": "^8.3.1" } }