aravindnc / mongoose-paginate-v2

A custom pagination library for Mongoose with customizable labels.
https://www.npmjs.com/package/mongoose-paginate-v2
MIT License
500 stars 92 forks source link

class-transformer not working properly #169

Open necm1 opened 2 years ago

necm1 commented 2 years ago

Hey!

I'm actually facing an issue using class-transformer using NestJS. I try to exclude some properties for my response, but getting following error:

TypeError: obj.toObject is not a function

Method I'm using to create paginate:

    const posts = await this.post.paginate(undefined, {
      limit: 10,
      page,
      populate: [
        {
          path: 'user',
          transform: (doc: any, id: any) => {
            return new User(doc);
          },
          populate: {
            path: 'profile',
            model: 'Profile',
            transform: (doc: any, id: any) => {
              return new Profile(doc);
            },
          },
        },
        {path: 'book'},
      ],
      sort: {_id: -1},
    });

The following entities User & Profile is a normal Scheme which looks for example like this:

@Schema({collection: 'users', versionKey: false})
/**
 * @class User
 */
export class User {
  @Exclude()
  /**
   * @public
   * @property
   * @type {string}
   */
  public id: string;

  @Prop({required: true, unique: true})
  /**
   * @public
   * @property
   * @type {string}
   */
  public username: string;

  @Prop({required: true})
  @Exclude()
  /**
   * @public
   * @property
   * @type {string}
   */
  public password: string;

  @Prop({required: true, unique: true})
  /**
   * @public
   * @property
   * @type {string}
   */
  public email: string;

  @Exclude()
  @Prop()
  /**
   * @public
   * @property
   * @type {Date}
   */
  public created_at: Date;

  @Prop()
  @Exclude()
  /**
   * @public
   * @property
   * @type {Date}
   */
  public updated_at: Date;

  @Prop({type: Types.ObjectId, ref: 'Profile'})
  /**
   * @public
   * @property
   * @type {Profile}
   */
  public profile: Profile | Types.ObjectId;

  /**
   * User constructor
   *
   * @constructor
   * @param {Partial<User>} partial
   */
  constructor(partial: Partial<User>) {
    Object.assign(this, partial);
  }
}

How I create the response:

    const posts = await this.postService.getHomePostsPaginate(
      query.page ? query.page : 1
    );

    const postsArray: any[] = [];

    posts.docs.forEach((v) => {
      // @TODO transform v.user & v.user.profile
      v.user = new User(v);
      v.user.profile = new Profile(v.user.profile);
      postsArray.push(v);
    });

Even without using the transform object in my populate I'm facing the same error.

I've an example where it works using findOne() (Excluding properties works):


    const user = await this.userService.getByName(req.user.username, true);
    user.profile = new Profile(user.profile);
``

Is there any reason for this behaviour? I appreciate every help!
aravindnc commented 2 years ago

Duplicate of #160

aravindnc commented 2 years ago

@necm1 It seems there is an issue with toObject, which we need to figure it out.

aravindnc commented 2 years ago

I have spent sometime with this, and still no idea what's causing this issue.

georgeben commented 2 years ago

@necm1 Please how did you add paginate method to your mongoose model? Typescript keeps throwing an error that paginate does not exist on a mongoose document.

necm1 commented 2 years ago

@necm1 Please how did you add paginate method to your mongoose model? Typescript keeps throwing an error that paginate does not exist on a mongoose document.

You need to use the PaginateModel<T>-interface i.e.:

import {PaginateModel} from 'mongoose';

  /**
   * BookService constructor
   *
   * @constructor
   * @param {PaginateModel<Book & Document>} book
   * @param {CacheService} cacheService
   * @param {AuthorService} authorService
   */
  constructor(
    @InjectModel(Book.name) private book: PaginateModel<Book & Document>,
    private readonly cacheService: CacheService,
    private readonly authorService: AuthorService
  ) {}

@aravindnc some time passed - did you find any solution / fix for this issue?

johnpozy commented 2 months ago

this solution is working for me. mongoose-paginate-v2 return docs object alongside with other data such as totalDocs, totalPages etc.. the docs is equivalent to my schema, so i just re-convert it to object using plainToClass from class-transformer and the @Exclude() annotation is working as expected.

@Schema({
  collection: 'services',
  timestamps: true,
  toJSON: {
    virtuals: true,
    transform: (doc, ret) => {
      ret['id'] = ret['_id'];

      if (ret['fee'] && ret['fee'].length > 0) {
        ret['fees'] = ret['fee'][0]['ref'];
      }
    },
  },
})
export class Service implements IService {
  @Prop({
    type: mongoose.Schema.Types.ObjectId
  })
  @Exclude()
  _id: string;

  @Prop({
    type: mongoose.Schema.Types.ObjectId
  })
  id: string;

  @Prop({ type: String })
  service: string;

  @Prop({
    type: String,
    enum: EServiceType,
  })
  type: EServiceType;

  @Prop({
    type: String,
    enum: EServiceType,
  })
  vehicleType: EServiceType;

  @Prop({
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Service',
  })
  @Type(() => Service)
  parent?: string | undefined;

  @Exclude()
  @Prop([
    {
      type: ServiceFee,
    },
  ])
  @Type(() => ServiceFee)
  fee: ServiceFee[];

  @Prop({
    type: mongoose.Schema.Types.ObjectId,
    ref: ServiceFeeComponent.name,
  })
  @Type(() => ServiceFeeComponent)
  fees: ServiceFeeComponent[];

  @Prop({
    type: Date,
  })
  createdAt: Date;

  @Prop({
    type: Date,
  })
  updatedAt: Date;

  @Prop({
    type: String,
  })
  created_by: string;

  @Prop({
    type: String,
  })
  updated_by: string;

  constructor(partial: Partial<Service>) {
    Object.assign(this, partial);
  }
}

export const ServiceSchema = SchemaFactory.createForClass(Service);

ServiceSchema.plugin(mongoosePaginate);
public getServices = async (query: FilterQuery<Service>, options: PaginateOptions) => {
  return this._serviceModel
    .paginate(query, options)
    .then((services: PaginateResult<Service>) => ({
      ...services,
      docs: services.docs.map(service => plainToClass(Service, JSON.parse(JSON.stringify(service))))
    }));
};