diff --git a/functions/handlers/users.js b/functions/handlers/users.js index fc08972..a3cea0d 100644 --- a/functions/handlers/users.js +++ b/functions/handlers/users.js @@ -77,7 +77,8 @@ exports.signup = (req, res) => { createdAt: newUser.createdAt, userId, followedTopics: [], - imageUrl: defaultImageUrl + imageUrl: defaultImageUrl, + verified: false }; return db.doc(`/users/${newUser.handle}`).set(userCred); }) @@ -346,6 +347,59 @@ exports.getAuthenticatedUser = (req, res) => { }); }; +// Verifies the user sent to the request +// Must be run by the Admin user +exports.verifyUser = (req, res) => { + if (req.userData.handle !== "Admin") { + return res.status(403).json({error: "This must be done as Admin"}); + } + + db.doc(`/users/${req.body.user}`) + .get() + .then((doc) => { + if (doc.exists) { + let verifiedUser = doc.data(); + verifiedUser.verified = true; + return db.doc(`/users/${req.body.user}`).set(verifiedUser, {merge: true}); + } else { + return res.status(400).json({error: `User ${req.body.user} was not found`}); + } + }) + .then(() => { + return res.status(201).json({message: `${req.body.user} is now verified`}); + }) + .catch((err) => { + console.error(err); + return res.status(500).json({error: err.code}); + }); +} + +// Unverifies the user sent to the request +// Must be run by admin +exports.unverifyUser = (req, res) => { + if (req.userData.handle !== "Admin") { + return res.status(403).json({error: "This must be done as Admin"}); + } + + db.doc(`/users/${req.body.user}`) + .get() + .then((doc) => { + if (doc.exists) { + let unverifiedUser = doc.data(); + unverifiedUser.verified = false; + return db.doc(`/users/${req.body.user}`).set(unverifiedUser, {merge: true}); + } else { + return res.status(400).json({error: `User ${req.body.user} was not found`}); + } + }) + .then(() => { + return res.status(201).json({message: `${req.body.user} is no longer verified`}); + }) + .catch((err) => { + console.error(err); + return res.status(500).json({error: err.code}); + }); +} exports.getUserHandles = (req, res) => { admin .firestore() diff --git a/functions/index.js b/functions/index.js index 32adda0..f05c7b8 100644 --- a/functions/index.js +++ b/functions/index.js @@ -17,6 +17,8 @@ const { signup, deleteUser, updateProfileInfo, + verifyUser, + unverifyUser, getUserHandles } = require("./handlers/users"); @@ -42,6 +44,14 @@ app.post("/updateProfileInfo", fbAuth, updateProfileInfo); app.get("/user", fbAuth, getAuthenticatedUser); +// Verifies the user sent to the request +// Must be run by the Admin user +app.post("/verifyUser", fbAuth, verifyUser); + +// Unverifies the user sent to the request +// Must be run by admin +app.post("/unverifyUser", fbAuth, unverifyUser); + // get user handles with search phase app.get("/getUserHandles", fbAuth, getUserHandles); diff --git a/twistter-frontend/src/App.js b/twistter-frontend/src/App.js index 71c16a2..aeec8ee 100644 --- a/twistter-frontend/src/App.js +++ b/twistter-frontend/src/App.js @@ -31,6 +31,7 @@ import Delete from "./pages/Delete"; import writeMicroblog from "./Writing_Microblogs.js"; import editProfile from "./pages/editProfile"; import userLine from "./Userline.js"; +import verify from "./pages/verify"; import Search from "./pages/Search.js"; const theme = createMuiTheme(themeObject); @@ -76,6 +77,7 @@ class App extends Component { + diff --git a/twistter-frontend/src/pages/user.js b/twistter-frontend/src/pages/user.js index 9f5f656..3340b47 100644 --- a/twistter-frontend/src/pages/user.js +++ b/twistter-frontend/src/pages/user.js @@ -16,8 +16,9 @@ import Grid from "@material-ui/core/Grid"; import Chip from "@material-ui/core/Chip"; import Typography from "@material-ui/core/Typography"; -import AddCircle from "@material-ui/icons/AddCircle"; -import TextField from "@material-ui/core/TextField"; +import AddCircle from '@material-ui/icons/AddCircle'; +import TextField from '@material-ui/core/TextField'; +import VerifiedIcon from '@material-ui/icons/CheckSharp'; // component import '../App.css'; @@ -72,7 +73,8 @@ class user extends Component { .then(res => { this.setState({ profile: res.data.credentials.handle, - imageUrl: res.data.credentials.imageUrl + imageUrl: res.data.credentials.imageUrl, + verified: res.data.credentials.verified ? res.data.credentials.verified : false }); }) .catch(err => console.log(err)); @@ -100,10 +102,12 @@ class user extends Component { render() { let authenticated = this.props.user.authenticated; let classes = this.props; + let profileMarkup = this.state.profile ? ( -

