Added basic log-in setup and upgraded overall visuals

This commit is contained in:
Ninjdai 2023-12-06 19:11:14 +01:00
parent 5a8e65a471
commit dd32138229
19 changed files with 452 additions and 38 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
package-lock.json
*.sqlite
.env

View File

@ -1,6 +1,7 @@
const { launchWeb } = require('./web.js'); const { launchWeb } = require("./web.js");
const { EventEmitter } = require('events'); const { EventEmitter } = require("events");
const Sequelize = require('sequelize'); const Sequelize = require("sequelize");
require('dotenv').config()
const sequelize = new Sequelize("database", "user", "password", { const sequelize = new Sequelize("database", "user", "password", {
host: "localhost", host: "localhost",
@ -21,35 +22,41 @@ const contactsDB = sequelize.define("contacts", {
type: Sequelize.STRING, type: Sequelize.STRING,
}, },
called: { called: {
type: Sequelize.INTEGER,// 0: not called - 1: called - 2: ongoing call (- 3: no response ?) type: Sequelize.INTEGER, // 0: not called - 1: called - 2: ongoing call (- 3: no response ?)
defaultValue: false, defaultValue: false,
}, },
vote: { vote: {
type: Sequelize.BOOLEAN, type: Sequelize.BOOLEAN,
defaultValue: false, defaultValue: false,
} },
}); });
contactsDB.sync(); contactsDB.sync();
global.database = {}; global.database = {};
global.database.contacts = contactsDB global.database.contacts = contactsDB;
const submitEvent = new EventEmitter(); const submitEvent = new EventEmitter();
launchWeb(submitEvent); launchWeb(submitEvent);
submitEvent.on('call', async (call) => { submitEvent.on("call", async (call) => {
let vote; let vote;
if(call.vote) { if (call.vote) {
console.log(`${call.phone} va voter`); console.log(`${call.phone} va voter`);
vote = 1; vote = 1;
} else { } else {
console.log(`${call.phone} ne va pas voter`); console.log(`${call.phone} ne va pas voter`);
vote = 0; vote = 0;
} }
await global.database.contacts.update({ vote: vote }, { where: { phone: call.phone } }) await global.database.contacts.update(
{ vote: vote, called: 1 },
{ where: { phone: call.phone } },
);
}); });
submitEvent.on('add', (request) => { submitEvent.on("add", (request) => {
console.log(request); console.log(request);
}); });
global.events = {
submitEvent: submitEvent,
}

View File

@ -9,8 +9,11 @@
"author": "Ninjdai", "author": "Ninjdai",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dotenv": "^16.3.1",
"express": "^4.18.2", "express": "^4.18.2",
"express-session": "^1.17.3",
"sequelize": "^6.35.1", "sequelize": "^6.35.1",
"serve-favicon": "^2.5.0",
"sqlite3": "^5.1.6" "sqlite3": "^5.1.6"
} }
} }

View File

