I recently started working on a project in which the administrator can create tours online by filling out a form. By completing the form the information to be introduced intro a Mongoose Schema and create JSON.
In createcontent.js file takes the data from the form, I used new FormData(); because to upload images i use a module call multer.
const createTour_form = document.querySelector('.form-create__tour');
if (createTour_form) {
createTour_form.addEventListener('submit', (cr) => {
cr.preventDefault();
const create = new FormData();
// Name
create.append('name', document.getElementById('tour_name').value);
//Tour Duration
create.append('duration', document.getElementById('tour_duration').value);
//Number Participoants
create.append('maxGroupSize', document.getElementById('tour_participants').value);
//Tour Difficulty
create.append('difficulty', document.getElementById('tour_difficulty').value);
//Tour Price
create.append('price', document.getElementById('tour_prices').value);
//Short Description
create.append('summary', document.getElementById('tour_short_des').value);
//Description
create.append('description', document.getElementById('tour_long_des').value);
createTour(create);
})
}
I use a module slugify to convert the tour name to a slug that I use in the url.
This is my mongoose schema:
const tourchema = new mongoose.Schema({
name: {
type: String,
require: [true, 'A tour mush have a name'],
unique: true,
trim: true,
maxlength: [50, 'A tour name must have less or equal then 50 characters'],
minlength: [10, 'A tour name must have more or equal then 10 characters'],
//validate:[validator.isAlpha, 'Tour name must only contain characters']
},
slug: {
formType: String
},
duration: {
type: Number,
require: [true, 'A tour must have a duration']
},
maxGroupSize: {
type: Number,
require: [true, 'A tour must have a group size']
},
difficulty: {
type: String,
require: [true, 'A tour must have a difficulty'],
enum: {
values: ['easy', 'medium', 'difficult'],
message: 'Difficulty is either: easy, medium, difficult'
}
},
ratingsAverage: {
type: Number,
default: 4.5,
min: [1, 'Raiting must be above 1.0'],
max: [5, 'Raiting must be below 5.0'],
set: val => Math.round(val * 10) / 10
},
ratingsQuantity: {
type: Number,
default: 0
},
price: {
type: Number,
require: [true, 'A tour must have a price']
},
priceDiscount: {
type: Number,
validate: {
validator: function (val) {
//his only points to create doc on new document creation
return val < this.price;
},
message: 'Discount price ({VALUE}) shoud be below the regular price'
}
},
summary: {
type: String,
trim: true,
require: [true, 'A tour must have a description']
},
description: {
type: String,
trim: true
},
imageCover: {
type: String,
require: [true, 'A tour must have a cover image']
},
images: [String],
createdAt: {
type: Date,
default: Date.now()
},
startDates: [Date],
secretTour: {
type: Boolean,
default: false
},
startLocation: {
//GeoJSON
type: {
formType: String,
default: 'Point',
enum: ['Point']
},
coordinates: [Number],
adress: String,
description: String
},
locations: [
{
type: {
type: String,
default: 'Point',
enum: ['Point']
},
coordinates: [Number],
adress: String,
description: String,
day: Number
}
],
// guides:Array
guides: [
{
type: mongoose.Schema.ObjectId,
ref: 'User'
}
]
}
tourchema.index({ slug: 1 });
tourchema.pre('save', function (next) {
this.slug = slugify(this.name, { lower: true });
next();
});
When i upload data from form unsign axios winth asynchronus function:
import axios from 'axios';
import { showAlert } from './alert';
export const createTour = async(data)=>{
try{
const create_tour = await axios({
method:'POST',
url:'http://127.0.0.1:3000/api/v1/tours',
data:data
});
}
catch(err){
console.log(err);
showAlert('error',err.response.data.message);
}
}
when the referral action takes place an error occurs slugify: string argument expected
I did not find anything on the internet about this error.
I tried to build my own function to replace the module but it didn't work there is no solution to solve this error??
You can convert the "FormData" to an Object and then parse it.
function formDataToObj(formData) {
let obj = {};
for (let key of formData.keys()) {
obj[key] = formData.get(key)
}
return obj;
}
Example:
function formDataToObj(formData) {
let obj = {};
for (let key of formData.keys()) {
obj[key] = formData.get(key)
}
return obj;
}
const fd = new FormData()
fd.append('a', 1)
fd.append('b', 2)
const obj = formDataToObj(fd)
console.log( obj )
const json = JSON.stringify( obj )
console.log( json )
UPDATE: A better approach could be by using the native way like this:
Object.fromEntries(formData.entries())
Example:
const fd = new FormData()
fd.append('a', 1)
fd.append('b', 2)
const obj = Object.fromEntries(fd.entries())
console.log( obj )
const json = JSON.stringify( obj )
console.log( json )
Related
Hey I'm trying to build dashboard for calculate my medicine supplier total outstanding using nextjs.
*below *is my **createPurchase **api.
`
import PurchaseOrder from '../../models/PurchaseOrder'
import Supplier from '../../models/Supplier'
import connectDB from '../../middleware/mongoose';
import Medicine from '../../models/Medicine';
const handler = async (req, res) => {
if (req.method === 'POST') {
const medicines = [];
let totalOrderAmount = 0;
let totalPaybleGst = 0;
req.body.medicines.forEach(async medicine => {
let medicineOne = await Medicine.findById(medicine.medicine)
let newQuantity = parseInt(medicineOne.quantity) + parseInt(medicine.quantity)
const filter = { _id: medicine.medicine };
const update = { quantity: newQuantity };
await Medicine.findByIdAndUpdate(filter, update);
let newmedi = {
name: medicine.name,
company: medicine.company,
medicine: medicineOne,
quantity: newQuantity,
pack_detail: medicine.pack_detail,
category: medicine.category,
batch: medicine.batch,
mrp: medicine.mrp,
rate: medicine.rate,
gst: medicine.gst,
totalAmount: medicine.totalAmount,
expiryDate: medicine.expiryDate
}
totalOrderAmount += medicine.totalAmount;
totalPaybleGst += medicine.gst * medicine.rate * medicine.quantity * 0.01;
medicines.push(newmedi);
})
const paidAmount = req.body.paidAmount
const supplierBeforeUpdate = await Supplier.findById(req.body.supplier);
const newOustanding = supplierBeforeUpdate.totalOutstanding + totalPaybleGst + totalOrderAmount - paidAmount;
const filter = { _id: req.body.supplier };
const update = { totalOutstanding: newOustanding };
await Supplier.findOneAndUpdate(filter, update);
const supplierAffterUpdate = await Supplier.findById(req.body.supplier);
const purchaseOrder = await PurchaseOrder.create({
supplier: supplierAffterUpdate,
createdBy: req.body.createdBy,
medicines: medicines,
paybleGst: totalPaybleGst,
totalAmount: totalOrderAmount,
grandTotal: totalPaybleGst + totalOrderAmount,
paidAmount: paidAmount
})
res.status(200).json({ success: true, purchaseOrder: purchaseOrder })
}
else {
res.status(400).json({ error: "This method is not allowed" })
}
}
export default connectDB(handler);
`
this is my purchaseOrder Schema
`
const mongoose = require('mongoose');
const { Schema, model, models } = mongoose;
const medicinePurchaseSchema = new Schema({
name: { type: String, required: true },
company: { type: String, required: true },
medicine: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'Medicine'
},
quantity: { type: Number, required: true },
pack_detail: { type: String, required: true },
batch: { type: String, required: true },
mrp: { type: Number, required: true },
rate: { type: Number, required: true },
gst: { type: Number, required: true },
totalAmount: { type: Number, required: true },
expiryDate: { type: Date, required: true }
});
const purchaseOrderSchema = new Schema({
supplier: { type: Object, required: true},
createdBy: { type: String, required: true },
medicines: [medicinePurchaseSchema],
paybleGst: { type: Number, required: true },
totalAmount: { type: Number, required: true },
paidAmount: { type: Number, required: true },
grandTotal: { type: Number, required: true }
}, { timestamps: true })
const PurchaseOrder = models.PurchaseOrder || model('PurchaseOrder', purchaseOrderSchema);
export default PurchaseOrder;
`
`
const mongoose = require('mongoose');
const { Schema, model, models } = mongoose;
const medicineSchema = new Schema({
name: { type: String, required: true },
company: {type: String, required: true},
pack_detail: {type: Number, required: true},
quantity: { type: Number, required: true },
category: { type: String, required: true },
status: { type: String, required: true }
}, { timestamps: true });
const Medicine = models.Medicine || model('Medicine', medicineSchema);
export default Medicine;
`
this is my Medicine schema
but problem is I got **totalOrderAmount **and **totalPayableGst **is **0 **in newOutstanding calculation, i think my newOutstanding calculation line is executing before updating my these variable in medicines.each function.
How can I fix this, im trying since 2 days but i didn't get any solution.
anyone have any solution.
That forEach method call will execute synchronously and doesn't await any promises. The callbacks do have await, but those affect only the async function they occur in, not the forEach method.
Instead of using forEach, use map, so that you get back the array of promises (as the async callbacks return promises). To make sure those promises resolve to something useful, have those callbacks return the newmedi. With Promise.all you can then know when all those promises resolved, and get all the medicine values to store in the medicines array, and only continue with the rest of the function when that is done:
// Not forEach, but map, and await all returned promises
const medicines = await Promise.all(req.body.medicines.map(async medicine => {
/* ...rest of your callback code... */
return newmedi; // Don't push, but return it
}));
I have to schema built with mongoose. This is the first schema.
const mongoose = require('mongoose');
const {
formatToCamelCase,
formatToCapitalizeEach
} = require('../src/util/formatter');
const formAttributeElementSchema = new mongoose.Schema({
elementTitle: {
type: String,
required: [true, 'Please provide element title in formAttributeSchema.'],
set: elementTitle => formatToCapitalizeEach(elementTitle),
},
datatype: {
type: String,
required: [true, 'Please provide data type for form attribute.'],
lowercase: true,
enum: {
values: ['string', 'number'],
message: 'We still not support {VALUE} data type in form attribute'
},
default: 'string',
},
attributeName: {
type: String,
required: [true, 'Please provide attribute name in form atribute schema.'],
set: attributeName => formatToCamelCase(attributeName),
},
isRequired: {
type: Boolean,
required: true,
default: true,
},
HTMLInputType: {
type: String,
required: [true, 'Please determine HTML input type for this form attribute.'],
enum: {
values: ['text', 'radio', 'checkbox'],
message: '{VALUE} is not supported yet. Please use supported type.',
},
},
maxLength: {
type: Number,
required: false,
validate: {
validator: (maxLength) => {
if (maxLength <= 0) return false;
},
message: 'Max length value can not have 0 or negative value.',
}
},
minLength: {
type: Number,
required: false,
validate: {
validator: (minLength) => {
if (minLength < 0) return false;
},
message: 'Min length can not have negative value.'
}
}
}, { _id: false });
const FormAttributeElement = mongoose.model('FormAttributeElement', formAttributeElementSchema);
module.exports = { FormAttributeElement };
and the second schema is:
const mongoose = require('mongoose');
const { formatToCapitalizeEach } = require('../src/util/formatter');
const { emailValidator } = require('../src/util/validator');
const { FormAttributeElement } = require('./formAttributeElement');
const dynamicFormDetails = new mongoose.Schema({
formTitle: {
type: String,
required: [true, 'Please provide form title.'],
set: formTitle => formatToCapitalizeEach(formTitle),
},
issuer: {
type: String,
required: [true, 'Please provide issuer email on form detail.'],
lowercase: true,
validate: {
validator: (issuer) => emailValidator(issuer),
message: 'Please makse sure use correct email format in issuer field on form detail attribute.',
},
},
startAt: {
type: Date,
required: [true, 'please provide date on startAt attribute.'],
},
closedAt: {
type: Date,
required: [true, 'Please provide date on closedAt attribute']
},
formShape: {
type: Array,
required: [true, 'Please provide form shape in form details.'],
validate: {
validator: (formShape) => {
for (let i = 0; i < formShape.length; i += 1) {
if (!formShape[i] instanceof FormAttributeElement) return false;
}
},
message: 'Form body only can be instance of FormAttributeElement',
},
},
});
const DynamicFormDetail = mongoose.model('DynamicFormDetail', dynamicFormDetails);
module.exports = { DynamicFormDetail };
now, i want to test my schema, especially it's validation using this code:
const { mongodbAtlasConnection } = require('../db/mongodb-connection');
const { DynamicFormDetail } = require('./dynamicForm');
const { FormAttributeElement } = require('./formAttributeElement');
const main = async () => {
let formElements = [];
let element;
const body = [
{
elementTitle: 'test aku hanya ngetes',
datatype: 'anjir harusnya gabisa sih',
isRequired: true,
HTMLInputType: 'blah',
}
]
body.forEach((data) => {
element = new FormAttributeElement({
elementTitle: data.elementTitle,
datatype: data.datatype,
attributeName: data.elementTitle,
isRequired: data.isRequired,
HTMLInputType: data.HTMLInputType,
});
formElements.push(element);
});
const formDetails = new DynamicFormDetail({
formTitle: 'test 123 form title',
issuer: 'lucky#gmail.com',
startAt: '2021-10-6',
closedAt: '2021-10-7',
formShape: formElements,
});
try {
await mongodbAtlasConnection();
const result = await formDetails.save();
console.log(result);
} catch (e) {
console.log(e)
}
}
main();
Yes, in DynamicFormDetail model, the validation is work just fine. If i wasn't supplied required attribute it throws an error. But, the problem is in the instantization of FormAttributeElement (inside of forEach loop) i can insert anything even if it's againts the constraint i defined in the schema. How to trigger that validation if i don't want to use .save() function on that model?
Currently, I am working on a project on academic management of the university, every semester students will get marks for training and if someone is below 50/100 they will receive a warning email. I use mongoose, namely mongo atlas to store data, expressjs for backend, I create a model called "classes" to define the information of classes as follows:
const mongoose = require('mongoose')
const classSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
consultant: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Consultant',
required: true
},
classname: {
type: String,
required: true,
unique: true
},
studentList: [
{
code: {
type: String,
required: true
},
fullname: {
type: String,
required: true
}
}
]
})
const Class = mongoose.model('Class', classSchema)
module.exports = Class
and this my model of student:
const mongoose = require('mongoose')
const studentSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
fullname: {
type: String
},
code: {
type: String,
required: true,
unique: true
},
classname: {
type: String,
require: true
},
gender: {
type: String,
required: true,
enum: ['Male', 'Female', 'No Record'],
default: 'No Record'
},
birthday: {
type: String
},
vnumail: {
type: String,
unique: true,
required: true,
match: /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/
},
vnumail: {
type: String,
match: /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/
},
profileImage: {
type: String,
default:
'https://kittyinpink.co.uk/wp-content/uploads/2016/12/facebook-default-photo-male_1-1.jpg'
},
hometown: {
type: String
},
accademicTrainningList: [
{
score: {
type: Number,
required: true
},
schoolYear: {
type: String,
required: true
},
semester: {
type: String,
required: true,
enum: ['1', '2'],
default: '1'
},
classification: {
type: String,
required: true,
enum: [
'Excellent',
'Good',
'Intermediate',
'Average',
'Weak',
'Fail',
'No Record'
],
default: 'No Record'
}
}
],
scoreList: [
{
score: {
type: Number,
required: true
},
subjectCode: {
type: String,
required: true
},
subjectName: {
type: String,
required: true
}
}
],
receiveScholarship: [
{
scholarshipName: {
type: String,
required: true
},
value: {
type: Number,
required: true
}
}
],
prizeList: [
{
constestName: {
type: String,
required: true
},
ranking: {
type: Number,
required: true
}
}
],
scienceContestPrizeList: [
{
constestName: {
type: String,
required: true
},
ranking: {
type: Number,
required: true
}
}
],
wentAbroad: [
{
country: {
type: String
},
time: {
type: Date
}
}
],
tookTheTest: [
{
testName: {
type: String,
required: true
},
ranking: {
type: Number,
required: true
}
}
],
punishList: [
{
studentCode: {
type: mongoose.Schema.Types.ObjectId
}
}
]
})
studentSchema.pre('save', function (error, doc, next) {
if (error.name === 'MongoError' && error.code === 11000) {
next(new Error('There was a duplicate key error'))
} else {
next()
}
})
const Student = mongoose.model('Students', studentSchema)
module.exports = Student
I then create a route to add a new class and The input is a .xlsx file and I will extract the information in that file and add the properties of the xlsx file and add it to the database. I use the xlsx - npm library to extract the information and save it. this image demonstrate my input file
router.post(
'/',
upload.single('excel'),
extract_data,
add_new_class,
add_students_from_excel,
add_parent_from_excel,
add_user_from_excel
)
This is the middleware I use to extract the information:
const xlsx = require('xlsx')
const { formatClassname } = require('../../helpers')
exports.extract_data = (req, res, next) => {
let { file } = req
let workbook = xlsx.readFile(file.path)
const sheet_name_list = workbook.SheetNames
let { classname, schoolYear, semester } = req.body
data = []
sheet_name_list.forEach(sheet => {
let workSheet = workbook.Sheets[sheet]
let dataArr = xlsx.utils.sheet_to_json(workSheet)
dataArr.forEach(info => {
var fullname = info['Họ tên ']
var code = info['Mã SV ']
var birthday = info['Ngày sinh ']
var score = info['Điểm ']
data.push({
fullname,
code,
birthday,
classname: formatClassname(classname),
accademicTrainningList: {
score,
schoolYear,
semester,
classification:
(score >= 90 && 'Excellent') ||
(score >= 80 && score < 90 && 'Good') ||
(score >= 70 && score < 80 && 'Intermediate') ||
(score >= 60 && score < 70 && 'Average') ||
(score >= 50 && score < 60 && 'Weak') ||
(score < 50 && 'Fail')
}
})
})
})
req.data = data
next()
}
then in the next route, i insertMany into collection "students":
exports.add_students_from_excel = async (req, res, next) => {
const { data } = req
var studentList = []
data.forEach((student, index) => {
var {
fullname,
code,
birthday,
classname,
accademicTrainningList
} = student
studentList.push({
fullname,
birthday,
classname,
code,
vnumail: code + '#vnu.edu.vn',
classname,
accademicTrainningList
})
})
Student.insertMany(studentList, { ordered: false })
.then(docs => {
console.log('new students were inserted, reload the database')
next()
})
.catch(err => {
if (
(err.name === 'BulkWriteError' || err.name === 'MongoError') &&
err.code === 11000
) {
console.log('new students were inserted, reload the database')
next()
} else {
res.status(500).json({ err })
}
})
}
I succeeded and I added data about new class in model "class" and student list in model "student". This is the input data image and the result is saved on the mongo atlas
But as you can see, the "academicTrainningList" attribute in the "student" model is an array and I just added the first one, now I want to add more items for the second semester of 2016 and the next, i will have to updateMany, the input will be an xlsx file with the same student list and the score will be different, but i don't know what the syntax will look like, i'm a complete newbie and self-taught, thank you for your time time to read through this post and take the time to help me, it is very meaningful to me, have a nice day
If you want to update many resources you could .find(query) the resources and then for each resource:
forEach((doc)=>{doc.academicTrainingList.push(NEW_ITEM)}
I don´t know the logic behind your .xlsx files, but you could search your item Id in the table to find the correct NEW_ITEM to push to the array
I have this code that is supposed to edit an object grabbed from a MongoDB. One of the fields is a map that has an array in it. I push to that array in the map. I log the object itself and it definitely got put into the array of the Map. It just doesn't update in the Database
Guild Model:
let {
Schema,
model
} = require('mongoose');
let Guild = new Schema({
guildID: {
type: String,
required: true
},
guildname: {
type: String,
required: true
},
prefix: {
type: String,
required: false,
default: "y!"
},
welcome_channel: {
id: {
type: String,
required: false,
default: null
},
message: {
type: String,
required: false,
default: "has joined the server!",
}
},
mod_log: {
type: String,
required: false,
default: null
},
spaces: {
type: Map,
required: false,
default: []
}
})
module.exports = model('Guild', Guild);
I grab the guild object in my code later on, it isn't null or undefined.
let fetch_guild = await Guild.findOne({"guildid": id});
// successfully grabs the object from the db
let temp_space = fetch_guild.spaces.get(args[0]) ? fetch_guild.spaces.get(args[0]) : null;
if(temp_space.admins.includes(message.mentions.users.first().id)) return message.reply("That person is already an admin!");
temp_space.admins.push(message.mentions.users.first().id);
console.log(fetch_guild.spaces)
Result of that console.log:
Map {
'test' => { name: 'test', admins: [] },
'test2' => { name: 'test2', admins: [] },
'test3' => { name: 'test3', admins: [ '500765481788112916' ] }
}
And I save it using:
await fetch_guild.save();
In the database, it is still an empty array
try using updateOne() of mongoose.
await fetch_guild.updateOne(fetch_guild);
So I have the following schemas:
var Item_Detail = new Schema(
{
content: {
index: true,
type: Array
},
is_private: {
default: false,
index: true,
type: Boolean
},
order: {
index: true,
required: true,
type: Number
},
table: {
default: {},
type: Object
},
title: {
required: true,
index: true,
type: String,
},
type: {
default: "text",
enum: ["text", "table"],
index: true,
type: String
},
},
{
strict: false
}
)
const Item = new Schema(
{
details: {
default: [],
index: true,
type: [Item_Detail],
},
display_name: {
default: "",
index: true,
type: String,
},
image: {
default: "http://via.placeholder.com/700x500/ffffff/000000/?text=No%20Image&",
type: String
},
is_private: {
default: false,
index: true,
type: Boolean
},
tags: {
index: true,
type: [Tag]
}
},
{
strict: false
}
)
Now, Item_Detail is to be a subdocument of Item, but I'm not quite sure how I should enforce the defaults and type restriction. I also don't want Item_Detail to be a collection in its own right, so using create or save probably doesn't fit.
I think you can use embedded documents for this so in your item schema you can embed an item_detail:
const Item = new Schema({
...
item_detail: item_detail
})
Then on the server when you want to add an item_detail you can do the following
myItem = new Item({//enter item data here})
//assign item detail here
myItem.item_detail = item_detail ;
then proceed to save it
myItem.save()
Enforcing the type is easy the default value is the tricky one but mongoose allows you to specify a function instead of boolean for default (just like it allows you the same for required).
So you could do something like this (I shortened the schemas for bravity):
const itemDetails = new Schema({
info: {
type: String,
required: true
}
})
const item = new Schema({
details: {
default: function() {
return [new ItemDetails({ info: 'N/A' })]
},
type: [itemDetails],
}
})
This would allow you to do something like this:
var itm = new Item();
And the saved result would be:
{
"_id": ObjectId("5b72795d4aa17339b0815b8b"),
"details": [{
"_id": ObjectId("5b72795d4aa17339b0815b8c"),
"info": "N/A"
}]
}
So this gives you two things:
You cannot put any type in details other than itemDetails since you enforced the type with itemDetails.
You initialize your ItemDetails object with defaults you want in your default custom function.
Hope this helps.