Skip to content

Commit c46c8c8

Browse files
authored
Merge pull request #28 from MLKP1/dev
feat: Implementar reset de senha
2 parents 5ef78c8 + 33575a2 commit c46c8c8

File tree

19 files changed

+522
-4
lines changed

19 files changed

+522
-4
lines changed

.vscode/settings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@
1010
"editor.defaultFormatter": "Prisma.prisma",
1111
"editor.formatOnSave": true
1212
},
13-
"cSpell.words": ["bitnami", "zipcode"]
13+
"cSpell.words": ["bitnami", "fkey", "pkey", "zipcode"]
1414
}

package-lock.json

Lines changed: 47 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
"engines": {
77
"node": "20"
88
},
9+
"volta": {
10+
"node": "20.19.4"
11+
},
912
"prisma": {
1013
"seed": "tsx prisma/seed.ts"
1114
},
@@ -27,6 +30,7 @@
2730
"@commitlint/types": "^19.8.0",
2831
"@faker-js/faker": "^9.8.0",
2932
"@types/node": "^22.13.9",
33+
"@types/nodemailer": "^6.4.17",
3034
"conventional-changelog-atom": "^5.0.0",
3135
"prisma": "^6.4.1",
3236
"tsup": "^8.4.0",
@@ -39,8 +43,11 @@
3943
"@fastify/jwt": "^9.1.0",
4044
"@prisma/client": "^6.4.1",
4145
"bcryptjs": "^3.0.2",
46+
"dayjs": "^1.11.13",
4247
"dotenv": "^16.5.0",
4348
"fastify": "^5.3.2",
49+
"nanoid": "^5.1.5",
50+
"nodemailer": "^7.0.5",
4451
"zod": "^3.24.2"
4552
}
4653
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- CreateTable
2+
CREATE TABLE "auth_codes" (
3+
"id" TEXT NOT NULL,
4+
"code" INTEGER NOT NULL,
5+
"user_id" TEXT NOT NULL,
6+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
7+
8+
CONSTRAINT "auth_codes_pkey" PRIMARY KEY ("id")
9+
);
10+
11+
-- CreateIndex
12+
CREATE UNIQUE INDEX "auth_codes_code_key" ON "auth_codes"("code");
13+
14+
-- AddForeignKey
15+
ALTER TABLE "auth_codes" ADD CONSTRAINT "auth_codes_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

prisma/schema.prisma

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ datasource db {
77
url = env("DATABASE_URL")
88
}
99