@ -1,4 +1,6 @@
function generateCallHTML(phoneNumber, name, callcount) { const { navbar } = require('../../utils/navbar.js');
function generateCallHTML(phoneNumber, name, callcount, session) {
const head = `<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><link rel="stylesheet" type="text/css" href="/assets/css/form.css" /><title>AutoCallMenu</title></head>`; const head = `<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><link rel="stylesheet" type="text/css" href="/assets/css/form.css" /><title>AutoCallMenu</title></head>`;
const form = ` const form = `
<form action="/inputs/call" method="post"> <form action="/inputs/call" method="post">
@ -12,7 +14,7 @@ function generateCallHTML(phoneNumber, name, callcount) {
<button class="submit" type="submit">CONFIRMER</button> <button class="submit" type="submit">CONFIRMER</button>
</form> </form>
`; `;
const body = `<body><h1>Bienvenue :3</h1><h2>Appel: ${name} - ${phoneNumber}</h2><p>${callcount} appels restants</p>${form}</body>`; const body = `<body>${navbar(session)}<h1>Bienvenue :3</h1><h2>Appel: ${name} - ${phoneNumber}</h2><p>${callcount} appels restants</p>${form}</body>`;
return `<!DOCTYPE html><html>${head}${body}</html>`; return `<!DOCTYPE html><html>${head}${body}</html>`;
} }

View File

@ -0,0 +1,8 @@
module.exports = {
path: "/inputs/add",
requiresLogin: true,
type: "post",
async execute(request, response) {
global.events.submitEvent.emit("add", request.body);
},
}

View File

@ -0,0 +1,9 @@
module.exports = {
path: "/inputs/call",
requiresLogin: true,
type: "post",
async execute(request, response) {
global.events.submitEvent.emit("call", request.body);
response.redirect("/calls");
},
}

View File

@ -0,0 +1,36 @@
const { generateCallHTML } = require("../../../../src/html/callHTML.js");
const { getRandomUser } = require('../../../../src/database/getUser.js');
const { navbar } = require('../../../../utils/navbar.js');
module.exports = {
path: "/calls",
requiresLogin: true,
type: "get",
async execute(request, response) {
const res = await generateCallResponse(request.session);
response.send(res.replaceAll("<NAVBAR>", navbar(request.session)));
},
}
async function generateCallResponse(session) {
const user = await getRandomUser();
let callHTML;
if (user) callHTML = generateCallHTML(user.phone, user.name, user.count, session);
else callHTML = `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link rel="stylesheet" type="text/css" href="/assets/css/form.css" />
<title>AutoCallMenu</title>
</head>
<body>
<NAVBAR>
<h1>Bienvenue :3</h1>
<h2>0 appels restants !</h2>
</body>
</html>`;
return callHTML;
}

View File

@ -0,0 +1,11 @@
const { readFile } = require('fs').promises;
const { navbar } = require('../../../../utils/navbar.js');
module.exports = {
path: "/",
type: "get",
async execute(request, response) {
const res = await readFile(`${process.env.WWW}/index.html`, "utf8")
response.send(res.replaceAll("<NAVBAR>", navbar(request.session)));
},
}

View File

@ -0,0 +1,15 @@
const { readFile } = require('fs').promises;
const { navbar } = require('../../../../utils/navbar.js');
module.exports = {
path: "/login",
type: "get",
async execute(request, response, args) {
if (request.session.user) return response.redirect("/");
if(args?.fail) {
console.log('Failed login attempt !')
}
const res = await readFile(`${process.env.WWW}/login.html`, "utf8");
response.send(res.replaceAll("<NAVBAR>", navbar(request.session)));
},
}

View File

@ -0,0 +1,16 @@
const { readFile } = require('fs').promises;
module.exports = {
path: "/login",
type: "post",
async execute(request, response) {
const { username, password } = request.body;
console.log(request.body);
if (username == "ninjdai" && password == "azerty") {
request.session.user = { username };
response.redirect("/");
} else {
response.redirect("/login?fail=true");
}
},
}

View File

@ -0,0 +1,9 @@
module.exports = {
path: "/logout",
type: "get",
async execute(request, response) {
request.session.destroy(() => {
response.redirect("/login");
});
},
}

29
utils/handler.js Normal file
View File

@ -0,0 +1,29 @@
const fs = require('fs');
const pagesPath = "./src/html/pages/";
function deployHandler() {
global.handler = {
get: {},
post: {},
};
let numberOfPages = 0;
const PagesCategories = fs
.readdirSync(pagesPath)
.filter((file) => !file.includes("."));
for (const category of PagesCategories) {
const pageFiles = fs
.readdirSync(`${pagesPath}${category}`)
.filter((file) => file.endsWith(".js"));
for (const file of pageFiles) {
const page = require(`.${pagesPath}${category}/${file}`);
global.handler[page.type][page.path] = page;
console.log(
`\x1b[32mChargement de POST: ${page.path} !\x1b[0m`,
);
numberOfPages++;
console.log(numberOfPages + " pages chargées ! ");
}
}
}
module.exports = { deployHandler };

18
utils/navbar.js Normal file
View File

@ -0,0 +1,18 @@
module.exports = {
navbar(session) {
let logField;
if(session.user) {
logField = `<li style="float:right"><a class="active" href="/logout">${session.user.username} ✖ </a></li>`
} else {
logField = `<li style="float:right"><a class="active" href="/login">Login</a></li>`;
}
return `
<link rel="stylesheet" type="text/css" href="/assets/css/navbar.css" />
<ul>
<li><a href="/">Accueil</a></li>
<li><a href="/calls">Phoning</a></li>
<li><a href="#nope">Nope</a></li>
${logField}
</ul>`;
}
}

76
web.js
View File

@ -1,41 +1,67 @@
const express = require('express'); const express = require("express");
const { readFile } = require('fs').promises; const session = require("express-session");
const { generateCallHTML } = require('./src/html/callHTML.js'); const { readFile } = require("fs").promises;
const { getRandomUser } = require('./src/database/getUser.js'); const { deployHandler } = require("./utils/handler.js");
const favicon = require("serve-favicon");
function launchWeb(submitEvent){ function launchWeb() {
const app = express(); const app = express();
app.use(express.json()); // Used to parse JSON bodies app.use(express.json()); // Used to parse JSON bodies
app.use(express.urlencoded({ extended: false })); //Parse URL-encoded bodies app.use(express.urlencoded({ extended: false })); //Parse URL-encoded bodies
app.use(
session({
secret: process.env.SECRET,
resave: false,
saveUninitialized: true,
}),
);
app.use('/assets', express.static('./www/assets')) app.use("/assets", express.static(`${process.env.WWW}/assets`));
app.use(favicon(`${process.env.WWW}/assets/images/favicon.ico`));
app.get('/', async (request, response) => { deployHandler();
response.send(await readFile('./www/index.html', 'utf8'));
}); app.post("*", async (request, response) => {
app.get('/call', async (request, response) => { console.log("POST: " + request.originalUrl);
response.send(await generateCallResponse()); if (!global.handler.post[request.originalUrl]) return;
if (global.handler.post[request.originalUrl].requiresLogin && !request.session.user) {
return response.redirect("/login");
}
return await global.handler.post[request.originalUrl].execute(
request,
response,
);
}); });
app.post('/inputs/call', async (request, response) => { app.get("*", async (request, response) => {
submitEvent.emit('call', request.body);
response.send(await generateCallResponse()); const [path, args] = parseURL(request.originalUrl);
}); //console.log(parseURL(request.originalUrl));
app.post('/inputs/add', async (request, response) => { console.log(`GET: ${path}${args ? "?"+args : ""}`);
submitEvent.emit('add', request);
if (!global.handler.get[path]) return;
if (global.handler.get[path].requiresLogin && !request.session.user) {
return response.redirect("/login");
}
return await global.handler.get[path].execute(
request,
response,
args,
);
}); });
const PORT = process.env.PORT || 3000; const PORT = process.env.PORT || 3000;
app.listen(PORT, () => { console.log(`App available at http://localhost:${PORT}`) }); app.listen(PORT, () => {
}; console.log(`App available at http://localhost:${PORT}`);
});
}
async function generateCallResponse(){ function parseURL(URL) {
const user = await getRandomUser(); const spURL = URL.split("?");
let callHTML; const path = spURL[0];
if(user) callHTML = generateCallHTML( user.phone, user.name, user.count ); const args = spURL[1];
else callHTML = `Plus d'utilisateur à appeler !`; return [path, args];
return callHTML;
} }
module.exports = { launchWeb }; module.exports = { launchWeb };

121
www/assets/css/login.css Normal file
View File

@ -0,0 +1,121 @@
body {font-family: Arial, Helvetica, sans-serif;}
/* Full-width input fields */
input[type=text], input[type=password] {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: inline-block;
border: 1px solid #ccc;
box-sizing: border-box;
}
/* Set a style for all buttons */
button {
background-color: #04AA6D;
color: white;
padding: 14px 20px;
margin: 8px 0;
border: none;
cursor: pointer;
width: 100%;
}
button:hover {
opacity: 0.8;
}
/* Extra styles for the cancel button */
.cancelbtn {
width: auto;
padding: 10px 18px;
background-color: #f44336;
}
/* Center the image and position the close button */
.imgcontainer {
text-align: center;
margin: 24px 0 12px 0;
position: relative;
}
img.avatar {
width: 40%;
border-radius: 50%;
}
.container {
padding: 16px;
}
span.psw {
float: right;
padding-top: 16px;
}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
padding-top: 60px;
}
/* Modal Content/Box */
.modal-content {
background-color: #fefefe;
margin: 5% auto 15% auto; /* 5% from the top, 15% from the bottom and centered */
border: 1px solid #888;
width: 80%; /* Could be more or less, depending on screen size */
}
/* The Close Button (x) */
.close {
position: absolute;
right: 25px;
top: 0;
color: #000;
font-size: 35px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: red;
cursor: pointer;
}
/* Add Zoom Animation */
.animate {
-webkit-animation: animatezoom 0.6s;
animation: animatezoom 0.6s
}
@-webkit-keyframes animatezoom {
from {-webkit-transform: scale(0)}
to {-webkit-transform: scale(1)}
}
@keyframes animatezoom {
from {transform: scale(0)}
to {transform: scale(1)}
}
/* Change styles for span and cancel button on extra small screens */
@media screen and (max-width: 300px) {
span.psw {
display: block;
float: none;
}
.cancelbtn {
width: 100%;
}
}

