简体   繁体   中英

Trouble updating boolean value with save() using mongoose

So I've encountered an interesting issue where I've been able to use my front end to change a boolean value to true, but not back to false. My code:

updateUser.js (backend)

import express from 'express';
import mongoose from 'mongoose';
import User from '../models/userModel.js';

const app = express();
const uUserRouter = express.Router();

uUserRouter.post('/:id', (req, res) => {
  User.findById(req.params.id)
    .then(user => {
      user.username = req.body.username || user.username;
      user.email = req.body.email || user.email;
      user.password = req.body.password || user.password;
      user.avatar = req.body.avatar || user.avatar;
      user.isBanned = req.body.isBanned || user.isBanned;
      user.rank = req.body.rank || user.rank;

      user.save()
        .then(() => res.send('User Updated!'))
        .catch(err => res.status(400).send("Update Failed: " + err.message))
    })
    .catch(err => res.status(404).send('This user cannot be found: ' + err.message))
});

export default uUserRouter;

adminPanel.component.js (frontend)

import axios from 'axios';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Button, Container, Table } from 'react-bootstrap';
import { Link } from 'react-router-dom';

const Topic = props => (
    <tr>
        <td className="tTitle">
          {props.topic.title}
        </td>
        <td className="tDescription">
          {props.topic.description}
        </td>
        <td><Button variant="secondary" onClick={() => { props.deleteTopic(props.topic._id) }}>Delete</Button></td>
    </tr>

)
const User = props => (
    <tr>
        <td className="aPAvatar"><img src={props.user.avatar} height="15%" width="15%" alt="avatar"/></td>
        <td>{props.user.username}</td>
        <td>{props.user.rank}</td>
        <td>{props.user.isBanned ? <Button variant="primary" onClick={() => { props.unBanUser(props.user._id) }}>Unban User</Button> : <Button variant="warning" onClick={() => { props.banUser(props.user._id) }}>Ban User</Button>}</td>
        <td><Button variant="secondary" onClick={() => { props.deleteUser(props.user._id) }}>Delete</Button></td>
    </tr>
)
class AdminPanel extends Component {
    constructor(props) {
        super(props);

        this.banUser = this.banUser.bind(this);
        this.deleteTopic = this.deleteTopic.bind(this);
        this.deleteUser = this.deleteUser.bind(this);
        this.unBanUser = this.unBanUser.bind(this);

        this.state = { 
            topics: [],
            users: []
        };
    }
    componentDidMount() {
        function getTopics() {
            return axios.get('http://localhost:5000/forum/topics')
        }
        function getUsers() {
            return axios.get('http://localhost:5000/users')
        }
        Promise.all([getTopics(), getUsers()])
            .then((results) => {
                const topics = results[0].data;
                const users = results[1].data;
                this.setState({topics, users}, () => {
                    console.log(this.state);
                });

            }).catch((e) => {
                console.log('Error: ', e);
            });
        }
    listTopics = () => {
        return this.state.topics.map(currenttopic => {
            return <Topic topic={currenttopic} deleteTopic={this.deleteTopic} key={currenttopic._id} />
        })
    }

    listUsers = () => {
        return this.state.users.map(currentuser => {
            return <User user={currentuser} deleteUser={this.deleteUser} banUser={this.banUser} unBanUser={this.unBanUser} key={currentuser._id} />
        })
    }

    banUser(id) {
        if (window.confirm('Are you sure you want to ban this user?')) {
           axios.post('http://localhost:5000/users/update/'+id, {isBanned: true})
           .then(res => {console.log(res.data)})
           .catch((e) => {
               console.log('Error: ', e)
           });
           window.alert('User banned successfully');
           window.location.reload();
        } else {
            window.alert('Ban Canceled');
        }
    }

    unBanUser(id) {
        axios.post('http://localhost:5000/users/update/'+id, {isBanned: false})
        .then(res => {console.log(res.data)});
        console.log('test');
        window.alert('User Unbanned');
    }

    deleteTopic(id) {
        if (window.confirm('Are you sure you want to delete this topic?')) {
            axios.delete('http://localhost:5000/forum/topics/'+id)
               .then(res => console.log(res.data));

            this.setState({
                topics: this.state.topics.filter(el => el._id !== id)
            })
            window.alert('Topic Deleted');
        } else {
            window.alert('Deletion Canceled');
        }
    }

    deleteUser(id) {
        if (window.confirm('Are you sure you want to delete this user?')) {
            axios.delete('http://localhost:5000/users/delete/'+id)
            .then(res => console.log(res.data));

            this.setState({
                users: this.state.users.filter(el => el._id !== id)
            })
            window.alert('User Deleted')
        } else {
            window.alert('Deletion Canceled')
        }
    }

