Merge pull request #7 from NabilOuldHamou/features/createMessage

Ajout des sockets et du chargement des channels avec les sockets, de la deconnexion et resolution bug de gestion du sort des date des channels
This commit is contained in:
Bilal Dieumegard 2024-12-04 00:02:12 +01:00 committed by GitHub
commit 0e806a7428
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 151 additions and 40 deletions

View file

@ -1,5 +1,4 @@
<script lang="ts">
import { onMount } from "svelte";
import Alert from "$lib/components/ui/Alert.svelte"; // Importer le composant Alert
export let show = false;
@ -9,6 +8,8 @@
let showAlert = false;
let alertMessage = "";
export let socket;
let chatName = "";
const createChat = async () => {
@ -27,12 +28,15 @@
const data = await response.json();
alertMessage = `Le chat "${data.name}" a été créé avec succès.`;
chatName = ""; // Réinitialiser
socket.emit("new-channel", data);
onClose?.(); // Fermer le composant après création
} else {
alertMessage = "Une erreur est survenue lors de la création du chat.";
response.json().then(error => {
alertMessage = error.error;
});
}
} catch (err) {
alertMessage = "Erreur réseau ou serveur.";
alertMessage = err;
}
showAlert = true;
@ -42,10 +46,6 @@
}
};
const closeAlert = () => {
showAlert = false;
};
// Fonction pour détecter le clic en dehors
let createChatRef: HTMLElement | null = null;
@ -75,7 +75,7 @@
</div>
{/if}
<Alert show={showAlert} message={alertMessage} onClose={closeAlert} />
<Alert show={showAlert} message={alertMessage} onClose={() => (showAlert = false)} />
<style>
.fixed {

View file

@ -1,4 +1,6 @@
<script>
import Button from '$lib/components/ui/button/button.svelte';
export let user = {
pseudo: '',
prenom: '',
@ -7,8 +9,24 @@
profilePictureUrl: '', // Ajouter l'URL de l'image de profil
}; // Infos utilisateur
export let show = false; // Contrôle si la carte est visible
export let onClose = () => {
}; // Fonction pour fermer la carte
export let onClose = () => {}; // Fonction pour fermer la carte
const disconnect = async () => {
try {
// Envoyer une requête POST à l'endpoint /disconnect
const response = await fetch('/disconnect', {
method: 'POST',
});
// Vérifier si la déconnexion a réussi (ici, on se base sur le code de redirection)
if (response.redirected) {
// Si la redirection est effectuée, vous pouvez rediriger manuellement côté client
window.location.href = response.url;
}
} catch (error) {
console.error('Erreur lors de la déconnexion:', error);
}
};
</script>
{#if show}
@ -21,6 +39,7 @@
</div>
<p>{user.prenom} {user.nom}</p>
<p>{user.description}</p>
<Button on:click={disconnect}>Deconnecter</Button>
</div>
</div>
{/if}

17
src/lib/stores/socket.ts Normal file
View file

@ -0,0 +1,17 @@
import { io } from "socket.io-client";
// Initialisation de la socket
export const initSocket = () => {
const socketInstance = io("http://localhost:5173");
// Événements globaux de connexion
socketInstance.on("connect", () => {
console.log("Connected to Socket.IO server:", socketInstance.id);
});
socketInstance.on("disconnect", () => {
console.log("Disconnected from Socket.IO server");
});
return socketInstance;
}

View file

@ -1,6 +1,9 @@
import { redirect } from '@sveltejs/kit';
import { initSocket } from "$lib/stores/socket";
export async function load({ locals, url }) {
const token = locals.token;
if (token == undefined && url.pathname !== "/") {

View file

@ -1,5 +1,5 @@
import { type Actions } from '@sveltejs/kit';
import { redirect, error } from '@sveltejs/kit';
import { redirect, error, fail } from '@sveltejs/kit';
import logger from '$lib/logger';
export async function load({locals}) {
@ -9,7 +9,7 @@ export async function load({locals}) {
}
export const actions: Actions = {
login: async ({request, fetch, cookies, locals}) => {
login: async ({request, fetch, cookies}) => {
const formData = await request.formData();
const response = await fetch('/api/auth/login', {
@ -38,8 +38,7 @@ export const actions: Actions = {
return redirect(302, "/chats");
} else {
return error(400, data.message);
return fail(400, { error: data.message });
}
},
@ -65,8 +64,7 @@ export const actions: Actions = {
return redirect(302, "/chats");
} else {
return error(400, data.message);
return fail(400, { error: data.message });
}
}
}

View file

@ -1,10 +1,27 @@
<script lang="ts">
import Alert from "$lib/components/ui/Alert.svelte";
import { Label } from "$lib/components/ui/label";
import { Button } from "$lib/components/ui/button";
import { Input } from "$lib/components/ui/input";
import * as Card from "$lib/components/ui/card";
import { enhance } from '$app/forms';
import * as Tabs from "$lib/components/ui/tabs";
let { data, form } = $props();
import { writable } from 'svelte/store';
const showAlert = writable(false);
const alertMessage = writable("");
$effect(() => {
// Manipuler l'état via les stores, ce qui est plus réactif
if (form?.error) {
alertMessage.set(form.error);
showAlert.set(true);
} else {
showAlert.set(false);
}
});
</script>
@ -70,3 +87,5 @@
</Tabs.Root>
</div>
<Alert message={$alertMessage} show={$showAlert} onClose={() => ($showAlert = false)} />

View file

@ -26,7 +26,7 @@ export async function POST({request}) {
logger.debug(`Found user with email (${email}) in database`);
try {
if (await argon2.verify(user.password, password)) {
logger.debug(`Password for user ${user.email} is correct.`);
// @ts-ignore
const token = jwt.sign(user, process.env.JWT_SECRET, { expiresIn: "1h" });
logger.debug(`Generated a JWT token for user ${user.email}.`)
@ -39,7 +39,7 @@ export async function POST({request}) {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (e) {
logger.error(e);
return error(500, {message: "Erreur interne."});
return error(500, {message: e.body.message});
}

View file

@ -19,11 +19,23 @@ export async function GET({ params, url }) {
messages: {
take: 1, // Récupère le dernier message
orderBy: { createdAt: 'desc' },// Trie par date décroissante
// as lastMessage not list last message
},
},
});
console.log(canaux);
canaux = canaux.map((canaux) => {
return {
...canaux,
lastMessage: canaux.messages.length > 0 ? canaux.messages[0] : null,
messages: undefined
};
});
canaux = sortChannels(canaux);
console.log(canaux);
return json(canaux);
@ -50,7 +62,16 @@ export async function GET({ params, url }) {
},
});
canaux = canaux.map((canaux) => {
return {
...canaux,
lastMessage: canaux.messages.length > 0 ? canaux.messages[0] : null,
messages: undefined
};
});
canaux = sortChannels(canaux);
console.log(canaux);
logger.debug('Caching channels with EX of 3600 secs');
await redisClient.set('channels', JSON.stringify(canaux), { EX: 3600 });
@ -69,7 +90,7 @@ export async function POST({ request }) {
const { name } = await request.json();
try {
const canal = await prisma.channel.create({
let canal = await prisma.channel.create({
data: {
name
},
@ -79,13 +100,16 @@ export async function POST({ request }) {
const cachedChanels = await redisClient.get('channels');
let channels = cachedChanels != null ? JSON.parse(cachedChanels) : [];
console.log(channels);
console.log(canal);
canal = {
...canal,
lastMessage: null,
messages: undefined
}
channels.push(canal);
channels = sortChannels(channels);
console.log(channels);
logger.debug(`Added channel (${canal.id}) to channels cache.`);
await redisClient.set('channels', JSON.stringify(channels), { EX: 600 });
@ -100,11 +124,16 @@ export async function POST({ request }) {
}
function sortChannels(channels) {
return channels.sort((a, b) => {
// Vérifie si 'a.messages' existe et est un tableau, sinon utilise la date de création du canal
const lastMessageA = Array.isArray(a.messages) && a.messages.length > 0 ? a.messages[0]?.createdAt : a.createdAt;
const lastMessageB = Array.isArray(b.messages) && b.messages.length > 0 ? b.messages[0]?.createdAt : b.createdAt;
channels = channels.map((channel) => {
return {
...channel,
lastUpdate : channel.lastMessage != null ? channel.lastMessage.createdAt : channel.createdAt
};
});
return new Date(lastMessageB).getTime() - new Date(lastMessageA).getTime();
return channels.sort((a, b) => {
return new Date(b.lastUpdate) - new Date(a.lastUpdate);
});
}

View file

@ -73,7 +73,6 @@ export async function POST({ params, request }) {
channelId,
text,
},
include: { user: { select: { id: true, username: true } } },
});
updateCaches(); // Mettre à jour les caches après la création dun nouveau message

View file

@ -6,7 +6,7 @@
import ProfileCard from "$lib/components/ui/ProfileCard.svelte"; // Importer le composant ProfileCard
import CreateChat from "$lib/components/ui/CreateChat.svelte"; // Importer le composant CreateChat
import { formatDistanceToNow } from "$lib/utils/date.js";
import { io } from 'socket.io-client';
import { initSocket } from "$lib/stores/socket";
let showProfileCard = false; // État pour afficher ou masquer le ProfileCard
let showCreateChat = false; // État pour afficher ou masquer CreateChat
@ -18,6 +18,12 @@
profilePictureUrl: 'path/to/profile-picture.jpg', // URL de l'image de profil
};
let socket = initSocket(); // Initialiser le socket
socket.on("new-channel", (channel) => {
channels = [channel, ...channels];
});
function openProfileCard() {
console.log('openProfileCard');
showProfileCard = true; // Inverser l'état pour afficher/masquer le ProfilCard
@ -85,12 +91,17 @@
<div class="flex flex-col gap-4 overflow-y-auto">
{#each channels as channel}
<ChatItem id={channel.id} title={channel.name} lastMessage={channel.lastMessage} time={formatDistanceToNow(channel.createdAt)} />
<ChatItem
id={channel.id}
title={channel.name}
lastMessage={channel.lastMessage ? channel.lastMessage.text : "Ecrire le premier message"}
time={formatDistanceToNow(channel.lastUpdate)}
/>
{/each}
</div>
</div>
<CreateChat show={showCreateChat} onClose={closeCreateChat} />
<CreateChat show={showCreateChat} socket={socket} onClose={closeCreateChat} />
<ProfileCard {user} show={showProfileCard} onClose={closeProfileCard} />
<style>

View file

@ -1,4 +1,4 @@
export async function load({ fetch, params }) {
export async function load({ fetch, params, locals }) {
try {
const res = await fetch(`/api/channels/${params.id}/messages?page=1`, {
method: 'GET',
@ -7,15 +7,18 @@ export async function load({ fetch, params }) {
}
});
const messages = await res.json();
console.log(messages);
return {
messages,
channelId: params.id,
userId: locals.userId
}
}catch (error) {
console.error('Erreur lors du chargement des messages:', error);
return {
messages: [],
channelId: params.id,
userId: locals.userId
};
}
}

View file

@ -6,7 +6,7 @@
import UserChat from '$lib/components/ui/UserChat.svelte';
export let data;
export let messages = data.messages;
export let messages = data.messages.messages;
export let users = data.users;
let messageText = '';
@ -18,11 +18,12 @@
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ user: data.userId, text: messageText }),
body: JSON.stringify({ userId: data.userId, text: messageText }),
});
if (response.ok) {
messageText = '';
// Envoyer le message avec les sockets (à implémenter)
console.log('Message envoyé avec succès');
}else{
console.log('Erreur lors de l\'envoi du message');
@ -53,7 +54,7 @@
<!-- Afficher les messages (mock d'un utilisateur sélectionné ou aucun message par défaut) -->
{#if messages.length > 0}
{#each messages as message}
<Message username={message.username} messageContent={message.messageContent} />
<Message username={message.user.username} messageContent={message.text} />
{/each}
{:else}
<div class="text-center text-gray-500 mt-10">Sélectionnez un message le chat est vide.</div>
@ -62,7 +63,7 @@
<!-- Input pour envoyer un message -->
<div class="px-10 py-5 w-full flex gap-2 border-t">
<Textarea class="h-16 resize-none flex-grow" placeholder="Écrivez un message..." value={messageText}/>
<Textarea class="h-16 resize-none flex-grow" placeholder="Écrivez un message..." bind:value={messageText}/>
<Button size="icon" class="h-16 w-16 bg-blue-500 hover:bg-blue-600 h-full" on:click={sendMessage}>
<PaperPlane class="h-6 w-6" />
</Button>

View file

@ -0,0 +1,10 @@
import { redirect } from '@sveltejs/kit';
export async function POST({ cookies }) {
// Supprimer les cookies "token" et "UID"
cookies.delete("token", { path: '/' });
cookies.delete("UID", { path: '/' });
// Rediriger vers la page d'accueil ou la page de connexion après déconnexion
throw redirect(303, '/');
}

View file

@ -11,8 +11,10 @@ const webSocketServer = {
const io = new Server(server.httpServer)
io.on('connection', (socket) => {
socket.emit('eventFromServer', 'Hello, World 👋')
})
socket.on('new-channel', (channel) => {
io.emit('new-channel', channel)
});
});
}
}