- {this.state.profile} -

) : (

loading username...

); +
+ @{this.state.profile} {this.state.verified ? (): (null)} +
) : (

loading username...

); + let topicsMarkup = this.state.topics ? ( this.state.topics.map( topic => ( @@ -166,7 +170,36 @@ class user extends Component { onClick={this.handleAddCircle} />
- {authenticated && } + + + { + authenticated && + } + + + { + authenticated && + this.state.profile === 'Admin' && + } + + {postMarkup} diff --git a/twistter-frontend/src/pages/verify.js b/twistter-frontend/src/pages/verify.js new file mode 100644 index 0000000..b0c6583 --- /dev/null +++ b/twistter-frontend/src/pages/verify.js @@ -0,0 +1,153 @@ +import React, { Component } from "react"; +import axios from "axios"; +import PropTypes from "prop-types"; +// TODO: Add a read-only '@' in the left side of the handle input +// TODO: Add a cancel button, that takes the user back to their profile page + +// Material-UI stuff +import Button from "@material-ui/core/Button"; +import { Link } from 'react-router-dom'; +import CircularProgress from "@material-ui/core/CircularProgress"; +import Grid from "@material-ui/core/Grid"; +import TextField from "@material-ui/core/TextField"; +import Typography from "@material-ui/core/Typography"; +import withStyles from "@material-ui/core/styles/withStyles"; + +const styles = { + form: { + textAlign: "center" + }, + textField: { + marginBottom: 30 + }, + pageTitle: { + // marginTop: 20, + marginBottom: 40 + }, + button: { + positon: "relative", + marginBottom: 10 + }, + progress: { + position: "absolute" + } +}; + +export class verify extends Component { + + // Constructor for the state + constructor() { + super(); + this.state = { + handle: "", + loading: false, + errors: {} + }; + } + +// // Runs whenever the submit button is clicked. + handleSubmit = (event) => { + event.preventDefault(); + this.setState({ + loading: true + }); + const verifyHandle = { + user: this.state.handle + }; + + axios + .post("/verifyUser", verifyHandle) + .then((res) => { + console.log(res); + this.setState({ + loading: false + }); + // this.props.history.push('/'); + // TODO: Need to redirect user to their profile page + }) + .catch((err) => { + console.log(err); + this.setState({ + errors: err.response.data, + loading: false + }); + }); + }; + + // Updates the state whenever one of the textboxes changes. + // The key is the name of the textbox and the value is the + // value in the text box. + // Also sets errors to null of textboxes that have been edited + handleChange = (event) => { + this.setState({ + [event.target.name]: event.target.value, + errors: { + [event.target.name]: null + } + }); + }; + + render() { + const { classes } = this.props; + const { errors, loading } = this.state; + + return ( + + + + + Verify Users + +
+ + + + + + + + + + +
+ + + ); + } +} + +verify.propTypes = { + classes: PropTypes.object.isRequired +}; + +export default withStyles(styles)(verify);