NestJs: Query multiple entities from multiple database - service

I have mysql_server_1.database1.users
And mysql_server_2.database3.users_revenue
How can I query rows from users
How can I query rows from users_revenue
First, I've already setup the connections:
const mysql1__database1 = TypeOrmModule.forRootAsync({
imports: [ConfigModule],
// #ts-ignore
useFactory: (configService: ConfigService) => ({
type: configService.get("DASHBOARD_DB_TYPE"),
host: configService.get("DASHBOARD_DB_HOST"),
port: configService.get("DASHBOARD_DB_PORT"),
username: configService.get("DASHBOARD_DB_USER"),
password: configService.get("DASHBOARD_DB_PASSWORD"),
database: configService.get("DASHBOARD_DB_NAME"),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
// entities: [User],
autoLoadEntities: true,
synchronize: true,
}),
inject: [ConfigService],
});
const mysql2__database3 = TypeOrmModule.forRootAsync({
imports: [ConfigModule],
// #ts-ignore
useFactory: (configService: ConfigService) => ({
name: 'mysql2__database3',
type: configService.get("DASHBOARD2_DB_TYPE"),
host: configService.get("DASHBOARD2_DB_HOST"),
port: configService.get("DASHBOARD2_DB_PORT"),
username: configService.get("DASHBOARD2_DB_USER"),
password: configService.get("DASHBOARD2_DB_PASSWORD"),
database: configService.get("DASHBOARD2_DB_NAME"),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
// entities: [User],
autoLoadEntities: true,
synchronize: true,
}),
inject: [ConfigService],
});
#Module({
imports: [
mysql1__database1,
mysql2__database3,
StatsModule,
],
controllers: [AppController],
providers: [AppService, StatsService],
})
export class AppModule {}
user.service.ts
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
#Injectable()
export class UserService {
constructor(#InjectRepository(User) private usersRepository: Repository<User>) {}
async findAll(): Promise<User[]> {
return await this.usersRepository.find();
}
}
Then this code return an empty array instead of so many rows exists in my database;
const items = await this.userService.findAll();
--- update ---
I've take a look at the typeorm source code:
https://github.com/nestjs/typeorm/blob/8af34889fa7bf14d7dc5541beef1d5c2b50c2609/lib/common/typeorm.decorators.ts#L13
Then https://docs.nestjs.com/techniques/database#multiple-databases
At this point, you have User and Album entities registered with their own connection. With this setup, you have to tell the TypeOrmModule.forFeature() method and the #InjectRepository() decorator which connection should be used. If you do not pass any connection name, the default connection is used.
So I think it should work?
#InjectRepository(User, 'mysql2_database3')
#Module({
imports: [
TypeOrmModule.forFeature([User], "mysql2_database3"),
],
providers: [UserService],
controllers: [StatsController],
})
export class StatsModule {}
Still got the error:
Please make sure that the argument mysql2_database3Connection at index [0] is available in the TypeOrmModule context.

Thank to #jmc29 on discord, his guide helped
The solution is:
const mysql2__database3 = TypeOrmModule.forRootAsync({
imports: [ConfigModule],
// #ts-ignore
useFactory: (configService: ConfigService) => ({
name: 'mysql2__database3',
type: configService.get("DASHBOARD2_DB_TYPE"),
host: configService.get("DASHBOARD2_DB_HOST"),
port: configService.get("DASHBOARD2_DB_PORT"),
username: configService.get("DASHBOARD2_DB_USER"),
password: configService.get("DASHBOARD2_DB_PASSWORD"),
database: configService.get("DASHBOARD2_DB_NAME"),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
autoLoadEntities: true,
synchronize: true,
}),
inject: [ConfigService],
});
add one more line:
const mysql2__database3 = TypeOrmModule.forRootAsync({
name: 'mysql2__database3', // -----> Add this line, it's is required
imports: [ConfigModule],
// #ts-ignore
useFactory: (configService: ConfigService) => ({
name: 'mysql2__database3',
type: configService.get("DASHBOARD2_DB_TYPE"),
host: configService.get("DASHBOARD2_DB_HOST"),
port: configService.get("DASHBOARD2_DB_PORT"),
username: configService.get("DASHBOARD2_DB_USER"),
password: configService.get("DASHBOARD2_DB_PASSWORD"),
database: configService.get("DASHBOARD2_DB_NAME"),
entities: [__dirname + '/**/*.entity{.ts,.js}'],
autoLoadEntities: true,
synchronize: true,
}),
inject: [ConfigService],
});

