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:
commit
0e806a7428
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