Merge pull request #9 from NabilOuldHamou/features/paginationMessagesClient
Ajout de la pagination coter client
This commit is contained in:
commit
7a0f9d1c90
5 changed files with 144 additions and 65 deletions
|
@ -1,8 +1,10 @@
|
|||
<script lang="ts">
|
||||
import { formatDistanceToNow } from '$lib/utils/date';
|
||||
|
||||
export let id: string; // ID du chat
|
||||
export let title: string; // Nom ou titre du chat
|
||||
export let lastMessage: string; // Dernier message affiché
|
||||
export let time: string; // Heure du dernier message
|
||||
export let createdAt: string; // Heure du dernier message
|
||||
</script>
|
||||
|
||||
<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="text-sm text-gray-500 truncate">{lastMessage}</p>
|
||||
</div>
|
||||
<p class="text-xs text-gray-400">{time}</p>
|
||||
<p class="text-xs text-gray-400">{formatDistanceToNow(createdAt)}</p>
|
||||
</a>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -6,58 +6,70 @@ import logger from '$lib/logger';
|
|||
export async function GET({ params, url }) {
|
||||
const channelId = params.id;
|
||||
|
||||
// @ts-ignore
|
||||
const limit = parseInt(url.searchParams.get('limit'))-1 || 19; // Limit -1 sinon ça retourne un message de trop
|
||||
// @ts-ignore
|
||||
const page = parseInt(url.searchParams.get('page')) || 1;
|
||||
// Gestion des paramètres de pagination
|
||||
const limit = parseInt(url.searchParams.get('limit') || '20'); // Par défaut, 20 messages
|
||||
const page = parseInt(url.searchParams.get('page') || '1'); // Par défaut, page 1
|
||||
|
||||
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 });
|
||||
if (redisMessages != null) {
|
||||
logger.debug(`Cache entry found, fetching messages for channel (${channelId}) from cache`);
|
||||
const messages = redisMessages.length != 0 ? await redisClient.mGet(redisMessages).then(
|
||||
(messages) => messages.map((m) => JSON.parse(m))
|
||||
) : [];
|
||||
try {
|
||||
// Essayer de récupérer les messages du cache Redis
|
||||
logger.debug(`Tentative de récupération des messages du cache pour le channel : ${channelId}`);
|
||||
let redisMessages = await redisClient.zRange(`channel:${channelId}:messages`, start, stop, { REV: true });
|
||||
|
||||
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({
|
||||
where: {
|
||||
channelId
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
text: true,
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
surname: true,
|
||||
name: true,
|
||||
profilePicture: true
|
||||
}
|
||||
return json({ limit, page, messages });
|
||||
} else {
|
||||
logger.debug(`Aucun message trouvé dans le cache, récupération depuis MongoDB pour le channel : ${channelId}`);
|
||||
}
|
||||
|
||||
// Si aucun message n'est trouvé dans Redis, charger depuis MongoDB
|
||||
const messagesFromDB = await prisma.message.findMany({
|
||||
where: { channelId },
|
||||
select: {
|
||||
id: true,
|
||||
createdAt: true,
|
||||
text: true,
|
||||
user: {
|
||||
select: {
|
||||
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) {
|
||||
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}`});
|
||||
logger.debug(`Messages ajoutés au cache Redis pour le channel : ${channelId}`);
|
||||
}
|
||||
|
||||
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 }) {
|
||||
|
@ -65,7 +77,7 @@ export async function POST({ params, request }) {
|
|||
const { userId, text } = await request.json();
|
||||
|
||||
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({
|
||||
data: {
|
||||
userId,
|
||||
|
@ -82,39 +94,43 @@ export async function POST({ params, request }) {
|
|||
username: true,
|
||||
surname: true,
|
||||
name: true,
|
||||
profilePicture: true
|
||||
}
|
||||
}
|
||||
profilePicture: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Ajouter le message dans Redis
|
||||
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 });
|
||||
} 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 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE({ params, request }) {
|
||||
const channelId = params.id;
|
||||
|
||||
const { messageId } = await request.json();
|
||||
|
||||
try {
|
||||
// Supprimer le message de la base de données
|
||||
await prisma.message.delete({
|
||||
where: { id: messageId },
|
||||
});
|
||||
// Supprimer le message dans MongoDB
|
||||
await prisma.message.delete({ where: { id: messageId } });
|
||||
|
||||
// Supprimer le message dans Redis
|
||||
await redisClient.del(`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' });
|
||||
} 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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@
|
|||
id={channel.id}
|
||||
title={channel.name}
|
||||
lastMessage={channel.lastMessage ? channel.lastMessage.text : "Ecrire le premier message"}
|
||||
time={formatDistanceToNow(channel.lastUpdate)}
|
||||
createdAt={channel.lastUpdate}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export async function load({ fetch, params, locals }) {
|
||||
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',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
import PaperPlane from "svelte-radix/PaperPlane.svelte";
|
||||
import Message from "$lib/components/Message.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 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>
|
||||
|
||||
<div class="h-full flex">
|
||||
|
@ -50,11 +102,18 @@
|
|||
<!-- Chat principal (colonne droite) -->
|
||||
<div class="flex-1 flex flex-col h-full">
|
||||
<!-- 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) -->
|
||||
{#if messages.length > 0}
|
||||
{#each messages as message}
|
||||
<Message username={message.user.username} messageContent={message.text} />
|
||||
<Message username={message.user.username} messageContent={message.text} createdAt={message.createdAt} />
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="text-center text-gray-500 mt-10">Sélectionnez un message le chat est vide.</div>
|
||||
|
@ -75,7 +134,9 @@
|
|||
.h-full {
|
||||
height: 100%;
|
||||
}
|
||||
.selected {
|
||||
background-color: #e2e8f0;
|
||||
.loading-indicator {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
color: gray;
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Add table
Reference in a new issue