for those who face this problem , this is my solution
AppModule
#Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [
database,
databaseAllo
]
}),
TypeOrmModule.forRootAsync({
useFactory: (configs: ConfigService) => configs.get("db_config"),
inject: [ConfigService],
}),
TypeOrmModule.forRootAsync({
name:"db_allo", <= create connection to my second db
useFactory: (configs: ConfigService) => configs.get("db_config_allo"),
inject: [ConfigService],
}),
AuthModule,
JwtAuthModule
],
controllers: []
})
export class AppModule {}
my project module ( contain table from second db )
#Module({
imports: [
TypeOrmModule.forFeature([AlloMpcTable], "db_allo" <= call connection again),
],
providers: [
AlloRepository
],
exports: [AlloRepository],
controllers: [],
})
export class AlloModule {}
my project repository
#Injectable()
export class AlloRepository extends BaseRepository<AlloMpcTable> {
constructor(
#InjectRepository(AlloMpcTable, "db_allo") <= you need to call connection again
private readonly allo: Repository<AlloMpcTable>,
) {
super(allo)
}
public async Find(id: number): Promise<AlloMpcTable> {
return await this.allo.findOne(id)
}
}
so in your case, you need to call "mysql2_database3" again in your providers: [UserService]

Related

how can i access my env file from a nestjs module using passport js?

I am working on a small api using nestjs and passeport js
I have been trying to access the content of my env file, from within my auth module...but it's surpinsigly challenging...
import { userService } from 'src/user/services/user.service';
import { AuthService } from './auth.service';
import { PassportModule } from '#nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { JwtModule } from '#nestjs/jwt';
import { JwtStrategy } from './jwt.strategy';
import { ConfigService } from '#nestjs/config';
#Module({
providers: [AuthService, userService, LocalStrategy, JwtStrategy],
imports: [
TypeOrmModule.forFeature([UserEntity, SpotEntity, SpotUserEntity]),
UserModule,
PassportModule,
JwtModule.register({
secret: ConfigService.get('JWT_SECRET'),
signOptions: { expiresIn: '600s' },
}),
],
exports: [AuthService],
})
export class AuthModule {}
Off course this cannot work because i am trying to utilize ConfigService.get() instead of this.configService.get()
I know i would need to instanciate configService in a constructor first, but modules do not have constructors, this is where i'm stuck at.
You can try registerAsync i.e
import { ConfigModule, ConfigService } from '#nestjs/config';
......
imports: [
PassportModule.register({
defaultStrategy: 'jwt',
}),
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: (config: ConfigService) => {
return {
signOptions: {
expiresIn: config.get<string>('JWT_EXPIRY'),
},
secret: config.get<string>('JWT_SECRET'),
};
},
inject: [ConfigService],
})
]
....

Change credentials in typeorm denpendig of user logged

I'm working with nestjs and typeorm to connect to postgres database I want to change the credentials of the connection in runtime depending of the role of the user logged. There is any way to achieve that?
#Module({
imports: [
TypeOrmModule.forRootAsync({
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
type: 'postgres',
host: configService.get<string>('database.host'),
port: configService.get<number>('database.port'),
database: configService.get<string>('database.name'),
autoLoadEntities: true,
//I want these two properties to be dynamic
username: configService.get<string>('database.user'),
password: configService.get<string>('database.password'),
})
}),
]
})
export class AppModule {}

JWT not expiring in nestjs application even after setting expiresIn value