10+
model AuthCodes {
11+
id String @id @default(cuid())
12+
code Int @unique
13+
14+
user User @relation(fields: [userId], references: [id])
15+
userId String @map("user_id")
16+
17+
createdAt DateTime @default(now()) @map("created_at")
18+
19+
@@map("auth_codes")
20+
}
21+
1022
model Address {
1123
id String @id @default(cuid())
1224
number Int
@@ -45,9 +57,10 @@ model User {
4557
createdAt DateTime @default(now()) @map("created_at")
4658
updatedAt DateTime @updatedAt @map("updated_at")
4759
48-
Address Address?
49-
Order Order[]
50-
Cart Cart?
60+
AuthCodes AuthCodes[]
61+
Address Address?
62+
Order Order[]
63+
Cart Cart?
5164
5265
@@map("users")
5366
}

src/env/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import 'dotenv/config'
22
import { z } from 'zod'
33

44
const envSchema = z.object({
5+
DATABASE_URL: z.string(),
56
PORT: z.coerce.number().min(1000).max(9999).default(3333),
67
NODE_ENV: z.enum(['dev', 'test', 'prod']).default('dev'),
78
JWT_SECRET: z.string(),
9+
GMAIL_USER: z.string().email(),
10+
GMAIL_PASS: z.string(),
811
})
912

1013
const _env = envSchema.safeParse(process.env)
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { FastifyReply, FastifyRequest } from 'fastify'
2+
import { z } from 'zod'
3+
4+
import { InvalidCredentialsError } from '@/services/errors/invalid-credentials-error'
5+
import { makeResetPasswordService } from '@/services/factories/users/make-reset-password-service'
6+
7+
export async function resetPassword(
8+
request: FastifyRequest,
9+
reply: FastifyReply,
10+
) {
11+
const resetPasswordBodySchema = z.object({
12+
code: z.number().int().min(100000).max(999999),
13+
password: z.string().min(6),
14+
userId: z.string().cuid(),
15+
})
16+
17+
const { code, password, userId } = resetPasswordBodySchema.parse(request.body)
18+
19+
try {
20+
const resetPasswordService = makeResetPasswordService()
21+
22+
await resetPasswordService.execute({ code, password, userId })
23+
} catch (err) {
24+
if (err instanceof InvalidCredentialsError) {
25+
return reply.status(404).send({ message: err.message })
26+
}
27+
28+
throw err
29+
}
30+
31+
return reply.status(204).send()
32+
}

src/http/controllers/users/routes.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import { logout } from './logout'
1010
import { refresh } from './refresh'
1111
import { register } from './register'
1212
import { remove } from './remove'
13+
import { resetPassword } from './resetPassword'
14+
import { sendCode } from './sendCode'
1315
import { update } from './update'
1416

1517
export async function usersRoutes(app: FastifyInstance) {
@@ -22,6 +24,8 @@ export async function usersRoutes(app: FastifyInstance) {
2224
app.post('/auth/login', authenticate)
2325
app.patch('/auth/refresh', refresh)
2426
app.delete('/auth/logout', { onRequest: [verifyJWT] }, logout)
27+
app.post('/auth/send-code', sendCode)
28+
app.post('/auth/reset-password', resetPassword)
2529

2630
app.get('/users', { onRequest: [verifyUserRole('ADMIN')] }, list)
2731
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import type { FastifyReply, FastifyRequest } from 'fastify'
2+
import { z } from 'zod'
3+
4+
import { CodeGeneratedRecentlyError } from '@/services/errors/code-generated-recently-error'
5+
import { UnableToSendEmailError } from '@/services/errors/unable-to-send-email-error'
6+
import { UserNotExistsError } from '@/services/errors/user-not-exists-error'
7+
import { makeSendCodeService } from '@/services/factories/users/make-send-code-service'
8+
9+
export async function sendCode(request: FastifyRequest, reply: FastifyReply) {
10+
const sendCodeBodySchema = z.object({
11+
email: z.string().email(),
12+
})
13+
14+
const { email } = sendCodeBodySchema.parse(request.body)
15+
16+
try {
17+
const sendCodeService = makeSendCodeService()
18+
const emailSent = await sendCodeService.execute({ email })
19+
20+
return reply.status(200).send(emailSent)
21+
} catch (err) {
22+
if (err instanceof UserNotExistsError) {
23+
return reply.status(404).send({ message: err.message })
24+
}
25+
26+
if (err instanceof CodeGeneratedRecentlyError) {
27+
return reply.status(429).send({ message: err.message })
28+
}
29+
30+
if (err instanceof UnableToSendEmailError) {
31+
return reply.status(503).send({ message: err.message })
32+
}
33+
34+
throw err
35+
}
36+
}

src/lib/nanoid.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { customAlphabet } from 'nanoid'
2+
3+
const defaultAlphabet = '123456789'
4+
const defaultLength = 6
5+
6+
interface generateAuthCodeParams {
7+
alphabet?: string
8+
length?: number
9+
}
10+
11+
export function generateAuthCode({
12+
alphabet = defaultAlphabet,
13+
length = defaultLength,
14+
}: generateAuthCodeParams) {
15+
const code = customAlphabet(alphabet, length)()
16+
return Number.parseInt(code)
17+
}

0 commit comments

Comments
 (0)