Ajout de la pagination coter client

This commit is contained in:
Bilal Dieumegard 2024-12-04 15:04:39 +01:00
parent 0c92acc24b
commit 818b10a32d
5 changed files with 144 additions and 65 deletions

View file

@ -1,8 +1,10 @@
<script lang="ts"> <script lang="ts">
import { formatDistanceToNow } from '$lib/utils/date';
export let id: string; // ID du chat export let id: string; // ID du chat
export let title: string; // Nom ou titre du chat export let title: string; // Nom ou titre du chat
export let lastMessage: string; // Dernier message affiché export let lastMessage: string; // Dernier message affiché
export let time: string; // Heure du dernier message export let createdAt: string; // Heure du dernier message
</script> </script>
<a href={`/chats/${id}`} class="chat-item p-4 border rounded-md hover:bg-gray-100 cursor-pointer flex justify-between items-center"> <a href={`/chats/${id}`} class="chat-item p-4 border rounded-md hover:bg-gray-100 cursor-pointer flex justify-between items-center">
@ -10,7 +12,7 @@
<p class="font-semibold text-lg">{title}</p> <p class="font-semibold text-lg">{title}</p>
<p class="text-sm text-gray-500 truncate">{lastMessage}</p> <p class="text-sm text-gray-500 truncate">{lastMessage}</p>
</div> </div>
<p class="text-xs text-gray-400">{time}</p> <p class="text-xs text-gray-400">{formatDistanceToNow(createdAt)}</p>
</a> </a>
<style> <style>

View file

