Skip to content

Commit fcb21ea

Browse files
committed
Finish the rendez-vous system
1 parent d618bb3 commit fcb21ea

File tree

12 files changed

+370
-88
lines changed

12 files changed

+370
-88
lines changed

mails/rendez_vous_created.mjml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<mjml>
2+
<mj-body background-color="#f3f3f3">
3+
<mj-section background-color="#E8E8E8">
4+
<mj-column width="100%">
5+
<mj-text font-size="20px" color="black" font-family="helvetica">
6+
<h3>Hi <%= firstname %>,</h3>
7+
<br />
8+
<p>A new rendez-vous as been planned on WAP, the scheduled date is: <%= date %></p>
9+
<br />
10+
<p>The agenda for this rendez-vous is the following:</p>
11+
<p><%- agenda %></p>
12+
<br />
13+
<p>If you cannot attend this scheduled rendez-vous, please let know your team member as soon as possible.</p>
14+
</mj-text>
15+
<mj-text font-size="20px" color="black" font-family="helvetica">
16+
<h3 style="color: #FF6363">What is WAP ?</h3>
17+
<p>WAP is an open-source project which goal is to simplify the managment of sprints and generation of the PLDs.</p>
18+
<p>It has been created by a student to help in the process of handling users stories of a sprint and their versionning.</p>
19+
<p>You can find more about WAP on the main <a href="<%= wapRepository %>">Github repository</a>.</p>
20+
</mj-text>
21+
</mj-column>
22+
</mj-section>
23+
<mj-section background-color="#C8C8C8">
24+
<mj-column>
25+
<mj-text font-size="13px" color="grey" font-family="helvetica">
26+
You received this email because a rendez-vous as been editor or created on WAP.
27+
<br /><br />
28+
This is an automated email, please do not respond to it.
29+
</mj-text>
30+
</mj-column>
31+
</mj-section>
32+
</mj-body>
33+
</mjml>

mails/report_posted.mjml

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<mjml>
2+
<mj-body background-color="#f3f3f3">
3+
<mj-section background-color="#E8E8E8">
4+
<mj-column width="100%">
5+
<mj-text font-size="20px" color="black" font-family="helvetica">
6+
<h3>Hi <%= firstname %>,</h3>
7+
<br />
8+
<p>A report as been posted for the rendez-vous of the <%= date %>, it currently says as follow:</p>
9+
<p><%- report %></p>
10+
<br />
11+
<p>The attendances as also been posted, you have been marked as <%= attendance %></p>
12+
<br />
13+
<p>If you think your attendance status is wrong, please contact your WAP administrator.</p>
14+
</mj-text>
15+
<mj-text font-size="20px" color="black" font-family="helvetica">
16+
<h3 style="color: #FF6363">What is WAP ?</h3>
17+
<p>WAP is an open-source project which goal is to simplify the managment of sprints and generation of the PLDs.</p>
18+
<p>It has been created by a student to help in the process of handling users stories of a sprint and their versionning.</p>
19+
<p>You can find more about WAP on the main <a href="<%= wapRepository %>">Github repository</a>.</p>
20+
</mj-text>
21+
</mj-column>
22+
</mj-section>
23+
<mj-section background-color="#C8C8C8">
24+
<mj-column>
25+
<mj-text font-size="13px" color="grey" font-family="helvetica">
26+
You received this email because a rendez-vous as been editor or created on WAP.
27+
<br /><br />
28+
This is an automated email, please do not respond to it.
29+
</mj-text>
30+
</mj-column>
31+
</mj-section>
32+
</mj-body>
33+
</mjml>

package-lock.json

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "wap",
3-
"version": "0.9.0",
3+
"version": "0.10.0",
44
"description": "Web Administration Panel",
55
"main": "build/app.js",
66
"repository": "https://github.com/theohemmer/wap",
@@ -14,6 +14,7 @@
1414
"license": "MIT",
1515
"dependencies": {
1616
"bcrypt": "^5.0.1",
17+
"dayjs": "^1.11.7",
1718
"dotenv": "^16.0.2",
1819
"ejs": "^3.1.8",
1920
"express": "^4.18.1",

src/controllers/rendezVousController.ts

Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import IController from "./controller";
22
import express, {Request, Response} from "express";
33
import { authUser } from "../middlewares/auth";
4+
import { checkPerm } from "../middlewares/checkPerms";
5+
import RendezVous from "../models/rendezVous";
6+
import User from "../models/user";
7+
import RendezVousUserAttendance from "../models/rendezVousUserAttendance";
8+
import dayjs from "dayjs";
9+
import ejs from "ejs";
10+
import { sendRendezVousCreatedMail, sendRendezVousPassedMail } from "../mails";
411

512
class RendezVousController implements IController {
613
public path = "/rendezVous";
@@ -11,16 +18,151 @@ class RendezVousController implements IController {
1118
}
1219

1320
private initializeRoutes() {
14-
this.router.get("/", authUser, this.rendezVous);
21+
this.router.get("/", authUser, checkPerm("MAINTENER"), this.rendezVous);
22+
this.router.get("/create", authUser, checkPerm("EDITOR"), this.createGET);
23+
this.router.post("/create", authUser, checkPerm("EDITOR"), this.createPOST);
24+
this.router.get("/edit/:id", authUser, checkPerm("EDITOR"), this.editGET);
25+
this.router.post("/edit/:id", authUser, checkPerm("EDITOR"), this.editPOST);
1526
}
1627

17-
private rendezVous = (req: Request, res: Response) => {
28+
private rendezVous = async (req: Request, res: Response) => {
29+
const canEdit = req.user.role == "ADMIN" || req.user.role == "EDITOR";
30+
const rendezVouss = await RendezVous.findAll({
31+
include: [
32+
{
33+
model: RendezVousUserAttendance,
34+
include: [
35+
User
36+
]
37+
}
38+
],
39+
order: [
40+
["date", "DESC"]
41+
]
42+
});
43+
1844
return res.render("rendezVous/rendezVous", {
1945
currentPage: '/rendezVous',
2046
wap: req.wap,
2147
user: req.user,
48+
canEdit: canEdit,
49+
rendezVouss: rendezVouss,
50+
escapeXML: ejs.escapeXML,
51+
dayjs: dayjs
2252
})
2353
}
54+
55+
private createGET = (req: Request, res: Response) => {
56+
return res.render("rendezVous/createRendezVous", {
57+
currentPage: '/rendezVous',
58+
wap: req.wap,
59+
user: req.user
60+
});
61+
}
62+
63+
private createPOST = async (req: Request, res: Response) => {
64+
if (!req.body.date || !req.body.agenda) return res.malformed();
65+
const date = new Date(req.body.date);
66+
const allUsers = await User.findAll();
67+
const rendezVous = await RendezVous.create({
68+
date: date,
69+
agenda: req.body.agenda.replace(/[\r]+/g, '')
70+
});
71+
72+
for (const user of allUsers) {
73+
const userAppointment = await RendezVousUserAttendance.create();
74+
await userAppointment.$set('rendezVous', rendezVous);
75+
await userAppointment.$set('user', user);
76+
sendRendezVousCreatedMail(user, date, rendezVous.agenda);
77+
}
78+
79+
return res.redirect("/rendezVous/?info=success");
80+
}
81+
82+
private editGET = async (req: Request, res: Response) => {
83+
const toEdit = await RendezVous.findOne({
84+
where: {
85+
id: req.params.id as any
86+
},
87+
include: [
88+
{
89+
model: RendezVousUserAttendance,
90+
include: [
91+
User
92+
]
93+
}
94+
]
95+
});
96+
return res.render("rendezVous/editRendezVous", {
97+
currentPage: '/rendezVous',
98+
wap: req.wap,
99+
user: req.user,
100+
toEdit: toEdit,
101+
passed: toEdit.sheduling == "PASSED",
102+
dayjs: dayjs
103+
})
104+
}
105+
106+
private editPOST = async (req: Request, res: Response) => {
107+
let newPassed = false;
108+
const rendezVous = await RendezVous.findOne({
109+
where: {
110+
id: req.params.id
111+
},
112+
include: [
113+
{
114+
model: RendezVousUserAttendance,
115+
include: [
116+
User
117+
]
118+
}
119+
]
120+
})
121+
if (!rendezVous) return res.redirect("/rendezVous/?error=invalid_id");
122+
if (req.body.date) {
123+
const date = new Date(req.body.date);
124+
rendezVous.date = date;
125+
}
126+
if (req.body.agenda) {
127+
rendezVous.agenda = req.body.agenda.replace(/[\r]+/g, '');
128+
}
129+
if (req.body.report) {
130+
rendezVous.report = req.body.report.replace(/[\r]+/g, '');
131+
}
132+
if (req.body.passed && req.body.passed == "true") {
133+
if (rendezVous.sheduling != "PASSED") {
134+
newPassed = true;
135+
}
136+
rendezVous.sheduling = "PASSED";
137+
}
138+
await rendezVous.save();
139+
for (const attendance of rendezVous.userAttendances) {
140+
if (req.body["presence_" + attendance.id]) {
141+
const value = req.body["presence_" + attendance.id];
142+
if (value == "na") {
143+
attendance.attendance = "UNDEFINED";
144+
} else if (value == "present") {
145+
attendance.attendance = "PRESENT";
146+
} else if (value == "absent") {
147+
attendance.attendance = "ABSENT";
148+
}
149+
await attendance.save();
150+
}
151+
}
152+
if (newPassed) {
153+
for (const attendance of rendezVous.userAttendances) {
154+
let userAttendanceStr = "indéfini";
155+
if (attendance.attendance == "PRESENT") {
156+
userAttendanceStr = "present";
157+
} else if (attendance.attendance == "ABSENT") {
158+
userAttendanceStr = "absent";
159+
}
160+
sendRendezVousPassedMail(attendance.user, rendezVous.date, rendezVous.report, userAttendanceStr);
161+
}
162+
}
163+
return res.redirect("/rendezVous/?info=success");
164+
}
165+
24166
}
25167

26168
export default RendezVousController;

src/mails/index.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import ejs from 'ejs';
33
import User from '../models/user';
44
import { wap } from "../app";
55
import Card from '../models/card';
6+
import dayjs from 'dayjs';
67

78
let transporter :Transporter = null;
89

@@ -123,4 +124,43 @@ export async function sendCardAwaitingApprovalEmail(user: User, creator: User, c
123124
html: rendered
124125
}
125126
await transporter.sendMail(options);
127+
}
128+
129+
export async function sendRendezVousCreatedMail(user: User, date: Date, agenda: string) {
130+
if (!checkMailTransporter())
131+
return;
132+
const rendered = await ejs.renderFile('./mails/build/rendez_vous_created.ejs', {
133+
firstname: user.firstname,
134+
date: dayjs(date).format("DD/MM/YYYY [à] HH:MM [(UTC+2)]"),
135+
agenda: ejs.escapeXML(agenda).replace(/\n/g, '<br>'),
136+
wapCardsLink: wap.config.Hostname.value + "/cards",
137+
wapRepository: "https://github.com/theohemmer/wap"
138+
});
139+
const options = {
140+
from: '"WAP" ' + wap.config.SMTP_User.value.toString(),
141+
subject: "WAP - Rendez-Vous created",
142+
to: user.email,
143+
html: rendered
144+
}
145+
await transporter.sendMail(options);
146+
}
147+
148+
export async function sendRendezVousPassedMail(user: User, date: Date, report: string, attendance: string) {
149+
if (!checkMailTransporter())
150+
return;
151+
const rendered = await ejs.renderFile('./mails/build/report_posted.ejs', {
152+
firstname: user.firstname,
153+
date: dayjs(date).format("DD/MM/YYYY [à] HH:MM [(UTC+2)]"),
154+
report: ejs.escapeXML(report).replace(/\n/g, '<br>'),
155+
attendance: attendance,
156+
wapCardsLink: wap.config.Hostname.value + "/cards",
157+
wapRepository: "https://github.com/theohemmer/wap"
158+
});
159+
const options = {
160+
from: '"WAP" ' + wap.config.SMTP_User.value.toString(),
161+
subject: "WAP - Rendez-Vous report published",
162+
to: user.email,
163+
html: rendered
164+
}
165+
await transporter.sendMail(options);
126166
}

src/migrations/20230205233000-create-rendezVous.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ module.exports = {
2323
},
2424
sheduling: {
2525
type: DataTypes.ENUM("PLANNED", "PASSED")
26+
},
27+
createdAt: {
28+
type: DataTypes.DATE,
29+
defaultValue: Sequelize.literal("CURRENT_TIMESTAMP")
30+
},
31+
updatedAt: {
32+
type: DataTypes.DATE,
33+
defaultValue: Sequelize.fn("NOW")
2634
}
2735
}, { transaction: transaction })
2836
])

src/migrations/20230205233001-create-rendezVousUserAttendance.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ module.exports = {
1919
},
2020
attendance: {
2121
type: DataTypes.ENUM("UNDEFINED", "PRESENT", "ABSENT")
22+
},
23+
createdAt: {
24+
type: DataTypes.DATE,
25+
defaultValue: Sequelize.literal("CURRENT_TIMESTAMP")
26+
},
27+
updatedAt: {
28+
type: DataTypes.DATE,
29+
defaultValue: Sequelize.fn("NOW")
2230
}
2331
}, { transaction: transaction })
2432
])

0 commit comments

Comments
 (0)