Mise en place Tchat via websocket

This commit is contained in:
Lensors 2025-04-12 22:10:38 +02:00
parent d644c8efef
commit 3933d81f8f
13 changed files with 309 additions and 32 deletions

View File

@ -1,5 +1,6 @@
DROP TABLE IF EXISTS bloc; DROP TABLE IF EXISTS bloc;
DROP TABLE IF EXISTS page; DROP TABLE IF EXISTS page;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS utilisateur; DROP TABLE IF EXISTS utilisateur;
CREATE TABLE utilisateur ( CREATE TABLE utilisateur (
@ -12,24 +13,54 @@ CREATE TABLE utilisateur (
CREATE TABLE page ( CREATE TABLE page (
id INT PRIMARY KEY AUTO_INCREMENT, id INT PRIMARY KEY AUTO_INCREMENT,
titre VARCHAR(200) NOT NULL, titre VARCHAR(200) NOT NULL,
date_creation DATETIME NOT NULL, date_creation DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification DATETIME NOT NULL, date_modification DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
auteur_id INT NOT NULL, auteur_id INT NOT NULL,
droits ENUM('LECTURE', 'ECRITURE', 'ADMIN') NOT NULL, droits ENUM('LECTURE', 'ECRITURE', 'ADMIN') NOT NULL,
FOREIGN KEY (auteur_id) REFERENCES utilisateur(id) FOREIGN KEY (auteur_id) REFERENCES utilisateur(id) ON DELETE CASCADE
); );
CREATE TABLE bloc ( CREATE TABLE bloc (
id INT PRIMARY KEY AUTO_INCREMENT, id INT PRIMARY KEY AUTO_INCREMENT,
type ENUM('TEXTE', 'LISTE', 'TITRE', 'CODE', 'PAGE') NOT NULL, type ENUM('TEXTE', 'LISTE', 'TITRE', 'CODE', 'PAGE') NOT NULL,
contenu TEXT, contenu TEXT,
date_creation DATETIME NOT NULL, date_creation DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
date_modification DATETIME NOT NULL, date_modification DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
page_id INT NOT NULL, page_id INT NOT NULL,
ordre INT NOT NULL, ordre INT NOT NULL,
auteur_id INT NOT NULL, auteur_id INT NOT NULL,
metadata JSON, metadata JSON,
FOREIGN KEY (page_id) REFERENCES page(id), FOREIGN KEY (page_id) REFERENCES page(id) ON DELETE CASCADE,
FOREIGN KEY (auteur_id) REFERENCES utilisateur(id) FOREIGN KEY (auteur_id) REFERENCES utilisateur(id) ON DELETE CASCADE
); );
CREATE TABLE messages (
id INT AUTO_INCREMENT PRIMARY KEY,
login VARCHAR(100) NOT NULL,
contenu TEXT,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
DELIMITER //
CREATE TRIGGER trg_bloc_maj
BEFORE UPDATE ON bloc
FOR EACH ROW
BEGIN
SET NEW.date_modification = NOW();
END;
//
DELIMITER ;
DELIMITER //
CREATE TRIGGER trg_page_maj
BEFORE UPDATE ON page
FOR EACH ROW
BEGIN
SET NEW.date_modification = NOW();
END;
//
DELIMITER ;

View File

@ -25,13 +25,17 @@ public class AfficherPage extends HttpServlet {
if(u != null) { if(u != null) {
u.chargerPages(); u.chargerPages();
ArrayList<Page> listePages = u.getListePages(); ArrayList<Page> listePages = u.getListePages();
ArrayList<Message> listeMessages = Message.getListeMessages();
request.setAttribute("listePages", listePages); request.setAttribute("listePages", listePages);
request.setAttribute("listeMessages", listeMessages);
if (idStr != null ) { if (idStr != null ) {
try { try {
int id = Integer.parseInt(idStr); int id = Integer.parseInt(idStr);
Page page = Page.getPageById(u.getId(), id); Page page = Page.getPageById(u.getId(), id);
if (page != null) { if (page != null) {
request.setAttribute("page", page); request.setAttribute("page", page);
request.getRequestDispatcher("/WEB-INF/AfficherPage.jsp").forward(request, response); request.getRequestDispatcher("/WEB-INF/AfficherPage.jsp").forward(request, response);

View File

@ -0,0 +1,82 @@
package projet;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
public class Message extends ParamBD{
private String login;
private String contenu;
public Message() {
}
public Message(String login, String contenu) {
this.login = login;
this.contenu = contenu;
}
public String getLogin() {
return login;
}
public void setLogin(String auteur) {
this.login = auteur;
}
public String getContenu() {
return contenu;
}
public void setContenu(String contenu) {
this.contenu = contenu;
}
protected static void ajouterMessage(String login, String contenu) {
try {
Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword);
String sql = " INSERT INTO messages(contenu, login)"
+ " VALUES (?, ?)"
+ ";";
PreparedStatement pst = connexion.prepareStatement(sql);
pst.setString(1, contenu);
pst.setString(2, login);
pst.executeUpdate();
pst.close();
connexion.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
protected static ArrayList<Message> getListeMessages() {
ArrayList<Message> messages = new ArrayList<>();
try {
Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword);
String sql = " SELECT login, contenu"
+ " FROM messages"
+ ";";
PreparedStatement pst = connexion.prepareStatement(sql);
ResultSet rs = pst.executeQuery();
while(rs.next()) {
String contenu = rs.getString("contenu");
String auteur = rs.getString("login");
Message message = new Message(auteur, contenu);
messages.add(message);
}
rs.close();
pst.close();
connexion.close();
} catch (SQLException e) {
e.printStackTrace();
}
return messages;
}
}

View File

@ -43,8 +43,9 @@ public class NouvellePage extends HttpServlet {
if (titre == null || titre.isEmpty()) { if (titre == null || titre.isEmpty()) {
response.sendRedirect("AfficherPage"); response.sendRedirect("AfficherPage");
} else { } else {
Page.ajouterPage(u.getId(), titre, LocalDate.now()); int id = Page.ajouterPage(u.getId(), titre, LocalDate.now());
response.sendRedirect("AfficherPage"); if (id !=-1) response.sendRedirect("AfficherPage?id=" + id);
else response.sendRedirect("AfficherPage");
} }
} else { } else {
response.sendRedirect("/Projet/"); response.sendRedirect("/Projet/");

View File

@ -5,6 +5,7 @@ import java.sql.DriverManager;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement;
import java.time.LocalDate; import java.time.LocalDate;
import java.sql.Date; import java.sql.Date;
import java.util.ArrayList; import java.util.ArrayList;
@ -97,13 +98,15 @@ public class Page extends ParamBD {
return "Page{id=" + id + ", titre='" + titre + "', dateCreation=" + dateCreation + ", dateModification=" + dateModification + ", droits=" + droits + "}"; return "Page{id=" + id + ", titre='" + titre + "', dateCreation=" + dateCreation + ", dateModification=" + dateModification + ", droits=" + droits + "}";
} }
protected static void ajouterPage(int idU, String t, LocalDate dl) { protected static int ajouterPage(int idU, String t, LocalDate dl) {
int idGenere = -1;
try { try {
Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword); Connection connexion = DriverManager.getConnection(bdURL, bdLogin, bdPassword);
String sql = " INSERT INTO page(titre, date_creation, date_modification, auteur_id, droits)" String sql = " INSERT INTO page(titre, date_creation, date_modification, auteur_id, droits)"
+ " VALUES (?, ?, ?, ?, ?)" + " VALUES (?, ?, ?, ?, ?)"
+ ";"; + ";";
PreparedStatement pst = connexion.prepareStatement(sql); PreparedStatement pst = connexion.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
pst.setString(1, t); pst.setString(1, t);
pst.setDate(2, Date.valueOf(dl)); pst.setDate(2, Date.valueOf(dl));
pst.setDate(3, Date.valueOf(dl)); pst.setDate(3, Date.valueOf(dl));
@ -111,11 +114,19 @@ public class Page extends ParamBD {
pst.setString(5, "ADMIN"); pst.setString(5, "ADMIN");
pst.executeUpdate(); pst.executeUpdate();
ResultSet rs = pst.getGeneratedKeys();
if (rs.next()) {
idGenere = rs.getInt(1);
}
rs.close();
pst.close(); pst.close();
connexion.close(); connexion.close();
} catch (SQLException e) { } catch (SQLException e) {
e.printStackTrace(); e.printStackTrace();
} }
return idGenere;
} }
protected static Page getPageById(int idU, int id) { protected static Page getPageById(int idU, int id) {

View File

@ -0,0 +1,52 @@
package projet;
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
@ServerEndpoint("/ws/tchat")
public class Tchat {
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<>());
@OnOpen
public void onOpen(Session session) {
sessions.add(session);
}
@OnMessage
public void onMessage(String message, Session senderSession) throws IOException {
System.out.println(message);
String[] parts = message.split(" : ", 2);
System.out.println(parts[0].trim());
System.out.println(parts[1].trim());
System.out.println("");
if (parts.length == 2) {
String login = parts[0].trim();
String contenu = parts[1].trim();
Message.ajouterMessage(login, contenu);
synchronized (sessions) {
for (Session session : sessions) {
if (session.isOpen()) {
session.getBasicRemote().sendText(message);
}
}
}
}
}
@OnClose
public void onClose(Session session) {
sessions.remove(session);
}
@OnError
public void onError(Session session, Throwable throwable) {
sessions.remove(session);
throwable.printStackTrace();
}
}

View File

@ -9,7 +9,6 @@ import java.sql.Statement;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import projet.Bloc.Type;
import projet.Page.Droit; import projet.Page.Droit;

View File

@ -73,6 +73,5 @@
</form> </form>
</div> </div>
</div> </div>
</main>
<jsp:include page="Footer.jsp" /> <jsp:include page="Footer.jsp" />

View File

@ -8,10 +8,14 @@
<div class="columns"> <div class="columns">
<!-- La colonne pour le menu des pages -->
<div class="column is-one-fifth"> <div class="column is-one-fifth">
<jsp:include page="MenuPages.jsp" /> <jsp:include page="MenuPages.jsp" />
</div> </div>
<div class="column">
<!-- La colonne pour la page choisie -->
<div class="column is-half">
<c:choose> <c:choose>
<c:when test="${not empty page.titre}"> <c:when test="${not empty page.titre}">
<h2 class="block">${page.titre}</h2> <h2 class="block">${page.titre}</h2>
@ -41,7 +45,8 @@
data-id="${bloc.id}" data-id="${bloc.id}"
data-ordre="${bloc.ordre}" data-ordre="${bloc.ordre}"
data-type="${bloc.type}" data-type="${bloc.type}"
>${bloc.contenu}</textarea> placeholder = "Tapez ici...";
></textarea>
</div> </div>
<div class="control"> <div class="control">
<button class="delete is-danger delete-bloc-btn" data-id="${bloc.id}"></button> <button class="delete is-danger delete-bloc-btn" data-id="${bloc.id}"></button>
@ -54,12 +59,22 @@
</c:otherwise> </c:otherwise>
</c:choose> </c:choose>
</div> </div>
<div class="column is-one-quarter">
<!-- La colonne pour le Tchat -->
<div class="column is-one-quarter is-flex is-flex-direction-column">
<jsp:include page="Tchat.jsp" /> <jsp:include page="Tchat.jsp" />
</div> </div>
</div> </div>
<script> <script>
// focus sur le dernier textarea quand la page s'ouvre
window.addEventListener('DOMContentLoaded', () => {
const textareas = document.querySelectorAll('textarea');
if (textareas.length > 0) {
textareas[textareas.length - 1].focus(); // focus sur le dernier
}
});
// Fonction pour ajouter un nouvel événement à chaque textarea // Fonction pour ajouter un nouvel événement à chaque textarea
function addTextareaEvent(textarea) { function addTextareaEvent(textarea) {
@ -152,7 +167,7 @@
autoResize(t) autoResize(t)
}); });
function addDeleteEvent(button) { function addDeleteBloc(button) {
button.addEventListener('click', function () { button.addEventListener('click', function () {
const blocId = button.dataset.id; const blocId = button.dataset.id;
const blocContainer = button.closest('.bloc-container'); const blocContainer = button.closest('.bloc-container');
@ -184,7 +199,58 @@
} }
document.querySelectorAll('.delete-bloc-btn').forEach(t => { document.querySelectorAll('.delete-bloc-btn').forEach(t => {
addDeleteEvent(t); addDeleteBloc(t);
});
function addDeletePage(button) {
button.addEventListener('click', function () {
const pageId = button.dataset.id;
if (confirm("Voulez-vous vraiment supprimer cette page ?")) {
const params = new URLSearchParams();
params.append("pageId", pageId);
fetch("/Projet/SupprimerPage", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded"
},
body: params
}).then(response => {
if (response.ok) {
location.reload();
} else {
console.error("Erreur lors de la suppression du bloc.");
}
});
}
});
}
document.querySelectorAll('.delete-page-btn').forEach(t => {
addDeletePage(t);
});
// Pour le Tchat
const socket = new WebSocket("ws://" + window.location.host + "/Projet/ws/tchat");
socket.onmessage = function(event) {
const container = document.querySelector(".messages-container");
const p = document.createElement("p");
p.textContent = event.data;
container.appendChild(p);
container.scrollTop = container.scrollHeight;
};
document.querySelector("input[name='contenu']").addEventListener("keydown", function(event) {
const login = document.getElementById("user-login").textContent;
if (event.key === "Enter") {
const message = this.value;
if (message.trim() !== "") {
socket.send(login + " : " + message);
this.value = "";
}
}
}); });
</script> </script>
<jsp:include page="Footer.jsp" /> <jsp:include page="Footer.jsp" />

View File

@ -1,4 +1,4 @@
<footer class="footer">
<a href="https://bulma.io"> <a href="https://bulma.io">
<img <img
src="https://bulma.io/assets/images/made-with-bulma.png" src="https://bulma.io/assets/images/made-with-bulma.png"
@ -6,7 +6,7 @@
width="128" width="128"
height="24"> height="24">
</a> </a>
</footer>
</main> </main>
</body> </body>
</html> </html>

View File

@ -13,6 +13,25 @@
border: none; border: none;
outline: none; outline: none;
} }
.tchat-container {
flex-grow: 1;
max-height: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.messages-container {
flex-grow: 1;
overflow-y: auto;
margin-bottom: 0.5rem;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
background: #f9f9f9;
}
.tchat-container input[type="text"] {
margin-top: auto;
}
</style> </style>
</head> </head>
<body> <body>
@ -26,7 +45,7 @@
<div class="navbar-end"> <div class="navbar-end">
<c:if test="${not empty utilisateur}"> <c:if test="${not empty utilisateur}">
<div class="navbar-item"> <div class="navbar-item">
<p>${utilisateur.login}</p> <p id="user-login">${utilisateur.login}</p>
</div> </div>
</c:if> </c:if>
<c:choose> <c:choose>

View File

@ -7,8 +7,9 @@
<ul class="menu-list" id="menuPages"> <ul class="menu-list" id="menuPages">
<c:forEach var="page" items="${listePages}"> <c:forEach var="page" items="${listePages}">
<li> <li>
<button class="delete delete-page-btn is-pulled-right has-background-danger" data-id="${page.id}"></button>
<a href="AfficherPage?id=${page.id}">${page.titre}</a> <a href="AfficherPage?id=${page.id}">${page.titre}</a>
<button class="delete is-small delete-page-btn is-primary" data-id="${page.id}"></button>
</li> </li>
</c:forEach> </c:forEach>
<li> <li>

View File

@ -1,10 +1,22 @@
<%@ page language="java" contentType="text/html; charset=UTF-8" <%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%> pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<div class="tchat-container is-flex is-flex-direction-column">
<h2 class="block">Tchat</h2> <h2 class="block">Tchat</h2>
<div class="messages-container" id="Messages" style="height: 60vh;">
<c:forEach var="message" items="${listeMessages}">
<p>
${message.login} : ${message.contenu}
</p>
</c:forEach>
</div>
<div class="block"> <div class="block">
<input <input
class="input is-primary is-small" class="input is-primary is-small"
type="text" type="text"
placeholder="Ecrivez ici"> id="tchat"
placeholder="Tchatez ici"
name="contenu">
</div>
</div> </div>