web: ComplainIO
This commit is contained in:
parent
f533f777a8
commit
6013199f17
37
web/complainio/complainio/challenge/Dockerfile
Normal file
37
web/complainio/complainio/challenge/Dockerfile
Normal file
@ -0,0 +1,37 @@
|
||||
FROM node:latest
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
# Sorry for that, carbone js library requires libreoffice to work
|
||||
RUN apt update && \
|
||||
apt install libxinerama1 libfontconfig1 libdbus-glib-1-2 libcairo2 libcups2 libglu1-mesa libsm6 netcat-traditional gcc -y && \
|
||||
wget https://downloadarchive.documentfoundation.org/libreoffice/old/7.5.1.1/deb/x86_64/LibreOffice_7.5.1.1_Linux_x86-64_deb.tar.gz && \
|
||||
tar -zxvf LibreOffice_7.5.1.1_Linux_x86-64_deb.tar.gz && \
|
||||
rm LibreOffice_7.5.1.1_Linux_x86-64_deb.tar.gz && \
|
||||
cd LibreOffice_7.5.1.1_Linux_x86-64_deb/DEBS && \
|
||||
dpkg -i *.deb && \
|
||||
mkdir /tmp/files && \
|
||||
chmod 777 /tmp/files/
|
||||
|
||||
WORKDIR /build/
|
||||
COPY getflag.c .
|
||||
RUN gcc getflag.c -o getflag && \
|
||||
mv getflag / && \
|
||||
chmod u+s /getflag
|
||||
COPY ./flag.txt /root/
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
COPY ./odt_templates/ /tmp/files/
|
||||
|
||||
COPY ./src/ .
|
||||
|
||||
RUN npm i --omit=dev && npm cache clean --force && \
|
||||
rm -fr /build/ && \
|
||||
chown -R node:node /app/
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
USER node
|
||||
|
||||
CMD ["node","index.js"]
|
1
web/complainio/complainio/challenge/flag.txt
Normal file
1
web/complainio/complainio/challenge/flag.txt
Normal file
@ -0,0 +1 @@
|
||||
HERO{FAKE_FLAG}
|
21
web/complainio/complainio/challenge/getflag.c
Normal file
21
web/complainio/complainio/challenge/getflag.c
Normal file
@ -0,0 +1,21 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
int main() {
|
||||
const char *file_path = "/root/flag.txt";
|
||||
|
||||
FILE *file = fopen(file_path, "r");
|
||||
if (file == NULL) {
|
||||
perror("An error occured, ping worty on discord");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
char ch;
|
||||
while ((ch = fgetc(file)) != EOF) {
|
||||
putchar(ch);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
BIN
web/complainio/complainio/challenge/odt_templates/baguette.odt
Normal file
BIN
web/complainio/complainio/challenge/odt_templates/baguette.odt
Normal file
Binary file not shown.
BIN
web/complainio/complainio/challenge/odt_templates/flag.odt
Normal file
BIN
web/complainio/complainio/challenge/odt_templates/flag.odt
Normal file
Binary file not shown.
BIN
web/complainio/complainio/challenge/odt_templates/too_loud.odt
Normal file
BIN
web/complainio/complainio/challenge/odt_templates/too_loud.odt
Normal file
Binary file not shown.
@ -0,0 +1,15 @@
|
||||
const db = require("../models");
|
||||
const Complains = db.Complains;
|
||||
const utils = require("../utils");
|
||||
|
||||
exports.getAll = async (req, res) => {
|
||||
let decoded = utils.verify_jwt(req);
|
||||
if(decoded.username) {
|
||||
res.status(200).send(await Complains.findAll());
|
||||
} else {
|
||||
res.status(401).send({
|
||||
data:
|
||||
"Invalid token."
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
const db = require("../models");
|
||||
const utils = require("../utils");
|
||||
const fs = require("fs");
|
||||
const carbone = require('carbone');
|
||||
const Files = db.Files;
|
||||
const Users = db.Users;
|
||||
|
||||
exports.createTemplate = async (req, res) => {
|
||||
let decoded = utils.verify_jwt(req);
|
||||
if(decoded.username) {
|
||||
if(req.body.uuid && req.body.id && typeof(req.body.uuid) === "string" && typeof(req.body.id) == "number") {
|
||||
const file = await Files.findOne({where: {uuid: req.body.uuid}});
|
||||
let current_user = await Users.findOne({where: {username: decoded.username}});
|
||||
if(current_user.id != req.body.id) {
|
||||
res.status(401).send({
|
||||
data:
|
||||
"Unauthorized."
|
||||
});
|
||||
} else {
|
||||
if(req.body.firstname !== current_user.firstname || req.body.lastname !== current_user.lastname) {
|
||||
await utils.update_user(req.body, decoded);
|
||||
current_user = await Users.findOne({where: {username: decoded.username}});
|
||||
}
|
||||
if(file.uuid) {
|
||||
var data = {
|
||||
firstname: current_user.firstname,
|
||||
lastname: current_user.lastname
|
||||
};
|
||||
carbone.render(file.path, data, function(err, result){
|
||||
if (err) {
|
||||
res.status(500).send({
|
||||
data:
|
||||
"Internal Server Error."
|
||||
});
|
||||
} else {
|
||||
res.status(200).send({
|
||||
data:
|
||||
result.toString('base64')
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(404).send({
|
||||
data:
|
||||
"File not found."
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"Bad request."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(401).send({
|
||||
data:
|
||||
"Invalid token."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.getProfilePicture = async (req, res) => {
|
||||
if(req.query.token !== undefined && typeof(req.query.token) === "string") {
|
||||
let decoded = utils.verify_jwt(req, req.query.token);
|
||||
if(decoded.username) {
|
||||
if(req.params.uuid && typeof(req.params.uuid) === "string") {
|
||||
const file = await Files.findOne({where: {uuid: req.params.uuid}});
|
||||
if(file && file.uuid) {
|
||||
const current_user = await Users.findOne({where: {username: decoded.username}});
|
||||
if(file.user_id === current_user.id) {
|
||||
try {
|
||||
var s = fs.createReadStream(file.path);
|
||||
s.on('open', function() {
|
||||
res.set('Content-Type', 'image/png');
|
||||
s.pipe(res);
|
||||
});
|
||||
s.on('error', function() {
|
||||
res.set('Content-Type', 'text/plain');
|
||||
res.status(500).end('Internal Server Error');
|
||||
})
|
||||
} catch(err) {
|
||||
res.status(500).send({
|
||||
data:
|
||||
"Internal Server Error."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(401).send({
|
||||
data:
|
||||
"Unauthorized."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(404).send({
|
||||
data:
|
||||
"File not found."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"Bad request."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(401).send({
|
||||
data:
|
||||
"Invalid token."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"Bad request."
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
const db = require("../models");
|
||||
const { createHash } = require('crypto');
|
||||
const utils = require("../utils");
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const Users = db.Users;
|
||||
const Files = db.Files;
|
||||
const UPLOADS_DIR = "/tmp/files/"
|
||||
const fs = require("fs");
|
||||
|
||||
exports.login = async (req, res) => {
|
||||
if(req.body.username !== undefined && req.body.password !== undefined &&
|
||||
typeof(req.body.username) === "string" && typeof(req.body.password) === "string") {
|
||||
const user = await Users.findOne({where: {username: req.body.username}});
|
||||
if(user === null) {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"The username or password is incorrect."
|
||||
});
|
||||
} else {
|
||||
if(createHash('sha256').update(req.body.password).digest('hex') === user.password) {
|
||||
res.status(200).send({
|
||||
data:
|
||||
"User connected",
|
||||
token:
|
||||
utils.sign_jwt({username: req.body.username})
|
||||
})
|
||||
} else {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"The username or password is incorrect."
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"Bad request."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.register = async (req, res) => {
|
||||
if(req.body.username !== undefined && req.body.password !== undefined && req.body.firstname !== undefined && req.body.lastname !== undefined &&
|
||||
typeof(req.body.username) === "string" && typeof(req.body.password) === "string" && typeof(req.body.firstname) === "string" && typeof(req.body.lastname) === "string") {
|
||||
const user = await Users.findOne({where: {username: req.body.username}});
|
||||
if(user === null) {
|
||||
const created_user = await Users.create({
|
||||
username: req.body.username,
|
||||
password: createHash('sha256').update(req.body.password).digest('hex'),
|
||||
firstname: req.body.firstname,
|
||||
lastname: req.body.lastname
|
||||
});
|
||||
if(created_user.id === null) {
|
||||
res.status(500).send({
|
||||
data:
|
||||
"Internal Server Error."
|
||||
});
|
||||
} else {
|
||||
res.status(200).send({
|
||||
data:
|
||||
"User created."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"User already exists."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"Bad request."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.me = async (req, res) => {
|
||||
let decoded = utils.verify_jwt(req);
|
||||
if(decoded.username) {
|
||||
const user = await Users.findOne({where: {username: decoded.username}, attributes: ['id', 'username', 'firstname', 'lastname']});
|
||||
if(user) {
|
||||
let infos = {"id": user.id, "lastname": user.lastname, "firstname": user.firstname, "username": user.username, "pp": null};
|
||||
const potential_picture = await Files.findOne({where: {user_id: user.id}, attributes: ['uuid']});
|
||||
if(potential_picture) {
|
||||
infos["pp"] = potential_picture.uuid;
|
||||
}
|
||||
res.status(200).send(infos);
|
||||
} else {
|
||||
res.status(500).send({
|
||||
data:
|
||||
"Internal Server Error."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(401).send({
|
||||
data:
|
||||
"Then token is invalid or was not provided."
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
exports.profile = async (req, res) => {
|
||||
if(req.body.id !== undefined && req.body.username !== undefined && req.body.firstname !== undefined && req.body.lastname !== undefined &&
|
||||
typeof(req.body.id) === "number" && typeof(req.body.username) === "string" && typeof(req.body.firstname) === "string" && typeof(req.body.lastname) === "string") {
|
||||
let user_database_content = await Users.findByPk(req.body.id);
|
||||
if(user_database_content === null) {
|
||||
res.status(404).send({
|
||||
data: "User not found."
|
||||
});
|
||||
} else {
|
||||
let decoded = utils.verify_jwt(req);
|
||||
if(decoded.username) {
|
||||
let current_user = await Users.findOne({where: {username: decoded.username}});
|
||||
if(current_user.id != req.body.id) {
|
||||
res.status(401).send({
|
||||
data:
|
||||
"Unauthorized."
|
||||
})
|
||||
} else {
|
||||
let result = await utils.update_user(req.body, decoded);
|
||||
res.status(result[0]).send({
|
||||
data:
|
||||
result[1]
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(401).send({
|
||||
data:
|
||||
"Invalid token."
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"Bad request."
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
exports.upload = async (req, res) => {
|
||||
if(req.body.token !== undefined && typeof(req.body.token) === "string") {
|
||||
let decoded = utils.verify_jwt(req, req.body.token);
|
||||
if(decoded.username) {
|
||||
if(req.files.picture !== undefined) {
|
||||
let profile_picture_image = req.files.picture;
|
||||
let new_uuid = uuidv4();
|
||||
let filename = UPLOADS_DIR + new_uuid+'.png';
|
||||
profile_picture_image.mv(filename);
|
||||
const current_user = await Users.findOne({where: {username: decoded.username}});
|
||||
const has_current_pp = await Files.findOne({where: {user_id: current_user.id}});
|
||||
if(has_current_pp) {
|
||||
try {
|
||||
fs.unlinkSync(has_current_pp.path);
|
||||
await has_current_pp.destroy();
|
||||
} catch(err) {
|
||||
res.status(500).send({
|
||||
data:
|
||||
"Internal Server Error."
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
const created_file = await Files.create({uuid: new_uuid, path: filename, user_id: current_user.id});
|
||||
if(created_file.id === null) {
|
||||
res.status(500).send({
|
||||
data:
|
||||
"Internal Server Error."
|
||||
});
|
||||
} else {
|
||||
res.redirect("/profile");
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"Bad request."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(401).send({
|
||||
data:
|
||||
"Invalid token."
|
||||
});
|
||||
}
|
||||
} else {
|
||||
res.status(400).send({
|
||||
data:
|
||||
"Bad request."
|
||||
});
|
||||
}
|
||||
}
|
BIN
web/complainio/complainio/challenge/src/database.sqlite
Normal file
BIN
web/complainio/complainio/challenge/src/database.sqlite
Normal file
Binary file not shown.
28
web/complainio/complainio/challenge/src/index.js
Normal file
28
web/complainio/complainio/challenge/src/index.js
Normal file
@ -0,0 +1,28 @@
|
||||
const express = require('express');
|
||||
const fileUpload = require('express-fileupload');
|
||||
const app = express();
|
||||
const path = require("path");
|
||||
const bodyParser = require("body-parser");
|
||||
app.use(express.json());
|
||||
app.use(fileUpload());
|
||||
app.use(express.static(path.join(__dirname,'public')));
|
||||
app.disable('x-powered-by');
|
||||
require("./routes/complain.routes")(app);
|
||||
|
||||
const PORT = 3000;
|
||||
|
||||
app.get('/', (_, res) => {
|
||||
res.sendFile(path.join(__dirname,'/views/index.html'));
|
||||
});
|
||||
|
||||
app.get('/profile', (_, res) => {
|
||||
res.sendFile(path.join(__dirname,'/views/profile.html'));
|
||||
})
|
||||
|
||||
app.get('/complain', (_, res) => {
|
||||
res.sendFile(path.join(__dirname,'/views/complain.html'));
|
||||
})
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Sales management application is running on port ${PORT}.`);
|
||||
});
|
@ -0,0 +1,21 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const Complains = sequelize.define("complains", {
|
||||
id: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true
|
||||
},
|
||||
reason: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
file_id: {
|
||||
type: DataTypes.STRING
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: false,
|
||||
updatedAt: false,
|
||||
createdAt: false
|
||||
});
|
||||
|
||||
return Complains;
|
||||
};
|
@ -0,0 +1,24 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const Files = sequelize.define("files", {
|
||||
uuid: {
|
||||
type: DataTypes.STRING,
|
||||
primaryKey: true
|
||||
},
|
||||
path: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
user_id: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
references: 'users',
|
||||
referencesKey: 'id'
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: false,
|
||||
updatedAt: false,
|
||||
createdAt: false
|
||||
});
|
||||
|
||||
return Files;
|
||||
};
|
16
web/complainio/complainio/challenge/src/models/index.js
Normal file
16
web/complainio/complainio/challenge/src/models/index.js
Normal file
@ -0,0 +1,16 @@
|
||||
const Sequelize = require("sequelize");
|
||||
const sequelize = new Sequelize({
|
||||
dialect: "sqlite",
|
||||
storage: process.env.DB_PATH,
|
||||
operatorsAliases: false
|
||||
});
|
||||
const db = {};
|
||||
|
||||
db.Sequelize = Sequelize;
|
||||
db.sequelize = sequelize;
|
||||
|
||||
db.Files = require("./files.model.js")(sequelize, Sequelize);
|
||||
db.Users = require("./users.model.js")(sequelize, Sequelize);
|
||||
db.Complains = require("./complains.model.js")(sequelize, Sequelize);
|
||||
|
||||
module.exports = db;
|
@ -0,0 +1,28 @@
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const Users = sequelize.define("users", {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
username: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
password: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
firstname: {
|
||||
type: DataTypes.STRING
|
||||
},
|
||||
lastname: {
|
||||
type: DataTypes.STRING
|
||||
}
|
||||
},
|
||||
{
|
||||
timestamps: false,
|
||||
updatedAt: false,
|
||||
createdAt: false
|
||||
});
|
||||
|
||||
return Users;
|
||||
};
|
2414
web/complainio/complainio/challenge/src/package-lock.json
generated
Normal file
2414
web/complainio/complainio/challenge/src/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
web/complainio/complainio/challenge/src/package.json
Normal file
20
web/complainio/complainio/challenge/src/package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "carboneio",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"carbone": "3.5.5",
|
||||
"express": "^4.19.2",
|
||||
"express-fileupload": "^1.5.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"nodejs-base64": "^2.0.0",
|
||||
"sequelize": "^6.37.3",
|
||||
"sqlite3": "^5.1.7"
|
||||
}
|
||||
}
|
7
web/complainio/complainio/challenge/src/public/css/bootstrap.min.css
vendored
Normal file
7
web/complainio/complainio/challenge/src/public/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
7
web/complainio/complainio/challenge/src/public/js/bootstrap.min.js
vendored
Normal file
7
web/complainio/complainio/challenge/src/public/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
101
web/complainio/complainio/challenge/src/public/js/complain.js
Normal file
101
web/complainio/complainio/challenge/src/public/js/complain.js
Normal file
@ -0,0 +1,101 @@
|
||||
let uuid;
|
||||
|
||||
function load_infos(){
|
||||
$.ajax({
|
||||
url: "/api/complains",
|
||||
type: "GET",
|
||||
headers: {
|
||||
"Authorization": "Bearer "+localStorage.getItem("token")
|
||||
},
|
||||
success: function(msg){
|
||||
let html = "";
|
||||
for(var i=0; i<msg.length; i++) {
|
||||
html += '<div class="col-md-4 mb-4"><div class="card h-100"><div class="card-body"><h5 class="card-title">Complain template n°'+msg[i]["id"]+'</h5><p class="card-text">'+msg[i]["reason"]+'</p><a href="#" class="btn btn-primary" onclick="template(\''+msg[i]["file_id"]+'\')">Create template</a></div></div></div>'
|
||||
}
|
||||
$("#complains").append(html);
|
||||
},
|
||||
error: function(msg){
|
||||
$("#error_content").text("Unexcepted error, please try log in again !");
|
||||
$("#unexceptErrorModal").modal('show');
|
||||
setTimeout(function(){
|
||||
localStorage.removeItem("token");
|
||||
window.location.href = "/#popupError=1";
|
||||
},3000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
$( document ).ready(async function() {
|
||||
if(!localStorage.getItem("token")) {
|
||||
window.location.href = "/#popupLogin=1";
|
||||
}
|
||||
load_infos();
|
||||
});
|
||||
|
||||
function template(uuid) {
|
||||
$.ajax({
|
||||
url: "/api/me",
|
||||
type: "GET",
|
||||
headers: {
|
||||
"Authorization": "Bearer "+localStorage.getItem("token")
|
||||
},
|
||||
success: function(msg){
|
||||
$("#firstname").val(msg["firstname"]);
|
||||
$("#lastname").val(msg["lastname"]);
|
||||
$("#user_id").val(msg["id"]);
|
||||
},
|
||||
error: function(msg){
|
||||
$("#error_content").text("Unexcepted error, please try log in again !")
|
||||
$("#unexceptErrorModal").modal('show');
|
||||
setTimeout(function(){
|
||||
localStorage.removeItem("token");
|
||||
window.location.href = "/#popupError=1";
|
||||
},3000)
|
||||
}
|
||||
})
|
||||
$("#uuid").val(uuid);
|
||||
$("#token").val(localStorage.getItem("token"));
|
||||
$("#changeModal").modal('show');
|
||||
}
|
||||
|
||||
function create_template(){
|
||||
$("#changeModal").modal("hide");
|
||||
$("#msgTemplateTxt").remove()
|
||||
if($("#firstname").val().length == 0 || $("#lastname").val().length == 0) {
|
||||
$("#msgTemplate").append("<p id='msgTemplateTxt' style='color: red;'>You must fill in all fields.</p>");
|
||||
return;
|
||||
}
|
||||
var data = {"firstname": $("#firstname").val(), "lastname": $("#lastname").val(), "uuid": $("#uuid").val(), "id": parseInt($("#user_id").val())}
|
||||
$.ajax({
|
||||
url: "/api/create_template",
|
||||
type: "POST",
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
headers: {
|
||||
"Authorization": "Bearer "+localStorage.getItem("token")
|
||||
},
|
||||
success: function(msg){
|
||||
var a = document.createElement("a");
|
||||
a.href= "data:application/octet-stream;base64,"+msg["data"];
|
||||
a.download = "complain.odt";
|
||||
a.click();
|
||||
},
|
||||
error: function(msg){
|
||||
$("#error_content").text("Unexcepted error, please try log in again !")
|
||||
$("#unexceptErrorModal").modal('show');
|
||||
setTimeout(function(){
|
||||
localStorage.removeItem("token");
|
||||
window.location.href = "/#popupError=1";
|
||||
},3000)
|
||||
}
|
||||
})
|
||||
$("#firstname").val("");
|
||||
$("#lastname").val("");
|
||||
}
|
||||
|
||||
$("#changeModal").on('hidden.bs.modal', function() {
|
||||
$("#firstname").val("");
|
||||
$("#lastname").val("");
|
||||
});
|
2
web/complainio/complainio/challenge/src/public/js/jquery-3.5.1.min.js
vendored
Normal file
2
web/complainio/complainio/challenge/src/public/js/jquery-3.5.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
101
web/complainio/complainio/challenge/src/public/js/main.js
Normal file
101
web/complainio/complainio/challenge/src/public/js/main.js
Normal file
@ -0,0 +1,101 @@
|
||||
$( document ).ready(async function() {
|
||||
if(localStorage.getItem("token")) {
|
||||
$("#navbarNavAuth").css("visibility","visible");
|
||||
} else {
|
||||
$("#navbarNavUnauth").css("visibility","visible");
|
||||
}
|
||||
|
||||
if(window.location.hash) {
|
||||
hash_content = window.location.hash.split("#")[1];
|
||||
data = hash_content.split("=");
|
||||
if(data.length === 2) {
|
||||
if(data[0] === "popupLogin" && data[1] === "1") {
|
||||
$("#msgLogin").append("<p id='msgLoginTxt' style='color: red;'>An error occured, please try to log in again.</p>");
|
||||
$("#loginModal").modal('show');
|
||||
} else if(data[0] === "popupLogin" && data[1] == "2") {
|
||||
$("#msgLogin").append("<p id='msgLoginTxt' style='color: red;'>Please log in before accessing this page.</p>");
|
||||
$("#loginModal").modal('show');
|
||||
}
|
||||
}
|
||||
history.replaceState(null, null, ' ');
|
||||
}
|
||||
});
|
||||
|
||||
function register() {
|
||||
$("#msgRegisterTxt").remove();
|
||||
if($("#usernameRegister").val().length == 0 || $("#passwordRegister").val().length == 0 || $("#firstname").val().length == 0 || $("#lastname").val().length == 0) {
|
||||
$("#msgRegister").append("<p id='msgRegisterTxt' style='color: red;'>You must fill in all fields.</p>");
|
||||
return;
|
||||
}
|
||||
var data = {"username": $("#usernameRegister").val(), "password": $("#passwordRegister").val(), "firstname": $("#firstname").val(), "lastname": $("#lastname").val()};
|
||||
$.ajax({
|
||||
url: "/api/register",
|
||||
type: "POST",
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
success: function(msg) {
|
||||
$("#usernameRegister").val('');
|
||||
$("#passwordRegister").val('');
|
||||
$("#successRegisterModal").modal('show');
|
||||
},
|
||||
error: function(msg) {
|
||||
let error = msg.responseJSON;
|
||||
if(error != undefined) {
|
||||
if(msg.status == 400) {
|
||||
$("#msgRegister").append("<p id='msgRegisterTxt' style='color: red;'>"+error["data"]+"</p>");
|
||||
} else {
|
||||
$("#msgRegister").append("<p id='msgRegisterTxt' style='color: red;'>Internal Server Error... 😔</p>");
|
||||
}
|
||||
} else {
|
||||
$("#msgRegister").append("<p id='msgRegisterTxt' style='color: red;'>Internal Server Error... 😔</p>");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function login() {
|
||||
$("#msgLoginTxt").remove();
|
||||
if($("#usernameLogin").val().length == 0 || $("#passwordLogin").val().length == 0) {
|
||||
$("#msgLogin").append("<p id='msgLoginTxt' style='color: red;'>You must fill in all fields.</p>");
|
||||
return;
|
||||
}
|
||||
var data = {"username": $("#usernameLogin").val(), "password": $("#passwordLogin").val()};
|
||||
$.ajax({
|
||||
url: "/api/login",
|
||||
type: "POST",
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
success: function(msg) {
|
||||
localStorage.setItem("token", msg["token"]);
|
||||
window.location.href = "/";
|
||||
},
|
||||
error: function(msg) {
|
||||
let error = msg.responseJSON;
|
||||
if(error != undefined) {
|
||||
if(msg.status == 400) {
|
||||
$("#msgLogin").append("<p id='msgLoginTxt' style='color: red;'>"+error["data"]+"</p>");
|
||||
} else {
|
||||
$("#msgLogin").append("<p id='msgLoginTxt' style='color: red;'>Internal Server Error... 😔</p>");
|
||||
}
|
||||
} else {
|
||||
$("#msgLogin").append("<p id='msgLoginTxt' style='color: red;'>Internal Server Error... 😔</p>");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
$("#loginModal").on('hidden.bs.modal', function() {
|
||||
$("#usernameLogin").val("");
|
||||
$("#passwordLogin").val("");
|
||||
$("#msgLoginTxt").remove();
|
||||
});
|
||||
|
||||
$("#registerModal").on('hidden.bs.modal', function() {
|
||||
$("#firstname").val("");
|
||||
$("#lastname").val("");
|
||||
$("#usernameRegister").val("");
|
||||
$("#passwordRegister").val("");
|
||||
$("#msgRegisterTxt").remove();
|
||||
});
|
6
web/complainio/complainio/challenge/src/public/js/popper.min.js
vendored
Normal file
6
web/complainio/complainio/challenge/src/public/js/popper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
94
web/complainio/complainio/challenge/src/public/js/profile.js
Normal file
94
web/complainio/complainio/challenge/src/public/js/profile.js
Normal file
@ -0,0 +1,94 @@
|
||||
function load_infos(){
|
||||
$.ajax({
|
||||
cache: false,
|
||||
url: "/api/me",
|
||||
type: "GET",
|
||||
headers: {
|
||||
"Authorization": "Bearer "+localStorage.getItem("token")
|
||||
},
|
||||
success: function(msg){
|
||||
$("#firstname").val(msg["firstname"]);
|
||||
$("#lastname").val(msg["lastname"]);
|
||||
$("#username").val(msg["username"]);
|
||||
$("#user_id").val(msg["id"]);
|
||||
$("#token").val(localStorage.getItem("token"));
|
||||
if(msg["pp"] !== null) {
|
||||
$("#pp").attr("src","/api/picture/"+msg["pp"]+"?token="+localStorage.getItem("token"));
|
||||
$("#pp").width(200);
|
||||
$("#pp").height(200);
|
||||
}
|
||||
},
|
||||
error: function(msg){
|
||||
$("#error_content").text("Unexcepted error, please try log in again !")
|
||||
$("#unexceptErrorModal").modal('show');
|
||||
setTimeout(function(){
|
||||
localStorage.removeItem("token");
|
||||
window.location.href = "/#popupError=1";
|
||||
},3000)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
$( document ).ready(async function() {
|
||||
if(!localStorage.getItem("token")) {
|
||||
window.location.href = "/#popupLogin=1";
|
||||
}
|
||||
load_infos();
|
||||
$("#picture").on("change", function(){
|
||||
document.forms["form_picture"].submit();
|
||||
});
|
||||
});
|
||||
|
||||
function save(){
|
||||
$("#errorTxt").val('');
|
||||
if($("#username").val().length == 0 || $("#firstname").val().length == 0 || $("#lastname").val().length == 0 || $("#user_id").val().length == 0) {
|
||||
$("#errorModal").modal('show');
|
||||
$("#errorTxt").text("You must fill in all fields.");
|
||||
return;
|
||||
}
|
||||
var data = {"username": $("#username").val(), "firstname": $("#firstname").val(), "lastname": $("#lastname").val(), "id": parseInt($("#user_id").val())};
|
||||
$.ajax({
|
||||
url: "/api/profile",
|
||||
type: "PATCH",
|
||||
data: JSON.stringify(data),
|
||||
contentType: 'application/json',
|
||||
headers:{
|
||||
"Authorization": "Bearer "+localStorage.getItem("token")
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(msg) {
|
||||
$("#successModal").modal('show');
|
||||
load_infos();
|
||||
},
|
||||
error: function(msg) {
|
||||
let error = msg.responseJSON;
|
||||
if(error != undefined) {
|
||||
if(msg.status == 400){
|
||||
$("#errorModal").modal('show');
|
||||
$("#errorTxt").text(error["data"]);
|
||||
} else if(msg.status == 404) {
|
||||
$("#errorModal").modal('show');
|
||||
$("#errorTxt").text("It seems that your current user does not exist in database, please log in again.");
|
||||
setTimeout(function(){
|
||||
localStorage.removeItem("token");
|
||||
window.location.href = "/#popupError=1";
|
||||
},3000)
|
||||
} else if(msg.status == 401) {
|
||||
$("#errorModal").modal('show');
|
||||
$("#errorTxt").text("Nasty things are going on, please log in again.");
|
||||
setTimeout(function(){
|
||||
localStorage.removeItem("token");
|
||||
window.location.href = "/#popupError=1";
|
||||
},3000)
|
||||
} else {
|
||||
$("#errorModal").modal('show');
|
||||
$("#errorTxt").text("Internal Server Error... 😔");
|
||||
}
|
||||
} else {
|
||||
$("#errorModal").modal('show');
|
||||
$("#errorTxt").text("Internal Server Error... 😔");
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
module.exports = app => {
|
||||
const usersController = require("../controllers/users.controller.js");
|
||||
const filesController = require("../controllers/files.controller.js");
|
||||
const complainsController = require("../controllers/complains.controller.js")
|
||||
|
||||
var router = require("express").Router();
|
||||
|
||||
router.post("/login", usersController.login);
|
||||
router.post("/register", usersController.register);
|
||||
router.patch("/profile", usersController.profile);
|
||||
router.post("/upload", usersController.upload);
|
||||
router.get("/me", usersController.me);
|
||||
|
||||
router.post("/create_template", filesController.createTemplate);
|
||||
router.get("/picture/:uuid", filesController.getProfilePicture);
|
||||
|
||||
router.get('/complains', complainsController.getAll);
|
||||
|
||||
app.use("/api", router);
|
||||
};
|
66
web/complainio/complainio/challenge/src/utils/index.js
Normal file
66
web/complainio/complainio/challenge/src/utils/index.js
Normal file
@ -0,0 +1,66 @@
|
||||
let jwt = require('jsonwebtoken');
|
||||
const secret_key = process.env.SECRET_KEY || "hero";
|
||||
const db = require("../models");
|
||||
const Users = db.Users;
|
||||
const FORBIDDEN_MODIFIED = ["id","username","password"];
|
||||
const all_fields = FORBIDDEN_MODIFIED.concat(["firstname","lastname"]);
|
||||
|
||||
const merge = (obj1, obj2) => {
|
||||
for (let key of Object.keys(obj2)) {
|
||||
const val = obj2[key];
|
||||
if(FORBIDDEN_MODIFIED.includes(key)) {
|
||||
continue
|
||||
}
|
||||
if (typeof obj1[key] !== "undefined" && typeof val === "object") {
|
||||
obj1[key] = merge(obj1[key], val);
|
||||
} else {
|
||||
obj1[key] = val;
|
||||
}
|
||||
}
|
||||
|
||||
return obj1;
|
||||
};
|
||||
|
||||
exports.verify_jwt = (req, user_token=null) => {
|
||||
let decoded = {};
|
||||
let token = "";
|
||||
if(req.headers.authorization !== undefined) {
|
||||
let parts = req.headers.authorization.split("Bearer ")
|
||||
if(parts.length == 2) {
|
||||
token = parts[1];
|
||||
}
|
||||
}
|
||||
if(user_token !== null) {
|
||||
token = user_token;
|
||||
}
|
||||
try {
|
||||
decoded = jwt.verify(token, secret_key);
|
||||
} catch(err) {}
|
||||
return decoded;
|
||||
}
|
||||
|
||||
exports.sign_jwt = (data) => {
|
||||
return jwt.sign(data, secret_key)
|
||||
}
|
||||
|
||||
exports.update_user = async (data, _) => {
|
||||
const user_id = data.id;
|
||||
let current_user = await Users.findByPk(user_id);
|
||||
if(!current_user) {
|
||||
return [404, "User not found."];
|
||||
}
|
||||
let user_database_content = {};
|
||||
let incoming_user_updates = data;
|
||||
for(var i=0; i<all_fields.length; i++) {
|
||||
user_database_content[all_fields[i]] = current_user[all_fields[i]];
|
||||
}
|
||||
merge(user_database_content, incoming_user_updates);
|
||||
const num = await Users.update(user_database_content, {
|
||||
where: {id: user_id}
|
||||
})
|
||||
if(num == 1) {
|
||||
return [200,"User updated."];
|
||||
} else {
|
||||
return [500,"Internal server error."];
|
||||
}
|
||||
}
|
98
web/complainio/complainio/challenge/src/views/complain.html
Normal file
98
web/complainio/complainio/challenge/src/views/complain.html
Normal file
@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ComplainIO - A platform for generating complaint templates</title>
|
||||
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">ComplainIO</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNavAuth">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Back to home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Create a complaint report</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/profile">My profile</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" onclick='localStorage.removeItem("token"); window.location.href="/";'>Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<h1>ComplainIO templates</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4" id="complains">
|
||||
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="unexceptErrorModal" tabindex="-1" role="dialog" aria-labelledby="unexceptErrorModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="unexceptErrorModalLabel">UhOh.. ❌</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p style="color: red;" id="error_content"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="changeModal" tabindex="-1" role="dialog" aria-labelledby="changeModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="changeModalLabel">Do you want to change your firstname and lastname that will be displayed in the complain document ? (Keep theses values if not)</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick='$("#firstname").val("");$("#lastname").val("");'>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="msgTemplate"></div>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="firstname">Firstname</label>
|
||||
<input type="text" class="form-control" id="firstname" name="firstname" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lastname">Lastname</label>
|
||||
<input type="text" class="form-control" id="lastname" name="lastname" required>
|
||||
</div>
|
||||
<input type="hidden" id="uuid" name="uuid" required>
|
||||
<input type="hidden" id="token" name="token" required>
|
||||
<input type="hidden" id="user_id" name="id" required>
|
||||
<button type="button" class="btn btn-primary btn-block" onclick='create_template()'>Create complain</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/js/jquery-3.5.1.min.js"></script>
|
||||
<script src="/js/popper.min.js"></script>
|
||||
<script src="/js/bootstrap.min.js"></script>
|
||||
<script src="/js/complain.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
139
web/complainio/complainio/challenge/src/views/index.html
Normal file
139
web/complainio/complainio/challenge/src/views/index.html
Normal file
@ -0,0 +1,139 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ComplainIO - A platform for generating complaint templates</title>
|
||||
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="#">ComplainIO</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNavUnauth" style="visibility: hidden;">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" type="button" data-toggle="modal" data-target="#loginModal">Login</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" type="button" data-toggle="modal" data-target="#registerModal">Register</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="navbarNavAuth" style="visibility: hidden;">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/complain">Create a complaint report</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/profile">My profile</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" onclick='localStorage.removeItem("token"); window.location.href="/";'>Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="modal fade" id="loginModal" tabindex="-1" role="dialog" aria-labelledby="loginModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="loginModalLabel">Login</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick='$("#usernameLogin").val("");$("#passwordLogin").val("");$("#msgLoginTxt").remove();'>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="msgLogin"></div>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" id="usernameLogin" placeholder="Enter your username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" id="passwordLogin" placeholder="***********" required>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-block" onclick="login()">Log in</button>
|
||||
<p>No account ? <a href="#" onclick='$("#loginModal").modal("hide");$("#registerModal").modal("show");'>Create one</a> !</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="registerModal" tabindex="-1" role="dialog" aria-labelledby="registerModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="registerModalLabel">Register</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" onclick='$("#emailRegister").val("");$("#usernameRegister").val("");$("#passwordRegister").val("");$("#firstname").val("");$("#lastname").val("");$("#msgRegisterTxt").remove();'>
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="msgRegister"></div>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" id="usernameRegister" placeholder="Enter your username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="firstname">Firstname</label>
|
||||
<input type="text" class="form-control" id="firstname" placeholder="Enter your firstname" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lastname">Lastname</label>
|
||||
<input type="text" class="form-control" id="lastname" placeholder="Enter your lastname" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" id="passwordRegister" placeholder="***********" required>
|
||||
</div>
|
||||
<button type="button" class="btn btn-primary btn-block" onclick="register()">Register</button>
|
||||
<p>Already have an account ? <a href="#" onclick='$("#registerModal").modal("hide");$("#loginModal").modal("show");'>Log in</a> !</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="successRegisterModal" tabindex="-1" role="dialog" aria-labelledby="successRegisterModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="successRegisterModal">Youhou ! 🎉</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p style="color: green;">Your account was created ! You can now log in to complain !</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
<!-- Contenu principal -->
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<h1>Welcome on ComplainIO</h1>
|
||||
<p>Need to complain for a legitimate reason? Or just for the fun of it, because you're French? You've come to the right place !</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lien vers le JS de Bootstrap et ses dépendances -->
|
||||
<script src="/js/jquery-3.5.1.min.js"></script>
|
||||
<script src="/js/popper.min.js"></script>
|
||||
<script src="/js/bootstrap.min.js"></script>
|
||||
<script src="/js/main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
128
web/complainio/complainio/challenge/src/views/profile.html
Normal file
128
web/complainio/complainio/challenge/src/views/profile.html
Normal file
@ -0,0 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>ComplainIO - A platform for generating complaint templates</title>
|
||||
<link href="/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" href="/">ComplainIO</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarNavAuth">
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/">Back to home</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="/complain">Create a complaint report</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">My profile</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#" onclick='localStorage.removeItem("token"); window.location.href="/";'>Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="container mt-5">
|
||||
<div class="row">
|
||||
<div class="col text-center">
|
||||
<h1>Your ComplainIO profile</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card profile-card">
|
||||
<div class="card-body">
|
||||
<img src="" alt="No profile picture, click here to upload one." class="profile-img" id="pp" style="border-radius: 50%;" onclick="$('input[type=file]').trigger('click');">
|
||||
<h3 class="card-title"></h3>
|
||||
<form>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" class="form-control" id="username" value="" required readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="firstname">Firstname</label>
|
||||
<input type="text" class="form-control" id="firstname" value="" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lastname">Lastname</label>
|
||||
<input type="text" class="form-control" id="lastname" value="" required>
|
||||
</div>
|
||||
<input type="hidden" id="user_id" required>
|
||||
<button type="button" class="btn btn-primary" onclick="save()">Save</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="unexceptErrorModal" tabindex="-1" role="dialog" aria-labelledby="unexceptErrorModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="unexceptErrorModalLabel">UhOh.. ❌</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p style="color: red;" id="error_content"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="errorModal" tabindex="-1" role="dialog" aria-labelledby="errorModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="errorModalLabel">UhOh.. ❌</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p id="errorTxt" style="color: red;"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="successModal" tabindex="-1" role="dialog" aria-labelledby="successModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="successModalLabel">We've taken the changes into account! 😊</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p style="color: green;">Everything is ready for you to complain !</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="/js/jquery-3.5.1.min.js"></script>
|
||||
<script src="/js/popper.min.js"></script>
|
||||
<script src="/js/bootstrap.min.js"></script>
|
||||
<script src="/js/profile.js"></script>
|
||||
|
||||
<!-- Div use for file upload -->
|
||||
<div style="visibility: hidden;">
|
||||
<form method="POST" enctype="multipart/form-data" id="form_picture" action="/api/upload">
|
||||
<input type="file" id="picture" name="picture"/>
|
||||
<input type="text" id="token" name="token"/>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
13
web/complainio/complainio/docker-compose.yml
Normal file
13
web/complainio/complainio/docker-compose.yml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
version: "2.2"
|
||||
|
||||
services:
|
||||
|
||||
complainio:
|
||||
build:
|
||||
context: ./challenge/
|
||||
restart: always
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- DB_PATH=/app/database.sqlite
|
Loading…
Reference in New Issue
Block a user