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, storedPassword) {
return await bcryptjs.compare(providedPassword, storedPassword);
}

const password = {
hash,
compare,
};

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

async function findOneByUsername(username) {
Expand Down Expand Up @@ -33,77 +34,131 @@ async function findOneByUsername(username) {
}

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

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

async function validateUniqueEmail(email) {
async function runInsertQuery(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,
],
});
return results.rows[0];
}
}

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

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

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

if (results.rowCount > 0) {
throw new ValidationError({
message: "O username informado já está sendo utilizado.",
action: "Utilize outro username para realizar o cadastro.",
});
}
if ("password" in userInputValues) {
await hashPasswordInObject(userInputValues);
}

async function runInsertQuery(userInputValues) {
const userWithNewValues = { ...currentUser, ...userInputValues };

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

async function runUpdateQuery(userWithNewValues) {
const results = await database.query({
text: `
INSERT INTO
users (username, email, password)
VALUES
($1, $2, $3)
UPDATE
users
SET
username = $2,
email = $3,
password = $4,
updated_at = timezone('utc', now())
WHERE
id = $1
RETURNING
*
;`,
`,
values: [
userInputValues.username,
userInputValues.email,
userInputValues.password,
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],
});

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

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á está 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;
9 changes: 9 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.4",
"dotenv-expand": "11.0.6",
"next": "13.1.6",
Expand Down
9 changes: 9 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,11 @@ 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@curso.dev",
password: "senha123",
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@curso.dev",
password: "senha123",
password: response2Body.password,
created_at: response2Body.created_at,
updated_at: response2Body.updated_at,
});
Expand Down
Loading