Skip to content

Commit

Permalink
feat: implement registration service and registration confirm service
Browse files Browse the repository at this point in the history
  • Loading branch information
ribeirogab committed Sep 15, 2024
1 parent f872282 commit 1fa209f
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum HttpMethodEnum {
}

export enum AppErrorCodeEnum {
VerificationCodeNotFound = 'verification_code_not_found',
RegisterTokenNotFound = 'register_token_not_found',
EmailAlreadyInUse = 'email_already_in_use',
}
Expand Down
4 changes: 4 additions & 0 deletions src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
LoginService,
LogoutService,
RefreshLoginService,
RegistrationConfirmService,
RegistrationService,
} from './services';

// Adapters
Expand Down Expand Up @@ -52,7 +54,9 @@ container.registerSingleton<UserRepository>('UserRepository', UserRepository);

// Services
container.registerSingleton<CreateRegisterTokenService>('CreateRegisterTokenService', CreateRegisterTokenService);
container.registerSingleton<RegistrationConfirmService>('RegistrationConfirmService', RegistrationConfirmService);
container.registerSingleton<GetRegisterTokenService>('GetRegisterTokenService', GetRegisterTokenService);
container.registerSingleton<RegistrationService>('RegistrationService', RegistrationService);
container.registerSingleton<CreateUserService>('CreateUserService', CreateUserService);
container.registerSingleton<LogoutService>('LogoutService', LogoutService);
container.registerSingleton<LoginService>('LoginService', LoginService);
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export * from './models/session';
export * from './models/user';

// Repositories
export * from './repositories/verification-code.repository';
export * from './repositories/register-token.repository';
export * from './repositories/email-template.repository';
export * from './repositories/user-token.repository';
Expand All @@ -31,9 +32,11 @@ export * from './routers/router';
// Services
export * from './services/recovery-password-link.service';
export * from './services/create-register-token.service';
export * from './services/registration-confirm.service';
export * from './services/get-register-token.service';
export * from './services/recovery-password.service';
export * from './services/refresh-login.service';
export * from './services/registration.service';
export * from './services/create-user.service';
export * from './services/logout.service';
export * from './services/login.service';
18 changes: 13 additions & 5 deletions src/interfaces/repositories/verification-code.repository.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
import type { VerificationCode } from '../models/verification-code';
import type {
VerificationCode,
VerificationCodeTypeEnum,
} from '../models/verification-code';

export type VerificationCodeRepositoryFilterDto = {
type: VerificationCodeTypeEnum;
code: string;
};

export interface VerificationCodeRepository {
create(dto: Omit<VerificationCode, 'code'>): Promise<void>;

findByCode(dto: { code: string }): Promise<VerificationCode | null>;

findByContent(dto: { content: string }): Promise<VerificationCode | null>;
findOne(
dto: VerificationCodeRepositoryFilterDto,
): Promise<VerificationCode | null>;

deleteByCode(dto: { code: string }): Promise<void>;
deleteOne(dto: VerificationCodeRepositoryFilterDto): Promise<void>;
}
7 changes: 7 additions & 0 deletions src/interfaces/services/registration-confirm.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type RegistrationConfirmServiceDto = {
code: string;
};

export interface RegistrationConfirmService {
execute(dto: RegistrationConfirmServiceDto): Promise<void>;
}
9 changes: 9 additions & 0 deletions src/interfaces/services/registration.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export type RegistrationServiceDto = {
password: string;
email: string;
name: string;
};

export interface RegistrationService {
execute(dto: RegistrationServiceDto): Promise<void>;
}
33 changes: 16 additions & 17 deletions src/repositories/verification-code.repository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type {
VerificationCode,
VerificationCodeRepositoryFilterDto,
VerificationCodeRepository as VerificationCodeRepositoryInterface,
} from '@/interfaces';

Expand All @@ -20,28 +21,26 @@ export class VerificationCodeRepository
this.codes.push({ ...dto, code });
}

