I am working through the full stack certification on devchallenges.io and I'm doing the authentication app challenge. So far I have been able to create the login and register functionality and have been able to set up the functionality to get the logged in user and display their information however, when trying to upload a file on the front end, the image upload doesn't seem to work. It works perfectly fine in Postman as shown in this video. On the front end, other fields seem to get updated such as the name, bio. Example of the error here.
Github source code: https://github.com/gbopola/Auth-App
server.js
const express = require('express');
const connectDB = require('./config/db');
const app = express();
const { check, validationResult } = require('express-validator');
const User = require('./models/User');
const gravatar = require('gravatar');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const config = require('config');
const auth = require('./middleware/auth');
const cloudinary = require('./utils/cloudinary');
const upload = require('./utils/multer');
// Connect database
connectDB();
// Init Middleware
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ limit: '50mb', extended: true }));
// @route POST /register
// @desc Register user
// @access Public
app.post(
'/register',
[
check('email', 'Please include a valid email').isEmail(),
check('password', 'Please enter a password').notEmpty(),
],
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const { email, password } = req.body;
try {
// See if user exists
let user = await AuthUser.findOne({ email });
if (user) {
return res
.status(400)
.json({ errors: [{ msg: 'User already exists' }] });
}
// Get users gravatar
const avatar = gravatar.url(email, {
s: '200',
r: 'pg',
d: 'mm',
});
user = new AuthUser({
email,
avatar,
password,
});
// Encrypt password
const salt = await bcrypt.genSalt(10);
user.password = await bcrypt.hash(password, salt);
await user.save();
// Return jsonwebtoken
const payload = {
user: {
id: user.id,
},
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: '5 days' },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
} catch (error) {
console.error(error.message);
res.status(500).send('Server error');
}
}
);
// @route POST /login
// @desc Authenticate user & get token
// @access Public
app.post(
'/login',
check('email', 'Please include a valid email').isEmail(),
check('password', 'Password is required').exists(),
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array(),
});
}
const { email, password } = req.body;
try {
// See if user exists
let user = await User.findOne({ email });
if (!user) {
return res
.status(400)
.json({ errors: [{ msg: 'Invalid credentials' }] });
}
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
return res
.status(400)
.json({ errors: [{ msg: 'Invalid credentials' }] });
}
// Return jsonwebtoken
const payload = {
user: {
id: user.id,
},
};
jwt.sign(
payload,
config.get('jwtSecret'),
{ expiresIn: '5 days' },
(err, token) => {
if (err) throw err;
res.json({ token });
}
);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
}
);
// @route GET /profile
// @desc Get full user profile
// @access Private
app.get('/profile', auth, async (req, res) => {
try {
let user = await User.findById(req.user.id).select('-password');
res.json(user);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
});
// @route POST /profile/edit/:id
// @desc edit profile
// @access Private
app.put('/profile/edit/:id', upload.single('image'), auth, async (req, res) => {
const { name, bio, email, phone, password } = req.body;
try {
let user = await AuthUser.findById(req.params.id);
// Delete image from cloudinary
if (user.cloudinary_id !== '')
await cloudinary.uploader.destroy(user.cloudinary_id);
// Upload image to cloudinary
let result;
if (req.file) {
result = await cloudinary.uploader.upload(req.file.path);
}
const data = {
name: name || user.name,
avatar: (result && result.secure_url) || user.avatar,
bio: bio || user.bio,
email: email || user.email,
phone: phone || user.phone,
password: password || user.password,
cloudinary_id: (result && result.public_id) || user.cloudinary_id,
};
if (password !== '') {
// Encrypt password
const salt = await bcrypt.genSalt(10);
data.password = await bcrypt.hash(password, salt);
}
// Update
user = await User.findByIdAndUpdate(req.params.id, data, { new: true });
return res.json(data);
} catch (err) {
console.error(err.message);
res.status(500).send('Server error');
}
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
Auth Action.js
// Update use profile
export const updateProfile = ({
name,
bio,
phone,
email,
password,
id,
profileImg,
navigate,
}) => {
return async (dispatch) => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
const body = JSON.stringify({
name,
bio,
phone,
email,
password,
id,
profileImg,
});
try {
const res = await axios.put(`/profile/edit/${id}`, body, config);
dispatch({
type: PROFILE_UPDATE_SUCCESS,
payload: res.data,
});
navigate('/profile');
} catch (error) {
console.log(error);
}
};
};
import React, { useEffect, useState, useRef } from 'react';
import { Navbar } from './Navbar';
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux';
import { loadUser } from '../redux/actions/auth';
import { Link, useParams, useNavigate } from 'react-router-dom';
import { store } from '../store';
import { updateProfile } from '../redux/actions/auth';
export const EditProfile = () => {
const state = useSelector((state) => state.auth);
const { id } = useParams();
const dispatch = useDispatch();
const navigate = useNavigate();
// States
const [isEditing, setEdit] = useState(false);
const [profileImg, setImg] = useState(state.user.avatar);
const [formData, setFormData] = useState({
name: '',
bio: '',
phone: '',
email: '',
password: '',
});
const { email, password, bio, phone, name } = formData;
const inputFile = useRef(null);
let styles = {
width: '72px',
height: '72px',
borderRadius: '8px',
backgroundImage: `url(${!isEditing ? state.user.avatar : profileImg})`,
backgroundPosition: 'center',
backgroundSize: 'cover',
position: 'relative',
};
// handle image change
const imageHandler = (e) => {
const reader = new FileReader();
reader.onload = () => {
if (reader.readyState === 2) {
setImg(reader.result);
setEdit(true);
}
};
if (e.target.files[0]) {
reader.readAsDataURL(e.target.files[0]);
}
};
const changePhoto = () => {
inputFile.current.click();
};
const onChange = (e) => {
setFormData({ ...formData, [e.target.name]: e.target.value });
};
const changeInfo = () => {
dispatch(
updateProfile({
name,
bio,
phone,
email,
password,
id,
profileImg,
navigate,
})
);
};
return (
<div className="EditProfile">
<Navbar />
<div className="edit-profile-container">
<div className="back-to-profile">
<Link className="link-to-profile" to="/profile">
<span>
<i className="fas fa-chevron-left"></i>
</span>
Back
</Link>
</div>
<div className="profile-wrapper">
<div className="profile-heading">
<div>
<h2>Change Info</h2>
<p className="personal-info-grey">
Changes will be reflected to every services
</p>
</div>
</div>
<div className="profile-photo">
<input
type="file"
accept="image/*"
name="image-upload"
id="upload"
onChange={imageHandler}
ref={inputFile}
/>
<div className="example" onClick={changePhoto}>
<i className="fas fa-camera"></i>
<div id="overlay"></div>
<div id="profile-img-edit" style={styles}></div>
</div>
<p className="personal-info-grey change-photo">CHANGE PHOTO</p>
</div>
<div className="name">
<label>Name</label>
<input
type="text"
className="edit-profile-input"
placeholder="Enter your name"
name="name"
value={name}
onChange={(e) => onChange(e)}
/>
</div>
<div className="bio">
<label>Bio</label>
<textarea
className="edit-profile-input"
id="bio"
placeholder="Enter your bio"
name="bio"
value={bio}
onChange={(e) => onChange(e)}
/>
</div>
<div className="phone">
<label>Phone</label>
<input
type="text"
className="edit-profile-input"
placeholder="Enter your phone"
name="phone"
value={phone}
onChange={(e) => onChange(e)}
/>
</div>
<div className="email">
<label>Email</label>
<input
type="text"
className="edit-profile-input"
placeholder="Enter your email"
name="email"
value={email}
onChange={(e) => onChange(e)}
/>
</div>
<div className="password">
<label>Password</label>
<input
type="password"
className="edit-profile-input"
placeholder="Enter your password"
name="password"
value={password}
onChange={(e) => onChange(e)}
/>
<button className="edit-save" onClick={changeInfo}>
Save
</button>
</div>
</div>
</div>
</div>
);
};
CodePudding user response:
Usually you don't send the image to the user, you just send the url that leads to the image.
CodePudding user response:
Normally passing images to the backend does not work unless you append it to a new form data -------------Example----------------- const formData = new FormData() formData.append('image', image)
then send formData as an object