diff --git a/functions/handlers/users.js b/functions/handlers/users.js
index 1af539d..5fc219c 100644
--- a/functions/handlers/users.js
+++ b/functions/handlers/users.js
@@ -53,6 +53,8 @@ exports.signup = (req, res) => {
return res.status(400).json(errors);
}
+ const noImg = 'no-img.png';
+
let token, userId;
db.doc(`/users/${newUser.handle}`)
@@ -77,6 +79,7 @@ exports.signup = (req, res) => {
email: newUser.email,
handle: newUser.handle,
createdAt: newUser.createdAt,
+ imageUrl: `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${noImg}?alt=media`,
userId
};
handle2Email.set(userCred.handle, userCred.email);
@@ -207,7 +210,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
@@ -241,6 +244,7 @@ exports.getUserDetails = (req, res) => {
});
};
+// Returns all data stored for a user
exports.getAuthenticatedUser = (req, res) => {
let credentials = {};
db.doc(`/users/${req.user.handle}`)
@@ -258,4 +262,51 @@ exports.getAuthenticatedUser = (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 = {};
+
+ 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", () => {
+ admin.storage().bucket().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.status(201).json({ message: "Image uploaded successfully"});
+ })
+ .catch((err) => {
+ console.error(err);
+ return res.status(500).json({ error: err.code})
+ })
+ });
+ busboy.end(req.rawBody);
+}
diff --git a/functions/index.js b/functions/index.js
index 737d44f..47dda28 100644
--- a/functions/index.js
+++ b/functions/index.js
@@ -16,7 +16,8 @@ const {
login,
signup,
deleteUser,
- updateProfileInfo
+ updateProfileInfo,
+ uploadProfileImage
} = require("./handlers/users");
// Adds a user to the database and registers them in firebase with
@@ -39,8 +40,13 @@ 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);
+
/*------------------------------------------------------------------*
* handlers/post.js *
*------------------------------------------------------------------*/
diff --git a/functions/package.json b/functions/package.json
index 2e308b3..492ff05 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/src/pages/editProfile.js b/twistter-frontend/src/pages/editProfile.js
index ff6f3f7..39ad93f 100644
--- a/twistter-frontend/src/pages/editProfile.js
+++ b/twistter-frontend/src/pages/editProfile.js
@@ -4,13 +4,23 @@ 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
+import noImage from '../images/no-img.png';
+
// Material-UI stuff
import Button from "@material-ui/core/Button";
+import Box from "@material-ui/core/Box";
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";
+import IconButton from "@material-ui/core/IconButton";
+import EditIcon from "@material-ui/icons/Edit";
+import Tooltip from "@material-ui/core/Tooltip";
+
+// Redux stuff
+import { connect } from "react-redux";
+import { uploadImage } from "../redux/actions/userActions";
const styles = {
form: {
@@ -27,25 +37,52 @@ const styles = {
positon: "relative",
marginBottom: 30
},
+ box: {
+ position: "relative"
+ },
progress: {
position: "absolute"
+ },
+ uploadProgress: {
+ position: "absolute",
+ marginLeft: -155,
+ marginTop: 95
}
};
export class edit extends Component {
+ // mapReduxToState = (credentials) => {
+ // this.setState({
+ // imageUrl: credentials.imageUrl ? credentials.imageUrl : noImage,
+ // firstName: credentials.firstName ? credentials.firstName : '',
+ // lastName: credentials.lastName ? credentials.lastName : '',
+ // email: credentials.email ? credentials.email : 'error, email doesn\'t exist',
+ // handle: credentials.handle ? credentials.handle : 'error, handle doesn\'t exist',
+ // bio: credentials.bio ? credentials.bio : ''
+ // });
+ // };
+
+
+
// Runs as soon as the page loads.
// Sets the default values of all the textboxes to the data
// that is stored in the database for the user.
componentDidMount() {
+ // const { credentials } = this.props;
+ // console.log(this.props.user);
+ // this.mapReduxToState(credentials);
axios
.get("/getProfileInfo")
.then((res) => {
+ // Need to have the ternary if statements, because react throws an error if
+ // any of the res.data keys are undefined
this.setState({
- firstName: res.data.firstName,
- lastName: res.data.lastName,
+ imageUrl: res.data.imageUrl,
+ firstName: res.data.firstName ? res.data.firstName : "",
+ lastName: res.data.lastName ? res.data.lastName : "",
email: res.data.email,
handle: res.data.handle,
- bio: res.data.bio
+ bio: res.data.bio ? res.data.bio : ""
});
})
.catch((err) => {
@@ -63,6 +100,7 @@ export class edit extends Component {
constructor() {
super();
this.state = {
+ imageUrl: "",
firstName: "",
lastName: "",
email: "",
@@ -108,6 +146,7 @@ export class edit extends Component {
})
.catch((err) => {
console.log(err);
+ // TODO: Should redirect to login page if they get a 403
this.setState({
errors: err.response.data,
loading: false
@@ -128,10 +167,65 @@ export class edit extends Component {
});
};
+ handleImageChange = (event) => {
+ const image = event.target.files[0];
+ const formData = new FormData();
+ formData.append('image', image, image.name);
+ this.props.uploadImage(formData);
+ }
+
+ handleEditPicture = () => {
+ const fileInput = document.getElementById('imageUpload');
+ fileInput.click();
+ }
+
+ // logging = () => {
+ // console.log(this.state);
+ // console.log(this.props);
+ // this.mapReduxToState(this.props.credentials);
+ // }
+
render() {
const { classes } = this.props;
+ const uploading = this.props.UI.loading;
const { errors, loading } = this.state;
+ // let imageMarkup = this.state.imageUrl ? (
+ //
+ // ) : (
);
+
+ let imageMarkup = this.props.user.credentials.imageUrl ? (
+
+
+ {uploading && (
+
+ )}
+
+ ) : (
+
+
+ {uploading && (
+
+ )}
+
+ )
+
return (
@@ -140,6 +234,13 @@ export class edit extends Component {
Edit Profile