SoftwareBrothers / adminjs

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

Issue with adapter for adminjs/mongoose #1697

Open bogdan-kocic opened 3 months ago

bogdan-kocic commented 3 months ago

Contact Details

No response

What happened?

After starting application I receive this error: There are no adapters supporting one of the resource you provided. I'm using @adminjs/mongoose adapter.

Bug prevalence

always

AdminJS dependencies version

"dependencies": { "@adminjs/express": "^6.1.0", "@adminjs/mongoose": "^4.1.0", "@adminjs/nestjs": "^6.1.0", "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.2.2", "@nestjs/core": "^10.0.0", "@nestjs/jwt": "^10.2.0", "@nestjs/mongoose": "^10.0.6", "@nestjs/passport": "^10.0.3", "@nestjs/platform-express": "^10.0.0", "@nestjs/throttler": "^5.2.0", "@nestjs/typeorm": "^10.0.2", "@types/nodemailer": "^6.4.15", "@vercel/node": "^3.1.7", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "compression": "^1.7.4", "express-formidable": "^1.2.0", "express-session": "^1.18.0", "firebase-admin": "^12.2.0", "handlebars": "^4.7.8", "helmet": "^7.1.0", "mongodb": "^5.9.2", "mongoose": "^8.4.3", "nanoid": "^3.3.7", "nestjs-seeder": "^0.3.2", "nodemailer": "^6.9.14", "passport": "^0.7.0", "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", "slugify": "^1.6.6", "typeorm": "^0.3.20", "zod": "^3.23.8" }, "devDependencies": { "@nestjs/cli": "^10.0.0", "@nestjs/schematics": "^10.0.0", "@nestjs/testing": "^10.3.10", "@types/bcrypt": "^5.0.2", "@types/express": "^4.17.17", "@types/jest": "^29.5.2", "@types/node": "^20.3.1", "@types/passport-jwt": "^4.0.1", "@types/passport-local": "^1.0.38", "@types/supertest": "^6.0.0", "@typescript-eslint/eslint-plugin": "^6.0.0", "@typescript-eslint/parser": "^6.0.0", "eslint": "^8.42.0", "eslint-config-prettier": "^9.0.0", "eslint-plugin-prettier": "^5.0.0", "jest": "^29.5.0", "prettier": "^3.0.0", "source-map-support": "^0.5.21", "supertest": "^6.3.3", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", "typescript": "^5.1.3" }

What browsers do you see the problem on?

Chrome

Relevant log output

NoResourceAdapterError: There are no adapters supporting one of the resource you provided
    at file:///*/*/*/*/*/node_modules/adminjs/lib/backend/utils/resources-factory/resources-factory.js:73:15

Relevant code that's giving you issues

Mongo schema:

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import * as mongoose from 'mongoose';
import { Entity } from './entity.schema';
import { Document } from 'mongoose';
import { softDeletePlugin } from './soft_delete/soft-delete-plugin';

@Schema()
export class Appointment extends Document {
  @Prop({ type: mongoose.Schema.Types.ObjectId, ref: Entity.name })
  entity: Entity;
  @Prop({ type: Object, required: true })
  conversation_log: object;
}

export type AppointmentDocument = Appointment & Document;
const AppointmentSchema = SchemaFactory.createForClass(Appointment);

AppointmentSchema.plugin(softDeletePlugin);

export { AppointmentSchema };

App module:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule } from '@nestjs/config';
import * as process from 'process';
import { AppointmentsModule } from './appointments/appointments.module';
import { UsersModule } from './users/user.module';
import { AuthModule } from './auth/auth.module';
import { FilterModule } from './filters/filter.module';
import { EntitiesModule } from './entities/entities.module';
import { CitiesModule } from './cities/cities.module';
import { MailService } from './services/mail.service';
import { Appointment } from './schemas/appointment.schema';

export const dynamicImport = async (packageName: string) =>
  new Function(`return import('${packageName}')`)();

const DEFAULT_ADMIN = {
  email: '',
  password: '',
};

const authenticate = async (email: string, password: string) => {
  if (email === DEFAULT_ADMIN.email && password === DEFAULT_ADMIN.password) {
    return Promise.resolve(DEFAULT_ADMIN);
  }
  return null;
};

