Creation of duplicate IDs in arrays of different documents in mongo db - node.js

I want to add an element to the array of all collections in the city collection, but Mongo creates the ID as duplicate.
this is my code
await this.cityRepository.updateMany(
{},
{
$push: {
tags: {
title: tagValue.title,
description: tagValue.description,
applyToAllCity: tagValue.cityId ? false : true,
},
},
},
);
City Schema
export class BaseCity extends Document {
#Prop({
type: String,
required: true,
})
_id: string;
#Prop({ type: String, unique: true })
code: string;
#Prop({ type: String, ref: Province.name })
province: string | Province;
#Prop({ type: String })
faName: string;
}
#Schema({ timestamps: true })
#Schema({ collection: 'city', virtuals: true, _id: false, timestamps: true })
export class City extends BaseCity {
#Prop({ type: String })
imageName: string;
#Prop({ index: true, type: String })
enName: string;
#Prop({ type: Number })
displayOrder: number;
#Prop({ type: Boolean })
isFeatured: boolean;
#Prop({ type: Boolean })
isEnabled: boolean;
#Prop({ type: Coordinate })
coordinate: Coordinate;
#Prop([{ type: Region, ref: Region.name, default: [] }])
region: Region[];
#Prop([{ type: SubMenu }])
subMenu: SubMenu[];
#Prop([{ type: CityTags }])
tags: CityTags[];
}
const CitySchema = SchemaFactory.createForClass(City);
CitySchema.index({ faName: 'text' });
export { CitySchema };
DB
As you can see, ID 63ec8f47efbd82c8face341a is duplicated in all documents.
Is there a solution to solve this problem?

To avoid duplicate IDs, you could use the $addToSet instead of $push. The $addToSet adds an element to an array only if it does not already exist in the set.
Check this:
await this.cityRepository.updateMany(
{},
{
$addToSet: {
tags: {
title: tagValue.title,
description: tagValue.description,
applyToAllCity: tagValue.cityId ? false : true,
},
},
},
);
Update:
To keep unique ids
await this.cityRepository.updateMany(
{},
{
$push: {
tags: {
_id: new ObjectId(),
title: tagValue.title,
description: tagValue.description,
applyToAllCity: tagValue.cityId ? false : true,
},
},
},
);

Related

mongoDb. Populate document based on specific name

