Skip to content

Commit 7ac9c1a

Browse files
committed
✨ feat: user registration implemented
1 parent 4891d74 commit 7ac9c1a

File tree

8 files changed

+242
-7
lines changed

8 files changed

+242
-7
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/* eslint-disable camelcase */
2+
exports.up = pgm => {
3+
pgm.createTable('users', {
4+
id: {
5+
type: 'VARCHAR(50)',
6+
prrimaryKey: true,
7+
},
8+
username: {
9+
type: 'VARCHAR(50)',
10+
unique: true,
11+
notNull: true,
12+
},
13+
password: {
14+
type: 'TEXT',
15+
notNull: true,
16+
},
17+
fullname: {
18+
type: 'TEXT',
19+
notNull: true,
20+
},
21+
});
22+
};
23+
24+
exports.down = pgm => {
25+
pgm.dropTable('users');
26+
};

src/api/users/handler.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
const ClientError = require('../../exceptions/ClientError');
2+
3+
class UsersHandler {
4+
constructor(service, validator) {
5+
this._service = service;
6+
this._validator = validator;
7+
8+
// Bind This
9+
this.postUsersHandler = this.postUsersHandler.bind(this);
10+
this.getUserByIdHandler = this.getUserByIdHandler.bind(this);
11+
}
12+
13+
async postUsersHandler(request, h) {
14+
try {
15+
this._validator.validateUserPayload(request.payload);
16+
17+
const { username, password, fullname } = request.payload;
18+
19+
const userId = await this._service.addUser({ username, password, fullname });
20+
const response = h.response({
21+
status: 'success',
22+
message: 'User berhasil ditambahkan',
23+
data: {
24+
userId,
25+
},
26+
});
27+
response.code(201);
28+
return response;
29+
} catch (error) {
30+
if (error instanceof ClientError) {
31+
const response = h.response({
32+
status: 'fail',
33+
message: error.message,
34+
});
35+
response.code(error.statusCode);
36+
return response;
37+
}
38+
39+
// error Server!
40+
const response = h.response({
41+
status: 'error',
42+
message: 'Maaf, terjadi kesalahan pada server kami.',
43+
});
44+
response.code(500);
45+
console.error(error)
46+
return response;
47+
}
48+
}
49+
50+
async getUserByIdHandler(request, h) {
51+
try {
52+
const { id } = request.params;
53+
const user = await this._service.getUserById(id);
54+
55+
return {
56+
status: 'success',
57+
data: {
58+
user,
59+
},
60+
};
61+
} catch (error) {
62+
if (error instanceof ClientError) {
63+
const response = h.response({
64+
status: 'fail',
65+
message: error.message,
66+
});
67+
response.code(error.statusCode);
68+
return response;
69+
}
70+
71+
// error Server!
72+
const response = h.response({
73+
status: 'error',
74+
message: 'Maaf, terjadi kesalahan pada server kami.',
75+
});
76+
response.code(500);
77+
console.error(error)
78+
return response;
79+
}
80+
}
81+
}
82+
83+
module.exports = UsersHandler;

src/api/users/index.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
const UsersHandler = require('./handler');
2+
const routes = require('./routes');
3+
4+
5+
module.exports = {
6+
name: 'users',
7+
version: '1.0.0',
8+
register: async (server, { service, validator }) => {
9+
const usersHandler = new UsersHandler(service, validator);
10+
server.route(routes(usersHandler));
11+
},
12+
};

src/api/users/routes.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const routes = (handler) => [
2+
{
3+
method: 'POST',
4+
path: '/users',
5+
handler: handler.postUsersHandler,
6+
},
7+
{
8+
method: 'GET',
9+
path: '/users/{id}',
10+
handler: handler.getUserByIdHandler,
11+
},
12+
];
13+
14+
module.exports = routes;

src/server.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
11
require('dotenv').config();
22

33
const Hapi = require('@hapi/hapi');
4+
5+
// notes
46
const notes = require('./api/notes');
57
const NotesService = require('./services/postgres/NotesService');
68
const NotesValidator = require('./validator/notes');
79

