Skip to content

Commit 17074dc

Browse files
committed
feat: add support for PATCH in /api/v1/users/[username]
1 parent e1ff751 commit 17074dc

5 files changed

Lines changed: 402 additions & 60 deletions

File tree

models/user.js

Lines changed: 104 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -3,65 +3,13 @@ import password from "models/password.js";
33
import { ValidationError, NotFoundError } from "infra/errors.js";
44

55
async function create(userInputValues) {
6-
await validadeUniqueEmail(userInputValues.email);
76
await validateUniqueUsername(userInputValues.username);
7+
await validadeUniqueEmail(userInputValues.email);
88
await hashPasswordInObject(userInputValues);
99

1010
const newUser = await runInsertQuery(userInputValues);
1111
return newUser;
1212

13-
async function validadeUniqueEmail(email) {
14-
const result = await database.query({
15-
text: `
16-
SELECT
17-
email
18-
FROM
19-
users
20-
WHERE
21-
LOWER(email) = LOWER($1)
22-
LIMIT
23-
1
24-
;`,
25-
values: [email],
26-
});
27-
28-
if (result.rowCount > 0) {
29-
throw new ValidationError({
30-
message: "The email address provided is already in use.",
31-
action: "Please use a different email to create your account.",
32-
});
33-
}
34-
}
35-
36-
async function validateUniqueUsername(username) {
37-
const result = await database.query({
38-
text: `
39-
SELECT
40-
username
41-
FROM
42-
users
43-
WHERE
44-
LOWER(username) = LOWER($1)
45-
LIMIT
46-
1
47-
;`,
48-
values: [username],
49-
});
50-
51-
if (result.rowCount > 0) {
52-
throw new ValidationError({
53-
message: "The username provided is already in use.",
54-
action: "Please use a different username to create your account.",
55-
});
56-
}
57-
}
58-
59-
async function hashPasswordInObject(userInputValues) {
60-
const hashedPassword = await password.hash(userInputValues.password);
61-
62-
userInputValues.password = hashedPassword;
63-
}
64-
6513
async function runInsertQuery(userInputValues) {
6614
const result = await database.query({
6715
text: `
@@ -114,5 +62,106 @@ async function findOneByUsername(username) {
11462
}
11563
}
11664

117-
const userModel = { create, findOneByUsername };
118-
export default userModel;
65+
async function update(username, userInputValues) {
66+
const user = await findOneByUsername(username);
67+
68+
if ("username" in userInputValues) {
69+
await validateUniqueUsername(userInputValues.username);
70+
}
71+
72+
if ("email" in userInputValues) {
73+
await validadeUniqueEmail(userInputValues.email);
74+
}
75+
76+
if ("password" in userInputValues) {
77+
await hashPasswordInObject(userInputValues);
78+
}
79+
80+
const userWithNewValues = { ...user, ...userInputValues };
81+
82+
const updatedUser = await runUpdateQuery(userWithNewValues);
83+
84+
return updatedUser;
85+
86+
async function runUpdateQuery(userWithNewValues) {
87+
const result = await database.query({
88+
text: `
89+
UPDATE
90+
users
91+
SET
92+
username = $2,
93+
email = $3,
94+
password = $4,
95+
updated_at = timezone('utc', now())
96+
WHERE
97+
id = $1
98+
RETURNING
99+
*
100+
`,
101+
values: [
102+
userWithNewValues.id,
103+
userWithNewValues.username,
104+
userWithNewValues.email,
105+
userWithNewValues.password,
106+
],
107+
});
108+
109+
return result.rows[0];
110+
}
111+
}
112+
113+
async function validateUniqueUsername(username) {
114+
const result = await database.query({
115+
text: `
116+
SELECT
117+
username
118+
FROM
119+
users
120+
WHERE
121+
LOWER(username) = LOWER($1)
122+
LIMIT
123+
1
124+
;`,
125+
values: [username],
126+
});
127+
128+
if (result.rowCount > 0) {
129+
throw new ValidationError({
130+
message: "The username provided is already in use.",
131+
action: "Please use a different username.",
132+
});
133+
}
134+
}
135+
136+
async function validadeUniqueEmail(email) {
137+
const result = await database.query({
138+
text: `
139+
SELECT
140+
email
141+
FROM
142+
users
143+
WHERE
144+
LOWER(email) = LOWER($1)
145+
LIMIT
146+
1
147+
;`,
148+
values: [email],
149+
});
150+
151+
if (result.rowCount > 0) {
152+
throw new ValidationError({
153+
message: "The email address provided is already in use.",
154+
action: "Please use a different email.",
155+
});
156+
}
157+
}
158+
159+
async function hashPasswordInObject(userInputValues) {
160+
const hashedPassword = await password.hash(userInputValues.password);
161+
162+
userInputValues.password = hashedPassword;
163+
}
164+
165+
const user = { create, findOneByUsername, update };
166+
167+
export default user;

pages/api/v1/users/[username]/index.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import user from "models/user.js";
55
const router = createRouter();
66

77
router.get(getHandler);
8+
router.patch(patchHandler);
89

910
export default router.handler(controller.errorHandlers);
1011

@@ -15,3 +16,12 @@ async function getHandler(request, response) {
1516

1617
return response.status(200).json(userFound);
1718
}
19+
20+
async function patchHandler(request, response) {
21+
const { username } = request.query;
22+
const userInputValues = request.body;
23+
24+
const updatedUser = await user.update(username, userInputValues);
25+
26+
return response.status(200).json(updatedUser);
27+
}

tests/integration/api/v1/users/[username]/get.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ describe("GET to api/v1/users/[username]", () => {
7979
expect(Date.parse(response2Body.updated_at)).not.toBeNaN();
8080
});
8181

82-
test("With nonexistent username", async () => {
82+
test("With nonexistent 'username'", async () => {
8383
// Get user
8484
const response = await fetch(
8585
"http://localhost:3000/api/v1/users/usuario_inexistente",

0 commit comments

Comments
 (0)