I want to get some basics in mongo, but I am stuck as I am not sure what I am doing wrong.
So, I have two documents: Race and Building.
My schema for race looks like this:
import mongoose, { Model, Schema, Types } from "mongoose";
export interface IRaceSchema {
name: string;
buildings: Types.ObjectId;
description: string;
}
const raceSchema = new mongoose.Schema<IRaceSchema, Model<IRaceSchema>>(
{
name: {
type: String,
unique: true,
},
description: String,
buildings: [
{
type: Schema.Types.ObjectId,
ref: "Building",
},
],
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
raceSchema.pre(/^find/, function (next) {
this.populate({
path: "buildings",
match: { race: { $eq: this.name } },
});
next();
});
export const Race = mongoose.model<IRaceSchema>("Race", raceSchema);
In here i have populate from my building document.
My buildingModel looks like this:
import mongoose, { Model, Schema, Types } from "mongoose";
interface IBuildingSchema {
name: string;
description?: string;
race: string;
buildingType?: string;
level: number;
bonus?: string[];
}
const buildingSchema = new mongoose.Schema<
IBuildingSchema,
Model<IBuildingSchema>
>(
{
name: String,
description: String,
buildingType: String,
level: {
type: Number,
default: 1,
},
bonus: [String],
race: {
type: String,
enum: ["orc", "human", "elvs", "nekro"],
},
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true },
}
);
export const Building = mongoose.model<IBuildingSchema>(
"Building",
buildingSchema
);
My issue is, that I want to populate my Race document with buildings based on race assigned to my building
I am using $eq but not sure if this is proper way, as nothing is populating

NestJS mongose schema with array of schema doesn't populate virtual id

I have the following schema
export type MapDocument = Map & Document
#Schema({
timestamps: true,
versionKey: false,
id: true
})
export class Map {
constructor(partial?: Partial<Map>) {
if (partial)
Object.assign(this, partial)
}
#IsOptional()
#IsUUID()
#Prop({ type: Object, default: uuidv4, required: false})
#Exclude({ toPlainOnly: true })
_id?: Object
#ApiPropertyOptional({ type: String, format: 'uuid' })
#IsOptional()
#IsUUID()
id?: string
#ApiProperty()
#IsAscii()
#MaxLength(10)
#Prop()
building: string
#ApiProperty()
#IsInt()
#Prop()
#Transform(({ value }) => +value, { toClassOnly: true })
floor: number
#ApiPropertyOptional({ type: Image, format: 'uuid'})
#IsOptional()
#IsUUID()
#Prop({ type: String, ref: 'Image' })
#Transform(({ value }) => new Image(value).id, { toClassOnly: true })
image?: string
#ApiProperty({ type: [Marker] })
#IsArray()
#Type(() => Marker)
#Prop({ type: [MarkerSchema] })
markers?: Marker[]
}
const MapSchema = SchemaFactory.createForClass(Map)
MapSchema.index({ building: 1, floor: 1 }, { unique: true });
const mongooseLeanVirtuals = require('mongoose-lean-virtuals')
MapSchema.plugin(mongooseLeanVirtuals);
export { MapSchema }
export class UpdateMap extends PartialType(Map) {}
Marker is declared as follows
export type MarkerDocument = Marker & Document
#Schema({
timestamps: true,
versionKey: false,
id: true
})
export class Marker {
constructor(partial?: Partial<Marker>) {
if (partial)
Object.assign(this, partial)
}
#IsOptional()
#IsUUID()
#Prop({ type: Object, default: uuidv4, required: false})
#Exclude({ toPlainOnly: true })
_id?: Object
#ApiPropertyOptional({ type: String, format: 'uuid' })
#IsOptional()
#IsUUID()
id?: string
#ApiPropertyOptional({ type: Desk, format: 'uuid'})
#IsOptional()
#IsUUID()
#Prop({ type: String, required: false, ref: 'Desk' })
#Transform(({ value }) => new Desk(value).id, { toClassOnly: true })
desk?: string
#ApiProperty()
#IsNumberString()
#Prop()
xPercent: string
#ApiProperty()
#IsNumberString()
#Prop()
yPercent: string
}
const MarkerSchema = SchemaFactory.createForClass(Marker)
const mongooseLeanVirtuals = require('mongoose-lean-virtuals')
MarkerSchema.plugin(mongooseLeanVirtuals);
export { MarkerSchema }
export class UpdateMarker extends PartialType(Marker) {}
Important to note that has a field (desk) referencing another collection but I don't want these items to be stored in their own collection but as a subdocument of the 'maps' collection directly
The 'desk' schema is declared as follows
export type DeskDocument = Desk & Document
#Schema({
timestamps: true,
versionKey: false,
id: true
})
#ApiExtraModels(Image)
export class Desk {
constructor(partial?: Partial<Desk>) {
if (partial)
Object.assign(this, partial)
}
#IsOptional()
#IsUUID()
#Prop({ type: Object, default: uuidv4, required: false})
#Exclude({ toPlainOnly: true })
_id?: Object
#ApiPropertyOptional({ type: String, format: 'uuid' })
#IsOptional()
#IsUUID()
id?: string
#ApiProperty({ type: Board, format: 'uuid'})
#IsUUID('all')
#Prop({ type: String, required: true, ref: 'Board' })
#Transform(({ value }) => value.id, { toClassOnly: true })
board: string
#ApiProperty({ type: [Number] })
#IsArray()
#Prop({ type: () => [Number], required: true })
relays?: number[]
#ApiProperty()
#IsAscii()
#MaxLength(250)
#Prop()
name: string
#ApiProperty()
#IsAscii()
#MaxLength(250)
#Prop()
description: string
#ApiProperty()
#IsAscii()
#MaxLength(10)
#Prop()
building: string
#ApiProperty()
#IsInt()
#Prop()
#Transform(({ value }) => +value, { toClassOnly: true })
floor: number
#ApiProperty()
#IsAscii()
#MaxLength(10)
#Prop()
code: string
#ApiPropertyOptional({ type: [Image], format: 'uuid'})
#IsOptional()
#IsUUID('all', { each: true })
#Prop({ type: [String], required: false, ref: 'Image' })
#Transform(({ value }) => value.id, { toClassOnly: true })
images?: String[]
}
const DeskSchema = SchemaFactory.createForClass(Desk)
DeskSchema.index({ board: 1, relays: 1 }, { unique: true });
const mongooseLeanVirtuals = require('mongoose-lean-virtuals')
DeskSchema.plugin(mongooseLeanVirtuals)
export { DeskSchema }
export class UpdateDesk extends PartialType(Desk) {}
The find methods tried to populate everything down to desk (no need for me to populate deeper than that)
async findAll(): Promise<Map[]> {
return (await this.mapModel.find().populate({
path: 'image',
model: Image,
transform: (doc: Image) => new Image(doc),
}).populate({
path: 'markers',
model: Marker,
transform: (doc: Marker) => new Marker(doc),
populate: {
path: 'desk',
model: Desk,
transform: (doc: Desk) => new Desk(doc),
options: { lean: true, virtuals: true },
}
}).lean({ virtuals: true }).exec())
.map(map => new Map(map))
}
Two issues
Minor: If I don't specify lean: true as an option for the desk populate I get the full mongo document. This is not the case for the 'markers' array which relies on the 'lean' settings
Main: the desk object gets populated but the virtual 'id' field doesn't
This is the output I get:
{
"building": "A",
"floor": 1,
"image": {
"name": "OfficePlan12",
"url": "https://drive.google.com/uc?id=1H2nnIRjR2e7Z7yoVnHxLCTaYs8s5iHrT",
"updatedAt": "2022-06-24T09:03:03.786Z",
"id": "2b31f419-e069-4058-813e-54ce0a941440"
},
"updatedAt": "2022-06-26T10:19:22.650Z",
"markers": [
{
"yPercent": "15.853658536585366",
"xPercent": "18.083462132921174",
"desk": {
"images": [
"b85eefee-eeca-4193-87ae-9329fad8256a",
"692743d0-a860-4451-b313-b21a144ef387"
],
"description": "Work like a boss",
"name": "Management desk",
"createdAt": "2022-02-19T21:12:18.832Z",
"updatedAt": "2022-06-07T14:02:13.556Z",
"building": "A",
"code": "01",
"floor": 1,
"relays": [
"1"
],
"board": "932c3e9b-85bd-42c8-9bc0-a318eea7b036"
},
"updatedAt": "2022-06-26T10:19:22.650Z",
"createdAt": "2022-06-26T10:19:22.650Z",
"id": "a8149e84-2f62-46c3-990f-531eff82f6d5"
}
],
"id": "37dc791f-724b-44e2-baaf-bfc606385996"
}
As you can see the 'desk' object doesn't have 'id' field.
Any help would be much appreciated, thanks!
I finally found a workaround.
It looks pretty bad to be fair but it does the trick.
Basically I modified the transform function for the 'desk' document which is the first 'outer' document of my 'inner' document 'image'.
The function looks as follows:
transform: (doc: Desk) => new Desk((new this.deskModel(doc)).toObject({ virtuals: true })),
options: { lean: true },
populate: {
path: 'images',
model: Image,
transform: (doc: Image) => new Image(doc)
}
Basically I needed to inject the corresponding model in the service constructor to be able to call the 'toObject' function with 'options' and 'populate'. Finally I need to use the obtained object to build a new instance of 'Desk' to make sure that class validation functions are applied correctly to my endpoints.
Hope this will help someone and that maybe someone can suggest a more elegant solution.