This is what my auth.module.ts looks like:
import { Module } from "#nestjs/common";
import { ConfigModule, ConfigService } from "#nestjs/config";
import { JwtModule } from "#nestjs/jwt";
import { PassportModule } from "#nestjs/passport";
import { TypeOrmModule } from "#nestjs/typeorm";
import appConfig from "src/config/app.config";
import devConfig from "src/config/dev.config";
import stagConfig from "src/config/stag.config";
import { User } from "src/entities/entity/user.entity";
import { AuthService } from "./auth.service";
import { JwtStrategy } from "./passport-strategies/jwt-strategy";
import { LocalStrategy } from "./passport-strategies/local-strategy";
#Module({
imports: [
PassportModule,
ConfigModule.forRoot({
load: [appConfig, devConfig, stagConfig],
ignoreEnvFile: true,
isGlobal: true,
}),
TypeOrmModule.forFeature([
User
]),
JwtModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
// secret: configService.get<string>('jwt.secret'),
secret: process.env.TOKEN_KEY,
signOptions: { expiresIn: 30 }
}),
inject: [ConfigService]
}),
],
providers: [
AuthService,
LocalStrategy,
JwtStrategy
],
exports: [AuthService],
})
export class AuthModule {}
As you can see I have set signOptions: { expiresIn: 30 } but when I analyze the token it has no expiration parameter and does not expire.
I am using https://jwt.io/#encoded-jwt to analyze the token:
You have to pass the expiresIn value as string and mention s for seconds like this
JwtModule.register({
secret: jwtConstants.secret,
signOptions: { expiresIn: '60s' },
}),
Do let me know if this works!
The same problem. search ignoreExpiration flag. Here's my problem
e:
#Injectable()
export class JWTStrategy extends PassportStrategy(Strategy) {
constructor(
readonly configService: ConfigService,
private userService: UserService,
) {
super({
jwtFromRequest: ExtractJwt.fromHeader(
configService.get('API_ACCESS_TOKEN_HEADER'),
),
ignoreExpiration: true, // *** FIXME: here
secretOrKey: configService.get('API_ACCESS_TOKEN_SECRET'),
});
}
...
Here is type:
export interface StrategyOptions {
secretOrKey?: string | Buffer | undefined;
secretOrKeyProvider?: SecretOrKeyProvider | undefined;
jwtFromRequest: JwtFromRequestFunction;
issuer?: string | undefined;
audience?: string | undefined;
algorithms?: string[] | undefined;
ignoreExpiration?: boolean | undefined;
passReqToCallback?: boolean | undefined;
jsonWebTokenOptions?: VerifyOptions | undefined;
}
The ignoreExpiration used, https://github.com/auth0/node-jsonwebtoken/blob/master/verify.js#L147
Hope it will help you

How to write Nestjs unit tests for #Injectable() mongodb service

Can someone please guide me. I'm learning Nestjs and doing a small project, and I'm not able to get the unit test working for a controller and service which has dependency on the database.module. How do I go about mocking the database.module in the product.service.ts? Any help will be highly appreciated.
database.module.ts
try {
const client = await MongoClient.connect(process.env.MONGODB, { useNewUrlParser: true, useUnifiedTopology: true });
return client.db('pokemonq')
} catch (e) {
console.log(e);
throw e;
}
};
#Module({
imports: [],
providers: [
{
provide: 'DATABASE_CONNECTION',
useFactory: setupDbConnection
},
],
exports: ['DATABASE_CONNECTION'],
})
export class DatabaseModule {}
product.service.ts
#Injectable()
export class ProductService {
protected readonly appConfigObj: EnvConfig;
constructor(
private readonly appConfigService: AppConfigService,
#Inject('DATABASE_CONNECTION') => **How to mock this injection?**
private db: Db,
) {
this.appConfigObj = this.appConfigService.appConfigObject;
}
async searchBy (){}
async findBy (){}
}
product.service.spec.ts
describe('ProductService', () => {
let service: ProductService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [],
providers: [
ConfigService,
DatabaseModule,
AppConfigService,
ProductService,
{
provide: DATABASE_CONNECTION,
useFactory: () => {}
}
],
}).compile();
service = module.get< ProductService >(ProductService);
});
afterAll(() => jest.restoreAllMocks());
}
product.controller.spec.ts
describe('ProductController', () => {
let app: TestingModule;
let ProductController: ProductController;
let ProductService: ProductService;
const response = {
send: (body?: any) => {},
status: (code: number) => response,
json: (body?: any) => response
}
beforeEach(async () => {
app = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
load: [appConfig],
isGlobal: true,
expandVariables: true
}),
ProductModule,
],
providers: [
AppConfigService,
ProductService,
],
controllers: [ProductController]
}).compile();
productController = app.get< ProductController >(ProductController);
productService = app.get< ProductService >(ProductService);
});
afterAll(() => jest.restoreAllMocks());
}
Anything that is not being tested directly in a unit test should theoretically be mocked. In this case, you have two dependencies, AppConfigService adn DATABASE_CONNECTION. You're unit test should provide mock objects that look like the injected dependencies, but have defined and easy to modify behavior. In this case, something like this may be what you're looking for
beforeEach(async () => {
const modRef = await Test.createTestingModule({
providers: [
ProductService,
{
provide: AppConfigService,
useValue: {
appConfigObject: mockConfigObject
}
},
{
provide: 'DATABASE_CONNECTION',
useValue: {
<databaseMethod>: jest.fn()
}
]
}).compile();
// assuming these are defined in the top level describe
prodService = modRef.get(ProductionService);
conn = modRef.get('DATABASE_CONNECTION');
config = modRef.get(AppConfigService);
});
In your controller test, you shouldn't worry about mocking anything other than the ProdctService.
If you need more help there's a large repository of examples here
Edit 9/04/2020
Mocking chained methods is a major pain point when working with things like Mongo. There's a few ways you can go about it, but the easiest is probably to create a mock object like
const mockModel = {
find: jest.fn().mockReturnThis(),
update: jest.fn().mockReturnThis(),
collation: jest.fn().mockReturnThis(),
...etc
}
And on the last call in the chain, make it return the expected outcome so your service can keep running the rest of the code. This would mean if you have a call like
const value = model.find().collation().skip().limit().exec()
you would need to set the exec() method to return the value you expect it to, probably using something like
jest.spyOn(mockModel, 'exec').mockResolvedValueOnce(queryReturn);
I am also exploring using native Mongodb with NestJS. Below is my working test for cron job service updating value in db.
src/cron/cron.service.ts
import { Inject, Injectable } from '#nestjs/common';
import { Cron, CronExpression } from '#nestjs/schedule';
import { Db } from 'mongodb';
import { Order } from 'src/interfaces/order.interface';
#Injectable()
export class CronService {
constructor(
#Inject('DATABASE_CONNECTION')
private db: Db,
) {}
#Cron(CronExpression.EVERY_30_SECONDS)
async confirmOrderEveryMinute() {
console.log('Every 30 seconds');
await this.db
.collection<Order>('orders')
.updateMany(
{
status: 'confirmed',
updatedAt: {
$lte: new Date(new Date().getTime() - 30 * 1000),
},
},
{ $set: { status: 'delivered' } },
)
.then((res) => console.log('Orders delivered...', res.modifiedCount));
}
}
src/cron/cron.service.spec.ts
import { Test, TestingModule } from '#nestjs/testing';
import { Db } from 'mongodb';
import { CronService } from './cron.service';
describe('CronService', () => {
let service: CronService;
let connection: Db;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
CronService,
{
provide: 'DATABASE_CONNECTION',
useFactory: () => ({
db: Db,
collection: jest.fn().mockReturnThis(),
updateMany: jest.fn().mockResolvedValue({ modifiedCount: 1 }),
}),
},
],
}).compile();
service = module.get<CronService>(CronService);
connection = module.get('DATABASE_CONNECTION');
});
it('should be defined', async () => {
expect(service).toBeDefined();
});
it('should confirmOrderEveryMinute', async () => {
await service.confirmOrderEveryMinute();
expect(connection.collection('orders').updateMany).toHaveBeenCalled();
});
});

