Upload Image to Existing doc in Firebase/Firestore v9 with React - javascript

I am currently having trouble uploading an image/file to firestore. I have been stuck on this for hours now. I am using the latest version as well as the latest version of nextJS and reactJS. I am tried copying the docs but using both uploadString and uploadBytesResumable yield no success. I also tried various online tutorials. My rules are set in firestore such that anyone can read or write.
I can upload when there is no image selected in the filePickerRef/imgFile; however, when I include a file input along with my text input, only the text is uploaded. I am using a form with a button and an input to submit the text, when this is submitted, I check if there is a file uploaded (imgFile is a state that gets set when the file input tag is changed -- filePickerRef.current.files[0] should also have the file but neither work...), if there is, I attempt to upload the file to the document I just added the message and timestamp to (Note: this file is uploaded to a file input outside of the form and when the input changes, it calls setImgFile(event.target.files[0]). the input also has the prop ref='filePickerRef' which was generated by the useRef() hook).
The error I get is:
FirebaseError: Firebase Storage: An unknown error occurred, please check the error payload for server response. (storage/unknown)
with the response:
{
"error": {
"code": 404,
"message": "Not Found."
}
}
When I run the web tools debugger, I can see that both imgFile and filePickerRef.current.files[0] have the File object of the file I selected to be uploaded, but the upload still fails.
Here is my code below:
const uploadMessage = (event) => {
event.preventDefault()
// no text input
if (!inputRef.current.value) return
addDoc(collection(db, 'msgs'), {
message: inputRef.current.value,
timestamp: serverTimestamp()
}).then(docRef => {
if (imgFile) {
//const file = filePickerRef.current.files[0]
const file = imgFile
// upload image
const storafeRef = ref(storage, `msgs/${docRef.id}`)
const uploadTask = uploadBytesResumable(storafeRef, file)
// note: imageToPost is state set by a file reader using readAsDataURL() when the file input changes
//uploadString(storafeRef, imageToPost, 'data_url').then(() => console.log('submitted image!'))
uploadTask.on("state_changed",
snapshot => {
const progress = Math.round((snapshot.bytesTransferred / snapshot.totalBytes) * 100)
setProgresspercent(progress)
},
error => {
alert(error)
},
() => {
getDownloadURL(uploadTask.snapshot.ref).then(url => {
setDoc(doc(db, 'msgs', docRef.id), { url }, { merge: true })
})
})
console.log('image uploaded.')
} else {
console.log('no image.')
}
console.log('Added doc: ', docRef.id)
}).then(() => {
inputRef.current.value = ''
setImageToPost(null)
setImgFile(null)
filePickerRef.current.value = ''
console.log('removed image.')
}).catch(error => {
console.error("Error adding document: ", error);
})
}
These are my firestore rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write;
}
}
}

Related

I'm trying to upload image but image is not adding to cloud firestore, its uploaded on firebase storage But not showing in React app