@ -6,58 +6,70 @@ import logger from '$lib/logger';
export async function GET({ params, url }) { export async function GET({ params, url }) {
const channelId = params.id; const channelId = params.id;
// @ts-ignore // Gestion des paramètres de pagination
const limit = parseInt(url.searchParams.get('limit'))-1 || 19; // Limit -1 sinon ça retourne un message de trop const limit = parseInt(url.searchParams.get('limit') || '20'); // Par défaut, 20 messages
// @ts-ignore const page = parseInt(url.searchParams.get('page') || '1'); // Par défaut, page 1
const page = parseInt(url.searchParams.get('page')) || 1;
const offset = (page - 1) * limit; const offset = (page - 1) * limit;
const start = offset; // Début de la plage
const stop = offset + limit - 1; // Fin de la plage inclusif
let redisMessages = await redisClient.zRange(`channel:${channelId}:messages`, offset, limit, { REV: true }); try {
if (redisMessages != null) { // Essayer de récupérer les messages du cache Redis
logger.debug(`Cache entry found, fetching messages for channel (${channelId}) from cache`); logger.debug(`Tentative de récupération des messages du cache pour le channel : ${channelId}`);
const messages = redisMessages.length != 0 ? await redisClient.mGet(redisMessages).then( let redisMessages = await redisClient.zRange(`channel:${channelId}:messages`, start, stop, { REV: true });
(messages) => messages.map((m) => JSON.parse(m))
) : [];
return json({limit: limit+1, page, messages}); if (redisMessages && redisMessages.length > 0) {
} logger.debug(`Messages trouvés dans le cache pour le channel : ${channelId}`);
const messages = await redisClient.mGet(redisMessages).then(
(messages) => messages.map((m) => JSON.parse(m))
);
const messages = await prisma.message.findMany({ return json({ limit, page, messages });
where: { } else {
channelId logger.debug(`Aucun message trouvé dans le cache, récupération depuis MongoDB pour le channel : ${channelId}`);
}, }
select: {
id: true, // Si aucun message n'est trouvé dans Redis, charger depuis MongoDB
createdAt: true, const messagesFromDB = await prisma.message.findMany({
text: true, where: { channelId },
user: { select: {
select: { id: true,
id: true, createdAt: true,
username: true, text: true,
surname: true, user: {
name: true, select: {
profilePicture: true id: true,
} username: true,
surname: true,
name: true,
profilePicture: true,
},
},
},
orderBy: [{ createdAt: 'desc' }],
take: limit,
skip: offset,
});
if (messagesFromDB.length > 0) {
// Stocker les messages dans Redis
for (const message of messagesFromDB) {
await redisClient.set(`message:${message.id}`, JSON.stringify(message));
await redisClient.zAdd(`channel:${channelId}:messages`, {
score: new Date(message.createdAt).getTime(),
value: `message:${message.id}`,
});
} }
},
orderBy: [
{ createdAt: 'desc' }
],
});
for (const m of messages) { logger.debug(`Messages ajoutés au cache Redis pour le channel : ${channelId}`);
await redisClient.set(`message:${m.id}`, JSON.stringify(m)); }
// @ts-ignore
await redisClient.zAdd(`channel:${channelId}:messages`, {score: Date.parse(m.createdAt), value: `message:${m.id}`}); return json({ limit, page, messages: messagesFromDB });
} catch (err) {
logger.error(`Erreur lors de la récupération des messages : ${err.message}`);
return json({ error: 'Erreur lors de la récupération des messages' }, { status: 500 });
} }
redisMessages = await redisClient.zRange(`channel:${channelId}:messages`, offset, limit, { REV: true });
const mgetMessages =await redisClient.mGet(redisMessages).then(
(messages) => messages.map((m) => JSON.parse(<string>m))
);
return json({limit: limit+1, page, messages: mgetMessages});
} }
export async function POST({ params, request }) { export async function POST({ params, request }) {
@ -65,7 +77,7 @@ export async function POST({ params, request }) {
const { userId, text } = await request.json(); const { userId, text } = await request.json();
try { try {
// Créer un nouveau message dans la base de données // Créer un nouveau message dans MongoDB
const newMessage = await prisma.message.create({ const newMessage = await prisma.message.create({
data: { data: {
userId, userId,
@ -82,39 +94,43 @@ export async function POST({ params, request }) {
username: true, username: true,
surname: true, surname: true,
name: true, name: true,
profilePicture: true profilePicture: true,
} },
} },
}, },
}); });
// Ajouter le message dans Redis
await redisClient.set(`message:${newMessage.id}`, JSON.stringify(newMessage)); await redisClient.set(`message:${newMessage.id}`, JSON.stringify(newMessage));
await redisClient.zAdd(`channel:${channelId}:messages`, {score: Date.parse(newMessage.createdAt), value: `message:${newMessage.id}`}); await redisClient.zAdd(`channel:${channelId}:messages`, {
score: new Date(newMessage.createdAt).getTime(),
value: `message:${newMessage.id}`,
});
logger.debug(`Nouveau message ajouté pour le channel : ${channelId}`);
return json(newMessage, { status: 201 }); return json(newMessage, { status: 201 });
} catch (err) { } catch (err) {
console.error(err); logger.error(`Erreur lors de la création du message : ${err.message}`);
return json({ error: 'Erreur lors de la création du message' }, { status: 500 }); return json({ error: 'Erreur lors de la création du message' }, { status: 500 });
} }
} }
export async function DELETE({ params, request }) { export async function DELETE({ params, request }) {
const channelId = params.id; const channelId = params.id;
const { messageId } = await request.json(); const { messageId } = await request.json();
try { try {
// Supprimer le message de la base de données // Supprimer le message dans MongoDB
await prisma.message.delete({ await prisma.message.delete({ where: { id: messageId } });
where: { id: messageId },
});
// Supprimer le message dans Redis
await redisClient.del(`message:${messageId}`); await redisClient.del(`message:${messageId}`);
await redisClient.zRem(`channel:${channelId}:messages`, `message:${messageId}`); await redisClient.zRem(`channel:${channelId}:messages`, `message:${messageId}`);
logger.debug(`Message supprimé pour le channel : ${channelId}`);
return json({ message: 'Message supprimé avec succès' }); return json({ message: 'Message supprimé avec succès' });
} catch (err) { } catch (err) {
console.error(err); logger.error(`Erreur lors de la suppression du message : ${err.message}`);
return json({ error: 'Erreur lors de la suppression du message' }, { status: 500 }); return json({ error: 'Erreur lors de la suppression du message' }, { status: 500 });
} }
} }

View file

