Skip to content

Commit 45cd590

Browse files
authored
Merge pull request #8 from ryumada/develop
Collaboration Implemented
2 parents 68a443e + 3ea4298 commit 45cd590

File tree

16 files changed

+432
-6
lines changed

16 files changed

+432
-6
lines changed

docs/scenario.collaboration.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
Folder: Collaborations
2+
3+
## Skenario 1: Adding Note using Owner User
4+
- status code: **201** (Created)
5+
- header response
6+
- **Content-Type**: **application/json; charset=utf-8**
7+
- body response (**data**)
8+
- is **noteId** exist?
9+
10+
## Skenario 2: Adding Collaborator User as Collaborator to Added Note
11+
- status code: **201** (Created)
12+
- header response
13+
- **Content-Type**: **application/json; charset=utf-8**
14+
- body response (**data**)
15+
- is **collaborationId** exist?
16+
17+
## Skenario 3: Getting All Notes using Collaborator User
18+
- status code: **200** (OK)
19+
- header response
20+
- **Content-Type**: **application/json; charset=utf-8**
21+
- body response (**data**)
22+
- is the result an **array** named **notes** that have **one item**?
23+
24+
## Skenario 4: Getting Added Note using Collaborator User
25+
- status code: **200** (OK)
26+
- header response
27+
- **Content-Type**: **application/json; charset=utf-8**
28+
29+
## Skenario 5: Editing Added Note using Collaborator User
30+
- status code: **200** (OK)
31+
- header response
32+
- **Content-Type**: **application/json; charset=utf-8**
33+
34+
## Skenario 6: Deleting Added Note using Collaborator User
35+
- status code: **403** (Forbidden)
36+
- header response
37+
- **Content-Type**: **application/json; charset=utf-8**
38+
- body response (**data**)
39+
- does the result have **correct property & value**?
40+
41+
## Skenario 7: Deleting Collaborator User from Collaborator to Added Note
42+
- status code: **200** (OK)
43+
- header response
44+
- **Content-Type**: **application/json; charset=utf-8**
45+
- body response (**data**)
46+
- does the result have **correct property & value**?
47+
48+
## Skenario 8: Adding Collaborator User as Collaborator to Added Note using Collaborator User
49+
- status code: **403** (Forbidden)
50+
- header response
51+
- **Content-Type**: **application/json; charset=utf-8**
52+
- body response (**data**)
53+
- does the result have **correct property & value**?
54+
55+
## Skenario 9: Getting All Notes using Collaborator User
56+
- status code: **200** (OK)
57+
- header response
58+
- **Content-Type**: **application/json; charset=utf-8**
59+
- body response (**data**)
60+
- is the result an **array** named **notes** that have **zero (0) item**?
61+
62+
## Skenario 10: Getting Added Note using Collaborator User
63+
- status code: **403** (Forbidden)
64+
- header response
65+
- **Content-Type**: **application/json; charset=utf-8**
66+
- body response (**data**)
67+
- does the result have **correct property & value**?
68+
69+
## Skenario 11: Editing Added Note using Collaborator User
70+
- status code: **403** (Forbidden)
71+
- header response
72+
- **Content-Type**: **application/json; charset=utf-8**
73+
- body response (**data**)
74+
- does the result have **correct property & value**?
75+
76+
## Skenario 12: Deleting Added Note using Owner User
77+
- status code: **200** (OK)
78+
- header response
79+
- **Content-Type**: **application/json; charset=utf-8**
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/* eslint-disable quotes */
2+
/* eslint-disable camelcase */
3+
4+
exports.shorthands = undefined;
5+
6+
exports.up = pgm => {
7+
// membuat user baru.
8+
pgm.sql("INSERT INTO users(id, username, password, fullname) VALUES ('old_notes', 'old_notes', 'old_notes', 'old_notes')");
9+
10+
// mengubah nilai owner pada note yang owner-nya bernilai NULL
11+
pgm.sql("UPDATE notes SET owner = 'old_notes' WHERE owner = NULL");
12+
13+
// memberi constraint foreign key pada owner terhadap kolom id dari tabel users
14+
pgm.addConstraint('notes', 'fk_notes.owner_users.id', 'FOREIGN KEY(owner) REFERENCES users(id) ON DELETE CASCADE');
15+
};
16+
17+
exports.down = pgm => {
18+
// menghapus constraint fk_notes.owner_users.id pada tabel notes
19+
pgm.dropConstraint('notes', 'fk_notes.owner_users.id');
20+
21+
// mengubah nilai owner old_notes pada note menjadi NULL
22+
pgm.sql("UPDATE notes SET owner = NULL WHERE owner = 'old_notes'");
23+
24+
// menghapus user baru
25+
pgm.sql("DELETE FROM users WHERE id = 'old_notes'");
26+
};
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* eslint-disable camelcase */
2+
3+
exports.shorthands = undefined;
4+
5+
exports.up = pgm => {
6+
// membuat table collaborations
7+
pgm.createTable('collaborations', {
8+
id: {
9+
type: 'VARCHAR(50)',
10+
primaryKey: true,
11+
},
12+
note_id: {
13+
type: 'VARCHAR(50)',
14+
notNull: true,
15+
},
16+
user_id: {
17+
type: 'VARCHAR(50)',
18+
notNul: true,
19+
},
20+
});
21+
22+
/**
23+
* Menambahkan constraint UNIQUE, kombinasi dari kolom note_id dan user_id.
24+
* Guna menghindari duplikasi data antara nilai keduanya.
25+
*/
26+
pgm.addConstraint('collaborations', 'unique_note_id_and_user_id', 'UNIQUE(note_id, user_id)');
27+
28+
// memberikan constraint foreign key pada kolom note_id dan user_id terhadap notes.id dan users.id
29+
pgm.addConstraint('collaborations', 'fk_collaborations.note_id_notes.id', 'FOREIGN KEY(note_id) REFERENCES notes(id) ON DELETE CASCADE');
30+
pgm.addConstraint('collaborations', 'fk_collaborations.user_id_users.id', 'FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE');
31+
};
32+
33+
exports.down = pgm => {
34+
// menghapus tabel collaborations
35+
pgm.dropTable('collaborations');
36+
};

