Cloud Functions for Firebaseで画像を保存したい場合になかなかうまくいかず、QiitaやZenn上での情報も少なかったため、記事を残します。
下記コードはいずれもNode.js(JavaScript)で記載します。
はじめに
そもそもFirestore, Realtime Database, Cloud SQL等では画像を保存できないため、Cloud Storageに保存して、その場所を各DBに保存します。
取得した画像をCloud Storageに保存する
const admin = require("firebase-admin");
// jpgイメージの場合
const uploadImage = async (Id, image) => {
const storage = admin.storage();
const imageBuffer = Buffer.from(image, "binary");
const fileName = "testfolder/testImage.jpg";
const file = storage.bucket().file(fileName);
// ストレージに画像を保存
await file.save(imageBuffer, {metadata : {contentType : `image/jpeg`}});
// firestoreに画像の場所を保存
await admin.firestore().collection("Ids").doc(Id).update({
"image" : fileName
});
}
firebase-adminのstorage()を利用して、saveするだけです。
httpsリクエストから画像を受け取り、Cloud Storageに画像を保存する
firebase-functionsのhttps関数を使用して、multipart/form-dataで送信された画像を保存したい場合、busboyメソッドを使用することで保存することができます。
const functions = require("firebase-functions/v2");
const admin = require("firebase-admin");
const busboy = require("busboy");
exports.image = functions.https.onRequest(async (req, res) => {
const storage = admin.storage();
const bucket = storage.bucket();
try {
let fileName;
const bb = busboy({ headers: req.headers });
const busboyPromise = new Promise((resolve, reject) => {
bb.on("file", (name, stream, info) => {
fileName = info.filename;
const distPath = bucket.file(fileName);
// busboyのストリームを接続し、Cloud Storage上に保存
stream.pipe(distPath.createWriteStream()).on("close", () => {
resolve();
})
.on("error", (error) => {
console.log("File processed error", error);
reject();
});
});
bb.end(req.rawBody);
});
await busboyPromise;
// ファイル名をFirestore上に保存
const Id = req.query.Id;
await admin.firestore().collection("Ids").doc(Id).update({
"image" : fileName
});
res.status(200).send("image store success!");
} catch (error) {
res.status(400).send(error);
}
});
当初はAPIでmultipart/form-data形式で送った画像をsave()メソッドで保存しようとしていたのですが、うまくいかず苦戦していました。。
一応busboyを使用することはGoogle Cloudのドキュメントにも書いています。なかなか気づけなかったってのが正直な感想ですが。
Cloud Storageの画像を取得する
fileNameからdownload()するだけです。
const admin = require("firebase-admin");
const getImage = async (fileName) => {
const storage = admin.storage();
const [image] = await storage.bucket().file(fileName).download();
return image;
}
おまけ:mimeTypeを判定する
buffer型のデータからmimeTypeを判断する関数も載せておきます。
file-typeというライブラリがあるのですが、CommonJSでは使用できず、自作して使用していました。
exports.checkFileType = (byteArray) => {
const patterns = {
jpeg: [0xFF, 0xD8, 0xFF],
png: [0x89, 0x50, 0x4E, 0x47],
gif: [0x47, 0x49, 0x46],
svg: [0x3C, 0x73, 0x76, 0x67],
// Add more patterns for other file types if needed
};
for (const [format, pattern] of Object.entries(patterns)) {
let match = true;
for (let i = 0; i < pattern.length; i++) {
if (pattern[i] !== byteArray[i]) {
match = false;
break;
}
}
if (match) {
return format;
}
}
return 'Unknown';
}
参考文献
- Firebaseでmultipart/form-dataを処理する
- busboy npm
- Stream Node.js
- file-type npm