SoftwareBrothers / adminjs-nestjs

NestJS module to import admin the Nest way
MIT License
162 stars 37 forks source link

integrate with typeorm #11

Closed rubiin closed 3 years ago

rubiin commented 3 years ago

hi there, i woul like to see how to integrate with typeorm

muturgan commented 3 years ago

(dublicated from https://stackoverflow.com/questions/66306793/how-to-use-admin-bro-nestjs-with-admin-bro-typeorm-and-postgres-in-a-right-way)

The admin-bro-nestjs repository contains a comprehensive example with example with mongoose. But I need use it with typeorm and postgres. I tried to adapt this example for typeorm:

// main.ts
import AdminBro from 'admin-bro';
import { Database, Resource } from '@admin-bro/typeorm';
import { NestFactory } from '@nestjs/core';

import { AppModule } from './app.module';

AdminBro.registerAdapter({ Database, Resource });

const bootstrap = async () => {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

and

// app.module.ts
import { Module } from '@nestjs/common';
import { AdminModule } from '@admin-bro/nestjs';
import { TypeOrmModule, getRepositoryToken } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { UserEntity } from './user/user.entity';

@Module({
  imports: [
    TypeOrmModule.forRoot({
        type: 'postgres',
        host: 'localhost',
        port: 5432,
        username: 'postgres',
        password: 'password',
        database: 'database_test',
        entities: [UserEntity],
        synchronize: true,
        logging: false,
      }),
    AdminModule.createAdminAsync({
      imports: [
        TypeOrmModule.forFeature([UserEntity]),
      ],
      inject: [
        getRepositoryToken(UserEntity),
      ],
      useFactory: (userRepository: Repository<UserEntity>) => ({
        adminBroOptions: {
          rootPath: '/admin',
          resources: [
            { resource: userRepository },
          ],
        },
        auth: {
          authenticate: async (email, password) => Promise.resolve({ email: 'test' }),
          cookieName: 'test',
          cookiePassword: 'testPass',
        },
      }),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

But on application start I get the following error:

NoResourceAdapterError: There are no adapters supporting one of the resource you provided

Could you explain putting all these libraries together?

vensauro commented 3 years ago

Hello @muturgan i made like this:

import { AdminModuleFactory } from '@admin-bro/nestjs/types/interfaces/admin-module-factory.interface';
import { UserEntity } from 'src/users/entities/user.entity';
import { Connection } from 'typeorm';
import { CurrentAdmin } from 'admin-bro';

import * as bcrypt from 'bcrypt';

export const adminFactory: AdminModuleFactory = {
  useFactory: (connection: Connection) => ({
    adminBroOptions: {
      databases: [connection],
      // resources: [UserEntity] // this too works, you dont need to inject the repository, just import
      rootPath: '/admin',
    },
    auth: {
      authenticate: async (email, password) => {
        const userRep = connection.getRepository(UserEntity);
        const user = await userRep.findOne({ email });

        const isValid = await bcrypt.compare(password, user.password);
        if (!isValid) return;

        return (user as unknown) as CurrentAdmin;
      },
      cookieName: 'some-secret-password',
      cookiePassword: 'some-secret-password',
    },
  }),
  inject: [Connection],
};
muturgan commented 3 years ago
connection: Connection

Excellent! It works. Thank you very much!

muturgan commented 3 years ago

Hello @muturgan i made like this:

import { AdminModuleFactory } from '@admin-bro/nestjs/types/interfaces/admin-module-factory.interface';
import { UserEntity } from 'src/users/entities/user.entity';
import { Connection } from 'typeorm';
import { CurrentAdmin } from 'admin-bro';

import * as bcrypt from 'bcrypt';

export const adminFactory: AdminModuleFactory = {
  useFactory: (connection: Connection) => ({
    adminBroOptions: {
      databases: [connection],
      // resources: [UserEntity] // this too works, you dont need to inject the repository, just import
      rootPath: '/admin',
    },
    auth: {
      authenticate: async (email, password) => {
        const userRep = connection.getRepository(UserEntity);
        const user = await userRep.findOne({ email });

        const isValid = await bcrypt.compare(password, user.password);
        if (!isValid) return;

        return (user as unknown) as CurrentAdmin;
      },
      cookieName: 'some-secret-password',
      cookiePassword: 'some-secret-password',
    },
  }),
  inject: [Connection],
};

If you want to, you can answer my question on stack owerflow (link at my post here) and I will glad to approve it :)

Zydnar commented 3 years ago

Doesn't work for me. I get:

TypeError: this.model.getRepository is not a function
    at Resource.prepareProps (D:\dental\node_modules\@admin-bro\typeorm\lib\Resource.js:117:40)

When I checked what is this.model, it was one of entities, but not AdminEntity.

muturgan commented 3 years ago

Doesn't work for me.

Hello @Zydnar ! Need more details. Is it possible to get a link to you repo? I want to try to find a problem...

Zydnar commented 3 years ago

@muturgan I have it on company server, so no, at least not today. However I can provide short version of what I've tried. I'm using nest with fastify so this may be main problem. I have tried two scenarios with same result.

1st solution

This one is more close to solution above:

// main.ts
import { initSwagger } from '../devTools/swagger/initSwagger';
import { NestFactory } from '@nestjs/core';
import { fastifyMiddlewareFunction, setupMiddleware } from './middleware';
import * as AdminBroExpress from '@admin-bro/express';
import AdminBro from 'admin-bro';
import {Database, Resource} from '@admin-bro/typeorm';
import { validate } from 'class-validator';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import { readFile } from 'fs';
import { join } from 'path';
import * as pino from 'pino';
import { AdminEntity } from './database/entities';
Resource.validate = validate;
async function bootstrap() {

  const [key, cert]: Buffer[] = await Promise.all(
    [
      new Promise((resolve, reject) => readFile(join(__dirname, '../..', 'https', 'private.key'), (err, file) => {
        if (err) {
          return reject(err);
        }
        return resolve(file);
      })),
      new Promise((resolve, reject) => readFile(join(__dirname, '../..', 'https', 'certificate.cert'), (err, file) => {
        if (err) {
          return reject(err);
        }
        return resolve(file);
      })),
    ],
  );
  const https = process.env.NODE_ENV === 'production' ? {} :
    {
      key,
      cert,
    };
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter({
      http2: true,
      https,
      logger: pino({
        level: 'info',
        prettyPrint: {
          levelFirst: true,
        },
      }),
    }),
  );

  await initSwagger(app);
  await setupMiddleware(app);

  // Admin Bro
  AdminBro
    .registerAdapter({
      Database,
      Resource,
    });
  ///
  await app.listen(process.env.NODE_ENV === 'production' ? 80 : 5000, process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost');
}

bootstrap()
  .then(() => console.log('Server Started!'))
  .catch(console.error);

And module:

// app.module.ts
@Module({
  imports: [
//...
AdminModule.createAdminAsync({
      imports: [
        TypeOrmModule.forFeature([AdminEntity]),
      ],
      ...adminFactory, // your factory
})
/...

And this causes the error.

2nd solution

Now second solution with same result, where I'm not using app.module. It's inspired by gist working on fastify: https://gist.github.com/MexsonFernandes/454ce2f552c5167bb5c979fdb82da63b#file-admin-bro-nestjs-js

//main.ts
import { initSwagger } from '../devTools/swagger/initSwagger';
import { NestFactory } from '@nestjs/core';
import { fastifyMiddlewareFunction, setupMiddleware } from './middleware';
import * as AdminBroExpress from '@admin-bro/express';
import AdminBro, { Router as AdminRouter } from 'admin-bro';
import {Database, Resource} from '@admin-bro/typeorm';
import { validate } from 'class-validator';
import { createConnection } from 'typeorm';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import { readFile } from 'fs';
import { join } from 'path';
import * as pino from 'pino';
import { AdminEntity } from './database/entities';
Resource.validate = validate;
async function bootstrap() {

  const [key, cert]: Buffer[] = await Promise.all(
    [
      new Promise((resolve, reject) => readFile(join(__dirname, '../..', 'https', 'private.key'), (err, file) => {
        if (err) {
          return reject(err);
        }
        return resolve(file);
      })),
      new Promise((resolve, reject) => readFile(join(__dirname, '../..', 'https', 'certificate.cert'), (err, file) => {
        if (err) {
          return reject(err);
        }
        return resolve(file);
      })),
    ],
  );
  const https = process.env.NODE_ENV === 'production' ? {} :
    {
      key,
      cert,
    };
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter({
      http2: true,
      https,
      logger: pino({
        level: 'info',
        prettyPrint: {
          levelFirst: true,
        },
      }),
    }),
  );

  await initSwagger(app);
  await setupMiddleware(app);
  // Admin Bro
  AdminBro
    .registerAdapter({
      Database,
      Resource,
    });
  const connection = await createConnection({
    name: 'admin',
    type: "postgres",
    host: "localhost",
    port: 5432,
    username: "postgres",
    password: "Poiu7890",
    database: "dental",
    entities: [AdminEntity],
    synchronize: true,
    logging: true,
  });
  AdminEntity.useConnection(connection);
  const adminBro = new AdminBro({
    rootPath: '/admin',
    resources: [{ resource: AdminEntity }],
    branding: {
      companyName: 'FreeDe',
      softwareBrothers: false,
    },
  });
  const router = (AdminBroExpress as any).buildRouter(adminBro);
/* my current attempt I'm trying to write it on my own
  await adminBro.initialize();
  console.log("AdminBro: bundle ready");
  const { routes, assets } = AdminRouter;
  app.use(((()=> (req, res: HTTP2ServerResponse, next)=>{

  }) as (() => fastifyMiddlewareFunction))());
*/
  app.use(adminBro.options.rootPath, router);
  ///
  await app.listen(process.env.NODE_ENV === 'production' ? 80 : 5000, process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost');
}

bootstrap()
  .then(() => console.log('Server Started!'))
  .catch(console.error);
rubiin commented 3 years ago

@muturgan I have it on company server, so no, at least not today. However I can provide short version of what I've tried. I'm using nest with fastify so this may be main problem. I have tried two scenarios with same result.

1st solution

This one is more close to solution above:

// main.ts
import { initSwagger } from '../devTools/swagger/initSwagger';
import { NestFactory } from '@nestjs/core';
import { fastifyMiddlewareFunction, setupMiddleware } from './middleware';
import * as AdminBroExpress from '@admin-bro/express';
import AdminBro from 'admin-bro';
import {Database, Resource} from '@admin-bro/typeorm';
import { validate } from 'class-validator';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import { readFile } from 'fs';
import { join } from 'path';
import * as pino from 'pino';
import { AdminEntity } from './database/entities';
Resource.validate = validate;
async function bootstrap() {

  const [key, cert]: Buffer[] = await Promise.all(
    [
      new Promise((resolve, reject) => readFile(join(__dirname, '../..', 'https', 'private.key'), (err, file) => {
        if (err) {
          return reject(err);
        }
        return resolve(file);
      })),
      new Promise((resolve, reject) => readFile(join(__dirname, '../..', 'https', 'certificate.cert'), (err, file) => {
        if (err) {
          return reject(err);
        }
        return resolve(file);
      })),
    ],
  );
  const https = process.env.NODE_ENV === 'production' ? {} :
    {
      key,
      cert,
    };
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter({
      http2: true,
      https,
      logger: pino({
        level: 'info',
        prettyPrint: {
          levelFirst: true,
        },
      }),
    }),
  );

  await initSwagger(app);
  await setupMiddleware(app);

  // Admin Bro
  AdminBro
    .registerAdapter({
      Database,
      Resource,
    });
  ///
  await app.listen(process.env.NODE_ENV === 'production' ? 80 : 5000, process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost');
}

bootstrap()
  .then(() => console.log('Server Started!'))
  .catch(console.error);

And module:

// app.module.ts
@Module({
  imports: [
//...
AdminModule.createAdminAsync({
      imports: [
        TypeOrmModule.forFeature([AdminEntity]),
      ],
      ...adminFactory, // your factory
})
/...

And this causes the error.

2nd solution

Now second solution with same result, where I'm not using app.module. It's inspired by gist working on fastify: https://gist.github.com/MexsonFernandes/454ce2f552c5167bb5c979fdb82da63b#file-admin-bro-nestjs-js

//main.ts
import { initSwagger } from '../devTools/swagger/initSwagger';
import { NestFactory } from '@nestjs/core';
import { fastifyMiddlewareFunction, setupMiddleware } from './middleware';
import * as AdminBroExpress from '@admin-bro/express';
import AdminBro, { Router as AdminRouter } from 'admin-bro';
import {Database, Resource} from '@admin-bro/typeorm';
import { validate } from 'class-validator';
import { createConnection } from 'typeorm';
import {
  FastifyAdapter,
  NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { AppModule } from './app.module';
import { readFile } from 'fs';
import { join } from 'path';
import * as pino from 'pino';
import { AdminEntity } from './database/entities';
Resource.validate = validate;
async function bootstrap() {

  const [key, cert]: Buffer[] = await Promise.all(
    [
      new Promise((resolve, reject) => readFile(join(__dirname, '../..', 'https', 'private.key'), (err, file) => {
        if (err) {
          return reject(err);
        }
        return resolve(file);
      })),
      new Promise((resolve, reject) => readFile(join(__dirname, '../..', 'https', 'certificate.cert'), (err, file) => {
        if (err) {
          return reject(err);
        }
        return resolve(file);
      })),
    ],
  );
  const https = process.env.NODE_ENV === 'production' ? {} :
    {
      key,
      cert,
    };
  const app = await NestFactory.create<NestFastifyApplication>(
    AppModule,
    new FastifyAdapter({
      http2: true,
      https,
      logger: pino({
        level: 'info',
        prettyPrint: {
          levelFirst: true,
        },
      }),
    }),
  );

  await initSwagger(app);
  await setupMiddleware(app);
  // Admin Bro
  AdminBro
    .registerAdapter({
      Database,
      Resource,
    });
  const connection = await createConnection({
    name: 'admin',
    type: "postgres",
    host: "localhost",
    port: 5432,
    username: "postgres",
    password: "Poiu7890",
    database: "dental",
    entities: [AdminEntity],
    synchronize: true,
    logging: true,
  });
  AdminEntity.useConnection(connection);
  const adminBro = new AdminBro({
    rootPath: '/admin',
    resources: [{ resource: AdminEntity }],
    branding: {
      companyName: 'FreeDe',
      softwareBrothers: false,
    },
  });
  const router = (AdminBroExpress as any).buildRouter(adminBro);
/* my current attempt I'm trying to write it on my own
  await adminBro.initialize();
  console.log("AdminBro: bundle ready");
  const { routes, assets } = AdminRouter;
  app.use(((()=> (req, res: HTTP2ServerResponse, next)=>{

  }) as (() => fastifyMiddlewareFunction))());
*/
  app.use(adminBro.options.rootPath, router);
  ///
  await app.listen(process.env.NODE_ENV === 'production' ? 80 : 5000, process.env.NODE_ENV === 'production' ? '0.0.0.0' : 'localhost');
}

bootstrap()
  .then(() => console.log('Server Started!'))
  .catch(console.error);

how are you using this on fastify

Zydnar commented 3 years ago

@rubiin https://github.com/SoftwareBrothers/admin-bro/blob/master/src/backend/utils/router/router.ts was very helpful - there is list of all static files and actions you have to register in fastify routing. Edit: note also fastify usually supports express middleware plugins.

rubiin commented 3 years ago

Do you have a minimal repo you can share

Zydnar commented 3 years ago

I will by the end of this week. I have to remove all unrelated plugins I use in my project.

rubiin commented 3 years ago

cool, do let me know. That would be a big help

Zydnar commented 3 years ago

@rubiin My sister is getting married this weekend, I hoped I will have more time so I'm keeping at least partially my promise. Bellow is code with all requests you need to make Admin Bro working on fastify. What I haven't finished are API requests, however views work perfectly fine. So what is left - you have to create in all API requests request and response with express.js properties. Admin session is easy part - you know how to make protected route.

AdminController code: https://pastebin.com/3QSyxsHZ

rubiin commented 3 years ago

@rubiin My sister is getting married this weekend, I hoped I will have more time so I'm keeping at least partially my promise. Bellow is code with all requests you need to make Admin Bro working on fastify. What I haven't finished are API requests, however views work perfectly fine. So what is left - you have to create in all API requests request and response with express.js properties. Admin session is easy part - you know how to make protected route.

AdminController code: https://pastebin.com/3QSyxsHZ

Thanks its a great help