diff --git a/functions/handlers/post.js b/functions/handlers/post.js index f57dbb7..1ef941d 100644 --- a/functions/handlers/post.js +++ b/functions/handlers/post.js @@ -1,115 +1,543 @@ /* eslint-disable prefer-arrow-callback */ /* eslint-disable promise/always-return */ -const admin = require('firebase-admin'); +const { admin, db } = require("../util/admin"); + exports.putPost = (req, res) => { - const newPost = { - body: req.body.body, - userHandle: req.user.handle, - userImage: req.body.userImage, - userID: req.user.uid, - microBlogTitle: req.body.microBlogTitle, - createdAt: new Date().toISOString(), - likeCount: 0, - commentCount: 0, - microBlogTopics: req.body.microBlogTopics - }; + const newPost = { + body: req.body.body, + userHandle: req.user.handle, + userImage: req.body.userImage, + userID: req.user.uid, + microBlogTitle: req.body.microBlogTitle, + createdAt: new Date().toISOString(), + likeCount: 0, + commentCount: 0, + microBlogTopics: req.body.microBlogTopics, + quoteBody: null + }; - admin.firestore().collection('posts').add(newPost) - .then((doc) => { - doc.update({postId: doc.id}) - const resPost = newPost; - resPost.postId = doc.id; - return res.status(200).json(resPost); + admin + .firestore() + .collection("posts") + .add(newPost) + .then(doc => { + doc.update({ postId: doc.id }); + const resPost = newPost; + resPost.postId = doc.id; + return res.status(200).json(resPost); }) - .catch((err) => { - console.error(err); - return res.status(500).json({ error: 'something went wrong'}); + .catch(err => { + console.error(err); + return res.status(500).json({ error: "something went wrong" }); }); }; +exports.deletePost = (req, res) => { + let posts = db.collection("posts") + .where("userHandle", "==", req.user.handle) + .get() + .then((query) => { + query.forEach((snap) => { + snap.ref.delete(); + }); + return; + }) +}; + exports.getallPostsforUser = (req, res) => { - var post_query = admin.firestore().collection("posts");//.where("userHandle", "==", req.user.handle); - post_query.orderBy('createdAt', 'desc').get() + var post_query = admin + .firestore() + .collection("posts") + .where("userHandle", "==", req.user.handle); + + post_query + .get() .then(function(myPosts) { - let posts = []; - myPosts.forEach(function(doc) { - if(doc.data().userHandle === req.user.handle) { - posts.push(doc.data()); - } - }); - return res.status(200).json(posts); + let posts = []; + myPosts.forEach(function(doc) { + posts.push(doc.data()); + }); + posts.sort((a, b) => -a.createdAt.localeCompare(b.createdAt)); + return res.status(200).json(posts); }) + .then(function() { - res.status(200).send("Successfully retrieved all user's posts from database."); - return; + return res + .status(200) + .json("Successfully retrieved all user's posts from database."); }) .catch(function(err) { - res.status(500).send("Failed to retrieve user's posts from database.", err); + return res + .status(500) + .json({message: "Failed to retrieve user's posts from database.", error: err}); }); }; +exports.hidePost = (req, res) => { + /* db + .collection("posts") + .doc(${req.params.postId}) */ + const postId = req.body.postId; + db.doc(`/posts/${postId}`) + .update({ + hidden: true + }) + .then(() => { + return res.status(200).json({message: "ok"}); + }) + .catch((error) => { + return res.status(500).json(error); + }) +}; + exports.getallPosts = (req, res) => { - var post_query = admin.firestore().collection("posts"); - post_query.orderBy('createdAt', 'desc').get() - .then(function(allPosts) { - let posts = []; - allPosts.forEach(function(doc) { - posts.push(doc.data()); + let posts = []; + let users = {}; + + // Get all the posts + var postsPromise = new Promise((resolve, reject) => { + db.collection("posts") + .get() + .then(allPosts => { + allPosts.forEach(post => { + posts.push(post.data()); }); - return res.status(200).json(posts); + posts.sort((a, b) => -a.createdAt.localeCompare(b.createdAt)); + resolve(); + }) + .catch(error => { + reject(error); + }); + }); + + // Get all users + var usersPromise = new Promise((resolve, reject) => { + db.collection("users") + .get() + .then(allUsers => { + allUsers.forEach(user => { + users[user.data().handle] = user.data(); + }); + resolve(); + }) + .catch(error => { + reject(error); + }); + }); + + // Wait for the two promises + Promise.all([postsPromise, usersPromise]) + .then(() => { + let newPosts = []; + // Add the image url of the person who made the post to all of the post objects + posts.forEach(post => { + post.profileImage = users[post.userHandle].imageUrl + ? users[post.userHandle].imageUrl + : null; + newPosts.push(post); + }); + return res.status(200).json(newPosts); + }) + .catch(error => { + return res.status(500).json({ error }); + }); +}; + +exports.getAlert = (req, res) => { + var post_query = admin + .firestore() + .collection("posts") + .where("microBlogTitle", "==", "Alert"); + + post_query + .get() + .then(function(myPosts) { + let posts = []; + myPosts.forEach(function(doc) { + posts.push(doc.data()); + }); + posts.sort((a, b) => -a.createdAt.localeCompare(b.createdAt)); + return res.status(200).json(posts); }) .then(function() { - res.status(200).send("Successfully retrieved every post from database."); - return; + return res + .status(200) + .json("Successfully retrieved all user's posts from database."); }) .catch(function(err) { - res.status(500).send("Failed to retrieve posts from database.", err); + return res + .status(500) + .json("Failed to retrieve user's posts from database.", err); + }); +}; + +exports.getOtherUsersPosts = (req, res) => { + var post_query = admin + .firestore() + .collection("posts") + .where("userHandle", "==", req.body.handle); + + // post_query += admin + // .firestore() + // .collection("posts") + // .where("microBlogTitle", "==", "Alert").where("userHandle", "==", "Admin"); + + post_query + .get() + .then(function(myPosts) { + let posts = []; + myPosts.forEach(function(doc) { + posts.push(doc.data()); + }); + posts.sort((a, b) => -a.createdAt.localeCompare(b.createdAt)); + return res.status(200).json(posts); + }) + .then(function() { + return res + .status(200) + .json("Successfully retrieved all user's posts from database."); + }) + .catch(function(err) { + return res + .status(500) + .json("Failed to retrieve user's posts from database.", err); + }); +}; + +exports.quoteWithPost = (req, res) => { + let quoteData; + const quoteDoc = admin + .firestore() + .collection("quote") + .where("userHandle", "==", req.user.handle) + .where("quoteId", "==", req.params.postId) + .limit(1); + + const postDoc = db.doc(`/posts/${req.params.postId}`); + + postDoc + .get() + .then(doc => { + if (doc.exists) { + quoteData = doc.data(); + return quoteDoc.get(); + } else { + return res.status(404).json({ error: "Post not found" }); + } + }) + .then(data => { + if (data.empty) { + return admin + .firestore() + .collection("quote") + .add({ + quoteId: req.params.postId, + userHandle: req.user.handle, + quoteBody: req.body.quoteBody + }) + .then(() => { + const post = { + body: quoteData.body, + userHandle: req.user.handle, + quoteBody: req.body.quoteBody, + createdAt: new Date().toISOString(), + userImage: req.body.userImage, + likeCount: 0, + commentCount: 0, + userID: req.user.uid, + microBlogTitle: quoteData.microBlogTitle, + microBlogTopics: quoteData.microBlogTopics, + quoteId: req.params.postId + }; + return admin + .firestore() + .collection("posts") + .add(post) + .then(doc => { + doc.update({ postId: doc.id }); + const resPost = post; + resPost.postId = doc.id; + return res.status(200).json(resPost); + }); + }); + } else { + return res.status(400).json({ error: "Post has already been quoted." }); + } + }) + + .catch(err => { + return res.status(500).json({ error: err }); + }); +}; + +exports.quoteWithoutPost = (req, res) => { + let quoteData; + const quoteDoc = admin + .firestore() + .collection("quote") + .where("userHandle", "==", req.user.handle) + .where("quoteId", "==", req.params.postId) + .limit(1); + + const postDoc = db.doc(`/posts/${req.params.postId}`); + + postDoc + .get() + .then(doc => { + if (doc.exists) { + quoteData = doc.data(); + return quoteDoc.get(); + } else { + return res.status(404).json({ error: "Post not found" }); + } + }) + .then(data => { + if (data.empty) { + return admin + .firestore() + .collection("quote") + .add({ + quoteId: req.params.postId, + userHandle: req.user.handle, + quoteBody: null + }) + .then(() => { + const post = { + userHandle: req.user.handle, + body: quoteData.body, + quoteBody: null, + createdAt: new Date().toISOString(), + likeCount: 0, + commentCount: 0, + userID: req.user.uid, + userImage: req.body.userImage, + microBlogTitle: quoteData.microBlogTitle, + microBlogTopics: quoteData.microBlogTopics, + quoteId: req.params.postId + }; + return admin + .firestore() + .collection("posts") + .add(post) + .then(doc => { + doc.update({ postId: doc.id }); + const resPost = post; + resPost.postId = doc.id; + return res.status(200).json(resPost); + }); + }); + } else { + return res.status(400).json({ error: "Post has already been quoted." }); + } + }) + .catch(err => { + // return res.status(500).json({ error: "Something is wrong" }); + return res.status(500).json({ error: err }); + }); +}; + +exports.checkforLikePost = (req, res) => { + const likedPostDoc = admin + .firestore() + .collection("likes") + .where("userHandle", "==", req.user.handle) + .where("postId", "==", req.params.postId) + .limit(1); + let result; + + likedPostDoc + .get() + .then(data => { + if (data.empty) { + result = false; + return res.status(200).json(result); + } else { + result = true; + return res.status(200).json(result); + } + }) + .catch(err => { + console.log(err); + return res.status(500).json({ error: err }); + }); +}; + +exports.likePost = (req, res) => { + const postId = req.params.postId; + let likedPostDoc; + db.doc(`/users/${req.userData.handle}`) + .get() + .then(userDoc => { + let likes = userDoc.data().likes; + if (likes === undefined || likes === null) { + likes = []; + } + + if (likes.includes(postId)) { + return res + .status(400) + .json({ error: "This user has already liked this post" }); + } + + likes.push(postId); + + return userDoc.ref.update({ likes }); + }) + .then(() => { + return db.doc(`/posts/${postId}`).get(); + }) + .then(postDoc => { + let postData = postDoc.data(); + postData.likeCount++; + likedPostDoc = postData; + return postDoc.ref.update({ likeCount: postData.likeCount }); + }) + .then(() => { + return res.status(201).json(likedPostDoc); + }) + .catch(err => { + console.log(err); + return res.status(500).json({ error: err }); + }); + + // let postData; + // const likeDoc = admin.firestore().collection('likes').where('userHandle', '==', req.user.handle) + // .where('postId', '==', req.params.postId).limit(1); + + // const postDoc = db.doc(`/posts/${req.params.postId}`); + + // postDoc.get() + // .then((doc) => { + // if(doc.exists) { + // postData = doc.data(); + // return likeDoc.get(); + // } + // else + // { + // return res.status(404).json({error: 'Post not found'}); + // } + // }) + // .then((data) => { + // if (data.empty) { + // return admin.firestore().collection('likes').add({ + // postId : req.params.postId, + // userHandle: req.user.handle + + // }) + // .then(() => { + // postData.likeCount++; + // return postDoc.update({likeCount : postData.likeCount}) + // }) + // .then(() => { + // return res.status(200).json(postData); + // }) + // } + // }) + // .catch((err) => { + // return res.status(500).json({error: 'Something is wrong'}); + // }) +}; + +exports.unlikePost = (req, res) => { + const postId = req.params.postId; + let likedPostDoc; + db.doc(`/users/${req.userData.handle}`) + .get() + .then(userDoc => { + let likes = userDoc.data().likes; + if (likes === undefined || likes === null) { + likes = []; + } + + if (!likes.includes(postId)) { + return res + .status(400) + .json({ error: "This user hasn't liked this post yet" }); + } + + let i; + for (i = 0; i < likes.length; i++) { + if (likes[i] === postId) { + likes.splice(i, 1); + } + } + + return userDoc.ref.update({ likes }); + }) + .then(() => { + return db.doc(`/posts/${postId}`).get(); + }) + .then(postDoc => { + let postData = postDoc.data(); + postData.likeCount--; + likedPostDoc = postData; + return postDoc.ref.update({ likeCount: postData.likeCount }); + }) + .then(() => { + return res.status(201).json(likedPostDoc); + }) + .catch(err => { + console.log(err); + return res.status(500).json({ error: err }); + }); + + // let postData; + // const likeDoc = admin.firestore().collection('likes').where('userHandle', '==', req.user.handle) + // .where('postId', '==', req.params.postId).limit(1); + + // const postDoc = db.doc(`/posts/${req.params.postId}`); + + // postDoc.get() + // .then((doc) => { + // if(doc.exists) { + // postData = doc.data(); + // return likeDoc.get(); + // } + // else + // { + // return res.status(404).json({error: 'Post not found'}); + // } + // }) + // .then((data) => { + // return db + // .doc(`/likes/${data.docs[0].id}`) + // .delete() + // .then(() => { + // postData.likeCount--; + // return postDoc.update({ likeCount: postData.likeCount }); + // }) + // .then(() => { + // res.status(200).json(postData); + // }); + + // }) + // .catch((err) => { + // console.error(err); + // return res.status(500).json({error: 'Something is wrong'}); + // }) +}; + +exports.getLikes = (req, res) => { + db.doc(`/users/${req.userData.handle}`) + .get() + .then(doc => { + let likes = doc.data().likes; + if (likes === undefined || likes === null) { + likes = []; + } + return res.status(200).json({ likes }); + }) + .catch(err => { + console.log(err); + return res.status(500).json({ error: err }); }); }; exports.getFilteredPosts = (req, res) => { - admin.firestore().collection('posts').where('userHandle', '==', 'new user').where('microBlogTopics', '=='); -}; - -exports.getFollowedPosts = (req, res) => { - var followers_list = admin.firestore().collection("users").doc(req.user.handle).collection("followedUsers"); - var post_query = admin.firestore().collection("posts"); - - followers_list.get() - .then(function(allFollowers) { - var followers_likedTopics = new Map(); - - allFollowers.forEach(function(followers) { - followers_likedTopics.set(followers.data().handle, followers.data().followedTopics); - }); - - post_query.orderBy('createdAt', 'desc').get() - .then(function(allPosts) { - let posts = []; - allPosts.forEach(function(doc) { - if(doc.data().userHandle === req.user.handle) { - posts.push(doc.data()); - } - else if(followers_likedTopics.has(doc.data().userHandle)) { - doc.data().microBlogTopics.forEach(function(topic) { - if(followers_likedTopics.get(doc.data().userHandle).includes(topic)) { - posts.push(doc.data()); - } - }); - } - }); - return res.status(200).json(posts); - }) - .catch(function(err) { - res.status(500).send("Failed to retrieve any posts.", err); - }); - }) - .then(function() { - //res.status(200).send("Successfully retrieved all interesting posts from followed users."); - return; - }) - .catch(function(err) { - res.status(500).send("Failed to retrieve any posts.", err); - }); + admin + .firestore() + .collection("posts") + .where("userHandle", "==", "new user") + .where("microBlogTopics", "=="); }; diff --git a/functions/handlers/topic.js b/functions/handlers/topic.js index 88e014e..3a81810 100644 --- a/functions/handlers/topic.js +++ b/functions/handlers/topic.js @@ -26,6 +26,41 @@ exports.putTopic = (req, res) => { }); }; +exports.putNewTopic = (req, res) => { + let new_following = []; + let userRef = db.doc(`/users/${req.userData.handle}`); + userRef + .get() + .then(doc => { + let topics = []; + new_following = doc.data().following; + // new_following.push(req.body.following); + new_following.forEach(follow => { + if (follow.handle === req.body.handle) { + // topics = follow.topics; + follow.topics.push(req.body.topic); + } + }); + // return res.status(201).json({ new_following }); + + // add stuff + userRef + .set({ following: new_following }, { merge: true }) + .then(doc => { + return res + .status(201) + .json({ message: `Following ${req.body.topic}` }); + }) + .catch(err => { + return res.status(500).json({ err }); + }); + return res.status(200).json({ message: "OK" }); + }) + .catch(err => { + return res.status(500).json({ err }); + }); +}; + exports.getAllTopics = (req, res) => { admin .firestore() diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 7b6bfef..6a06568 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -1,4 +1,6 @@ /* eslint-disable promise/catch-or-return */ +/* eslint-disable promise/always-return */ + const { admin, db } = require("../util/admin"); const config = require("../util/config"); const { validateUpdateProfileInfo } = require("../util/validator"); @@ -161,11 +163,13 @@ exports.login = (req, res) => { return; }) .catch(function(err) { - if (!doc.exists) { - return res - .status(403) - .json({ general: "Invalid credentials. Please try again." }); - } + // FIX: doc variable is out of scope + // if (!doc.exists) { + // return res + // .status(403) + // .json({ general: "Invalid credentials. Please try again." }); + // } + console.log(err); return res.status(500).send(err); }); } @@ -209,7 +213,7 @@ exports.deleteUser = (req, res) => { let errors = {}; function thenFunction(data) { - console.log(`${data} data for ${req.userData.handle} has been deleted.`); + console.log(`${data} for ${req.userData.handle} has been deleted.`); } function catchFunction(data, err) { @@ -217,14 +221,138 @@ exports.deleteUser = (req, res) => { errors[data] = err; } + function deleteDirectMessages() { + return new Promise((resolve, reject) => { + const deleteUsername = req.userData.handle; + db.doc(`/users/${deleteUsername}`) + .get() + .then(deleteUserDocSnap => { + const dms = deleteUserDocSnap.data().dms; + const dmRecipients = deleteUserDocSnap.data().dmRecipients; + + if (!dms) { + resolve(); + return; + } + + // Iterate over the list of users who this person has DM'd + let otherUsersPromises = []; + + // Resolve if they don't have a dmRecipients list + if ( + dmRecipients === undefined || + dmRecipients === null || + dmRecipients.length === 0 + ) { + resolve(); + return; + } + dmRecipients.forEach(dmRecipient => { + otherUsersPromises.push( + // Get each users data + db + .doc(`/users/${dmRecipient}`) + .get() + .then(otherUserDocSnap => { + // Get the index of deleteUsername so that we can remove the dangling + // reference to the DM document + let otherUserDMRecipients = otherUserDocSnap.data() + .dmRecipients; + let otherUserDMs = otherUserDocSnap.data().dms; + let index = -1; + otherUserDMRecipients.forEach((dmRecip, i) => { + if (dmRecip === deleteUsername) { + index = i; + } + }); + + if (index !== -1) { + // Remove deleteUsername from their dmRecipients list + otherUserDMRecipients.splice(index, 1); + + // Remove the DM channel with deleteUsername + otherUserDMs.splice(index, 1); + + // Update the users data + return otherUserDocSnap.ref.update({ + dmRecipients: otherUserDMRecipients, + dms: otherUserDMs + }); + } + }) + ); + }); + + // Wait for the removal of DM data stored on other users to be deleted + Promise.all(otherUsersPromises) + .then(() => { + // Iterate through DM references and delete them from the dm collection + let dmRefsPromises = []; + dms.forEach(dmRef => { + // Create a delete queue + let batch = db.batch(); + dmRefsPromises.push( + // Add the messages to the delete queue + db + .collection(`/dm/${dmRef.id}/messages`) + .listDocuments() + .then(docs => { + console.log("second"); + console.log(docs); + docs.map(doc => { + batch.delete(doc); + }); + + // Add the doc that the DM is stored in to the delete queue + batch.delete(dmRef); + + // Commit the writes + return batch.commit(); + }) + ); + }); + + return Promise.all(dmRefsPromises); + }) + .then(() => { + resolve(); + return; + }) + .catch(err => { + console.log("error " + err); + reject(err); + return; + }); + }) + .catch(err => { + console.log(err); + return res.status(500).json({ error: err }); + }); + }); + } + // Deletes user from authentication let auth = admin.auth().deleteUser(userId); // Deletes database data - let data = db - .collection("users") - .doc(`${req.user.handle}`) - .delete(); + let data = new Promise((resolve, reject) => { + deleteDirectMessages() + .then(() => { + return db + .collection("users") + .doc(`${req.user.handle}`) + .delete(); + }) + .then(() => { + resolve(); + return; + }) + .catch(err => { + console.log(err); + reject(err); + return; + }); + }); // Deletes any custom profile image let image; @@ -298,7 +426,7 @@ exports.updateProfileInfo = (req, res) => { // Update the database entry for this user db.collection("users") .doc(req.user.handle) - .set(profileData, { merge: true }) + .set(profileData) .then(() => { console.log(`${req.user.handle}'s profile info has been updated.`); return res.status(201).json({ @@ -331,6 +459,26 @@ exports.getUserDetails = (req, res) => { }); }; +exports.getAllHandles = (req, res) => { + var user_query = admin.firestore().collection("users"); + user_query + .get() + .then(allUsers => { + let users = []; + allUsers.forEach(user => { + users.push(user.data().handle); + }); + return res.status(200).json(users); + }) + .catch(err => { + return res.status(500).json({ + message: "Failed to retrieve posts from database.", + error: err + }); + }); +}; + +// Returns all data stored for a user exports.getAuthenticatedUser = (req, res) => { let credentials = {}; db.doc(`/users/${req.user.handle}`) @@ -414,6 +562,614 @@ exports.unverifyUser = (req, res) => { return res.status(500).json({ error: err.code }); }); }; + +// Returns all the DMs that the user is currently participating in +exports.getDirectMessages = (req, res) => { + /* Return value + * data: [DMs] + * dm : { + * dmId: str + * messages: [msgs] + * msg: { + * author: str + * createdAt: ISOString + * message: str + * messageId: str + * } + * recipient: str + * hasDirectMessagesEnabled: bool + * recentMessage: str + * recentMessageTimestamp: ISOString + * } + */ + + // Returns all the messages in a dm documentSnapshot + function getMessages(dm) { + let promise = new Promise((resolve, reject) => { + let messagesCollection = dm.collection("messages"); + + // If the messagesCollection is missing, that means that there aren't any messages + if (messagesCollection === null || messagesCollection === undefined) { + return; + } + + let msgs = []; + let promises = []; + + // Get all of the messages in the DM + messagesCollection.get().then(dmQuerySnap => { + dmQuerySnap.forEach(dmQueryDocSnap => { + promises.push( + dmQueryDocSnap.ref.get().then(messageData => { + msgs.push(messageData.data()); + return; + }) + ); + }); + + let waitPromise = Promise.all(promises); + waitPromise.then(() => { + // Sort the messages in reverse order by date + // Newest should be at the bottom, because that's how they will be displayed on the front-end + msgs.sort((a, b) => { + return b.createdAt > a.createdAt + ? -1 + : b.createdAt < a.createdAt + ? 1 + : 0; + }); + resolve(msgs); + }); + }); + }); + return promise; + } + + const dms = req.userData.dms; + const dmRecipients = req.userData.dmRecipients; + + // Return null if this user has no DMs + if (dms === undefined || dms === null || dms.length === 0) + return res.status(200).json({ data: null }); + + let dmsData = []; + let dmPromises = []; + + dms.forEach(dm => { + let dmData = {}; + // Make a new promise for each DM document + dmPromises.push( + new Promise((resolve, reject) => { + dm.get() // DM document reference + .then(doc => { + let docData = doc.data(); + + // Recipient is the person you are messaging + docData.authors[0] === req.userData.handle + ? (dmData.recipient = docData.authors[1]) + : (dmData.recipient = docData.authors[0]); + + // Save the createdAt time + dmData.createdAt = docData.createdAt; + + // Get all the messages from this dm document + getMessages(dm).then(msgs => { + dmData.messages = msgs; + dmData.recentMessage = + msgs.length !== 0 ? msgs[msgs.length - 1].message : null; + dmData.recentMessageTimestamp = + msgs.length !== 0 ? msgs[msgs.length - 1].createdAt : null; + dmData.dmId = doc.id; + resolve(dmData); + }); + }) + .catch(err => { + console.err(err); + return res.status(400).json({ + error: { + message: + "An error occurred when reading the DM document reference", + error: err + } + }); + }); + }).then(dmData => { + dmsData.push(dmData); + }) + ); + }); + + // Get all the data from the users to get the data on whether they have DMs enabled or not + let userPromises = []; + dmRecipients.forEach(recipient => { + userPromises.push(db.doc(`/users/${recipient}`).get()); + }); + + // Wait for all DM document promises to resolve before returning data + Promise.all(dmPromises) + .then(() => { + return Promise.all(userPromises); + }) + .then(userData => { + // Sort the DMs so that the ones with the newest messages are at the top + dmsData.sort((a, b) => { + if ( + a.recentMessageTimestamp === null && + b.recentMessageTimestamp === null + ) { + if (b.createdAt < a.createdAt) { + return -1; + } else if (b.createdAt > a.createdAt) { + return 1; + } else { + return 0; + } + } else if (a.recentMessageTimestamp === null) { + return 1; + } else if (b.recentMessageTimestamp === null) { + return -1; + } else if (b.recentMessageTimestamp < a.recentMessageTimestamp) { + return -1; + } else if (b.recentMessageTimestamp > a.recentMessageTimestamp) { + return 1; + } else { + return 0; + } + }); + + dmsData.forEach(dm => { + dm.hasDirectMessagesEnabled = + userData + .find(user => { + if (dm.recipient === user.data().handle) { + return true; + } else { + return false; + } + }) + .data().dmEnabled === false + ? false + : true; + }); + return res.status(200).json({ data: dmsData }); + }) + .catch(err => { + return res.status(500).json({ + error: { + message: "An error occurred while sorting", + error: err + } + }); + }); +}; + +// Toggles direct messages on or off depending on the requese +/* Request Parameters + * enable: bool + */ +exports.toggleDirectMessages = (req, res) => { + const enable = req.body.enable; + const user = req.userData.handle; + db.doc(`/users/${user}`) + .update({ dmEnabled: enable }) + .then(() => { + return res.status(201).json({ message: "Success" }); + }) + .catch(err => { + return res.status(500).json({ error: err }); + }); +}; + +// Returns a promise that resolves if user has DMs enabled +// and rejects if there is an error or DMs are disabled +isDirectMessageEnabled = username => { + return new Promise((resolve, reject) => { + let result = {}; + result.code = null; + result.message = null; + if (username === null || username === undefined || username === "") { + result.code = 400; + result.message = + "No user was sent in the request. The request should have a non-empty 'user' key."; + reject(result); + } + + db.doc(`/users/${username}`) + .get() + .then(doc => { + if (doc.exists) { + // console.log(doc.data()) + if ( + doc.data().dmEnabled === true || + doc.data().dmEnabled === null || + doc.data().dmEnabled === undefined + ) { + // Assume DMs are enabled if they don't have a dmEnabled key + resolve(result); + } else { + result.code = 400; + result.message = `${username} has DMs disabled`; + reject(result); + } + } else { + console.log(`${username} is not in the database`); + result.code = 400; + result.message = `${username} is not in the database`; + reject(result); + } + }) + .catch(err => { + console.log("HI"); + console.error(err); + result.code = 500; + result.message = err; + reject(result); + }); + }); +}; + +// Returns a promise that resolves if the data in the DM is valid and +// rejects if there are any error. Errors are returned in the promise +verifyDirectMessageIntegrity = dmRef => { + return new Promise((resolve, reject) => { + resolve("Not implemented yet"); + }); +}; + +// Checks if there are any DM channels open with userB on userA's side +oneWayCheck = (userA, userB) => { + return new Promise((resolve, reject) => { + db.doc(`/users/${userA}`) + .get() + .then(userASnapshot => { + const dmList = userASnapshot.data().dms; + const dmRecipients = userASnapshot.data().dmRecipients; + + if ( + dmList === null || + dmList === undefined || + dmRecipients === null || + dmRecipients === undefined + ) { + // They don't have any DMs yet + console.log("No DMs array"); + userASnapshot.ref + .set({ dms: [], dmRecipients: [] }, { merge: true }) + .then(() => { + resolve(); + }); + } else if (dmList.length === 0) { + // Their DMs are empty + console.log("DMs array is empty"); + resolve(); + } else { + // let dmDocs = []; + // let forEachPromises = []; + // dmList.forEach((dmRef) => { + // forEachPromises.push( + // dmRef.get() + // // .then((dmDoc) => { + // // TODO: Figure out why dmDoc.exists() isn't working + // // Make sure all of the docs exist and none of the references + // // are broken + // // if (dmDoc.exists()) { + // // dmDocs.push(dmDoc); + // // } else { + // // console.log(`DM reference /dm/${dmDoc.id} is invalid`); + // // reject(`DM reference /dm/${dmDoc.id} is invalid`); + // // } + // // }) + // ) + // }) + + dmRecipients.forEach(dmRecipient => { + if (dmRecipient === userB) { + console.log(`You already have a DM with ${userB}`); + // reject(new Error(`You already have a DM with ${userB}`)); + reject({ + code: 400, + message: `You already have a DM with that user` + }); + return; + } + }); + + resolve(); + + // Promise.all(forEachPromises) + // .then((dmDocs) => { + // // Check if any of the DMs have for userA have userA and userB as the authors. + // // This would mean that they already have a DM channel + // dmDocs.forEach((dmDoc) => { + // // Checking if any of the authors key in any of their DMs are missing + // let authors = dmDoc.data().authors; + + // // if (authors[0] === "keanureeves") { + // // console.log("it is") + // // resolve(); + // // } else { + // // console.log("it is not") + // // reject("not my keanu"); + // // } + // // if (authors === null || authors === undefined || authors.length !== 2) { + // // // console.log(`The authors key in /dm/${dmDoc.id} is undefined or missing values`); + // // // reject(`The authors key in /dm/${dmDoc.id} is undefined or missing values`); + // // console.log('a') + // // reject("a") + // // } else if ((authors[0] === userA && authors[1] === userB) || (authors[1] === userA && authors[0] === userB)) { + // // // console.log(`${userA} already has a DM channel between ${userA} and ${userB}`); + // // // reject(`${userA} already has a DM channel between ${userA} and ${userB}`); + // // console.log('b') + // // reject('b') + // // } else { + // // // BUG: For some reason the promise.all is resolving even though there are multiple rejects + // // // and only one resolve + // // console.log("c"); + // // resolve(); + // // } + // // console.log(authors) + // // console.log([userA, userB]) + // if (authors[0] === null || authors === undefined || authors.length !== 2) { + // console.log('a'); + // reject('a'); + // } else if (authors[0] === userA && authors[1] === userB) { + // console.log("b"); + // reject('b'); + // } else { + // console.log('c'); + // resolve(); + // } + // }) + // }) + } + }); + }); +}; + +// Returns a promise that resolves if there is not already a DM channel +// between the creator and recipient usernames. It rejects if one already +// exists or there is an error. +checkNoDirectMessageExists = (creator, recipient) => { + return new Promise((resolve, reject) => { + let creatorPromise = oneWayCheck(creator, recipient); + let recipientPromise = oneWayCheck(recipient, creator); + let temp_array = []; + temp_array.push(creatorPromise); + temp_array.push(recipientPromise); + + Promise.all(temp_array) + .then(() => { + resolve(); + }) + .catch(err => { + reject(err); + }); + }); +}; + +addDirectMessageToUser = (username, recipient, dmRef) => { + return new Promise((resolve, reject) => { + db.doc(`/users/${username}`) + .get() + .then(docSnap => { + let dmList = docSnap.data().dms; + let dmRecipients = docSnap.data().dmRecipients; + dmList.push(dmRef); + dmRecipients.push(recipient); + return db + .doc(`/users/${username}`) + .update({ dms: dmList, dmRecipients }); + }) + .then(() => { + resolve(); + }) + .catch(err => { + reject(err); + }); + }); +}; + +// Sends a DM from the caller to the requested DM document +/* Request Parameters + * message: str + * user: str + */ +exports.sendDirectMessage = (req, res) => { + // TODO: add error checking for if message or user is null + const creator = req.userData.handle; + const recipient = req.body.user; + const message = req.body.message; + + const newMessage = { + author: creator, + createdAt: new Date().toISOString(), + message, + messageId: null + }; + + db.doc(`/users/${recipient}`) + .get() + .then(recipDoc => { + // Return if the other user has DM's disabled + if ( + recipDoc.data().dmEnabled === false && + recipDoc.data().dmEnabled !== null && + recipDoc.data().dmEnabled !== undefined + ) { + return res.status(400).json({ error: "This user has DMs disabled" }); + } + }); + + db.doc(`/users/${creator}`) + .get() + .then(userDoc => { + let dmList = userDoc.data().dms; + + // Return if the creator doesn't have any DMs. + // This means they have not created a DM's channel yet + if (dmList === null || dmList === undefined) { + return res + .status(400) + .json({ + error: `There is no DM channel between ${creator} and ${recipient}. Use /api/dms/new.` + }); + } + + let dmRefPromises = []; + dmList.forEach(dmRef => { + dmRefPromises.push( + new Promise((resolve, reject) => { + dmRef + .get() + .then(dmDoc => { + let authors = dmDoc.data().authors; + if ( + (authors[0] === creator && authors[1] === recipient) || + (authors[1] === creator && authors[0] === recipient) + ) { + resolve({ correct: true, dmRef }); + } else { + resolve({ correct: false, dmRef }); + } + }) + .catch(err => { + reject(err); + }); + }) + ); + }); + + return Promise.all(dmRefPromises); + }) + .then(results => { + let correctDMRef = null; + results.forEach(result => { + if (result.correct) { + correctDMRef = result.dmRef; + } + }); + + if (correctDMRef === null) { + console.log( + `There is no DM channel between ${creator} and ${recipient}. Use /api/dms/new.` + ); + return res.status(400).json({ + error: `There is no DM channel between ${creator} and ${recipient}. Use /api/dms/new.` + }); + } + + return db.collection(`/dm/${correctDMRef.id}/messages`).add(newMessage); + }) + .then(newMsgRef => { + return newMsgRef.update({ messageId: newMsgRef.id }, { merge: true }); + }) + .then(() => { + return res.status(200).json({ message: "OK" }); + }) + .catch(err => { + console.log(err); + return res.status(500).json({ error: err }); + }); +}; + +// Creates a DM between the caller and the user in the request +/* Request Parameters + * user: str + */ +exports.createDirectMessage = (req, res) => { + const creator = req.userData.handle; + const recipient = req.body.user; + + // Check if they are DMing themselves + if (creator === recipient) + return res.status(400).json({ error: "You can't DM yourself" }); + + // Check if this user has DMs enabled + let creatorEnabled = isDirectMessageEnabled(creator); + + // Check if the requested user has DMs enabled + let recipientEnabled = isDirectMessageEnabled(recipient); + + // Make sure that they don't already have a DM channel + let noDMExists = checkNoDirectMessageExists(creator, recipient); + + let dataValidations = [creatorEnabled, recipientEnabled, noDMExists]; + + Promise.all(dataValidations) + .then(() => { + // Create a new DM document + return db.collection("dm").add({}); + }) + .then(dmDocRef => { + // Fill it with some data. + // Note that there isn't a messages collection by default. + let dmData = { + dmId: dmDocRef.id, + authors: [creator, recipient], + createdAt: new Date().toISOString() + }; + + // Update DM document + let dmDocPromise = dmDocRef.set(dmData); + + // Add the DM reference to the creator + let updateCreatorPromise = addDirectMessageToUser( + creator, + recipient, + dmDocRef + ); + + // Add the DM reference to the recipient + let updateRecipientPromise = addDirectMessageToUser( + recipient, + creator, + dmDocRef + ); + + // Wait for all promises + return Promise.all([ + dmDocPromise, + updateCreatorPromise, + updateRecipientPromise + ]); + }) + .then(() => { + return res.status(201).json({ message: "Success!" }); + }) + .catch(err => { + console.log(err); + + if (err.code && err.message && err.code > 0) { + // Specific error that I've created + return res.status(err.code).json({ error: err.message }); + } else { + // Generic or firebase error + return res.status(500).json({ error: err }); + } + }); +}; + +// Checks if the requested user has DMs enable or not +/* Request Parameters + * user: str + */ +exports.checkDirectMessagesEnabled = (req, res) => { + isDirectMessageEnabled(req.body.user) + .then(() => { + return res.status(200).json({ enabled: true }); + }) + .catch(result => { + console.log(result); + if (result.code === 200) { + // DMs are disabled + return res.status(200).json({ enabled: false }); + } else { + // Some other error occured + return res.status(result.code).json({ err: result.message }); + } + }); +}; + exports.getUserHandles = (req, res) => { db.doc(`/users/${req.body.userHandle}`) .get() @@ -436,7 +1192,13 @@ exports.addSubscription = (req, res) => { let userRef = db.doc(`/users/${req.userData.handle}`); userRef.get().then(doc => { new_following = doc.data().following; - new_following.push(req.body.following); + const struct = { + handle: req.body.following, + topics: ["Admin"] + }; + new_following + ? new_following.push(struct) + : (new_following = req.body.following); // add stuff userRef @@ -449,8 +1211,11 @@ exports.addSubscription = (req, res) => { .catch(err => { return res.status(500).json({ err }); }); - return res.status(200).json({ error: "Follow success!" }); - }); + // return res.status(200).json({ message: "ok" }); + }) + .catch((error) => { + return res.status(400).json({message: "That user doesn't exist", error}); + }) }; exports.getSubs = (req, res) => { @@ -466,6 +1231,142 @@ exports.getSubs = (req, res) => { }); }; +// Uploads a profile image +exports.uploadProfileImage = (req, res) => { + const BusBoy = require("busboy"); + const path = require("path"); + const os = require("os"); + const fs = require("fs"); + + const busboy = new BusBoy({ headers: req.headers }); + + let imageFileName; + let imageToBeUploaded = {}; + let oldImageFileName = req.userData.imageUrl + ? req.userData.imageUrl.split("/o/")[1].split("?alt")[0] + : null; + // console.log(`old file: ${oldImageFileName}`); + + busboy.on("file", (fieldname, file, filename, encoding, mimetype) => { + if (mimetype !== "image/jpeg" && mimetype !== "image/png") { + return res.status(400).json({ error: "Wrong filetype submitted" }); + } + // console.log(fieldname); + // console.log(filename); + // console.log(mimetype); + const imageExtension = filename.split(".")[filename.split(".").length - 1]; // Get the image file extension + imageFileName = `${Math.round( + Math.random() * 100000000000 + )}.${imageExtension}`; // Get a random filename + const filepath = path.join(os.tmpdir(), imageFileName); + imageToBeUploaded = { filepath, mimetype }; + file.pipe(fs.createWriteStream(filepath)); + }); + busboy.on("finish", () => { + // Save the file to the storage bucket + admin + .storage() + .bucket(config.storageBucket) + .upload(imageToBeUploaded.filepath, { + resumable: false, + metadata: { + metadata: { + contentType: imageToBeUploaded.mimetype + } + } + }) + .then(() => { + // Add the new URL to the user's profile + const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${imageFileName}?alt=media`; + return db.doc(`/users/${req.user.handle}`).update({ imageUrl }); + }) + .then(() => { + // Delete their old image if they have one + if (oldImageFileName !== null && oldImageFileName !== "no-img.png") { + admin + .storage() + .bucket(config.storageBucket) + .file(oldImageFileName) + .delete() + .then(() => { + return res + .status(201) + .json({ message: "Image uploaded successfully1" }); + }) + .catch(err => { + console.log(err); + return res + .status(201) + .json({ message: "Image uploaded successfully2" }); + }); + // return res.status(201).json({ message: "Image uploaded successfully"}); + } else { + return res + .status(201) + .json({ message: "Image uploaded successfully3" }); + } + }) + .catch(err => { + console.error(err); + return res.status(500).json({ error: err.code }); + }); + }); + busboy.end(req.rawBody); + + // const BusBoy = require('busboy'); + // const path = require('path'); + // const os = require('os'); + // const fs = require('fs'); + + // const busboy = new BusBoy({ headers: req.headers }); + + // let imageToBeUploaded = {}; + // let imageFileName; + + // busboy.on('file', (fieldname, file, filename, encoding, mimetype) => { + // // console.log(fieldname, file, filename, encoding, mimetype); + // if (mimetype !== 'image/jpeg' && mimetype !== 'image/png') { + // return res.status(400).json({ error: 'Wrong file type submitted' }); + // } + // // my.image.png => ['my', 'image', 'png'] + // const imageExtension = filename.split('.')[filename.split('.').length - 1]; + // // 32756238461724837.png + // imageFileName = `${Math.round( + // Math.random() * 1000000000000 + // ).toString()}.${imageExtension}`; + // const filepath = path.join(os.tmpdir(), imageFileName); + // imageToBeUploaded = { filepath, mimetype }; + // file.pipe(fs.createWriteStream(filepath)); + // }); + // busboy.on('finish', () => { + // admin + // .storage() + // .bucket(config.storageBucket) + // .upload(imageToBeUploaded.filepath, { + // resumable: false, + // metadata: { + // metadata: { + // contentType: imageToBeUploaded.mimetype + // } + // } + // }) + // .then(() => { + // const imageUrl = `https://firebasestorage.googleapis.com/v0/b/${ + // config.storageBucket + // }/o/${imageFileName}?alt=media`; + // return db.doc(`/users/${req.user.handle}`).update({ imageUrl }); + // }) + // .then(() => { + // return res.json({ message: 'image uploaded successfully' }); + // }) + // .catch((err) => { + // console.error(err); + // return res.status(500).json({ error: 'something went wrong' }); + // }); + // }); + // busboy.end(req.rawBody); +}; + exports.removeSub = (req, res) => { let new_following = []; let userRef = db.doc(`/users/${req.userData.handle}`); @@ -473,7 +1374,7 @@ exports.removeSub = (req, res) => { new_following = doc.data().following; // remove username from array new_following.forEach(function(follower, index) { - if (follower === `${req.body.unfollow}`) { + if (follower.handle === `${req.body.unfollow}`) { new_following.splice(index, 1); } }); @@ -489,6 +1390,7 @@ exports.removeSub = (req, res) => { .catch(err => { return res.status(500).json({ err }); }); - return res.status(500).json({ error: "shouldn't execute" }); + + return res.status(200).json({ message: "ok" }); }); }; diff --git a/functions/index.js b/functions/index.js index c3f5947..5306ae7 100644 --- a/functions/index.js +++ b/functions/index.js @@ -11,12 +11,19 @@ app.use(cors()); *------------------------------------------------------------------*/ const { getAuthenticatedUser, + getDirectMessages, + sendDirectMessage, + createDirectMessage, + checkDirectMessagesEnabled, + toggleDirectMessages, + getAllHandles, getUserDetails, getProfileInfo, login, signup, deleteUser, updateProfileInfo, + uploadProfileImage, verifyUser, unverifyUser, getUserHandles, @@ -37,16 +44,42 @@ app.post("/login", login); //Deletes user account app.delete("/delete", fbAuth, deleteUser); +// Returns all direct messages that the user is participating in +app.get("/dms", fbAuth, getDirectMessages); + +// Send a message in a DM from one user to another +app.post("/dms/send", fbAuth, sendDirectMessage); + +// Create a new DM between two users +app.post("/dms/new", fbAuth, createDirectMessage); + +// Checks if the user provided has DMs enabled or not +app.post("/dms/enabled", checkDirectMessagesEnabled); + +// Used to toggle DMs on or off for the current user +app.post("/dms/toggle", fbAuth, toggleDirectMessages); + +app.get("/getUser", fbAuth, getUserDetails); + app.post("/getUserDetails", fbAuth, getUserDetails); +// Returns a list of all usernames +// Used for searching +app.get("/getAllHandles", fbAuth, getAllHandles); + // Returns all profile data of the currently logged in user app.get("/getProfileInfo", fbAuth, getProfileInfo); // Updates the currently logged in user's profile information app.post("/updateProfileInfo", fbAuth, updateProfileInfo); +// Returns all user data for the logged in user. +// Used when setting the state in Redux. app.get("/user", fbAuth, getAuthenticatedUser); +// Uploads a profile image +app.post("/user/image", fbAuth, uploadProfileImage); + // Verifies the user sent to the request // Must be run by the Admin user app.post("/verifyUser", fbAuth, verifyUser); @@ -70,25 +103,54 @@ app.post("/removeSub", fbAuth, removeSub); /*------------------------------------------------------------------* * handlers/post.js * *------------------------------------------------------------------*/ -const { getallPostsforUser, getallPosts, getFollowedPosts, putPost } = require("./handlers/post"); + + +const { + getallPostsforUser, + getallPosts, + putPost, + hidePost, + likePost, + unlikePost, + getLikes, + quoteWithPost, + quoteWithoutPost, + checkforLikePost, + getOtherUsersPosts, + getAlert +} = require("./handlers/post"); app.get("/getallPostsforUser", fbAuth, getallPostsforUser); app.get("/getallPosts", getallPosts); -app.get("/getFollowedPosts", fbAuth, getFollowedPosts); +//Hides Post +app.post("/hidePost", fbAuth, hidePost); // Adds one post to the database app.post("/putPost", fbAuth, putPost); +app.get("/likes", fbAuth, getLikes); +app.get("/like/:postId", fbAuth, likePost); +app.get("/unlike/:postId", fbAuth, unlikePost); +app.get("/checkforLikePost/:postId", fbAuth, checkforLikePost); + +app.post("/quoteWithPost/:postId", fbAuth, quoteWithPost); +app.post("/quoteWithoutPost/:postId", fbAuth, quoteWithoutPost); + +app.post("/getOtherUsersPosts", fbAuth, getOtherUsersPosts); + +app.get("/getAlert", fbAuth, getAlert); + /*------------------------------------------------------------------* - * handlers/topic.js * + * handlers/topic.js * *------------------------------------------------------------------*/ const { putTopic, getAllTopics, deleteTopic, - getUserTopics + getUserTopics, + putNewTopic } = require("./handlers/topic"); // add topic to database @@ -103,4 +165,6 @@ app.post("/deleteTopic", fbAuth, deleteTopic); // get topic for this user app.post("/getUserTopics", fbAuth, getUserTopics); +app.post("/putNewTopic", fbAuth, putNewTopic); + exports.api = functions.https.onRequest(app); diff --git a/functions/package.json b/functions/package.json index 29b3e89..04a805c 100644 --- a/functions/package.json +++ b/functions/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "axios": "^0.19.0", + "busboy": "^0.3.1", "firebase": "^6.6.2", "firebase-admin": "^8.6.0", "firebase-functions": "^3.1.0", diff --git a/functions/util/fbAuth.js b/functions/util/fbAuth.js index 35253e7..d440bb9 100644 --- a/functions/util/fbAuth.js +++ b/functions/util/fbAuth.js @@ -4,12 +4,12 @@ const { admin, db } = require('./admin'); // The function will only execute if the user is logged in, or rather, they have // a valid token module.exports = (req, res, next) => { - console.log(req); - console.log(req.body); - console.log(req.headers); - console.log(req.headers.authorization); - console.log(JSON.stringify(req.body)); - console.log(JSON.stringify(req.header)); + // console.log(req); + // console.log(req.body); + // console.log(req.headers); + // console.log(req.headers.authorization); + // console.log(JSON.stringify(req.body)); + // console.log(JSON.stringify(req.header)); let idToken; diff --git a/twistter-frontend/package-lock.json b/twistter-frontend/package-lock.json index 385d8c1..9529c36 100644 --- a/twistter-frontend/package-lock.json +++ b/twistter-frontend/package-lock.json @@ -3097,6 +3097,11 @@ "merge": "^1.2.0" } }, + "exenv": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", + "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" + }, "exit-hook": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", @@ -3962,6 +3967,11 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "fuse.js": { + "version": "3.4.6", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.4.6.tgz", + "integrity": "sha512-H6aJY4UpLFwxj1+5nAvufom5b2BT2v45P1MkPvdGIK8fWjQx/7o6tTT1+ALV0yawQvbmvCF0ufl2et8eJ7v7Cg==" + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -7172,6 +7182,22 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.9.0.tgz", "integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==" }, + "react-lifecycles-compat": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", + "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" + }, + "react-modal": { + "version": "3.11.1", + "resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.11.1.tgz", + "integrity": "sha512-8uN744Yq0X2lbfSLxsEEc2UV3RjSRb4yDVxRQ1aGzPo86QjNOwhQSukDb8U8kR+636TRTvfMren10fgOjAy9eA==", + "requires": { + "exenv": "^1.2.0", + "prop-types": "^15.5.10", + "react-lifecycles-compat": "^3.0.0", + "warning": "^4.0.3" + } + }, "react-redux": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.1.1.tgz", @@ -9766,6 +9792,11 @@ "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" }, + "underscore": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.1.tgz", + "integrity": "sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==" + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -9996,6 +10027,14 @@ "makeerror": "1.0.x" } }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + }, "watch": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/watch/-/watch-0.10.0.tgz", diff --git a/twistter-frontend/package.json b/twistter-frontend/package.json index a0ec11c..47db703 100644 --- a/twistter-frontend/package.json +++ b/twistter-frontend/package.json @@ -11,17 +11,20 @@ "clsx": "^1.0.4", "create-react-app": "^3.1.2", "dayjs": "^1.8.17", + "fuse.js": "^3.4.6", "install": "^0.13.0", "jwt-decode": "^2.2.0", "node-pre-gyp": "^0.13.0", "react": "^16.9.0", "react-dom": "^16.9.0", + "react-modal": "^3.11.1", "react-redux": "^7.1.1", "react-router-dom": "^5.1.0", "react-scripts": "0.9.5", "redux": "^4.0.4", "redux-thunk": "^2.3.0", - "typeface-roboto": "0.0.75" + "typeface-roboto": "0.0.75", + "underscore": "^1.9.1" }, "devDependencies": {}, "scripts": { diff --git a/twistter-frontend/src/App.js b/twistter-frontend/src/App.js index 9e29d2e..6dd4550 100644 --- a/twistter-frontend/src/App.js +++ b/twistter-frontend/src/App.js @@ -31,6 +31,7 @@ import editProfile from "./pages/editProfile"; import userLine from "./Userline.js"; import verify from "./pages/verify"; import Search from "./pages/Search.js"; +import directMessages from "./pages/directMessages"; import otherUser from "./pages/otherUser"; const theme = createMuiTheme(themeObject); @@ -62,7 +63,7 @@ class App extends Component {
Userline
{sortedPosts.map((microBlog) =>
Microblog Title: {microBlog.microBlogTitle}
@@ -50,7 +50,7 @@ class Userline extends Component {
Number of comments: {microBlog.commentCount}
Number of likes: {microBlog.likeCount}
Body of post: {microBlog.body}
-
Tagged topics: {microBlog.microBlogTopics.join("," + " ")}
+
Tagged topics: {microBlog.microBlogTopics.join(", ")}