0

in my NestJS app I have currently an Product schema and a payment method schema as subdocument in the product schema.

@Schema()
export class Product {
  @Prop({ required: true })
  name: string;

  @Prop([PaymentDetails])
  payments: PaymentDetails[];

}

@Schema()
export class PaymentDetails {
  @Prop()
  method: String;

  @Prop({ type: Boolean, default: false })
  isDefault: Boolean;
}

Now for validation purpose I created a DTO for the payment method. When I want to add the payment method to my product, then the push()-methods expects an instance of PaymentDetails[]. How to use my DTO now to create/push a new entry?

async addPayment(user: IUser, productId: string, payment: PaymentMethodDto) {
    const filter = { _id: productId };

    let p = await this.productModel.findOne<ProductDocument>(filter).exec();

    p.payments.push(`PaymentDetails[] expected`))

    return p.save();
}
Gerrit
  • 2,515
  • 5
  • 38
  • 63

2 Answers2

1

I solved the issue with:

@Prop([PaymentDetailsSchema])
payments: PaymentDetails[];

and

export type ProductDocument = HydratedDocument<
  Product,
  {
    payments: Types.DocumentArray<PaymentDetails>;
  }
>;

Afterwards doc.payments.id() is available.

Gerrit
  • 2,515
  • 5
  • 38
  • 63
0

I've read through your question and was a little confused by parts of it. Lets break it down and see if it's helpful.

Your schema definitions seem to be alright, although I think the more "correct" way is to get a schema for the sub-document class and pass that schema to the prop of the parent document as such:

@Schema()
export class PaymentDetails {
  ...
}
const PaymentDetailsSchema = SchemaFactory.createForClass(PaymentDetails);

export class Product {
  ...

  @Prop([PaymentDetailsSchema])
  /* @Prop({ type: [PaymentDetailsSchema] }) */
  payments: PaymentDetails[];
}

DTOs in nest serve as validators on the API level. Which means that inputs from outside(for example POST/PUT methods) are matching the shape of the relevant DTO. This happens via validation pipes that method payloads go through before they end up in your controller.

In nest convention there are 3 layers:

  • Controller - handles input/output to/from the service, authentication/authorization
  • Service - handles business logic
  • Data Access Layer (DAL) - handles data access/persistence

The control flow is Controller > Service > DAL > Your DB

What you tried to add to the DTO should be handled by the DAL. Your DAL can be either generic, having general CRUD methods for your various data, or it can be specific to your use case. It can be both.

You can use findByIdAndUpdate with a $push operator:

async addPayment(user: IUser, productId: string, payment: PaymentMethodDto) {
    const filter = { _id: productId };

    const productDoc = await this.productModel.findByIdAndUpdate(
      productId,
      {
        $push: { 'payments': payment } },
      },
      {
        new: true, // Will return the updated document, with the new payment added
        useFindAndModify: false, // You might not need this
      },
    );

    return productDoc.toObject();
}
EcksDy
  • 1,289
  • 9
  • 25
  • Thanks for your feedback. I expected from the docs the possibility to push to the array on the object based on the link attached. But then PaymentDetails-type is expected instead of PaymentDetailsDto. How would I convert between) https://mongoosejs.com/docs/subdocs.html#adding-subdocs-to-arrays – Gerrit Mar 17 '23 at 12:50
  • Getting the document into memory, pushing a payment into the array and using `.save()` should work like its shown in the docs. I'd look into what differences the DTO and schema have, to understand why it shows an error. If for some reason the two are different by design - I'd make a private mapping function that converts `PaymentDetailsDto` to `PaymentDetails`. If your `PaymentDetails` and `PaymentDetailsDto` are overlapping then there shouldnt be an issue. What differences do they have? – EcksDy Mar 17 '23 at 15:04