@ -95,7 +95,7 @@
id={channel.id} id={channel.id}
title={channel.name} title={channel.name}
lastMessage={channel.lastMessage ? channel.lastMessage.text : "Ecrire le premier message"} lastMessage={channel.lastMessage ? channel.lastMessage.text : "Ecrire le premier message"}
time={formatDistanceToNow(channel.lastUpdate)} createdAt={channel.lastUpdate}
/> />
{/each} {/each}
</div> </div>

View file

@ -1,6 +1,6 @@
export async function load({ fetch, params, locals }) { export async function load({ fetch, params, locals }) {
try { try {
const res = await fetch(`/api/channels/${params.id}/messages?page=1`, { const res = await fetch(`/api/channels/${params.id}/messages?page=1&limit=10`, {
method: 'GET', method: 'GET',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'

View file

@ -4,6 +4,9 @@
import PaperPlane from "svelte-radix/PaperPlane.svelte"; import PaperPlane from "svelte-radix/PaperPlane.svelte";
import Message from "$lib/components/Message.svelte"; import Message from "$lib/components/Message.svelte";
import UserChat from '$lib/components/ui/UserChat.svelte'; import UserChat from '$lib/components/ui/UserChat.svelte';
import { tick } from 'svelte';
import { formatDistanceToNow } from '$lib/utils/date.ts';
export let data; export let data;
export let messages = data.messages.messages; export let messages = data.messages.messages;
@ -30,6 +33,55 @@
} }
} }
let currentPage = 1;
let isLoading = false;
let scrollContainer: HTMLElement;
async function loadMoreMessages() {
if (isLoading) return;
isLoading = true;
// Sauvegarder la hauteur actuelle
const previousHeight = scrollContainer.scrollHeight;
try {
const response = await fetch(`/api/channels/${data.channelId}/messages?page=${currentPage + 1}&limit=10`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (response.ok) {
const newMessages = await response.json();
if(newMessages.messages.length <= 0){
console.log('Pas d\'autres anciens messages');
return;
}
messages = [...newMessages.messages, ...messages]; // Ajouter les nouveaux messages en haut
currentPage++;
} else {
console.error('Erreur lors du chargement des anciens messages');
}
} catch (error) {
console.error('Erreur réseau lors du chargement des messages:', error);
} finally {
isLoading = false;
// Réajuster la position de défilement
await tick(); // Attendre la mise à jour du DOM
const newHeight = scrollContainer.scrollHeight;
scrollContainer.scrollTop = newHeight - previousHeight;
}
}
function handleScroll(event: Event) {
const container = event.target as HTMLElement;
if (container.scrollTop === 0 && !isLoading) {
loadMoreMessages();
}
}
</script> </script>
<div class="h-full flex"> <div class="h-full flex">
@ -50,11 +102,18 @@
<!-- Chat principal (colonne droite) --> <!-- Chat principal (colonne droite) -->
<div class="flex-1 flex flex-col h-full"> <div class="flex-1 flex flex-col h-full">
<!-- Messages --> <!-- Messages -->
<div class="m-10 flex flex-col gap-5 overflow-y-auto flex-grow "> <div
class="m-10 flex flex-col gap-5 overflow-y-auto flex-grow "
bind:this={scrollContainer}
on:scroll={handleScroll}
>
{#if isLoading}
<div class="loading-indicator">Chargement...</div>
{/if}
<!-- Afficher les messages (mock d'un utilisateur sélectionné ou aucun message par défaut) --> <!-- Afficher les messages (mock d'un utilisateur sélectionné ou aucun message par défaut) -->
{#if messages.length > 0} {#if messages.length > 0}
{#each messages as message} {#each messages as message}
<Message username={message.user.username} messageContent={message.text} /> <Message username={message.user.username} messageContent={message.text} createdAt={message.createdAt} />
{/each} {/each}
{:else} {:else}
<div class="text-center text-gray-500 mt-10">Sélectionnez un message le chat est vide.</div> <div class="text-center text-gray-500 mt-10">Sélectionnez un message le chat est vide.</div>
@ -75,7 +134,9 @@
.h-full { .h-full {
height: 100%; height: 100%;
} }
.selected { .loading-indicator {
background-color: #e2e8f0; text-align: center;
padding: 10px;
color: gray;
} }
</style> </style>