From ff6e9547201e1e373c3b39ef63f11d61a47029b1 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:27:15 -0300 Subject: [PATCH 01/13] chore: Criar tabela AuthCodes --- .vscode/settings.json | 2 +- .../migration.sql | 15 +++++++++++++++ prisma/schema.prisma | 19 ++++++++++++++++--- 3 files changed, 32 insertions(+), 4 deletions(-) create mode 100644 prisma/migrations/20250824233512_criar_tabela_auth_codes/migration.sql diff --git a/.vscode/settings.json b/.vscode/settings.json index 9623b34..f361f55 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,5 @@ "editor.defaultFormatter": "Prisma.prisma", "editor.formatOnSave": true }, - "cSpell.words": ["bitnami", "zipcode"] + "cSpell.words": ["bitnami", "fkey", "pkey", "zipcode"] } diff --git a/prisma/migrations/20250824233512_criar_tabela_auth_codes/migration.sql b/prisma/migrations/20250824233512_criar_tabela_auth_codes/migration.sql new file mode 100644 index 0000000..cea7cce --- /dev/null +++ b/prisma/migrations/20250824233512_criar_tabela_auth_codes/migration.sql @@ -0,0 +1,15 @@ +-- CreateTable +CREATE TABLE "auth_codes" ( + "id" TEXT NOT NULL, + "code" INTEGER NOT NULL, + "user_id" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "auth_codes_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "auth_codes_code_key" ON "auth_codes"("code"); + +-- AddForeignKey +ALTER TABLE "auth_codes" ADD CONSTRAINT "auth_codes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 6849d5f..8bf2a2a 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -7,6 +7,18 @@ datasource db { url = env("DATABASE_URL") } +model AuthCodes { + id String @id @default(cuid()) + code Int @unique + + user User @relation(fields: [userId], references: [id]) + userId String @map("user_id") + + createdAt DateTime @default(now()) @map("created_at") + + @@map("auth_codes") +} + model Address { id String @id @default(cuid()) number Int @@ -45,9 +57,10 @@ model User { createdAt DateTime @default(now()) @map("created_at") updatedAt DateTime @updatedAt @map("updated_at") - Address Address? - Order Order[] - Cart Cart? + AuthCodes AuthCodes[] + Address Address? + Order Order[] + Cart Cart? @@map("users") } From ca65c6223700541074e7803e3b1687171f98feb8 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:34:12 -0300 Subject: [PATCH 02/13] =?UTF-8?q?feat:=20Criar=20reposit=C3=B3rios=20de=20?= =?UTF-8?q?AuthCodes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/repositories/auth-codes-repository.ts | 9 +++ .../prisma/prisma-auth-codes-repository.ts | 58 +++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 src/repositories/auth-codes-repository.ts create mode 100644 src/repositories/prisma/prisma-auth-codes-repository.ts diff --git a/src/repositories/auth-codes-repository.ts b/src/repositories/auth-codes-repository.ts new file mode 100644 index 0000000..bb20a5d --- /dev/null +++ b/src/repositories/auth-codes-repository.ts @@ -0,0 +1,9 @@ +import type { AuthCodes, Prisma } from '@prisma/client' + +export interface AuthCodesRepository { + create(data: Prisma.AuthCodesCreateInput): Promise + list(): Promise + getByCodeAndUser(userId: string, code: number): Promise + getLastCodeByUser(userId: string): Promise + deleteByCode(code: number): Promise +} diff --git a/src/repositories/prisma/prisma-auth-codes-repository.ts b/src/repositories/prisma/prisma-auth-codes-repository.ts new file mode 100644 index 0000000..6b6126f --- /dev/null +++ b/src/repositories/prisma/prisma-auth-codes-repository.ts @@ -0,0 +1,58 @@ +import { prisma } from '@/lib/prisma' +import type { Prisma } from '@prisma/client' + +export class PrismaAuthCodesRepository { + async create(data: Prisma.AuthCodesCreateInput) { + const authCode = await prisma.authCodes.create({ + data, + }) + + return authCode + } + + async list() { + const authCodes = await prisma.authCodes.findMany({ + orderBy: { + createdAt: 'desc', + }, + }) + + return authCodes + } + + async getByCodeAndUser(userId: string, code: number) { + const authCode = await prisma.authCodes.findFirst({ + where: { + userId, + AND: { + code, + }, + }, + }) + + return authCode + } + + async getLastCodeByUser(userId: string) { + const authCode = await prisma.authCodes.findFirst({ + where: { + userId, + }, + orderBy: { + createdAt: 'desc', + }, + }) + + return authCode + } + + async deleteByCode(code: number) { + const authCode = await prisma.authCodes.delete({ + where: { + code, + }, + }) + + return authCode + } +} From a32cd40e22d83f2de9c0a51f0389a2b4b675fe31 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:37:45 -0300 Subject: [PATCH 03/13] build: Adicionar libs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dayjs para formatar datas - nanoid para gerar códigos - nodemailer para envio de emails - volta para versionar o node no projeto --- package-lock.json | 47 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 7 +++++++ 2 files changed, 54 insertions(+) diff --git a/package-lock.json b/package-lock.json index 00f0733..516f485 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,11 @@ "@fastify/jwt": "^9.1.0", "@prisma/client": "^6.4.1", "bcryptjs": "^3.0.2", + "dayjs": "^1.11.13", "dotenv": "^16.5.0", "fastify": "^5.3.2", + "nanoid": "^5.1.5", + "nodemailer": "^7.0.5", "zod": "^3.24.2" }, "devDependencies": { @@ -25,6 +28,7 @@ "@commitlint/types": "^19.8.0", "@faker-js/faker": "^9.8.0", "@types/node": "^22.13.9", + "@types/nodemailer": "^6.4.17", "conventional-changelog-atom": "^5.0.0", "prisma": "^6.4.1", "tsup": "^8.4.0", @@ -1432,6 +1436,16 @@ "undici-types": "~6.20.0" } }, + "node_modules/@types/nodemailer": { + "version": "6.4.17", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.17.tgz", + "integrity": "sha512-I9CCaIp6DTldEg7vyUTZi8+9Vo0hi1/T8gv3C89yk1rSAAzoKQ8H8ki/jBYJSFoH/BisgLP8tkZMlQ91CIquww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/abstract-logging": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz", @@ -1916,6 +1930,12 @@ "node": ">= 8" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2995,6 +3015,33 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nanoid": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", + "integrity": "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/nodemailer": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.5.tgz", + "integrity": "sha512-nsrh2lO3j4GkLLXoeEksAMgAOqxOv6QumNRVQTJwKH4nuiww6iC2y7GyANs9kRAxCexg3+lTWM3PZ91iLlVjfg==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", diff --git a/package.json b/package.json index bd8d37e..dd1ccf8 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,9 @@ "engines": { "node": "20" }, + "volta": { + "node": "20.19.4" + }, "prisma": { "seed": "tsx prisma/seed.ts" }, @@ -27,6 +30,7 @@ "@commitlint/types": "^19.8.0", "@faker-js/faker": "^9.8.0", "@types/node": "^22.13.9", + "@types/nodemailer": "^6.4.17", "conventional-changelog-atom": "^5.0.0", "prisma": "^6.4.1", "tsup": "^8.4.0", @@ -39,8 +43,11 @@ "@fastify/jwt": "^9.1.0", "@prisma/client": "^6.4.1", "bcryptjs": "^3.0.2", + "dayjs": "^1.11.13", "dotenv": "^16.5.0", "fastify": "^5.3.2", + "nanoid": "^5.1.5", + "nodemailer": "^7.0.5", "zod": "^3.24.2" } } From 97c864fb0259ad1d8508f765a5767b3eb8e0e763 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:38:54 -0300 Subject: [PATCH 04/13] =?UTF-8?q?chore:=20Adicionar=20vari=C3=A1veis=20de?= =?UTF-8?q?=20ambiente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit variáveis para configuração do gmail e variável da url banco --- src/env/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/env/index.ts b/src/env/index.ts index 3762a7a..ce824cb 100644 --- a/src/env/index.ts +++ b/src/env/index.ts @@ -2,9 +2,12 @@ import 'dotenv/config' import { z } from 'zod' const envSchema = z.object({ + DATABASE_URL: z.string(), PORT: z.coerce.number().min(1000).max(9999).default(3333), NODE_ENV: z.enum(['dev', 'test', 'prod']).default('dev'), JWT_SECRET: z.string(), + GMAIL_USER: z.string().email(), + GMAIL_PASS: z.string(), }) const _env = envSchema.safeParse(process.env) From df6705b98cee7f3096874aa8f40476b51ee62f61 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:40:06 -0300 Subject: [PATCH 05/13] =?UTF-8?q?feat:=20Criar=20arquivo=20de=20configura?= =?UTF-8?q?=C3=A7=C3=A3o=20do=20nanoid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/nanoid.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/lib/nanoid.ts diff --git a/src/lib/nanoid.ts b/src/lib/nanoid.ts new file mode 100644 index 0000000..5bdb3ee --- /dev/null +++ b/src/lib/nanoid.ts @@ -0,0 +1,17 @@ +import { customAlphabet } from 'nanoid' + +const defaultAlphabet = '123456789' +const defaultLength = 6 + +interface generateAuthCodeParams { + alphabet?: string + length?: number +} + +export function generateAuthCode({ + alphabet = defaultAlphabet, + length = defaultLength, +}: generateAuthCodeParams) { + const code = customAlphabet(alphabet, length)() + return Number.parseInt(code) +} From 087a3bd52fc88f6e6e3734c39aa80e12812a7321 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:40:24 -0300 Subject: [PATCH 06/13] =?UTF-8?q?feat:=20Criar=20arquivo=20de=20configura?= =?UTF-8?q?=C3=A7=C3=A3o=20do=20nodemailer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lib/nodemailer.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/lib/nodemailer.ts diff --git a/src/lib/nodemailer.ts b/src/lib/nodemailer.ts new file mode 100644 index 0000000..935addb --- /dev/null +++ b/src/lib/nodemailer.ts @@ -0,0 +1,28 @@ +import { env } from '@/env' +import { type SendMailOptions, createTransport } from 'nodemailer' + +const transporter = createTransport({ + service: 'gmail', + port: 587, + secure: false, + auth: { + user: env.GMAIL_USER, + pass: env.GMAIL_PASS, + }, +}) + +transporter.verify() + +export async function sendMail(options: SendMailOptions) { + const mailOptions = { + from: `Pizza Stars <${env.GMAIL_USER}>`, + to: options.to, + subject: options.subject, + text: options.text, + html: options.html, + ...options, + } + + const email = await transporter.sendMail(mailOptions) + return email +} From ebc0c87102cdac6098648323f9b69030d1422d82 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:41:02 -0300 Subject: [PATCH 07/13] feat: Criar classes de erro para o reset de senha --- src/services/errors/code-generated-recently-error.ts | 5 +++++ src/services/errors/unable-to-send-email-error.ts | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 src/services/errors/code-generated-recently-error.ts create mode 100644 src/services/errors/unable-to-send-email-error.ts diff --git a/src/services/errors/code-generated-recently-error.ts b/src/services/errors/code-generated-recently-error.ts new file mode 100644 index 0000000..60a771c --- /dev/null +++ b/src/services/errors/code-generated-recently-error.ts @@ -0,0 +1,5 @@ +export class CodeGeneratedRecentlyError extends Error { + constructor() { + super('Code generated recently.') + } +} diff --git a/src/services/errors/unable-to-send-email-error.ts b/src/services/errors/unable-to-send-email-error.ts new file mode 100644 index 0000000..e520879 --- /dev/null +++ b/src/services/errors/unable-to-send-email-error.ts @@ -0,0 +1,5 @@ +export class UnableToSendEmailError extends Error { + constructor() { + super('Unable to send the email') + } +} From 9f653e0da142e874d120183de3f3cf9b3be71a2d Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:42:43 -0300 Subject: [PATCH 08/13] =?UTF-8?q?feat:=20Implementar=20o=20envio=20do=20c?= =?UTF-8?q?=C3=B3digo=20de=20senha=20por=20email?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/services/users/send-code.ts | 157 ++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 src/services/users/send-code.ts diff --git a/src/services/users/send-code.ts b/src/services/users/send-code.ts new file mode 100644 index 0000000..106d82e --- /dev/null +++ b/src/services/users/send-code.ts @@ -0,0 +1,157 @@ +import type { AuthCodesRepository } from '@/repositories/auth-codes-repository' +import type { UsersRepository } from '@/repositories/users-repository' +import { CodeGeneratedRecentlyError } from '../errors/code-generated-recently-error' +import { UserNotExistsError } from '../errors/user-not-exists-error' + +import { generateAuthCode } from '@/lib/nanoid' +import { sendMail } from '@/lib/nodemailer' +import dayjs from 'dayjs' +import { UnableToSendEmailError } from '../errors/unable-to-send-email-error' + +interface SendCodeServiceRequest { + email: string +} + +export class SendCodeService { + constructor( + private usersRepository: UsersRepository, + private authCodesRepository: AuthCodesRepository, + ) {} + + async execute({ email }: SendCodeServiceRequest) { + const user = await this.usersRepository.findByEmail(email) + + if (!user) { + throw new UserNotExistsError() + } + + const lastCodeFromUser = await this.authCodesRepository.getLastCodeByUser( + user.id, + ) + + const date = dayjs().diff( + lastCodeFromUser?.createdAt || null, + 'milliseconds', + ) + + const dateInMinutes = date / 1000 / 60 + const isCodeGeneratedInFiveMinutes = dateInMinutes <= 5 + + if (isCodeGeneratedInFiveMinutes) { + throw new CodeGeneratedRecentlyError() + } + + const code = generateAuthCode({}) + + await this.authCodesRepository.create({ + code, + user: { + connect: { + id: user.id, + }, + }, + }) + + const emailSent = await sendMail({ + to: email, + subject: `Seu código Pizza Stars é ${code}`, + text: `Aqui está seu código: ${code}`, + // TODO: improve this email html, put it on a the email folder + html: ` + + + + + + Password Reset + + + +
+
+

🍕 Pizza Stars

+

Password Reset Request

+
+
+

Reset Your Password

+

We received a request to reset your password. Use the code below to proceed:

+ +
+ ${code} +
+ +
+ ⚠️ This code will expire in 30 minutes for security reasons. +
+ +

If you didn't request this password reset, please ignore this email or contact our support team.

+
+ +
+ + + `, + priority: 'high', + }) + + if (!emailSent.messageId) { + throw new UnableToSendEmailError() + } + + return { messageId: emailSent.messageId } + } +} From 18e92d2687993a55add2e381654e79e4cc66d27d Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:44:02 -0300 Subject: [PATCH 09/13] feat: Implementar o reset de senha --- src/services/users/reset-password.ts | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/services/users/reset-password.ts diff --git a/src/services/users/reset-password.ts b/src/services/users/reset-password.ts new file mode 100644 index 0000000..3aa212a --- /dev/null +++ b/src/services/users/reset-password.ts @@ -0,0 +1,52 @@ +import type { AuthCodesRepository } from '@/repositories/auth-codes-repository' +import type { UsersRepository } from '@/repositories/users-repository' +import { InvalidCredentialsError } from '../errors/invalid-credentials-error' +import { UserNotExistsError } from '../errors/user-not-exists-error' + +import { hash } from 'bcryptjs' +import dayjs from 'dayjs' + +interface ResetPasswordServiceRequest { + code: number + userId: string + password: string +} + +export class ResetPasswordService { + constructor( + private usersRepository: UsersRepository, + private authCodes: AuthCodesRepository, + ) {} + + async execute({ code, userId, password }: ResetPasswordServiceRequest) { + const isValidCode = await this.authCodes.getByCodeAndUser(userId, code) + + if (!isValidCode) { + throw new InvalidCredentialsError() + } + + const date = dayjs().diff(isValidCode.createdAt, 'milliseconds') + const dateInHours = date / 1000 / 60 / 60 + const isCodeGeneratedMoreThanFiveHours = dateInHours >= 5 + + if (isCodeGeneratedMoreThanFiveHours) { + throw new InvalidCredentialsError() + } + + const user = await this.usersRepository.findById(userId) + + if (!user) { + throw new UserNotExistsError() + } + + password = await hash(password, 10) + + const alteredPassword = await this.usersRepository.updateById(user.id, { + password, + }) + + await this.authCodes.deleteByCode(code) + + return { alteredPassword } + } +} From 01abfe3de0aa9a736c0c0360a25821ae3e0b2246 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:45:11 -0300 Subject: [PATCH 10/13] =?UTF-8?q?feat:=20Implementar=20factories=20para=20?= =?UTF-8?q?reset=20de=20senha=20e=20envio=20de=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../users/make-reset-password-service.ts | 15 +++++++++++++++ .../factories/users/make-send-code-service.ts | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 src/services/factories/users/make-reset-password-service.ts create mode 100644 src/services/factories/users/make-send-code-service.ts diff --git a/src/services/factories/users/make-reset-password-service.ts b/src/services/factories/users/make-reset-password-service.ts new file mode 100644 index 0000000..af9fbe9 --- /dev/null +++ b/src/services/factories/users/make-reset-password-service.ts @@ -0,0 +1,15 @@ +import { PrismaAuthCodesRepository } from '@/repositories/prisma/prisma-auth-codes-repository' +import { PrismaUsersRepository } from '@/repositories/prisma/prisma-users-repository' +import { ResetPasswordService } from '@/services/users/reset-password' + +export function makeResetPasswordService() { + const usersRepository = new PrismaUsersRepository() + const authCodesRepository = new PrismaAuthCodesRepository() + + const resetPasswordService = new ResetPasswordService( + usersRepository, + authCodesRepository, + ) + + return resetPasswordService +} diff --git a/src/services/factories/users/make-send-code-service.ts b/src/services/factories/users/make-send-code-service.ts new file mode 100644 index 0000000..1bc9d09 --- /dev/null +++ b/src/services/factories/users/make-send-code-service.ts @@ -0,0 +1,15 @@ +import { PrismaAuthCodesRepository } from '@/repositories/prisma/prisma-auth-codes-repository' +import { PrismaUsersRepository } from '@/repositories/prisma/prisma-users-repository' +import { SendCodeService } from '@/services/users/send-code' + +export function makeSendCodeService() { + const usersRepository = new PrismaUsersRepository() + const authCodesRepository = new PrismaAuthCodesRepository() + + const sendCodeService = new SendCodeService( + usersRepository, + authCodesRepository, + ) + + return sendCodeService +} From d9f827c1c4c38634069a380f91b044bbfff1d74c Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:46:43 -0300 Subject: [PATCH 11/13] =?UTF-8?q?feat:=20Implementar=20controller=20de=20e?= =?UTF-8?q?nvio=20de=20c=C3=B3digo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/http/controllers/users/sendCode.ts | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/http/controllers/users/sendCode.ts diff --git a/src/http/controllers/users/sendCode.ts b/src/http/controllers/users/sendCode.ts new file mode 100644 index 0000000..e89133e --- /dev/null +++ b/src/http/controllers/users/sendCode.ts @@ -0,0 +1,36 @@ +import type { FastifyReply, FastifyRequest } from 'fastify' +import { z } from 'zod' + +import { CodeGeneratedRecentlyError } from '@/services/errors/code-generated-recently-error' +import { UnableToSendEmailError } from '@/services/errors/unable-to-send-email-error' +import { UserNotExistsError } from '@/services/errors/user-not-exists-error' +import { makeSendCodeService } from '@/services/factories/users/make-send-code-service' + +export async function sendCode(request: FastifyRequest, reply: FastifyReply) { + const sendCodeBodySchema = z.object({ + email: z.string().email(), + }) + + const { email } = sendCodeBodySchema.parse(request.body) + + try { + const sendCodeService = makeSendCodeService() + const emailSent = await sendCodeService.execute({ email }) + + return reply.status(200).send(emailSent) + } catch (err) { + if (err instanceof UserNotExistsError) { + return reply.status(404).send({ message: err.message }) + } + + if (err instanceof CodeGeneratedRecentlyError) { + return reply.status(429).send({ message: err.message }) + } + + if (err instanceof UnableToSendEmailError) { + return reply.status(503).send({ message: err.message }) + } + + throw err + } +} From 4641ea433c39cca010dd087fede5aabb088aeeda Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:47:01 -0300 Subject: [PATCH 12/13] feat: Implementar controller de reset de senha --- src/http/controllers/users/resetPassword.ts | 32 +++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/http/controllers/users/resetPassword.ts diff --git a/src/http/controllers/users/resetPassword.ts b/src/http/controllers/users/resetPassword.ts new file mode 100644 index 0000000..dc86d4a --- /dev/null +++ b/src/http/controllers/users/resetPassword.ts @@ -0,0 +1,32 @@ +import type { FastifyReply, FastifyRequest } from 'fastify' +import { z } from 'zod' + +import { InvalidCredentialsError } from '@/services/errors/invalid-credentials-error' +import { makeResetPasswordService } from '@/services/factories/users/make-reset-password-service' + +export async function resetPassword( + request: FastifyRequest, + reply: FastifyReply, +) { + const resetPasswordBodySchema = z.object({ + code: z.number().int().min(100000).max(999999), + password: z.string().min(6), + userId: z.string().cuid(), + }) + + const { code, password, userId } = resetPasswordBodySchema.parse(request.body) + + try { + const resetPasswordService = makeResetPasswordService() + + await resetPasswordService.execute({ code, password, userId }) + } catch (err) { + if (err instanceof InvalidCredentialsError) { + return reply.status(404).send({ message: err.message }) + } + + throw err + } + + return reply.status(204).send() +} From 33575a2e970f813722dba28b812cfaf06488ed85 Mon Sep 17 00:00:00 2001 From: lucaslinyker Date: Sun, 24 Aug 2025 23:47:58 -0300 Subject: [PATCH 13/13] =?UTF-8?q?feat:=20Adicionar=20rotas=20para=20altera?= =?UTF-8?q?=C3=A7=C3=A3o=20de=20senha?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - envio de código por email - reset de senha pelo código --- src/http/controllers/users/routes.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/http/controllers/users/routes.ts b/src/http/controllers/users/routes.ts index c3a8892..dcba088 100644 --- a/src/http/controllers/users/routes.ts +++ b/src/http/controllers/users/routes.ts @@ -10,6 +10,8 @@ import { logout } from './logout' import { refresh } from './refresh' import { register } from './register' import { remove } from './remove' +import { resetPassword } from './resetPassword' +import { sendCode } from './sendCode' import { update } from './update' export async function usersRoutes(app: FastifyInstance) { @@ -22,6 +24,8 @@ export async function usersRoutes(app: FastifyInstance) { app.post('/auth/login', authenticate) app.patch('/auth/refresh', refresh) app.delete('/auth/logout', { onRequest: [verifyJWT] }, logout) + app.post('/auth/send-code', sendCode) + app.post('/auth/reset-password', resetPassword) app.get('/users', { onRequest: [verifyUserRole('ADMIN')] }, list) }