Skip to content

Commit

Permalink
fix(): fix password hash
Browse files Browse the repository at this point in the history
  • Loading branch information
NarHakobyan committed Mar 31, 2024
1 parent 06f1e5d commit 633f97b
Show file tree
Hide file tree
Showing 17 changed files with 129 additions and 71 deletions.
12 changes: 6 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{
"name": "awesome-nestjs-boilerplate",
"version": "10.0.0",
"description": "Awesome NestJS Boilerplate, Typescript, Postgres, TypeORM",
"description": "Awesome NestJS Boilerplate, Typescript, Postgres, MikroOrm",
"author": "Narek Hakobyan <[email protected]>",
"private": true,
"license": "MIT",
"scripts": {
"build:prod": "nest build",
"start:dev": "ts-node src/main.ts",
"start:prod": "node dist/main.js",
"typeorm": "typeorm-ts-node-esm",
"migration:generate": "yarn run typeorm migration:generate -d ormconfig",
"migration:create": "yarn run typeorm migration:create -d ormconfig",
"new": "hygen new",
"migration:revert": "yarn run typeorm migration:revert",
"schema:drop": "yarn run typeorm schema:drop",
"migration:generate": "mikro-orm migration:creater",
"migration:create": "mikro-orm migration:creater --blank",
"migration:down": "mikro-orm migration:down",
"migration:fresh": "mikro-orm migration:fresh",
"schema:fresh": "mikro-orm schema:fresh",
"watch:dev": "ts-node-dev src/main.ts",
"debug:dev": "cross-env TS_NODE_CACHE=false ts-node-dev --inspect --ignore '/^src/.*\\.spec\\.ts$/' src/main.ts",
"lint": "eslint . --ext .ts",
Expand Down
23 changes: 21 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ import './boilerplate.polyfill';

import path from 'node:path';

import { MikroOrmModule } from '@mikro-orm/nestjs';
import { MikroORM } from '@mikro-orm/core';
import { MikroOrmMiddleware, MikroOrmModule } from '@mikro-orm/nestjs';
import type {
MiddlewareConsumer,
NestModule,
OnModuleInit,
} from '@nestjs/common';
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ThrottlerModule } from '@nestjs/throttler';
Expand Down Expand Up @@ -70,4 +76,17 @@ import { SharedModule } from './shared/shared.module';
],
providers: [],
})
export class AppModule {}
export class AppModule implements NestModule, OnModuleInit {
constructor(private readonly orm: MikroORM) {}

async onModuleInit(): Promise<void> {
await this.orm.getMigrator().up();
}

// for some reason the auth middlewares in profile and article modules are fired before the request context one,
// so they would fail to access contextual EM. by registering the middleware directly in AppModule, we can get
// around this issue
configure(consumer: MiddlewareConsumer) {
consumer.apply(MikroOrmMiddleware).forRoutes('*');
}
}
15 changes: 10 additions & 5 deletions src/common/abstract.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ export abstract class AbstractEntity<

translations?: Collection<AbstractTranslationEntity>;

private dtoClass?: Constructor<DTO, [AbstractEntity, O?, Optional?]>;
abstract dtoClass?: () => Constructor<DTO, [AbstractEntity, O?, Optional?]>;