This is the code for uploading image on React app. Its showing on firebase storage but not added in to cloud firestore. and that's why I am not able to upload any image on React app manually. I've googled and searched so many times but cant fix this.
function Imgupload({username}){
**strong text** const [caption,setCaption]=useState('');
const [image,setImage]=useState(null);
//const [url,setUrl]= useState("");
const [progress,setProgress]=useState(0);
const handlechange = (e)=>{
if(e.target.files[0]){
setImage(e.target.files[0]);
}
};
const handleUpload = () =>{
const uploadTask= storage.ref('images/$ {image.name}').put(image);
uploadTask.on(
"state_change",
(snapshot) =>{
//progress function..
const progress=Math.round(
(snapshot.bytesTransferred/snapshot.totalBytes) * 100
);
setProgress(progress);
},
(error) =>{ //error func..
console.log(error);
alert(error.message);
},
()=>{ //complete func..
storage
.ref("images")
.child(image.name)
.getDownloadURL()
.then (url =>{
//post img on db..
db.collection("posts").add({
timestamp:firebase.firestore.FieldValue.serverTimestamp(),
caption :caption,
imgurl :url,
username :username
});
setProgress(0);
setCaption("");
setImage(null);
});
}
);
}
This is the Error from console:
localhost/:1 Uncaught (in promise) FirebaseError: Firebase Storage: Object 'images/Screenshot (202).png' does not exist. (storage/object-not-found)
{
"error": {
"code": 404,
"message": "Not Found."`enter code here`
}
}
Can't fix this problem. please help.
You have an extra space in the file name here:
const uploadTask= storage.ref('images/$ {image.name}').put(image);
// 👆
You don't have that same space when you call getDownloadURL here:
storage
.ref("images")
.child(image.name)
.getDownloadURL()
So these two code fragments point to different files, which explains why the second fragment can't find the file you uploaded.
I recommend using a single variable to hold the reference, and use that in both fragments:
const imageRef = storage
.ref("images")
.child(image.name);
const uploadTask= imageRef.put(image);
...
imageRef.getDownloadURL()...

Firebase Realtime database showing strange behavior

I am using react-native-firebase in an ejected expo app and trying to build a presence detection system in my chat app which will detect that if the message recipient is online and if not when was he/she was last online. The data will be stored as follows in firebase realtime database:
{
lastSeen:[{
[userId]:{
state: boolean
time: serverTimeStamp
}
}]
}
The problem is that firebase console never shows the data and only if recipient is online then app shows this data (even though its not visible in console) but if user is offline then nothing is returned and no error generated. I have set read and write to true in realtimeDB rules. Here is the code I am using:
import database from "#react-native-firebase/database";
export const updateUserLastSeen = (userId) => {
const userStatusDatabaseRef = database().ref("/lastSeen/" + userId);
console.log("updatelast", userId);
userStatusDatabaseRef
.set({
state: true,
time: database.ServerValue.TIMESTAMP,
})
.then(() => console.log("online"))
.catch((e) => console.log(e));
// database()
// .ref(".info/connected")
// .on("value", function (snapshot) {
// if (snapshot.val() == false) {
// return;
// }
userStatusDatabaseRef
.onDisconnect()
.set({
state: false,
time: database.ServerValue.TIMESTAMP,
})
.then(function () {
console.log("disconnect configured");
// userStatusDatabaseRef.set({
// state: true,
// time: database.ServerValue.TIMESTAMP,
// });
});
// });
};
export const checkUserLastSeen = (userId, setUserLastSeen) => {
console.log("check last", userId);
database()
.ref("/lastSeen/" + userId)
.on("value", (snapshot) => {
setUserLastSeen(snapshot.val());
console.log("User data: ", snapshot.val());
});
console.log("after check last");
};
I tried both the code from firebase docs and rnfirebase docs. In above code, none of the "then" or "catch" functions get called in updateUserLastSeen but in checkUserLastSeen "on" is invoked only if bearer of userId is online. Also, I am using realtime db only for this purpose and cloud firestore for other data storing and its working fine.
Any help would be appreciated. Thanks.
If neither then nor catch of a write is called, it typically means that the client is not connected to the server.
I recommend checking to make sure your app has a network connection, and that you've configured the (correct) URL for your database.

VueJS and Firebase Storage: How to wait until image done uploading before submitting URI to database?

I have a comment system I built that allows a user to add an image along with their comment.
I am trying to wait until an image upload is finished before adding a comment to firestore, but my attempt is not working. I have a method named photoUpload() that uploads the image to firebase storage. That method contains an uploadTask listener for progress details. However, my comment is being added to the database before the image is done uploading.
How to delay and wait until it's finished before submitting the comment?
Here's my code:
data function:
data() {
return {
text: '',
image: null,
overlayShow: false,
progress: 0,
downloadUrl: null
}
},
Here is my image upload task:
photoUpload() {
this.filename = uuidv4()
const storageRef = this.$fireStorage.ref()
this.photoRef = storageRef.child(
`photos/${this.userProfile.uid}/commentPhotos/${this.filename}`
)
// uploads string data to this reference's location
const uploadTask = this.photoRef.putString(this.image, 'data_url')
// set the callbacks for each event
const next = (uploadTaskSnapshot) => {
this.progress =
(uploadTaskSnapshot.bytesTransferred /
uploadTaskSnapshot.totalBytes) *
100
console.log('Upload is ' + this.progress + '% done')
}
const error = (error) => {
...snijp...
}
const complete = async () => {
// Upload completed successfully, now we can get the download URL
this.downloadUrl = await uploadTask.snapshot.ref.getDownloadURL()
}
// listens for events on this task
uploadTask.on(
// 3 callbacks available for each event
this.$fireStorageObj.TaskEvent.STATE_CHANGED,
{
next,
error,
complete
}
)
}
To add a comment to firestore, I run this method:
async addComment() {
this.overlayShow = true
if (this.hasImage) {
this.photoUpload() // <---------I need to wait on this to finish!
}
try {
console.log(this.downloadUrl) //<-----this is returning null even after image is uploaded
// create comment
const docRef = this.$fireStore.collection('comments').doc()
await docRef.set({
createdAt: this.$fireStoreObj.FieldValue.serverTimestamp(),
id: docRef.id,
content: this.text,
attachment: this.downloadUrl, //<---- because photo upload is not finished, this gets null
})
console.log('comment added!')
// update comment count on photo doc
await this.$fireStore
.collection('photos')
.doc(this.photo.id)
.set(
{
comments: this.$fireStoreObj.FieldValue.increment(1)
},
{ merge: true }
)
this.text = ''
this.downloadUrl = null
this.clearImage()
this.overlayShow = false
} catch (error) {
console.error('Error adding new comment', error)
}
}
You should make uploadComplete async, and return a promise that resolves only after the upload is complete and the download URL is available. Since all of its work is asynchronous, you must build a way for the caller to know that, otherwise the function will return immediately before anything is complete.
It might be easier if you also await the await the uploadTask (it acts like a promise) to know when it's complete, instead of using the callbacks.

GraphQL: Error when resolving promises during file upload

So I've been working with GraphQL uploads, and before stating my problem here's an overview for the tech stack that I am using:
Backend: Mongoose, Express, Apollo, GraphQL
Frontend: VueJS, Apollo, GraphQL
I'm using Apollo Upload Client to send the Upload files to the server side from the client. Since I am sending a list of files type scalar Upload from the client, I am receiving a list of promises that need to be resolved. On using Promise.all() I am getting the following error (which, weirdly, I wasn't getting before and I don't know why). If I upload more than one file, the first file just gets lost somewhere and the second file uploads.... But this isn't all the time. Sometimes it doesn't happen. Maybe I am not resolving or catering to the promises properly. Note that I also have to save the file name in MongoDB through Mongoose
{ BadRequestError: Request disconnected during file upload stream parsing.
at IncomingMessage.request.once (F:\repos\pushbox\node_modules\graphql-upload\lib\processRequest.js:245:35)
at Object.onceWrapper (events.js:285:13)
at IncomingMessage.emit (events.js:197:13)
at resOnFinish (_http_server.js:583:7)
at ServerResponse.emit (events.js:202:15)
at onFinish (_http_outgoing.js:683:10)
at processTicksAndRejections (internal/process/next_tick.js:74:9)
message: 'Request disconnected during file upload stream parsing.',
expose: true,
statusCode: 499,
status: 499 }
I have an HTML file input tag that takes multiple files and the mutation I use is:
async uploadFiles() {
// Check if input tag is empty
if (this.files.length === 0) {
this.uploadErrorAlert = true;
return;
}
// Mutation
this.isUploading = true;
await this.$apollo.mutate({
mutation: UPLOAD_FILES,
variables: {
files: this.files,
id: this.selectedCard.id,
},
})
.then(() => {
// clear files from the input tag
this.files = '';
this.$refs.selectedFiles.value = '';
this.isUploading = false;
})
.catch((err) => {
console.error(err);
});
},
And finally, the resolver on the server is:
/**
* Uploads files sent on disk and saves
* the file names in the DB
*
* #param {Object} attachments - List of files for a card
*
* #return {Boolean} - true if upload is
* successful
*/
uploadFiles: async (_, attachments, { controllers }) => {
Promise.all(attachments.files.map(async (file) => {
const { createReadStream, filename } = await file;
const stream = createReadStream();
/**
* We need unique names for every file being uploaded,
* so we use the ID generated by MongoDB and concat it
* to the filename sent by the user.
*
* Therefore we instantiate an attachment object to get an ID
*/
const attachment = await controllers.attachment.add({ id: attachments.id, file: '' });
const newFileName = `${attachment.id}_${filename}`;
const path = `${process.env.UPLOAD_DIR}/${newFileName}`;
await controllers.attachment.update({
id: attachment.id,
file: newFileName,
});
console.log(`reached for ${path}`);
// Attempting to save file in server
return new Promise((resolve, reject) => stream
.pipe(createWriteStream(path))
.on('finish', () => resolve())
.on('error', (error) => {
console.log('dude?');
if (stream.truncated) {
// Delete the truncated file
unlinkSync(path);
}
reject(error);
}));
})).then(() => {
pubsub.publish(ATTACHMENTS_ADDED, { attachmentsChanged: controllers.attachment.getAll() });
}).catch((err) => {
console.log(err);
});
},
Any help would be appreciated!
Okay so I don't know how I missed this issue here, but this right there is the solution! The issue is on the module's, that I am using, github issue forum.
So the problem is solved by using await before the Promise.all() function. So now the code inside the uploadFiles resolver looks like:
await Promise.all(attachments.files.map(async (file) => {
const { createReadStream, filename } = await file;
const stream = createReadStream();
/**
* We need unique names for every file being uploaded,
* so we use the ID generated by MongoDB and concat it
* to the filename sent by the user.
*
* Therefore we instantiate an attachment object to get an ID
*/
const attachment = await controllers.attachment.add({ id: attachments.id, file: '' });
const newFileName = `${attachment.id}_${filename}`;
const path = `${process.env.UPLOAD_DIR}/${newFileName}`;
await controllers.attachment.update({
id: attachment.id,
file: newFileName,
});
console.log(`reached for ${path}`);
// Attempting to save file in server
return new Promise((resolve, reject) => stream
.pipe(createWriteStream(path))
.on('finish', () => resolve())
.on('error', (error) => {
console.log('dude?');
if (stream.truncated) {
// Delete the truncated file
unlinkSync(path);
}
reject(error);
}));
})).then(() => {
pubsub.publish(ATTACHMENTS_ADDED, { attachmentsChanged: controllers.attachment.getAll() });
}).catch((err) => {
console.log(err);
});

Return data ID upon uploading Image in angular ts and firebase

Im uploading an image using angularTS and firebase. my plan is to upload an image and saved the following data. after that I expect a return ID from the firebase database callback. problem is, I can't get the data because it return to me like this.
__zone_symbol__state : true
__zone_symbol__value : "viHnY2OpUnJkO2VbfgwJ"
__proto__ : Object
this is my code for uploading image.
this.venueSvc.setVenuePhoto(this.venue, this.photo[0])
.subscribe(snapshot => {
console.log(snapshot);
console.log(snapshot.__zone_symbol__value);
console.log(snapshot.value);
});
and this is my service to upload the image to firebase database.
setVenuePhoto(venue: any, photo: File): Observable<any> {
console.log(venue);
let subjectTemp: any;
let uploadPhoto: any;
const uploadTask = firebase.storage()
.ref().child(this.PATH_VENUES + '/' + photo.name)
.put(photo);
uploadTask.on('state_changed', snapshot => {
uploadPhoto = snapshot;
let progress = Math.floor((uploadPhoto.bytesTransferred / uploadPhoto.totalBytes) * 100);
}, error => {
console.log(error);
}, () => {
venue.dateCreated = firebase.firestore.FieldValue.serverTimestamp();
venue.dateUpdated = firebase.firestore.FieldValue.serverTimestamp();
venue.venuePhoto = uploadPhoto.task.snapshot.downloadURL;
subjectTemp = this.venuesCollection.add(<Venue>venue.getData()).then(value => {
return value.id
});
this.viewUploadSubject.next(subjectTemp);
});
return this.viewUploadSubject;
}
I should be able to get the ID because im gonna need it to update the venue. Any suggestion would be appreciated.
You are uploading your image to Storage, not Firebase. They are two different services, and Storage doesn't provide and ID for file that you update.
If you want to reference an file from Storage, you can just save It's path.
Or you can save an reference for this file on Firebase so that you can get references from files more easily.
You could save the downloadUrl in the database instead of the id
uploadPhoto.task.snapshot.downloadURL
I don't know the structure of the rest of your app but you could save the downloadURL either update the db in your complete method on the upload
uploadTask.on('state_changed', progress, error, complete);
or save it the downloadUrl in state and after the upload save the url to the database

Categories

Resources