How to auto update the following schema based upon certain condition?

Suppose I have Auction Schema as follows
import { Document, model, Schema } from "mongoose";
import { IProduct } from "./product";
const AuctionSchema = new Schema(
{
name: {
type: String,
required: true,
unique: true,
},
products: [{ type: Schema.Types.ObjectId, ref: "Product" }],
status: { type: Boolean, default: false },
duration: { type: Number },
start: { type: Date },
end: { type: Date },
},
{ timestamps: true }
);
export interface IAuction extends Document {
name: string;
status?: boolean;
products: IProduct["_id"][];
start?: Date;
end?: Date;
duration?: number;
}
const Auction = model<IAuction>("Auction", AuctionSchema);
export default Auction;
I want to update this auction related field i.e status to false automatically if the field start and end are same
How to do this?

NestJS/Mongoose - Create an Array of Object with reference to another Schema

I'm building the back-end side of a personal application, and I have two particular models/schemas. One if for Products an another for Orders. I want to do the following:
The Orders need to have the following array with this structure:
products: [
{
product: string;
quantity: number;
}
]
The product should be an ObjectId of mongo, and this needs a reference for a 'Product' model.
How I can reach this? I don't really know how to "type" this with the #Prop() decorator.
#Prop({
// HOW I DO THIS?
})
products: [{ quantity: number; product: mongoose.Types.ObjectId }];
This is my Order Schema:
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import * as mongoose from 'mongoose';
import { Document } from 'mongoose';
export type OrderDocument = Order & Document;
#Schema()
export class Order {
#Prop({ type: String, required: true })
name: string;
#Prop({ type: Number, min: 0, required: true })
total: number;
#Prop({
type: String,
default: 'PENDING',
enum: ['PENDING', 'IN PROGRESS', 'COMPLETED'],
})
status: string;
#Prop({
// HOW I DO THIS?
})
products: [{ quantity: number; product: mongoose.Types.ObjectId }];
#Prop({
type: mongoose.Schema.Types.ObjectId,
ref: 'Customer',
required: true,
})
customer: mongoose.Types.ObjectId;
#Prop({
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true,
})
owner: mongoose.Types.ObjectId;
#Prop({ type: Date, default: Date.now() })
createdAt: Date;
}
export const OrderSchema = SchemaFactory.createForClass(Order);
#Prop({
type:[{quantity:{type:Number}, product:{type:Schema.Types.ObjectId}}]
})
products: { quantity: number; product: Product }[];
The best way to achieve both reference population and mongoose schema validation is to create a subschema for the nested entity.
import { Document, SchemaTypes, Types } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
#Schema({ _id: false, versionKey: false })
class OrderProduct {
#Prop({ required: true })
quantity: number;
#Prop({ type: [SchemaTypes.ObjectId], ref: 'Product', required: true })
product: Product[];
}
const OrderProductSchema = SchemaFactory.createForClass(OrderProduct);
#Schema()
export class Order {
// other order schema props ...
#Prop([{ type: OrderProductSchema }])
products: OrderProduct[];
}