toDto(options?: O): DTO {
const dtoClass = this.dtoClass;
const dtoClass = this.dtoClass?.();

if (!dtoClass) {
throw new Error(
Expand All @@ -53,10 +53,15 @@ export abstract class AbstractEntity<
}
}

export class AbstractTranslationEntity<
export abstract class AbstractTranslationEntity<
DTO extends AbstractTranslationDto = AbstractTranslationDto,
O = never,
> extends AbstractEntity<DTO, O> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
O = any,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Optional = any,
> extends AbstractEntity<DTO, O, Optional> {
abstract dtoClass?: () => Constructor<DTO, [AbstractEntity, O?, Optional?]>;

@Enum(() => LanguageCode)
languageCode!: LanguageCode;
}
27 changes: 27 additions & 0 deletions src/common/extended-entity-repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { AnyEntity, EntityManager } from '@mikro-orm/postgresql';
import { EntityRepository } from '@mikro-orm/postgresql';

export class ExtendedEntityRepository<
// eslint-disable-next-line @typescript-eslint/ban-types
T extends object,
> extends EntityRepository<T> {
persist(entity: AnyEntity | AnyEntity[]): EntityManager {
return this.em.persist(entity);
}

async persistAndFlush(entity: AnyEntity | AnyEntity[]): Promise<void> {
await this.em.persistAndFlush(entity);
}

remove(entity: AnyEntity): EntityManager {
return this.em.remove(entity);
}

async removeAndFlush(entity: AnyEntity): Promise<void> {
await this.em.removeAndFlush(entity);
}

async flush(): Promise<void> {
return this.em.flush();
}
}
2 changes: 1 addition & 1 deletion src/decorators/transform.decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,5 +153,5 @@ export function S3UrlParser(): PropertyDecorator {
}

export function PhoneNumberSerializer(): PropertyDecorator {
return Transform((params) => parsePhoneNumber(params.value as string).number);
return Transform((params) => params.value ? parsePhoneNumber(params.value as string).number: undefined);
}
4 changes: 2 additions & 2 deletions src/decorators/use-dto.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { type Constructor } from '../types';
import type {Constructor} from '../types';

export function UseDto(dtoClass: Constructor): ClassDecorator {
export function UseDto(dtoClass: () => Constructor): ClassDecorator {
return (ctor) => {
// FIXME make dtoClass function returning dto

Expand Down
71 changes: 40 additions & 31 deletions src/entity-subscribers/user-subscriber.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,40 @@
// import {
// type EntitySubscriberInterface,
// EventSubscriber,
// type InsertEvent,
// type UpdateEvent,
// } from 'typeorm';
//
// import { generateHash } from '../common/utils';
// import { UserEntity } from '../modules/user/user.entity';
//
// @EventSubscriber()
// export class UserSubscriber implements EntitySubscriberInterface<UserEntity> {
// listenTo(): typeof UserEntity {
// return UserEntity;
// }
//
// beforeInsert(event: InsertEvent<UserEntity>): void {
// if (event.entity.password) {
// event.entity.password = generateHash(event.entity.password);
// }
// }
//
// beforeUpdate(event: UpdateEvent<UserEntity>): void {
// // FIXME check event.databaseEntity.password
// const entity = event.entity as UserEntity;
//
// if (entity.password !== event.databaseEntity.password) {
// entity.password = generateHash(entity.password!);
// }
// }
// }
import type {
EventArgs,
EventSubscriber,
FlushEventArgs,
} from '@mikro-orm/core';
import { EntityManager } from '@mikro-orm/postgresql';
import { Injectable } from '@nestjs/common';

import { generateHash } from '../common/utils';
import { UserEntity } from '../modules/user/user.entity';

@Injectable()
export class UserSubscriber implements EventSubscriber<UserEntity> {
constructor(em: EntityManager) {
em.getEventManager().registerSubscriber(this);
}

getSubscribedEntities() {
return [UserEntity];
}

onFlush(args: FlushEventArgs): void {
for (const changeSet of args.uow.getChangeSets()) {
const changedPassword = changeSet.payload.password;

if (changedPassword) {
changeSet.entity.password = generateHash(changedPassword);
args.uow.recomputeSingleChangeSet(changeSet.entity);
}
}
}

beforeUpdate(event: EventArgs<UserEntity>): void {
const entity = event.entity;

if (entity.password !== event.changeSet?.entity.password) {
entity.password = generateHash(entity.password!);
}
}
}
7 changes: 0 additions & 7 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { MikroORM } from '@mikro-orm/core';
import {
ClassSerializerInterceptor,
HttpStatus,
Expand Down Expand Up @@ -61,12 +60,6 @@ export async function bootstrap(): Promise<NestExpressApplication> {

const configService = app.select(SharedModule).get(ApiConfigService);

const orm = app.get(MikroORM);
const migrator = orm.getMigrator();

// Run migrations automatically
await migrator.up(); // This will apply all pending migrations

// only start nats if it is enabled
if (configService.natsEnabled) {
const natsConfig = configService.natsConfig;
Expand Down
6 changes: 3 additions & 3 deletions src/modules/post/commands/create-post.command.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { Collection } from '@mikro-orm/core';
import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityRepository } from '@mikro-orm/postgresql';
import type { ICommand, ICommandHandler } from '@nestjs/cqrs';
import { CommandHandler } from '@nestjs/cqrs';
import { find } from 'lodash';

import type { CreatePostDto } from '../dtos/create-post.dto';
import { PostEntity } from '../post.entity';
import { PostTranslationEntity } from '../post-translation.entity';
import { ExtendedEntityRepository } from '../../../common/extended-entity-repository.ts';

export class CreatePostCommand implements ICommand {
constructor(
Expand All @@ -22,9 +22,9 @@ export class CreatePostHandler
{
constructor(
@InjectRepository(PostEntity)
private postRepository: EntityRepository<PostEntity>,
private postRepository: ExtendedEntityRepository<PostEntity>,
@InjectRepository(PostTranslationEntity)
private postTranslationRepository: EntityRepository<PostTranslationEntity>,
private postTranslationRepository: ExtendedEntityRepository<PostTranslationEntity>,
) {}

async execute(command: CreatePostCommand) {
Expand Down
4 changes: 2 additions & 2 deletions src/modules/post/post-translation.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import { Entity, ManyToOne, Property } from '@mikro-orm/core';
import { AbstractTranslationEntity } from '../../common/abstract.entity';
import { PostTranslationDto } from './dtos/post-translation.dto';
import { PostEntity } from './post.entity';
import { UseDto } from '../../decorators/use-dto.decorator.ts';

@Entity({ tableName: 'post_translations' })
@UseDto(PostTranslationDto)
export class PostTranslationEntity extends AbstractTranslationEntity<PostTranslationDto> {
dtoClass = () => PostTranslationDto as any;

@Property()
title!: string;

Expand Down
5 changes: 3 additions & 2 deletions src/modules/post/post.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@ import {
} from '@mikro-orm/core';

import { AbstractEntity } from '../../common/abstract.entity';
import { UseDto } from '../../decorators/use-dto.decorator.ts';
import { UserEntity } from '../user/user.entity';
import { PostDto } from './dtos/post.dto';
import { PostTranslationEntity } from './post-translation.entity';

@Entity({ tableName: 'posts' })
@UseDto(PostDto)
export class PostEntity extends AbstractEntity<PostDto> {
// FIXME fix type
dtoClass = () => PostDto as any;

@Property({ type: 'uuid', fieldName: 'user_id', persist: false })
userId!: Uuid;

Expand Down
2 changes: 1 addition & 1 deletion src/modules/user/dtos/user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
PhoneFieldOptional,
StringFieldOptional,
} from '../../../decorators/field.decorators';
import { type UserEntity } from '../user.entity';
import type { UserEntity } from '../user.entity';

// TODO, remove this class and use constructor's second argument's type
export type UserDtoOptions = Partial<{ isActive: boolean }>;
Expand Down
4 changes: 2 additions & 2 deletions src/modules/user/user-settings.entity.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Entity, OneToOne, Property } from '@mikro-orm/core';

import { AbstractEntity } from '../../common/abstract.entity';
import { UseDto } from '../../decorators/use-dto.decorator.ts';
import type { UserDtoOptions } from './dtos/user.dto';
import { UserDto } from './dtos/user.dto';
import { UserEntity } from './user.entity';

@Entity({ tableName: 'user_settings' })
@UseDto(UserDto)
export class UserSettingsEntity extends AbstractEntity<
UserDto,
UserDtoOptions
> {
dtoClass = () => UserDto as any;

@Property({ default: false })
isEmailVerified = false;

Expand Down
4 changes: 2 additions & 2 deletions src/modules/user/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ import {

import { AbstractEntity } from '../../common/abstract.entity';
import { RoleType } from '../../constants';
import { UseDto } from '../../decorators/use-dto.decorator.ts';
import { PostEntity } from '../post/post.entity';
import type { UserDtoOptions } from './dtos/user.dto';
import { UserDto } from './dtos/user.dto';
import { UserSettingsEntity } from './user-settings.entity';

@Entity({ tableName: 'users' })
@UseDto(UserDto)
export class UserEntity extends AbstractEntity<UserDto, UserDtoOptions> {
dtoClass = () => UserDto as any;

@Property({ nullable: true, type: 'varchar' })
firstName!: string | null;

Expand Down
5 changes: 3 additions & 2 deletions src/modules/user/user.module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { Module } from '@nestjs/common';

import { UserSubscriber } from '../../entity-subscribers/user-subscriber.ts';
import { CreateSettingsHandler } from './commands/create-settings.command';
import { UserController } from './user.controller';
import { UserEntity } from './user.entity';
import { UserService } from './user.service';
import { UserSettingsEntity } from './user-settings.entity';
import { MikroOrmModule } from '@mikro-orm/nestjs';

const handlers = [CreateSettingsHandler];

@Module({
imports: [MikroOrmModule.forFeature([UserEntity, UserSettingsEntity])],
controllers: [UserController],
exports: [UserService],
providers: [UserService, ...handlers],
providers: [UserService, UserSubscriber, ...handlers],
})
export class UserModule {}
6 changes: 3 additions & 3 deletions src/modules/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { FilterQuery } from '@mikro-orm/core/typings';
import { InjectRepository } from '@mikro-orm/nestjs';
import { EntityRepository } from '@mikro-orm/postgresql';
import { Injectable } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { plainToClass } from 'class-transformer';
Expand All @@ -16,12 +15,13 @@ import { CreateSettingsDto } from './dtos/create-settings.dto';
import type { UserDto } from './dtos/user.dto';
import { UserEntity } from './user.entity';
import type { UserSettingsEntity } from './user-settings.entity';
import { ExtendedEntityRepository } from '../../common/extended-entity-repository.ts';

@Injectable()
export class UserService {
constructor(
@InjectRepository(UserEntity)
private userRepository: EntityRepository<UserEntity>,
private userRepository: ExtendedEntityRepository<UserEntity>,
private validatorService: ValidatorService,
private awsS3Service: AwsS3Service,
private commandBus: CommandBus,
Expand Down Expand Up @@ -74,7 +74,7 @@ export class UserService {
user.avatar = await this.awsS3Service.uploadImage(file);
}

await this.userRepository.insert(user);
await this.userRepository.persistAndFlush(user);

user.settings = await this.createSettings(
user.id,
Expand Down
Loading

0 comments on commit 633f97b

Please sign in to comment.