@Module({
  controllers: [],
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    MongooseModule.forRootAsync({
      useFactory: async () => ({
        uri: process.env.DB_CONNECTION_URI,
      }),
    }),
    dynamicImport('@adminjs/nestjs').then(({ AdminModule }) =>
      AdminModule.createAdminAsync({
        useFactory: () => ({
          adminJsOptions: {
            rootPath: '/admin',
            resources: [Appointment],
          },
          auth: {
            authenticate,
            cookieName: '',
            cookiePassword: '',
          },
          sessionOptions: {
            resave: true,
            saveUninitialized: true,
            secret: 'secret',
          },
        }),
      }),
    ),
    AppointmentsModule,
    UsersModule,
    AuthModule,
    FilterModule,
    EntitiesModule,
    CitiesModule,
  ],
  providers: [MailService],
})
export class AppModule {}
dziraf commented 3 months ago

I haven't used @nestjs/mongoose personally but if it returns different metadata than mongoose, it won't work and would require another adapter just for it.

However, you might be missing AdminJS.registerAdapter({ Database, Resource }) because I cannot find it in the code you shared.

bogdan-kocic commented 3 months ago

Hey @dziraf, thank you for responding. I'm relatively new to nodejs. Can you please explain me what do you mean by mongoose. With what should I compare @nestjs/mongoose to?

Sorry, I didn't provide my main.ts, here is where I register the adapter.

import { NestFactory, Reflector } from '@nestjs/core';
import { AppModule, dynamicImport } from './app.module';
import helmet from 'helmet';
import { JwtAuthGuard } from './auth/guards/jwt.guard';
import * as admin from 'firebase-admin';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  const reflector = app.get(Reflector);
  app.useGlobalGuards(new JwtAuthGuard(reflector));

  app.enableCors({ origin: '*' });
  app.use(helmet());

  const adminJSModule = await dynamicImport('adminjs');
  const AdminJS = adminJSModule.default;

  const AdminJSMongoose = await dynamicImport('@adminjs/mongoose');

  AdminJS.registerAdapter({
    Resource: AdminJSMongoose.Resource,
    Database: AdminJSMongoose.Database,
  });

  await app.listen(3000);
}
bootstrap();
dziraf commented 3 months ago
  AdminJS.registerAdapter({
    Resource: AdminJSMongoose.Resource,
    Database: AdminJSMongoose.Database,
  });

Try placing that before bootstrap function or before const app = await .... Adapters have to be registered before AdminJS instance is created which is done at that step.

Can you please explain me what do you mean by mongoose. With what should I compare @nestjs/mongoose to?

Every adapter (@adminjs/mongoose, @adminjs/typeorm, etc) uses your ODM's/ORM's schemas/entitites to build AdminJS resources. mongoose is a MongoDB library which @adminjs/mongoose is integrated with. @nestjs/mongoose comes with it's own decorators and schema definition, so if resulting metadata is different to what mongoose returns, @adminjs/mongoose won't work with @nestjs/mongoose.

Alternatively, try providing AppointmentSchema instead of Appointment to resources

bogdan-kocic commented 3 months ago

Greate, with AppointmentSchema it managed to register the adapter. I have different error now: TypeError: Cannot read properties of undefined (reading 'paths') at Resource.properties (file:///Users/*/*/*/*/node_modules/@adminjs/mongoose/lib/resource.js:47:57) at decorateDatabaseProperties (file:///Users/*/*/*/*/node_modules/adminjs/lib/backend/decorators/resource/utils/decorate-properties.js:9:19)

I have noticed another issue on this repo with exactly the same error. Did you manage to reproduce the error after pulling the repo. Reference to the issue I'm talking about: #1257

I have tracked issue, it is located in node_modules/@adminjs/mongoose/lib/resource.js return Object.entries(this.MongooseModel.schema.paths).map(([, path], position) => (new Property(path, position)));

If I change to 'this.MongooseModel.paths' it works, but again other things don't work. For some reason this.MongooseModel structur is not as adminjs/resource.js expects. For example, missing this.MongooseModel.db used in resource.js as well.

bogdan-kocic commented 3 months ago

Hello @dziraf,

I can't make it work, what ever I do. I cloned example project and it doesn't work aswell.

Do you have any more suggestions?

bogdan-kocic commented 3 months ago

Hey @dziraf,

I managed to make it work by exporting another class from my schema.ts

export class City extends Document export type CityDocument = City & Document; const CityModel = mongoose.model('City', CitySchema); //new export { CitySchema, CityModel };

Now I have db connection problems, I assume that CityModel class doesn't have to do anything with db connection?

error: Operation cities.find() buffering timed out after 10000ms