Commit 2f5c5adb authored by jeremy.gobet's avatar jeremy.gobet
Browse files

Merge branch 'new-features' into 'master'

Backend and API

See merge request jeremy.gobet/app-et-archi-web-tp-2020!8
parents 01922b70 12f40a50
......@@ -96,16 +96,16 @@ Une authentification sans mot de passe permet l'identification de l'utilisateur
### Interface REST API
Voici l'API REST CRUD exposée par le backend
| Verbe HTTP | Endpoint | Données | Description |
|:-----------|:-------------------------|:------------|:------------|
| GET | emails/ | | Retourne la liste des mails des utilisateurs |
| GET | evaluated-jokes/ | | Retourne les blagues les mieux notées |
| GET | evaluated-jokes/*:email* | email* | Retourne les blagues notées par l'utilisateur |
| POST | evaluated-jokes/ | email*, id* | Ajout d'une nouvelle note à la blague (id) |
| PUT | evaluated-jokes/ | email*, id* | Modification de la note de la blague (id) |
| DELETE | evaluated-jokes/ | email*, id* | Suppression de la note de la blague (id) |
Voici l'API REST CRUD exposée par le backend sur /api/v1/
| Verbe HTTP | Endpoint | Données | Description |
|:-----------|:-----------------------------|:----------------------|:------------|
| GET | emails/ | | Retourne la liste des mails des utilisateurs |
| GET | jokes/:limit | | Retourne les blagues les mieux notées |
| GET | jokes/*:userEmail*/:limit | | Retourne les blagues notées par l'utilisateur |
| POST | jokes/ | userEmail\*, jokeId\* | Ajout d'une nouvelle note à la blague (jokeId) |
| DELETE | jokes/ | userEmail\*, jokeId\* | Suppression de la note de la blague (jokeId) |
| GET | likes/*:userEmail*/*:jokeId* | | Retourne la note d'une blague (jokeId) |
\* Obligatoire
......
let persist = require('./persist');
exports.getBestJokes = function(limit = 20) {
return persist.getIdsOrderByOccurence().slice(0, limit);
}
exports.getJokesFor = function(email, limit = 20) {
return persist.getIdsForUser(email).slice(0, limit);
}
exports.getJokeFor = function(jokeId, email) {
return persist.isIdExistsForUser(jokeId, email);
}
exports.saveJokeFor = function(jokeId, email) {
return persist.addIdToUser(jokeId, email);
}
exports.deleteJokeFor = function(jokeId, email) {
return persist.removeIdToUser(jokeId, email);
}
let database = {
'dev@hesge.ch': [
1,2,3
],
'test@hesge.ch': [
2,4,6
]
}
exports.isIdExistsForUser = function(id, email) {
createArrayWhenInexistant(email);
return isIdExists(id, email);
}
exports.addIdToUser = function(id, email) {
createArrayWhenInexistant(email);
if(!isIdExists(id, email)) {
database[email].push(id);
return true;
}
return false;
}
exports.removeIdToUser = function(id, email) {
createArrayWhenInexistant(email);
if(isIdExists(id, email)) {
const index = database[email].indexOf(parseInt(id))
database[email].splice(index, 1);
return true;
}
return false;
}
exports.getIdsForUser = function(email) {
createArrayWhenInexistant(email);
return database[email].sort();
}
exports.getIdsOrderByOccurence = function(email) {
createArrayWhenInexistant(email);
var ids = Object.values(database)
.flat()
.reduce((acc, curr) => {
acc[curr] ? acc[curr]++ : acc[curr] = 1
return acc
}, {});
return Object.keys(ids)
.sort()
.map(key => ids[key]);
}
function createArrayWhenInexistant(email) {
if(!Array.isArray(database[email])) {
database[email] = [];
}
}
function isIdExists(id, email) {
return database[email].indexOf(parseInt(id)) !== -1;
}
const express = require('express');
const bodyParser = require('body-parser');
const jokes = require('./modules/jokes');
const PORT_NUMBER = 8080;
let app = express()
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.static('../frontend'))
app.listen(8080);
console.log('Server started')
/*
* API Documentation
*/
app.get('/api', function(request, response) {
response.setHeader('Content-Type', 'text/plain');
response.send(`
Voici l'API REST CRUD exposée par le backend sur /api/v1/
| Verbe HTTP | Endpoint | Données | Description |
|:-----------|:-----------------------------|:--------------------|:-----------------------------------------------|
| GET | emails/ | | Retourne la liste des mails des utilisateurs |
| GET | jokes/:limit | | Retourne les blagues les mieux notées |
| GET | jokes/*:userEmail*/:limit | | Retourne les blagues notées par l'utilisateur |
| POST | jokes/ | userEmail\*, jokeId\* | Ajout d'une nouvelle note à la blague (jokeId) |
| DELETE | jokes/ | userEmail\*, jokeId\* | Suppression de la note de la blague (jokeId) |
| GET | likes/*:userEmail*/*:jokeId* | | Retourne la note d'une blague (jokeId) |
`);
});
/*
* Best jokes
* params limit the number of jokes returned
*/
app.get('/api/v1/jokes/:limit?', function(request, response) {
console.log('Best jokes', request.params);
const limit = request.params.limit ? request.params.limit : 10;
response.json(jokes.getBestJokes(limit));
});
/*
* Best jokes of specific users
* params userEmail* user's e-mail
* params limit the number of jokes returned
*/
app.get('/api/v1/jokes/:userEmail/:limit?', function(request, response) {
console.log('My jokes', request.params);
if(request.params.userEmail !== undefined) {
response.json(jokes.getJokesFor(request.params.userEmail, request.params.limit));
}
response.status(400).end(); // Bad request
});
/*
* Like a joke
* params userEmail* user's e-mail
* params jokeId* joke id to like
*/
app.post('/api/v1/jokes', function(request, response) {
console.log('Like joke', request.body);
if(request.body.userEmail !== undefined && request.body.jokeId !== undefined) {
let result = jokes.saveJokeFor(request.body.jokeId, request.body.userEmail);
if(result) {
response.status(201).end(); // 201: Created
}
}
response.status(400).end(); // 400: Bad request
});
/*
* Dislike a joke
* params userEmail* user's e-mail
* params jokeId* joke id to dislike
*/
app.delete('/api/v1/jokes', function(request, response) {
console.log('Dislike', request.body);
if(request.body.userEmail !== undefined && request.body.jokeId !== undefined) {
let result = jokes.deleteJokeFor(request.body.jokeId, request.body.userEmail);
if(result) {
response.status(202).end(); // 202: Accepted
}
}
response.status(400).end(); // 400: Bad request
});
/*
* Is specific joke liked or not by specific user
* params userEmail* user's e-mail
* params jokeId* joke id to retreive
*/
app.get('/api/v1/likes/:userEmail/:jokeId', function(request, response) {
console.log('Get like', request.params);
if(request.params.userEmail !== undefined && request.params.jokeId !== undefined) {
let result = jokes.getJokeFor(request.params.jokeId, request.params.userEmail);
response.status(200).json({ isLiked: result }); // 200: Ok
}
response.status(400).end(); // 400: Bad request
})
app.listen(PORT_NUMBER);
console.log('Server started on port: ' + PORT_NUMBER);
......@@ -10,38 +10,38 @@
<header>
<div class="container">
<img src="res/img/home.png"></img>
<img src="res/img/home.png" alt="logo" />
<span><h1>Blagues de Chuck Norris</h1></span>
</div>
</header>
<section class="container console">
<div class="content"><h3>Console de connection</h3><div>
<div class="content console-content" id="user-disconnected">
<span id="console-error"></span>
<input id="user-email" type="email" name="email" placeholder="Entrez votre address e-mail"/>
<div>
<button id="login-btn">Connexion</button>
</div>
</div>
<div class="content console-content" id="user-connected" style="display: none;">
<span id="welcome-user"></span>
<div>
<button id="logout-btn">Déconnexion</button>
</div>
</div>
</section>
<main class="container">
<nav class="content">
<button>Blagues aléatoires</button>
<button>Mes blagues préférées</button>
<button>Meilleures blagues</button>
<button>Nouvelles blagues</button>
<button id="random-jokes">Blagues aléatoires</button>
<button id="my-jokes" disabled>Mes blagues préférées</button>
<button id="best-jokes" disabled>Meilleures blagues</button>
<button id="last-jokes">Nouvelles blagues</button>
</nav>
<section class="content main-content">
<h2>Blagues</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>
<h1 id="main-title"></h1>
<div id="jokes"></div>
</section>
</main>
......@@ -53,5 +53,6 @@
</div>
</footer>
<script type="module" src="res/js/main.js"></script>
</body>
</html>
:root {
--container-width: 768px;
--container-margin: 16px;
--content-padding: 16px;
--navigation-width: 192px;
--header-height: 100px;
......@@ -53,6 +54,7 @@ header > .container {
header > .container > img {
height: 100%;
margin: 0 54px;
}
header > .container > span {
......@@ -70,6 +72,18 @@ header > .container h1 {
font-size: 2em;
}
@media (max-width: 800px) {
header > .container h1 {
font-size: 1em;
}
}
@media (max-width: 500px) {
header > .container img {
display: none;
}
}
/* NAVIGATION */
.console {
......@@ -79,7 +93,41 @@ header > .container h1 {
.console h3 {
margin: 0;
padding: 16px;
padding: var(--content-padding);
}
.console-content {
display: flex;
flex-direction: row;
justify-content: flex-end;
padding: var(--content-padding);
}
.console-content > input, .console-content > span {
min-width: 200px;
margin: 0 var(--content-padding);
}
@media (max-width: 600px) {
.console-content {
flex-direction: column;
}
.console-content > input, .console-content button {
height: 32px;
}
.console-content > input {
margin: 0;
}
.console-content button {
width: 100%;
}
}
#console-error {
color: red;
}
/* MAIN */
......@@ -96,7 +144,7 @@ nav {
display: flex;
flex-direction: column;
margin-bottom: var(--container-margin);
padding: 16px;
padding: var(--content-padding);
width: calc(var(--navigation-width) - 32px);
}
......@@ -105,6 +153,14 @@ nav {
width: calc(var(--container-width) - var(--container-margin) - var(--navigation-width) - 40px);
}
div.joke {
padding: 8px var(--content-padding);
margin: 8px 0;
border-radius: 4px;
background-color: #eee;
}
@media (max-width: 800px) {
main {
flex-direction: column;
......@@ -114,7 +170,7 @@ nav {
nav {
flex-direction: row;
flex-wrap: wrap;
padding: 16px;
padding: var(--content-padding);
width: auto;
}
......@@ -127,6 +183,16 @@ nav {
}
}
@media (max-width: 600px) {
nav {
flex-direction: column;
}
nav > button {
height: 32px;
}
}
/* FOOTER */
body {
......
import events from './events.js';
import login from './login.js';
/*
* Manage changing on display
*/
export default {
init: () => {
// Random jokes
events.listen(events.RANDOM_JOKES_BTN_CLICKED, () => displayTitle('Blagues aléatoires'));
events.listen(events.RANDOM_JOKES_SUCCEED, displayJokes);
// Last jokes
events.listen(events.LAST_JOKES_BTN_CLICKED, () => displayTitle('Dernières blagues'));
events.listen(events.LAST_JOKES_SUCCEED, displayJokes);
// Login & logout
events.listen(events.LOGIN_SUCCEED, displayUserConnected);
events.listen(events.LOGIN_FAILED, displayUserLoginError);
events.listen(events.LOGOUT_SUCCEED, displayUserDisconnected);
// Like & dislike
events.listen([events.DISLIKE_JOKE_SUCCEED, events.LIKE_JOKE_FAILED], activeLikeBtn);
events.listen([
events.LIKE_JOKE_SUCCEED,
events.DISLIKE_JOKE_FAILED,
events.RETREIVE_LIKE_SUCCEED
], activeDislikeBtn);
}
}
function displayUserConnected(data) {
document.getElementById('console-error').style.display = 'none';
document.getElementById('user-disconnected').style.display = 'none';
document.getElementById('user-connected').style.display = 'flex';
document.getElementById('welcome-user').innerHTML = 'Connecté: ' + data.userEmail;
changeDisabledPropertyForAllLikeBtn(false);
}
function displayUserLoginError(data) {
const consoleError = document.getElementById('console-error');
consoleError.style.display = 'block';
consoleError.innerHTML = 'L\'adresse e-mail n\'est pas valide';
}
function displayUserDisconnected() {
document.getElementById('user-connected').style.display = 'none';
document.getElementById('user-disconnected').style.display = 'flex';
changeDisabledPropertyForAllLikeBtn(true);
}
function changeDisabledPropertyForAllLikeBtn(disabled) {
let likeBtnElementArray = document.getElementsByClassName('like-btn');
for (let likeBtnElement of likeBtnElementArray) {
likeBtnElement.disabled = disabled;
}
}
function displayTitle(title) {
document.getElementById('jokes').innerHTML = '<p>Chargement... </p>';
document.getElementById('main-title').innerHTML = title;
}
function displayJokes(jokeArray) {
emptyJokeContainer();
for(let joke of jokeArray) {
displayJoke(joke);
}
}
function emptyJokeContainer() {
document.getElementById('jokes').innerHTML = '';
}
function displayJoke(data) {
let element = createJokeElement(data.id, data.joke);
element.title = data.joke;
document.getElementById('jokes').appendChild(element);
}
function createJokeElement(jokeId, text) {
let jokeElement = document.createElement('div');
jokeElement.className = 'joke';
jokeElement.setAttribute('id', 'joke-id-' + jokeId);
jokeElement.appendChild(createParagraph(text));
jokeElement.appendChild(createLikeBtn(jokeId));
return jokeElement;
}
function createParagraph(jokeText) {
let paragraphElement = document.createElement('p');
paragraphElement.innerHTML = jokeText;
return paragraphElement;
}
function createLikeBtn(jokeId, liked) {
let likeBtnElement = document.createElement('button');
likeBtnElement.className = 'like-btn';
likeBtnElement.innerHTML = liked ? 'Dislike' : 'Like';
likeBtnElement.disabled = !login.isUserConnected();
likeBtnElement.onclick = () => sendeventsAndToggleButton(likeBtnElement, jokeId);
return likeBtnElement;
}
function sendeventsAndToggleButton(likeBtnElement, jokeId) {
if (likeBtnElement.innerHTML === 'Like') {
events.send(events.LIKE_JOKE_BTN_CLICKED, { jokeId });
} else {
events.send(events.DISLIKE_JOKE_BTN_CLICKED, { jokeId });
}
likeBtnElement.disabled = !login.isUserConnected();
}
function activeLikeBtn(data) {
const jokeElement = document.getElementById('joke-id-' + data.jokeId);
if (jokeElement) {
const likeBtnElement = jokeElement.getElementsByClassName('like-btn')[0];
likeBtnElement.innerHTML = 'Like';
likeBtnElement.disabled = false;
}
}
function activeDislikeBtn(data) {
const jokeElement = document.getElementById('joke-id-' + data.jokeId);
if (jokeElement) {
const dislikeBtnElement = jokeElement.getElementsByClassName('like-btn')[0];
dislikeBtnElement.innerHTML = 'Dislike';
dislikeBtnElement.disabled = false;
}
}
/*
* Manage events listening and subscribing
*/
export default {
// Login
LOGIN_BTN_CLICKED: 'User clicked on login button',
LOGIN_SUCCEED: 'User login succeed',
LOGIN_FAILED: 'User login failed',
// Logout
LOGOUT_BTN_CLICKED: 'User clicked on logout button',
LOGOUT_SUCCEED: 'User logout succeed',
// My jokes
MY_JOKES_BTN_CLICKED: 'User clicked on my-jokes button',
// Random jokes
RANDOM_JOKES_BTN_CLICKED: 'User clicked on random-jokes button',
RANDOM_JOKES_SUCCEED: 'Random jokes retreiving from API succeed',
RANDOM_JOKES_FAILED: 'Random jokes retreiving from API failed',
// Best jokes
BEST_JOKES_BTN_CLICKED: 'User clicked on best-jokes button',
// Last jokes
LAST_JOKES_BTN_CLICKED: 'User clicked on last-jokes button',
LAST_JOKES_SUCCEED: 'Last jokes retreiving from API succeed',
LAST_JOKES_FAILED: 'Last jokes retreiving from API failed',
// Like joke
LIKE_JOKE_BTN_CLICKED: 'User clicked on like joke button',
LIKE_JOKE_SUCCEED: 'Like joke sending to API succeed',
LIKE_JOKE_FAILED: 'Like joke sending to API failed',
// Dislike joke
DISLIKE_JOKE_BTN_CLICKED: 'User clicked on dislike joke button',
DISLIKE_JOKE_SUCCEED: 'Dislike joke sending to API succeed',
DISLIKE_JOKE_FAILED: 'Dislike joke sending to API failed',
RETREIVE_LIKE_SUCCEED: 'Retreive like for joke id',
send: (action, data = null) => {
const event = new CustomEvent(action, { detail: data });
document.body.dispatchEvent(event);
if (data) {
console.log(action, data);
} else {
console.log(action);
}
},