Markdown のメタデータを Firestore に保存する
Markdown のメタデータを Firebase のデータベースに保存する方法の詳細を紹介します。基本的に同じファイル名のファイルは、Firebase のストレージの同じフォルダ内には存在しないので、Markdown のファイル名をキーにして、重複したデータを Firestore に保存しないようにする処理が必要になります。
ポイントは重複を避ける事
Markdown のメタデータを Firebase のデータベース Firestore に保存する場合に大切なポイントは、重複するデータのデータベースのエントリ(Firestore のドキュメント)を避ける事にあります。 Markdown で書かれたファイルを Firebase のストレージにアップロード(投稿)する場合、同じファイル名で何回かアップロード(投稿)するケースが発生します。例えば、原稿の中身を変更したりした場合には、同じファイル名で何回かアップロード(投稿)する事は当然あり得る操作になります。
Firebase のストレージの場合、同じフォルダに同じファイル名でアップロードする場合、上書き保存をしてくれるので問題ではありません。
しかし、Firestore のデータベースの場合、前回紹介した、「addDoc()」を使う場合、毎回新たなデータベースのエントリ(Firestore のドキュメント)を作成してしまいます。つまり、同じファイル名に対する Firestore のドキュメントが複数発生する状態が起きてしまうことになります。
実際にはファイルは一つしかありませんが、データベースの対応するエントリ(Firestore のドキュメント)が幾つもある事になるので処理上の問題が発生します。
解決策は大きく二つです!
では、どのようにこの問題を解決すれば良いかがポイントになります。 最初に結論を言ってしまうと、解決策は大きく分けて二つが考えられます。
Firestore のドキュメントを追加する前に同じ名前のファイルに相当するドキュメントがあるかチェックする
Firestore のドキュメントの 🆔 を予め指定してデータをデータベースに書き込む
最初の方法は、Firestore のドキュメントを追加する前に、同じ名前のファイルに相当するドキュメントがあるかをチェックするというやり方です。この方法は、Firestore のクエリを使って、相当するファイル名のドキュメントがあるかを、「getDocs()」を使って取得します。この取得したドキュメントの数が「0」ならば、新規に追加をします。取得したドキュメントの数が「1」の場合には、このドキュメントの 🆔 を取得して、そのドキュメントを上書きします。この処理を行なっている場合、基本的にこれ以外のケースは発生しないはずですが、発生した場合はエラーにします。
この方法ならば、重複したドキュメントを追加する事を避けることができます。ドキュメントの 🆔 がわかる場合は、「setDoc()」という関数を使うと、その 🆔 のドキュメントを上書きすることができます。
二つ目の方法は、Firestore のドキュメントを書き込む際に、毎回 🆔 を指定する方法があります。この場合、ファイル名は同じフォルダ内には同一のファイルは存在しないので 🆔 として利用することが可能です。(この例では複数のフォルダにアップロードすることは想定していません。)
🆔 が予め分かっている場合には、「setDoc()」で書き込めば、毎回データは上書きされるので重複した Firestore のドキュメントは作成されません。
アクセス数で Firebase の課金が変わる
上で紹介した方法を使えば、重複したドキュメントを避けることが可能です。ここで考えた方が良いのは、アップロードの処理の際の Firestore のアクセス数が違うということです。
最初のやり方を使う場合、クエリで Firestore のデータベースを検索するためのアクセスが必要になります。その上で、取得したドキュメントの数に応じて「addDoc()」か「setDoc()」を使って Firestore のドキュメントを書き込むという処理の流れになります。
Firestore のドキュメントを書き込む操作は避けられませんが、その前処理として、クエリで検索するというアクセスが1回余分になります。このアクセスをどのように考えるかで実装のやり方が変わってきます。
「addDoc()」では Firestore の処理で、自動的にドキュメントの 🆔 を作ってデータを書き込みます。「setDoc()」では指定された 🆔 でドキュメントを作成します。「addDoc()」でドキュメントを追加する場合には、その 🆔 とファイル名の関係はわからないので検索して確認する必要があるという事になります。
実施例
この記事では、Firebase のアクセス数を必要以上に増やさないということで、アップロードするファイル名を 🆔 に指定して Firestore のドキュメントを作成する方法でドキュメントの書き込みをする方法を採用しています。
export function setMetadata(target_collection, docId, metadata) {
return new Promise(async (resolve) => {
try {
await setDoc(doc(db, target_collection, docId), metadata);
resolve(true);
} catch (error) {
resolve(false);
}
});
}
export function uploadFile(path, file) {
const fileRef = ref(storage, path);
const uploadTask = uploadBytesResumable(fileRef, file);
uploadTask.on(
"state_changed",
(snapshot) => {
// Current completion in a percentage of the total transfer
const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log("Upload is ", progress + " % done.");
switch (snapshot.state) {
case "paused":
console.log("Upload is paused.");
break;
case "running":
console.log("Upload is running.");
break;
}
},
(error) => {
// Error -- Handle unsuccessful uploads
alert("1:ファイルのアップロードに失敗しました");
},
() => {
// Handle successful uploads on complete
// --> Gets the download URL
getDownloadURL(uploadTask.snapshot.ref)
.then((downloadURL) => {
fetch(downloadURL).then((res) => {
res.text().then(async (rawtext) => {
const processed = matter(rawtext);
const meta = {
url: downloadURL,
title: processed.data.title ? processed.data.title : "",
path: path,
name: file.name,
description: processed.data.description
? processed.data.description
: "",
type: processed.data.type ? processed.data.type : "",
};
setMetadata("sample", file.name, meta).then((result) => {
if (result) {
// Operation is successful to add the database entry
alert("ファイルをアップロードしました");
} else {
// Operation failed to add the database entry
alert("2:ファイルのアップロードに失敗しました");
}
});
});
});
})
.catch((error) => {
alert("3:ファイルのアップロードに失敗しました");
});
}
);
}
こうすることで、Firebase のストレージのファイルのリストと、Firestore のドキュメントで同じ情報を保持する事が可能になります。
まとめ
今回は、Firebase のアクセス数を最低限にする方法で、Firebase のデータベースである Firestore に Markdown のメタデータを保存する方法を紹介しました。 今回は、一つのフォルダにファイルを保存する前提にしたので、ファイル名だけで Firestore のドキュメントの 🆔 として利用する事が可能でした。ところが、複数のフォルダにアップロードする場合には、ファイルのパスを示す文字の中に「/」が入リます。この場合、Firestore の 🆔 としてこの文字列を使うと、Firestore のコレクションの改装と見做されてしまうので注意が必要です。
こうした場合には、アクセス数は増えてもファイル名とは別の 🆔 を利用する方が実装がやりやすくなります。実際の実装では、こうした制約も考慮しながら実装の方法を検討する必要があります。