SoftwareBrothers / adminjs

AdminJS is an admin panel for apps written in node.js
https://adminjs.co
MIT License
8.19k stars 662 forks source link

[Bug]: Nested object not saving (mongoose) #1577

Open rlabrovi opened 11 months ago

rlabrovi commented 11 months ago

Contact Details

No response

What happened?

I'm trying to save nested object but data I provide is not saving to database. I'm using adminjs v6.7.5

export const questionResource = {
  resource: model("Question", QuestionSchema),
  options: {
    navigation: {
      name: "bla",
    },
    properties: {
      options: {
        type: "mixed",
        isArray: true,
      },
      "options.title": {
        type: "mixed",
        isArray: false,
      },
      "options.title.en": {
        type: "string",
        isVisible: { list: true, filter: true, show: true, edit: true },
        label: "Option Title (English)",
      },
      "options.title.it": {
        type: "string",
        isVisible: { list: false, filter: true, show: true, edit: true },
        label: "Option Title (Italian)",
      },
      "options.title.fr": {
        type: "string",
        isVisible: { list: false, filter: true, show: true, edit: true },
        label: "Option Title (Franch)",
      },
      "options.title.de": {
        type: "string",
        isVisible: { list: false, filter: true, show: true, edit: true },
        label: "Option Title (German)",
      },
      "options.correct": {
        type: "boolean",
        isVisible: { list: true, filter: true, show: true, edit: true },
        label: "Is Correct",
      },
    },
  },
};

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { ObjectId, Types } from 'mongoose';

class LocalizedString {
  _id: ObjectId;

  @Prop({ required: true })
  en: string;

  @Prop({ required: true })
  it: string;

  @Prop({ required: true })
  fr: string;

  @Prop({ required: true })
  de: string;
}

const LocalizedStringSchema = SchemaFactory.createForClass(LocalizedString);

export class Options {
  _id: ObjectId;

  @Prop({ type: LocalizedString, required: true })
  title: LocalizedString;

  correct: boolean;
}

const OptionsSchema = SchemaFactory.createForClass(Options);

@Schema({ timestamps: true })
export class Question {
  _id: ObjectId;

  @Prop({ type: LocalizedString, required: true })
  title: LocalizedString;

  @Prop({ type: LocalizedString })
  funFact: LocalizedString;

  @Prop({ type: [{ type: OptionsSchema }], required: true })
  options: Types.DocumentArray<Options>;

  @Prop({ type: String, required: false })
  imagePath: string;

  @Prop()
  imageType: string;

  @Prop()
  bucket: string;
}

const QuestionSchema = SchemaFactory.createForClass(Question);

export { QuestionSchema, LocalizedStringSchema, OptionsSchema };

I have noticed that in network tab when creating new item it's sending options as empty array, but if I console.log actions new before paylod the data is there:

Screenshot 2023-11-06 at 17 37 38 Screenshot 2023-11-06 at 17 37 54

Bug prevalence

When creating new resource

AdminJS dependencies version

  "dependencies": {
    "@adminjs/express": "^5.0.1",
    "@adminjs/mongoose": "^3.0.1",
    "@adminjs/nestjs": "^5.1.0",
    "@adminjs/upload": "^3.0.1",
    "@nestjs/common": "9.2.1",
    "@nestjs/core": "9.2.1",
    "@nestjs/mongoose": "^9.2.1",
    "@nestjs/platform-express": "9.2.1",
    "@nestjs/swagger": "^6.3.0",
    "@nestjs/terminus": "^10.1.1",
    "adminjs": "^6.7.5",
    "class-transformer": "0.5.1",
    "class-validator": "^0.14.0",
    "dotenv": "^16.3.1",
    "express-formidable": "^1.2.0",
    "express-session": "^1.17.3",
    "mongoose": "^6.8.3",
    "reflect-metadata": "0.1.13",
    "rxjs": "7.5.5"
  },
  "devDependencies": {
    "@nestjs/cli": "^9.5.0",
    "@nestjs/config": "^3.1.1",
    "@nestjs/schematics": "9.0.4",
    "@trivago/prettier-plugin-sort-imports": "^4.2.1",
    "@types/express": "4.17.13",
    "@types/node": "18.0.0",
    "@types/supertest": "2.0.12",
    "@typescript-eslint/eslint-plugin": "^5.0.0",
    "@typescript-eslint/parser": "^5.0.0",
    "eslint": "8.18.0",
    "eslint-config-prettier": "^8.3.0",
    "eslint-plugin-prettier": "^4.0.0",
    "prettier": "^2.8.8",
    "source-map-support": "^0.5.20",
    "supertest": "6.2.3",
    "ts-loader": "9.3.0",
    "ts-node": "10.8.1",
    "tsconfig-paths": "3.14.1",
    "types

What browsers do you see the problem on?

Firefox

Relevant log output

No response

Relevant code that's giving you issues

No response

dziraf commented 11 months ago

I have noticed that in network tab when creating new item it's sending options as empty array, but if I console.log actions new before paylod the data is there:

Do you mean options is empty in Network tab but before hook of new action has got options in the request.payload? This should be impossible since the payload contains whatever had been sent from the frontend

rlabrovi commented 11 months ago

@dziraf Exactly that. I also thought that is impossible but here is video.

https://github.com/SoftwareBrothers/adminjs/assets/3057962/de4b4794-5f83-4912-85c2-f124fe534e18

dziraf commented 11 months ago

In the video you're showing a Response (not Request) which only means that options are not returned from the server. So my guess is that they're actually sent from the frontend but Mongo is unable to process the flattened payload. You could try to modify your before hook to unflatten it:

import { flat } from 'adminjs';

const unflattenPayload = (request) => {
  if (request.method === 'get') return request;

  const { payload = {} } = request;
  const newPayload = flat.unflatten(payload);

  request.payload = newPayload;

  return request;
}
rlabrovi commented 11 months ago

@dziraf Thanks for reply. I tried with unflattening payload, now it's creating options property but without any values in it:

https://github.com/SoftwareBrothers/adminjs/assets/3057962/4e47c1cc-23b1-4afe-a4dc-c7f141c2aae5

Any ides what else can I do?

dziraf commented 11 months ago

This function could be messing up the payload: https://github.com/SoftwareBrothers/adminjs-mongoose/blob/master/src/resource.ts#L181

I'm not sure why it's even needed for Mongoose though. Could you try to modify this function in your:

node_modules/@adminjs/mongoose/lib/resource.js

by adding console.logs to see where your payload disappears? It's still odd because:

const parsedParams = { ...params }

makes a copy of your payload so in worst case scenario it should persist it with parsing.