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

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);

Related

Nestjs Mongoose nested schema is not creating default values

Here is my code that I used in my entity
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import * as mongoose from 'mongoose';
#Schema({ _id: false })
export class Current {
#Prop({ default: '' })
operation: string;
#Prop({
default: '',
enum: [rentReportStatus.optin, rentReportStatus.optout, ''],
})
status: string;
#Prop({ default: Date.now() })
createdAt: Date;
}
const CurrentSchema = SchemaFactory.createForClass(Current);
#Schema({ collection: 'tests', timestamps: true })
export class Test extends BaseEntity {
#Prop({ type: CurrentSchema })
current: Current;
#Prop()
userId: mongoose.Schema.Types.ObjectId;
#Prop()
createdBy: mongoose.Schema.Types.ObjectId;
#Prop()
updatedBy: mongoose.Schema.Types.ObjectId;
}
export const RentReportingSchema = SchemaFactory.createForClass(RentReporting);
I tried many ways but current is not getting initialized with default values during save operation

#Prop decorator for specific nested objects in array

I have an array of objects that returns in JSON, each object looks like that:
{
"reviewId": "f1a0ec26-9aca-424f-8b05-cff6aa3d2337",
"authorName": "Some name",
"comments": [
{
"userComment": {
"text": "\tAmazing!",
"lastModified": {
"seconds": "1659685904",
},
"starRating": 5,
"reviewerLanguage": "en",
}
},
{
"developerComment": {
"text": "Thank you.",
"lastModified": {
"seconds": "1659688852",
}
}
}
]
}
I'm trying to create a Schema for this specific object, but I have some issues and I cannot understand how to create it, this is what i've done so far:
import mongoose, { Document, Mongoose } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
export type ReviewDocument = Review & Document;
#Schema()
export class Review {
#Prop({ type: String, required: true })
reviewId: string;
#Prop({ type: String })
authorName: string;
#Prop({ type: mongoose.Schema.Types.Array })
comments: Comments[];
}
#Schema()
export class Comments {
#Prop({ type: mongoose.Schema.Types.ObjectId })
userComment: UserComment;
#Prop({ type: mongoose.Schema.Types.ObjectId })
developerComment: DeveloperComment;
}
#Schema()
export class UserComment {
#Prop({ type: String })
text: string;
#Prop({ type: String })
originalText: string;
#Prop({ type: String })
lastModified: string;
#Prop({ type: Number })
starRating: number;
#Prop({ type: String })
reviewerLanguage: string;
}
#Schema()
export class DeveloperComment {
#Prop({ type: String })
text: string;
#Prop({ type: String })
lastModified: string;
}
export const ReviewSchema = SchemaFactory.createForClass(Review);
It gives me an error:
/.../rtr-backend/src/schemas/reviews.schema.ts:21
userComment: UserComment;
^
ReferenceError: Cannot access 'UserComment' before initialization
at Object.<anonymous> (/.../rtr-backend/src/schemas/reviews.schema.ts:21:18)
at Module._compile (node:internal/modules/cjs/loader:1120:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1174:10)
at Module.load (node:internal/modules/cjs/loader:998:32)
at Function.Module._load (node:internal/modules/cjs/loader:839:12)
at Module.require (node:internal/modules/cjs/loader:1022:19)
at require (node:internal/modules/cjs/helpers:102:18)
at Object.<anonymous> (/.../rtr-backend/src/reviews/reviews.module.ts:6:1)
at Module._compile (node:internal/modules/cjs/loader:1120:14)
at Object.Module._extensions..js (node:internal/modules/cjs/loader:1174:10)
What is best practice?
There are a few things to notice when you define a mongoose schema:
The schema types of primitive properties (e.g. string, number) are automatically inferred, so you don't need to explicitly specify { type: String} in the decorator.
In order to preserve the nested schema validation, each object has to have its own schema or be defined using the raw schema definition.
For each nested schema please mind the default properties created by mongoose, such as timestamps, _id, __v, and so on. You could manipulate them by passing options object in the #Schema() decorator.
Here is the schema definition for your use case:
import { Prop, raw, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document, Types } from 'mongoose';
#Schema({ _id: false })
class UserComment {
#Prop({ required: true })
text: string;
#Prop(raw({ seconds: { type: Number } }))
lastModified: Record<string, number>;
#Prop({ required: true })
starRating: number;
#Prop({ required: true })
reviewerLanguage: string;
}
const UserCommentSchema = SchemaFactory.createForClass(UserComment);
#Schema({ _id: false })
class DeveloperComment {
#Prop({ required: true })
text: string;
#Prop(raw({ seconds: { type: Number } }))
lastModified: Record<string, number>;
}
const DeveloperCommentSchema = SchemaFactory.createForClass(DeveloperComment);
#Schema({ _id: false })
class Comment {
#Prop({ type: UserCommentSchema })
userComment?: UserComment;
#Prop({ type: DeveloperCommentSchema })
developerComment?: DeveloperComment;
}
const CommentSchema = SchemaFactory.createForClass(Comment);
#Schema({ timestamps: true, versionKey: false })
export class Review {
_id: Types.ObjectId;
createdAt: Date;
updatedAt: Date;
#Prop({ unique: true, required: true })
reviewId: string;
#Prop({ required: true })
authorName: string;
#Prop({ type: [CommentSchema], default: [] })
comments: Comment[];
}
export type ReviewDocument = Review & Document;
export const ReviewSchema = SchemaFactory.createForClass(Review);
PS: In this documentation page you could find plenty of use cases: https://docs.nestjs.com/techniques/mongodb
I think you need to first define the schemas in the order of their dependencies, like this:
import mongoose, { Document, Mongoose } from 'mongoose';
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
export type ReviewDocument = Review & Document;
#Schema()
export class UserComment {
#Prop({ type: String })
text: string;
#Prop({ type: String })
originalText: string;
#Prop({ type: String })
lastModified: string;
#Prop({ type: Number })
starRating: number;
#Prop({ type: String })
reviewerLanguage: string;
}
#Schema()
export class DeveloperComment {
#Prop({ type: String })
text: string;
#Prop({ type: String })
lastModified: string;
}
#Schema()
export class Comments {
#Prop({ type: mongoose.Schema.Types.ObjectId })
userComment: UserComment;
#Prop({ type: mongoose.Schema.Types.ObjectId })
developerComment: DeveloperComment;
}
#Schema()
export class Review {
#Prop({ type: String, required: true })
reviewId: string;
#Prop({ type: String })
authorName: string;
#Prop({ type: mongoose.Schema.Types.Array })
comments: Comments[];
}
export const ReviewSchema = SchemaFactory.createForClass(Review);

