Skip to content

Commit

Permalink
refactor: add expired_at in the confirmation_code
Browse files Browse the repository at this point in the history
  • Loading branch information
hdev14 committed Feb 13, 2024
1 parent f183609 commit d1eb450
Show file tree
Hide file tree
Showing 10 changed files with 55 additions and 29 deletions.
7 changes: 4 additions & 3 deletions script.sql
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ CREATE TABLE alerts (
CREATE TABLE user_confirmation_codes (
id VARCHAR(36) NOT NULL,
user_id VARCHAR(36) NOT NULL,
code VARCHAR(4) NOT NULL,
CONSTRAINT user_confirmation_codes_pk PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES users(id)
code VARCHAR(4) NOT NULL,
expired_at TIMESTAMP NOT NULL,
CONSTRAINT user_confirmation_codes_pk PRIMARY KEY (id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
2 changes: 1 addition & 1 deletion src/application/middlewares/error_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default function errorHandler(
response: Response,
_next: NextFunction,
) {
console.log(error);
console.error(error);

if (request.originalUrl.startsWith('/forms') || request.originalUrl.startsWith('/pages')) {
return response.redirect('/pages/500');
Expand Down
3 changes: 3 additions & 0 deletions src/b3_stock_alerts/AuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,16 @@ export default class AuthService {
}

const code = randomInt(1000, 9999).toString();
const expired_at = new Date();
expired_at.setMinutes(expired_at.getMinutes() + 10);

await this.confirmation_code.sendCode({ email, code });

await this.user_repository.createConfirmationCode({
id: randomUUID(),
user_id: user.id,
code,
expired_at,
});

return {};
Expand Down
3 changes: 2 additions & 1 deletion src/b3_stock_alerts/AuthService.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ describe("AuthService's unit tests", () => {
});

it('saves the code after send it for user email', async () => {
expect.assertions(3);
expect.assertions(4);

const email = faker.internet.email();

Expand All @@ -175,6 +175,7 @@ describe("AuthService's unit tests", () => {
expect(params.id).toBeDefined();
expect(params.user_id).toBe(user.id);
expect(params.code).toBe('1234');
expect(params.expired_at).toBeInstanceOf(Date);
});
});

Expand Down
4 changes: 2 additions & 2 deletions src/b3_stock_alerts/EmailGateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ export default class EmailGateway implements AlertNotification, ConfirmationCode
from: process.env.APPLICATION_EMAIL,
to: params.email,
subject: 'Código de confirmação',
text: `Segue o código de confirmação ${params.code}. Acesse o link ${process.env.SERVER_URL}/pages/confirm-code?email=${params.email}.`,
html: `<p>Segue o código de confirmação ${params.code}.</p><br/><p>Acesse o <a href="${process.env.SERVER_URL}/pages/confirm-code?email=${params.email}">link.</a></p>`,
text: `Segue o código de confirmação ${params.code}. Acesse o link ${process.env.SERVER_URL}/pages/confirm-code?email=${params.email}. O código expira em 10 minutos.`,
html: `<p>Segue o código de confirmação ${params.code}.</p><p>Acesse o <a href="${process.env.SERVER_URL}/pages/confirm-code?email=${params.email}">link.</a></p><p>O código expira em 10 minutos.</p>`,
};

await this.transporter.sendMail(message);
Expand Down
6 changes: 3 additions & 3 deletions src/b3_stock_alerts/EmailGateway.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,16 @@ describe("EmailGateway's unit tests", () => {
expect.assertions(1);

const email = faker.internet.email();
const code = faker.string.alphanumeric()
const code = faker.string.alphanumeric();

await email_gateway.sendCode({ email, code });

expect(transport_mock.sendMail).toHaveBeenCalledWith({
from: 'test@server.com',
to: email,
subject: 'Código de confirmação',
text: `Segue o código de confirmação ${code}. Acesse o link http://localhost:5000/pages/confirm-code?email=${email}.`,
html: `<p>Segue o código de confirmação ${code}.</p><br/><p>Acesse o link http://localhost:5000/pages/confirm-code?email=${email}.</p>`,
text: `Segue o código de confirmação ${code}. Acesse o link http://localhost:5000/pages/confirm-code?email=${email}. O código expira em 10 minutos.`,
html: `<p>Segue o código de confirmação ${code}.</p><p>Acesse o <a href="http://localhost:5000/pages/confirm-code?email=${email}">link.</a></p><p>O código expira em 10 minutos.</p>`,
});
});
});
Expand Down
25 changes: 18 additions & 7 deletions src/b3_stock_alerts/PgUserRepository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import Postgres from '@shared/Postgres';
import { Client } from 'pg';
import ConfirmationCode from './ConfirmationCode';
import { User } from './User';
import { UserConfirmationCode } from './UserConfirmationCode';
import UserRepository from './UserRepository';
Expand Down Expand Up @@ -79,22 +78,34 @@ export default class PgUserRepository implements UserRepository {
}

async createConfirmationCode(confirmation_code: UserConfirmationCode): Promise<void> {
const {
id, user_id, code, expired_at,
} = confirmation_code;
await this.client.query(
'INSERT INTO user_confirmation_codes (id, user_id, code) VALUES ($1, $2, $3)',
[confirmation_code.id, confirmation_code.user_id, confirmation_code.code],
'INSERT INTO user_confirmation_codes (id, user_id, code, expired_at) VALUES ($1, $2, $3, $4)',
[
id,
user_id,
code,
`${expired_at.getFullYear()}-${expired_at.getMonth() + 1}-${expired_at.getDate()} ${expired_at.getHours()}:${expired_at.getMinutes()}:${expired_at.getSeconds()}`,
],
);
}

async getConfirmationCode(email: string, code: string): Promise<ConfirmationCode | null> {
async getConfirmationCode(email: string, code: string): Promise<UserConfirmationCode | null> {
const result = await this.client.query(
'SELECT ucc.id, user_id, code FROM user_confirmation_codes ucc JOIN users ON users.email = $1 WHERE code = $2',
'SELECT ucc.id, user_id, code, expired_at FROM user_confirmation_codes ucc JOIN users ON users.email = $1 WHERE code = $2',
[email, code],
);

if (result.rows[0] === undefined) {
const confirmation_code = result.rows[0];

if (confirmation_code === undefined) {
return null;
}

return result.rows[0];
confirmation_code.expired_at = new Date(confirmation_code.expired_at);

return confirmation_code;
}
}
30 changes: 20 additions & 10 deletions src/b3_stock_alerts/PgUserRepository.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { faker } from '@faker-js/faker/locale/pt_BR';
import Postgres from '@shared/Postgres';
import PgUserRepository from './PgUserRepository';
import { User } from './User';
import { UserConfirmationCode } from './UserConfirmationCode';

const get_client_spy = jest.spyOn(Postgres, 'getClient');

Expand Down Expand Up @@ -174,23 +173,30 @@ describe('PgUserRepository', () => {
it('inserts a new confirmation code', async () => {
expect.assertions(1);

const confirmation_code: UserConfirmationCode = {
const confirmation_code = {
id: faker.string.uuid(),
user_id: faker.string.uuid(),
code: faker.string.numeric(4),
expired_at: new Date('2024-02-13 17:15:00'),
};

await repository.createConfirmationCode(confirmation_code);

expect(query_mock).toHaveBeenCalledWith('INSERT INTO user_confirmation_codes (id, user_id, code) VALUES ($1, $2, $3)', [
confirmation_code.id, confirmation_code.user_id, confirmation_code.code,
]);
expect(query_mock).toHaveBeenCalledWith(
'INSERT INTO user_confirmation_codes (id, user_id, code, expired_at) VALUES ($1, $2, $3, $4)',
[
confirmation_code.id,
confirmation_code.user_id,
confirmation_code.code,
'2024-2-13 17:15:0',
],
);
});
});

describe('PgUserRepository.getConfirmationCode', () => {
it('returns a confirmation code', async () => {
expect.assertions(2);
expect.assertions(5);

const email = faker.internet.email();
const code = faker.string.numeric(4);
Expand All @@ -199,17 +205,21 @@ describe('PgUserRepository', () => {
id: faker.string.uuid(),
user_id: faker.string.uuid(),
code,
expired_at: '2004-10-19 10:23:54',
}

query_mock.mockResolvedValueOnce({ rows: [confirmation_code] });

const result = await repository.getConfirmationCode(email, code);
const result = (await repository.getConfirmationCode(email, code))!;

expect(query_mock).toHaveBeenCalledWith(
'SELECT ucc.id, user_id, code FROM user_confirmation_codes ucc JOIN users ON users.email = $1 WHERE code = $2',
'SELECT ucc.id, user_id, code, expired_at FROM user_confirmation_codes ucc JOIN users ON users.email = $1 WHERE code = $2',
[email, code],
);
expect(result).toEqual(confirmation_code);
expect(result.id).toEqual(confirmation_code.id);
expect(result.user_id).toEqual(confirmation_code.user_id);
expect(result.code).toEqual(confirmation_code.code);
expect(result.expired_at.getTime()).toEqual(new Date('2004-10-19 10:23:54').getTime());
});

it("returns NULL if confirmation code doesn't exit", async () => {
Expand All @@ -223,7 +233,7 @@ describe('PgUserRepository', () => {
const result = await repository.getConfirmationCode(email, code);

expect(query_mock).toHaveBeenCalledWith(
'SELECT ucc.id, user_id, code FROM user_confirmation_codes ucc JOIN users ON users.email = $1 WHERE code = $2',
'SELECT ucc.id, user_id, code, expired_at FROM user_confirmation_codes ucc JOIN users ON users.email = $1 WHERE code = $2',
[email, code],
);

Expand Down
1 change: 1 addition & 0 deletions src/b3_stock_alerts/UserConfirmationCode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export type UserConfirmationCode = {
id: string;
user_id: string;
code: string;
expired_at: Date;
}
3 changes: 1 addition & 2 deletions src/b3_stock_alerts/UserRepository.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import ConfirmationCode from './ConfirmationCode';
import { User } from './User';
import { UserConfirmationCode } from './UserConfirmationCode';

Expand All @@ -10,5 +9,5 @@ export default interface UserRepository {
deleteUser(user_id: string): Promise<void>;
getUserByEmail(email: string): Promise<User | null>;
createConfirmationCode(confirmation_code: UserConfirmationCode): Promise<void>;
getConfirmationCode(email: string, code: string): Promise<ConfirmationCode | null>;
getConfirmationCode(email: string, code: string): Promise<UserConfirmationCode | null>;
}

0 comments on commit d1eb450

Please sign in to comment.