I want to implement method in schema class like below.
import { SchemaFactory, Schema, Prop } from '#nestjs/mongoose';
import { Document } from 'mongoose';
import bcrypt from 'bcrypt';
#Schema()
export class Auth extends Document {
#Prop({ required: true, unique: true })
username: string;
#Prop({ required: true })
password: string;
#Prop({
methods: Function,
})
async validatePassword(password: string): Promise<boolean> {
return bcrypt.compareAsync(password, this.password);
}
}
export const AuthSchema = SchemaFactory.createForClass(Auth);
this schema return undefined when log the method . How can I write method in class schema with nestjs/mongoose package?
You can use below approach to achieve this.
#Schema()
export class Auth extends Document {
...
validatePassword: Function;
}
export const AuthSchema = SchemaFactory.createForClass(Auth);
AuthSchema.methods.validatePassword = async function (password: string): Promise<boolean> {
return bcrypt.compareAsync(password, this.password);
};
Use this function instead of SchemaFactory.createForClass(Auth):
export function createSchema(document: any) {
const schema = SchemaFactory.createForClass(document);
const instance = Object.create(document.prototype);
for (const objName of Object.getOwnPropertyNames(document.prototype)) {
if (
objName != 'constructor' &&
typeof document.prototype[objName] == 'function'
) {
schema.methods[objName] = instance[objName];
}
}
return schema;
}
Related
I followed the instructions on how to generate models from an existing database as pointed here https://github.com/sequelize/sequelize-auto. However when I try to query the database using the model i get this error:
TypeError: Cannot convert undefined or null to object
at Function.keys ()
at Function.findAll (/home/jefry/node/sequelize/node_modules/sequelize/src/model.js:1755:47)
at /home/jefry/node/sequelize/src/components/users/controllers.ts:11:32
at Generator.next ()
at /home/jefry/node/sequelize/src/components/users/controllers.ts:31:71
at new Promise ()
at __awaiter (/home/jefry/node/sequelize/src/components/users/controllers.ts:27:12)
at list (/home/jefry/node/sequelize/src/components/users/controllers.ts:45:12)
at Layer.handle [as handle_request] (/home/jefry/node/sequelize/node_modules/express/lib/router/layer.js:95:5)
at next (/home/jefry/node/sequelize/node_modules/express/lib/router/route.js:144:13)
Controller*
export async function list(req: Request, res: Response) {
try {
const list = await primera.findAll();
res.json({ list });
} catch (error) {
console.log(error);
}
}
Model*
import * as Sequelize from 'sequelize';
import { DataTypes, Model, Optional } from 'sequelize';
export interface primeraAttributes {
verdad?: number;
}
export type primeraOptionalAttributes = "verdad";
export type primeraCreationAttributes = Optional<primeraAttributes, primeraOptionalAttributes>;
export class primera extends Model<primeraAttributes, primeraCreationAttributes> implements primeraAttributes {
verdad?: number;
static initModel(sequelize: Sequelize.Sequelize): typeof primera {
return primera.init({
verdad: {
type: DataTypes.BOOLEAN,
allowNull: true
}
}, {
sequelize,
tableName: 'primera',
timestamps: false
});
}
}
init-models
import type { Sequelize } from "sequelize";
import { primera as _primera } from "./primera";
import type { primeraAttributes, primeraCreationAttributes } from "./primera";
export {
_primera as primera,
};
export type {
primeraAttributes,
primeraCreationAttributes,
};
export function initModels(sequelize: Sequelize) {
const primera = _primera.initModel(sequelize);
return {
primera: primera,
};
}
You should pass at least an empty object as an argument of findAll:
const list = await primera.findAll({});
Solution
With the solution posted below my create function player.service.ts now looks like this:
async create(createPlayerDto: CreatePlayerDto): Promise<Player> {
const newPlayer = this.playerRepository.create(createPlayerDto);
return await this.playerRepository.save(newPlayer);
}
The hook within my player.entity.ts:
#BeforeInsert()
async hashPassword() {
console.log('Hash password!');
this.password = await bcrypt.hash(this.password, this.saltOrRounds);
}
Problem
For my project with NestJS I have created a Player entity (player.entity.ts), which has the following columns and one hook. I have connected a MySQL8.0 database via the TypeORM package.
import {
Entity,
Column,
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
BeforeInsert,
} from 'typeorm';
import * as bcrypt from 'bcrypt';
#Entity({ name: 'players' })
export class Player {
readonly saltOrRounds = 10;
#PrimaryGeneratedColumn()
id: number;
#Column({
type: 'varchar',
unique: true,
})
username: string;
#Column()
password: string;
#Column({
unique: true,
type: 'varchar',
})
email: string;
#CreateDateColumn({
name: 'created_at',
type: 'datetime',
})
created_at: 'datetime';
#UpdateDateColumn({
name: 'updated_at',
type: 'datetime',
})
updated_at: 'datetime';
#BeforeInsert()
async hashPassword() {
return (
this.password && (await bcrypt.hash(this.password, this.saltOrRounds))
);
}
}
As you can see the #BeforeInsert() hook should take the password, hash it and then return the hashed password.
The relevant route for the creation of a new player is placed within the players.controller.ts:
import { Body, Controller, Delete, Get, Param, Post } from '#nestjs/common';
import { PlayersService } from './players.service';
import { CreatePlayerDto } from './dto/create-player.dto';
import { Player } from './interfaces/player.interface';
#Controller('players')
export class PlayersController {
constructor(private playerService: PlayersService) {}
#Post()
async create(#Body() createPlayerDto: CreatePlayerDto) {
return this.playerService.create(createPlayerDto);
}
}
The controller utilizes the player.service.ts and makes uses the EntityManager to perform the create/insert operation:
import { Injectable } from '#nestjs/common';
import { InjectEntityManager, InjectRepository } from '#nestjs/typeorm';
import { Player } from './entities/player.entity';
import { EntityManager, Repository } from 'typeorm';
import { CreatePlayerDto } from './dto/create-player.dto';
#Injectable()
export class PlayersService {
constructor(
#InjectEntityManager()
private entityManager: EntityManager,
#InjectRepository(Player)
private playerRepository: Repository<Player>,
) {}
async create(createPlayerDto: CreatePlayerDto): Promise<Player> {
return this.entityManager.save(Player, createPlayerDto);
}
}
I've also tried to use the Repository with the same result. A new player is created everytime I make a POST request to the /create endpoint. But unfortunatelly none of the used hooks and/or listeners work.
Instantiate the Entity, assign the attributes to it and then save.
async create(attributes: DeepPartial<T>) {
const playerEntity = Object.assign(new Player(), attributes);
return this.repository.save(playerEntity);
}
or you could use the create method on the repository and then save it.
const record = playerRepository.create(attributes);
await playerRepository.save(record);
I'm trying to create a generic crud controller that use a generic crud service in a NestJS application. It works properly but the swagger module doesn't generate the documentation about the REST services parameters correctly.
This is the service:
import { Model, Document } from "mongoose";
export abstract class CrudService<CrudModel extends Document, CreateDto, UpdateDto> {
constructor(protected readonly model: Model<CrudModel>) {}
async findAll(): Promise<CrudModel[]> {
return this.model.find().exec();
}
async create(dto: CreateDto): Promise<CrudModel> {
const createdDto = new this.model(dto);
return createdDto.save();
}
async update(id: any, dto: UpdateDto): Promise<CrudModel> {
return this.model.findOneAndUpdate({ _id: id }, dto, { new: true });
}
async delete(id: any): Promise<boolean> {
const deleteResult = await this.model.deleteOne({ _id: id });
return deleteResult.ok === 1 && deleteResult.deletedCount === 1;
}
}
This is the controller:
import { Body, Delete, Get, Param, Post, Put } from "#nestjs/common";
import { Document } from "mongoose";
import { CrudService } from "./crud-service.abstract";
export abstract class CrudController<CrudModel extends Document, CreateDto, UpdateDto> {
constructor(protected readonly service: CrudService<CrudModel, CreateDto, UpdateDto>) {}
#Get()
async findAll(): Promise<CrudModel[]> {
return this.service.findAll();
}
#Post()
async create(#Body() dto: CreateDto): Promise<CrudModel> {
return this.service.create(dto);
}
#Put(':id')
async update(#Param('id') id: string, #Body() dto: UpdateDto): Promise<CrudModel> {
return this.service.update(id, dto);
}
#Delete(':id')
async delete(#Param('id') id: string): Promise<boolean> {
return this.service.delete(id);
}
}
I found this issue on Github repo: https://github.com/nestjs/swagger/issues/86
The last comment mentions a solution using mixins but I can't figure it out how to adapt it to my needs
I eventually went to describing my own schema.
Here is the custom decorator (NestJs example)
import { applyDecorators } from '#nestjs/common';
import { ApiOkResponse, getSchemaPath } from '#nestjs/swagger';
export const OpenApiPaginationResponse = (model: any) => {
return applyDecorators(
ApiOkResponse({
schema: {
properties: {
totalPages: {
type: 'number'
},
currentPage: {
type: 'number'
},
itemsPerPage: {
type: 'number'
},
data: {
type: 'array',
items: { $ref: getSchemaPath(model) }
}
}
}
})
);
};
And here is a example of how it is applied to a controller
#OpenApiPaginationResponse(DTOHere)
public async controllerMethod() {}
Hope this helps you out
I had the same problem recently, I found the solution with Decorators in Nestjs because for native properties Swagger can't recognize our Generic Type Class < T >.
I will show you how I could implement my solution with a Parameterized Pagination Class.
Specify our Pagination Class
export class PageDto<T> {
#IsArray()
readonly data: T[];
#ApiProperty({ type: () => PageMetaDto })
readonly meta: PageMetaDto;
constructor(data: T[], meta: PageMetaDto) {
this.data = data;
this.meta = meta;
}
}
Our parametrized type is data.
Create our Decorator class, that will map our parametrized type data
export const ApiOkResponsePaginated = <DataDto extends Type<unknown>>(
dataDto: DataDto,
) =>
applyDecorators(
ApiExtraModels(PageDto, dataDto),
ApiOkResponse({
schema: {
allOf: [
{ $ref: getSchemaPath(PageDto) },
{
properties: {
data: {
type: 'array',
items: { $ref: getSchemaPath(dataDto) },
},
},
},
],
},
}),
);
In this decorator we used the definition of SwaggerDocumentation for specify what will be our class that Swagger will be mapped.
Add our Decorator ApiOkResponsePaginated class in our Controller.
#Get('/invoices')
#ApiOkResponsePaginated(LightningInvoice)
async getAllInvoices(
#Auth() token: string,
#Query() pageOptionsDto: any,
): Promise<any> {
return this.client.send('get_invoices', {
token,
pageOptionsDto,
});
}
And that's how you can visualize in Swagger the representation of PageDto<LightningInvoice> response.
I hope this answer help you in your code.
I'm using typescript and typeorm. I have this Entity:
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
#Entity()
export class Sample {
#PrimaryGeneratedColumn()
id: number;
#Column({ length: 50 })
name: string;
#Column('text', { nullable: true })
description: string;
}
I query a single result like this:
const connection = await this.getConnection();
const sampleRepo = await connection.getRepository(Sample);
const sample = await sampleRepo.createQueryBuilder('sample')
.where('sample.id = :id', { id: id })
.getOne();
Now, I need to do some stuff with the result columns, but the sample object is of type EntitySchema. So, in typescript, I can't do sample.id because the error:
Property 'id' does not exist on type 'EntitySchema<any>'
Is there anyway to convert the EntitySchema into an actual Sample object?
As it turns out, this is due to a bad implementation. I moved the creation of the repository to a separate class:
export default class Database {
private connectionManager: ConnectionManager
constructor() {
this.connectionManager = getConnectionManager();
}
public getRepository<T extends EntitySchema>(type: ObjectType<T> | EntitySchema<T> | string): Promise<Repository<T>> {
const connection = await this.getConnection();
return connection.getRepository(type);
}
public async getConnection(connectionName = 'default'): Promise<Connection> {
let connection: Connection;
if (this.connectionManager.has(connectionName)) {
connection = this.connectionManager.get(connectionName);
if (!connection.isConnected) {
connection = await connection.connect();
}
}
else {
const connectionOptions: ConnectionOptions = Object
.assign({ name: connection }, connectionProperties);
connection = await createConnection(connectionOptions);
}
return connection;
}
}
It looks like connection.getRepository doesn't return a promise. As well, the T generic shouldn't be extending EntitySchema. To make the function work as intended, I had to write it like this:
public getRepository<T>(type: ObjectType<T> | EntitySchema<T> | string): Promise<Repository<T>> {
return new Promise((resolve, reject) => {
this.getConnection().then(conn => {
resolve(conn.getRepository(type));
}).catch(reject);
});
}
I want to access findById function of CRUDService in ItemService. I'm getting response from readAll function but not getting from findById. I think dao object what I'm passing to CRUDService from ItemService through constructor is not working. I'm new in node js and express js. Could you help me please.
This is Crud Service
class CRUDService{
constructor(dao) {
this.dao = dao;
}
readAll = () => {
const rows = dao.findAll();
return rows;
};
findById = (rowId) => {
const row = dao.findByPk(rowId);
return row;
};
}
module.exports = CRUDService
This is Item Service
const CRUDService = require('../common/crud.service.js');
const ItemDAO = require('./item.dao.js');
class ItemService extends CRUDService{
constructor() {
const dao = new ItemDAO();
super(ItemDAO);
}
readAll = () => {
const rows = ItemDAO.findAll();
return rows;
};
}
module.exports = ItemService
This is DAO
const {Sequelize, Model} = require('sequelize');
const sequelize = require('../database');
class ItemDAO extends Model {}
ItemDAO.init(
{
id: {
type: Sequelize.UUID,
primaryKey: true,
defaultValue: Sequelize.UUIDV1
},
name_en: Sequelize.STRING,
name_local: Sequelize.STRING,
created_at: Sequelize.TIME,
created_by: Sequelize.STRING,
is_deleted: Sequelize.BOOLEAN
},
{
sequelize,
modelName: 'Item',
schema: 'cat',
timestamps: false,
tableName: 'item'
}
);
module.exports = ItemDAO;
You need to pass the instance of your ItemDAO to the super constructor.
const CRUDService = require('../common/crud.service.js');
const ItemDAO = require('./item.dao.js');
class ItemService extends CRUDService{
constructor() {
super(new ItemDAO()); // ---> here
}
readAll = () => {
const rows = this.readAll();
return rows;
};
}
module.exports = ItemService
Also need to modify your service.
class CRUDService{
constructor(dao) {
this.dao = dao;
}
readAll = () => this.dao.findAll().then(rows => rows);
findById = (rowId) => this.dao.findByPk(rowId).then(row => row);
}
Also remember those methods return promises so better to use .then() or use async/await.