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:
parent
98c70f062a
commit
5f0be72d71
14 changed files with 151 additions and 40 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
17
src/lib/stores/socket.ts
Normal 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;
|
||||
}
|
|
@ -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 !== "/") {
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
||||
|
@ -69,4 +86,6 @@
|
|||
</Tabs.Content>
|
||||
|
||||
</Tabs.Root>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Alert message={$alertMessage} show={$showAlert} onClose={() => ($showAlert = false)} />
|
|
@ -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});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -18,12 +18,24 @@ export async function GET({ params, url }) {
|
|||
include: {
|
||||
messages: {
|
||||
take: 1, // Récupère le dernier message
|
||||
orderBy: { createdAt: 'desc' }, // Trie par date décroissante
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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 d’un nouveau message
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
10
src/routes/disconnect/+server.ts
Normal file
10
src/routes/disconnect/+server.ts
Normal 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, '/');
|
||||
}
|
|
@ -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)
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue