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">
|
<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>
|
||||||
|
|
|
@ -6,27 +6,33 @@ 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 });
|
||||||
|
|
||||||
|
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))
|
(messages) => messages.map((m) => JSON.parse(m))
|
||||||
) : [];
|
);
|
||||||
|
|
||||||
return json({limit: limit+1, page, messages});
|
return json({ limit, page, messages });
|
||||||
|
} else {
|
||||||
|
logger.debug(`Aucun message trouvé dans le cache, récupération depuis MongoDB pour le channel : ${channelId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const messages = await prisma.message.findMany({
|
// Si aucun message n'est trouvé dans Redis, charger depuis MongoDB
|
||||||
where: {
|
const messagesFromDB = await prisma.message.findMany({
|
||||||
channelId
|
where: { channelId },
|
||||||
},
|
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
createdAt: true,
|
createdAt: true,
|
||||||
|
@ -37,27 +43,33 @@ export async function GET({ params, url }) {
|
||||||
username: true,
|
username: true,
|
||||||
surname: true,
|
surname: true,
|
||||||
name: true,
|
name: true,
|
||||||
profilePicture: true
|
profilePicture: true,
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
orderBy: [
|
},
|
||||||
{ createdAt: 'desc' }
|
},
|
||||||
],
|
orderBy: [{ createdAt: 'desc' }],
|
||||||
|
take: limit,
|
||||||
|
skip: offset,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const m of messages) {
|
if (messagesFromDB.length > 0) {
|
||||||
await redisClient.set(`message:${m.id}`, JSON.stringify(m));
|
// Stocker les messages dans Redis
|
||||||
// @ts-ignore
|
for (const message of messagesFromDB) {
|
||||||
await redisClient.zAdd(`channel:${channelId}:messages`, {score: Date.parse(m.createdAt), value: `message:${m.id}`});
|
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}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
redisMessages = await redisClient.zRange(`channel:${channelId}:messages`, offset, limit, { REV: true });
|
logger.debug(`Messages ajoutés au cache Redis pour le channel : ${channelId}`);
|
||||||
const mgetMessages =await redisClient.mGet(redisMessages).then(
|
}
|
||||||
(messages) => messages.map((m) => JSON.parse(<string>m))
|
|
||||||
);
|
|
||||||
|
|
||||||
return json({limit: limit+1, page, messages: mgetMessages});
|
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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 });
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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>
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Reference in a new issue