10+
// users
11+
const users = require('./api/users');
12+
const UsersService = require('./services/postgres/UsersService');
13+
const UsersValidator = require('./validator/users');
14+
815
const init = async () => {
916
const notesService = new NotesService();
17+
const usersService = new UsersService();
18+
1019
const server = Hapi.server({
1120
port: process.env.PORT,
1221
host: process.env.HOST,
@@ -17,13 +26,22 @@ const init = async () => {
1726
},
1827
});
1928

20-
await server.register({
21-
plugin: notes,
22-
options: {
23-
service: notesService,
24-
validator: NotesValidator,
25-
}
26-
});
29+
await server.register([
30+
{
31+
plugin: notes,
32+
options: {
33+
service: notesService,
34+
validator: NotesValidator,
35+
},
36+
},
37+
{
38+
plugin: users,
39+
options: {
40+
service: usersService,
41+
validator: UsersValidator,
42+
},
43+
},
44+
]);
2745

2846
await server.start();
2947
console.log(`Server berjalan pada ${server.info.uri}`);

src/services/postgres/UsersService.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const { Pool } = require('pg');
2+
const { nanoid } = require('nanoid');
3+
const bcrypt = require('bcrypt');
4+
5+
const InvariantError = require('../../exceptions/InvariantError');
6+
const NotFoundError = require('../../exceptions/NotFoundError');
7+
8+
class UsersService {
9+
constructor() {
10+
this._pool = new Pool();
11+
}
12+
13+
async addUser({ username, password, fullname }) {
14+
// TODO: Verifikasi username, pastikan belum terdaftar.
15+
await this.verifyNewUsername(username);
16+
17+
// TODO: Bila verifikasi lolos, masukan user ke database.
18+
const id = `user-${nanoid(16)}`;
19+
const hashedPassword = await bcrypt.hash('password', 10);
20+
const query = {
21+
text: 'INSERT INTO users VALUES($1, $2, $3, $4) RETURNING id',
22+
values: [id, username, hashedPassword, fullname],
23+
};
24+
25+
const result = await this._pool.query(query);
26+
if (result.rows.length < 0) {
27+
throw new InvariantError('User gagal ditambahkan');
28+
}
29+
30+
return result.rows[0].id;
31+
}
32+
33+
async getUserById(userId) {
34+
const query = {
35+
text: 'SELECT id, username, fullname FROM users WHERE id = $1',
36+
values: [userId],
37+
};
38+
39+
const result = await this._pool.query(query);
40+
if (!result.rows.length) {
41+
throw new NotFoundError('User tidak ditemukan');
42+
}
43+
44+
return result.rows[0];
45+
}
46+
47+
async verifyNewUsername(username) {
48+
const query = {
49+
text: 'SELECT username FROM users WHERE username = $1',
50+
values: [username],
51+
};
52+
53+
const result = await this._pool.query(query);
54+
if (result.rows.length > 0) {
55+
throw new InvariantError('Gagal menambahkan user. Username sudah digunakan.');
56+
}
57+
}
58+
}
59+
60+
module.exports = UsersService;

src/validator/users/index.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const InvariantError = require('../../exceptions/InvariantError');
2+
const { userPayloadSchema } = require('./schema');
3+
4+
const UsersValidator = {
5+
validateUserPayload: (payload) => {
6+
const validationResult = userPayloadSchema.validate(payload);
7+
if (validationResult.error) {
8+
throw new InvariantError(validationResult.error.message)
9+
}
10+
},
11+
};
12+
13+
module.exports = UsersValidator;

src/validator/users/schema.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const Joi = require('joi');
2+
3+
const userPayloadSchema = Joi.object({
4+
username: Joi.string().required(),
5+
password: Joi.string().required(),
6+
fullname: Joi.string().required(),
7+
});
8+
9+
module.exports = { userPayloadSchema };

0 commit comments

Comments
 (0)