diff --git a/functions/handlers/post.js b/functions/handlers/post.js index e3d0c01..1db0144 100644 --- a/functions/handlers/post.js +++ b/functions/handlers/post.js @@ -4,8 +4,9 @@ exports.putPost = (req, res) => { const newPost = { body: req.body.body, - userHandle: req.body.userHandle, + userHandle: req.userData.handle, userImage: req.body.userImage, + userID: req.userData.userId, microBlogTitle: req.body.microBlogTitle, createdAt: new Date().toISOString(), likeCount: 0, @@ -27,7 +28,7 @@ exports.putPost = (req, res) => { }; exports.getallPostsforUser = (req, res) => { - admin.firestore().collection('posts').where('userHandle', '==', 'new user' ).get() + admin.firestore().collection('posts').where('userHandle', '==', req.userData.handle ).get() .then((data) => { let posts = []; data.forEach(function(doc) { diff --git a/functions/handlers/topic.js b/functions/handlers/topic.js new file mode 100644 index 0000000..4d3dc3d --- /dev/null +++ b/functions/handlers/topic.js @@ -0,0 +1,52 @@ +/* eslint-disable promise/always-return */ +const { admin, db } = require("../util/admin"); +exports.putTopic = (req, res) => { + + const newTopic = { + topic: req.body.topic + }; + + admin.firestore().collection('topics').add(newTopic) + .then((doc) => { + const resTopic = newTopic; + newTopic.topicId = doc.id; + return res.status(200).json(resTopic); + }) + .catch((err) => { + console.error(err); + return res.status(500).json({ error: 'something is wrong'}); + }); +}; + +exports.getAllTopics = (req, res) => { + admin.firestore().collection('topics').get() + .then((data) => { + let topics = []; + data.forEach(function(doc) { + topics.push(doc.data()); + }); + return res.status(200).json(topics); + }) + .catch((err) => { + console.error(err); + return res.status(500).json({error: 'Failed to fetch all topics.'}) + }) +}; + +exports.deleteTopic = (req, res) => { + const topic = db.doc(`/topics/${req.params.topicId}`); + topic.get().then((doc) => { + if (!doc.exists) { + return res.status(404).json({error: 'Topic not found'}); + } else { + return topic.delete(); + } + }) + .then(() => { + res.json({ message: 'Topic successfully deleted!'}); + }) + .catch((err) => { + console.error(err); + return res.status(500).json({error: 'Failed to delete topic.'}) + }) +} \ No newline at end of file diff --git a/functions/handlers/users.js b/functions/handlers/users.js index 87733bc..f3ad079 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -75,7 +75,8 @@ exports.signup = (req, res) => { email: newUser.email, handle: newUser.handle, createdAt: newUser.createdAt, - userId + userId, + followedTopics: [] }; return db.doc(`/users/${newUser.handle}`).set(userCred); }) @@ -168,7 +169,7 @@ exports.login = (req, res) => { }) .catch((err) => { console.error(err); - if (err.code === "auth/wrong-password" || err.code === "auth/invalid-email") { + if (err.code === "auth/wrong-password" || err.code === "auth/invalid-email" || err.code === "auth/user-not-found") { return res .status(403) .json({ general: "Invalid credentials. Please try again." }); @@ -231,7 +232,7 @@ exports.updateProfileInfo = (req, res) => { // TODO: Add functionality for adding/updating profile images // Data validation - const { valid, errors, profileData } = validateUpdateProfileInfo(req.body); + const { valid, errors, profileData } = validateUpdateProfileInfo(req); if (!valid) return res.status(400).json(errors); // Update the database entry for this user @@ -256,50 +257,11 @@ exports.updateProfileInfo = (req, res) => { exports.getUserDetails = (req, res) => { let userData = {}; - db.doc(`/users/${req.params.handle}`) + db.doc(`/users/${req.body.handle}`) .get() .then((doc) => { if (doc.exists) { - userData.user = doc.data(); - return db - .collection("post") - .where("userHandle", "==", req.params.handle) - .orderBy("createdAt", "desc") - .get(); - } else { - return res.status(404).json({ - error: "User not found" - }); - } - }) - .then((data) => { - userData.posts = []; - data.forEach((doc) => { - userData.posts.push({ - body: doc.data().body, - createAt: doc.data().createAt, - userHandle: doc.data().userHandle, - userImage: doc.data().userImage, - likeCount: doc.data().likeCount, - commentCount: doc.data().commentCount, - postId: doc.id - }); - }); - return res.json(userData); - }) - .catch((err) => { - console.error(err); - return res.status(500).json({ error: err.code }); - }); -}; - -exports.getAuthenticatedUser = (req, res) => { - let userData = {}; - db.doc(`/users/${req.user.handle}`) - .get() - .then((doc) => { - if (doc.exists) { - userData.credentials = doc.data(); + userData = doc.data(); return res.status(200).json({userData}); } else { return res.status(400).json({error: "User not found."}) @@ -309,3 +271,22 @@ exports.getAuthenticatedUser = (req, res) => { return res.status(500).json({ error: err.code }); }); }; + +exports.getAuthenticatedUser = (req, res) => { + let credentials = {}; + db.doc(`/users/${req.user.handle}`) + .get() + .then((doc) => { + if (doc.exists) { + credentials = doc.data(); + return res.status(200).json({credentials}); + } else { + return res.status(400).json({error: "User not found."}) + }}) + .catch((err) => { + console.error(err); + return res.status(500).json({ error: err.code }); + }); +}; + + diff --git a/functions/index.js b/functions/index.js index 45d181a..33ce166 100644 --- a/functions/index.js +++ b/functions/index.js @@ -31,7 +31,7 @@ app.post("/login", login); //Deletes user account app.delete("/delete", fbAuth, deleteUser); -app.get("/getUser/:handle", getUserDetails); +app.get("/getUser", fbAuth, getUserDetails); // Returns all profile data of the currently logged in user app.get("/getProfileInfo", fbAuth, getProfileInfo); @@ -44,11 +44,30 @@ app.get("/user", fbAuth, getAuthenticatedUser); /*------------------------------------------------------------------* * handlers/post.js * *------------------------------------------------------------------*/ -const { getallPostsforUser, putPost } = require("./handlers/post"); +const { getallPostsforUser, putPost +} = require("./handlers/post"); app.get("/getallPostsforUser", getallPostsforUser); // Adds one post to the database app.post("/putPost", fbAuth, putPost); +/*------------------------------------------------------------------* + * handlers/topic.js * + *------------------------------------------------------------------*/ +const { + putTopic, + getAllTopics, + deleteTopic +} = require("./handlers/topic"); + +// add topic to database +app.post("/putTopic", fbAuth, putTopic); + +// get all topics from database +app.get("/getAllTopics", fbAuth, getAllTopics); + +// delete a specific topic +app.delete("/deleteTopic/:topicId", fbAuth, deleteTopic); + exports.api = functions.https.onRequest(app); diff --git a/functions/package-lock.json b/functions/package-lock.json index 439a2e2..acfe9ce 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -535,6 +535,11 @@ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", "dev": true }, + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -1190,11 +1195,18 @@ "progress": "^2.0.0", "regexpp": "^2.0.1", "semver": "^5.5.1", + "strip-ansi": "^4.0.0", "strip-json-comments": "^2.0.1", "table": "^5.2.3", "text-table": "^0.2.0" }, "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, "debug": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", @@ -1209,6 +1221,15 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } } } }, @@ -2355,6 +2376,7 @@ "mute-stream": "0.0.7", "run-async": "^2.2.0", "rxjs": "^6.4.0", + "string-width": "^2.1.0", "strip-ansi": "^5.1.0", "through": "^2.3.6" }, @@ -2403,6 +2425,12 @@ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", "optional": true }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, "is-obj": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", @@ -3252,7 +3280,8 @@ "dev": true, "requires": { "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0" + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" } }, "snakeize": { @@ -3292,12 +3321,47 @@ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", "optional": true }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + }, "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -3344,6 +3408,7 @@ "dev": true, "requires": { "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^5.1.0" } }, diff --git a/functions/package.json b/functions/package.json index 2e308b3..29b3e89 100644 --- a/functions/package.json +++ b/functions/package.json @@ -16,7 +16,8 @@ "axios": "^0.19.0", "firebase": "^6.6.2", "firebase-admin": "^8.6.0", - "firebase-functions": "^3.1.0" + "firebase-functions": "^3.1.0", + "strip-ansi": "^5.2.0" }, "devDependencies": { "eslint": "^5.12.0", diff --git a/functions/util/fbAuth.js b/functions/util/fbAuth.js index 3b59d14..35253e7 100644 --- a/functions/util/fbAuth.js +++ b/functions/util/fbAuth.js @@ -32,6 +32,7 @@ module.exports = (req, res, next) => { .then((data) => { req.user.handle = data.docs[0].data().handle; // Save username req.user.imageUrl = data.docs[0].data().imageUrl; + req.userData = data.docs[0].data(); // Stores all user data from the database return next(); }) .catch((err) => { diff --git a/functions/util/validator.js b/functions/util/validator.js index c2b1240..8265d6b 100644 --- a/functions/util/validator.js +++ b/functions/util/validator.js @@ -9,23 +9,39 @@ const isEmpty = (str) => { else return false; }; -exports.validateUpdateProfileInfo = (data) => { +exports.validateUpdateProfileInfo = (req) => { + const newData = req.body; + // const oldData = req.userData; let errors = {}; - let profileData = {}; + let profileData = req.userData; // ?: Should users be able to change their handles and emails? - // Only adds the key to the database if the values are not empty - if (!isEmpty(data.firstName)) profileData.firstName = data.firstName.trim(); - if (!isEmpty(data.lastName)) profileData.lastName = data.lastName.trim(); - if (!isEmpty(data.bio)) profileData.bio = data.bio.trim(); + // Deletes any unused keys so that they aren't stored in the database + if (newData.firstName) { + profileData.firstName = newData.firstName.toString().trim(); + } else { + delete profileData.firstName; + } - if (isEmpty(data.email)) { + if (newData.lastName) { + profileData.lastName = newData.lastName.toString().trim(); + } else { + delete profileData.lastName; + } + + if (newData.bio) { + profileData.bio = newData.bio.toString().trim(); + } else { + delete profileData.bio; + } + + if (isEmpty(newData.email)) { errors.email = "Must not be empty."; - } else if (!isEmail(data.email)) { + } else if (!isEmail(newData.email)) { errors.email = "Must be a valid email."; } else { - profileData.email = data.email; + profileData.email = newData.email; } return { diff --git a/package-lock.json b/package-lock.json index c7bbe95..c65b0ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,15 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "axios": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, "body-parser": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", @@ -164,6 +173,24 @@ "unpipe": "~1.0.0" } }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", @@ -204,6 +231,16 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==" }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + }, + "jwt-decode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", + "integrity": "sha1-fYa9VmefWM5qhHBKZX3TkruoGnk=" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", diff --git a/twistter-e4649-firebase-adminsdk-pgjve-1e57494429.json b/twistter-e4649-firebase-adminsdk-pgjve-1e57494429.json deleted file mode 100644 index d24aa3c..0000000 --- a/twistter-e4649-firebase-adminsdk-pgjve-1e57494429.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "type": "service_account", - "project_id": "twistter-e4649", - "private_key_id": "1e57494429e4fd7d17f6fc28524e14b8e5227596", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCyRdMZwXrpf3HB\nsVrGZVoPt+S+ahkT4SCCEWV/Q4O4hVyW277Rs0p7k5ZkZfkhcVVBUqKGMgW/Esvz\npx0VIgpg3gg308kMRmC3MGzYbodjynVyDHatyAZhhUi9cg0N2K9+vSsW5eSmWROe\nfVvB8hvr6m5xGVdJFA7DV4/0vm2cfy+Q/FlFb6vFmQTZtPZzGFf5BKoNMe7pMxey\n4zspAIZgRmxWDbAqqzX0PYk/WbXFgH/wn2X25S+ArHhoay2Hms65/NbWCV6mpFUJ\nYqzrlCn/WcwXGAm7tjmu00yD+bARabImh8R7+PSBaHMQ+SGdri2snIXWsvB/xi9/\nSNFqzHynAgMBAAECggEAVty/zapg1bnTt0FPziBfIA6FpaPzoSSN3uJUFozSdwuA\nAD+E/A9EiO7yFew71egvVrtJVmK0OxQRDSDNglkKPoWg8na+XL1D7a5qMpC0ZlKl\nJBNfljBCr6yuMySJqMf+Rp4siyUr4kO/0/cXyOnLYglhk7j5tzFPOi4FhgZtSRU9\nYckk5wwcBObUFE0Rmqf0gPcI9WuFUkusIjz3rjuEju1/U6E/VV5gHmMuQy3f9LHB\nnsiLAobx9+TGgs12CvkjWYpW5raUCCn5z/EYNPZSt7rg9CSWqXW009HCfTqi6i7o\nNpZ7qpp5DVQdHNFJunSGvI+k44+i8OE7HEY4xXt+OQKBgQDZ/E1QwJQoHuzPkrGW\nAc0a+NQeG8NwaXwsezlvYXMTbL27SxKXC3dzPT1WgNUKpaKj3wLJPar4NgPPSPi3\nqgmcvMqgwm4B+HPbXc1oxBS7/jD3pWJVyPO9Re17Uc0RYV/DORQhWe1Yq7TyMHvl\nbD/KqIvOxswigVxK4JMIxp4s7wKBgQDRXJfb4BHdR7CGfuTVQ19gH5uLgrK9ezBk\nQOLK+u9yBpoKyYSnD3OH/i0wG5bm3rUegzvwHGKKhDfiLbajMIt04n2DuVUm57HQ\n+Jca29V8XMWfhTbu3kDl+OOFmLvPCwg8C9edNTJWUVYu3EbwsyzCQY5TDroWGQdF\n6cQIkAIbyQKBgQCUtWd1UHuCR16cWOHniQEIhnoGtEAHHx9EJShQkLV1qfhhnlxn\nSL5LkpqWubsc0VR74LbA3N4XCJpevdRXT5vRHoZJV3q+w2UeYQaxkxrmCQoU1/GW\nvklxdRQGzg5M7hXrU7Qk8HlXxYPiuSq8n7WBJqyB+uLmI0P4HO6RzRW5ZwKBgBkr\nf5pQkvU+dCuHP+2fvuyogCPCn8iF8ehroJh0mKrlvklDtu36vpH/7eDVwEubRL0Z\nW/BfCT3L7YgEpOtzn6B6xko60tDtlAQijtAM09qysJOgCV2oXLcJOBlMpm+azO+j\nINXmmlmkR680jlbLw7rK9NhpcdfMRIKUOxwobAh5AoGBAJMN6n7Hy8nJRJZ18iHY\n3hEkVbKrLLMMHFDQj5BZQCeAp1v0Fj30RYJWS4hTzR9ht0MtdmPub460lh9hhqCM\nl3UnCJoz00ApF3Z2FRN/l0KpCFD9Gw2Hjyc3u9sEUpqd8G3cg3IZE6eZ6CZWljFT\nHcuQ2cTFdB8bq8oLUxXeY3Yv\n-----END PRIVATE KEY-----\n", - "client_email": "firebase-adminsdk-pgjve@twistter-e4649.iam.gserviceaccount.com", - "client_id": "102241295911303209723", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-pgjve%40twistter-e4649.iam.gserviceaccount.com" -} diff --git a/twistter-e4649-firebase-adminsdk-pgjve-382fc005e1.json b/twistter-e4649-firebase-adminsdk-pgjve-382fc005e1.json new file mode 100644 index 0000000..3d3ce5d --- /dev/null +++ b/twistter-e4649-firebase-adminsdk-pgjve-382fc005e1.json @@ -0,0 +1,12 @@ +{ + "type": "service_account", + "project_id": "twistter-e4649", + "private_key_id": "382fc005e17340a21dd39079feca75371330644d", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDL1bwTpEluR6dJ\nVfS/K1d76j2I3Wrn4qSbrTYm4/cAPBPlsBRO0bwrkhr+g0I6rwW/fFz8udjNAFB1\nfDfz769buR6go5us71I5LxMNviIhzFr59ZsEkcnUCgW8G7koicLEjWdstI4lJxZT\nRRolH+SFpPwsQAY399zjdBiCC8STbavVVhe+ChEDl/S2K72W7A7nWSMbLsyGsz2o\nQ9uIG3onwFNE7WRIeObWCxSBKlydMmTJ+p/CsTdFY4fUHb97QsMEmzRJox10kmyt\nP9l5wfJ2wUYvRXRJKq7Mwx1UnLduXP2oe/XvR5fzJLJ780HO6BNBf5IKcTHXHVBA\ne6ZtiL5dAgMBAAECggEAFvfN1NttjXL0KKak3hMA1+z8Y2WqXC1aUFMOMlz8Qhct\nmXNjy8CFAYEvGtVTgHqkR9Vi7PShYfLSc3U8diI155H5H4S6pUaPmfNHTwRzosyn\ncQRPJA6sEqvRGvHMxVfwjbvultMpiTTZpnxiMSNiLqT5PUs26CuSb5bErt1V7dP5\nn/lhY+4rzfXSrw38ZsO/gEvLZ/7iRA+JZgqE3Qs2cD5idxqqOcOLLiW741JpXTmd\n5ug/urJgSyvz+cNo3yHnajEtAxiSfkpU4sUHZ/WWqaRGWxpt3XWILtR9Q/4afPeM\n/T82YvddoW5pUwDpgvZxdVYjopuoxvnS298L0AGySQKBgQDqDD7Yh6SbOR3w4ZN9\nRO7q5KiJbyZmVdZ3lLZddsL/vto9JkUtnLDOMpYnb2TezGfN6ErzAkRQCSUsWK4m\nERbSK6oyUvevhlt/gHGP34uc/OCxGk2D7WCAS3s54ofDt2369tzIpGjsnkYM+h3b\nuZ5lFoWHG1YM3JtgcIIU6UygTwKBgQDe9ArV08wPDuXf4rcldmImQDB7xhGcg9xU\njAKEn5FZW1mvmBy3Sq1qpZj0baZz6eEDn6FBLwH1Ke5gdfrd8WnESUNnFnBBqpA1\nospIgUmKZ1sjyly9CHMQ11Kbzt7+kA9GYZrMbaMjS8M18qFEBZ7CoPLv5DLyabJW\nOkPzTwb/kwKBgQDR3fUkmEzj202bx8pHE97gxfTSd9aJAQN06uaz3GByjyKGnqB9\ni/mGjBnUdrCOj9+s5VT/ntK+qdSpdUODYuOBxiGxSnBK9kFpjTVHe35nYOHiLOHB\nIMPdhtGSUCzJNNvrpBzJ1ZM4SZwq2sSXWFRN9On7Amog0liJG5mpQqGxRQKBgHmP\nzFycF3XaZKH2xm8ppgg/FXBXJYEWMEr07+aJ7kEvWq4wHPAfSoCMe+JB6vDmg2Zr\nYgvdao7W5v83NKpQl5+LZrHNfTWAnxJviSWRQJyzD/Fqw7fZ5Is5K/SCDfn0aC+y\nxilSWhHDnFNM0Hr7KX3rLap43QJpePAk4qnF3AX7AoGBAOe5C1VMKoTapnxvo/FI\nBCvXw05SSEExfXVR0ryoqUxgu1asCkYyJSoWxOZQ+33npQ1/zV1mCXn/lNZcyveW\nZ5/cUw71grW8KBYFrW7LeQIzjZb7xPI3Z4EV0uYd2b69GkQk1buHj/gsswRhGEvx\nJM1DN1SCenbGcVLprM0vVelp\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-pgjve@twistter-e4649.iam.gserviceaccount.com", + "client_id": "102241295911303209723", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-pgjve%40twistter-e4649.iam.gserviceaccount.com" +} diff --git a/twistter-frontend/package-lock.json b/twistter-frontend/package-lock.json index 5b4b20d..c14d5b6 100644 --- a/twistter-frontend/package-lock.json +++ b/twistter-frontend/package-lock.json @@ -61,6 +61,14 @@ } } }, + "@material-ui/icons": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.5.1.tgz", + "integrity": "sha512-YZ/BgJbXX4a0gOuKWb30mBaHaoXRqPanlePam83JQPZ/y4kl+3aW0Wv9tlR70hB5EGAkEJGW5m4ktJwMgxQAeA==", + "requires": { + "@babel/runtime": "^7.4.4" + } + }, "@material-ui/styles": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.5.0.tgz", @@ -9717,6 +9725,11 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, + "typeface-roboto": { + "version": "0.0.75", + "resolved": "https://registry.npmjs.org/typeface-roboto/-/typeface-roboto-0.0.75.tgz", + "integrity": "sha512-VrR/IiH00Z1tFP4vDGfwZ1esNqTiDMchBEXYY9kilT6wRGgFoCAlgkEUMHb1E3mB0FsfZhv756IF0+R+SFPfdg==" + }, "uglify-js": { "version": "3.4.10", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", diff --git a/twistter-frontend/package.json b/twistter-frontend/package.json index f03c528..f7dd12f 100644 --- a/twistter-frontend/package.json +++ b/twistter-frontend/package.json @@ -4,6 +4,7 @@ "private": true, "dependencies": { "@material-ui/core": "^4.4.3", + "@material-ui/icons": "^4.5.1", "@material-ui/styles": "^4.5.0", "@material-ui/system": "^4.5.0", "axios": "^0.19.0", @@ -18,7 +19,8 @@ "react-router-dom": "^5.1.0", "react-scripts": "0.9.5", "redux": "^4.0.4", - "redux-thunk": "^2.3.0" + "redux-thunk": "^2.3.0", + "typeface-roboto": "0.0.75" }, "devDependencies": {}, "scripts": { diff --git a/twistter-frontend/src/App.js b/twistter-frontend/src/App.js index b767156..2335b20 100644 --- a/twistter-frontend/src/App.js +++ b/twistter-frontend/src/App.js @@ -19,6 +19,8 @@ import { logoutUser, getUserData } from './redux/actions/userActions'; // Components import AuthRoute from "./util/AuthRoute"; +// axios.defaults.baseURL = 'http://localhost:5006/twistter-e4649/us-central1/api'; + // Pages import home from './pages/Home'; import signup from './pages/Signup'; @@ -34,14 +36,20 @@ const theme = createMuiTheme(themeObject); const token = localStorage.FBIdToken; if (token) { - const decodedToken = jwtDecode(token); - if (decodedToken.exp * 1000 < Date.now()) { - store.dispatch(logoutUser); + + try { + const decodedToken = jwtDecode(token); + if (decodedToken.exp * 1000 < Date.now()) { + store.dispatch(logoutUser()); + window.location.href = "/login"; + } else { + store.dispatch({ type: SET_AUTHENTICATED }); + axios.defaults.headers.common['Authorization'] = token; + store.dispatch(getUserData()); + } + } catch (invalidTokenError) { + store.dispatch(logoutUser()); window.location.href = "/login"; - } else { - store.dispatch({ type: SET_AUTHENTICATED }); - axios.defaults.headers.common['Authorization'] = token; - store.dispatch(getUserData()); } } diff --git a/twistter-frontend/src/Writing_Microblogs.js b/twistter-frontend/src/Writing_Microblogs.js index b1570d8..6ddb974 100644 --- a/twistter-frontend/src/Writing_Microblogs.js +++ b/twistter-frontend/src/Writing_Microblogs.js @@ -36,7 +36,6 @@ class Writing_Microblogs extends Component { // alert('A title for the microblog was inputted: ' + this.state.title + '\nA microblog was posted: ' + this.state.value); const postData = { body: this.state.value, - userHandle: "new user", userImage: "bing-url", microBlogTitle: this.state.title, microBlogTopics: this.state.topics.split(', ') @@ -46,7 +45,7 @@ class Writing_Microblogs extends Component { } axios - .post('/putPost', postData, headers) + .post("/putPost", postData, headers) .then((res) =>{ alert('Post was shared successfully!') console.log(res.data); diff --git a/twistter-frontend/src/components/layout/NavBar.js b/twistter-frontend/src/components/layout/NavBar.js index 5f8d28d..ceab08f 100644 --- a/twistter-frontend/src/components/layout/NavBar.js +++ b/twistter-frontend/src/components/layout/NavBar.js @@ -1,7 +1,7 @@ /* eslint-disable */ import React, { Component } from 'react'; import { Link } from 'react-router-dom'; -// import PropTypes from 'prop-types'; +import PropTypes from 'prop-types'; // Material UI stuff import AppBar from '@material-ui/core/AppBar'; @@ -11,26 +11,26 @@ import withStyles from "@material-ui/core/styles/withStyles"; // Redux stuff // import { logoutUser } from '../../redux/actions/userActions'; -// import { connect } from 'react-redux'; +import { connect } from 'react-redux'; -// const styles = { -// form: { -// textAlign: "center" -// }, -// textField: { -// marginBottom: 30 -// }, -// pageTitle: { -// marginBottom: 40 -// }, -// button: { -// positon: "relative", -// marginBottom: 30 -// }, -// progress: { -// position: "absolute" -// } -// }; +const styles = { + form: { + textAlign: "center" + }, + textField: { + marginBottom: 30 + }, + pageTitle: { + marginBottom: 40 + }, + button: { + positon: "relative", + marginBottom: 30 + }, + progress: { + position: "absolute" + } + }; @@ -38,42 +38,44 @@ import withStyles from "@material-ui/core/styles/withStyles"; export class Navbar extends Component { render() { + const authenticated = this.props.user.authenticated; return ( - - } + {!authenticated && - } + {authenticated && - } + {/* Commented out the delete button, because it should probably go on + the profile or editProfile page instead of the NavBar */} + {/* + */} ) } } -// const mapStateToProps = (state) => ({ -// user: state.user -// }) +const mapStateToProps = (state) => ({ + user: state.user +}) // const mapActionsToProps = { logoutUser }; -// Navbar.propTypes = { -// logoutUser: PropTypes.func.isRequired, -// user: PropTypes.object.isRequired, -// classes: PropTypes.object.isRequired -// } +Navbar.propTypes = { + user: PropTypes.object.isRequired, + classes: PropTypes.object.isRequired +} -// export default connect(mapStateToProps, mapActionsToProps)(withStyles(styles)(Navbar)); +export default connect(mapStateToProps)(withStyles(styles)(Navbar)); -export default Navbar; +// export default Navbar; diff --git a/twistter-frontend/src/components/profile/Profile.js b/twistter-frontend/src/components/profile/Profile.js new file mode 100644 index 0000000..6d4bd8c --- /dev/null +++ b/twistter-frontend/src/components/profile/Profile.js @@ -0,0 +1,48 @@ +import React, { Component, Fragment } from "react"; +import PropTypes from "prop-types"; +import axios from "axios"; +import { connect } from 'react-redux'; + +//MUI +import withStyles from "@material-ui/core/styles/withStyles"; +import Card from "@material-ui/core/CardMedia"; +import Typography from "@material-ui/core/Typography"; +import Button from "@material-ui/core/Button"; +import { Paper } from "@material-ui/core"; + +const styles = theme => ({ + ...theme +}); + +class Profile extends Component { + state = { + profile: null + }; + + componentDidMount() { + axios + .get("/user") + .then(res => { + console.log(res.data.userData.credentials.handle); + this.setState({ + profile: res.data.userData.credentials.handle + }); + }) + .catch(err => console.log(err)); + } + render() { + let profileMarkup = this.state.profile ? ( +

+ {this.state.profile} +

) :

loading profile...

+ + return profileMarkup; + } +} + +const mapStateToProps = state => ({ + user: state.user, + classes: PropTypes.object.isRequired +}); + +export default connect(mapStateToProps)(withStyles(styles)(Profile)); diff --git a/twistter-frontend/src/components/profile/StaticProfile.js b/twistter-frontend/src/components/profile/StaticProfile.js deleted file mode 100644 index e69de29..0000000 diff --git a/twistter-frontend/src/images/original.png b/twistter-frontend/src/images/original.png new file mode 100644 index 0000000..c28e365 Binary files /dev/null and b/twistter-frontend/src/images/original.png differ diff --git a/twistter-frontend/src/images/twistter-logo.png b/twistter-frontend/src/images/twistter-logo.png index c28e365..1baf97c 100644 Binary files a/twistter-frontend/src/images/twistter-logo.png and b/twistter-frontend/src/images/twistter-logo.png differ diff --git a/twistter-frontend/src/pages/editProfile.js b/twistter-frontend/src/pages/editProfile.js index 3c3c51b..ff6f3f7 100644 --- a/twistter-frontend/src/pages/editProfile.js +++ b/twistter-frontend/src/pages/editProfile.js @@ -88,6 +88,14 @@ export class edit extends Component { handle: this.state.handle, bio: this.state.bio }; + + // Removes all keys from newProfileData that are empty, undefined, or null + Object.keys(newProfileData).forEach(key => { + if (newProfileData[key] === "" || newProfileData[key] === undefined || newProfileData[key] === null) { + delete newProfileData[key]; + } + }) + axios .post("/updateProfileInfo", newProfileData) .then((res) => { diff --git a/twistter-frontend/src/pages/user.js b/twistter-frontend/src/pages/user.js index f1113a0..27a0c94 100644 --- a/twistter-frontend/src/pages/user.js +++ b/twistter-frontend/src/pages/user.js @@ -1,49 +1,124 @@ /* eslint-disable */ import React, { Component } from 'react'; +import PropTypes from 'prop-types'; import axios from 'axios'; //import '../App.css'; import { makeStyles, styled } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; import Card from '@material-ui/core/Card'; -import CardMedia from '@material-ui/core/CardMedia'; -import CardContent from '@material-ui/core/CardContent'; import Chip from '@material-ui/core/Chip'; -import Paper from '@material-ui/core/Paper'; +import Typography from "@material-ui/core/Typography"; +import AddCircle from '@material-ui/icons/AddCircle'; +import TextField from '@material-ui/core/TextField'; +// component +import Userline from '../Userline'; +import noImage from '../images/no-img.png'; -const PostCard = styled(Card)({ - background: 'linear-gradient(45deg, #1da1f2 90%)', - border: 3, - borderRadius: 3, - height:325, - width: 345, - padding: '0 30px', +const MyChip = styled(Chip)({ + margin: 2, + color: 'primary' }); - -class user extends Component { - componentDidMount(){ - //TODO: get user details - //TODO: get posts +class user extends Component { + state = { + profile: null, + imageUrl: null, + topics: null, + newTopic: null + }; + + handleDelete = (topic) => { + alert(`Delete topic: ${topic}!`); + } + + handleAddCircle = () => { + axios.post('/putTopic', { + topic: this.state.newTopic + }) + .then(function () { + location.reload(); + }) + .catch(function (err) { + console.log(err); + }); } + handleChange(event) { + this.setState({ + newTopic: event.target.value + }) + } + + componentDidMount() { + axios + .get("/user") + .then(res => { + this.setState({ + profile: res.data.credentials.handle, + imageUrl: res.data.credentials.imageUrl + }); + }) + .catch(err => console.log(err)); + axios + .get("/getAllTopics") + .then(res => { + this.setState({ + topics: res.data + }) + }) + .catch(err => console.log(err)); + } render() { + const classes = this.props; + let profileMarkup = this.state.profile ? ( +

+ {this.state.profile} +

) : (

loading username...

); + + + let topicsMarkup = this.state.topics ? ( + this.state.topics.map(topic => this.handleDelete(topic)}/>) + ) : (

loading topics...

); + + let imageMarkup = this.state.imageUrl ? ( + + ) : (); + return (

Post

- - - Username - + {imageMarkup} + {profileMarkup} + {topicsMarkup} + this.handleChange(event)} + /> +
); } } - - export default user; diff --git a/twistter-frontend/src/util/theme.js b/twistter-frontend/src/util/theme.js index 38622a6..2ac393e 100644 --- a/twistter-frontend/src/util/theme.js +++ b/twistter-frontend/src/util/theme.js @@ -50,7 +50,13 @@ export default { marginBottom: 20 }, paper: { - padding: 20 + padding: 10, + display: 'flex', + justifyContent: 'center', + flexWrap: 'wrap' + }, + chip: { + margin: 0.5, }, profile: { '& .image-wrapper': {