feat: cache removing channels and messages that haven't been used in long

This commit is contained in:
Nabil Ould Hamou 2024-12-11 16:53:42 +01:00
parent 45fb025065
commit 33e4bc87d9
6 changed files with 93 additions and 59 deletions

View file

@ -8,8 +8,6 @@
export let message = null; // Contenu du message export let message = null; // Contenu du message
let defaultProfilePicture = "/images/default-profile.png";
export let setActiveProfile; export let setActiveProfile;
export let activeProfileId = null; export let activeProfileId = null;
@ -21,11 +19,28 @@
timeElapsed = formatDistanceToNow(message.createdAt); timeElapsed = formatDistanceToNow(message.createdAt);
}; };
let user = null;
async function fetchUser() {
const res = await fetch(`/api/users/${message.user.id}`, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
});
const data = await res.json();
console.log(data)
user = data;
}
// Initialisation de l'intervalle // Initialisation de l'intervalle
onMount(() => { onMount(async () => {
updateElapsed(); // Calcul initial updateElapsed(); // Calcul initial
const interval = setInterval(updateElapsed, 1000); // Mise à jour toutes les secondes const interval = setInterval(updateElapsed, 1000); // Mise à jour toutes les secondes
await fetchUser();
return () => { return () => {
clearInterval(interval); // Nettoyage lors du démontage clearInterval(interval); // Nettoyage lors du démontage
}; };
@ -41,9 +56,13 @@
} }
} }
</script> </script>
<Card.Root class="relative"> {#if user !== null}
<Card.Root class="relative">
<Card.Header <Card.Header
class="flex items-center justify-between {myMessage ? 'flex-row' : 'flex-row-reverse'}" class="flex items-center justify-between {myMessage ? 'flex-row' : 'flex-row-reverse'}"
> >
@ -60,20 +79,20 @@
> >
<!-- Image de profil --> <!-- Image de profil -->
<img <img
src={message.user.profilePicture ? `http://localhost:5173/${message.user.profilePicture}` : defaultProfilePicture} src={`http://localhost:5173/${user.profilePicture}`}
alt="Profile Picture" alt="Profile Picture"
class="h-10 w-10 rounded-full border border-gray-300 cursor-pointer" class="h-10 w-10 rounded-full border border-gray-300 cursor-pointer"
/> />
<!-- Infos du profil (affichées au survol) --> <!-- Infos du profil (affichées au survol) -->
<ProfileInfo user={message.user} show={activeProfileId === message.id} position={myMessage} /> <ProfileInfo user={user} show={activeProfileId === message.id} position={myMessage} />
</div> </div>
<div class="flex flex-col text-right {myMessage ? 'text-right' : 'text-left'}"> <div class="flex flex-col text-right {myMessage ? 'text-right' : 'text-left'}">
<Card.Title <Card.Title
class="text-gray-800 text-sm sm:text-base md:text-lg truncate {myMessage ? 'font-bold' : ''}" class="text-gray-800 text-sm sm:text-base md:text-lg truncate {myMessage ? 'font-bold' : ''}"
> >
{myMessage ? "(Moi)" : ""} {message.user.username} {myMessage ? "(Moi)" : ""} {user.username}
</Card.Title> </Card.Title>
</div> </div>
</div> </div>
@ -83,8 +102,10 @@
<Card.Content class="text-sm sm:text-base md:text-lg text-gray-700"> <Card.Content class="text-sm sm:text-base md:text-lg text-gray-700">
<p>{message.text}</p> <p>{message.text}</p>
</Card.Content> </Card.Content>
</Card.Root> </Card.Root>
{/if}
<style> <style>
img { img {
object-fit: cover; /* Assure un bon rendu des images */ object-fit: cover; /* Assure un bon rendu des images */

View file

@ -5,7 +5,7 @@ import logger from '$lib/logger';
import { sortChannels } from '$lib/utils/sort.ts'; import { sortChannels } from '$lib/utils/sort.ts';
// GET: Liste tous les canaux avec leur premier message // GET: Liste tous les canaux avec leur premier message
export async function GET({ params, url }) { export async function GET({ url }) {
if(url.searchParams.get("name") != null && url.searchParams.get("name") != ""){ if(url.searchParams.get("name") != null && url.searchParams.get("name") != ""){
const name = url.searchParams.get("name"); const name = url.searchParams.get("name");
try { try {

View file

@ -14,7 +14,7 @@ export async function GET({ params, url }) {
try { try {
logger.debug(`Tentative de récupération des messages du cache pour le channel : ${channelId}`); logger.debug(`Tentative de récupération des messages du cache pour le channel : ${channelId}`);
const redisMessageKeys = await redisClient.zRange( const redisMessageKeys = await redisClient.zRangeWithScores(
`channel:${channelId}:messages`, `channel:${channelId}:messages`,
offset, offset,
offset + limit - 1, offset + limit - 1,
@ -24,10 +24,23 @@ export async function GET({ params, url }) {
if (redisMessageKeys.length > 0) { if (redisMessageKeys.length > 0) {
const messages = await Promise.all( const messages = await Promise.all(
redisMessageKeys.map(async (key) => { redisMessageKeys.map(async (key) => {
const message = await redisClient.get(key); const message = await redisClient.get(key.value);
return JSON.parse(message); return JSON.parse(message);
}) })
); );
const redisPipeline = redisClient.multi();
for (const key of redisMessageKeys) {
const message = await redisClient.get(key.value);
const msg = JSON.parse(message)
redisPipeline.set(key.value, JSON.stringify(msg), {EX: 1800});
redisPipeline.zAdd(`channel:${channelId}:messages`, {
score: key.score,
value: key.value,
});
}
await redisPipeline.exec();
return json({ limit, page, messages: messages.reverse() }); return json({ limit, page, messages: messages.reverse() });
} }
@ -41,10 +54,6 @@ export async function GET({ params, url }) {
user: { user: {
select: { select: {
id: true, id: true,
username: true,
surname: true,
name: true,
profilePicture: true,
}, },
}, },
}, },
@ -57,7 +66,7 @@ export async function GET({ params, url }) {
const redisPipeline = redisClient.multi(); const redisPipeline = redisClient.multi();
for (const message of messagesFromDB) { for (const message of messagesFromDB) {
const messageKey = `message:${message.id}`; const messageKey = `message:${message.id}`;
redisPipeline.set(messageKey, JSON.stringify(message)); redisPipeline.set(messageKey, JSON.stringify(message), {EX: 1800});
redisPipeline.zAdd(`channel:${channelId}:messages`, { redisPipeline.zAdd(`channel:${channelId}:messages`, {
score: new Date(message.createdAt).getTime(), score: new Date(message.createdAt).getTime(),
value: messageKey, value: messageKey,
@ -93,10 +102,6 @@ export async function POST({ params, request }) {
user: { user: {
select: { select: {
id: true, id: true,
username: true,
surname: true,
name: true,
profilePicture: true,
}, },
}, },
channel: { channel: {

View file

@ -7,6 +7,7 @@ export async function load({ fetch, params, locals }) {
} }
}); });
const messages = await res.json(); const messages = await res.json();
return { return {
messages, messages,
channelId: params.id, channelId: params.id,

View file

@ -6,6 +6,7 @@
import UserChat from '$lib/components/ui/UserChat.svelte'; import UserChat from '$lib/components/ui/UserChat.svelte';
import { onMount, tick } from 'svelte'; import { onMount, tick } from 'svelte';
import { initSocket } from '$lib/stores/socket'; import { initSocket } from '$lib/stores/socket';
import { ArrowLeft } from 'lucide-svelte';
export let data; export let data;
export let messages = data.messages.messages; export let messages = data.messages.messages;
@ -95,11 +96,10 @@
} }
onMount(() => { onMount(() => {
scrollToBottom(scrollContainer);
socket.on("new-message", (message) => { socket.on("new-message", (message) => {
messages = [...messages , message ]; messages = [...messages , message ];
}); });
scrollToBottom(scrollContainer);
}); });
async function handleEnter(event: KeyboardEvent) { async function handleEnter(event: KeyboardEvent) {
@ -123,7 +123,10 @@
<div class="h-full flex"> <div class="h-full flex">
<!-- Liste des utilisateurs (colonne gauche) --> <!-- Liste des utilisateurs (colonne gauche) -->
<div class="w-1/4 bg-gray-100 border-r overflow-y-auto"> <div class="w-1/4 bg-gray-100 border-r overflow-y-auto">
<h2 class="text-3xl font-bold px-4 mt-5">Utilisateurs</h2> <div class="flex gap-4 px-4 mt-5">
<Button href="/chats" variant="outline" size="icon" ><ArrowLeft /></Button>
<h2 class="text-3xl font-bold">Utilisateurs</h2>
</div>
<div class="flex flex-col m-5 gap-2"> <div class="flex flex-col m-5 gap-2">
{#each users as user} {#each users as user}
<UserChat <UserChat
@ -151,7 +154,7 @@
{#if messages !== undefined && messages.length > 0} {#if messages !== undefined && messages.length > 0}
{#each messages as message} {#each messages as message}
<Message <Message
myMessage={data.userId == message.user.id} myMessage={data.userId === message.user.id}
message={message} message={message}
activeProfileId={activeProfileId} activeProfileId={activeProfileId}
setActiveProfile={setActiveProfile} setActiveProfile={setActiveProfile}

View file

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import ChoosePicture from "$lib/components/ui/ChoosePicture.svelte";
import ChoosePicture from "$lib/components/ui/ChoosePicture.svelte"; // Import du composant import { Button } from '$lib/components/ui/button';
export let data; export let data;
const user = data.user; const user = data.user;
@ -50,7 +50,7 @@
const res = await fetch(`/api/users/${user.id}`, { const res = await fetch(`/api/users/${user.id}`, {
method: 'PUT', method: 'PUT',
body: formData, // Transmet les données comme multipart/form-data body: formData,
}); });
const result = await res.json(); const result = await res.json();
console.log(result); console.log(result);
@ -120,15 +120,19 @@
<ChoosePicture bind:profilePicture={profilePicture} /> <ChoosePicture bind:profilePicture={profilePicture} />
</div> </div>
<div class="mt-6 flex justify-center"> <div class="mt-6 flex flex-col gap-6 items-center justify-center">
<button <button
type="submit" type="submit"
class="bg-blue-500 text-white px-6 py-2 rounded-md hover:bg-blue-600 focus:outline-none" class="bg-blue-500 text-white px-6 py-2 rounded-md hover:bg-blue-600 focus:outline-none"
> >
Mettre à jour Mettre à jour
</button> </button>
<Button href="/chats" variant="secondary">Retour au menu principal</Button>
</div> </div>
</form> </form>
</div> </div>
</div> </div>