Best practice to store price changes over time in Mongodb - database

I'm using Nestjs and Mongoose as ODM. I have a model called Product which has the following properties:
Product Model
#Schema({ timestamps: true, id:true, toJSON:{virtuals:true} })
export class Product {
#Prop({ required: true, type: String })
title: string
#Prop({ type: String })
brand: string
#Prop({ type: String })
description: string
#Prop({ type: Number})
currentPrice: number
}
every 4 hours product price will get updated. I'm wondering what's the best way to store product price history in MongoDB. in a SQL database like PostgreSQL, we make a ProductPrice table with (productId, price, date). But as MongoDB max document size is 16MB and thousands of products that I have& what is the best solution in MongoDB??

This is not challenging for MongoDB feel free and just create a ProductHistory collection for that:
{
id: ObjectId,
product_id: ObjectId,
created_at: Date,
price: 99,
currency: "$",
expiration: 4,
expired: false
}

Related

How we can add custom fields to mongodb database?

Hello guys I need your help,
I am making a simple app where users can create a post about their favorite person so there are different fields to write text, now I've made the functionality for the user to create any new field with a title of their choice like if a person "a" want to add a new field with name age and custom "b" wants to add a new field with name country, so I've made this but how can I get that custom data automatically in my database so if the user will create a new field with any name I want that field and text written in it in my database. Can you help me with that?
and please try to explain to me in a simple manner am just a beginner with mongoDB.
there was a guy who told me to do this with $addFields aggregation but he didn't tell me anything else I saw many documents but am not getting How can I make this done :(
thank you sooooo much in advance. :)
here's my schema for default fields:
const personSchema = new Schema({
firstName: String,
lastName: String,
birthDay: String,
gender: String,
pronouns: String,
relationship: String,
user: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
username: String,
}
});
You simply have to pass the strict option to false while you create your schema!
By default, mongoose will set this option to true so you can't send any unnecessary information to DB!
const personSchema = new Schema({
firstName: String,
lastName: String,
birthDay: String,
gender: String,
pronouns: String,
relationship: String,
user: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
username: String,
}
}, { strict: false });
https://mongoosejs.com/docs/guide.html#strict

Object Fields Missing in Autogenerated Mutation Input for GraphQL AppSync

I have a custom GraphQL schema for a blogging application for my ReactJS app. The main model type is Post.
Post contains a number of properties that have a custom type eg Gallery: [MediaObject]. The majority of these properties with custom types are missing from the automatically generated CreatePostInput by AWS Amplify CLI. This means that I cannot create a Post object that has these properties with custom type.
The lack of these custom type properties is evident in the AppSync console, under Queries. In the Explorer, go to Mutations, click on CreatePost mutation, and you will see that 6 of the 21 properties for Post are missing. The missing properties are tags, attributes, seo, featuredImage, gallery and createdBy.
They are also missing in the autogenerated graphql schema in api > build > schema.graphql in the react app.
I have made multiple clean projects from scratch, and the problem is reproducible. I have updated the amplify cli to 4.51.2 and still no luck.
I would expect the CreatePostInput to have the associated inputs for all of the fields, not just comments (see the autogenerated CreatePostInput below) eg: attribute: AttributeInput, but these are not generated either. This would allow me to create a Post object that has these custom fields populated. However, only the CommentInput has been generated. What am I doing wrong here, or have I misunderstood something?
Thanks!
Below is my gql schema and both the autogenerated CreatePostInput and CommentInput:
type Post #model {
id: ID
createdBy: User
createdAt: AWSDateTime
updatedAt:AWSDateTime
type: PostType!
title: String!
status: Status
content: String!
excerpt: String
slug: AWSURL
wordpressId: Int
wordpressImageURLs: [AWSURL]
author: String
likes: Int
tags: [Tag]
attributes: Attribute
seo: Seo
featuredImage: MediaObject
gallery: [MediaObject]
comments: [Comment]
numberOfComments: Int
}
type User #model {
id: ID
username: String!
firstName: String
lastName: String
email: AWSEmail!
avatar: MediaObject
location: String
createdAt: AWSDateTime
updatedAt: AWSDateTime
Posts: [Post]
}
type Tag #model {
id: ID
postIds: [ID]
name: String!
}
type Attribute #model {
id: ID
postId: ID
link: String
imgUrl: AWSURL
ogImage: AWSURL
ogDescription: String
canonicalLink: AWSURL
}
type Seo #model {
id: ID
postId: ID
metaRobotsNoIndex: String
metaRobotsNoFollow: String
canonical: AWSURL
metaDesc: String
opengraphDescription: String
opengraphModifiedTime: AWSDateTime
opengraphPublishedTime: AWSDateTime
opengraphTitle: String
opengraphUrl: AWSURL
opengraphImage: AWSURL
twitterDescription: String
twitterImage: String
twitterTitle: String
schemaSeo: String
}
type Comment
{
id: ID
postId: ID!
createdBy: ID
author: String
createdAt: AWSDateTime
text: String!
likes: Int
}
type MediaObject #model {
id: ID
linkedId: ID
createdBy: ID
mediaItemUrl: AWSURL
srcSet: String
medium: AWSURL
thumb: AWSURL
sourceURL: AWSURL
description: String
bucket: String
region: String
key: String
type: MediaObjectType
}
type Like #model {
id: ID!
objectVotedOnID: ID!
createdBy: ID
createdAt: AWSDateTime
likeOn: LikeOnType
}
enum PostType {
TOOLS
BLOGS
INSPIRATIONS
NEWS
POSTS
NEWSLETTERS
}
enum LikeOnType {
POST
COMMENT
}
enum Status {
PUBLISH
DRAFT
}
enum MediaObjectType {
IMAGE
VIDEO
AUDIO
}
Autogenerated (only CreatePostInput and CommentInput have been included for brevity), found in amplify > backend > api > build > schema.graphql:
input CreatePostInput {
id: ID
createdAt: AWSDateTime
updatedAt: AWSDateTime
type: PostType!
title: String!
status: Status
content: String!
excerpt: String
slug: AWSURL
wordpressId: Int
wordpressImageURLs: [AWSURL]
author: String
likes: Int
comments: [CommentInput]
numberOfComments: Int
}
input CommentInput {
id: ID
postId: ID!
createdBy: ID
author: String
createdAt: AWSDateTime
text: String!
likes: Int
}
At least for the example, you cited, Post and MediaObject have a one-to-many relationship. That is, a single Post has many MediaObjects. You won't see the many relations of a one-to-many generated on the parent object for mutations.
When creating a parent object that has children, you'll need to do this in 2 steps.
Create the parent object, and return the id.
Using the parentId, create the N child objects.
Here is some documentation from Amplify on how to do this: https://docs.amplify.aws/cli/graphql-transformer/connection#has-many
To anyone else that notices this strange behaviour: The reason why CommentInput is included in the CreatePostInput, whilst the other custom types are not, is because Comment was the only type that I had forgot to label with an #model directive. I assume that this means that AppSync then views this type on the comments field as a set of nested fields, rather than an object with its own DynamoDB table. I have asked the AWS Amplify team and will update this answer when I hear more from them.

