Sailsjs update model json - node.js

Let's say there is a a user model defined like this.
...
attributes: {
properties: {
type: 'json',
required: true,
defaultsTo: {
canEdit: {
owned: false,
other: false
}
}
}
}
How can I update user.properties.canEdit.owned and user.properties.canEdit.other?

It can be done this way.
User
.findOne()
.where({ ... })
.then(function(user) {
user.properties.canEdit.owned = true;
user.properties.canEdit.other = false;
user.save(function(err) { ... });
});

Related

How I can solve $near query in mongoddb adbpter query building issue?

I'm trying to create a nearby searching platform with keystone.js
But the nearby queries not working with the keystone js.
How I can run mongoose $nearSphere query or aggregation queries in the keystone?
Hereby added my best try and errors.
Index.js
require('dotenv').config({
path: './.env'
});
const {
Keystone
} = require('#keystonejs/keystone');
const {
GraphQLApp
} = require('#keystonejs/app-graphql');
const {
AdminUIApp
} = require('#keystonejs/app-admin-ui');
const {
PasswordAuthStrategy
} = require('#keystonejs/auth-password');
const {
MongooseAdapter
} = require('#keystonejs/adapter-mongoose');
const initialiseData = require('./initial-data');
const {
Post,
User,
Category,
SubCategory,
Comment,
ForgottenPasswordToken,
} = require('./schema');
const PROJECT_NAME = 'rentospot';
const keystone = new Keystone({
appVersion: {
version: '1.0.0',
addVersionToHttpHeaders: false,
access: false,
},
cookie: {
secure: process.env.NODE_ENV === 'production', // Default to true in production
maxAge: 1000 * 60 * 60 * 24 * 30, // 30 days
sameSite: false,
},
cookieSecret: process.env.COOKIE_SECRET,
adapter: new MongooseAdapter({
mongoUri: 'mongodb+srv://*******:******.mongodb.net/my-app-key-stone'
}),
onConnect: process.env.CREATE_TABLES !== 'true' && initialiseData,
});
const postList = keystone.createList('Post', Post);
keystone.createList('Category', Category);
keystone.createList('SubCategory', SubCategory);
keystone.createList('Comment', Comment);
keystone.createList('User', User);
keystone.createList('ForgottenPasswordToken', ForgottenPasswordToken);
const authStrategy = keystone.createAuthStrategy({
type: PasswordAuthStrategy,
list: 'User',
});
const extendSchema = {
queries: [{
schema: 'nearBy: [Post]',
resolver: async(_) => {
try {
const {
adapter
} = postList;
const result = await adapter.find({
// "feature": true,
"venue.location": {
"$nearSphere": {
"$geometry": {
"type": "Point",
"coordinates": [12, 75],
},
"$minDistance": 0,
"$maxDistance": 500,
},
}
});
console.log(result)
return result;
} catch (e) {
console.log("errrrrrrrrrrrrrror: ", e)
return null
}
},
}, ]
};
keystone.extendGraphQLSchema(extendSchema)
module.exports = {
keystone,
apps: [new GraphQLApp(),
new AdminUIApp({
name: PROJECT_NAME,
enableDefaultRoute: true,
authStrategy
})
],
};
Schema.js
const fetch = require('node-fetch');
const { v4: uuid } = require('uuid');
const { sendEmail } = require('./emails');
const {
File,
Text,
Slug,
Relationship,
Select,
Password,
Checkbox,
CalendarDay,
DateTime,
Integer
} = require('#keystonejs/fields');
const { S3Adapter } = require('#keystonejs/file-adapters');
const { AuthedRelationship } = require('#keystonejs/fields-authed-relationship');
const { formatISO } = require('date-fns');
const { Wysiwyg } = require('#keystonejs/fields-wysiwyg-tinymce');
const LocationGoogle = require('./LocationGoogle');
const fileAdapter = new S3Adapter({
bucket: process.env.BUCKET_NAME,
folder: process.env.S3_PATH,
publicUrl: ({ filename }) =>
// `https://${process.env.CF_DISTRIBUTION_ID}.cloudfront.net/${process.env.S3_PATH}/${filename}`,
`https://${process.env.CF_DISTRIBUTION_ID}/${process.env.S3_PATH}/${filename}`,
s3Options: {
// Optional paramaters to be supplied directly to AWS.S3 constructor
apiVersion: '2006-03-01',
accessKeyId: process.env.API_KEY_ID,
secretAccessKey: process.env.SECRET_ACCESS_KEY,
region: process.env.REGION
},
uploadParams: ({ id }) => ({
Metadata: {
keystone_id: `${id}`,
},
}),
});
// Access control functions
const userIsAdmin = ({ authentication: { item: user } }) => Boolean(user && user.isAdmin);
const userOwnsItem = ({ authentication: { item: user } }) => {
if (!user) {
return false;
}
return { id: user.id };
};
const userIsCurrentAuth = ({ authentication: { item: user } }) => Boolean(user); // item will be undefined for anonymous user
const userIsAdminOrOwner = auth => {
const isAdmin = access.userIsAdmin(auth);
const isOwner = access.userOwnsItem(auth);
return isAdmin ? isAdmin : isOwner;
};
const access = { userIsAdmin, userIsCurrentAuth, userOwnsItem, userIsAdminOrOwner };
// Read: public / Write: admin
const DEFAULT_LIST_ACCESS = {
create: access.userIsCurrentAuth,
read: true,
update: access.userIsAdminOrOwner,
delete: access.userIsAdmin,
};
exports.User = {
access: {
update: access.userIsCurrentAuth,
delete: access.userIsAdmin,
},
fields: {
name: { type: Text },
dob: {
type: CalendarDay,
format: 'do MMMM yyyy',
dateFrom: '1901-01-01',
dateTo: formatISO(new Date(), { representation: 'date' }),
},
phone: {
type: Text,
isUnique: true,
access: { read: access.userIsCurrentAuth },
// hooks: {
// validateInput:
// }
},
email: { type: Text, isUnique: true, access: { read: access.userIsCurrentAuth } },
password: { type: Password, isRequired: true },
isAdmin: { type: Checkbox, access: { update: access.userIsAdmin } },
twitterHandle: { type: Text },
image: { type: File, adapter: fileAdapter },
},
hooks: {
afterChange: async ({ updatedItem, existingItem }) => {
if (existingItem && updatedItem.password !== existingItem.password) {
const url = process.env.SERVER_URL || 'http://localhost:3000';
const props = {
recipientEmail: updatedItem.email,
signinUrl: `${url}/signin`,
};
const options = {
subject: 'Your password has been updated',
to: updatedItem,
from: process.env.MAILGUN_FROM,
domain: process.env.MAILGUN_DOMAIN,
apiKey: process.env.MAILGUN_API_KEY,
};
await sendEmail('password-updated.jsx', props, options);
}
},
},
};
// exports.Organiser = {
// access: DEFAULT_LIST_ACCESS,
// fields: {
// user: { type: Relationship, ref: 'User' },
// order: { type: Integer },
// role: { type: Text },
// },
// };
// TODO: We can't access the existing item at the list update level yet,
// read access needs to check if event is "active" or if the user is admin
// read: ({ existingItem, authentication }) => access.userIsAdmin({ authentication }) || !!(existingItem && existingItem.status === 'active'),
exports.Post = {
access: DEFAULT_LIST_ACCESS,
fields: {
title: { type: Text },
slug: { type: Slug, from: 'title' },
author: {
type: AuthedRelationship,
ref: 'User',
isRequired: true,
access: {
read: userIsCurrentAuth,
update: userIsAdmin,
},
},
status: {
type: Select,
defaultValue: 'published',
options: [
{ label: 'Draft', value: 'draft' },
{ label: 'Published', value: 'published' },
],
},
feature: { type: Checkbox, access: { update: access.userIsAdmin } },
assured: { type: Checkbox, access: { update: access.userIsAdmin } },
posted: { type: DateTime, format: 'dd/MM/yyyy' },
categories: {
type: Relationship,
ref: 'Category',
many: false,
},
subCategories: {
type: Relationship,
ref: 'SubCategory',
many: true,
},
description: { type: Wysiwyg },
venue: {
type: LocationGoogle,
googleMapsKey: process.env.GOOGLE_MAPS_KEY,
// hooks: {
// resolveInput: async ({ originalInput }) => {
// try {
// const placeId = originalInput.venue;
// if (typeof placeId === 'undefined') {
// // Nothing was passed in, so we can bail early.
// return undefined;
// }
// const r = await fetch(
// `https://maps.googleapis.com/maps/api/geocode/json?place_id=${placeId}&key=${process.env.GOOGLE_MAPS_KEY}`
// )
// const response = await r.json()
// if (response.results && response.results[0]) {
// const { place_id, formatted_address } = response.results[0];
// const { lat, lng } = response.results[0].geometry.location;
// return {
// googlePlaceID: place_id,
// formattedAddress: formatted_address,
// lat,
// lng,
// loc: {
// type: "Point",
// coordinates: [lng, lat]
// }
// };
// }
// return null;
// }
// catch (e) {
// console.log("error:", e)
// }
// },
// validateInput: async (x) => {
// console.log("---------------------------------item 2-------------------------------------- : ",x);
// },
// beforeChange: async (x) => {
// console.log("========", x)
// },
// afterChange: async (x) => {
// console.log("++++", x)
// }
// },
},
locationAddress: { type: Text },
locationDescription: { type: Text },
createdby: {
type: AuthedRelationship,
ref: 'User',
isRequired: true,
access: {
read: userIsCurrentAuth,
update: userIsAdmin,
},
},
image: {
type: File,
adapter: fileAdapter,
hooks: {
beforeChange: async ({ existingItem }) => {
if (existingItem && existingItem.image) {
await fileAdapter.delete(existingItem.image);
}
},
},
},
},
hooks: {
afterDelete: ({ existingItem }) => {
if (existingItem.image) {
fileAdapter.delete(existingItem.image);
}
},
},
adminConfig: {
defaultPageSize: 20,
defaultColumns: 'title, status',
defaultSort: 'title',
},
labelResolver: item => item.title,
};
exports.Category = {
// access: userIsAdmin,
fields: {
name: { type: Text },
slug: { type: Slug, from: 'name' },
image: {
type: File,
adapter: fileAdapter,
hooks: {
beforeChange: async ({ existingItem }) => {
if (existingItem && existingItem.image) {
await fileAdapter.delete(existingItem.image);
}
},
},
},
},
};
exports.SubCategory = {
// access: userIsAdmin,
fields: {
name: { type: Text },
slug: { type: Slug, from: 'name' },
categories: {
type: Relationship,
ref: 'Category',
many: false,
},
image: {
type: File,
adapter: fileAdapter,
hooks: {
beforeChange: async ({ existingItem }) => {
if (existingItem && existingItem.image) {
await fileAdapter.delete(existingItem.image);
}
},
},
},
},
};
exports.Comment = {
access: {
create: userIsCurrentAuth,
update: userIsAdminOrOwner,
},
fields: {
body: { type: Text, isMultiline: true },
originalPost: {
type: Relationship,
ref: 'Post',
},
author: {
type: AuthedRelationship,
ref: 'User',
isRequired: true,
access: {
create: userIsAdmin,
update: userIsAdmin,
},
},
posted: { type: CalendarDay },
},
labelResolver: item => item.body,
};
// exports.Talk = {
// access: DEFAULT_LIST_ACCESS,
// fields: {
// name: { type: Text },
// event: { type: Relationship, ref: 'Event.talks' },
// speakers: { type: Relationship, ref: 'User.talks', many: true },
// isLightningTalk: { type: Checkbox },
// description: { type: Wysiwyg },
// },
// };
// exports.Rsvp = {
// access: {
// create: true,
// read: true,
// update: ({ authentication: { item } }) => {
// if (!item) {
// return false;
// }
// return { user: { id: item.id } };
// },
// delete: access.userIsAdmin,
// },
// fields: {
// event: { type: Relationship, ref: 'Event' },
// user: { type: Relationship, ref: 'User' },
// status: { type: Select, options: 'yes, no' },
// },
// hooks: {
// validateInput: async ({ context, resolvedData, existingItem }) => {
// const { status } = resolvedData;
// const { event: eventId } = existingItem ? existingItem : resolvedData;
// if (status === 'no') {
// return;
// }
// const { data } = await context.executeGraphQL({
// query: `query {
// event: Event(where: { id: "${eventId}" }) {
// id
// startTime
// maxRsvps
// isRsvpAvailable
// }
// allRsvps(where: { event: { id: "${eventId}" }}) {
// id
// }
// }`,
// });
// const { event, allRsvps } = data;
// if (
// !event ||
// !event.isRsvpAvailable ||
// !event.startTime ||
// new Date() > new Date(event.startTime) ||
// allRsvps.length >= event.maxRsvps
// ) {
// throw 'Error rsvping to event';
// }
// },
// },
// };
// exports.Sponsor = {
// access: DEFAULT_LIST_ACCESS,
// fields: {
// name: { type: Text },
// website: { type: Text },
// logo: { type: CloudinaryImage, adapter: cloudinaryAdapter },
// },
// };
exports.ForgottenPasswordToken = {
access: {
create: true,
read: true,
update: access.userIsAdmin,
delete: access.userIsAdmin,
},
fields: {
user: {
type: Relationship,
ref: 'User',
access: {
read: access.userIsAdmin,
},
},
token: {
type: Text,
isRequired: true,
isUnique: true,
access: {
read: access.userIsAdmin,
},
},
requestedAt: { type: DateTime, isRequired: true },
accessedAt: { type: DateTime },
expiresAt: { type: DateTime, isRequired: true },
},
hooks: {
afterChange: async ({ context, updatedItem, existingItem }) => {
if (existingItem) return null;
const now = new Date().toISOString();
const { errors, data } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
query GetUserAndToken($user: ID!, $now: DateTime!) {
User( where: { id: $user }) {
id
email
}
allForgottenPasswordTokens( where: { user: { id: $user }, expiresAt_gte: $now }) {
token
expiresAt
}
}
`,
variables: { user: updatedItem.user.toString(), now },
});
if (errors) {
console.error(errors, `Unable to construct password updated email.`);
return;
}
const { allForgottenPasswordTokens, User } = data;
const forgotPasswordKey = allForgottenPasswordTokens[0].token;
const url = process.env.SERVER_URL || 'http://localhost:3000';
const props = {
forgotPasswordUrl: `${url}/change-password?key=${forgotPasswordKey}`,
recipientEmail: User.email,
};
const options = {
subject: 'Request for password reset',
to: User.email,
from: process.env.MAILGUN_FROM,
domain: process.env.MAILGUN_DOMAIN,
apiKey: process.env.MAILGUN_API_KEY,
};
await sendEmail('forgot-password.jsx', props, options);
},
},
};
exports.customSchema = {
mutations: [
{
schema: 'startPasswordRecovery(email: String!): ForgottenPasswordToken',
resolver: async (obj, { email }, context) => {
const token = uuid();
const tokenExpiration =
parseInt(process.env.RESET_PASSWORD_TOKEN_EXPIRY) || 1000 * 60 * 60 * 24;
const now = Date.now();
const requestedAt = new Date(now).toISOString();
const expiresAt = new Date(now + tokenExpiration).toISOString();
const { errors: userErrors, data: userData } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
query findUserByEmail($email: String!) {
allUsers(where: { email: $email }) {
id
email
}
}
`,
variables: { email: email },
});
if (userErrors || !userData.allUsers || !userData.allUsers.length) {
console.error(
userErrors,
`Unable to find user when trying to create forgotten password token.`
);
return;
}
const userId = userData.allUsers[0].id;
const result = {
userId,
token,
requestedAt,
expiresAt,
};
const { errors } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
mutation createForgottenPasswordToken(
$userId: ID!,
$token: String,
$requestedAt: DateTime,
$expiresAt: DateTime,
) {
createForgottenPasswordToken(data: {
user: { connect: { id: $userId }},
token: $token,
requestedAt: $requestedAt,
expiresAt: $expiresAt,
}) {
id
token
user {
id
}
requestedAt
expiresAt
}
}
`,
variables: result,
});
if (errors) {
console.error(errors, `Unable to create forgotten password token.`);
return;
}
return true;
},
},
{
schema: 'changePasswordWithToken(token: String!, password: String!): User',
resolver: async (obj, { token, password }, context) => {
const now = Date.now();
const { errors, data } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `
query findUserFromToken($token: String!, $now: DateTime!) {
passwordTokens: allForgottenPasswordTokens(where: { token: $token, expiresAt_gte: $now }) {
id
token
user {
id
}
}
}`,
variables: { token, now },
});
if (errors || !data.passwordTokens || !data.passwordTokens.length) {
console.error(errors, `Unable to find token`);
throw errors.message;
}
const user = data.passwordTokens[0].user.id;
const tokenId = data.passwordTokens[0].id;
const { errors: passwordError } = await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `mutation UpdateUserPassword($user: ID!, $password: String!) {
updateUser(id: $user, data: { password: $password }) {
id
}
}`,
variables: { user, password },
});
if (passwordError) {
console.error(passwordError, `Unable to change password`);
throw passwordError.message;
}
await context.executeGraphQL({
context: context.createContext({ skipAccessControl: true }),
query: `mutation DeletePasswordToken($tokenId: ID!) {
deleteForgottenPasswordToken(id: $tokenId) {
id
}
}
`,
variables: { tokenId },
});
return true;
},
},
],
};
LocationGoogle is a directory of the files exactly #keystonejs/fields-location-google and small changes in its Implementation.js
Implementation.js
const { Implementation } = require('#keystonejs/fields');
const { MongooseFieldAdapter } = require('#keystonejs/adapter-mongoose');
const fetch = require('node-fetch');
// Disabling the getter of mongoose >= 5.1.0
// https://github.com/Automattic/mongoose/blob/master/migrating_to_5.md#checking-if-a-path-is-populated
class LocationGoogleImplementation extends Implementation {
constructor(_, { googleMapsKey }) {
super(...arguments);
this.graphQLOutputType = 'LocationGoogle';
if (!googleMapsKey) {
throw new Error(
'You must provide a `googleMapsKey` to LocationGoogle Field. To generate a Google Maps API please visit: https://developers.google.com/maps/documentation/javascript/get-api-key'
);
}
this._googleMapsKey = googleMapsKey;
}
get _supportsUnique() {
return false;
}
extendAdminMeta(meta) {
return {
...meta,
googleMapsKey: this._googleMapsKey,
};
}
gqlOutputFields() {
return [`${this.path}: ${this.graphQLOutputType}`];
}
gqlQueryInputFields() {
return [...this.equalityInputFields('String'), ...this.inInputFields('String')];
}
getGqlAuxTypes() {
return [
`
type Location{
coordinates: [Float]
},
type ${this.graphQLOutputType} {
location: Location
googlePlaceID: String
formattedAddress: String
lat: Float
lng: Float
}
`,
];
}
// Called on `User.avatar` for example
gqlOutputFieldResolvers() {
return {
[this.path]: item => {
const itemValues = item[this.path];
if (!itemValues) {
return null;
}
return itemValues;
},
};
}
async resolveInput({ resolvedData }) {
const placeId = resolvedData[this.path];
// NOTE: The following two conditions could easily be combined into a
// single `if (!inputId) return inputId`, but that would lose the nuance of
// returning `undefined` vs `null`.
// Premature Optimisers; be ware!
if (typeof placeId === 'undefined') {
// Nothing was passed in, so we can bail early.
return undefined;
}
if (placeId === null) {
// `null` was specifically set, and we should set the field value to null
// To do that we... return `null`
return null;
}
const response = await fetch(
`https://maps.googleapis.com/maps/api/geocode/json?place_id=${placeId}&key=${this._googleMapsKey}`
).then(r => r.json());
if (response.results && response.results[0]) {
const { place_id, formatted_address } = response.results[0];
const { lat, lng } = response.results[0].geometry.location;
return {
location: {
type: "Point",
coordinates: [lng, lat]
},
googlePlaceID: place_id,
formattedAddress: formatted_address,
lat: lat,
lng: lng,
};
}
return null;
}
gqlUpdateInputFields() {
return [`${this.path}: String`];
}
gqlCreateInputFields() {
return [`${this.path}: String`];
}
getBackingTypes() {
const type = `null | {
location: {
type: "Point",
coordinates: [lng, lat]
};
googlePlaceID: string;
formattedAddress: string;
lat: number;
lng: number;
}
`;
return { [this.path]: { optional: true, type } };
}
}
const CommonLocationInterface = superclass =>
class extends superclass {
getQueryConditions(dbPath) {
return {
...this.equalityConditions(dbPath),
...this.inConditions(dbPath),
};
}
};
class MongoLocationGoogleInterface extends CommonLocationInterface(MongooseFieldAdapter) {
addToMongooseSchema(schema) {
const schemaOptions = {
type: {
location: {
type: { type: String, default: "Point" },
coordinates: [Number]
},
googlePlaceID: String,
formattedAddress: String,
lat: Number,
lng: Number,
},
};
schema.add({
[this.path]: this.mergeSchemaOptions(schemaOptions, this.config)
});
}
}
module.exports = {
LocationGoogleImplementation,
MongoLocationGoogleInterface
}
package.json
{
"name": "#keystonejs/example-projects-blank",
"description": "A blank KeystoneJS starter project.",
"private": true,
"version": "5.0.15",
"author": "The KeystoneJS Development Team",
"repository": "https://github.com/keystonejs/keystone/tree/master/packages/create-keystone-app/example-projects/blank",
"homepage": "https://github.com/keystonejs/keystone",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"scripts": {
"dev": "cross-env NODE_ENV=development ENABLE_DEV_FEATURES=false DISABLE_LOGGING=false nodemon --exec keystone dev --harmony",
"build": "cross-env NODE_ENV=production ENABLE_DEV_FEATURES=false keystone build",
"start": "cross-env NODE_ENV=production keystone start",
"create-tables": "cross-env keystone create-tables"
},
"dependencies": {
"#arch-ui/layout": "^0.2.14",
"#arch-ui/typography": "^0.0.18",
"#keystonejs/adapter-mongoose": "^10.1.2",
"#keystonejs/app-admin-ui": "^7.3.13",
"#keystonejs/app-graphql": "^6.2.1",
"#keystonejs/auth-password": "^6.0.0",
"#keystonejs/email": "^5.2.0",
"#keystonejs/fields-authed-relationship": "^1.0.16",
"#keystonejs/fields-location-google": "^3.2.1",
"#keystonejs/fields-wysiwyg-tinymce": "^5.3.15",
"#keystonejs/file-adapters": "^7.0.8",
"#keystonejs/keystone": "^18.1.0",
"cross-env": "^7.0.3",
"date-fns": "^2.16.1",
"dotenv": "^8.2.0",
"google-maps-react": "^2.0.6",
"node-fetch": "^2.6.1",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"uuid": "^8.3.2"
},
"devDependencies": {
"nodemon": "^2.0.7"
}
}
The Error screenshot attached bellow