src/api/collaborations/handler.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
const ClientError = require('../../exceptions/ClientError');
2+
3+
class CollaborationsHandler {
4+
constructor(collaborationsService, notesService, validator) {
5+
this._collaborationsService = collaborationsService;
6+
this._notesService = notesService;
7+
this._validator = validator;
8+
9+
// bind global variable this
10+
this.postCollaborationHandler = this.postCollaborationHandler.bind(this);
11+
this.deleteCollaborationHandler = this.deleteCollaborationHandler.bind(this);
12+
}
13+
14+
async postCollaborationHandler(request, h) {
15+
try {
16+
this._validator.validateCollaborationPayload(request.payload);
17+
const { id: credentialId } = request.auth.credentials;
18+
const { noteId, userId } = request.payload;
19+
20+
await this._notesService.verifyNoteOwner(noteId, credentialId);
21+
const collaborationId = await this._collaborationsService.addCollaboration(noteId, userId);
22+
23+
const response = h.response({
24+
status: 'success',
25+
message: 'Kolaborasi berhasil ditambahkan',
26+
data: {
27+
collaborationId,
28+
},
29+
});
30+
response.code(201);
31+
return response;
32+
} catch (error) {
33+
if(error instanceof ClientError) {
34+
const response = h.response({
35+
status: 'fail',
36+
message: error.message,
37+
});
38+
response.code(error.statusCode);
39+
return response;
40+
}
41+
42+
// SERVER ERROR
43+
const response = h.response({
44+
status: 'error',
45+
message: 'Maaf, terjadi kegagalan pada server kami.',
46+
});
47+
response.code(500);
48+
console.error(error);
49+
return response;
50+
}
51+
}
52+
53+
async deleteCollaborationHandler(request, h) {
54+
try{
55+
this._validator.validateCollaborationPayload(request.payload);
56+
const { id: credentialId } = request.auth.credentials;
57+
const { noteId, userId } = request.payload;
58+
59+
await this._notesService.verifyNoteOwner(noteId, credentialId);
60+
await this._collaborationsService.deleteCollaboration(noteId, userId);
61+
62+
return {
63+
status: 'success',
64+
message: 'Kolaborasi berhasil dihapus',
65+
};
66+
} catch(error) {
67+
if(error instanceof ClientError) {
68+
const response = h.response({
69+
status: 'fail',
70+
message: error.message,
71+
});
72+
response.code(error.statusCode);
73+
return response;
74+
}
75+
76+
// SERVER ERROR
77+
const response = h.response({
78+
status: 'error',
79+
message: 'Maaf, terjadi kegagalan pada server kami.'
80+
});
81+
response.code(500);
82+
console.error(error);
83+
return response;
84+
}
85+
}
86+
}
87+
88+
module.exports = CollaborationsHandler;

src/api/collaborations/index.js

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

src/api/collaborations/routes.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
const routes = (handler) => [
2+
{
3+
method: 'POST',
4+
path: '/collaborations',
5+
handler: handler.postCollaborationHandler,
6+
options: {
7+
auth: 'notesapp_jwt',
8+
},
9+
},
10+
{
11+
method: 'DELETE',
12+
path: '/collaborations',
13+
handler: handler.deleteCollaborationHandler,
14+
options: {
15+
auth: 'notesapp_jwt',
16+
},
17+
},
18+
];
19+
20+
module.exports = routes;

src/api/notes/handler.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class NotesHandler {
7171
const { id } = request.params;
7272
const { id: credentialId } = request.auth.credentials;
7373

74-
await this._service.verifyNoteOwner(id, credentialId);
74+
await this._service.verifyNoteAccess(id, credentialId);
7575
const note = await this._service.getNoteById(id);
7676

7777
return {
@@ -107,7 +107,7 @@ class NotesHandler {
107107
const { id } = request.params;
108108
const { id: credentialId } = request.auth.credentials;
109109

110-
await this._service.verifyNoteOwner(id, credentialId);
110+
await this._service.verifyNoteAccess(id, credentialId);
111111
await this._service.editNoteById(id, request.payload);
112112

113113
return {

src/api/users/handler.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class UsersHandler {
88
// bind this variable
99
this.postUserHandler = this.postUserHandler.bind(this);
1010
this.getUserByIdHandler = this.getUserByIdHandler.bind(this);
11+
this.getUsersByUsernameHandler = this.getUsersByUsernameHandler.bind(this);
1112
}
1213

1314
async postUserHandler(request, h) {
@@ -79,6 +80,38 @@ class UsersHandler {
7980
return response;
8081
}
8182
}
83+
84+
async getUsersByUsernameHandler(request, h) {
85+
try {
86+
const { username = '' } = request.query;
87+
const users = await this._service.getUsersByUsername(username);
88+
89+
return {
90+
status: 'success',
91+
data: {
92+
users,
93+
},
94+
};
95+
} catch (error) {
96+
if(error instanceof ClientError) {
97+
const response = h.response({
98+
status: 'fail',
99+
message: error.message,
100+
});
101+
response.code(error.statusCode);
102+
return response;
103+
}
104+
105+
// SERVER ERROR
106+
const response = h.response({
107+
status: 'error',
108+
message: 'Maaf, terjadi kegagalan pada server kami.',
109+
});
110+
response.code(500);
111+
console.error(error);
112+
return response;
113+
}
114+
}
82115
}
83116

84117
module.exports = UsersHandler;

src/api/users/routes.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ const routes = (handler) => [
99
path: '/users/{id}',
1010
handler: handler.getUserByIdHandler,
1111
},
12+
{
13+
method: 'GET',
14+
path: '/users',
15+
handler: handler.getUsersByUsernameHandler,
16+
},
1217
];
1318

1419
module.exports = routes;

src/server.js

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,14 @@ const AuthenticationsService = require('./services/postgres/AuthenticationsServi
2020
const TokenManager = require('./tokenize/TokenManager');
2121
const AuthenticationsValidator = require('./validator/authentications');
2222

23+
// collaborations
24+
const collaborations = require('./api/collaborations');
25+
const CollaborationsService = require('./services/postgres/CollaborationsService');
26+
const CollaborationsValidator = require('./validator/collaborations');
27+
2328
const init = async () => {
24-
const notesService = new NotesService();
29+
const collaborationsService = new CollaborationsService();
30+
const notesService = new NotesService(collaborationsService);
2531
const usersService = new UsersService();
2632
const authenticationsService = new AuthenticationsService();
2733

@@ -87,6 +93,14 @@ const init = async () => {
8793
validator: AuthenticationsValidator,
8894
},
8995
},
96+
{
97+
plugin: collaborations,
98+
options: {
99+
collaborationsService,
100+
notesService,
101+
validator: CollaborationsValidator,
102+
},
103+
},
90104
]);
91105

92106
await server.start();

0 commit comments

Comments
 (0)