Work around Sequelize’s unique constraints in belongsToMany associations

I'm using Sequelize in my project. These are the two models:
const User = db.define('user', {
name: Sequelize.STRING,
password: Sequelize.STRING
})
const Product = db.define('product', {
name: Sequelize.STRING,
price: Sequelize.INTEGER
})
Now users can purchase products and I have associations setup like below:
Product.belongsToMany(User, {through: 'UserProducts'})
User.belongsToMany(Product, {through: 'UserProducts'})
I also have this UserProducts table with an additional column.
const UserProducts = db.define('UserProducts', {
status: Sequelize.STRING
})
Sequelize creates a composite key with combination of userId and productId and will only allow one record with a combination of userId and productId. So, for example, userId 2 and productId 14.
This is a problem for me because sometimes people want to purchase multiple times. I need one of the following scenarios to work:
Don't use the composite key and instead have a completely new auto-increment column used as key in UserProducts.
Instead of making key with userId and productId alone, allow me to add one more column into the key such as the status so that unique key is a combination of the three.
I do want to use the associations as they provide many powerful methods, but want to alter the unique key to work in such a way that I can add multiple rows with the same combination of user id and product id.
And since my models/database is already running, I will need to make use of migrations to make this change.
Any help around this is highly appreciated.
If anyone else is having problems in v5 of Sequelize, it is not enough to specify a primary key on the 'through' model.
You have to explicitly set the unique property on the through model.
User.belongsToMany(Product, { through: { model: UserProducts, unique: false } });
Product.belongsToMany(User, { through: { model: UserProducts, unique: false } });
Belongs-To-Many creates a unique key when the primary key is not present on through model.
Since you also have additional property in your UserProducts table, you need to define the model for UserProducts and then tell the sequelize to use that model instead of creating its own
class User extends Model {}
User.init({
name: Sequelize.STRING,
password: Sequelize.STRING
}, { sequelize })
class Product extends Model {}
ProjProductect.init({
name: Sequelize.STRING,
price: Sequelize.INTEGER
}, { sequelize })
class UserProducts extends Model {}
UserProducts.init({
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
status: DataTypes.STRING
}, { sequelize })
User.belongsToMany(Project, { through: UserProducts })
Product.belongsToMany(User, { through: UserProducts })
refer: Sequelize v4 belongsToMany
UPDATE
since you are using v3 and already have a model created for your UserProducts table use following snippet
UserProducts = db.define('UserProducts', {
id: {
type: Sequelize.INTEGER,
primaryKey: true,
autoIncrement: true
},
status: DataTypes.STRING
})
Since setting explicitly the unique property on the through model is not working in v6, the only solution i found is to define the 3 parts of the association this way :
User.hasMany(UserProducts);
UserProducts.belongsTo(User);
Product.hasMany(UserProducts);
UserProducts.belongsTo(Product);
You can then create your models and associations :
const user = await User.create(user_data);
const product = await Product.create(product_data);
const up = await UserProduct.create(up_data);
await up.setUser(user);
await up.setProduct(product);
If anyone has a better solution, I would be happy to know it

