atelier prêt
This commit is contained in:
parent
38f33881e0
commit
2b894e62bb
11 changed files with 232 additions and 214 deletions
2
.env
Normal file
2
.env
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
DATABASE_URL="mongodb://temp-root-username:temp-password@localhost/chat_projetweb?authSource=admin"
|
||||||
|
JWT_SECRET="ba63466f102443f4bb6f3670891358bc4488d0c717f6ebcd3ee3c5144e55fe2d"
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -15,7 +15,6 @@ node_modules
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
# Env
|
# Env
|
||||||
.env
|
|
||||||
.env.*
|
.env.*
|
||||||
!.env.example
|
!.env.example
|
||||||
!.env.test
|
!.env.test
|
||||||
|
|
13
Dockerfile
13
Dockerfile
|
@ -1,13 +0,0 @@
|
||||||
# Build stage
|
|
||||||
FROM node:18.18-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
COPY . .
|
|
||||||
|
|
||||||
RUN npm i -g pnpm
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
|
|
||||||
RUN chmod +x /app/docker_entrypoint.sh
|
|
||||||
ENTRYPOINT ["/app/docker_entrypoint.sh"]
|
|
|
@ -1,22 +1,4 @@
|
||||||
services:
|
services:
|
||||||
|
|
||||||
app:
|
|
||||||
build: .
|
|
||||||
hostname: app
|
|
||||||
depends_on:
|
|
||||||
- mongodb
|
|
||||||
- redis
|
|
||||||
environment:
|
|
||||||
- DATABASE_URL=mongodb://temp-root-username:temp-password@mongodb/chat_projetweb?authSource=admin
|
|
||||||
- JWT_SECRET=ba63466f102443f4bb6f3670891358bc4488d0c717f6ebcd3ee3c5144e55fe2d
|
|
||||||
ports:
|
|
||||||
- "3000:3000"
|
|
||||||
networks:
|
|
||||||
- app_network
|
|
||||||
volumes:
|
|
||||||
- .:/usr/src/app
|
|
||||||
- /usr/src/app/node_modules
|
|
||||||
|
|
||||||
mongodb:
|
mongodb:
|
||||||
build: ./mongodb_rs
|
build: ./mongodb_rs
|
||||||
hostname: mongodb
|
hostname: mongodb
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
set -xe
|
|
||||||
|
|
||||||
pnpm install
|
|
||||||
pnpm prisma generate
|
|
||||||
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
pnpx prisma db push
|
|
||||||
|
|
||||||
ls
|
|
||||||
|
|
||||||
node build
|
|
|
@ -5,6 +5,7 @@ import jwt from 'jsonwebtoken';
|
||||||
import logger from '$lib/logger';
|
import logger from '$lib/logger';
|
||||||
|
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
|
// Étape 1 : Récupérer les données du formulaire de la requête
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -12,35 +13,40 @@ export async function POST({request}) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const password: string = formData.get('password').toString();
|
const password: string = formData.get('password').toString();
|
||||||
|
|
||||||
|
// Étape 2 : Vérifier si l'utilisateur existe dans la base de données
|
||||||
const user = await prismaClient.user.findFirst({
|
const user = await prismaClient.user.findFirst({
|
||||||
where: {
|
where: {
|
||||||
email: email,
|
email: email,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Si l'utilisateur n'est pas trouvé, retourner une erreur
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
logger.debug(`Could not find user with email (${email}) in database`);
|
logger.debug(`Could not find user with email (${email}) in database`);
|
||||||
return error(400, { message: "Email ou mot de passe invalide." });
|
return error(400, { message: "Email ou mot de passe invalide." });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Étape 3 : Vérifier si le mot de passe est correct
|
||||||
logger.debug(`Found user with email (${email}) in database`);
|
logger.debug(`Found user with email (${email}) in database`);
|
||||||
try {
|
try {
|
||||||
if (await argon2.verify(user.password, password)) {
|
if (await argon2.verify(user.password, password)) {
|
||||||
logger.debug(`Password for user ${user.email} is correct.`);
|
logger.debug(`Password for user ${user.email} is correct.`);
|
||||||
|
|
||||||
|
// Étape 4 : Générer un token JWT pour l'utilisateur
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const token = jwt.sign(user, process.env.JWT_SECRET, { expiresIn: "1h" });
|
const token = jwt.sign(user, process.env.JWT_SECRET, { expiresIn: "1h" });
|
||||||
logger.debug(`Generated a JWT token for user ${user.email}.`)
|
logger.debug(`Generated a JWT token for user ${user.email}.`);
|
||||||
|
|
||||||
|
// Étape 5 : Retourner le token et l'ID de l'utilisateur
|
||||||
return json({ token: token, userId: user.id });
|
return json({ token: token, userId: user.id });
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
return error(400, { message: "Email ou mot de passe invalide." });
|
return error(400, { message: "Email ou mot de passe invalide." });
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// Étape 6 : Gestion des erreurs
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
return error(500, { message: e.body.message });
|
return error(500, { message: e.body.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,52 +4,47 @@ import * as argon2 from 'argon2';
|
||||||
import jwt from 'jsonwebtoken';
|
import jwt from 'jsonwebtoken';
|
||||||
import logger from '$lib/logger';
|
import logger from '$lib/logger';
|
||||||
|
|
||||||
|
// POST: Créer un utilisateur et générer un token JWT
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
|
// Étape 1 : Récupérer les données du formulaire de la requête
|
||||||
const formData = await request.formData();
|
const formData = await request.formData();
|
||||||
|
|
||||||
// @ts-ignore
|
const username: string = formData.get('username').toString().toLowerCase(); // Nom d'utilisateur en minuscules
|
||||||
const username: string = formData.get('username').toString().toLowerCase();
|
const email: string = formData.get('email').toString().toLowerCase(); // Email en minuscules
|
||||||
// @ts-ignore
|
const password: string = formData.get('password').toString(); // Mot de passe brut
|
||||||
const email: string = formData.get('email').toString().toLowerCase();
|
|
||||||
// @ts-ignore
|
|
||||||
const password: string = formData.get('password').toString();
|
|
||||||
|
|
||||||
const user = await prismaClient.user.findFirst({
|
// Étape 2 : Vérifier si l'utilisateur existe déjà dans la base de données
|
||||||
where: {
|
// Question 12 - A implémenter : Recuperer l'utilisateur avec le nom d'utilisateur ou l'email fourni.
|
||||||
OR: [
|
// - Utilisez la méthode await prismaClient.user.findFirst pour récupérer l'utilisateur par son nom d'utilisateur ou son email.
|
||||||
{ username: username },
|
// - Utilisez une clause OR pour rechercher l'utilisateur par nom d'utilisateur ou email.
|
||||||
{ email: email },
|
// - Stockez l'utilisateur dans une variable 'user' constante (const).
|
||||||
]
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Si l'utilisateur existe déjà, retourner une erreur
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
logger.debug(`A user with email (${email}) already exists in database`);
|
logger.debug(`A user with email (${email}) already exists in database`);
|
||||||
return error(400, { message: "Un compte avec cette adresse email ou nom d'utilisateur existe déjà." });
|
return error(400, { message: "Un compte avec cette adresse email ou nom d'utilisateur existe déjà." });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Étape 3 : Hash du mot de passe
|
||||||
const hash = await argon2.hash(password);
|
const hash = await argon2.hash(password);
|
||||||
|
|
||||||
const newUser = await prismaClient.user.create({
|
// Étape 4 : Créer un nouvel utilisateur dans la base de données
|
||||||
data: {
|
// Question 13 - A implémenter : Utilisez `prismaClient.user.create` pour créer un utilisateur avec les données récupérées et le mot de passe hashé.
|
||||||
username: username,
|
// - Utilisez await prismaClient.user.create pour créer un utilisateur avec les données fournies.
|
||||||
email: email,
|
// - Incluez le nom d'utilisateur, l'email, le mot de passe hashé, le nom (par défaut vide) et le nom de famille (par défaut vide).
|
||||||
password: hash,
|
// - Stockez le nouvel utilisateur dans une variable `newUser` (const).
|
||||||
surname: "",
|
|
||||||
name: ""
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// @ts-ignore
|
// Étape 5 : Générer un token JWT pour l'utilisateur
|
||||||
const token = jwt.sign(newUser, process.env.JWT_SECRET, { expiresIn: "1h" });
|
const token = jwt.sign(newUser, process.env.JWT_SECRET, { expiresIn: "1h" });
|
||||||
logger.debug(`Generated a JWT token for user ${newUser.email}.`)
|
logger.debug(`Generated a JWT token for user ${newUser.email}.`);
|
||||||
|
|
||||||
|
// Étape 6 : Retourner le token et l'ID de l'utilisateur créé
|
||||||
return json({ token: token, userId: newUser.id });
|
return json({ token: token, userId: newUser.id });
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
// Étape 7 : Gestion des erreurs
|
||||||
logger.error(e);
|
logger.error(e);
|
||||||
return error(500, { message: "Erreur interne." });
|
return error(500, { message: "Erreur interne." });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -6,9 +6,11 @@ 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({ url }) {
|
export async function GET({ url }) {
|
||||||
|
// Vérifier si un paramètre "name" est passé dans l'URL (pour filtrer par nom)
|
||||||
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 {
|
||||||
|
// Étape 1 : Récupérer les canaux depuis la base de données (Prisma)
|
||||||
let canaux = await prisma.channel.findMany({
|
let canaux = await prisma.channel.findMany({
|
||||||
where: {
|
where: {
|
||||||
name: {
|
name: {
|
||||||
|
@ -18,118 +20,139 @@ export async function GET({ url }) {
|
||||||
},
|
},
|
||||||
include: {
|
include: {
|
||||||
messages: {
|
messages: {
|
||||||
take: 1, // Récupère le dernier message
|
take: 1,
|
||||||
orderBy: { createdAt: 'desc' },// Trie par date décroissante
|
orderBy: { createdAt: 'desc' },
|
||||||
// as lastMessage not list last message
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Étape 2 : Transformation des résultats
|
||||||
canaux = canaux.map((canaux) => {
|
canaux = canaux.map((canaux) => {
|
||||||
return {
|
return {
|
||||||
...canaux,
|
...canaux,
|
||||||
lastMessage: canaux.messages.length > 0 ? canaux.messages[0] : null,
|
lastMessage: canaux.messages.length > 0 ? canaux.messages[0] : null,
|
||||||
messages: undefined
|
messages: undefined,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
canaux = sortChannels(canaux);
|
// Étape 3 : Trier les canaux par date du dernier message
|
||||||
|
canaux = sortChannels(canaux); // Fonction pour trier les canaux
|
||||||
|
|
||||||
return json(canaux);
|
return json(canaux);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// Gérer les erreurs et renvoyer une réponse d'erreur en cas de problème
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
return json({ error: 'Erreur serveur' }, { status: 500 });
|
return json({ error: 'Erreur serveur' }, { status: 500 });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
|
// Étape 4 : Vérifier si les canaux sont dans le cache Redis
|
||||||
let channels = [];
|
let channels = [];
|
||||||
|
// Question 8 - A implémenter : Récupérer les canaux dans le cache Redis.
|
||||||
const cachedChannels = await redisClient.get('channels');
|
// - A l'aide de la clé 'channels', récupérez les canaux depuis le cache Redis.
|
||||||
|
// - Utilisez await redisClient.get pour récupérer les canaux depuis le cache Redis.
|
||||||
|
// - Stockez les canaux dans une variable 'cachedChannels' constante (const).
|
||||||
|
|
||||||
if (cachedChannels != null) {
|
if (cachedChannels != null) {
|
||||||
logger.debug('Cache entry found, fetching channels from cache');
|
logger.debug('Cache entry found, fetching channels from cache');
|
||||||
channels = JSON.parse(cachedChannels);
|
channels = JSON.parse(cachedChannels); // Charger les canaux depuis le cache
|
||||||
} else {
|
} else {
|
||||||
logger.debug('No cache entry was found, fetching channels from database');
|
logger.debug('No cache entry was found, fetching channels from database');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Étape 5 : Si le cache est insuffisant, récupérer les canaux depuis la base de données
|
||||||
if (channels.length < 10) {
|
if (channels.length < 10) {
|
||||||
logger.debug('Fetching channels from database to fill cache');
|
logger.debug('Fetching channels from database to fill cache');
|
||||||
let canaux = await prisma.channel.findMany({
|
// Question 9 - A implémenter : Récupérer les canaux dans la base de donnée mongoDB.
|
||||||
include: {
|
// - Utilisez Prisma pour récupérer les canaux depuis MongoDB.
|
||||||
messages: {
|
// - Utilisez la méthode await prisma.channel.findMany pour récupérer les canaux.
|
||||||
take: 1, // Récupère le dernier message
|
// - Incluez le dernier message des canaux.
|
||||||
orderBy: { createdAt: 'desc' }, // Trie par date décroissante
|
// - Stockez les canaux dans une variable 'canaux' (let).
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Transformation des canaux pour ne garder que le dernier message
|
||||||
canaux = canaux.map((canaux) => {
|
canaux = canaux.map((canaux) => {
|
||||||
return {
|
return {
|
||||||
...canaux,
|
...canaux,
|
||||||
lastMessage: canaux.messages.length > 0 ? canaux.messages[0] : null,
|
lastMessage: canaux.messages.length > 0 ? canaux.messages[0] : null,
|
||||||
messages: undefined
|
messages: undefined, // Ne pas retourner la liste complète des messages
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Fusionner les nouveaux canaux récupérés avec les canaux existants dans le cache
|
||||||
channels = channels.concat(canaux);
|
channels = channels.concat(canaux);
|
||||||
|
|
||||||
|
// Supprimer les doublons en vérifiant par l'ID du canal
|
||||||
channels = channels.filter((channel, index, self) =>
|
channels = channels.filter((channel, index, self) =>
|
||||||
index === self.findIndex((t) => (
|
index === self.findIndex((t) => (
|
||||||
t.id === channel.id
|
t.id === channel.id
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Trier les canaux par la date du dernier message
|
||||||
channels = sortChannels(channels);
|
channels = sortChannels(channels);
|
||||||
|
|
||||||
|
// Limiter à 10 canaux maximum
|
||||||
channels = channels.slice(0, 10);
|
channels = channels.slice(0, 10);
|
||||||
|
|
||||||
await redisClient.set('channels', JSON.stringify(channels), { EX: 3600 });
|
// Mettre à jour le cache Redis avec les canaux
|
||||||
|
await redisClient.set('channels', JSON.stringify(channels), { EX: 3600 }); // Cache pendant 1 heure
|
||||||
}
|
}
|
||||||
|
|
||||||
return json(channels);
|
return json(channels); // Retourner les canaux sous forme JSON
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err)
|
// Gérer les erreurs et renvoyer une réponse d'erreur en cas de problème
|
||||||
|
logger.error(err);
|
||||||
return json({ error: 'Erreur serveur' }, { status: 500 });
|
return json({ error: 'Erreur serveur' }, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// POST: Créer un nouveau canal et mettre à jour le cache Redis
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
|
// Étape 1 : Récupérer les données de la requête (nom du canal)
|
||||||
const { name } = await request.json();
|
const { name } = await request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let canal = await prisma.channel.create({
|
// Étape 2 : Créer un nouveau canal dans la base de données
|
||||||
data: {
|
// Question 10 - A implémenter : Utilisez `prisma.channel.create` pour créer un canal avec le nom fourni.
|
||||||
name
|
// - Dans une variable `canal` (let) , stockez le résultat de la création du canal.
|
||||||
},
|
|
||||||
});
|
|
||||||
logger.debug('Creating a new channel in database with id ' + canal.id);
|
logger.debug('Creating a new channel in database with id ' + canal.id);
|
||||||
|
|
||||||
|
// Étape 3 : Mettre à jour le cache Redis des canaux
|
||||||
const cachedChanels = await redisClient.get('channels');
|
const cachedChanels = await redisClient.get('channels');
|
||||||
|
|
||||||
let channels = cachedChanels != null ? JSON.parse(cachedChanels) : [];
|
let channels = cachedChanels != null ? JSON.parse(cachedChanels) : [];
|
||||||
|
|
||||||
|
// Étape 4 : Structurer les données du canal à ajouter au cache
|
||||||
canal = {
|
canal = {
|
||||||
...canal,
|
...canal,
|
||||||
lastMessage: null,
|
lastMessage: null,
|
||||||
lastUpdate: canal.createdAt,
|
lastUpdate: canal.createdAt,
|
||||||
messages: undefined
|
messages: undefined,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Étape 5 : Ajouter le canal au tableau des canaux et trier
|
||||||
channels.push(canal);
|
channels.push(canal);
|
||||||
|
|
||||||
|
// Trier les canaux par la dernière mise à jour
|
||||||
channels = sortChannels(channels);
|
channels = sortChannels(channels);
|
||||||
|
|
||||||
|
// Étape 6 : Mettre à jour le cache Redis avec la nouvelle liste de canaux
|
||||||
logger.debug(`Added channel (${canal.id}) to channels cache.`);
|
logger.debug(`Added channel (${canal.id}) to channels cache.`);
|
||||||
await redisClient.set('channels', JSON.stringify(channels), { EX: 600 });
|
// Question 11 - A implémenter : Mettez à jour le cache Redis avec la liste des canaux, avec une expiration de 10 minutes.
|
||||||
|
// - Utilisez await redisClient.set pour mettre à jour le cache des canaux.
|
||||||
|
// - Utilisez une expiration de 10 minutes (600 secondes).
|
||||||
|
// - Stockez les canaux dans le cache Redis en utilisant la clé 'channels'.
|
||||||
|
// - Utilisez JSON.stringify pour convertir les canaux en chaîne JSON.
|
||||||
|
|
||||||
|
// Étape 7 : Retourner la réponse avec le canal créé
|
||||||
return json(canal, { status: 201 });
|
return json(canal, { status: 201 });
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// Étape 8 : Gérer les erreurs en cas de problème
|
||||||
console.log(err);
|
console.log(err);
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
return json({ error: 'Erreur lors de la création du canal' }, { status: 500 });
|
return json({ error: 'Erreur lors de la création du canal' }, { status: 500 });
|
||||||
|
@ -137,3 +160,4 @@ export async function POST({ request }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,46 +3,56 @@ import prisma from '$lib/prismaClient';
|
||||||
import redisClient from '$lib/redisClient';
|
import redisClient from '$lib/redisClient';
|
||||||
import logger from '$lib/logger';
|
import logger from '$lib/logger';
|
||||||
|
|
||||||
// Récupérer les informations du canal et le dernier message (avec cache Redis)
|
// GET: Récupérer les informations d'un canal
|
||||||
export async function GET({ params }) {
|
export async function GET({ params }) {
|
||||||
const channelId = params.id;
|
const channelId = params.id;
|
||||||
|
|
||||||
|
// Définir la clé de cache Redis pour le canal
|
||||||
const channelCacheKey = `channel:${channelId}:info`;
|
const channelCacheKey = `channel:${channelId}:info`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Étape 1 : Vérifier si les informations du canal sont en cache Redis
|
||||||
const cachedChannel = await redisClient.get(channelCacheKey);
|
const cachedChannel = await redisClient.get(channelCacheKey);
|
||||||
if (cachedChannel) {
|
if (cachedChannel) {
|
||||||
|
// Si le canal est dans le cache, renvoyez-le
|
||||||
logger.debug(`Cache entry found, fetching channel (${channelId}) from cache`);
|
logger.debug(`Cache entry found, fetching channel (${channelId}) from cache`);
|
||||||
return json(JSON.parse(cachedChannel));
|
return json(JSON.parse(cachedChannel));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Étape 2 : Si le canal n'est pas dans le cache, le récupérer depuis la base de données MongoDB
|
||||||
logger.debug(`No cache entry was found, fetching channel (${channelId}) from database`);
|
logger.debug(`No cache entry was found, fetching channel (${channelId}) from database`);
|
||||||
const canal = await prisma.channel.findUnique({
|
|
||||||
where: { id: channelId },
|
|
||||||
});
|
|
||||||
|
|
||||||
|
// Question 6 - A implémenter : Récupérer les informations du canal depuis la base de données.
|
||||||
|
// Récupérer les informations du canal depuis la base de données
|
||||||
|
// Utilisez la méthode await prisma.channel.findUnique pour récupérer le canal par son ID.
|
||||||
|
// Stockez les informations du canal dans une variable 'canal' constante (const).
|
||||||
|
|
||||||
|
// Vérifier si le canal existe dans la base de données
|
||||||
if (!canal) {
|
if (!canal) {
|
||||||
logger.debug(`No channel for id ${channelId} was found in database`)
|
logger.debug(`No channel for id ${channelId} was found in database`);
|
||||||
return json({ error: 'Canal non trouvé' }, { status: 404 });
|
return json({ error: 'Canal non trouvé' }, { status: 404 });
|
||||||
}
|
}
|
||||||
|
|
||||||
const lastMessage = await prisma.message.findFirst({
|
// Étape 3 : Récupérer le dernier message du canal
|
||||||
where: { id: channelId },
|
// Question 7 - A implémenter : Récupérer le dernier message du canal depuis la base de données.
|
||||||
orderBy: { createdAt: 'desc' },
|
// Utilisez la méthode await prisma.message.findFirst pour récupérer le dernier message du canal.
|
||||||
});
|
// Filtrez les messages par `channelId` et triez-les par date de création décroissante.
|
||||||
|
// Stockez le dernier message dans une variable 'lastMessage' constante (const).
|
||||||
|
|
||||||
// Créer un objet combiné pour le canal et le dernier message
|
// Créer un objet combiné pour le canal et son dernier message
|
||||||
const canalData = {
|
const canalData = {
|
||||||
canal,
|
canal,
|
||||||
lastMessage, // Inclure uniquement le dernier message
|
lastMessage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Étape 4 : Mettre à jour le cache global des canaux dans Redis
|
||||||
|
const cachedChannels = await redisClient.get('channels');
|
||||||
|
let channels = cachedChannels != null ? JSON.parse(cachedChannels) : [];
|
||||||
|
|
||||||
const cachedChanels = await redisClient.get('channels');
|
// Ajouter le canal actuel dans la liste des canaux
|
||||||
let channels = cachedChanels != null ? JSON.parse(cachedChanels) : [];
|
|
||||||
|
|
||||||
channels.push(canal);
|
channels.push(canal);
|
||||||
|
|
||||||
|
// Trier les canaux par la date du dernier message
|
||||||
channels = channels.sort(
|
channels = channels.sort(
|
||||||
(
|
(
|
||||||
a: { messages: { createdAt: Date }[]; createdAt: Date },
|
a: { messages: { createdAt: Date }[]; createdAt: Date },
|
||||||
|
@ -54,16 +64,19 @@ export async function GET({ params }) {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Enregistrer la liste mise à jour des canaux dans Redis
|
||||||
logger.debug(`Added channel (${canal.id}) to channels cache.`);
|
logger.debug(`Added channel (${canal.id}) to channels cache.`);
|
||||||
await redisClient.set('channels', JSON.stringify(channels), { EX: 600 });
|
await redisClient.set('channels', JSON.stringify(channels), { EX: 600 });
|
||||||
|
|
||||||
|
// Étape 5 : Ajouter les informations du canal et du dernier message dans le cache Redis
|
||||||
logger.debug(`Creating a new cache entry with key channel:${channelId}:info`);
|
logger.debug(`Creating a new cache entry with key channel:${channelId}:info`);
|
||||||
await redisClient.set(channelCacheKey, JSON.stringify(canalData), {EX: 600, NX: true}); // Cache pendant 5 minutes
|
await redisClient.set(channelCacheKey, JSON.stringify(canalData), { EX: 600, NX: true });
|
||||||
|
|
||||||
|
|
||||||
|
// Étape 6 : Retourner les données du canal et du dernier message
|
||||||
return json(canalData);
|
return json(canalData);
|
||||||
} catch (err) {
|
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
// Si une erreur survient lors de la récupération des informations du canal ou du dernier message, retournez une erreur
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
return json({ error: 'Erreur lors de la récupération du canal ou du dernier message' }, { status: 500 });
|
return json({ error: 'Erreur lors de la récupération du canal ou du dernier message' }, { status: 500 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import redisClient from '$lib/redisClient';
|
||||||
import logger from '$lib/logger';
|
import logger from '$lib/logger';
|
||||||
import { sortChannels } from '$lib/utils/sort.ts';
|
import { sortChannels } from '$lib/utils/sort.ts';
|
||||||
|
|
||||||
|
// GET: Liste tous les messages d'un canal avec pagination
|
||||||
export async function GET({ params, url }) {
|
export async function GET({ params, url }) {
|
||||||
const channelId = params.id;
|
const channelId = params.id;
|
||||||
logger.debug(`GET /api/channels/${channelId}/messages`);
|
logger.debug(`GET /api/channels/${channelId}/messages`);
|
||||||
|
@ -14,16 +15,17 @@ 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}`);
|
||||||
let redisMessageKeys = await redisClient.zRangeWithScores(
|
|
||||||
`channel:${channelId}:messages`,
|
|
||||||
offset,
|
|
||||||
offset + limit - 1,
|
|
||||||
{ REV: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
|
// Étape 1 : Récupérer les clés Redis des messages
|
||||||
|
// Question 1 - A implémenter : Récupérer les messages depuis Redis avec la pagination.
|
||||||
|
// - Utilisez await redisClient.zRangeWithScores pour récupérer les clés des messages dans une variable redisMessageKeys (let).
|
||||||
|
// - La clé Redis pour l'ensemble trié des messages est sous la forme : `channel:<channelId>:messages`.
|
||||||
|
// - Vous devez récupérer les messages selon l'offset et le limit.
|
||||||
|
|
||||||
|
|
||||||
|
//Suppression des messages dans le cache si message:<id> n'existe plus
|
||||||
const redisPipelineRemove = redisClient.multi();
|
const redisPipelineRemove = redisClient.multi();
|
||||||
|
|
||||||
|
|
||||||
for (const messageKey of redisMessageKeys) {
|
for (const messageKey of redisMessageKeys) {
|
||||||
// Vérifie si la clé existe dans Redis
|
// Vérifie si la clé existe dans Redis
|
||||||
const messageKeyValue = messageKey.value;
|
const messageKeyValue = messageKey.value;
|
||||||
|
@ -36,6 +38,7 @@ export async function GET({ params, url }) {
|
||||||
}
|
}
|
||||||
await redisPipelineRemove.exec();
|
await redisPipelineRemove.exec();
|
||||||
|
|
||||||
|
// Étape 2 : Si des messages sont trouvés dans Redis
|
||||||
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) => {
|
||||||
|
@ -44,11 +47,12 @@ export async function GET({ params, url }) {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Met à jour le TTL pour les messages et l'ensemble trié
|
||||||
const redisPipeline = redisClient.multi();
|
const redisPipeline = redisClient.multi();
|
||||||
for (const key of redisMessageKeys) {
|
for (const key of redisMessageKeys) {
|
||||||
const message = await redisClient.get(key.value);
|
const message = await redisClient.get(key.value);
|
||||||
const msg = JSON.parse(message)
|
const msg = JSON.parse(message);
|
||||||
redisPipeline.set(key.value, JSON.stringify(msg), {EX: 1800});
|
redisPipeline.set(key.value, JSON.stringify(msg), { EX: 1800 }); // TTL 30 minutes
|
||||||
redisPipeline.zAdd(`channel:${channelId}:messages`, {
|
redisPipeline.zAdd(`channel:${channelId}:messages`, {
|
||||||
score: key.score,
|
score: key.score,
|
||||||
value: key.value,
|
value: key.value,
|
||||||
|
@ -59,35 +63,28 @@ export async function GET({ params, url }) {
|
||||||
return json({ limit, page, messages: messages.reverse() });
|
return json({ limit, page, messages: messages.reverse() });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Étape 3 : Aucun message trouvé dans Redis, récupération depuis MongoDB
|
||||||
logger.debug(`Aucun message trouvé dans le cache, récupération depuis MongoDB pour le channel : ${channelId}`);
|
logger.debug(`Aucun message trouvé dans le cache, récupération depuis MongoDB pour le channel : ${channelId}`);
|
||||||
const messagesFromDB = await prisma.message.findMany({
|
// Question 2 - A implémenter : Si aucun message n'est trouvé dans Redis, récupérez-les depuis MongoDB.
|
||||||
where: { channelId },
|
// - Utiliser Prisma pour récupérer les messages depuis MongoDB.
|
||||||
select: {
|
// - Utilisez la méthode await prisma.message.findMany de Prisma pour récupérer les messages.
|
||||||
id: true,
|
// - Filtrez les messages par `channelId` et recuperer l'id, le createdAt, le text, le user ( avec son id ).
|
||||||
createdAt: true,
|
// - Appliquez la pagination avec `skip` et `take` pour gérer le `offset` et le `limit`.
|
||||||
text: true,
|
// - Triez les messages par date de création décroissante.
|
||||||
user: {
|
// - Stockez les messages dans une variable `messagesFromDB` constante (const).
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
orderBy: { createdAt: 'desc' },
|
|
||||||
skip: offset,
|
|
||||||
take: limit,
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
// Étape 4 : Si des messages sont récupérés depuis MongoDB, les stocker dans Redis
|
||||||
if (messagesFromDB.length > 0) {
|
if (messagesFromDB.length > 0) {
|
||||||
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), {EX: 1800});
|
redisPipeline.set(messageKey, JSON.stringify(message), { EX: 1800 }); // TTL 30 minutes
|
||||||
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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await redisPipeline.exec();
|
await redisPipeline.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,47 +95,35 @@ export async function GET({ params, url }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Fonction POST permettant de créer un nouveau message
|
||||||
export async function POST({ params, request }) {
|
export async function POST({ params, request }) {
|
||||||
const channelId = params.id;
|
const channelId = params.id;
|
||||||
const { userId, text } = await request.json();
|
const { userId, text } = await request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Créer un nouveau message dans MongoDB
|
// Étape 1 : Créer un nouveau message dans MongoDB
|
||||||
let newMessage = await prisma.message.create({
|
// Question 3 - A implémenter : Utilisez Prisma pour créer un nouveau message dans MongoDB.
|
||||||
data: {
|
// - Utilisez await prisma.message.create pour créer un message avec les données fournies.
|
||||||
userId,
|
// - Incluez l'id, la date de création, le text, l'utilisateur (avec son id), et le canal (avec son id et son name).
|
||||||
channelId,
|
// - Stockez le message dans une variable `newMessage` (let).
|
||||||
text,
|
// - Utiliser userId, channelId et text pour créer le message.
|
||||||
},
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
createdAt: true,
|
|
||||||
text: true,
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
channel: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ajouter le message dans Redis
|
// Étape 2 : Ajouter le message dans Redis
|
||||||
await redisClient.set(`message:${newMessage.id}`, JSON.stringify(newMessage), {EX: 1800});
|
// Question 4 - A implémenter : Stocker le message dans Redis.
|
||||||
await redisClient.zAdd(`channel:${channelId}:messages`, {
|
// - Utiliser await redisClient.set pour ajouter le message dans Redis.
|
||||||
score: new Date(newMessage.createdAt).getTime(),
|
// - Avec un Time To Live de 30 Minutes (1800 secondes)
|
||||||
value: `message:${newMessage.id}`,
|
// - Assurez-vous de structurer la clé comme suit : `message:<messageId>`.
|
||||||
});
|
|
||||||
|
|
||||||
//update the channels cache with the new message
|
// Question 5 - A implémenter : Ajouter la clé du message dans la liste ordonnée des messages du canal.
|
||||||
|
// - Utiliser await redisClient.zAdd pour insérer le message dans un ensemble trié basé sur la date de création.
|
||||||
|
// Assurez-vous de structurer la clé comme suit : `channel:<channelId>:messages`
|
||||||
|
|
||||||
|
// Étape 3 : Mettre à jour le cache des channels avec le nouveau message
|
||||||
const cachedChannels = await redisClient.get('channels');
|
const cachedChannels = await redisClient.get('channels');
|
||||||
let channels = cachedChannels ? JSON.parse(cachedChannels) : [];
|
let channels = cachedChannels ? JSON.parse(cachedChannels) : [];
|
||||||
let channel = channels.find((c) => c.id === channelId);
|
let channel = channels.find((c) => c.id === channelId);
|
||||||
|
|
||||||
if (channel) {
|
if (channel) {
|
||||||
channel.lastMessage = {
|
channel.lastMessage = {
|
||||||
id: newMessage.id,
|
id: newMessage.id,
|
||||||
|
@ -150,12 +135,17 @@ export async function POST({ params, request }) {
|
||||||
channel.messages = undefined;
|
channel.messages = undefined;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
channel = {...newMessage.channel, lastMessage: {
|
channel = {
|
||||||
|
...newMessage.channel,
|
||||||
|
lastMessage: {
|
||||||
id: newMessage.id,
|
id: newMessage.id,
|
||||||
text: newMessage.text,
|
text: newMessage.text,
|
||||||
user: newMessage.user,
|
user: newMessage.user,
|
||||||
createdAt: newMessage.createdAt,
|
createdAt: newMessage.createdAt,
|
||||||
}, lastUpdate: newMessage.createdAt, messages: undefined};
|
},
|
||||||
|
lastUpdate: newMessage.createdAt,
|
||||||
|
messages: undefined
|
||||||
|
};
|
||||||
channels = [channel, ...channels];
|
channels = [channel, ...channels];
|
||||||
}
|
}
|
||||||
await redisClient.set('channels', JSON.stringify(channels), { EX: 600 });
|
await redisClient.set('channels', JSON.stringify(channels), { EX: 600 });
|
||||||
|
@ -170,12 +160,14 @@ export async function POST({ params, request }) {
|
||||||
|
|
||||||
logger.debug(`Nouveau message ajouté pour le channel : ${channelId}`);
|
logger.debug(`Nouveau message ajouté pour le channel : ${channelId}`);
|
||||||
return json(newMessage, { status: 201 });
|
return json(newMessage, { status: 201 });
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(`Erreur lors de la création du message : ${err.message}`);
|
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();
|
||||||
|
|
|
@ -4,34 +4,52 @@ import redisClient from '$lib/redisClient';
|
||||||
import prisma from '$lib/prismaClient';
|
import prisma from '$lib/prismaClient';
|
||||||
import logger from '$lib/logger';
|
import logger from '$lib/logger';
|
||||||
|
|
||||||
|
// GET: Récupérer tous les utilisateurs avec cache Redis
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
// Vérifier si les utilisateurs sont dans le cache Redis
|
// Étape 1 : Vérifier si les utilisateurs sont déjà présents dans le cache Redis
|
||||||
const cachedUsers = await redisClient.get('users');
|
const cachedUsers = await redisClient.get('users');
|
||||||
|
|
||||||
|
// Si les utilisateurs sont trouvés dans le cache, renvoyez-les directement
|
||||||
if (cachedUsers) {
|
if (cachedUsers) {
|
||||||
|
// Si le cache contient les utilisateurs, on les récupère et on les renvoie
|
||||||
logger.debug('Cache entry found, fetching users from cache');
|
logger.debug('Cache entry found, fetching users from cache');
|
||||||
return json(JSON.parse(cachedUsers));
|
return json(JSON.parse(cachedUsers));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Étape 2 : Si les utilisateurs ne sont pas dans le cache, les récupérer depuis la base de données
|
||||||
logger.debug('No cache entry was found, fetching users from database');
|
logger.debug('No cache entry was found, fetching users from database');
|
||||||
// Sinon, récupérer les utilisateurs depuis MongoDB
|
|
||||||
|
// Question 14 - A implémenter : Utilisez Prisma pour récupérer la liste des utilisateurs dans la base de données.
|
||||||
|
// - Utilisez la méthode await prisma.user.findMany pour récupérer la liste des utilisateurs.
|
||||||
|
// - Stockez les utilisateurs dans une variable 'users' constante (const).
|
||||||
const users = await prisma.user.findMany();
|
const users = await prisma.user.findMany();
|
||||||
|
|
||||||
// Mettre les utilisateurs en cache
|
// Étape 3 : Mettre en cache les utilisateurs récupérés depuis la base de données
|
||||||
logger.debug('Caching users with EX of 600 secs');
|
logger.debug('Caching users with EX of 600 secs');
|
||||||
await redisClient.set('users', JSON.stringify(users), { EX: 600 });
|
|
||||||
|
|
||||||
|
// Question 15 - A implémenter : Mettre dans le cache Redis avec une expiration de 600 secondes.
|
||||||
|
// - Utilisez await redisClient.set pour mettre en cache les utilisateurs.
|
||||||
|
// - Utilisez la méthode JSON.stringify pour convertir les utilisateurs en format JSON.
|
||||||
|
// - Utilisez l'option { EX: 600 } pour définir l'expiration
|
||||||
|
|
||||||
|
// Étape 4 : Retourner la liste des utilisateurs récupérés
|
||||||
return json(users);
|
return json(users);
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// Étape 5 : Gestion des erreurs
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
return json({ error: 'Erreur serveur' }, { status: 500 });
|
return json({ error: 'Erreur serveur' }, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// POST: Créer un utilisateur et mettre à jour le cache
|
||||||
export async function POST({ request }) {
|
export async function POST({ request }) {
|
||||||
|
// Étape 1 : Récupérer les données envoyées dans la requête (username, surname, name, email, password)
|
||||||
const { username, surname, name, email, password } = await request.json();
|
const { username, surname, name, email, password } = await request.json();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Étape 2 : Créer un nouvel utilisateur dans la base de données
|
||||||
const user = await prisma.user.create({
|
const user = await prisma.user.create({
|
||||||
data: {
|
data: {
|
||||||
username: username.toLowerCase(),
|
username: username.toLowerCase(),
|
||||||
|
@ -41,22 +59,35 @@ export async function POST({ request }) {
|
||||||
password,
|
password,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Étape 3 : Loguer la création de l'utilisateur avec son ID
|
||||||
logger.debug('Creating a new user in database with id ' + user.id);
|
logger.debug('Creating a new user in database with id ' + user.id);
|
||||||
|
|
||||||
// Mettre le nouvel utilisateur dans le cache
|
// Étape 4 : Mettre à jour le cache global des utilisateurs
|
||||||
logger.debug(`Caching user (${user.id})`);
|
logger.debug(`Caching user (${user.id})`);
|
||||||
const cachedUsers = await redisClient.get('users');
|
const cachedUsers = await redisClient.get('users');
|
||||||
const usersArray = cachedUsers != null ? JSON.parse(cachedUsers) : [];
|
const usersArray = cachedUsers != null ? JSON.parse(cachedUsers) : [];
|
||||||
usersArray.push(user);
|
usersArray.push(user);
|
||||||
|
|
||||||
|
// Étape 5 : Enregistrer la liste mise à jour des utilisateurs dans le cache Redis
|
||||||
logger.debug(`Added user (${user.id}) to users cache.`);
|
logger.debug(`Added user (${user.id}) to users cache.`);
|
||||||
await redisClient.set('users', JSON.stringify(usersArray), { EX: 600 })
|
await redisClient.set('users', JSON.stringify(usersArray), { EX: 600 });
|
||||||
logger.debug(`Creating a new cache entry with key user:${user.id}, with EX of 3600 secs`);
|
|
||||||
await redisClient.set(`user:${user.id}`, JSON.stringify(user), { EX: 3600 });
|
|
||||||
|
|
||||||
|
// Étape 6 : Créer un cache individuel pour cet utilisateur spécifique
|
||||||
|
logger.debug(`Creating a new cache entry with key user:${user.id}, with EX of 3600 secs`);
|
||||||
|
// Question 16 - A implémenter : Mettre en cache l'utilisateur créé avec une expiration de 1 heure (3600 secondes).
|
||||||
|
// - Utilisez await redisClient.set pour mettre en cache l'utilisateur.
|
||||||
|
// - Utilisez JSON.stringify pour convertir l'utilisateur en format JSON.
|
||||||
|
// - Utilisez l'option { EX: 3600 } pour définir l'expiration.
|
||||||
|
// - Utilisez la clé 'user:${user.id}' pour stocker l'utilisateur dans le cache.
|
||||||
|
|
||||||
|
// Étape 7 : Retourner l'utilisateur créé avec un statut 201
|
||||||
return json(user, { status: 201 });
|
return json(user, { status: 201 });
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err)
|
// Étape 8 : Gestion des erreurs
|
||||||
|
logger.error(err);
|
||||||
return json({ error: 'Erreur lors de la création de l’utilisateur' }, { status: 500 });
|
return json({ error: 'Erreur lors de la création de l’utilisateur' }, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue