| | 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, |
| | }; |
| |
|