public async findByCode(dto: {
code: string;
}): Promise<VerificationCode | null> {
const code = this.codes.find(
(registerCode) => registerCode.code === dto.code,
public async findOne({
code,
type,
}: VerificationCodeRepositoryFilterDto): Promise<VerificationCode | null> {
const verificationCode = this.codes.find(
(registerCode) =>
registerCode.code === code && registerCode.type === type,
);

return code || null;
return verificationCode || null;
}

public async findByContent(dto: {
content: string;
}): Promise<VerificationCode | null> {
const code = this.codes.find(
(registerCode) => registerCode.content === dto.content,
public async deleteOne({
code,
type,
}: VerificationCodeRepositoryFilterDto): Promise<void> {
this.codes = this.codes.filter(
(verificationCode) =>
verificationCode.code !== code && verificationCode.type !== type,
);

return code || null;
}

public async deleteByCode(dto: { code: string }): Promise<void> {
this.codes = this.codes.filter((code) => code.code !== dto.code);
}

private generateCode(length: number): string {
Expand Down
2 changes: 2 additions & 0 deletions src/services/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export * from './create-register-token.service';
export * from './registration-confirm.service';
export * from './get-register-token.service';
export * from './refresh-login.service';
export * from './registration.service';
export * from './create-user.service';
export * from './logout.service';
export * from './login.service';
60 changes: 60 additions & 0 deletions src/services/registration-confirm.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { inject, injectable } from 'tsyringe';

import { AppErrorCodeEnum, HttpStatusCodesEnum } from '@/constants';
import { AppError } from '@/errors';
import {
type RegistrationConfirmServiceDto,
type RegistrationConfirmService as RegistrationConfirmServiceInterface,
type User,
type UserRepository,
type VerificationCodeRepository,
VerificationCodeTypeEnum,
} from '@/interfaces';

@injectable()
export class RegistrationConfirmService
implements RegistrationConfirmServiceInterface
{
constructor(
@inject('UserRepository')
private readonly userRepository: UserRepository,

@inject('VerificationCodeRepository')
private verificationCodeRepository: VerificationCodeRepository,
) {}

public async execute({ code }: RegistrationConfirmServiceDto): Promise<void> {
const verificationCode = await this.verificationCodeRepository.findOne({
type: VerificationCodeTypeEnum.Registration,
code,
});

if (!verificationCode) {
throw new AppError({
message: AppErrorCodeEnum.VerificationCodeNotFound,
status_code: HttpStatusCodesEnum.NOT_FOUND,
});
}

const user = this.parseUser(verificationCode.content);

const userExists = await this.userRepository.findByEmail({
email: user.email,
});

if (userExists) {
throw new AppError({
message: AppErrorCodeEnum.EmailAlreadyInUse,
status_code: HttpStatusCodesEnum.CONFLICT,
});
}

await this.userRepository.create(user);
}

private parseUser(content: string): User {
const user = JSON.parse(content) as User;

return user;
}
}
68 changes: 68 additions & 0 deletions src/services/registration.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { inject, injectable } from 'tsyringe';

import type { HashAdapter } from '@/adapters';
import { AppErrorCodeEnum, HttpStatusCodesEnum } from '@/constants';
import { AppError } from '@/errors';
import {
type RegistrationServiceDto,
type RegistrationService as RegistrationServiceInterface,
type User,
type UserRepository,
type VerificationCodeRepository,
VerificationCodeTypeEnum,
} from '@/interfaces';

@injectable()
export class RegistrationService implements RegistrationServiceInterface {
constructor(
@inject('UserRepository')
private readonly userRepository: UserRepository,

@inject('VerificationCodeRepository')
private verificationCodeRepository: VerificationCodeRepository,

@inject('HashAdapter')
private hashAdapter: HashAdapter,
) {}

public async execute({
password,
email,
name,
}: RegistrationServiceDto): Promise<void> {
const userExists = await this.userRepository.findByEmail({
email,
});

if (userExists) {
throw new AppError({
message: AppErrorCodeEnum.EmailAlreadyInUse,
status_code: HttpStatusCodesEnum.CONFLICT,
});
}

const salt = this.hashAdapter.generateSalt();
const hashedPassword = this.hashAdapter.hash({
text: password,
salt,
});

const content: Omit<User, 'id'> = {
password: hashedPassword,
password_salt: salt,
email,
name,
};

const expirationTimeInMinutes = 60; // 1 hour
const expiresAt = new Date(
Date.now() + expirationTimeInMinutes * 60 * 1000,
).toISOString();

await this.verificationCodeRepository.create({
type: VerificationCodeTypeEnum.Registration,
content: JSON.stringify(content),
expires_at: expiresAt,
});
}
}

0 comments on commit 1fa209f

Please sign in to comment.