I'm trying to build a user model with some privileges.
Schema looks like this:
var mongoose = require("mongoose"),
passportLocalMongoose = require("passport-local-mongoose");
let userSchema = new mongoose.Schema({
username:
{type: String,
unique: true
},
password: String,
privileges:
[{
region: { type: Number, unique: true },
read: Number,
write: Number,
edit: Number
}]
});
userSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", userSchema);
POST route looks like this.
router.post('/register', function(req, res)
{
console.log(req.body);
User.register(new User({
username: req.body.username,
privileges:{
region: req.body.privileges['region'],
read: req.body.privileges['read'],
write: req.body.privileges['write'],
edit: req.body.privileges['edit']
}
}), req.body.password, function(err)
{
if(err)
{
console.log(err);
res.redirect("/register");
}
else
{
console.log("fine");
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login'
})
}
})
});
<form action="/register" method="POST">
<input type="text" name="username"><br/>
<input type="password" name="password"><br/>
<input type="text" name="privileges[region]"><br/>
<input type="text" name="privileges[read]"><br/>
<input type="text" name="privileges[write]"><br/>
<input type="text" name="privileges[edit]"><br/>
<input type="text" name="privileges[delete]"><br/>
<button>Submit</button>
</form>
Basically, it should work like this:
From the form I should get an array of privileges.
Now when I enter data in the fields like this:
test
1234
1 2
1 1
1 1
1 1
(test - username, 1234 - password, 1 2 region array, 1 1 - read array, 1 1 write array, 1 1 edit array) I get this error:
Now I get the reason - privileges[edit] is type="text" and it can't be parsed into the DB as a Number. But why does it happen ONLY for edit? I find it strange.
I tried changing input type to number, but after that I can't enter an array anymore.
I think I might need a middleware which transforms the text into numbers. Am I right? If so, how should it do it? Should it transform each element of the array individually or the array as a whole?
Thanks.
In the schema defined for User, privileges is an array of SubDocuments having this schema.
{
region: { type: Number, unique: true },
read: Number,
write: Number,
edit: Number
}
When setting this field, the data provided needs to match that schema. e.g.
new User({
username: req.body.username,
privileges: [
{
region: ":region_value",
read: ":read_value",
write: ":write_value",
edit: ":edit_value"
},
{
region: ":region_value",
read: ":read_value",
write: ":write_value",
edit: ":edit_value"
},
//....
],
})
I assume that the design for privileges was done purposely in this way to allow for a user to have many privileges.
A straightforward way to set privileges is to design the form appropriately. The form field can allow for setting several privileges. For example, to set two privileges, you can achieve that by writing the markup this way:
<input type="text" name="privileges[0][region]"><br/>
<input type="text" name="privileges[0][read]"><br/>
<input type="text" name="privileges[0][write]"><br/>
<input type="text" name="privileges[0][edit]"><br/>
<input type="text" name="privileges[0][delete]"><br/>
<input type="text" name="privileges[1][region]"><br/>
<input type="text" name="privileges[1][read]"><br/>
<input type="text" name="privileges[1][write]"><br/>
<input type="text" name="privileges[1][edit]"><br/>
<input type="text" name="privileges[1][delete]"><br/>
This way privileges in the req.body will have the right format e.g.
{ privileges:
[ { region: '1', read: '2', write: '2', edit: '2', delete: '4' },
{ region: '2', read: '4', write: '4', edit: '4', delete: '4' } ] }
So that you can simply write
new User({
username: req.body.username,
privileges: req.body.privileges
})
It's more straightforward to ensure the client passes the right data than trying to massage the data after the fact.
A limit in the design of the form this way means that the number of privileges that a user can have has to be determined ahead of time. A work around this is to build the form dynamically and give control to the user to add more privileges as the case may be. See the following example to get an idea about how to go about it:
function addPrivilege(e) {
e.preventDefault();
const privileges = $('.privileges');
const lastCount = privileges.length;
console.log($(this).data('template').replace(/:x:/g, lastCount))
const template = $(this).data('template').replace(':x:', lastCount);
privileges.after($('<div class="privileges"></div>').append(template))
}
$(document).ready(function () {
$("#addPrivilegeBtn").on('click', addPrivilege);
});
.privileges {
background: #ccc;
padding: 8px 16px;
margin: 4px 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form>
<div class="privileges">
<input type="text" name="privileges[0][region]"><br/>
<input type="text" name="privileges[0][read]"><br/>
<input type="text" name="privileges[0][write]"><br/>
<input type="text" name="privileges[0][edit]"><br/>
<input type="text" name="privileges[0][delete]"><br/>
</div>
<button
id="addPrivilegeBtn"
data-template='<input type="text"name="privileges[:x:][region]"><br/>
<input type="text" name="privileges[:x:][read]"><br/>
<input type="text" name="privileges[:x:][write]"><br/>
<input type="text" name="privileges[:x:][edit]"><br/>
<input type="text" name="privileges[:x:][delete]"><br/>'
>Add privilege</button>
</form>
Related
I'm trying to make a simple blog app using NodeJS, MongoDB and Express. My goal here is to be able to have the user write a title and text, have it go to my MongoDB database and have it show in a new, redirected page according to the ID of the new submission. However this is not working, the page will redirect and show "Cannot GET /users/postpage/(ID here)"
Here are my file paths:
config -->
|auth.js
|keys.js
|passport.js
models -->
|Createpost.js
|User.js
routes -->
|index.js
|users.js
views -->
dashboard.ejs
postpage.ejs
app.js
My code in Createpost.js:
const TextSchema = new mongoose.Schema({
postTitle: {
type: String,
required: true
},
postText: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
});
const Createpost = mongoose.model('Createpost', TextSchema);
My code in users.js:
router.get('postpage/:/id', (req,res)=>{
res.send(req.params.id)
})
router.post('/postpage', async (req,res)=> {
let newPost = new Createpost({
postTitle:req.body.postTitle,
postText:req.body.postText
})
try {
newPost = await newPost.save()
res.redirect(`/users/postpage/${newPost.id}`)
} catch (error){
console.log(error)
res.render('users/postpage', {newPost:newPost})
}
})
My code in postpage.ejs:
<form action="/users/postpage" method="POST">
<div class="form-group">
<label for="postTitle">Title</label>
<input required
type="postTitle"
id="postTitle"
name="postTitle"
class="form-control"
placeholder="Enter title"
value="<%= typeof postTitle != 'undefined' ? postTitle : '' %>"
/>
</div>
<div class="form-group">
<label for="postText">text</label>
<input required
type="postText"
id="postText"
name="postText"
class="form-control"
placeholder="Enter text"
value="<%= typeof postText != 'undefined' ? postText : '' %>"
/>
</div>
I am trying to create simple web app to receive dog adoption applications.
I succesfully run migrations and seeds and by doing so created these two tables:
The problem is that when I try to create new application using GUI, I get the below error:
{"response":"Error in database ForeignKeyViolationError: insert into applications (doggo_name, email, name, phone) values ('Coco', 'sam.do#gmail.com', 'Sam Do', '+12345667') - ER_NO_REFERENCED_ROW_2: Cannot add or update a child row: a foreign key constraint fails (dog_adoption.applications, CONSTRAINT applications_doggo_id_foreign FOREIGN KEY (doggo_id) REFERENCES doggos (id))"}
This is second day I am trying to figure out what is wrong. Please see my code:
MIGRATION FILE:
exports.up = function(knex) {
return knex.schema
.createTable('doggos', (table) => {
table.increments('id').notNullable();
table.string('doggo').notNullable();
table.integer('age').notNullable();
table.string('breed').notNullable();
table.string('picture').notNullable();
})
.createTable('applications', (table) => {
table.increments('id').notNullable();
table.string('name').notNullable();
table.string('email').notNullable();
table.integer('phone').notNullable();
table.string('doggo_name').notNullable();
table.integer('doggo_id').unsigned().notNullable();
table.foreign('doggo_id').references('doggos.id');
table.dateTime('updated_at').defaultTo(knex.raw('NULL ON UPDATE CURRENT_TIMESTAMP'));
table.dateTime('created_at').notNullable().defaultTo(knex.raw('CURRENT_TIMESTAMP'));
});
};
APPLICATION seed:
exports.seed = function(knex) {
return knex('doggos').select().then(doggos => {
return knex('applications').insert([
{ name: "xxxxxxxxx", email: "peggy33#gmail.com", phone: 79187877, doggo_name: 'Coco', doggo_id: doggos.find(doggo => doggo.doggo === 'Coco').id},
{ name: "xxxxxxxxxxxxx", email: "watson.dddk#gmail.com", phone: 51393129, doggo_name: 'Tyson', doggo_id: doggos.find(doggo => doggo.doggo === 'Tyson').id},
{ name: "xxxxxxxxxxxxx", email: "ravsp33#gmail.com", phone: 12345678, doggo_name: 'Nicky', doggo_id: doggos.find(doggo => doggo.doggo === 'Nicky').id}
]);
});
};
HTML FORM:
<form action="/apply" method="POST">
<div class="application-container">
<label for="name">What is your name?</label>
<input type="text" placeholder="Your name" name="name" required>
<label for="email">E-mail address</label>
<input type="text" placeholder="e-mail" name="email" required>
<label for="phone">Phone number</label>
<input type="text" placeholder="phone" name="phone" required>
<label for="doggo_name">Name of dog you are interested with</label>
<input type="text" placeholder="Name of dog you are interested with" name="doggo_name" required>
<button class="btn btn-primary" type="submit">Submit</button>
<button class="btn btn-primary" onclick="window.location.href='/'">Cancel</button>
</div>
</form>
</body>
ROUTE:
router.post('/apply', async (req,res) => {
const { name, email, phone, doggo_name } = req.body;
console.log(name, email, phone, doggo_name);
try {
const submittedApplication = await Application.query().insert({
name,
email,
phone,
doggo_name,
// how to pass doggo_id to the database?
});
return res.send({ response: `Succesfully applied for adoption. Please wait patiently for our response!`});
} catch (error) {
return res.send({ response: "Error in database " + error });
}
});
I would really appreciate if somebody could look at it with a fresh eye nd give me a hand with data persistence to my 'applications' table.
You make the doggo_id not nullable, so it will get 0 as default for all existing rows instead of NULL.
But then you also set it as foreign key to doggos.id. The foreign key constraint immediately fails on all rows since they would now all reference the doggo with ID 0 which presumably doesn't exist.
You can solve that problem by making it nullable (remove notNullable() from doggo_id), which works because NULL means "not referencing anything at the moment" (as opposed to "referencing the doggo with ID zero"), or by setting a default of an ID that belongs to an actually existing doggo and not zero, if that makes sense in your use case.
I'm using mongoose, adding values from HTML and saving to db with help of mongoose. I'm having issue adding value from req.body.chapter into array from HTML.
Route:
const newBook = {
book: req.body.book,
summary: req.body.summary,
chapters: //how to add value into chapter-array?
}
Book-model:
const BookSchema = new Schema({
Title: {
type: String,
required: true
},
Summary: {
type: String,
required: true
},
chapters : [{
chapter: {
type: String,
required: true
}]
});
HTML:
<div class="form-group">
<label for="book">Title:</label>
<input type="text" class="form-control" name="book" required>
</div>
<div class="form-group">
<label for="Summary">Summary:</label>
<input type="text" class="form-control" name="Summary" required>
</div>
<div class="form-group">
<label for="chapter">chapter:</label>
<input type="text" class="form-control" name="chapter" required>
</div>
So if the data looks like this, you can just put it in:
req.body.chapters = ["1", "3"];
Route:
const newBook = {
book: req.body.book,
summary: req.body.summary,
chapters: req.body.chapters
}
If you just want to add the chapters to existing data, then have a look at Using Mongoose / MongoDB $addToSet functionality on array of objects
How can I find the document matching the ID being posted in a hidden input?
Here is my schema:
var MessageSchema = Schema({
name: {type: String, required: true},
message: {type: String, required: true},
replies: [{ type: Schema.Types.ObjectId, ref: 'Comment' }]
}, {timestamps: true});
Here is my form:
<% for(var i=0; i<messages.length; i++) { %>
<form action="/comment/create" method="post">
<label>Name: </label>
<input type="name" name="name">
<label>Comment: </label>
<input type="text" name="comment">
<input type="hidden" name="replyTo" value=<%= messages[i]['_id']%> >
<button>Reply</button>
</form>
<% } %>
And my post route is a mess so at this point I just want to know how to find the dang message.
app.post('/comment/create', function(req, res) {
console.log(req.body.replyTo);
var message = Message.find({ _id: req.body.replyTo });
console.log(message);
res.redirect('/');
})
console.log(req.body.replyTo) returns the id 59022ff22951ce73ed9bb773.
console.log(message) returns undefined.
The call to Messages.find is asynchronous, and therefore will not provide a proper value when assigning it's result to the variable message. You need to handle this either with a callback or a Promise:
As a callback
Message.find({ _id: req.body.replyTo }, (err, res) => {
console.log(res); // message
});
As a Promise
Message.find({ _id: req.body.replyTo })
.then((res) => {
console.log(res); // message
})
.catch((err) => {
// ...
});
I am trying to figure out how it is possible to pass an array as the value for the property of an instance. I currently have the dataType set to STRING in my model and have values from jQuery fields insert each form field value into an array that I parse from the body and set to the property, discoverSource. Unfortunately I receive a string violation error that says I can't use an array or object. What does this mean and how can I change the dataType of the field or route to allow me to pass the comma separated values to the field?
E.x. For discoverySource I pass values to two fields (NJ, NY). On submit, the values are combined in an array as ["NJ", "NY"] and the error displays:
Error Message:
{"name":"SequelizeValidationError","message":"string violation: discoverySource cannot be an array or an object","errors":[{"message":"discoverySource cannot be an array or an object","type":"string violation","path":"discoverySource","value":["NJ","NY"]}]}
Here is my model:
module.exports = function(sequelize, DataTypes) {
var Organization = sequelize.define('organization', {
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
autoIncrement: true,
primaryKey: true
},
organizationName: {
type: DataTypes.STRING,
field: 'organization_name'
},
admin: DataTypes.STRING,
discoverySource: {
type: DataTypes.TEXT,
field: 'discovery_source'
},
members: DataTypes.STRING
},{
freezeTableName: true,
classMethods: {
associate: function(db) {
Organization.belongsToMany(db.User, { through: 'member', foreignKey: 'user_id' });
},
},
});
return Organization;
}
Here is the route:
var express = require('express');
var appRoutes = express.Router();
var passport = require('passport');
var localStrategy = require('passport-local').Strategy;
var models = require('../models/db-index');
appRoutes.route('/sign-up/organization')
.get(function(req, res){
models.User.find({
where: {
user_id: req.user.email
}, attributes: [ 'user_id', 'email'
]
}).then(function(user){
res.render('pages/app/sign-up-organization.hbs',{
user: req.user
});
})
})
.post(function(req, res, user){
models.Organization.create({
organizationName: req.body.organizationName,
admin: req.body.admin,
discoverySource: req.body.discoverySource
}).then(function(organization, user){
res.redirect('/app');
}).catch(function(error){
res.send(error);
console.log('Error at Post' + error);
})
});
Here is my view file:
<!DOCTYPE html>
<head>
{{> head}}
</head>
<body>
{{> navigation}}
<div class="container">
<div class="col-md-6 col-md-offset-3">
<form action="/app/sign-up/organization" method="post">
<p>{{user.email}}</p>
<input type="hidden" name="admin" value="{{user.email}}">
<input type="hidden" name="organizationId">
<label for="sign-up-organization">Company/Organization Name</label>
<input type="text" class="form-control" id="sign-up-organization" name="organizationName" value="" placeholder="Company/Organization">
Add Another Discovery Source
<div id="sign-up-organization-discovery-source">
<input type="text" id="discovery-source-field" placeholder="Discovery Source" name="discoverySource[0]">
</div>
<br />
<button type="submit">Submit</button>
</form>
Already have an account? Login here!
</div>
</div>
<script type="text/javascript">
$(function() {
var dataSourceField = $('#sign-up-organization-discovery-source');
var i = $('#sign-up-organization-discovery-source p').size();
var sourceCounter = 1;
$('#sign-up-add-discovery-source').on('click', function() {
$('<p><label for="discovery-source-field"><input type="text" id="discovery-source-field" size="20" name="discoverySource['+ sourceCounter++ +']" value="" placeholder="Discovery Source" /></label> Remove</p>').appendTo(dataSourceField);
i++;
return false;
});
$('#sign-up-organization-discovery-source').on('click', '.remove', function() {
if (i > 1) {
$(this).parent('p').remove();
i--;
}
return false;
});
});
</script>
</body>
To answer the last comment, I need to be able to make the code more readable, so I'm posting it here in a new answer.
Having thought about it a little more, it would make more sense to add it as custom 'getter' function. I'll also include the 'instanceMethods' to demonstrate how that works, as well.
var Organization = sequelize.define('organization', {
...
},{
freezeTableName: true,
classMethods: {
associate: function(db) {
Organization.belongsToMany(db.User, { through: 'member', foreignKey: 'user_id' });
},
},
// Here's where custom getters would go
getterMethods: {
discoverySources: function() {
return this.getDataValue('discoverySource');
}
},
// here's the instance methods
instanceMethods: {
getSourcesArray: function() {
return this.getDataValue('discoverySource');
}
}
});
Both of these options add the functions to each instance created by the Model. The main difference being in how they are accessed.
organization.discoverySources; // -> ['s1', 's2', etc...]
organization.getSourcesArray(); // -> ['s1', 's2', etc...]
note the additional () required on the instanceMethod. Those are added as functions of the instance, the getterMethods get added as properties.
setterMethods work the same way to allow you to define custom setters.
Hope that clarifies things a bit.