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
1 change: 1 addition & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ POSTGRES_USER=local_user
POSTGRES_DB=local_db
POSTGRES_PASSWORD=local_password
DATABASE_URL=postgres://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB
PASSWORD_PEPPER="development"
27 changes: 27 additions & 0 deletions models/password.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import bcryptjs from "bcryptjs";

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

async function compare(providedPassword, storedPassword) {
const pepper = getPepper();
return await bcryptjs.compare(providedPassword + pepper, storedPassword);
}

const password = {
hash,
compare,
};

export default password;

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

function getPepper() {
return process.env.PASSWORD_PEPPER || "";
}
139 changes: 96 additions & 43 deletions models/user.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import database from "infra/database";
import password from "models/password.js";
import { ValidationError, NotFoundError } from "infra/errors";

async function findOneByUsername(username) {
Expand Down Expand Up @@ -31,54 +32,13 @@ 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) {
const result = await database.query({
text: `
SELECT
email
FROM
users
WHERE
LOWER(email) = LOWER($1)
`,
values: [email],
});

if (result.rowCount > 0) {
throw new ValidationError({
message: "O email informado já está sendo utilizado.",
action: "Utilize outro email para realizar o cadastro.",
});
}
}

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

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

async function runInsertQuery(userInputValues) {
const result = await database.query({
text: `
Expand All @@ -100,9 +60,102 @@ async function create(userInputValues) {
}
}

async function update(username, userInputValues) {
const currentUser = await findOneByUsername(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];
}
}

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

export default user;

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

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

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

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

async function hashPasswordInObject(userInputValues) {
const hashedPassword = await password.hash(userInputValues.password);
userInputValues.password = hashedPassword;
}
21 changes: 21 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 @@ -4,6 +4,7 @@ import user from "models/user.js";

const router = createRouter();
router.get(getHandler);
router.patch(patchHandler);

export default router.handler(controller.errorHandlers);

Expand All @@ -12,3 +13,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 @@ -35,7 +35,7 @@ describe("GET /api/v1/users/[username]", () => {
id: response2Body.id,
username: "CaseMatch",
email: "case.match@felipebatista.dev",
password: "senha123",
password: response2Body.password,
created_at: response2Body.created_at,
updated_at: response2Body.updated_at,
});
Expand Down Expand Up @@ -71,7 +71,7 @@ describe("GET /api/v1/users/[username]", () => {
id: response2Body.id,
username: "MismatchCase",
email: "mismatch.case@felipebatista.dev",
password: "senha123",
password: response2Body.password,
created_at: response2Body.created_at,
updated_at: response2Body.updated_at,
});
Expand Down
Loading