how to make fixed size arrays inside array mongoose schema - javascript

I have example input
[[1,2],[3,2],[1,3],...,[4,5]]
How to write model schema in mongoose?
This is my Schema
const SubproductSchema = new Schema({
...
positions: [{
type: [Number],
validate: {
validator: function(value){
return value.length == 2;
},
message: 'Positions should be 2'
}
}]
}, {
timestamps: true
});
And this does not work.
Input should be array with fixedSize with 2 length in array like this [[1,2],[3,2],[1,3],...,[4,5]]
If input is [[1,2,4],[3,2],[1,3],...,[4,5]], it should validate with 'Position should be 2'
UPDATED
I also tried this code (logically correct I guess):
const SubproductSchema = new Schema({
...
positions: {
type: [{
type: [Number],
validate: {
validator: function(value){
return value.length == 2;
},
message: 'Positions should be 2'
}
}],
}
}, {
timestamps: true
});
And my post is
{
...
"positions": [[2,3], [1,4], [4, 5]]
}
And it output error:
Subproduct validation failed: positions: Cast to Array failed for
value \"[ [ 2, 3 ], [ 1, 4 ], [ 4, 5 ] ]\" at path \"positions\""
model should look like

This is what you were looking...
const SubproductSchema = new Schema({
...
positions: [{
type: [Number],
validate: [limit, 'Positions should be 2']
}]
}, { timestamps: true });
const limit = (val) => {
let subElementsValidated = true;
val.forEach(el => {
if (el.length != 2){
subElementsValidated = false;
return;
}
});
return subElementsValidated;
}

You can change the validate options like this
const SubproductSchema = new Schema({
...
positions: [{
type: [Number],
validate: [limit, 'Positions should be 2']
}]
}, {
timestamps: true
});
const limit = (val) => {
return val.length == 2;
}

Related

reporting error messages when using ajv.addKeyword

const ajv = require("ajv");
const add_formats = require("ajv-formats");
const add_errors = require("ajv-errors");
const ajv_inst = new ajv({ $data: true, allErrors: true });
add_errors(add_formats(ajv_inst));
ajv_inst.addKeyword({
keyword: "age",
validate: (schema, data) => schema.max_age >= data && schema.min_age <= data,
metaSchema: {
// schema to validate keyword value
type: "object",
properties: {
min_age: { type: "integer" },
max_age: { type: "integer" },
},
required: ["min_age", "max_age"],
additionalProperties: false,
},
});
const schema = {
age: {
min_age: 18,
max_age: 100,
},
};
const validate = ajv_inst.compile(schema);
console.log(validate(17));
console.log(ajv_inst.errors);
output:
false
null
So it is working properly, except that there is no error message. How do I add custom error messages for custom keywords?

How to trigger mongoose schema at instantization instead of save()?

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?

How to transfer field names to groups in a React json schema ui: groups nested object scenario

How to transfer field names to groups in a React json schema ui: groups nested object scenario. For example: object1.field1
I want to group the modelMetada and legancyMetaData objects given in the schema by selecting them from the properties inside.
The reason I want this is to show it by grouping it as I want in the tab.
const newSchema = {
$schema: 'http://json-schema.org/draft-07/schema#',
title: 'Physical Model',
type: 'object',
properties: {
modelMetadata: {
type: 'object',
properties: {
name: {
type: 'string',
title: 'Model Name',
description: 'Name of the Model',
},
displayName: {
type: 'string',
title: 'Model Display Name',
description: 'Display Name of the Model',
},
},
},
legacyMetaData: {
type: 'object',
properties: {
id: {
type: 'string',
title: 'Legacy ID',
description: 'ID of the Model in the Legacy System',
},
name: {
type: 'string',
title: 'Legacy Name',
description: 'Legacy Name of the Model',
},
},
},
},
};
const groups = [
{
modelMetadata: ['modelMetadata'],
legacyMetaData: ['legacyMetaData'],
customize: ['modelMetadata.displayName','legacyMetaData.name'], // ????
},
];
The code block I grouped
const doGrouping = ({ properties, groups, formContext }):any => {
const mapped = groups.map((g) => {
if (typeof g === 'string') {
const found = properties.filter((p) => p.name === g);
if (found.length === 1) {
const el = found[0];
return el.content;
}
return EXTRANEOUS;
} else if (typeof g === 'object') {
const { templates } = formContext;
const GroupComponent = templates
? templates[g['ui:template']]
: DefaultTemplate;
// #ts-ignore
const tempProperties = Object.keys(g).reduce((acc, key) => {
const field = g[key];
if (key.startsWith('ui:')) return acc;
if (!Array.isArray(field)) return acc;
return [
...acc,
{
name: key,
children: doGrouping({
properties,
formContext,
groups: field,
}),
},
];
}, []);
return <GroupComponent properties={tempProperties} />;
}
throw new Error(`Invalid object type: ${typeof g} ${g}`);
});
const remainder = mapped.filter((m) => m === REST);
if (remainder.length > 0) {
throw new Error('Remainder fields not supported');
}
return mapped;
};

Mongoose updateMany for array of array

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

How can I create Json with data from a form?

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 )

Categories