Mongoose query array of ObjectId's for one ObjectId

Trying to query a subdocument array which contains ObjectId refs, and find one item.
The items are ObjectId's and get populated when I add a new lesson and 'assign' it to a specific location.
This mongoose schema shows what i mean.
var mongoose = require('mongoose');
var lessonNames = new mongoose.Schema({
day: {type: String },
name: {type: String},
startTime: {type: String},
endTime: {type: String}
});
var locationNames = new mongoose.Schema({
name: {type: String, required: true},
address: String,
lessons: [ { type: mongoose.Schema.Types.ObjectId, ref: 'lessonnames'}]
});
mongoose.model('lessonnames', lessonNames);
mongoose.model('locationnames', locationNames);
I have added a lesson to one location, and now i'm trying to find this specific lesson. I've tried a couple of methods, but none worked for me. Every time i get a null or undefined reported when i output the query to the console.log.
if (location.lessons && location.lessons.length > 0) {
loc
.findById(req.params.locationid)
.populate('lessons', '_id')
.exec(function (err, myLesson) {
console.log(myLesson.lessons)
});
When i execute the above statement, i see the following being generated in the nodejs console, with mongoose debugging enabled.
Mongoose: locationnames.findOne({ _id: ObjectId("56d4b687c4bcb5681a870cb5") }) { fields: undefined }
GET /api/locations/56d4b687c4bcb5681a870cb5/lesson/56d4b687c4bcb5681a870cb4
Mongoose: lessonnames.find({ _id: { '$in': [ ObjectId("56d4b687c4bcb5681a870cb4") ] } }) { fields: { _id: 1 } }
The code line below is the output from console.log. As you can see, the ObjectId is shown as part of the lessons array. But how do I make a query which 'selects' this ObjectId, so i can reference with it to a lesson.
[{"_id":"56d4b687c4bcb5681a870cb4"}]
Search the web, and saw some posts about the _id nog being a string type, and i should cast it to be a string. But i think it should be possible with ObjectId and it should be rather easy to query for it, but i lack good knowledge of mongoose and mongodb to get it working.
Any help would be appreciated!
Please try it as below, the {_id: "56d4b687c4bcb5681a870cb4"} in populate to match the ids in lessons array.
loc.findById(req.params.locationid)
.populate('lessons', null, {_id: "56d4b687c4bcb5681a870cb4"})
.exec(function (err, location) {
});

How to combine two collections in Mongoose and sort them using createdDate

I am developing application using MEAN.js in which I have two documents as below:
var RingtoneSchema = new Schema({
language: {
label: String,
code : String
},
deliver:{
type:Boolean,
default:false
},
artists: [RingtoneArtist],
digital_booklet:{
title: {
type: String,
trim:true
},
copyright:{
type: String,
trim:true
}
}
title: {
type: String,
default: '',
required: 'Please fill Album title',
trim: true
},
created: {
type: Date,
default: Date.now
}
});
var AlbumSchema = new Schema({
language: {
label: String,
code : String
},
deliver:{
type:Boolean,
default:false
},
artists: [artist],
title: {
type: String,
default: '',
required: 'Please fill Album title',
trim: true
},
created: {
type: Date,
default: Date.now
});
I want to get all the records from both schema and order them by created date to display them in client side. I don't know how to do this using mongoose.
Any help will be appreciated.
Mongodb itself is not relational database and any kind of "join" operation is not possible. I see two ways to go:
Easy way, but not the best way:
If you, say, need to show 30 items on a page you need:
Load 30 items from Ringtone, load 30 items from AlbumSchema
Put them together in one array
Do final sort in memory.
Downside of this approach is you need to load more data into memory than needed and also perform in memory sort manually.
A better way. If you need to show data from two collections it can be a good sign to have one collection instead if two. Your two collections schema looks very similar. Later you can use sparse indexes to optimize queries which require only one of two entities.
Implementation details if you pick first way: you can load data from two queries in parallel using async or using promises and do final in memory sort using underscore
Hope this helps!

Resources