    render() {
        return (
          <Container>
            {this.props.isLoggedIn && this.props.rank === 'Admin' ? 
            <Container>
                <div className="userList">
                <h3>Users</h3>
                <Table>
                    <thead>
                        <tr>
                            <td>Avatar</td>
                            <td>Username</td>
                            <td>Rank</td>
                            <td>Ban Hammer</td>
                            <td>Delete</td>
                        </tr>
                    </thead>
                    <tbody>
                        {this.listUsers()}
                    </tbody>
                </Table>

            </div>
            <div className="topicList">
                <h3>Topics</h3>
                <Table>
                    <thead>
                        <tr>
                            <td>Title</td>
                            <td>Discription</td>
                            <td>Delete</td>
                        </tr>
                    </thead>
                    <tbody>
                        {this.listTopics()}
                    </tbody>
                </Table>
                <Link to="/new-topic">
                  <Button variant="secondary" className="newTopic">Create a Topic</Button>
                </Link>
              </div>
            </Container>
            :
            <Container>
                <h1>You must be logged in and an Administrator to view this page. <Link to='/'>Return to Home</Link></h1>
            </Container>
            }
            
          </Container>
        )
    };
}

const mapSateToProps = state => {
    return {
        isLoggedIn: state.login.loggedIn,
        rank: state.login.user.rank
    }
}

export default connect(mapSateToProps)(AdminPanel);

userModel.js

import mongoose from 'mongoose';
import bcrypt from 'bcrypt'
const Schema = mongoose.Schema;
const SALT_WORK_FACTOR = 10;


const userSchema = new Schema({
  username: {
    type: String,
    required: true,
    trim: true,
    /*validate: {
      validator: function(username) {User.doesNotExist({ username })},
      message: "Username already exists"
    },*/
    minlength: 5
  },
  email: { 
    type: String,
    /*validate: {
      validator: function(email) {User.doesNotExist({ email })},
      message: "Email already exists"
    }*/
  },
  password: {type: String,
    required: true,
    index: {unique: true} },
  avatar: {
    type: String,
    default: `${process.env.AWSFILESERICE}/pics/defaultPP.png`
  },
  isBanned: {
    type: Boolean,
    default: false
  },
  rank: {
    type: String,
    default: "Default"
  },
  joined: {
    type: Date
  },
  topics: [{type: Schema.Types.ObjectId, ref: 'Topics'}],
  threads: [{type: Schema.Types.ObjectId, ref: 'Topics.Threads'}],
  posts: [{type: Schema.Types.ObjectId, ref: 'Topics.Threads.Posts'}]
}, { timestamps: true});

userSchema.pre('save', function(next) {
  const user = this;
  if (!user.isModified('password')) return next();

  bcrypt.genSalt(SALT_WORK_FACTOR, function(err, salt) {
    if (err) return next(err);
    bcrypt.hash(user.password, salt, function(err, hash) {
      if (err) return next(err);
      user.password = hash;
      next();
    });
  });
});

userSchema.statics.doesNotExist = async function (field) {
  return await this.where(field).countDocuments() === 0;
}

userSchema.methods.authenticate = function(candidatePassword, cb) {
    bcrypt.compare(candidatePassword, this.password, function(err, isMatch) {
      if (isMatch === true) {
        return cb(null, isMatch);
      } else {
        return cb();
      }
    })
  };

const User = mongoose.model('User', userSchema);

export default User;

(ignore the commented out validation field, its an uncompleted experimentation:) )

Every other post to the update route (which should be "users/update/#id") works. ie username, password, email, even isBanned = true. Changing it back to false is not working. Yes, the unban button does "work" it sends the request to the backend like its supposed to and the backend sends "user updated" back to the frontend but the isBanned value stays the same in the database. I'm hoping I just overlooked something, but I read everything I could to solve the issue along with rereading my code... Thanks in advance for your help!

Solution

This solution should work if you want to use boolean values strictly,

... // Other user schema data
isBanned: String

Then when saving it use value.toString() to convert the boolean to string, so it saves as an string that contains true/false value, then when needed use

 var originalValue = false; var toSave = originalValue.toString(); console.log(toSave + ", It's a "+typeof toSave); // When needed to check if the user is banned or not // I'll ignore DB things var isBanned = JSON.parse(toSave); console.log(isBanned + ", It's a "+typeof isBanned);

For me is the easiest and the best solution

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM