Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ frontend/node_modules/
# Ignorer les node_modules du backend
backend/node_modules/
backend/uploads/
backend/exports/

# Autres fichiers à ignorer
.DS_Store
Expand Down
41 changes: 41 additions & 0 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"googleapis": "^148.0.0",
"handlebars": "^4.7.8",
"jsdom": "^26.1.0",
"json2csv": "^6.0.0-alpha.2",
"jsonwebtoken": "^9.0.2",
"multer": "^1.4.5-lts.2",
"nodemailer": "^6.10.1",
Expand Down
3 changes: 3 additions & 0 deletions backend/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import challengeRoutes from './src/routes/challenge.routes';
import emailRoutes from './src/routes/email.routes';
import newsRoutes from './src/routes/news.routes';
import discordRoutes from './src/routes/discord.routes';
import tentRoutes from './src/routes/tent.routes';
import { server_port } from './src/utils/secret';
import { initUser } from './src/database/initdb/initUser'
import { initRoles } from './src/database/initdb/initrole'
Expand Down Expand Up @@ -56,9 +57,11 @@ async function startServer() {
app.use('/api/email',authenticateUser, emailRoutes);
app.use('/api/news',authenticateUser, newsRoutes);
app.use('/api/discord',authenticateUser, discordRoutes);
app.use('/api/tent',authenticateUser, tentRoutes);
app.use("/api/uploads/imgnews", express.static(path.join(__dirname, "/uploads/imgnews")));
app.use("/api/uploads/foodmenu", express.static(path.join(__dirname, "/uploads/foodmenu")));
app.use("/api/uploads/plannings", express.static(path.join(__dirname, "/uploads/plannings")));
app.use("/api/exports/bus", express.static(path.join(__dirname, "/exports/bus")));

// Démarrage du serveur
app.listen(server_port, () => {
Expand Down
21 changes: 17 additions & 4 deletions backend/src/controllers/email.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,29 @@ export interface EmailOptions {
}

// Fonction pour générer l'HTML à partir du template
export const generateEmailHtml = (templateName: string, data: any) => {
export const generateEmailHtml = (templateName: string, data: any) => {
switch (templateName) {
case 'templateNotebook':
return template.compileTemplate({ notebook: data.notebook }, template.templateNotebook);
return template.compileTemplate({ notebook: data.notebook }, template.templateNotebook);

case 'templateAttributionBus':
return template.compileTemplate({ bus: data.bus, time: data.tim }, template.templateAttributionBus);
return template.compileTemplate({ bus: data.bus, time: data.time }, template.templateAttributionBus);

case 'templateWelcome':
return template.compileTemplate({ token: data.token }, template.templateWelcome);

case 'templateNotifyNews':
return template.compileTemplate({title: data.title, description: data.description,}, template.templateNotifyNews);
return template.compileTemplate(
{ title: data.title, description: data.description },
template.templateNotifyNews
);

case 'templateNotifyTentConfirmation':
return template.compileTemplate(
{ user1: data.user1, user2: data.user2, confirmed: data.confirmed },
template.templateNotifyTentConfirmation
);

default:
return null;
}
Expand Down
11 changes: 11 additions & 0 deletions backend/src/controllers/im_export.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,14 @@ export const updatePlannings = async (req: Request, res: Response) => {
Error(res, { msg: "Erreur lors de la mise à jour du Planning" });
}
};


export const exportUsersCSV = async (req: Request, res: Response) => {
try {
const filePath = await export_service.exportUsersToCSV();
Ok(res, {msg : 'CSV des bus généré'});
} catch (error) {
console.error(error);
Error(res, { msg: "Erreur lors de l'export CSV" });
}
};
113 changes: 113 additions & 0 deletions backend/src/controllers/tent.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { Request, Response } from "express";
import * as tent_service from "../services/tent.service";
import { Error, Ok } from "../utils/responses";
import { sendEmail } from "../services/email.service";
import { getUserById } from "../services/user.service";
import { generateEmailHtml } from "./email.controller";

export const createTent = async (req: Request, res: Response) => {
const { userId2 } = req.body;
const userId1 = req.user?.userId; // Créateur = utilisateur connecté

if (!userId1 || !userId2) {
return Error(res, { msg: "Identifiants utilisateurs manquants." });
}

try {
await tent_service.createTent(userId1, userId2);
Ok(res, { msg: "Tente réservée avec succès." });
} catch (err: any) {
Error(res, { msg: err.message || "Erreur lors de la création de la tente." });
}
};

export const cancelTent = async (req: Request, res: Response) => {

const userId1 = req.user?.userId;

if (!userId1) {
return Error(res, { msg: "Identifiants utilisateurs manquants." });
}

try {
await tent_service.cancelTent(userId1);
Ok(res, { msg: "Tente annulée." });
} catch (err) {
Error(res, { msg: "Erreur lors de l'annulation." });
}
};

export const getUserTent = async (req: Request, res: Response) => {
const userId = req.user?.userId;
if (!userId) return Error(res, { msg: "Utilisateur non authentifié." });

try {
const tent = await tent_service.getTentByUser(userId);
Ok(res, { data: tent });
} catch (err) {
Error(res, { msg: "Erreur lors de la récupération." });
}
};

export const getAllTentPairs = async (req: Request, res: Response) => {
try {
const tents = await tent_service.getAllTents();
Ok(res, { data: tents });
} catch (err) {
Error(res, { msg: "Erreur lors de la récupération des binômes." });
}
};

export const toggleTentConfirmation = async (req: Request, res: Response) => {

const { userId1, userId2, confirmed } = req.body;

if (!userId1 || !userId2 || typeof confirmed !== "boolean") {
return Error(res, { msg: "Paramètres manquants ou invalides." });
}

try {
// Mise à jour de la tente
await tent_service.toggleTentConfirmation(userId1, userId2, confirmed);

// Récupération des infos utilisateurs
const user1 = await getUserById(userId1);
const user2 = await getUserById(userId2);

if (!user1 || !user2) {
return Error(res, { msg: "Impossible de récupérer les utilisateurs." });
}

// Génération du contenu HTML
const htmlEmail = generateEmailHtml("templateNotifyTentConfirmation", {
user1: `${user1.firstName} ${user1.lastName}`,
user2: `${user2.firstName} ${user2.lastName}`,
confirmed,
});

// Options d’email
const emailOptions = {
from: "integration@utt.fr",
to: [user1.email, user2.email],
subject: confirmed
? "🎉 Votre tente a été validée !"
: "⛺ Votre tente a été dévalidée",
text: "", // optionnel
html: htmlEmail,
};

// Envoi
await sendEmail(emailOptions);

Ok(res, {
msg: confirmed
? "Tente validée et email envoyé."
: "Tente dévalidée et email envoyé.",
});
} catch (err: any) {
console.error(err);
Error(res, {
msg: err.message || "Erreur lors de la mise à jour ou de l'envoi d'email.",
});
}
};
1 change: 1 addition & 0 deletions backend/src/database/initdb/initrole.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const roles = [
{ name: "Argentique", description: "Couvrir les événements de l’intégration, prendre des photos" },
{ name: "Bouffe", description: "Prévoir, organiser et coordonner tous les repas de l’inté. La bouffe c’est sacré !" },
{ name: "Bar", description: "Prévoir, organiser et coordonner toutes les boissons de l’inté !" },
{ name: "Bénévole", description: "Deviens bénévole et participe à différentes activités de l’inté !" },
{ name: "Cahier de vacances", description: "Élaborer le futur cahier de vacances des nouveaux avec des petits exercices et blagues." },
{ name: "Chasse au trésor", description: "Elaborer une chasse au trésor dans toute la capitale Troyenne." },
{ name: "Communication", description: "Préparer et gérer toute la communication de l’intégration" },
Expand Down
7 changes: 4 additions & 3 deletions backend/src/routes/im_export.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ const uploadPlannings = createUploadMiddleware("uploads/plannings/", false);
const imexportRouter = express.Router();


imexportRouter.post('/admin/foodimport',checkRole("Admin",[]),uploadFoodMenu.multerUpload.single("foodFile"), uploadFoodMenu.verifyAndSave, imexportController.updateFoodMenu)
imexportRouter.post('/admin/plannings',checkRole("Admin",[]),uploadPlannings.multerUpload.single("planningFile"), uploadPlannings.verifyAndSave, imexportController.updatePlannings)
imexportRouter.post('/admin/export',checkRole("Admin",[]), imexportController.exportAllDataToSheets)
imexportRouter.post('/admin/foodimport',checkRole("Admin",[]),uploadFoodMenu.multerUpload.single("foodFile"), uploadFoodMenu.verifyAndSave, imexportController.updateFoodMenu);
imexportRouter.post('/admin/plannings',checkRole("Admin",[]),uploadPlannings.multerUpload.single("planningFile"), uploadPlannings.verifyAndSave, imexportController.updatePlannings);
imexportRouter.post('/admin/exportgsheet',checkRole("Admin",[]), imexportController.exportAllDataToSheets);
imexportRouter.get('/admin/exportbus',checkRole("Admin",[]), imexportController.exportUsersCSV);



Expand Down
21 changes: 21 additions & 0 deletions backend/src/routes/tent.routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import express from 'express';
import * as tentController from '../controllers/tent.controller';
import { checkRole } from '../middlewares/user.middleware';

const tentRouter = express.Router();

// Admin routes
tentRouter.get('/admin/tents', checkRole("Admin",[]), tentController.getAllTentPairs);
tentRouter.post('/admin/toggleconfirmation', checkRole("Admin",[]), tentController.toggleTentConfirmation);


// User routes
tentRouter.post("/user/tent",checkRole("Nouveau",[]), tentController.createTent);
tentRouter.delete("/user/tent",checkRole("Nouveau",[]), tentController.cancelTent);
tentRouter.get("/user/tent",checkRole("Nouveau",[]), tentController.getUserTent);





export default tentRouter;
14 changes: 14 additions & 0 deletions backend/src/schemas/Relational/usertent.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { pgTable, serial, integer, primaryKey, boolean, timestamp } from "drizzle-orm/pg-core";
import { userSchema } from "../Basic/user.schema";

export const userTentSchema = pgTable("user_tent", {
user_id_1: integer("user_id_1").references(() => userSchema.id, { onDelete: "cascade" }),
user_id_2: integer("user_id_2").references(() => userSchema.id, { onDelete: "cascade" }),
confirmed: boolean("confirmed").default(false), // optionnel : pour savoir si les deux ont validé
created_at: timestamp("created_at").defaultNow(),
}, (table) => [
primaryKey({ columns: [table.user_id_1, table.user_id_2] }),
]);


export type UserTent = typeof userTentSchema.$inferSelect;
48 changes: 48 additions & 0 deletions backend/src/services/im_export.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// src/services/googleSheets.service.ts
import { google } from 'googleapis';
import { JWT } from 'google-auth-library';
import { existsSync, mkdirSync, writeFileSync } from "fs";
import { parse } from "json2csv";
import * as user_service from './user.service';


const path = require('path');
const keyFilePath = path.resolve(__dirname, '../utils/google_credentials.json');
Expand Down Expand Up @@ -37,3 +41,47 @@ export const writeToGoogleSheet = async (
throw error;
}
};


export const exportUsersToCSV = async (): Promise<string> => {
const users = await user_service.getUsersAll();

const formattedUsers = users.map(u => {
const isOrga = u.roles && u.roles.length > 0;
const isCE = u.permission === "Student" && u.teamId !== null;
const isBenevole = u.roles.some(role => role.roleName === "Bénévole");

return {
userId: u.id,
prenom: u.first_name ?? "",
nom: u.last_name ?? "",
mail: u.email ?? "",
telephone: u.contact ?? "",
nouveau: u.permission === "Nouveau",
ce: isCE,
num_equipe: u.teamId ?? null,
benevole: isBenevole,
orga: isOrga,
majeur: u.majeur ?? false,
};
});

const csv = parse(formattedUsers, {
fields: [
"id", "prenom", "nom", "mail", "telephone",
"nouveau", "ce", "num_equipe", "benevole", "orga", "majeur", "bus_manual"
]
});

const exportDir = path.join(__dirname, "../../exports/bus");
const filePath = path.join(exportDir, "bus.csv");

if (!existsSync(exportDir)) {
mkdirSync(exportDir, { recursive: true });
}

writeFileSync(filePath, csv);

return filePath;

};
Loading