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 = getNumbersOfRounds();
return await bcryptjs.hash(password, rounds);
}

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

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

const password = {
hash,
compare,
};

export default password;
150 changes: 102 additions & 48 deletions models/user.js
Original file line number Diff line number Diff line change
@@ -1,54 +1,43 @@
import database from "infra/database.js";
import password from "models/password.js";
import { ValidationError, NotFoundError } from "infra/errors.js";

async function create(userInputValues) {
await validateUniqueEmail(userInputValues.email);
await validateUniqueUsername(userInputValues.username);

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

async function validateUniqueEmail(email) {
const results = await database.query({
text: `
SELECT
email
FROM
users
WHERE
LOWER(email) = LOWER($1)
;`,
values: [email],
});

if (results.rowCount > 0) {
throw new ValidationError({
message: "O email informado já esta sendo utilizado.",
action: "Utilize outro email para realizar o cadastro.",
});
}
}
async function findOneByUsername(username) {
const userFound = await runSelectQuery(username);
return userFound;

async function validateUniqueUsername(username) {
async function runSelectQuery(username) {
const results = await database.query({
text: `
SELECT
username
*
FROM
users
WHERE
LOWER(username) = LOWER($1)
LIMIT
1
;`,
values: [username],
});

if (results.rowCount > 0) {
throw new ValidationError({
message: "O username informado já esta sendo utilizado.",
action: "Utilize outro username para realizar o cadastro.",
if (results.rowCount === 0) {
throw new NotFoundError({
message: "O username informado não foi encontrado no sistema.",
action: "Verifique se o username está digitado corretamente.",
});
}
return results.rows[0];
}
}

async function create(userInputValues) {
await validateUniqueUsername(userInputValues.username);
await validateUniqueEmail(userInputValues.email);
await hashPasswordInObject(userInputValues);

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

async function runInsertQuery(userInputValues) {
const results = await database.query({
Expand All @@ -71,38 +60,103 @@ async function create(userInputValues) {
}
}

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

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

if ("email" in userInputValues) {
await validateUniqueEmail(userInputValues.email);
}

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

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

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

async function runUpdateQuery(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)
LIMIT
1
;`,
values: [username],
values: [username],
});

if (results.rowCount > 0) {
throw new ValidationError({
message: "O username informado já esta sendo utilizado.",
action: "Utilize outro username para realizar esta operação.",
});
}
}

if (results.rowCount === 0) {
throw new NotFoundError({
message: "O username informado não foi encontrado no sistema.",
action: "Verifique se o username está digitado corretamente.",
});
}
return results.rows[0];
async function validateUniqueEmail(email) {
const results = await database.query({
text: `
SELECT
email
FROM
users
WHERE
LOWER(email) = LOWER($1)
;`,
values: [email],
});

if (results.rowCount > 0) {
throw new ValidationError({
message: "O email informado já esta sendo utilizado.",
action: "Utilize outro email para realizar esta operação.",
});
}
}

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.5.0",
"dotenv-expand": "12.0.2",
"next": "15.3.1",
Expand Down
10 changes: 10 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 user from "models/user.js";
const router = createRouter();

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

export default router.handler(controller.errorHandlers);

Expand All @@ -13,3 +14,12 @@ 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 @@ -33,7 +33,7 @@ describe("GET api/v1/users/[username]", () => {
id: response2Body.id,
username: "MesmoCase",
email: "mesmo.case@teste.com.br",
password: "123abc",
password: response2Body.password,
created_at: response2Body.created_at,
updated_at: response2Body.updated_at,
});
Expand Down Expand Up @@ -66,7 +66,7 @@ describe("GET api/v1/users/[username]", () => {
id: response2Body.id,
username: "CaseDiferente",
email: "case.diferente@teste.com.br",
password: "123abc",
password: response2Body.password,
created_at: response2Body.created_at,
updated_at: response2Body.updated_at,
});
Expand Down
Loading