Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions models/password.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import bcryptjs from "bcryptjs";

async function hash(password) {
const rounds = getNumberOfRounds();
return await bcryptjs.hash(password, rounds);
}

function getNumberOfRounds() {
return process.env.NODE_ENV === "production" ? 14 : 1;
}

async function compare(providedPassword, storedHashedPassword) {
return await bcryptjs.compare(providedPassword, storedHashedPassword);
}

const password = {
hash,
compare,
};

export default password;
126 changes: 90 additions & 36 deletions models/user.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import database from "infra/database.js";
import { NotFoundError, ValidationError } from "infra/errors.js";
import password from "models/password.js";

async function findOneByUsername(username) {
const userFound = await runSelectQuery(username);
Expand Down Expand Up @@ -34,72 +35,125 @@ async function findOneByUsername(username) {
async function create(userInputValues) {
await validateUniqueUsername(userInputValues.username);
await validateUniqueEmail(userInputValues.email);
await hashPasswordInObject(userInputValues);

const newUser = await runInsertUserQuery(userInputValues);
return newUser;

async function validateUniqueEmail(email) {
async function runInsertUserQuery(userInputValues) {
const results = await database.query({
text: `
SELECT
email
FROM
users
WHERE
LOWER(email) = LOWER($1)
INSERT INTO
users (username, email, password)
VALUES ($1, $2, $3)
RETURNING
*
;`,
values: [email],
values: [
userInputValues.username,
userInputValues.email,
userInputValues.password,
],
});
if (results.rowCount > 0) {
throw new ValidationError({
message: "O email informado já está sendo utilizado.",
action: "Utilize outro email para realizar o cadastro.",
});
}
return results.rows[0];
}
}

async function update(username, userInputValues) {
const currentUser = await findOneByUsername(username);

if ("username" in userInputValues) {
await validateUniqueUsername(userInputValues.username);
}

async function validateUniqueUsername(username) {
if ("email" in userInputValues) {
await validateUniqueEmail(userInputValues.email);
}

if ("password" in userInputValues) {
await hashPasswordInObject(userInputValues);
}

const userWithNewValues = { ...currentUser, ...userInputValues };

const updatedUser = await runUpdateUserQuery(userWithNewValues);
return updatedUser;

async function runUpdateUserQuery(userWithNewValues) {
const results = await database.query({
text: `
UPDATE
users
SET
username = $2,
email = $3,
password = $4,
updated_at = timezone('utc', now())
WHERE
id = $1
RETURNING
*
`,
values: [
userWithNewValues.id,
userWithNewValues.username,
userWithNewValues.email,
userWithNewValues.password,
],
});

return results.rows[0];
}
}
async function validateUniqueUsername(username) {
const results = await database.query({
text: `
SELECT
username
FROM
users
WHERE
LOWER(username) = LOWER($1)
;`,
values: [username],
values: [username],
});
if (results.rowCount > 0) {
throw new ValidationError({
message: "O username informado já está sendo utilizado.",
action: "Utilize outro username para realizar esta operação.",
});
if (results.rowCount > 0) {
throw new ValidationError({
message: "O username informado já está sendo utilizado.",
action: "Utilize outro username para realizar o cadastro.",
});
}
}
}

async function runInsertUserQuery(userInputValues) {
const results = await database.query({
text: `
INSERT INTO
users (username, email, password)
VALUES ($1, $2, $3)
RETURNING
*
async function validateUniqueEmail(email) {
const results = await database.query({
text: `
SELECT
email
FROM
users
WHERE
LOWER(email) = LOWER($1)
;`,
values: [
userInputValues.username,
userInputValues.email,
userInputValues.password,
],
values: [email],
});
if (results.rowCount > 0) {
throw new ValidationError({
message: "O email informado já está sendo utilizado.",
action: "Utilize outro email para realizar esta operação.",
});
return results.rows[0];
}
}

async function hashPasswordInObject(userInputValues) {
const hashedPassword = await password.hash(userInputValues.password);
userInputValues.password = hashedPassword;
}

const user = {
create,
findOneByUsername,
update,
};

export default user;
10 changes: 10 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"license": "MIT",
"dependencies": {
"async-retry": "1.3.3",
"bcryptjs": "3.0.2",
"dotenv": "16.4.5",
"dotenv-expand": "11.0.6",
"next": "14.2.5",
Expand Down
8 changes: 8 additions & 0 deletions pages/api/v1/users/[username]/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { createRouter } from "next-connect";
const router = createRouter();

router.get(getHandler);
router.patch(patchHandler);

export default router.handler(controller.errorHandlers);

Expand All @@ -13,3 +14,10 @@ async function getHandler(request, response) {
const userFound = await user.findOneByUsername(username);
return response.status(200).json(userFound);
}
async function patchHandler(request, response) {
const username = request.query.username;
const userInputValues = request.body;

const updatedUser = await user.update(username, userInputValues);
return response.status(200).json(updatedUser);
}
4 changes: 2 additions & 2 deletions tests/integration/api/v1/users/[username]/get.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ describe("GET /api/v1/users/[username]", () => {
id: response2Body.id,
username: "MesmoCase",
email: "mesmo.case@example.com",
password: "password123",
password: response2Body.password,
created_at: response2Body.created_at,
updated_at: response2Body.updated_at,
});
Expand Down Expand Up @@ -73,7 +73,7 @@ describe("GET /api/v1/users/[username]", () => {
id: response2Body.id,
username: "CaseDiferente",
email: "case.diferente@example.com",
password: "password123",
password: response2Body.password,
created_at: response2Body.created_at,
updated_at: response2Body.updated_at,
});
Expand Down
Loading