68
www/assets/css/navbar.css Normal file
View File

@ -0,0 +1,68 @@
ul {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #333;
}
li {
float: left;
}
li a {
display: block;
color: white;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
li a:hover:not(.active) {
background-color: #111;
}
.active {
background-color: #04AA6D;
}
.dropbtn {
background-color: #4CAF50;
color: white;
padding: 16px;
font-size: 16px;
border: none;
cursor: pointer;
}
.dropdown {
position: relative;
display: inline-block;
}
.dropdown-content {
display: none;
position: absolute;
right: 0;
background-color: #f9f9f9;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
z-index: 1;
}
.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}
.dropdown-content a:hover {background-color: #f1f1f1;}
.dropdown:hover .dropdown-content {
display: block;
}
.dropdown:hover .dropbtn {
background-color: #3e8e41;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

View File

@ -7,7 +7,8 @@
<title>AutoCallMenu</title> <title>AutoCallMenu</title>
</head> </head>
<body> <body>
<NAVBAR>
<h1>Bienvenue :3</h1> <h1>Bienvenue :3</h1>
<a href="/call"><p>Accéder aux téléphones</p></a> <a href="/calls"><p>Accéder aux téléphones</p></a>
</body> </body>
</html> </html>

31
www/login.html Normal file
View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title></title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/assets/css/login.css" rel="stylesheet">
</head>
<body>
<NAVBAR>
<form class="modal-content animate" action="/login" method="post">
<div class="container">
<label for="uname"><b>Username</b></label>
<input type="text" placeholder="Enter Username" name="username" required>
<label for="psw"><b>Password</b></label>
<input type="password" placeholder="Enter Password" name="password" required>
<button type="submit">Login</button>
<!--<label>
<input type="checkbox" checked="checked" name="remember"> Remember me
</label>-->
</div>
<div class="container" style="background-color:#f1f1f1">
<a href="/"><button type="button" class="cancelbtn">Cancel</button></a>
<!--<span class="psw">Forgot <a href="#">password?</a></span>-->
</div>
</form>
</body>
</html>