Merge branch 'master' into 'Kyfront'
# Conflicts: # sass/style.sass # views/template/layout.pug # views/template/navbar.pug
This commit is contained in:
commit
549b30006e
42 changed files with 578 additions and 73 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -112,3 +112,7 @@ config/config.json
|
||||||
|
|
||||||
# Sass output
|
# Sass output
|
||||||
public/stylesheets
|
public/stylesheets
|
||||||
|
|
||||||
|
# Floobits
|
||||||
|
.floo
|
||||||
|
.flooignore
|
||||||
|
|
18
agenda/index.js
Normal file
18
agenda/index.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
const { Worker } = require("worker_threads");
|
||||||
|
let nb = 0;
|
||||||
|
|
||||||
|
|
||||||
|
function startWorker(workerArgs) {
|
||||||
|
let worker = new Worker(...workerArgs);
|
||||||
|
worker.on("error", (err) => {
|
||||||
|
console.error(err);
|
||||||
|
nb--;
|
||||||
|
if (nb > 0)
|
||||||
|
startWorker(workerArgs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = (app) => {
|
||||||
|
const workerArgs = ["./agenda/worker.js", {workerData: app.get("config")}];
|
||||||
|
startWorker(workerArgs);
|
||||||
|
};
|
229
agenda/worker.js
Normal file
229
agenda/worker.js
Normal file
|
@ -0,0 +1,229 @@
|
||||||
|
const https = require("https");
|
||||||
|
const config = require("worker_threads").workerData;
|
||||||
|
const models = require("../models");
|
||||||
|
|
||||||
|
const reg_event = /(?:(?:BEGIN:VEVENT\nDTSTAMP:(?:[A-Z0-9]*?)\nDTSTART:([A-Z0-9]*?)\nDTEND:([A-Z0-9]*?)\nSUMMARY: {0,}([a-zéèàA-Z0-9-. \, \\/ô]*?)\nLOCATION:([a-zA-Zéèà0-9-. \,\\]*?)\nDESCRIPTION:(?:\\n){0,}(LP(?:[ a-zA-Z0-9]*))\\n((?:(?:[A-Z]*) (?:[A-Z]*)(?: (?:[A-Z]*)){0,}\\n){0,})(?:.*?)\nEND:VEVENT)|(?:BEGIN:VEVENT\nDTSTAMP:(?:[A-Z0-9]*?)\nDTSTART:([A-Z0-9]*?)\nDTEND:([A-Z0-9]*?)\nSUMMARY: {0,}((?:S(?:[A-Z0-9-]*)|M(?:[A-Z0-9-]*)(?:\/M(?:[A-Z0-9-]*)){0,}|Conférence)[a-zéèàA-Z0-9-. \, \\/]*?)\nLOCATION:([a-zA-Zéèà0-9-. \,\\]*?)\nDESCRIPTION:(?:\\n){0,}((?:(?:G[0-9]S[0-9]|S[0-9]|ASPE)\\n){0,})((?:(?:[A-Z]*) (?:[A-Z]*)(?: (?:[A-Z]*)){0,}\\n){0,})(?:.*?)\nEND:VEVENT))/gs;
|
||||||
|
const reg_location = /((?:[SH0-9][0-9]{2})|(?:(?:Préfa |Amphi)[0-9]))/g;
|
||||||
|
const reg_teachers = /^(?:((?:[a-zA-Z]*[ -]{0,}){0,}) ([a-zA-Z]*))$/m;
|
||||||
|
const reg_date = /([0-9]{4})([0-9]{2})([0-9]{2})T([0-9]{2})([0-9]{2})([0-9]{2})Z/;
|
||||||
|
const reg_classe = /(?:(LP)[ -]{0,}(.*)|(?:(G[0-9])(S[0-9])))/;
|
||||||
|
const base_url = config["edt"];
|
||||||
|
|
||||||
|
function fetchEvents(days = 1, TS_Start = new Date()) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let start = new Date(TS_Start);
|
||||||
|
if (start == "Invalid Date")
|
||||||
|
start = new Date();
|
||||||
|
let end = new Date(start);
|
||||||
|
end.setDate(end.getDate() + days);
|
||||||
|
|
||||||
|
let url = base_url + start.getFullYear() + "-" + start.getMonth() + "-" + start.getDate() + "&lastDate=" + end.getFullYear() + "-" + end.getMonth() + "-" + end.getDate();
|
||||||
|
https.get(url, (resp) => {
|
||||||
|
let data = "";
|
||||||
|
resp.on("data", (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
resp.on("end", () => {
|
||||||
|
let output = [];
|
||||||
|
data = data.replace(/\r/g, "");
|
||||||
|
let m;
|
||||||
|
while ((m = reg_event.exec(data)) !== null) {
|
||||||
|
if (m.index === reg_event.lastIndex) {
|
||||||
|
reg_event.lastIndex++;
|
||||||
|
}
|
||||||
|
let event = [];
|
||||||
|
let promotion = [];
|
||||||
|
let classe = [];
|
||||||
|
/*
|
||||||
|
m = [
|
||||||
|
FullMatch,
|
||||||
|
StartTime,
|
||||||
|
EndTime,
|
||||||
|
EventDescription ( SubjectID,Name,Class?) ,
|
||||||
|
Location,
|
||||||
|
ClassGroup,
|
||||||
|
Teacher(s)
|
||||||
|
]
|
||||||
|
*/
|
||||||
|
if (m[1] !== undefined) {
|
||||||
|
// LPXXXX
|
||||||
|
classe.push(m[5]);
|
||||||
|
} else {
|
||||||
|
m.splice(1, 6);
|
||||||
|
// GXSX | SX | ASPE
|
||||||
|
let csplit = m[5].split("\\n");
|
||||||
|
csplit.pop();
|
||||||
|
csplit.forEach(e => {
|
||||||
|
if (e === "ASPE") promotion.push(e);
|
||||||
|
if (/^S[0-9]$/.test(e)) promotion.push(e);
|
||||||
|
if (/^G[0-9]S[0-9]$/.test(e)) classe.push(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
event["title"] = m[3];
|
||||||
|
event["promotion"] = promotion;
|
||||||
|
event["class"] = classe;
|
||||||
|
/*
|
||||||
|
Date
|
||||||
|
*/
|
||||||
|
let d1 = reg_date.exec(m[1]);
|
||||||
|
let d2 = reg_date.exec(m[2]);
|
||||||
|
event["startDate"] = new Date(d1[1] + "-" + d1[2] + "-" + d1[3] + "T" + d1[4] + ":" + d1[5] + ":" + d1[6] + ".00Z");
|
||||||
|
event["endDate"] = new Date(d2[1] + "-" + d2[2] + "-" + d2[3] + "T" + d2[4] + ":" + d2[5] + ":" + d2[6] + ".00Z");
|
||||||
|
/*
|
||||||
|
Location
|
||||||
|
*/
|
||||||
|
event["locations"] = [];
|
||||||
|
let loc;
|
||||||
|
while ((loc = reg_location.exec(m[4])) !== null) {
|
||||||
|
if (loc.index === reg_location.lastIndex) {
|
||||||
|
reg_location.lastIndex++;
|
||||||
|
}
|
||||||
|
event["locations"].push(loc[1]);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
Teachers
|
||||||
|
*/
|
||||||
|
event['teachers'] = []
|
||||||
|
let fullTeachers = m[6].split('\\n');
|
||||||
|
fullTeachers.forEach(e => {
|
||||||
|
if(e !== ""){
|
||||||
|
let splittedTeachers = reg_teachers.exec(e);
|
||||||
|
event['teachers'].push([splittedTeachers[1],splittedTeachers[2]]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
output.push(event);
|
||||||
|
}
|
||||||
|
resolve(output);
|
||||||
|
});
|
||||||
|
}).on("error", (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function compare(a, b) {
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let set = {};
|
||||||
|
a.forEach((i) => {
|
||||||
|
if (set[i] !== undefined) {
|
||||||
|
set[i]++;
|
||||||
|
} else {
|
||||||
|
set[i] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let difference = b.every((i) => {
|
||||||
|
if (set[i] === undefined) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
set[i]--;
|
||||||
|
if (set[i] === 0) {
|
||||||
|
delete set[i];
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return Object.keys(set) == 0 && difference;
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareGroups(list1, list2) {
|
||||||
|
return compare(list1, list2.map(g => [g.number, g.Semester.name, g.Semester.year]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareTeachers(list1, list2) {
|
||||||
|
return compare(list1, list2.map(t => t.lastName.toUpperCase() + " " + t.firstName.toUpperCase()));
|
||||||
|
}
|
||||||
|
|
||||||
|
function compareSemesters(list1, list2) {
|
||||||
|
return compare(list1, list2.map(s => s.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateDatabase() {
|
||||||
|
let events = await fetchEvents(365, new Date(2020, 9, 1));
|
||||||
|
for (let event of await models.Event.findAll({
|
||||||
|
include: [{
|
||||||
|
model: models.Group,
|
||||||
|
include: {model: models.Semester, required: true}
|
||||||
|
}, models.User, models.Semester]
|
||||||
|
})) {
|
||||||
|
if (!events.find(e => (e.title === event.name && e.startDate.getTime() === event.startDate.getTime() &&
|
||||||
|
e.endDate.getTime() === event.endDate.getTime() && e.locations.join(", ") === event.locations &&
|
||||||
|
compareGroups(e.class, event.Groups) && compareTeachers(e.teachers, event.Users) &&
|
||||||
|
compareSemesters(e.semesters, event.Semesters))))
|
||||||
|
await event.destroy();
|
||||||
|
else
|
||||||
|
delete events[events.indexOf(event)];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let event of events) {
|
||||||
|
let e = await models.Event.create({
|
||||||
|
name: event.title,
|
||||||
|
startDate: event.startDate,
|
||||||
|
endDate: event.endDate,
|
||||||
|
locations: event.locations.join(", ")
|
||||||
|
});
|
||||||
|
|
||||||
|
let teachers = [];
|
||||||
|
for (let teacher of event.teachers) {
|
||||||
|
let t = await models.User.findOne({where: {permissions: 2, lastName: teacher[0], firstName: teacher[1]}});
|
||||||
|
if (!t)
|
||||||
|
t = await models.User.create({
|
||||||
|
email: teacher[1].toLowerCase().replace(" ", "-") + "." + teacher[0].toLowerCase().replace(" ", "-") + "@univ-lyon1.fr",
|
||||||
|
firstName: teacher[1],
|
||||||
|
lastName: teacher[0],
|
||||||
|
permissions: 2,
|
||||||
|
passwordHash: Math.round((Math.pow(36, 12 + 1) - Math.random() * Math.pow(36, 12))).toString(36).slice(1)
|
||||||
|
});
|
||||||
|
teachers.push(t);
|
||||||
|
}
|
||||||
|
await e.addUsers(teachers);
|
||||||
|
|
||||||
|
let semesters = [];
|
||||||
|
for (let semester of event.promotion) {
|
||||||
|
let s = await models.Semester.findOne({where: {name: semester, year: event.startDate.getFullYear()}});
|
||||||
|
if (!s)
|
||||||
|
s = await models.Semester.create({
|
||||||
|
name: semester,
|
||||||
|
year: event.startDate.getFullYear()
|
||||||
|
});
|
||||||
|
semesters.push(s);
|
||||||
|
}
|
||||||
|
await e.addSemesters(semesters);
|
||||||
|
|
||||||
|
let groups = [];
|
||||||
|
for (let group of event.class) {
|
||||||
|
let rGroup = reg_classe.exec(group);
|
||||||
|
let semesterName;
|
||||||
|
let groupName;
|
||||||
|
if (rGroup[1] !== undefined) {
|
||||||
|
// LP
|
||||||
|
semesterName = 'LP';
|
||||||
|
groupName = rGroup[2];
|
||||||
|
} else {
|
||||||
|
// GXSX
|
||||||
|
semesterName = rGroup[4];
|
||||||
|
groupName = rGroup[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let s = await models.Semester.findOne({where: {name: semesterName, year: event.startDate.getFullYear()}});
|
||||||
|
if (!s)
|
||||||
|
s = await models.Semester.create({
|
||||||
|
name: semesterName,
|
||||||
|
year: event.startDate.getFullYear()
|
||||||
|
});
|
||||||
|
|
||||||
|
let g = await models.Group.findOne({where: {number: groupName, SemesterId: s.id}});
|
||||||
|
if (!g)
|
||||||
|
g = await models.Group.create({
|
||||||
|
number: groupName,
|
||||||
|
SemesterId: s.id
|
||||||
|
});
|
||||||
|
groups.push(g);
|
||||||
|
}
|
||||||
|
await e.addGroups(groups);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDatabase().then(() => setInterval(updateDatabase, 30000));
|
4
app.js
4
app.js
|
@ -12,8 +12,8 @@ const edtRouter = require("./routes/edt");
|
||||||
const homeRouter = require("./routes/home");
|
const homeRouter = require("./routes/home");
|
||||||
const marksRouter = require("./routes/marks");
|
const marksRouter = require("./routes/marks");
|
||||||
const registerRouter = require("./routes/register");
|
const registerRouter = require("./routes/register");
|
||||||
const viescolRouter = require("./routes/viescol")
|
const viescolRouter = require("./routes/viescol");
|
||||||
const profilRouter = require("./routes/profil")
|
const profilRouter = require("./routes/profil");
|
||||||
|
|
||||||
let app = express();
|
let app = express();
|
||||||
const sessionMiddleware = session({
|
const sessionMiddleware = session({
|
||||||
|
|
13
bin/www
13
bin/www
|
@ -30,7 +30,7 @@ fs.writeFileSync("public/stylesheets/style.css", css.css);
|
||||||
* Get port from environment and store in Express.
|
* Get port from environment and store in Express.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let port = normalizePort(process.env.PORT || "3000");
|
const port = normalizePort(process.env.PORT || "3000");
|
||||||
app.set("port", port);
|
app.set("port", port);
|
||||||
app.set("mailClient", mailClient);
|
app.set("mailClient", mailClient);
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ app.set("mailClient", mailClient);
|
||||||
* Create HTTP server.
|
* Create HTTP server.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create socket.io server
|
* Create socket.io server
|
||||||
|
@ -66,7 +66,7 @@ models.sequelize.sync().then(() => {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function normalizePort(val) {
|
function normalizePort(val) {
|
||||||
let port = parseInt(val, 10);
|
const port = parseInt(val, 10);
|
||||||
|
|
||||||
if (isNaN(port)) {
|
if (isNaN(port)) {
|
||||||
// named pipe
|
// named pipe
|
||||||
|
@ -90,7 +90,7 @@ function onError(error) {
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
let bind = typeof port === "string"
|
const bind = typeof port === "string"
|
||||||
? "Pipe " + port
|
? "Pipe " + port
|
||||||
: "Port " + port;
|
: "Port " + port;
|
||||||
|
|
||||||
|
@ -114,9 +114,10 @@ function onError(error) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function onListening() {
|
function onListening() {
|
||||||
let addr = server.address();
|
const addr = server.address();
|
||||||
let bind = typeof addr === "string"
|
const bind = typeof addr === "string"
|
||||||
? "pipe " + addr
|
? "pipe " + addr
|
||||||
: "port " + addr.port;
|
: "port " + addr.port;
|
||||||
debug("Listening on " + bind);
|
debug("Listening on " + bind);
|
||||||
|
require("../agenda")(app);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
"contact": "Your Contact <contact@your-email.fr>",
|
"contact": "Your Contact <contact@your-email.fr>",
|
||||||
"mailPath": "https://yourServer.com"
|
"mailPath": "https://yourServer.com"
|
||||||
},
|
},
|
||||||
|
"edt": "https://adelb.univ-lyon1.fr/jsp/custom/modules/plannings/anonymous_cal.jsp?resources=XXXX&projectId=XXXX&calType=ical&firstDate=",
|
||||||
"secret": "keyboard cat",
|
"secret": "keyboard cat",
|
||||||
"passwordPrivateKey": "ecc635295f200847b79299df48e15759"
|
"passwordPrivateKey": "ecc635295f200847b79299df48e15759"
|
||||||
}
|
}
|
||||||
|
|
23
models/UE.js
Normal file
23
models/UE.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { Model } = require("sequelize");
|
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
class UE extends Model {
|
||||||
|
static associate(models) {
|
||||||
|
UE.hasMany(models.Event);
|
||||||
|
UE.belongsToMany(models.User, {through: "UEUser"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UE.init({
|
||||||
|
name : {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: true
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: "UE",
|
||||||
|
});
|
||||||
|
return UE;
|
||||||
|
};
|
36
models/event.js
Normal file
36
models/event.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { Model } = require("sequelize");
|
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
class Event extends Model {
|
||||||
|
static associate(models) {
|
||||||
|
Event.belongsToMany(models.Semester, {through: "EventSemester"});
|
||||||
|
Event.belongsToMany(models.Group, {through: "EventGroup"});
|
||||||
|
Event.belongsToMany(models.User, {through: "EventTeacher"});
|
||||||
|
Event.belongsTo(models.UE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Event.init({
|
||||||
|
name: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
locations: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
startDate: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
},
|
||||||
|
endDate: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: "Event",
|
||||||
|
});
|
||||||
|
return Event;
|
||||||
|
};
|
23
models/group.js
Normal file
23
models/group.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { Model } = require("sequelize");
|
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
class Group extends Model {
|
||||||
|
static associate(models) {
|
||||||
|
Group.belongsTo(models.Semester);
|
||||||
|
Group.belongsToMany(models.User, {through: "UserGroup"});
|
||||||
|
Group.belongsToMany(models.Event, {through: "EventGroup"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Group.init({
|
||||||
|
number: {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: "Group",
|
||||||
|
});
|
||||||
|
return Group;
|
||||||
|
};
|
28
models/semester.js
Normal file
28
models/semester.js
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const { Model } = require("sequelize");
|
||||||
|
|
||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
class Semester extends Model {
|
||||||
|
static associate(models) {
|
||||||
|
Semester.hasMany(models.Group);
|
||||||
|
Semester.belongsToMany(models.Event, {through: "EventSemester"});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Semester.init({
|
||||||
|
year: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
unique: "semester"
|
||||||
|
},
|
||||||
|
name : {
|
||||||
|
type: DataTypes.STRING,
|
||||||
|
allowNull: false,
|
||||||
|
unique: "semester"
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
sequelize,
|
||||||
|
modelName: "Semester",
|
||||||
|
});
|
||||||
|
return Semester;
|
||||||
|
};
|
|
@ -8,7 +8,7 @@ const {
|
||||||
} = require("sequelize");
|
} = require("sequelize");
|
||||||
module.exports = (sequelize, DataTypes) => {
|
module.exports = (sequelize, DataTypes) => {
|
||||||
function hash(password, email) {
|
function hash(password, email) {
|
||||||
let cipher = crypto.createCipheriv(
|
const cipher = crypto.createCipheriv(
|
||||||
"aes-256-cbc",
|
"aes-256-cbc",
|
||||||
privateKey,
|
privateKey,
|
||||||
crypto.createHash("md5").update(email).digest("base64").slice(0, 16)
|
crypto.createHash("md5").update(email).digest("base64").slice(0, 16)
|
||||||
|
@ -18,6 +18,9 @@ module.exports = (sequelize, DataTypes) => {
|
||||||
|
|
||||||
class User extends Model {
|
class User extends Model {
|
||||||
static associate(models) {
|
static associate(models) {
|
||||||
|
User.belongsToMany(models.Group, {through: "UserGroup"});
|
||||||
|
User.belongsToMany(models.Event, {through: "EventTeacher"});
|
||||||
|
User.belongsToMany(models.UE, {through: "UEUser"});
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPassword(password) {
|
checkPassword(password) {
|
||||||
|
|
|
@ -11,7 +11,7 @@ document.getElementById("login").addEventListener("submit", e=>{
|
||||||
alert('Format d\'email incorrect.');
|
alert('Format d\'email incorrect.');
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
})
|
});
|
||||||
|
|
||||||
socket.on("login", data=>{
|
socket.on("login", data=>{
|
||||||
if(data.error){
|
if(data.error){
|
||||||
|
@ -19,4 +19,4 @@ socket.on("login", data=>{
|
||||||
}else{
|
}else{
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}
|
}
|
||||||
})
|
});
|
|
@ -18,4 +18,9 @@ socket.on("logout", data=>{
|
||||||
}else{
|
}else{
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function profilRedirect(){
|
||||||
|
document.location.href="/profil";
|
||||||
|
}
|
11
public/javascripts/marks.js
Normal file
11
public/javascripts/marks.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
function setVisible(targetId) {
|
||||||
|
document.getElementById("notvisible").id = "visible";
|
||||||
|
}
|
||||||
|
|
||||||
|
function setNotVisible(targetId) {
|
||||||
|
document.getElementById(targetId).id = "notvisible";
|
||||||
|
}
|
||||||
|
|
||||||
|
function marksformChange(targetId) {
|
||||||
|
document.getElementById("marksform").style.display = "block";
|
||||||
|
}
|
21
public/javascripts/profil.js
Normal file
21
public/javascripts/profil.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
let socket = io.connect();
|
||||||
|
|
||||||
|
document.getElementById("editprofil").addEventListener("submit", e=>{
|
||||||
|
e.preventDefault();
|
||||||
|
socket.emit("profileEdit", {
|
||||||
|
"email": mail,
|
||||||
|
"firstName": document.getElementById("firstname-input").value,
|
||||||
|
"lastName": document.getElementById("lastname-input").value,
|
||||||
|
"newPassword": document.getElementById("new-password").value,
|
||||||
|
"password": document.getElementById("password").value
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
socket.on("profileEdit", data=>{
|
||||||
|
if(data.error){
|
||||||
|
alert(data.error.message);
|
||||||
|
}else{
|
||||||
|
window.location.href = "/";
|
||||||
|
}
|
||||||
|
});
|
|
@ -20,4 +20,4 @@ socket.on("register", data=>{
|
||||||
}else{
|
}else{
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
let express = require("express");
|
const express = require("express");
|
||||||
let router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/", (req, res) => {
|
router.get("/", (req, res) => {
|
||||||
res.render("pages/edt", { title: "L'ETU" });
|
res.render("pages/edt", { title: "L'ETU" });
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
@ -8,7 +8,7 @@ const sessionCheck = require("./utils/sessionCheck");
|
||||||
router.get("/check", async (req, res) => {
|
router.get("/check", async (req, res) => {
|
||||||
if (!req.query.token)
|
if (!req.query.token)
|
||||||
return error(req, res, "Missing argument", 400);
|
return error(req, res, "Missing argument", 400);
|
||||||
let user = await models.User.findOne({where: {emailToken: req.query.token}});
|
const user = await models.User.findOne({where: {emailToken: req.query.token}});
|
||||||
if (user) {
|
if (user) {
|
||||||
user.emailVerified = true;
|
user.emailVerified = true;
|
||||||
if (user.email.endsWith("@etu.univ-lyon1.fr"))
|
if (user.email.endsWith("@etu.univ-lyon1.fr"))
|
||||||
|
@ -25,7 +25,7 @@ router.get("/forget", sessionCheck(-1), async (req, res) => {
|
||||||
if (!req.query.token)
|
if (!req.query.token)
|
||||||
res.render("forget", {title: "L'ETU"});
|
res.render("forget", {title: "L'ETU"});
|
||||||
else {
|
else {
|
||||||
let user = await models.User.findOne({where: {passwordToken: data.token}});
|
const user = await models.User.findOne({where: {passwordToken: data.token}});
|
||||||
if (!user)
|
if (!user)
|
||||||
return error(req, res, "Invalid token", 400);
|
return error(req, res, "Invalid token", 400);
|
||||||
else if (user.passwordTokenDate && ((new Date().getTime() - user.passwordTokenDate.getTime()) / 1000 > 3600))
|
else if (user.passwordTokenDate && ((new Date().getTime() - user.passwordTokenDate.getTime()) / 1000 > 3600))
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
let express = require("express");
|
const express = require("express");
|
||||||
let router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
/* GET home page. */
|
/* GET home page. */
|
||||||
router.get("/", (req, res) => {
|
router.get("/", (req, res) => {
|
||||||
res.render("pages/home", { title: "L'ETU" });
|
res.render("pages/home", { title: "L'ETU" });
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
let express = require("express");
|
const express = require("express");
|
||||||
let router = express.Router();
|
const router = express.Router();
|
||||||
const sessionCheck = require("./utils/sessionCheck");
|
const sessionCheck = require("./utils/sessionCheck");
|
||||||
|
|
||||||
router.get("/", sessionCheck(1), (req, res) => {
|
router.get("/", sessionCheck(1), (req, res) => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
let express = require("express");
|
const express = require("express");
|
||||||
let router = express.Router();
|
const router = express.Router();
|
||||||
const sessionCheck = require("./utils/sessionCheck");
|
const sessionCheck = require("./utils/sessionCheck");
|
||||||
|
|
||||||
router.get("/",sessionCheck(-1), (req, res) => {
|
router.get("/",sessionCheck(-1), (req, res) => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
let express = require("express");
|
const express = require("express");
|
||||||
let router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/", (req, res) => {
|
router.get("/", (req, res) => {
|
||||||
res.render("pages/marks", { title: "L'ETU" });
|
res.render("pages/marks", { title: "L'ETU" });
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
let express = require("express");
|
const express = require("express");
|
||||||
let router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/", (req, res) => {
|
router.get("/", (req, res) => {
|
||||||
res.render("pages/profil", { title: "L'ETU" });
|
res.render("pages/profil", { title: "L'ETU" });
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
let express = require("express");
|
const express = require("express");
|
||||||
let router = express.Router();
|
const router = express.Router();
|
||||||
const sessionCheck = require("./utils/sessionCheck");
|
const sessionCheck = require("./utils/sessionCheck");
|
||||||
|
|
||||||
router.get("/",sessionCheck(-1), (req, res) => {
|
router.get("/",sessionCheck(-1), (req, res) => {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
let error = require("./error");
|
const error = require("./error");
|
||||||
|
|
||||||
function sessionCheck(permission) {
|
function sessionCheck(permission) {
|
||||||
return (req, res, next) => {
|
return (req, res, next) => {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
let express = require("express");
|
const express = require("express");
|
||||||
let router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
router.get("/", (req, res) => {
|
router.get("/", (req, res) => {
|
||||||
res.render("pages/viescol", { title: "L'ETU" });
|
res.render("pages/viescol", { title: "L'ETU" });
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|
|
@ -512,4 +512,10 @@ div#visible + div
|
||||||
font-size: 20px
|
font-size: 20px
|
||||||
|
|
||||||
#hamburger
|
#hamburger
|
||||||
position: fixed
|
position: fixed
|
||||||
|
|
||||||
|
#notvisible
|
||||||
|
display: none
|
||||||
|
|
||||||
|
#marksform
|
||||||
|
display: none
|
||||||
|
|
|
@ -11,4 +11,4 @@ module.exports = socket => {
|
||||||
else
|
else
|
||||||
await emailCheck(socket, user, null);
|
await emailCheck(socket, user, null);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
@ -10,4 +10,4 @@ module.exports = socket => {
|
||||||
else
|
else
|
||||||
await emailPassword(socket, user, null);
|
await emailPassword(socket, user, null);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
@ -5,7 +5,7 @@ module.exports = socket => {
|
||||||
return async (data) => {
|
return async (data) => {
|
||||||
let user = await models.User.findOne({where: {passwordToken: data.token}});
|
let user = await models.User.findOne({where: {passwordToken: data.token}});
|
||||||
if (!user)
|
if (!user)
|
||||||
socket.emit("setPassword", {error: {message: "invalid_token"}})
|
socket.emit("setPassword", {error: {message: "invalid_token"}});
|
||||||
else if (user.passwordTokenDate && ((new Date().getTime() - user.passwordTokenDate.getTime()) / 1000 > 3600))
|
else if (user.passwordTokenDate && ((new Date().getTime() - user.passwordTokenDate.getTime()) / 1000 > 3600))
|
||||||
socket.emit("setPassword", {error: {message: "expired_token"}});
|
socket.emit("setPassword", {error: {message: "expired_token"}});
|
||||||
else {
|
else {
|
||||||
|
@ -16,4 +16,4 @@ module.exports = socket => {
|
||||||
socket.emit("setPassword", true);
|
socket.emit("setPassword", true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
39
sockets/get/agendaGet.js
Normal file
39
sockets/get/agendaGet.js
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
const models = require("../../models");
|
||||||
|
|
||||||
|
module.exports = socket => {
|
||||||
|
return async (data) => {
|
||||||
|
let startDate = data.startDate ? data.startDate : new Date(), endDate = data.endDate ? data.endDate : new Date();
|
||||||
|
let options = {where: {startDate: startDate, endDate: endDate}, include: []};
|
||||||
|
if (data.semesters) {
|
||||||
|
for (let semester of data.semesters) {
|
||||||
|
let s = await models.Semester.findByPk(semester);
|
||||||
|
if (!s) {
|
||||||
|
socket.emit("agendaGet", {error: {message: "semester_not_found"}});
|
||||||
|
return
|
||||||
|
} else
|
||||||
|
options.include.push({model: models.Semester, where: {id: s.id}, require: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.groups) {
|
||||||
|
for (let group of data.groups) {
|
||||||
|
let g = await models.Group.findByPk(group);
|
||||||
|
if (!g) {
|
||||||
|
socket.emit("agendaGet", {error: {message: "group_not_found"}});
|
||||||
|
return
|
||||||
|
} else
|
||||||
|
options.include.push({model: models.Group, where: {id: g.id}, require: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.teachers) {
|
||||||
|
for (let teacher of data.teachers) {
|
||||||
|
let t = await models.User.findByPk(teacher);
|
||||||
|
if (!t) {
|
||||||
|
socket.emit("agendaGet", {error: {message: "teacher_not_found"}});
|
||||||
|
return
|
||||||
|
} else
|
||||||
|
options.include.push({model: models.User, where: {id: t.email}, require: true});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
socket.emit("agendaGet", await models.Event.findAll(options));
|
||||||
|
}
|
||||||
|
};
|
21
sockets/get/groupGet.js
Normal file
21
sockets/get/groupGet.js
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
const models = require("../../models");
|
||||||
|
|
||||||
|
module.exports = socket => {
|
||||||
|
return async (data) => {
|
||||||
|
let options = {where: {}, include: [{model: models.Semester, require: true}]};
|
||||||
|
if (data.number)
|
||||||
|
options.where.number = data.number;
|
||||||
|
if (data.semester) {
|
||||||
|
let s = await models.Semester.findByPk(data.semester);
|
||||||
|
if (!s) {
|
||||||
|
socket.emit("groupGet", {error: {message: "semester_not_found"}});
|
||||||
|
return
|
||||||
|
}
|
||||||
|
options.include[0].where = {id: s.id};
|
||||||
|
}
|
||||||
|
if (data.users)
|
||||||
|
options.include.push({model: models.User, require: true});
|
||||||
|
|
||||||
|
socket.emit("groupGet", await models.Group.findAll(options));
|
||||||
|
}
|
||||||
|
};
|
15
sockets/get/semesterGet.js
Normal file
15
sockets/get/semesterGet.js
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
const models = require("../../models");
|
||||||
|
|
||||||
|
module.exports = socket => {
|
||||||
|
return async (data) => {
|
||||||
|
let options = {where: {}, include: []};
|
||||||
|
if (data.name)
|
||||||
|
options.where.name = data.name;
|
||||||
|
if (data.year)
|
||||||
|
options.where.year = data.year;
|
||||||
|
if (data.groups)
|
||||||
|
options.include.push({model: models.Group, require: true});
|
||||||
|
|
||||||
|
socket.emit("semesterGet", await models.Semester.findAll(options));
|
||||||
|
}
|
||||||
|
};
|
17
sockets/get/userGet.js
Normal file
17
sockets/get/userGet.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
const models = require("../../models");
|
||||||
|
|
||||||
|
module.exports = socket => {
|
||||||
|
return async (data) => {
|
||||||
|
let options = {where: {}};
|
||||||
|
if (data.email)
|
||||||
|
options.where.email = data.email;
|
||||||
|
if (data.firstName && data.lastName) {
|
||||||
|
options.where.firstName = data.firstName;
|
||||||
|
options.where.lastName = data.lastName;
|
||||||
|
}
|
||||||
|
if (data.permissions)
|
||||||
|
options.where.permissions = data.permissions;
|
||||||
|
|
||||||
|
socket.emit("userGet", await models.User.findAll(options));
|
||||||
|
}
|
||||||
|
};
|
|
@ -10,6 +10,7 @@ module.exports = socket => {
|
||||||
} else {
|
} else {
|
||||||
socket.on("profileEdit", require("./profile/edit")(socket));
|
socket.on("profileEdit", require("./profile/edit")(socket));
|
||||||
socket.on("logout", require("./logout")(socket));
|
socket.on("logout", require("./logout")(socket));
|
||||||
|
socket.on("agendaGet", require("./get/agendaGet")(socket));
|
||||||
}
|
}
|
||||||
socket.emit("connected");
|
socket.emit("connected");
|
||||||
}
|
};
|
||||||
|
|
|
@ -15,4 +15,4 @@ module.exports = socket => {
|
||||||
socket.emit("login", user)
|
socket.emit("login", user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
@ -7,4 +7,4 @@ module.exports = socket => {
|
||||||
socket.emit("logout", {error: { message: "not_logged_in"}});
|
socket.emit("logout", {error: { message: "not_logged_in"}});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
|
@ -6,17 +6,18 @@ module.exports = socket => {
|
||||||
if (!user)
|
if (!user)
|
||||||
socket.emit("profileEdit", {error: {message: "not_found"}});
|
socket.emit("profileEdit", {error: {message: "not_found"}});
|
||||||
else if (!user.checkPassword(data.oldPassword))
|
else if (!user.checkPassword(data.oldPassword))
|
||||||
socket.emit("profileEdit", {error: {message: "invalid_password"}})
|
socket.emit("profileEdit", {error: {message: "invalid_password"}});
|
||||||
else {
|
else {
|
||||||
if (data.firstName !== user.firstName)
|
if (data.firstName !== user.firstName)
|
||||||
user.firstName = data.firstName;
|
user.firstName = data.firstName;
|
||||||
if (data.lastName !== user.lastName)
|
if (data.lastName !== user.lastName)
|
||||||
user.lastName = data.lastName;
|
user.lastName = data.lastName;
|
||||||
|
user.newPassword = data.newPassword;
|
||||||
if (data.password && !user.checkPassword(data.password))
|
if (data.password && !user.checkPassword(data.password))
|
||||||
user.passwordHash = data.password
|
user.passwordHash = data.password;
|
||||||
socket.request.session.user = user;
|
socket.request.session.user = user;
|
||||||
socket.request.session.save();
|
socket.request.session.save();
|
||||||
socket.emit("profileEdit", user)
|
socket.emit("profileEdit", user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
@ -18,4 +18,4 @@ module.exports = socket => {
|
||||||
await emailCheck(socket, user, null);
|
await emailCheck(socket, user, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
|
@ -16,7 +16,7 @@ module.exports = async (socket, user, callBack) => {
|
||||||
subject: "forgot password"
|
subject: "forgot password"
|
||||||
}), async (err, message) => {
|
}), async (err, message) => {
|
||||||
if (err)
|
if (err)
|
||||||
socket.emit("forgotPassword", {error: {message: "fail_send_mail"}})
|
socket.emit("forgotPassword", {error: {message: "fail_send_mail"}});
|
||||||
else {
|
else {
|
||||||
user.passwordToken = token;
|
user.passwordToken = token;
|
||||||
user.passwordTokenDate = new Date();
|
user.passwordTokenDate = new Date();
|
||||||
|
|
|
@ -20,7 +20,7 @@ async function clean() {
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
[app, models] = await setup();
|
[app, models] = await setup();
|
||||||
})
|
});
|
||||||
|
|
||||||
it("Main page content", async () => {
|
it("Main page content", async () => {
|
||||||
await request(app)
|
await request(app)
|
||||||
|
|
|
@ -2,7 +2,7 @@ extends ../template/navbar
|
||||||
|
|
||||||
block content
|
block content
|
||||||
if student === true
|
if student === true
|
||||||
div(class="marksdetails" id="visibl")
|
div(class="marksdetails" id="notvisible" onclick="setNotVisible(this.getAttribute('id'))")
|
||||||
table
|
table
|
||||||
tr
|
tr
|
||||||
th(colspan="2") Details
|
th(colspan="2") Details
|
||||||
|
@ -22,24 +22,24 @@ block content
|
||||||
th(colspan="2") Maths
|
th(colspan="2") Maths
|
||||||
tbody
|
tbody
|
||||||
tr
|
tr
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 1
|
p DS 1
|
||||||
p 18/20
|
p 18/20
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 2
|
p DS 2
|
||||||
p 16/20
|
p 16/20
|
||||||
tr
|
tr
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 1
|
p DS 1
|
||||||
p 18/20
|
p 18/20
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 2
|
p DS 2
|
||||||
p 16/20
|
p 16/20
|
||||||
tr
|
tr
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 1
|
p DS 1
|
||||||
p 18/20
|
p 18/20
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 2
|
p DS 2
|
||||||
p 16/20
|
p 16/20
|
||||||
|
|
||||||
|
@ -50,17 +50,17 @@ block content
|
||||||
th(colspan="2") TP SE
|
th(colspan="2") TP SE
|
||||||
tbody
|
tbody
|
||||||
tr
|
tr
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 1
|
p DS 1
|
||||||
p 18/20
|
p 18/20
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 2
|
p DS 2
|
||||||
p 16/20
|
p 16/20
|
||||||
tr
|
tr
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 1
|
p DS 1
|
||||||
p 18/20
|
p 18/20
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 2
|
p DS 2
|
||||||
p 16/20
|
p 16/20
|
||||||
|
|
||||||
|
@ -71,17 +71,17 @@ block content
|
||||||
th(colspan="2") PHP
|
th(colspan="2") PHP
|
||||||
tbody
|
tbody
|
||||||
tr
|
tr
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 1
|
p DS 1
|
||||||
p 18/20
|
p 18/20
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 2
|
p DS 2
|
||||||
p 16/20
|
p 16/20
|
||||||
tr
|
tr
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 1
|
p DS 1
|
||||||
p 18/20
|
p 18/20
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 2
|
p DS 2
|
||||||
p 16/20
|
p 16/20
|
||||||
|
|
||||||
|
@ -92,17 +92,17 @@ block content
|
||||||
th(colspan="2") Anglais
|
th(colspan="2") Anglais
|
||||||
tbody
|
tbody
|
||||||
tr
|
tr
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 1
|
p DS 1
|
||||||
p 18/20
|
p 18/20
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 2
|
p DS 2
|
||||||
p 16/20
|
p 16/20
|
||||||
tr
|
tr
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 1
|
p DS 1
|
||||||
p 18/20
|
p 18/20
|
||||||
td
|
td(onclick="setVisible(this.getAttribute('id'))")
|
||||||
p DS 2
|
p DS 2
|
||||||
p 16/20
|
p 16/20
|
||||||
|
|
||||||
|
@ -111,11 +111,11 @@ block content
|
||||||
div(class="col s12 m10 offset-m1 marksgroup")
|
div(class="col s12 m10 offset-m1 marksgroup")
|
||||||
h3 Select a group
|
h3 Select a group
|
||||||
div(id="flexgroup")
|
div(id="flexgroup")
|
||||||
p G1S1
|
p(onclick="marksformChange(this.getAttribute('id'))") G1S1
|
||||||
p G2S2
|
p(onclick="marksformChange(this.getAttribute('id'))") G2S2
|
||||||
p G3S3
|
p(onclick="marksformChange(this.getAttribute('id'))") G3S3
|
||||||
|
|
||||||
form
|
form(id="marksform")
|
||||||
table(id="markstable")
|
table(id="markstable")
|
||||||
thead
|
thead
|
||||||
tr
|
tr
|
||||||
|
@ -185,4 +185,6 @@ block content
|
||||||
input(type="number" value="16")
|
input(type="number" value="16")
|
||||||
td
|
td
|
||||||
input(type="number")
|
input(type="number")
|
||||||
input(id="marksubmit" type="submit" value="Enregistrer")
|
input(id="marksubmit" type="submit" value="Enregistrer")
|
||||||
|
|
||||||
|
script(src="/javascripts/marks.js")
|
Reference in a new issue