Include Model with Group - Sequelize/NodeJS

I have this problem, and i cant solved that.
I need get the name form 'competenciaId' in another model.
const data = await FuncionariosCompetencias.findAndCountAll({
attributes: ['competenciaId',
sequelize.fn('count', sequelize.col('competenciaId'))],
include: [
{ model: Competencia, attributes: [] }
],
group: ['competenciaId', 'Competencia.competencia']
})
.then(function (data) {
return data;
})
.catch(error => {
return error;
});
res.json({ success: true, data: data });
If i removed the "includ:" i have the result but i need name to show it
var Funcionario_Competencias = sequelize.define('funcionarios_competencias', {
nivelcomp: {
type: Sequelize.INTEGER
}
},
{
timestamps: false
})
Funcionario_Competencias.belongsTo(Competencia)
Funcionario_Competencias.belongsTo(Funcionario)

Mongoose reports no error on updating, but does not update

losing my mind here for something for a MongoDB document update with Mongoose, not reporting any error but not actually updating successfully.
I have this schema:
/**
* Branch Schema
*/
let BranchSchema = new Schema({
name: String,
domain: String,
email: String,
bm: { type: Schema.ObjectId, ref: 'User' },
st: [{ type: Schema.ObjectId, ref: 'User' }],
stockCurrent: {
paper: Schema.Types.Object,
ink: Schema.Types.Object
},
stockNeeded: {
paper: Schema.Types.Object,
ink: Schema.Types.Object
},
}, { versionKey: false, usePushEach: true });
mongoose.model('Branch', BranchSchema);
Trying to update stockCurrent, using this logic:
Branch.findById(config.branch.id, function (err, branch) {
if (err) {
res.status(422).send({
message: 'הסניף לא נמצא'
});
} else {
console.log(branch);
Object.keys(req.body.stock).forEach(function (type) {
Object.keys(req.body.stock[type]).forEach(function (code) {
if (req.body.stock[type][code] > 0) {
if (typeof branch.stockCurrent[type][code] === 'undefined') {
branch.stockCurrent[type][code] = 0;
}
branch.stockCurrent[type][code] += req.body.stock[type][code];
}
});
});
console.log(branch);
branch.save(function (err, updated) {
console.log("err: " + err);
if (err) {
stock.remove();
res.status(422).send({
message: 'שגיאה בשמירת מלאי'
});
} else {
console.log(updated);
res.send({
message: 'מלאי נוסף בהצלחה'
});
}
});
}
});
I get to to success part, having my console log this:
{
"_id":5dd276a6bcc29a13789fcecb,
"name":"בצלאל ארכיטקטורה",
"domain":"bezalel.eazix.io",
"email":"eazix.1.bezalel#gmail.com",
"bm":5cdd2130d192ea03a87d2dfd,
"stockNeeded":{
"ink":{
"GY":2,
"PM":2,
"M":2,
"MBK":2,
"PBK":2,
"PC":2,
"Y":2,
"C":2,
"waste":2
},
"paper":{
"COATED":5,
"PLAIN":5,
"PHOTO":3
}
},
"stockCurrent":{
"paper":{
"PLAIN":0
},
"ink":{
"waste":0
}
},
"st":[
]
}{
"_id":5dd276a6bcc29a13789fcecb,
"name":"בצלאל ארכיטקטורה",
"domain":"bezalel.eazix.io",
"email":"eazix.1.bezalel#gmail.com",
"bm":5cdd2130d192ea03a87d2dfd,
"stockNeeded":{
"ink":{
"GY":2,
"PM":2,
"M":2,
"MBK":2,
"PBK":2,
"PC":2,
"Y":2,
"C":2,
"waste":2
},
"paper":{
"COATED":5,
"PLAIN":5,
"PHOTO":3
}
},
"stockCurrent":{
"paper":{
"COATED":1,
"PHOTO":2,
"PLAIN":0
},
"ink":{
"PM":1,
"waste":0
}
},
"st":[
]
}**"err":null**{
"_id":5dd276a6bcc29a13789fcecb,
"name":"בצלאל ארכיטקטורה",
"domain":"bezalel.eazix.io",
"email":"eazix.1.bezalel#gmail.com",
"bm":5cdd2130d192ea03a87d2dfd,
"stockNeeded":{
"ink":{
"GY":2,
"PM":2,
"M":2,
"MBK":2,
"PBK":2,
"PC":2,
"Y":2,
"C":2,
"waste":2
},
"paper":{
"COATED":5,
"PLAIN":5,
"PHOTO":3
}
},
"stockCurrent":{
"paper":{
"COATED":1,
"PHOTO":2,
"PLAIN":0
},
"ink":{
"PM":1,
"waste":0
}
},
"st":[
]
}
I can see the here the initial state, the updated version before saving, and the the err:null, and the allegedly updated document.
but alas! the document wasn't really updated. it remains the same.
I have tried many things, searching and looking for similar cases, checking my schema, adding useStrict:false to the schema, nothing helps.
Mongoose ver 4.13.20, Mongodb ver 3.6.17
SOS
Dor
I'm guessing the SchemaTypes are the problem? In Mongoose 4.x, these are the only valid SchemaTypes:
String
Number
Date
Buffer
Boolean
Mixed
Objectid
Array
Notice that Mixed is an option but not Object. You need to tell Mongoose that you updated a Mixed field using model.markModified('pathName'). See the Mixed docs.
So in your case, the code below may fix the issue:
branch.markModified('stockCurrent');
branch.save(function (err, updated) {
// ...

Non mapped properties - Sequelize

The supports are: React, Redux and Sequelize.
Basically I want to be able to mark an object as being dirty when the Redux store gets updated.
function updateCar(carToUpdate, car) {
switch(car[1]) {
case "models":
carToUpdate.models = car[3];
carToUpdate.setDirty();
break;
}
};
Then when someone clicks a save button, I want to only update those models that have their dirty flag set to true
var state = request.body;
state.forEach(function (car) {
if (car && car.isDirty) {
updateCar(car);
}
}
Now I have following model for the car:
module.exports = function (sequelize, DataTypes) {
var Cars = sequelize.define('Cars',
{
name: DataTypes.STRING,
type: DataTypes.INTEGER,
models: DataTypes.INTEGER,
//Next is NOT working, this does not make it non-mapped
isDirty: {
type: DataTypes.BOOLEAN,
scope: false
},
},
{
classMethods: {
associate: function (models) {
// associations can be defined here
}
},
instanceMethods: {
setDirty: function() {
this.isDirty = true;
}
}
});
return Cars;
};
Anyone who has experience with non-mapped fields or something similar?
Found it eventually.
The model of the car should contain a VIRTUAL property:
module.exports = function (sequelize, DataTypes) {
var Cars = sequelize.define('Cars',
{
name: DataTypes.STRING,
type: DataTypes.INTEGER,
models: DataTypes.INTEGER,
isDirty: {
type: DataTypes.VIRTUAL
},
},
{
classMethods: {
associate: function (models) {
// associations can be defined here
}
}
});
return Cars;
};
Next the flag should be set when updating the values:
function updateCar(carToUpdate, car) {
switch(car[1]) {
case "models":
carToUpdate.models = car[3];
carToUpdate.isDirty = true;
break;
}
};
Then the save method can check the isDirty flag
var state = request.body;
state.forEach(function (car) {
if (car && car.isDirty) {
console.log(car.name +": "+ car.isDirty);
updateCar(car);
}
}, this);
Last but not least, we reset the isDirty flag to false so we do not update the same models over and over.
var save = function () {
var state = carStore.getState();
return $.ajax({
url: "/Cars/update",
data: JSON.stringify(state),
method: "POST",
contentType: "application/json"
}).then(function() {
carStore.dispatch({
type: 'resetDirty',
data: null
});
});
};
And the dispatch method for resetting the flag:
function updateReducer(state, action) {
switch (action.type) {
case 'load':
return action.data;
case 'update':
return updateCars(action.carChanges, state);
case 'resetDirty':
return resetDirtyFlags(state);
default:
return action.data;
}
}
function resetDirtyFlags(state) {
var newState = $.extend(true, [], state);
if(state) {
newState.forEach(function(car) {
car.isDirty = false;
}, this);
return newState;
}
return newState;
}

Sails-mongo. Find in array

I'm using sails.js and sails-mongo adapter. Suppose I have a model:
module.exports = {
attributes: {
shema: true
, attributes: {
description: {
type: 'TEXT'
, max: 200
}
, tags: {
type: 'ARRAY'
}
}
}
};
How can I carry out a search in an tags array?
Model.find({
'tags.title': {
contains: 'query'
}
})
.done(function (err, response) {
/**/
});
db.schools.find( { criteria },
{ atributes: { $elemMatch: { tags: value } } } )
there are a great example here: http://docs.mongodb.org/manual/reference/operator/projection/elemMatch/
with waterline
Model.native(function(err, collection) {
// Execute any query that works with the mongo js driver
collection.find( { criteria },
{ atributes: { $elemMatch: { tags: value } } } )
});

Resources