MissingSchemaError in Nestjs

hello everyone i'm not able specify relation to another model. when i add a relation it's showing me this error
Book Model
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Document } from 'mongoose';
export type BookDocument = Book & Document;
#Schema({ timestamps: true, collection: 'books' })
export class Book {
#Prop({ type: String, required: true })
name: string;
#Prop({ type: String, required: true })
author: string;
#Prop({ type: String, required: true })
bookType: string;
}
export const BooksSchema = SchemaFactory.createForClass(Book);
BookLend Model
import { Prop, Schema, SchemaFactory } from '#nestjs/mongoose';
import { Schema as mongooseSchema, Document } from 'mongoose';
import { Book } from '../../books/enitiy/book.model';
import { IsNotEmpty } from 'class-validator';
export type BookLendDocument = BookLend & Document;
#Schema({ timestamps: true })
export class BookLend {
#IsNotEmpty()
#Prop({ type: mongooseSchema.Types.ObjectId, ref: 'books', required: true })
bookId: Book;
#IsNotEmpty()
#Prop({ type: String, required: true })
name: string;
#IsNotEmpty()
#Prop({ type: String, required: true })
returnDate: string;
#Prop({ type: String })
returnedOn: string;
#IsNotEmpty()
#Prop({ type: String, required: true })
status: string;
}
export const BookLendSchema = SchemaFactory.createForClass(BookLend);
i'm referring the books objectID to booklend booksID , when i use below code i'm getting error MissingSchemaError: Schema hasn't been registered for model "books".
const allBookLendDetails = await this.bookLend
.find()
.populate('bookId')
.exec();
hi guy's I fixed it by importing the BooksSchema into BookLend Module file
here ref code:-
import { Module } from '#nestjs/common';
import { BookLendService } from './book-lend.service';
import { BookLendController } from './book-lend.controller';
import { MongooseModule } from '#nestjs/mongoose';
import { BookLendSchema } from './entities/book-lend.entity';
import { CommonService } from '../common-service/common-service.service';
import { BooksSchema } from '../books/enitiy/book.model';
#Module({
imports: [
MongooseModule.forFeature([
{ name: 'booklend', schema: BookLendSchema },
{ name: 'books', schema: BooksSchema },
]),
],
controllers: [BookLendController],
providers: [BookLendService, CommonService],
})
export class BookLendModule {}
In the service file, you need to inject the model
constructor(
#InjectModel('booklend') private readonly bookLend: Model<BookLend>,
#InjectModel('books') private readonly books: Model<Book>
) {}

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();

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

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
}
]