Is it possible to have in Mongodb (with mongoose and typegoose) an index on array of nested keys? - mongodb

In mongodb (using mongoose and typegoose) is it possible to have an array index on a nested key?
export class Member extends Typegoose {
#prop({ required: true })
public email!: string;
#prop({ required: true })
private userId!: string
}
#index({ 'members.userId': 1 })
export class Group {
#arrayProp({ items: Member })
public members: Member[];
#prop()
name: string;
}
If so, how can I query this collection if I'd want to find a group by userId?
Like this?
Group.findOne({ usersIds: userId })

yes it is possible, here is how you can do it:
// NodeJS: 14.5.0
// MongoDB: 4.2-bionic (Docker)
import { getModelForClass, prop, index } from "#typegoose/typegoose"; // #typegoose/typegoose#7.3.0
import * as mongoose from "mongoose"; // mongoose#5.9.25 #types/mongoose#5.7.32
class Member {
#prop({ required: true })
public email!: string;
}
#index({ "members.email": 1 }, { unique: true }) // added unique to be easily testable
class Group {
#prop({ type: Member })
public members?: Member[];
#prop()
public name?: string;
}
const GroupModel = getModelForClass(Group);
(async () => {
await mongoose.connect(`mongodb://localhost:27017/`, { useNewUrlParser: true, dbName: "verifyMASTER", useCreateIndex: true, useUnifiedTopology: true });
await GroupModel.create({ name: "group1", members: [{ email: "h#h.h" }] });
try {
await GroupModel.create({ name: "group2", members: [{ email: "h#h.h" }] });
console.log("didnt fail");
} catch (err) {
// it should error
console.log("err", err);
}
console.log(await GroupModel.listIndexes());
await mongoose.disconnect();
})();
output of GroupModel.listIndexes:
[
{ v: 2, key: { _id: 1 }, name: '_id_', ns: 'verifyMASTER.groups' },
{
v: 2,
unique: true,
key: { 'members.email': 1 },
name: 'members.email_1',
ns: 'verifyMASTER.groups',
background: true
}
]

Related

How to add timestamps fields and _id into nested schema by default

I have a correct schema User where fields _id, createdAt, updatedAt are written by default. But in schema Message it doesn't work.
export type UserDocument = HydratedDocument<User>;
#Schema({ timestamps: true, versionKey: false })
export class Message {
#Prop()
content: string;
}
#Schema({ timestamps: true, versionKey: false })
export class User implements UserInterface {
#Prop()
name: string;
#Prop()
email: string;
#Prop([{ type: mongoose.Schema.Types.ObjectId, ref: 'Message' }])
messages?: Message[];
}
export const UserSchema = SchemaFactory.createForClass(User);
Here is my function of message creating:
async createMessage() {
const user = await this.userModel.findById('63ee4fc044d93a4f6bebf934');
user.messages.push({ content: 'a' });
return await user.save();
}
And error is:
Cast to ObjectId failed for value "{ content: 'a' }" (type Object) at path "messages" because of "BSONTypeError"
But this snippet works fine:
async createUser(createUserDto: CreateUserDto): Promise<CreatedUserDto> {
return this.userModel.findOneAndUpdate(
{ name: createUserDto.name },
createUserDto,
{ upsert: true, new: true },
);
}
How to fix it?
Fixed id, correct implementation:
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { HydratedDocument } from 'mongoose';
import { UserInterface } from '../interface/user.interface';
export type UserDocument = HydratedDocument<User>;
#Schema({ timestamps: true, versionKey: false })
export class Message {
#Prop()
content: string;
}
export const MessageSchema = SchemaFactory.createForClass(Message);
#Schema({ timestamps: true, versionKey: false })
export class User implements UserInterface {
#Prop()
name: string;
#Prop()
email: string;
#Prop({ type: [MessageSchema], default: [] })
messages?: Message[];
}
export const UserSchema = SchemaFactory.createForClass(User);

#nestjs/mongoose, virtual populate with 2 databases

I'm trying to populate userId field which exists in database 1 from user document which exists in database 2.
I've already defined connectionName parameter in MongooseModule.ForRootAsync() I can't find out where the problem is. Also it works if I request info from db1 and db2 individually.
Actually on console.log(commentPopulated) userId field is just the objectId without populated fields from User schema and also sometimes with certain #Prop() and .populate() parameters app throw me this error:
MissingSchemaError: Schema hasn't been registered for model "User".
Using #nestjs/mongoose decorators how do I achieve this?
app.module.ts
MongooseModule.forRootAsync({
connectionName: 'db1',
useFactory: () => ({
uri: process.env.DB1,
connectionFactory: (connection: { plugin: (arg0: unknown) => void }) => {
connection.plugin(_)
connection.plugin(autoPopulate)
return connection
},
}),
}),
MongooseModule.forRootAsync({
connectionName: 'db2',
useFactory: () => ({
uri: process.env.DB2,
connectionFactory: (connection: { plugin: (arg0: unknown) => void }) => {
connection.plugin(_)
connection.plugin(autoPopulate)
return connection
},
}),
}),
comment.module.ts
const commentModule: DynamicModule = MongooseModule.forFeatureAsync([
{
name: Comment.name,
useFactory: () => {
return CommentSchema
}
}
], 'db1')
#Module({
imports: [commentModule],
providers: [CommentService, CommentResolver]
})
export class CommentModule { }
comment.schema.ts
#Schema({ toJSON: { virtuals: true, getters: true }, toObject: { virtuals: true, getters: true } })
#ObjectType()
export class Comment extends Document {
#Prop()
#Field(() => String)
readonly _id: MongooseSchema.Types.ObjectId
#Prop({ required: true })
#Field(() => String)
text: string
//TODO: Reference User document from DB2, Comment document exists in DB1
#Prop({ type: MongooseSchema.Types.ObjectId, ref: User.name})
#Field(() => User, { nullable: true })
userId: MongooseSchema.Types.ObjectId
#Prop({ type: String, enum: UserType, required: true, default: UserType.Regular })
#Field(() => UserType, { defaultValue: UserType.Regular })
userType: UserType
#Prop({ default: Date.now })
#Field(() => Date)
created: Date
}
export const CommentSchema = SchemaFactory.createForClass(Comment)
user.module.ts
const userModule: DynamicModule = MongooseModule.forFeatureAsync([
{
name: User.name,
useFactory: () => {
return UserSchema
},
},
], 'db2')
#Module({
imports: [userModule],
providers: [UserService, UserResolver]
})
export class UserModule { }
user.schema.ts
#Schema()
#ObjectType()
export class User extends Document {
#Prop()
#Field(() => String)
readonly _id: MongooseSchema.Types.ObjectId
#Prop({ required: true })
#Field(() => String)
firstName: string
#Prop({ required: true })
#Field(() => String)
lastName: string
#Prop({ required: true })
#Field(() => String)
email: string
}
export const UserSchema = SchemaFactory.createForClass(User)
comment.service.ts
#Injectable()
export class CommentService {
constructor(#InjectModel(Comment.name, 'db1') private readonly model: Model<Comment>) { }
async getComments() {
const commentPopulated = await this.model.findById('63b8608c7d4f880cba028bfe').populate('userId')
console.log(commentPopulated)
return commentPopulated
}
}
I have tried randomly playing with parameters on #Prop() decorator with no success, I think there is the problem, also played with .populate() function parameters.

MissingSchemaError: Schema hasn't been registered for model "Product"

------------------------------
Here is Order Controller
-----------------------------
import nc from "next-connect";
import db from "../../../utils/db";
import Order from "../../../models/OrderModel";
import { isAuth } from "../../../utils/auth";
const handler = nc();
handler.use(isAuth);
handler.get(async (req, res) => {
try {
await db.connect();
const order = await Order.findById(req.body.order).populate({
path: "product",
model: "Product",
});
await db.disconnect();
res.send(order);
} catch (err) {
console.log(err);
}
});
export default handler;
--------------------------------------------------------------
Here is Order Schema
--------------------------------------------------------------
import mongoose from "mongoose";
const orderSchema = new mongoose.Schema(
{
product: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Product",
required: true,
},
],
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
}
},
{ timestamps: true }
);
const Order = mongoose.models.Order || mongoose.model("Order", orderSchema);
export default Order;
----------------------------
Here is Product Schema
----------------------------
import mongoose from "mongoose";
const productSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
description: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
category: {
type: String,
required: true,
},
period: {
type: String,
required: true,
},
features: [{ type: String, required: true }],
},
{ timestamps: true }
);
const Product =
mongoose.models.Product || mongoose.model("Product", productSchema);
export default Product;
--------------------
I am getting this error: "MissingSchemaError: Schema hasn't been registered for model "Product".
Use mongoose.model(name, schema)"
There are also some orders including product ObjectId and I am trying to get the data using populate on the POSTMAN but getting this error.
I've really searched much before posting this but I've didn't solve the error