Express.js, Mongoose: Populate returns null

I'm learning express.js and mongoDB and want to create a functionality, where user should be able add products.
My code contains 2 models, User and Product. User has a reference to Product. In User query when i try to populate Product', null is returned.
I'm testing it with postman, and the result is null in cart.
I am a beginner and don't understand how to solve the issue.
user schema
import mongoose, { Document } from 'mongoose'
import { ProductDocument } from './Products'
export type UserDocument = Document & {
firstName: string;
lastName: string;
password: string;
email: string;
isAdmin: boolean;
cart: ProductDocument[];
resetLink: string;
}
const userSchema = new mongoose.Schema({
firstName: {
type: String,
required: true,
max: 64,
},
lastName: {
type: String,
required: true,
max: 64,
},
password: {
type: String,
required: true,
min: 6,
},
email: {
type: String,
required: true,
lowercase: true,
trim: true,
},
isAdmin: {
type: Boolean,
default: false,
},
cart: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'Products',
},
],
resetLink: {
data: String,
default: '',
},
})
export default mongoose.model<UserDocument>('Users', userSchema)
product schema
import mongoose, { Document } from 'mongoose'
export type ProductDocument = Document & {
name: string;
description: string;
categories: string[];
variants: string[];
sizes: number[];
}
const productSchema = new mongoose.Schema({
name: {
type: String,
required: true,
index: true,
},
description: {
type: String,
},
categories: [String],
variants: [String],
sizes: [Number],
})
export default mongoose.model<ProductDocument>('Products', productSchema)
and the controller for responsible for populating
export const getProduct = async (req: Request, res: Response) => {
try {
const { productId } = await req.body
const productBought = await Products.findOne({ _id: productId }).then(
(product) => {
return product
}
)
console.log(productBought)
const updated = await Users.findOneAndUpdate(
{ _id: req.params.userId },
{ $push: { cart: productBought } },
{ new: true }
)
.populate('cart')
.exec()
return res.json(updated)
} catch (error) {
return res.status(404).json({ message: 'does not work' })
}
}
output from postman
{
"isAdmin": false,
"cart": [
null,
null
],
"_id": "5f894b3c06b4a108f8d9a7ab",
"firstName": "John",
"lastName": "Doe",
"password": "$2b$08$rA0/r8iVBWeSyyerPDpPWO.ztgouQoseX0QFBZ.mlPgb6tELlrhpy",
"email": "john#gmail.com",
"__v": 0
}
your "cart" like[ObjectId,ObjectId], but your "productBought" like[{},{}].
try let cart = [];productBought.forEach((v,k) => {cart[k] = v._id});
and use { $push: { cart: { $each: cart } },

Resources