diff --git a/package.json b/package.json
index fb08de5..1f27afd 100644
--- a/package.json
+++ b/package.json
@@ -36,6 +36,8 @@
"vite": "^5.0.3"
},
"dependencies": {
+ "lucide-svelte": "^0.462.0",
+ "multer": "^1.4.5-lts.1",
"svelte-radix": "^2.0.1"
}
}
diff --git a/src/lib/components/ui/Alert.svelte b/src/lib/components/ui/Alert.svelte
new file mode 100644
index 0000000..0558081
--- /dev/null
+++ b/src/lib/components/ui/Alert.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+
+ {message}
+
+
+ ×
+
+
+
diff --git a/src/lib/components/ui/ChatItem.svelte b/src/lib/components/ui/ChatItem.svelte
new file mode 100644
index 0000000..5afb5ee
--- /dev/null
+++ b/src/lib/components/ui/ChatItem.svelte
@@ -0,0 +1,19 @@
+
+
+
+
+
{title}
+
{lastMessage}
+
+
{time}
+
+
+
\ No newline at end of file
diff --git a/src/lib/components/ui/ChoosePicture.svelte b/src/lib/components/ui/ChoosePicture.svelte
new file mode 100644
index 0000000..ede5fcd
--- /dev/null
+++ b/src/lib/components/ui/ChoosePicture.svelte
@@ -0,0 +1,105 @@
+
+
+
+
+
+
+
+
+
+
+
+ Sélectionner une image
+
+
+
+
+
+
+ Supprimer l'image
+
+
+
diff --git a/src/lib/components/ui/ProfileCard.svelte b/src/lib/components/ui/ProfileCard.svelte
new file mode 100644
index 0000000..65faf27
--- /dev/null
+++ b/src/lib/components/ui/ProfileCard.svelte
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/components/ui/ProfileModal.svelte b/src/lib/components/ui/ProfileModal.svelte
new file mode 100644
index 0000000..36d195a
--- /dev/null
+++ b/src/lib/components/ui/ProfileModal.svelte
@@ -0,0 +1,36 @@
+
+
+
+
+
+
{name}
+ ×
+
+
+
+
+
{bio}
+
+
+
+
+ Fermer
+
+
+
+
+
+
diff --git a/src/lib/components/ui/Search.svelte b/src/lib/components/ui/Search.svelte
new file mode 100644
index 0000000..749d684
--- /dev/null
+++ b/src/lib/components/ui/Search.svelte
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
diff --git a/src/routes/api/canal/[id]/+page.server.ts b/src/routes/api/canal/[id]/+page.server.ts
new file mode 100644
index 0000000..cfdb6f3
--- /dev/null
+++ b/src/routes/api/canal/[id]/+page.server.ts
@@ -0,0 +1,123 @@
+import { json } from '@sveltejs/kit';
+import prisma from '$lib/prismaClient';
+import redisClient from '$lib/redisClient'; // Assurez-vous d'importer le client Redis
+
+// Récupérer les informations du canal et le dernier message (avec cache Redis)
+export async function GET({ params }) {
+ const canalId = parseInt(params.id);
+
+ // Clé cache pour les informations du canal et le dernier message
+ const canalCacheKey = `canal:${canalId}:info`;
+
+ try {
+ // Vérifier si les informations du canal et le dernier message sont dans le cache Redis
+ const cachedCanalData = await redisClient.get(canalCacheKey);
+ if (cachedCanalData) {
+ console.log('✅ Cache hit pour les informations du canal et le dernier message');
+ return json(JSON.parse(cachedCanalData));
+ }
+
+ console.log('❌ Cache miss');
+ // Si non, récupérer les informations du canal et le dernier message depuis Prisma
+ const canal = await prisma.canal.findUnique({
+ where: { id: canalId },
+ include: {
+ users: true, // Inclut les utilisateurs associés au canal
+ },
+ });
+
+ if (!canal) {
+ return json({ error: 'Canal non trouvé' }, { status: 404 });
+ }
+
+ // Récupérer le dernier message
+ const lastMessage = await prisma.message.findFirst({
+ where: { canalId },
+ include: {
+ user: { select: { id: true, pseudo: true } },
+ },
+ orderBy: { createdAt: 'desc' }, // Trie par date décroissante, donc le dernier message est récupéré en premier
+ });
+
+ // Créer un objet combiné pour le canal et le dernier message
+ const canalData = {
+ canal,
+ lastMessage, // Inclure uniquement le dernier message
+ };
+
+ // Mettre en cache les informations du canal et le dernier message pendant 5 minutes
+ await redisClient.set(canalCacheKey, JSON.stringify(canalData), 'EX', 300); // Cache pendant 5 minutes
+
+ console.log('❌ Cache miss - Mise en cache des résultats');
+ return json(canalData);
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur lors de la récupération du canal ou du dernier message' }, { status: 500 });
+ }
+}
+
+// Supprimer un canal et invalider le cache associé
+export async function DELETE({ params }) {
+ const canalId = parseInt(params.id);
+
+ try {
+ // Supprimer le canal de la base de données
+ await prisma.canal.delete({
+ where: { id: canalId },
+ });
+
+ // Invalider le cache
+ await redisClient.del(`canal:${canalId}:info`);
+
+ return json({ message: 'Canal supprimé avec succès' });
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur lors de la suppression du canal' }, { status: 500 });
+ }
+}
+
+// Modifier un canal
+export async function PUT({ params, request }) {
+ const canalId = parseInt(params.id);
+ const { nom, domaine } = await request.json(); // On suppose que ce sont les champs à mettre à jour
+
+ // Clé cache pour les informations du canal et le dernier message
+ const canalCacheKey = `canal:${canalId}:info`;
+
+ try {
+ // Mettre à jour les informations du canal dans la base de données
+ const updatedCanal = await prisma.canal.update({
+ where: { id: canalId },
+ data: {
+ nom, // Nom du canal
+ domaine, // Domaine du canal
+ },
+ include: {
+ users: true, // Inclut les utilisateurs associés au canal
+ },
+ });
+
+ // Récupérer le dernier message associé au canal après mise à jour
+ const lastMessage = await prisma.message.findFirst({
+ where: { canalId },
+ include: {
+ user: { select: { id: true, pseudo: true } },
+ },
+ orderBy: { createdAt: 'desc' },
+ });
+
+ // Créer un objet combiné pour les nouvelles informations du canal et le dernier message
+ const canalData = {
+ canal: updatedCanal,
+ lastMessage, // Inclure uniquement le dernier message
+ };
+
+ // Mettre en cache les nouvelles informations pendant 5 minutes
+ await redisClient.set(canalCacheKey, JSON.stringify(canalData), 'EX', 60 * 5); // Cache pendant 5 minutes
+
+ return json(canalData);
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur lors de la mise à jour du canal' }, { status: 500 });
+ }
+}
diff --git a/src/routes/api/canal/[id]/messages/+page.server.ts b/src/routes/api/canal/[id]/messages/+page.server.ts
new file mode 100644
index 0000000..9fd337b
--- /dev/null
+++ b/src/routes/api/canal/[id]/messages/+page.server.ts
@@ -0,0 +1,149 @@
+import { json } from '@sveltejs/kit';
+import prisma from '$lib/prismaClient';
+import redisClient from '$lib/redisClient'; // Assure-toi d'importer ton client Redis
+
+export async function GET({ params, url }) {
+ const canalId = parseInt(params.id);
+
+ // Gestion de la pagination avec des paramètres optionnels `page` et `limit`
+ const page = parseInt(url.searchParams.get('page')) || 1;
+ const limit = parseInt(url.searchParams.get('limit')) || 10;
+ const offset = (page - 1) * limit;
+
+ // Générer une clé cache Redis unique en fonction du canal et des paramètres de pagination
+ const cacheKey = `canal:${canalId}:messages:page:${page}:limit:${limit}`;
+
+ try {
+ // 1. Vérifier si les messages sont déjà dans le cache Redis
+ const cachedMessages = await redisClient.get(cacheKey);
+ if (cachedMessages) {
+ console.log('✅ Cache hit');
+ return json(JSON.parse(cachedMessages)); // Si les données sont en cache, les retourner
+ }
+
+ // 2. Si les messages ne sont pas en cache, récupérer depuis la base de données
+ const messages = await prisma.message.findMany({
+ where: { canalId },
+ include: {
+ user: {
+ select: { id: true, pseudo: true }, // Inclut uniquement l’ID et le pseudo de l’utilisateur
+ },
+ },
+ orderBy: {
+ createdAt: 'asc', // Trie par date croissante
+ },
+ skip: offset,
+ take: limit,
+ });
+
+ // 3. Compter le nombre total de messages pour la pagination
+ const totalMessages = await prisma.message.count({
+ where: { canalId },
+ });
+
+ const response = {
+ messages,
+ pagination: {
+ page,
+ limit,
+ totalMessages,
+ totalPages: Math.ceil(totalMessages / limit),
+ },
+ };
+
+ // 4. Mettre en cache les messages avec une expiration (par exemple 5 minutes)
+ await redisClient.set(cacheKey, JSON.stringify(response), 'EX', 60 * 5); // Cache pendant 5 minutes
+
+ console.log('❌ Cache miss - Mise en cache des résultats');
+ return json(response); // Retourner les données récupérées
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur lors de la récupération des messages' }, { status: 500 });
+ }
+}
+
+export async function POST({ params, request }) {
+ const canalId = parseInt(params.id);
+ const { userId, text } = await request.json();
+
+ try {
+ // Créer un nouveau message dans la base de données
+ const newMessage = await prisma.message.create({
+ data: {
+ userId,
+ canalId,
+ text,
+ },
+ include: { user: { select: { id: true, pseudo: true } } },
+ });
+
+ updateCaches(); // Mettre à jour les caches après la création d’un nouveau message
+
+ return json(newMessage, { status: 201 });
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur lors de la création du message' }, { status: 500 });
+ }
+}
+
+export async function DELETE({ params }) {
+ const messageId = parseInt(params.id);
+
+ try {
+ // Supprimer le message de la base de données
+ await prisma.message.delete({
+ where: { id: messageId },
+ });
+
+ updateCaches(); // Mettre à jour les caches après la suppression d’un message
+
+ return json({ message: 'Message supprimé avec succès' });
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur lors de la suppression du message' }, { status: 500 });
+ }
+}
+
+// Fonction pour mettre à jour tous les caches des messages
+function updateCaches(canalId) {
+ // Mettre à jour tous les caches
+ // Mettre à jour toutes les pages dans le cache
+ let page : number = 1;
+ let limit : number = 10;
+ let offset : number = (page - 1) * limit;
+ while (true) {
+ const cacheKey = `canal:${canalId}:messages:page:${page}:limit:${limit}`;
+ const cachedMessages = await redisClient.get(cacheKey);
+ if (!cachedMessages) {
+ break;
+ }
+ const totalMessages = await prisma.message.count({
+ where: { canalId },
+ });
+ const messages = await prisma.message.findMany({
+ where: { canalId },
+ include: {
+ user: {
+ select: { id: true, pseudo: true },
+ },
+ },
+ orderBy: {
+ createdAt: 'asc',
+ },
+ skip: offset,
+ take: limit,
+ });
+ const response = {
+ messages,
+ pagination: {
+ page,
+ limit,
+ totalMessages,
+ totalPages: Math.ceil(totalMessages / limit),
+ },
+ };
+ await redisClient.set(cacheKey, JSON.stringify(response), 'EX', 60 * 5);
+ page++;
+ offset = (page - 1) * limit;
+ }
+}
diff --git a/src/routes/api/canals/+page.server.ts b/src/routes/api/canals/+page.server.ts
new file mode 100644
index 0000000..b154366
--- /dev/null
+++ b/src/routes/api/canals/+page.server.ts
@@ -0,0 +1,47 @@
+import { json } from '@sveltejs/kit';
+import prisma from '$lib/prismaClient';
+import redisClient from '$lib/redisClient';
+
+// GET: Liste tous les canaux
+export async function GET() {
+ try {
+ const cachedCanaux = await redisClient.get('canaux');
+ if (cachedCanaux) {
+ console.log('✅ Cache hit');
+ return json(JSON.parse(cachedCanaux));
+ }
+
+ console.log('❌ Cache miss');
+ const canaux = await prisma.canal.findMany({
+ include: { users: true, messages: true }, // Inclut les relations
+ });
+
+ await redisClient.set('canaux', JSON.stringify(canaux), { EX: 600 }); // Met en cache
+ return json(canaux);
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur serveur' }, { status: 500 });
+ }
+}
+
+export async function POST({ request }) {
+ const { nom, domaine, userIds } = await request.json();
+
+ try {
+ const canal = await prisma.canal.create({
+ data: {
+ nom,
+ domaine,
+ users: {
+ connect: userIds.map((id) => ({ id })), // Associe des utilisateurs au canal
+ },
+ },
+ });
+
+ return json(canal, { status: 201 });
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur lors de la création du canal' }, { status: 500 });
+ }
+}
+
diff --git a/src/routes/api/user/[id]/+page.server.ts b/src/routes/api/user/[id]/+page.server.ts
new file mode 100644
index 0000000..5052a08
--- /dev/null
+++ b/src/routes/api/user/[id]/+page.server.ts
@@ -0,0 +1,241 @@
+import { json } from '@sveltejs/kit';
+import redisClient from '$lib/redisClient';
+import prisma from '$lib/prismaClient';
+import multer from 'multer';
+import path from 'path';
+import fs from 'fs';
+
+const destinationDir = '/uploads';
+
+const storage = multer.diskStorage({
+ destination: (req, file, cb) => {
+ cb(null, `.${destinationDir}'); // Dossier où les images sont stockées`
+ },
+ filename: (req, file, cb) => {
+ cb(null, `${Date.now()}-${file.originalname}`);
+ },
+ fileFilter(req, file, cb) {
+ const fileExtension = path.extname(file.originalname).toLowerCase();
+ if (fileExtension !== '.jpg' && fileExtension !== '.jpeg' && fileExtension !== '.png') {
+ return cb(new Error('Seules les images JPG, JPEG et PNG sont autorisées.'));
+ }
+ cb(null, true);
+ }
+});
+
+const upload = multer({ storage });
+
+export async function GET({ params }) {
+ const userId = params.id;
+
+ try {
+ // Vérifier si l'utilisateur est dans le cache Redis
+ const cachedUser = await redisClient.get(`user:${userId}`);
+ if (cachedUser) {
+ console.log('✅ Cache hit');
+ return json(JSON.parse(cachedUser));
+ }
+
+ console.log('❌ Cache miss');
+ // Si non, récupérer depuis MongoDB via Prisma
+ const user = await prisma.user.findUnique({
+ where: { id: parseInt(userId) },
+ });
+
+ if (!user) {
+ return json({ error: 'Utilisateur non trouvé' }, { status: 404 });
+ }
+
+ // Mettre l'utilisateur en cache
+ await redisClient.set(`user:${userId}`, JSON.stringify(user), { EX: 3600 });
+
+ return json(user);
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur serveur' }, { status: 500 });
+ }
+}
+
+export async function POST({ request }) {
+ return new Promise((resolve, reject) => {
+ // Utilisation de multer pour récupérer le fichier
+ upload.single('profilePicture')(request.raw, request.raw, async (err) => {
+ if (err) {
+ console.error('Erreur de téléchargement:', err);
+ return reject(json({ error: 'Erreur lors du téléchargement du fichier' }, { status: 500 }));
+ }
+
+ // Récupérer les données du formulaire (sans le fichier)
+ const { pseudo, nom, prenom, email, password } = await request.json();
+
+ // L'URL de l'image sera le chemin relatif à partir du dossier uploads
+ const imageUrl = request.file ? `${destinationDir}/${request.file.filename}` : null;
+
+ try {
+ // Créer un nouvel utilisateur avec l'URL de l'image
+ const user = await prisma.user.create({
+ data: {
+ pseudo,
+ nom,
+ prenom,
+ email,
+ password,
+ profilePictureUrl: imageUrl, // Stocker l'URL de l'image
+ },
+ });
+
+ // Mettre l'utilisateur dans le cache Redis
+ await redisClient.set(`user:${user.id}`, JSON.stringify(user), { EX: 3600 });
+
+ // Réponse avec les données de l'utilisateur
+ return resolve(json(user, { status: 201 }));
+ } catch (error) {
+ console.error('Erreur lors de la création de l\'utilisateur:', error);
+ return reject(json({ error: 'Erreur lors de la création de l\'utilisateur' }, { status: 500 }));
+ }
+ });
+ });
+}
+
+// Mettre à jour un utilisateur avec PUT
+export async function PUT({ params, request }) {
+ const userId = parseInt(params.id);
+
+ const cachedUser = await redisClient.get(`user:${userId}`);
+ // Récupérer l'utilisateur à partir de la base de données
+ let existingUser;
+
+ if (cachedUser) {
+ console.log('✅ Cache hit');
+ // Si l'utilisateur est dans le cache, on le parse
+ existingUser = JSON.parse(cachedUser);
+ } else {
+ // Si l'utilisateur n'est pas dans le cache, on le récupère de la base de données
+ console.log('❌ Cache miss');
+ existingUser = await prisma.user.findUnique({
+ where: { id: userId },
+ });
+
+ if (!existingUser) {
+ return json({ error: 'Utilisateur non trouvé' }, { status: 404 });
+ }
+
+ // Utilisation de multer pour récupérer l'image (si présente)
+ return new Promise((resolve, reject) => {
+ upload.single('profilePicture')(request.raw, request.raw, async (err) => {
+ if (err) {
+ console.error('Erreur de téléchargement:', err);
+ return reject(json({ error: 'Erreur lors du téléchargement du fichier' }, { status: 500 }));
+ }
+
+ // Extraire les autres données (pseudo, nom, etc.) du body de la requête
+ const { pseudo, nom, prenom, email, password } = await request.json();
+
+ let updatedUserData = {
+ pseudo,
+ nom,
+ prenom,
+ email,
+ password, // Assurez-vous de bien sécuriser les mots de passe
+ };
+
+ // Si une nouvelle image est envoyée
+ if (request.file) {
+ // Vérifiez si l'utilisateur a déjà une image
+ if (existingUser.profilePictureUrl) {
+ // Supprimer l'ancienne image
+ const oldImagePath = `.${destinationDir}/${path.basename(existingUser.profilePictureUrl)}`;
+ if (fs.existsSync(oldImagePath)) {
+ fs.unlinkSync(oldImagePath); // Suppression du fichier
+ }
+ }
+
+ // Ajouter la nouvelle image à la base de données
+ updatedUserData.profilePictureUrl = `${destinationDir}/${request.file.filename}`;
+ } else if (!request.file && existingUser.profilePictureUrl) {
+ // Si aucune image n'est envoyée, supprimer l'image actuelle
+ const oldImagePath = `.${destinationDir}/${path.basename(existingUser.profilePictureUrl)}`;
+ if (fs.existsSync(oldImagePath)) {
+ fs.unlinkSync(oldImagePath); // Supprimer l'ancienne image
+ }
+
+ // Mettre à jour l'URL de l'image en null
+ updatedUserData.profilePictureUrl = null;
+ }
+
+ try {
+ // Mettre à jour l'utilisateur dans la base de données
+ const updatedUser = await prisma.user.update({
+ where: { id: userId },
+ data: updatedUserData,
+ });
+
+ // Mettre à jour l'utilisateur dans le cache Redis
+ await redisClient.set(`user:${userId}`, JSON.stringify(updatedUser), 'EX', 3600); // Cache pendant 1 heure (3600 secondes)
+
+ // Réponse avec l'utilisateur mis à jour
+ return resolve(json(updatedUser));
+ } catch (error) {
+ console.error('Erreur lors de la mise à jour de l\'utilisateur:', error);
+ return reject(json({ error: 'Erreur lors de la mise à jour de l\'utilisateur' }, { status: 500 }));
+ }
+ });
+ });
+}
+
+
+export async function DELETE({ params }) {
+ const userId = parseInt(params.id);
+
+ try {
+ // Vérifier si l'utilisateur est dans le cache Redis
+ const cachedUser = await redisClient.get(`user:${userId}`);
+ let userToDelete;
+
+ if (cachedUser) {
+ console.log('✅ Cache hit');
+ // Si l'utilisateur est dans le cache, on le parse
+ userToDelete = JSON.parse(cachedUser);
+ } else {
+ // Si l'utilisateur n'est pas dans le cache, on le récupère de la base de données
+ console.log('❌ Cache miss');
+ userToDelete = await prisma.user.findUnique({
+ where: { id: userId },
+ });
+
+ if (!userToDelete) {
+ return json({ error: 'Utilisateur non trouvé' }, { status: 404 });
+ }
+
+ // Mettre l'utilisateur dans le cache Redis
+ await redisClient.set(`user:${userId}`, JSON.stringify(userToDelete), { EX: 3600 }); // Cache pendant 1 heure
+ }
+
+ // Si l'utilisateur a une image de profil, la supprimer
+ if (userToDelete.profilePictureUrl) {
+ // Calculer le chemin du fichier à supprimer
+ const imagePath = `.${destinationDir}/${path.basename(userToDelete.profilePictureUrl)}`;
+ if (fs.existsSync(imagePath)) {
+ fs.unlinkSync(imagePath); // Supprimer le fichier image
+ }
+ }
+
+ // Supprimer l'utilisateur de la base de données
+ await prisma.user.delete({
+ where: { id: userId },
+ });
+
+ // Supprimer l'utilisateur du cache Redis
+ await redisClient.del(`user:${userId}`);
+
+ // Réponse après suppression réussie
+ return json({ message: 'Utilisateur et image supprimés avec succès' });
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur lors de la suppression de l’utilisateur' }, { status: 500 });
+ }
+}
+
+
+
+
diff --git a/src/routes/api/users/+page.server.ts b/src/routes/api/users/+page.server.ts
new file mode 100644
index 0000000..0d7b259
--- /dev/null
+++ b/src/routes/api/users/+page.server.ts
@@ -0,0 +1,27 @@
+// src/routes/api/users/+server.js
+import { json } from '@sveltejs/kit';
+import redisClient from '$lib/redisClient';
+import prisma from '$lib/prismaClient';
+
+export async function GET() {
+ try {
+ // Vérifier si les utilisateurs sont dans le cache Redis
+ const cachedUsers = await redisClient.get('users');
+ if (cachedUsers) {
+ console.log('✅ Cache hit');
+ return json(JSON.parse(cachedUsers));
+ }
+
+ console.log('❌ Cache miss');
+ // Sinon, récupérer les utilisateurs depuis MongoDB
+ const users = await prisma.user.findMany();
+
+ // Mettre les utilisateurs en cache
+ await redisClient.set('users', JSON.stringify(users), { EX: 600 });
+
+ return json(users);
+ } catch (err) {
+ console.error(err);
+ return json({ error: 'Erreur serveur' }, { status: 500 });
+ }
+}
diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte
index 6e7ab5b..eacda00 100644
--- a/src/routes/chat/+page.svelte
+++ b/src/routes/chat/+page.svelte
@@ -1,3 +1,53 @@
-
+
+
+
+
+
+
Créer un nouveau chat
+
+
+
+
+
+
+ Créer
+
+
+
+
+
+{#if showAlert}
+ showAlert = false} />
+{/if}
+
+
diff --git a/src/routes/chats/+page.svelte b/src/routes/chats/+page.svelte
new file mode 100644
index 0000000..fabf379
--- /dev/null
+++ b/src/routes/chats/+page.svelte
@@ -0,0 +1,32 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/routes/user/edit/+page.svelte b/src/routes/user/edit/+page.svelte
new file mode 100644
index 0000000..ce54044
--- /dev/null
+++ b/src/routes/user/edit/+page.svelte
@@ -0,0 +1,140 @@
+
+
+
+
+
Modifier les informations du compte
+
+
+ {#if showMessage}
+
+ {message}
+
+ {/if}
+
+
+
+
+
+
+
diff --git a/static/profile-default.svg b/static/profile-default.svg
new file mode 100644
index 0000000..726ac6f
--- /dev/null
+++ b/static/profile-default.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file