Reputation: 147
I'm creating a dynamic page board.
And the url on this page was created through firestore add().
I want to allow users to upload images when they create posts on this bulletin board.
Is there a way to put an image in the firestore field?
If not, should I make them upload images to storage? If so, can I load the image from the page created through add()?
Upvotes: 0
Views: 732
Reputation: 26171
Because you have failed to specify a target language, I'm going to assume you are using the JavaScript Web SDK. In every SDK, add(data)
is syntactic sugar for doc().set(data)
. So to generate a form ID we can use in our file uploads, we can use:
const formRef = firebase.firestore().collection("forms").doc();
Ultimately, when storing images in your database, you have two options.
One of the simplest ways to store a binary file in a document is to make use of a Data URL. These URLs take the binary data, encode it into Base64, add some metadata about the stored data and then produce a string that looks like:
// Data URL for a PNG image of a single #FFFFFF pixel
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAMSURBVBhXY/j//z8ABf4C/qc1gYQAAAAASUVORK5CYII="
These data URLs can then be stored in a document (e.g. /forms/{formId}
) or in one of its subcollections (e.g. /forms/{formId}/attachments/{attachmentId}
). By storing images this way, you inflate the file size by about 25% or more due to Base64 encoding.
Using this method, you gain the ability to control access to the image using Cloud Firestore Security Rules but it also requires that you implement the logic to take the image from the database and insert it into your webpage.
To convert an image that is added to a form to a Data URL, refer to the docs for FileReader#readAsDataURL()
. You can also convert a web canvas to a Data URL as shown in this question thread.
function readFileAsDataURL(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.addEventListener("load", function () { resolve(this.result) }, false);
reader.addEventListener("error", function () { resolve(this.error) }, false);
reader.readAsDataURL(file);
});
}
async function onSubmitForm(e) {
const user = firebase.auth().currentUser;
if (!user) {
setErrorMessage("You must be signed in!");
return false;
}
const uid = user.uid;
const files = document.querySelector('input[type=file]').files;
const dataURLs = files
? await Promise.all([].map.call(files, readFileAsDataURL))
: [];
// prepare database stuff
const db = firebase.firestore();
const batch = db.batch();
// create form reference
const formRef = db.collection("forms").doc();
// assemble & queue upload of each attachment
const attachmentsColRef = formRef.collection("attachments");
const attachmentIds = dataURLs.map((url, i) => {
const file = files[i];
const attachmentRef = attachmentsColRef.doc();
const attachmentData = {
id: attachmentRef.id, // optional
lastModified: firebase.firestore.Timestamp.fromMillis(file.lastModified),
name: file.name,
src: url,
type: file.type
}
batch.set(attachmentRef, attachmentData);
return attachmentRef.id;
}
// assemble form data
const formData = {
attachments: attachmentIds,
content: "this is my first post",
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
id: formRef.id, // optional
name: "john",
title: "Hello world! I'm John",
uid,
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
views: 0
}
// queue upload of form data
batch.set(formRef, formData);
// do the upload
return batch.commit()
.then(() => {
console.log(`Successfully created Form #${formRef.id}!`);
return true;
})
.catch((error) => {
setErrorMessage("Failed to submit form data!");
console.error(`Failed to create Form #${formRef.id}!`, error);
return false;
});
}
This part will focus on Google Cloud Storage, but can also be applied to other app-based file storage services (like Amazon Elastic File System, Amazon S3 Buckets, DigitalOcean Spaces, etc.) and consumer-focused file storage services (like Google Drive, OneDrive, Dropbox, WeTransfer, etc.).
Similar to Cloud Firestore, you can store images in this "database of binary objects" and secure it using Cloud Storage Security Rules. However, in contrast to Cloud Firestore, this storage method is optimized for storing binary data in many different forms and offers many focused features that are specific to file handling like restricting uploaded files, resumed uploads, caching information for browsers, retention policies (e.g. delete all files older than 1 year) and file versioning if you wanted to make use of it.
A file stored in Cloud Storage can be accessed through the Cloud Storage and Firebase SDKs, or via a URL (if a file is private, it will require an access token be provided when accessing the URL).
These URLs are usually of the form:
// public file, in Google Cloud Storage
"https://storage.googleapis.com/BUCKET_NAME/OBJECT_NAME"
// private file, with an access token, in Firebase Cloud Storage
"https://firebasestorage.googleapis.com/v0/b/PROJECT_ID.appspot.com/o/OBJECT_NAME.png?alt=media&token=ACCESS_TOKEN"
async function onSubmitForm(e) {
const user = firebase.auth().currentUser;
if (!user) {
setErrorMessage("You must be signed in!");
return false;
}
const uid = user.uid;
const files = document.querySelector('input[type=file]').files;
// prepare database stuff
const db = firebase.firestore();
const batch = db.batch();
// create form reference
const formRef = db.collection("forms").doc();
// upload and assemble the document for each attachment
const attachmentsColRef = formRef.collection("attachments");
const attachmentIds = !files ? [] : await Promise.all(
[].map.call(files, async (file) => {
const attachmentRef = attachmentsColRef.doc();
const storageRef = firebase.storage()
.ref(`formUploads/${formRef.id}`)
.child(`attachments/${attachmentRef.id}`)
.child(uid)
.child(file.name);
const uploadResult = await storageRef.put(file);
const url = await storageRef.getDownloadURL();
const attachmentData = {
id: attachmentRef.id, // optional
lastModified: firebase.firestore.Timestamp.fromMillis(file.lastModified),
name: file.name,
src: url,
type: file.type
}
batch.set(attachmentRef, attachmentData);
return attachmentRef.id;
})
);
// assemble form data
const formData = {
attachments: attachmentIds,
content: "this is my first post",
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
id: formRef.id, // optional
name: "john",
title: "Hello world! I'm John",
uid,
updatedAt: firebase.firestore.FieldValue.serverTimestamp(),
views: 0
}
// queue upload of form data
batch.set(formRef, formData);
// do the upload
return batch.commit()
.then(() => {
console.log(`Successfully created Form #${formRef.id}!`);
return true;
})
.catch((error) => {
setErrorMessage("Failed to submit form data!");
console.error(`Failed to create Form #${formRef.id}!`, error);
return false;
});
}
Upvotes: 1