mirror of
https://git.v0id.ovh/n3wt-innov/n3wt-school.git
synced 2026-04-06 05:01:25 +00:00
fix: messagerie
This commit is contained in:
@ -72,11 +72,11 @@ class ChatConsumer(AsyncWebsocketConsumer):
|
|||||||
if presence:
|
if presence:
|
||||||
await self.broadcast_presence_update(self.user_id, 'online')
|
await self.broadcast_presence_update(self.user_id, 'online')
|
||||||
|
|
||||||
|
await self.accept()
|
||||||
|
|
||||||
# Envoyer les statuts de présence existants des autres utilisateurs connectés
|
# Envoyer les statuts de présence existants des autres utilisateurs connectés
|
||||||
await self.send_existing_user_presences()
|
await self.send_existing_user_presences()
|
||||||
|
|
||||||
await self.accept()
|
|
||||||
|
|
||||||
logger.info(f"User {self.user_id} connected to chat")
|
logger.info(f"User {self.user_id} connected to chat")
|
||||||
|
|
||||||
async def send_existing_user_presences(self):
|
async def send_existing_user_presences(self):
|
||||||
|
|||||||
@ -34,12 +34,13 @@ import Footer from '@/components/Footer';
|
|||||||
import MobileTopbar from '@/components/MobileTopbar';
|
import MobileTopbar from '@/components/MobileTopbar';
|
||||||
import { RIGHTS } from '@/utils/rights';
|
import { RIGHTS } from '@/utils/rights';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
|
import { useChatConnection } from '@/context/ChatConnectionContext';
|
||||||
|
|
||||||
export default function Layout({ children }) {
|
export default function Layout({ children }) {
|
||||||
const t = useTranslations('sidebar');
|
const t = useTranslations('sidebar');
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
const { profileRole, establishments, clearContext } =
|
const { profileRole, establishments, clearContext } = useEstablishment();
|
||||||
useEstablishment();
|
const { totalUnreadCount, resetUnreadCount } = useChatConnection();
|
||||||
|
|
||||||
const sidebarItems = {
|
const sidebarItems = {
|
||||||
admin: {
|
admin: {
|
||||||
@ -83,6 +84,7 @@ export default function Layout({ children }) {
|
|||||||
name: t('messagerie'),
|
name: t('messagerie'),
|
||||||
url: FE_ADMIN_MESSAGERIE_URL,
|
url: FE_ADMIN_MESSAGERIE_URL,
|
||||||
icon: MessageSquare,
|
icon: MessageSquare,
|
||||||
|
badge: totalUnreadCount,
|
||||||
},
|
},
|
||||||
feedback: {
|
feedback: {
|
||||||
id: 'feedback',
|
id: 'feedback',
|
||||||
@ -119,7 +121,11 @@ export default function Layout({ children }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fermer la sidebar quand on change de page sur mobile
|
// Fermer la sidebar quand on change de page sur mobile
|
||||||
setIsSidebarOpen(false);
|
setIsSidebarOpen(false);
|
||||||
}, [pathname]);
|
// Réinitialiser le compteur non lu quand on ouvre la messagerie
|
||||||
|
if (pathname?.includes('/messagerie')) {
|
||||||
|
resetUnreadCount();
|
||||||
|
}
|
||||||
|
}, [pathname, resetUnreadCount]);
|
||||||
|
|
||||||
// Filtrage dynamique des items de la sidebar selon le rôle
|
// Filtrage dynamique des items de la sidebar selon le rôle
|
||||||
let sidebarItemsToDisplay = Object.values(sidebarItems);
|
let sidebarItemsToDisplay = Object.values(sidebarItems);
|
||||||
|
|||||||
@ -4,10 +4,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import Sidebar from '@/components/Sidebar';
|
import Sidebar from '@/components/Sidebar';
|
||||||
import { useRouter, usePathname } from 'next/navigation';
|
import { useRouter, usePathname } from 'next/navigation';
|
||||||
import { MessageSquare, Settings, Home } from 'lucide-react';
|
import { MessageSquare, Settings, Home } from 'lucide-react';
|
||||||
import {
|
import { FE_PARENTS_HOME_URL, FE_PARENTS_MESSAGERIE_URL } from '@/utils/Url';
|
||||||
FE_PARENTS_HOME_URL,
|
|
||||||
FE_PARENTS_MESSAGERIE_URL
|
|
||||||
} from '@/utils/Url';
|
|
||||||
import ProtectedRoute from '@/components/ProtectedRoute';
|
import ProtectedRoute from '@/components/ProtectedRoute';
|
||||||
import { disconnect } from '@/app/actions/authAction';
|
import { disconnect } from '@/app/actions/authAction';
|
||||||
import Popup from '@/components/Popup';
|
import Popup from '@/components/Popup';
|
||||||
@ -15,6 +12,7 @@ import MobileTopbar from '@/components/MobileTopbar';
|
|||||||
import { RIGHTS } from '@/utils/rights';
|
import { RIGHTS } from '@/utils/rights';
|
||||||
import { useEstablishment } from '@/context/EstablishmentContext';
|
import { useEstablishment } from '@/context/EstablishmentContext';
|
||||||
import Footer from '@/components/Footer';
|
import Footer from '@/components/Footer';
|
||||||
|
import { useChatConnection } from '@/context/ChatConnectionContext';
|
||||||
|
|
||||||
export default function Layout({ children }) {
|
export default function Layout({ children }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -22,6 +20,7 @@ export default function Layout({ children }) {
|
|||||||
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
const [isPopupVisible, setIsPopupVisible] = useState(false);
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||||
const { clearContext } = useEstablishment();
|
const { clearContext } = useEstablishment();
|
||||||
|
const { totalUnreadCount, resetUnreadCount } = useChatConnection();
|
||||||
const softwareName = 'N3WT School';
|
const softwareName = 'N3WT School';
|
||||||
const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
|
const softwareVersion = `${process.env.NEXT_PUBLIC_APP_VERSION}`;
|
||||||
|
|
||||||
@ -41,7 +40,8 @@ export default function Layout({ children }) {
|
|||||||
name: 'Messagerie',
|
name: 'Messagerie',
|
||||||
url: FE_PARENTS_MESSAGERIE_URL,
|
url: FE_PARENTS_MESSAGERIE_URL,
|
||||||
icon: MessageSquare,
|
icon: MessageSquare,
|
||||||
}
|
badge: totalUnreadCount,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Déterminer la page actuelle pour la sidebar
|
// Déterminer la page actuelle pour la sidebar
|
||||||
@ -70,7 +70,11 @@ export default function Layout({ children }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fermer la sidebar quand on change de page sur mobile
|
// Fermer la sidebar quand on change de page sur mobile
|
||||||
setIsSidebarOpen(false);
|
setIsSidebarOpen(false);
|
||||||
}, [pathname]);
|
// Réinitialiser le compteur non lu quand on ouvre la messagerie
|
||||||
|
if (pathname?.includes('/messagerie')) {
|
||||||
|
resetUnreadCount();
|
||||||
|
}
|
||||||
|
}, [pathname, resetUnreadCount]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProtectedRoute requiredRight={RIGHTS.PARENT}>
|
<ProtectedRoute requiredRight={RIGHTS.PARENT}>
|
||||||
|
|||||||
@ -80,12 +80,7 @@ export default function Page() {
|
|||||||
return <Loader />; // Affichez le composant Loader
|
return <Loader />; // Affichez le composant Loader
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="min-h-screen flex items-center justify-center p-4 bg-neutral">
|
||||||
className="min-h-screen flex items-center justify-center p-4"
|
|
||||||
style={{
|
|
||||||
background: 'linear-gradient(135deg, #80fdd6 100%, #2bb180 100%)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="bg-white rounded-md border border-gray-200 shadow-sm p-8 w-full max-w-md">
|
<div className="bg-white rounded-md border border-gray-200 shadow-sm p-8 w-full max-w-md">
|
||||||
<div className="flex justify-center mb-6">
|
<div className="flex justify-center mb-6">
|
||||||
<Logo className="h-150 w-150" />
|
<Logo className="h-150 w-150" />
|
||||||
|
|||||||
@ -10,7 +10,7 @@ export default function Footer({ softwareName, softwareVersion }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="text-sm font-light flex items-center justify-between">
|
<div className="text-sm font-light flex items-center justify-between">
|
||||||
<div className="text-sm font-light mr-4">
|
<div className="text-sm font-light mr-4">
|
||||||
{softwareName} - {softwareVersion}
|
{softwareName} - {softwareVersion}
|
||||||
</div>
|
</div>
|
||||||
<Logo className="w-8 h-8" />
|
<Logo className="w-8 h-8" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,14 +3,21 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { useRouter } from 'next/navigation';
|
import { useRouter } from 'next/navigation';
|
||||||
import ProfileSelector from '@/components/ProfileSelector';
|
import ProfileSelector from '@/components/ProfileSelector';
|
||||||
|
|
||||||
const SidebarItem = ({ icon: Icon, text, active, url, onClick }) => (
|
const SidebarItem = ({ icon: Icon, text, active, url, onClick, badge }) => (
|
||||||
<div
|
<div
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={`flex items-center gap-3 px-2 py-2 rounded-md cursor-pointer hover:bg-primary/10 ${
|
className={`flex items-center gap-3 px-2 py-2 rounded-md cursor-pointer hover:bg-primary/10 ${
|
||||||
active ? 'bg-primary/5 text-primary' : 'text-gray-600'
|
active ? 'bg-primary/5 text-primary' : 'text-gray-600'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Icon size={20} />
|
<div className="relative shrink-0">
|
||||||
|
<Icon size={20} />
|
||||||
|
{badge > 0 && (
|
||||||
|
<span className="absolute -top-1.5 -right-1.5 min-w-[16px] h-4 px-0.5 bg-red-500 text-white text-[10px] font-label font-semibold rounded-full flex items-center justify-center leading-none">
|
||||||
|
{badge > 99 ? '99+' : badge}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<span>{text}</span>
|
<span>{text}</span>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -45,6 +52,7 @@ function Sidebar({ currentPage, items, onCloseMobile }) {
|
|||||||
text={item.name}
|
text={item.name}
|
||||||
active={item.id === selectedItem}
|
active={item.id === selectedItem}
|
||||||
url={item.url}
|
url={item.url}
|
||||||
|
badge={item.badge}
|
||||||
onClick={() => handleItemClick(item.url)}
|
onClick={() => handleItemClick(item.url)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -6,9 +6,12 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useSession, getSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
import { WS_CHAT_URL } from '@/utils/Url';
|
import {
|
||||||
|
WS_CHAT_URL,
|
||||||
|
BE_GESTIONMESSAGERIE_CONVERSATIONS_URL,
|
||||||
|
} from '@/utils/Url';
|
||||||
|
|
||||||
const ChatConnectionContext = createContext();
|
const ChatConnectionContext = createContext();
|
||||||
|
|
||||||
@ -22,6 +25,8 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
const [reconnectAttempts, setReconnectAttempts] = useState(0);
|
||||||
const [currentUserId, setCurrentUserId] = useState(null);
|
const [currentUserId, setCurrentUserId] = useState(null);
|
||||||
const maxReconnectAttempts = 5;
|
const maxReconnectAttempts = 5;
|
||||||
|
const isConnectedRef = useRef(false);
|
||||||
|
const [totalUnreadCount, setTotalUnreadCount] = useState(0);
|
||||||
|
|
||||||
// Système de callbacks pour les messages
|
// Système de callbacks pour les messages
|
||||||
const messageCallbacksRef = useRef(new Set());
|
const messageCallbacksRef = useRef(new Set());
|
||||||
@ -54,15 +59,13 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Configuration WebSocket
|
// Configuration WebSocket
|
||||||
const getWebSocketUrl = async (userId) => {
|
const getWebSocketUrl = (userId) => {
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
logger.warn('ChatConnection: No user ID provided for WebSocket URL');
|
logger.warn('ChatConnection: No user ID provided for WebSocket URL');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forcer un refresh de session pour obtenir un token JWT valide
|
const token = session?.user?.token;
|
||||||
const freshSession = await getSession();
|
|
||||||
const token = freshSession?.user?.token;
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
@ -71,11 +74,8 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construire l'URL WebSocket avec le token
|
|
||||||
const baseUrl = WS_CHAT_URL(userId);
|
const baseUrl = WS_CHAT_URL(userId);
|
||||||
const wsUrl = `${baseUrl}?token=${encodeURIComponent(token)}`;
|
return `${baseUrl}?token=${encodeURIComponent(token)}`;
|
||||||
|
|
||||||
return wsUrl;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Connexion WebSocket
|
// Connexion WebSocket
|
||||||
@ -108,7 +108,7 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
setConnectionStatus('connecting');
|
setConnectionStatus('connecting');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const wsUrl = await getWebSocketUrl(userIdToUse);
|
const wsUrl = getWebSocketUrl(userIdToUse);
|
||||||
|
|
||||||
if (!wsUrl) {
|
if (!wsUrl) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@ -123,12 +123,15 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
'ChatConnection: Connected successfully for user:',
|
'ChatConnection: Connected successfully for user:',
|
||||||
userIdToUse
|
userIdToUse
|
||||||
);
|
);
|
||||||
|
isConnectedRef.current = true;
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
setConnectionStatus('connected');
|
setConnectionStatus('connected');
|
||||||
setReconnectAttempts(0);
|
setReconnectAttempts(0);
|
||||||
|
fetchInitialUnreadCount();
|
||||||
};
|
};
|
||||||
|
|
||||||
websocketRef.current.onclose = (event) => {
|
websocketRef.current.onclose = (event) => {
|
||||||
|
isConnectedRef.current = false;
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
setConnectionStatus('disconnected');
|
setConnectionStatus('disconnected');
|
||||||
|
|
||||||
@ -149,6 +152,7 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
websocketRef.current.onerror = (error) => {
|
websocketRef.current.onerror = (error) => {
|
||||||
logger.error('ChatConnection: WebSocket error', error);
|
logger.error('ChatConnection: WebSocket error', error);
|
||||||
setConnectionStatus('error');
|
setConnectionStatus('error');
|
||||||
|
isConnectedRef.current = false;
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -161,6 +165,17 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
handlePresenceUpdate(data);
|
handlePresenceUpdate(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Incrémenter le compteur de messages non lus si c'est un message entrant
|
||||||
|
if (data.type === 'new_message' && data.message) {
|
||||||
|
const senderId = String(
|
||||||
|
data.message.sender?.id ?? data.message.sender_id ?? ''
|
||||||
|
);
|
||||||
|
const currentId = String(session?.user?.user_id ?? '');
|
||||||
|
if (senderId && currentId && senderId !== currentId) {
|
||||||
|
setTotalUnreadCount((prev) => prev + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Notifier tous les callbacks enregistrés
|
// Notifier tous les callbacks enregistrés
|
||||||
notifyMessageCallbacks(data);
|
notifyMessageCallbacks(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -170,6 +185,7 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('ChatConnection: Error creating WebSocket', error);
|
logger.error('ChatConnection: Error creating WebSocket', error);
|
||||||
setConnectionStatus('error');
|
setConnectionStatus('error');
|
||||||
|
isConnectedRef.current = false;
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -186,6 +202,7 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
websocketRef.current = null;
|
websocketRef.current = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isConnectedRef.current = false;
|
||||||
setIsConnected(false);
|
setIsConnected(false);
|
||||||
setConnectionStatus('disconnected');
|
setConnectionStatus('disconnected');
|
||||||
setReconnectAttempts(0);
|
setReconnectAttempts(0);
|
||||||
@ -207,24 +224,51 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
// Obtenir la référence WebSocket pour les composants qui en ont besoin
|
// Obtenir la référence WebSocket pour les composants qui en ont besoin
|
||||||
const getWebSocket = () => websocketRef.current;
|
const getWebSocket = () => websocketRef.current;
|
||||||
|
|
||||||
|
// Réinitialiser le compteur de messages non lus (ex: quand on ouvre la messagerie)
|
||||||
|
const resetUnreadCount = useCallback(() => {
|
||||||
|
setTotalUnreadCount(0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Récupérer le total initial des messages non lus depuis l'API
|
||||||
|
const fetchInitialUnreadCount = useCallback(async () => {
|
||||||
|
const token = session?.user?.token;
|
||||||
|
if (!token) return;
|
||||||
|
try {
|
||||||
|
const resp = await fetch(BE_GESTIONMESSAGERIE_CONVERSATIONS_URL, {
|
||||||
|
credentials: 'include',
|
||||||
|
headers: { Authorization: `Bearer ${token}` },
|
||||||
|
});
|
||||||
|
if (!resp.ok) return;
|
||||||
|
const data = await resp.json();
|
||||||
|
const conversations = Array.isArray(data) ? data : (data.results ?? []);
|
||||||
|
const total = conversations.reduce(
|
||||||
|
(sum, conv) => sum + (conv.unread_count || 0),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
setTotalUnreadCount(total);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(
|
||||||
|
'ChatConnection: impossible de récupérer le compteur non lu initial',
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [session?.user?.token]);
|
||||||
|
|
||||||
// Effet pour la gestion de la session et connexion automatique
|
// Effet pour la gestion de la session et connexion automatique
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Si la session change vers authenticated et qu'on a un user_id, essayer de se connecter
|
if (
|
||||||
if (status === 'authenticated' && session?.user?.user_id && !isConnected) {
|
status === 'authenticated' &&
|
||||||
|
session?.user?.user_id &&
|
||||||
|
!isConnectedRef.current
|
||||||
|
) {
|
||||||
connectToChat(session.user.user_id);
|
connectToChat(session.user.user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si la session devient unauthenticated, déconnecter
|
if (status === 'unauthenticated' && isConnectedRef.current) {
|
||||||
if (status === 'unauthenticated' && isConnected) {
|
|
||||||
disconnectFromChat();
|
disconnectFromChat();
|
||||||
}
|
}
|
||||||
}, [
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
status,
|
}, [status, session?.user?.user_id]);
|
||||||
session?.user?.user_id,
|
|
||||||
isConnected,
|
|
||||||
connectToChat,
|
|
||||||
disconnectFromChat,
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Nettoyage à la destruction du composant
|
// Nettoyage à la destruction du composant
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -241,14 +285,16 @@ export const ChatConnectionProvider = ({ children }) => {
|
|||||||
const value = {
|
const value = {
|
||||||
isConnected,
|
isConnected,
|
||||||
connectionStatus,
|
connectionStatus,
|
||||||
userPresences, // Ajouter les présences utilisateur
|
userPresences,
|
||||||
connectToChat,
|
connectToChat,
|
||||||
disconnectFromChat,
|
disconnectFromChat,
|
||||||
sendMessage,
|
sendMessage,
|
||||||
getWebSocket,
|
getWebSocket,
|
||||||
reconnectAttempts,
|
reconnectAttempts,
|
||||||
maxReconnectAttempts,
|
maxReconnectAttempts,
|
||||||
addMessageCallback, // Ajouter cette fonction
|
addMessageCallback,
|
||||||
|
totalUnreadCount,
|
||||||
|
resetUnreadCount,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { logger } from '@/utils/logger';
|
|
||||||
import { getToken } from 'next-auth/jwt';
|
import { getToken } from 'next-auth/jwt';
|
||||||
|
|
||||||
const BACKEND_URL = process.env.NEXT_PUBLIC_API_URL;
|
const BACKEND_URL = process.env.NEXT_PUBLIC_API_URL;
|
||||||
@ -49,7 +48,6 @@ export default async function handler(req, res) {
|
|||||||
const buffer = Buffer.from(await backendRes.arrayBuffer());
|
const buffer = Buffer.from(await backendRes.arrayBuffer());
|
||||||
return res.send(buffer);
|
return res.send(buffer);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Download proxy error:', error);
|
|
||||||
return res.status(500).json({ error: 'Erreur lors du téléchargement' });
|
return res.status(500).json({ error: 'Erreur lors du téléchargement' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user