diff --git a/.firebaserc b/.firebaserc index 0b3cf99..2e9e776 100644 --- a/.firebaserc +++ b/.firebaserc @@ -5,4 +5,4 @@ "Twistter1": "twistter-6a42d", "SuperTiger69": "twistter-e4649" } -} \ No newline at end of file +} diff --git a/firebase.json b/firebase.json index ad362e4..0e2c6a7 100644 --- a/firebase.json +++ b/firebase.json @@ -1,25 +1,30 @@ { - "database": { - "rules": "database.rules.json" - }, - "firestore": { - "rules": "firestore.rules", - "indexes": "firestore.indexes.json" - }, - "functions": { - "predeploy": [ - "npm --prefix \"$RESOURCE_DIR\" run lint" - ] - }, - "hosting": { - "public": "public", - "ignore": [ - "firebase.json", - "**/.*", - "**/node_modules/**" - ] - }, - "storage": { - "rules": "storage.rules" + "database": { + "rules": "database.rules.json" + }, + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "functions": { + "predeploy": [ + "npm --prefix \"$RESOURCE_DIR\" run lint" + ] + }, + "hosting": { + "public": "public", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [{ + "source": "/feed", + "destination": "/feed.html" + }] + }, + "storage": { + "rules": "storage.rules" + } } -} + \ No newline at end of file diff --git a/functions/handlers/post.js b/functions/handlers/post.js index e2d195b..d15ea33 100644 --- a/functions/handlers/post.js +++ b/functions/handlers/post.js @@ -1,20 +1,21 @@ +const admin = require('firebase-admin'); /* eslint-disable promise/always-return */ exports.putPost = (req, res) => { - if (req.body.body.trim() === '') { - return res.status(400).json({ body: 'Body must not be empty!'}); - } + const newPost = { body: req.body.body, - userHandle: req.user.handle, - userImage: req.user.imageUrl, + userHandle: req.body.userHandle, + userImage: req.body.userImage, + microBlogTitle: req.body.microBlogTitle, createdAt: new Date().toISOString(), likeCount: 0, - commentCount: 0 + commentCount: 0, + }; - db.collection('post').add(newPost) - .then((doc) => { + admin.firestore().collection('posts').add(newPost) + .then((doc) => { const resPost = newPost; resPost.postId = doc.id; return res.status(200).json(resPost); @@ -25,3 +26,20 @@ exports.putPost = (req, res) => { }); }; +exports.getallPostsforUser = (req, res) => { + + admin.firestore().collection('posts').where('userHandle', '==', 'user' ).get() + .then((data) => { + let posts = []; + data.forEach(function(doc) { + posts.push(doc.data()); + }); + return res.status(200).json(posts); + }) + .catch((err) => { + console.error(err); + return res.status(500).json({error: 'Failed to fetch all posts written by specific user.'}) + }) + } + + diff --git a/functions/handlers/users.js b/functions/handlers/users.js index c165ae0..54dcf00 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -1,6 +1,138 @@ -const {db} = require('../util/admin'); +/* eslint-disable promise/catch-or-return */ +const {admin, db} = require('../util/admin'); +const config = require('../util/config'); + const {validateUpdateProfileInfo} = require('../util/validator'); +const firebase = require('firebase'); +firebase.initializeApp(config); + + + +exports.signup = (req, res) => { + const newUser = { + email: req.body.email, + handle: req.body.handle, + password: req.body.password, + confirmPassword: req.body.confirmPassword, + createdAt: new Date().toISOString() + }; + + // console.log(newUser); + + let errors = {}; + + const emailRegEx = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@(([[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + + //Email check + if(newUser.email.trim() === '') { + errors.email = 'Email must not be blank.'; + } + else if(!newUser.email.match(emailRegEx)) { + errors.email = 'Email is invalid.'; + } + + //handle check + if(newUser.handle.trim() === '') { + errors.handle = 'Username must not be blank.'; + } + else if(newUser.handle.length < 4 || newUser.handle.length > 30) { + errors.handle = 'Username must be between 4-30 characters long.'; + } + + //Password check + if(newUser.password.trim() === '') { + errors.password = 'Password must not be blank.'; + } + else if(newUser.password.length < 8 || newUser.password.length > 20) { + errors.password = 'Password must be between 8-20 characters long.'; + } + + //Confirm password check + if(newUser.confirmPassword !== newUser.password) { + errors.confirmPassword = 'Passwords must match.'; + } + + //Overall check + if(Object.keys(errors).length > 0) { + return res.status(400).json(errors); + } + + let idToken, userId; + + db.doc(`/users/${newUser.handle}`).get() + .then(doc => { + if(doc.exists) { + return res.status(400).json({ handle: 'This username is already taken.' }); + } + return firebase.auth().createUserWithEmailAndPassword(newUser.email, newUser.password); + }) + .then(data => { + userId = data.user.uid; + return data.user.getIdToken(); + }) + .then(token => { + idToken = token; + const userCred = { + email: req.body.email, + handle: newUser.handle, + createdAt: newUser.createdAt, + userId + } + return db.doc(`/users/${newUser.handle}`).set(userCred); + }) + .then(() => { + return res.status(201).json({ idToken }); + }) + .catch(err => { + console.error(err); + if(err.code === 'auth/email-already-in-use') { + return res.status(500).json({ email: 'This email is already taken.' }); + } + return res.status(500).json({ error: err.code }); + }); +}; + +exports.login = (req, res) => { + const user = { + email: req.body.email, + password: req.body.password + } + + //Auth validation + let errors = {}; + + //Email check + if(user.email.trim() === '') { + errors.email = 'Email must not be blank.'; + } + + //Password check + if(user.password.trim() === '') { + errors.password = 'Password must not be blank.'; + } + + //Overall check + if(Object.keys(errors).length > 0) { + return res.status(400).json(errors); + } + + firebase.auth().signInWithEmailAndPassword(user.email, user.password) + .then(data => { + return data.user.getIdToken(); + }) + .then(token => { + return res.json({token}); + }) + .catch(err => { + console.error(err); + if(err.code === 'auth/wrong-password') { + return res.status(403).json({ general: 'Invalid credentials. Please try again.' }); + } + return res.status(500).json({ error: err.code }); + }); + }; + exports.getProfileInfo = (req, res) => { // FIXME: Delete this after login is implemented req.user = {}; diff --git a/functions/index.js b/functions/index.js index e764338..04d3b4e 100644 --- a/functions/index.js +++ b/functions/index.js @@ -6,10 +6,24 @@ app.use(cors()); const fbAuth = require('./util/fbAuth'); + +const {db} = require('./util/admin'); + +// const firebase = require('firebase'); +// firebase.initializeApp(config); + + + + + /*------------------------------------------------------------------* - * handlers/users.js * - *------------------------------------------------------------------*/ -const {getUserDetails, getProfileInfo, updateProfileInfo} = require('./handlers/users'); +* handlers/users.js * +*------------------------------------------------------------------*/ +const {getUserDetails, getProfileInfo, updateProfileInfo, signup, login} = require('./handlers/users'); + +app.post('/signup', signup); + +app.post('/login', login); app.get('/getUser/:handle', getUserDetails); @@ -24,7 +38,9 @@ app.post('/updateProfileInfo', updateProfileInfo); /*------------------------------------------------------------------* * handlers/post.js * *------------------------------------------------------------------*/ -const {putPost} = require('./handlers/post'); +const {putPost, getallPostsforUser} = require('./handlers/post'); + +app.get('/getallPostsforUser', getallPostsforUser); // Adds one post to the database app.post('/putPost', fbAuth, putPost); diff --git a/functions/package-lock.json b/functions/package-lock.json index a119731..a20f589 100644 --- a/functions/package-lock.json +++ b/functions/package-lock.json @@ -70,9 +70,15 @@ "integrity": "sha512-foQHhvyB0RR+mb/+wmHXd/VOU+D8fruFEW1k79Q9wzyTPpovMBa1Mcns5fwEWBhUfi8bmoEtaGB8RSAHnTFzTg==" }, "@firebase/database": { +<<<<<<< HEAD + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.5.3.tgz", + "integrity": "sha512-LnXKRE1AmjlS+iRF7j8vx+Ni8x85CmLP5u5Pw5rDKhKLn2eTR1tJKD937mUeeGEtDHwR1rrrkLYOqRR2cSG3hQ==", +======= "version": "0.5.4", "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.5.4.tgz", "integrity": "sha512-Hz1Bi3fzIcNNocE4EhvvwoEQGurG2BGssWD3/6a2bzty+K1e57SLea2Ied8QYNBUU1zt/4McHfa3Y71EQIyn/w==", +>>>>>>> 7969d3b10bc35a9078834c5ee2ba8c8fd60d338f "requires": { "@firebase/database-types": "0.4.3", "@firebase/logger": "0.1.25", @@ -107,7 +113,11 @@ "@firebase/firestore": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-1.5.3.tgz", +<<<<<<< HEAD + "integrity": "sha512-CPYLvkGZBKE47oQC9a0q13UMVRj3LvnSbB1nOerktE3CGRHKy44LxDumamN8Kj067hV/80mKK9FdbeUufwO/Rg==", +======= "integrity": "sha512-O/yAbXpitOA6g627cUl0/FHYlkTy1EiEKMKOlnlMOJF2fH+nLVZREXjsrCC7N2tIvTn7yYwfpZ4zpSNvrhwiTA==", +>>>>>>> 7969d3b10bc35a9078834c5ee2ba8c8fd60d338f "requires": { "@firebase/firestore-types": "1.5.0", "@firebase/logger": "0.1.25", @@ -600,7 +610,7 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { @@ -820,28 +830,28 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } } }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } +}, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", @@ -1180,9 +1190,15 @@ } }, "end-of-stream": { +<<<<<<< HEAD + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", +======= "version": "1.4.3", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.3.tgz", "integrity": "sha512-cbNhPFS6MlYlWTGncSiDYbdqKhwWFy7kNeb1YSOG6K65i/wPTkLVCJQj0hXA4j0m5Da+hBWnqopEnu1FFelisQ==", +>>>>>>> 7969d3b10bc35a9078834c5ee2ba8c8fd60d338f "optional": true, "requires": { "once": "^1.4.0" @@ -1694,7 +1710,8 @@ "functional-red-black-tree": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=" + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "optional": true }, "gaxios": { "version": "2.0.1", @@ -2242,9 +2259,15 @@ } }, "gtoken": { +<<<<<<< HEAD + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.0.0.tgz", + "integrity": "sha512-XaRCfHJxhj06LmnWNBzVTAr85NfAErq0W1oabkdqwbq3uL/QTB1kyvGog361Uu2FMG/8e3115sIy/97Rnd4GjQ==", +======= "version": "4.1.0", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-4.1.0.tgz", "integrity": "sha512-wqyn2gf5buzEZN4QNmmiiW2i2JkEdZnL7Z/9p44RtZqgt4077m4khRgAYNuu8cBwHWCc6MsP6eDUn/KkF6jFIw==", +>>>>>>> 7969d3b10bc35a9078834c5ee2ba8c8fd60d338f "optional": true, "requires": { "gaxios": "^2.0.0", @@ -2418,7 +2441,8 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "optional": true }, "inflight": { "version": "1.0.6", @@ -2433,7 +2457,8 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "optional": true }, "inquirer": { "version": "6.5.2", @@ -2503,8 +2528,10 @@ "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 + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } }, "is-obj": { "version": "2.0.0", @@ -2902,6 +2929,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "optional": true, "requires": { "wrappy": "1" } @@ -3346,7 +3374,8 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "optional": true }, "slice-ansi": { "version": "2.1.0", @@ -3399,9 +3428,9 @@ "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { + "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" } @@ -3415,7 +3444,7 @@ "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^3.0.0" @@ -3726,7 +3755,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "optional": true }, "write": { "version": "1.0.3", diff --git a/functions/package.json b/functions/package.json index c5049dd..2e308b3 100644 --- a/functions/package.json +++ b/functions/package.json @@ -10,7 +10,7 @@ "logs": "firebase functions:log" }, "engines": { - "node": "8" + "node": "10" }, "dependencies": { "axios": "^0.19.0", diff --git a/functions/util/fbAuth.js b/functions/util/fbAuth.js index 04d71ed..a9243cc 100644 --- a/functions/util/fbAuth.js +++ b/functions/util/fbAuth.js @@ -7,8 +7,8 @@ module.exports = (req, res, next) => { let idToken; // Checking that the token exists in the header of the request - if (req.headers.authorization) { - idToken = req.headers.authorization; + if (req.headers.authorization && req.headers.authorization.startsWith('Bearer ')) { + idToken = req.headers.authorization.split('Bearer ')[1]; } else { console.error('No token found'); return res.status(403).json({ error: 'Unauthorized'}); diff --git a/twistter-frontend/package.json b/twistter-frontend/package.json index 0cb0dda..bc4672f 100644 --- a/twistter-frontend/package.json +++ b/twistter-frontend/package.json @@ -24,5 +24,17 @@ "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" }, - "proxy": "https://us-central1-twistter-e4649.cloudfunctions.net/api" + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + }, + "proxy": "https://us-central1-twistter-e4649.cloudfunctions.net/api" } diff --git a/twistter-frontend/src/App.js b/twistter-frontend/src/App.js index 7d104c5..f7157c9 100644 --- a/twistter-frontend/src/App.js +++ b/twistter-frontend/src/App.js @@ -5,6 +5,7 @@ import './App.css'; import { BrowserRouter as Router } from 'react-router-dom'; import Route from 'react-router-dom/Route'; +import NavBar, { Navbar } from './components/layout/NavBar'; // Pages import home from './Home.js'; @@ -13,12 +14,15 @@ import login from './Login.js'; import user from './pages/user'; import writeMicroblog from './Writing_Microblogs.js'; import edit from './pages/edit.js'; +import userLine from './Userline.js'; class App extends Component { render() { return ( - +
+ +
@@ -27,6 +31,7 @@ class App extends Component { +
diff --git a/twistter-frontend/src/Login.js b/twistter-frontend/src/Login.js index 1c615b7..550ed54 100644 --- a/twistter-frontend/src/Login.js +++ b/twistter-frontend/src/Login.js @@ -5,8 +5,6 @@ import './App.css'; import logo from './images/twistter-logo.png'; import TextField from '@material-ui/core/TextField'; -import axios from 'axios'; - class Login extends Component { render() { return ( diff --git a/twistter-frontend/src/Register.js b/twistter-frontend/src/Register.js index 7f06806..c67d8c2 100644 --- a/twistter-frontend/src/Register.js +++ b/twistter-frontend/src/Register.js @@ -4,8 +4,6 @@ import './App.css'; import logo from './images/twistter-logo.png'; import TextField from '@material-ui/core/TextField'; -//import axios from 'axios'; - class Register extends Component { render() { return ( diff --git a/twistter-frontend/src/Userline.js b/twistter-frontend/src/Userline.js new file mode 100644 index 0000000..51d74f4 --- /dev/null +++ b/twistter-frontend/src/Userline.js @@ -0,0 +1,37 @@ +import React, { Component } from "react"; +import { BrowserRouter as Router } from 'react-router-dom'; +import Route from 'react-router-dom/Route'; +import axios from 'axios'; + +class Userline extends Component { + + constructor(props) + { + super(props); + this.state = { + microBlogs : [] + } + + } + + componentDidMount() { + + axios.get('http://localhost:5001/twistter-e4649/us-central1/api/getallPostsforUser') + .then(res => { + const post = res.data; + this.setState({microBlogs : post}) + + }) + + + } + + render() { + return ( + + ) + } +} +export default Userline; diff --git a/twistter-frontend/src/Writing_Microblogs.js b/twistter-frontend/src/Writing_Microblogs.js index eca41d4..1858193 100644 --- a/twistter-frontend/src/Writing_Microblogs.js +++ b/twistter-frontend/src/Writing_Microblogs.js @@ -1,6 +1,7 @@ import React, { Component } from "react"; import { BrowserRouter as Router } from 'react-router-dom'; import Route from 'react-router-dom/Route'; +import axios from 'axios'; class Writing_Microblogs extends Component { @@ -25,8 +26,22 @@ class Writing_Microblogs extends Component { } handleSubmit(event) { - alert('A title for the microblog was inputted: ' + this.state.title + '\nA microblog was posted: ' + this.state.value); + // alert('A title for the microblog was inputted: ' + this.state.title + '\nA microblog was posted: ' + this.state.value); + + const response = axios.post( + 'http://localhost:5001/twistter-e4649/us-central1/api/putPost', + { body: this.state.value, + userHandle: "new user", + userImage: "bing-url", + microBlogTitle: this.state.title + + }, + { headers: { 'Content-Type': 'application/json'} } + + ) + console.log(response.data); event.preventDefault(); + this.setState({value: '', title: '',characterCount: 10}) } handleChangeforPost(event) { @@ -37,6 +52,7 @@ class Writing_Microblogs extends Component { const charCount = event.target.value.length const charRemaining = 10 - charCount this.setState({characterCount: charRemaining }) + } diff --git a/twistter-frontend/src/components/layout/NavBar.js b/twistter-frontend/src/components/layout/NavBar.js index 9a7d6eb..e4ee5c5 100644 --- a/twistter-frontend/src/components/layout/NavBar.js +++ b/twistter-frontend/src/components/layout/NavBar.js @@ -10,8 +10,8 @@ export class Navbar extends Component { return ( - - + )