angular2 route not working

i use angular2 rc3 and angular2/router 3.0.0-alpha.8 to do my router:
here is my code:
bootstrap(AppComponent, [HTTP_PROVIDERS, APP_ROUTER_PROVIDERS, AUTH_PROVIDERS,
{
provide: AuthHttp,
useFactory: (http:Http) => new AuthHttp(new AuthConfig({tokenName: 'jwt'}), http),
deps: [Http]
},
{
provide: TranslateLoader,
useFactory: (http:Http) => new TranslateStaticLoader(http, 'app/i18n', '.json'),
deps: [Http]
},
{provide: LocationStrategy, useClass: PathLocationStrategy},
// use TranslateService here, and not TRANSLATE_PROVIDERS (which will define a default TranslateStaticLoader)
TranslateService,
ApiService,
CookieService,
AuthenticationService
]).catch(err => console.error(err));
my router is:
export const routes:RouterConfig = [
...AccountRouters,
...DealOverviewRouters
];
export const APP_ROUTER_PROVIDERS = [
provideRouter(routes)
];
export const AccountRouters: RouterConfig = [
{
path: 'account', component: AccountComponent
},
{
path: 'login', component: LoginComponent
},
];
export const DealOverviewRouters:RouterConfig = [
{
path: '',
redirectTo: '/deal-overview',
terminal: true
},
{
path: 'deal-overview', component: DealOverviewComponent
}
];
then in my app.component.ts:
constructor(public translate:TranslateService, private _service: AuthenticationService, private _router: Router) {
this.isLogin = _service.isLogin;
console.log(_service.isLogin);
}
ngOnInit() {
// this._service.checkCredentials();
if(!this.isLogin) {
console.log(1111);
this._router.navigateByUrl('/login');
}
}
it's really print 1111 on my console log;
but the redirect to /login not working.
I can visit my localhost:3000/login page directly and nothing error.
but only this._router.navigateByUrl('/login') not working.
I've had issues with the following syntax with the new router:
export const routes:RouterConfig = [
...AccountRouters,
...DealOverviewRouters
];
instead, manually place them all into the one const and try that:
export const routes:RouterConfig = [
{
path: '',
redirectTo: '/deal-overview',
terminal: true
},
{
path: 'deal-overview', component: DealOverviewComponent
},
{
path: 'account', component: AccountComponent
},
{
path: 'login', component: LoginComponent
}
];