| const fs = require('fs'); |
| const path = require('path'); |
| const axios = require('axios'); |
| const fetch = require('node-fetch'); |
| const { logger } = require('@librechat/data-schemas'); |
| const { getFirebaseStorage } = require('@librechat/api'); |
| const { ref, uploadBytes, getDownloadURL, deleteObject } = require('firebase/storage'); |
| const { getBufferMetadata } = require('~/server/utils'); |
|
|
| |
| |
| |
| |
| |
| |
| async function deleteFile(basePath, fileName) { |
| const storage = getFirebaseStorage(); |
| if (!storage) { |
| logger.error('Firebase is not initialized. Cannot delete file from Firebase Storage.'); |
| throw new Error('Firebase is not initialized'); |
| } |
|
|
| const storageRef = ref(storage, `${basePath}/${fileName}`); |
|
|
| try { |
| await deleteObject(storageRef); |
| logger.debug('File deleted successfully from Firebase Storage'); |
| } catch (error) { |
| logger.error('Error deleting file from Firebase Storage:', error.message); |
| throw error; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function saveURLToFirebase({ userId, URL, fileName, basePath = 'images' }) { |
| const storage = getFirebaseStorage(); |
| if (!storage) { |
| logger.error('Firebase is not initialized. Cannot save file to Firebase Storage.'); |
| return null; |
| } |
|
|
| const storageRef = ref(storage, `${basePath}/${userId.toString()}/${fileName}`); |
| const response = await fetch(URL); |
| const buffer = await response.buffer(); |
|
|
| try { |
| await uploadBytes(storageRef, buffer); |
| return await getBufferMetadata(buffer); |
| } catch (error) { |
| logger.error('Error uploading file to Firebase Storage:', error.message); |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function getFirebaseURL({ fileName, basePath = 'images' }) { |
| const storage = getFirebaseStorage(); |
| if (!storage) { |
| logger.error('Firebase is not initialized. Cannot get image URL from Firebase Storage.'); |
| return null; |
| } |
|
|
| const storageRef = ref(storage, `${basePath}/${fileName}`); |
|
|
| try { |
| return await getDownloadURL(storageRef); |
| } catch (error) { |
| logger.error('Error fetching file URL from Firebase Storage:', error.message); |
| return null; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function saveBufferToFirebase({ userId, buffer, fileName, basePath = 'images' }) { |
| const storage = getFirebaseStorage(); |
| if (!storage) { |
| throw new Error('Firebase is not initialized'); |
| } |
|
|
| const storageRef = ref(storage, `${basePath}/${userId}/${fileName}`); |
| await uploadBytes(storageRef, buffer); |
|
|
| |
| return await getFirebaseURL({ fileName, basePath: `${basePath}/${userId}` }); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| function extractFirebaseFilePath(urlString) { |
| try { |
| const url = new URL(urlString); |
| const pathRegex = /\/o\/(.+?)(\?|$)/; |
| const match = url.pathname.match(pathRegex); |
|
|
| if (match && match[1]) { |
| return decodeURIComponent(match[1]); |
| } |
|
|
| return ''; |
| } catch { |
| logger.debug( |
| '[extractFirebaseFilePath] Failed to extract Firebase file path from URL, returning empty string', |
| ); |
| |
| return ''; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const deleteFirebaseFile = async (req, file) => { |
| if (file.embedded && process.env.RAG_API_URL) { |
| const jwtToken = req.headers.authorization.split(' ')[1]; |
| try { |
| await axios.delete(`${process.env.RAG_API_URL}/documents`, { |
| headers: { |
| Authorization: `Bearer ${jwtToken}`, |
| 'Content-Type': 'application/json', |
| accept: 'application/json', |
| }, |
| data: [file.file_id], |
| }); |
| } catch (error) { |
| if (error.response?.status === 404) { |
| logger.warn( |
| `[deleteFirebaseFile] Document ${file.file_id} not found in RAG API, may have been deleted already`, |
| ); |
| } else { |
| logger.error('[deleteFirebaseFile] Error deleting document from RAG API:', error); |
| } |
| } |
| } |
|
|
| const fileName = extractFirebaseFilePath(file.filepath); |
| if (!fileName.includes(req.user.id)) { |
| throw new Error('Invalid file path'); |
| } |
| try { |
| await deleteFile('', fileName); |
| } catch (error) { |
| logger.error('Error deleting file from Firebase:', error); |
| if (error.code === 'storage/object-not-found') { |
| return; |
| } |
| throw error; |
| } |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| async function uploadFileToFirebase({ req, file, file_id }) { |
| const inputFilePath = file.path; |
| const inputBuffer = await fs.promises.readFile(inputFilePath); |
| const bytes = Buffer.byteLength(inputBuffer); |
| const userId = req.user.id; |
| const fileName = `${file_id}__${path.basename(inputFilePath)}`; |
| try { |
| const downloadURL = await saveBufferToFirebase({ userId, buffer: inputBuffer, fileName }); |
| return { filepath: downloadURL, bytes }; |
| } catch (err) { |
| logger.error('[uploadFileToFirebase] Error saving file buffer to Firebase:', err); |
| try { |
| if (file && file.path) { |
| await fs.promises.unlink(file.path); |
| } |
| } catch (unlinkError) { |
| logger.error( |
| '[uploadFileToFirebase] Error deleting temporary file, likely already deleted:', |
| unlinkError.message, |
| ); |
| } |
| throw err; |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| async function getFirebaseFileStream(_req, filepath) { |
| try { |
| const storage = getFirebaseStorage(); |
| if (!storage) { |
| throw new Error('Firebase is not initialized'); |
| } |
|
|
| const response = await axios({ |
| method: 'get', |
| url: filepath, |
| responseType: 'stream', |
| }); |
|
|
| return response.data; |
| } catch (error) { |
| logger.error('Error getting Firebase file stream:', error); |
| throw error; |
| } |
| } |
|
|
| module.exports = { |
| deleteFile, |
| getFirebaseURL, |
| saveURLToFirebase, |
| deleteFirebaseFile, |
| uploadFileToFirebase, |
| saveBufferToFirebase, |
| getFirebaseFileStream, |
| }; |
|
|