Following API best practices

This commit is contained in:
xfarrow 2024-03-06 10:19:37 +01:00
parent 4f53ef7561
commit ae14f04949
10 changed files with 46 additions and 38 deletions

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,5 @@
# APIs # APIs
## Programming language
The Blink APIs are currently written in NodeJS only.
## Prerequisites ## Prerequisites
* PostgreSQL; * PostgreSQL;
* NodeJS. * NodeJS.
@ -16,3 +13,8 @@ In order to deploy the Blink APIs, follow these steps:
You can test the APIs in two ways: You can test the APIs in two ways:
* Open `BlinkApiUsageExample.json` with Insomnia or Bruno in order to have the collection of APIs already configured and ready to be seen in action; * Open `BlinkApiUsageExample.json` with Insomnia or Bruno in order to have the collection of APIs already configured and ready to be seen in action;
* Run `npm test` in `./nodejs` to run a suite of automated tests. * Run `npm test` in `./nodejs` to run a suite of automated tests.
## For Developers
The current implementation of the Blink APIs is written in NodeJS. Feel free to develop them in any other
programming language (you can paste the folder here) but make sure to make them compatible with one another
and please follow the API design [best practices](https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design)

View File

@ -38,8 +38,9 @@ async function isPersonOrganizationAdministrator(personId, organizationId) {
*/ */
async function addOrganizationAdministrator(personId, organizationId, requester) { async function addOrganizationAdministrator(personId, organizationId, requester) {
const isPersonAdmin = await organization_admin_model.isPersonAdmin(requester, organizationId); const isRequesterAdmin = await isPersonOrganizationAdministrator(requester, organizationId);
if (isPersonAdmin) { const isPersonAdmin = await isPersonOrganizationAdministrator(personId, organizationId);
if (isRequesterAdmin && !isPersonAdmin) {
await knex('OrganizationAdministrator') await knex('OrganizationAdministrator')
.insert({ .insert({
id_person: personId, id_person: personId,

View File

@ -25,14 +25,14 @@ const jwtUtils = require('../utils/middleware_utils');
*/ */
async function addOrganizationAdmin(req, res) { async function addOrganizationAdmin(req, res) {
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.organization_id || !req.body.person_id) { if (!req.params.id || !req.body.person_id) {
return res.status(400).json({ return res.status(400).json({
error: 'Invalid request' error: 'Invalid request'
}); });
} }
try { try {
const success = await organizationAdminModel.addOrganizationAdministrator(req.body.person_id, req.body.organization_id, req.jwt.person_id); const success = await organizationAdminModel.addOrganizationAdministrator(req.body.person_id, req.params.id, req.jwt.person_id);
if (success) { if (success) {
return res.status(200).json({ return res.status(200).json({
success: true success: true
@ -60,14 +60,14 @@ async function addOrganizationAdmin(req, res) {
*/ */
async function removeOrganizationAdmin(req, res) { async function removeOrganizationAdmin(req, res) {
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.organization_id) { if (!req.params.organizationId) {
return res.status(400).json({ return res.status(400).json({
error: 'Invalid request' error: 'Invalid request'
}); });
} }
try { try {
await organizationAdminModel.removeOrganizationAdmin(req.jwt.person_id, req.body.organization_id); await organizationAdminModel.removeOrganizationAdmin(req.jwt.person_id, req.params.organizationId);
return res.status(200).json({ return res.status(200).json({
success: true success: true
}); });
@ -81,8 +81,8 @@ async function removeOrganizationAdmin(req, res) {
const protectedRoutes = express.Router(); const protectedRoutes = express.Router();
protectedRoutes.use(jwtUtils.verifyToken); protectedRoutes.use(jwtUtils.verifyToken);
protectedRoutes.post('/organization/admin', addOrganizationAdmin); protectedRoutes.post('/organizations/:id/admins', addOrganizationAdmin);
protectedRoutes.delete('/organization/admin', removeOrganizationAdmin); protectedRoutes.delete('/organizations/:organizationId/admins/me', removeOrganizationAdmin);
module.exports = { module.exports = {
protectedRoutes protectedRoutes

View File

@ -25,20 +25,25 @@ const jwtUtils = require('../utils/middleware_utils');
*/ */
async function createOrganizationPost(req, res) { async function createOrganizationPost(req, res) {
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.organization_id || !req.body.content) { if (!req.params.idOrganization || !req.body.content) {
return res.status(400).json({ return res.status(400).json({
error: 'Invalid request' error: 'Invalid request'
}); });
} }
const organization = organizationPostModel.createOrganizationPost( const organizationPost = organizationPostModel.createOrganizationPost(
req.body.organization_id, req.params.idOrganization,
req.body.content, req.body.content,
req.jwt.person_id); req.jwt.person_id);
try { try {
const insertedOrganization = await organizationPostModel.insertOrganizationPost(organization); const insertedOrganization = await organizationPostModel.insertOrganizationPost(organizationPost);
return res.status(200).json(insertedOrganization); if(!!insertedOrganization){
return res.status(200).json(insertedOrganization);
}
return res.status(401).json({
error: 'Forbidden'
});
} catch (error) { } catch (error) {
console.error(`Error in function ${createOrganizationPost.name}: ${error}`); console.error(`Error in function ${createOrganizationPost.name}: ${error}`);
return res.status(500).json({ return res.status(500).json({
@ -78,8 +83,8 @@ async function deleteOrganizationPost(req, res) {
const protectedRoutes = express.Router(); const protectedRoutes = express.Router();
protectedRoutes.use(jwtUtils.verifyToken); protectedRoutes.use(jwtUtils.verifyToken);
protectedRoutes.post('/organization/post', createOrganizationPost); protectedRoutes.post('/organizations/:idOrganization/posts', createOrganizationPost);
protectedRoutes.delete('/organization/post/:id', deleteOrganizationPost); protectedRoutes.delete('/organizations/posts/:id', deleteOrganizationPost);
// Exporting a function // Exporting a function
// means making a JavaScript function defined in one // means making a JavaScript function defined in one

View File

@ -147,13 +147,13 @@ async function getOrganization(req, res) {
} }
const publicRoutes = express.Router(); const publicRoutes = express.Router();
publicRoutes.get('/organization/:id', getOrganization); publicRoutes.get('/organizations/:id', getOrganization);
const protectedRoutes = express.Router(); const protectedRoutes = express.Router();
protectedRoutes.use(jwtUtils.verifyToken); protectedRoutes.use(jwtUtils.verifyToken);
protectedRoutes.post('/organization', createOrganization); protectedRoutes.post('/organizations', createOrganization);
protectedRoutes.put('/organization/:id', updateOrganization); protectedRoutes.put('/organizations/:id', updateOrganization);
protectedRoutes.delete('/organization/:id', deleteOrganization); protectedRoutes.delete('/organizations/:id', deleteOrganization);
module.exports = { module.exports = {
publicRoutes, publicRoutes,

View File

@ -92,7 +92,7 @@ async function registerPerson(req, res) {
* *
* @returns The token * @returns The token
*/ */
async function login(req, res) { async function createTokenByEmailAndPassword(req, res) {
// Ensure that the required fields are present before proceeding // Ensure that the required fields are present before proceeding
if (!req.body.email || !req.body.password) { if (!req.body.email || !req.body.password) {
return res.status(400).json({ return res.status(400).json({
@ -113,7 +113,7 @@ async function login(req, res) {
}); });
} }
} catch (error) { } catch (error) {
console.error(`Error in function ${login.name}: ${error}`); console.error(`Error in function ${createTokenByEmailAndPassword.name}: ${error}`);
return res.status(500).json({ return res.status(500).json({
error: 'Internal server error' error: 'Internal server error'
}); });
@ -312,16 +312,16 @@ async function confirmActivation(req, res) {
} }
const publicRoutes = express.Router(); // Routes not requiring token const publicRoutes = express.Router(); // Routes not requiring token
publicRoutes.post('/register', registerPerson); publicRoutes.post('/persons', registerPerson);
publicRoutes.post('/login', login); publicRoutes.post('/persons/me/token', createTokenByEmailAndPassword);
publicRoutes.get('/person/:id/details', getPerson); publicRoutes.get('/persons/:id/details', getPerson);
publicRoutes.get('/person/activation', confirmActivation); publicRoutes.get('/persons/me/activation', confirmActivation);
const protectedRoutes = express.Router(); // Routes requiring token const protectedRoutes = express.Router(); // Routes requiring token
protectedRoutes.use(jwtUtils.verifyToken); protectedRoutes.use(jwtUtils.verifyToken);
protectedRoutes.get('/person/myself', getMyself); protectedRoutes.get('/persons/me', getMyself);
protectedRoutes.put('/person', updatePerson); protectedRoutes.put('/persons/me', updatePerson);
protectedRoutes.delete('/person', deletePerson); protectedRoutes.delete('/persons/me', deletePerson);
// Exporting a function // Exporting a function
// means making a JavaScript function defined in one // means making a JavaScript function defined in one

View File

@ -43,7 +43,7 @@
alert('Please fill in all fields'); alert('Please fill in all fields');
return; return;
} }
const response = await fetch(`${API_URL}/login`, { const response = await fetch(`${API_URL}/persons/me/token`, {
method: "POST", method: "POST",
body: JSON.stringify({ body: JSON.stringify({
email: email, email: email,
@ -59,7 +59,7 @@
if (response.ok) { if (response.ok) {
console.log(`Login was successful. Token is ${data.token}`); console.log(`Login was successful. Token is ${data.token}`);
document.cookie = `token=${data.token};`; document.cookie = `token=${data.token};`;
window.location.href = 'userprofile.html?id=myself'; window.location.href = 'userprofile.html?id=me';
} else { } else {
alert(data.error); alert(data.error);
} }

View File

@ -65,7 +65,7 @@
}), }),
}; };
fetch(`${API_URL}/register`, options) fetch(`${API_URL}/persons`, options)
.then(response => { .then(response => {
if (response.ok) { if (response.ok) {
alert("Congratulations! You've successfully registered to Blink." + alert("Congratulations! You've successfully registered to Blink." +

View File

@ -58,21 +58,21 @@
let response; let response;
// Retrieving the logged in user's profile // Retrieving the logged in user's profile
if (!idToDisplay || idToDisplay === 'myself') { if (!idToDisplay || idToDisplay === 'me') {
document.getElementById('editBadge').style.display = 'block'; // show edit button document.getElementById('editBadge').style.display = 'block'; // show edit button
const token = getCookie('token'); const token = getCookie('token');
// Check whether the token exists // Check whether the token exists
if (!token) { if (!token) {
window.location.href = 'login.html'; window.location.href = 'login.html';
} }
response = await fetch(`${API_URL}/person/myself`, { response = await fetch(`${API_URL}/persons/me`, {
headers: { headers: {
"Content-type": "application/json; charset=UTF-8", "Content-type": "application/json; charset=UTF-8",
"authorization": token "authorization": token
} }
}); });
} else { } else {
response = await fetch(`${API_URL}/person/${idToDisplay}/details`, { response = await fetch(`${API_URL}/persons/${idToDisplay}/details`, {
headers: { headers: {
"Content-type": "application/json; charset=UTF-8", "Content-type": "application/json; charset=UTF-8",
} }