MongoDb aggregate query, how to select field from foreign table?

I am running a mongoDb query trying to get the data of my "User" which also include the "CounterParty.name" My aggregate query does get the user back, but does nothing with the fields from Counterparty. I went through the docs several times but cannot seem to figure it out.
My UserSchema
import { Schema, model } from "mongoose";
import mongoose from "mongoose";
export type UserDocument = mongoose.Document & {
email: string;
password: string;
status: string;
name: string;
counterParty: Schema.Types.ObjectId;
}
const userSchema = new Schema<UserDocument>(
{
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
name: {
type: String,
required: true
},
status: {
type: String,
default: "I am new"
},
counterParty: {
type: Schema.Types.ObjectId,
ref: "CounterParty",
}
},
{ timestamps: true }
)
export const User = model<UserDocument>('User', userSchema);
My CounterPartySchema
import { Schema, model } from "mongoose";
import mongoose from "mongoose";
export type CounterPartyDocument = mongoose.Document & {
name: string
}
const counterPartySchema = new Schema<CounterPartyDocument>(
{
name: {
type: String,
required: true,
unique: true,
trim: true
},
},
{ timestamps: true }
)
export const CounterParty = model<CounterPartyDocument>('CounterParty', counterPartySchema);
My query code
const user = await User.aggregate<any>([
{ '$match': { email: email } },
{
'$lookup': {
from: 'CounterParty',
localField: 'counterParty',
foreignField: '_id',
as: 'details'
}
}
])
console.log("user", user)
My console log statement now returns the user, but nothing to see of the counterParty.
Any help will be much appreciates! Cheers
Your aggregation query looks fine in the playground , maybe you dont have such _id: new ObjectId("...786") in the counterParty collection?

Nestes Schema virtual

I am trying to access the products collection in my find method. I added a .virtual method in order to access it but I currently get following error messsage:
MissingSchemaError: Schema hasn't been registered for model
"ProductDocument".
So far my implementation:
export type PriceDocument = Price & Document;
#Schema({ timestamps: true })
export class Price {
#Prop({
ref: 'ProductDocument',
required: true,
})
uid: string;
#Prop()
standardPrice: number;
#Prop({ required: true })
startDate: Date;
#Prop()
endDate: Date;
}
export const PriceSchema = SchemaFactory.createForClass<Price>(Price)
.index({
uid: 1, startDate: 1, endDate: 1,
}, { unique: true });
PriceSchema.virtual('products', {
ref: 'ProductDocument',
localField: 'uid',
foreignField: 'uid',
});
ProductPriceSchema.set('toObject', { virtuals: true });
ProductPriceSchema.set('toJSON', { virtuals: true });
This is how I try to access it in my find method
const productPriceDocument = await this.productPrice.find({
$and: queries, // dynamic queries
}).populate('products').exec();