1. 作成するアプリの概要
この Codelab では、Angular ライブラリの最新版である AngularFire を使用して、リアルタイムの共同編集マップを備えた旅行ブログを作成します。最終的なウェブアプリは、旅行先の各場所に画像をアップロードできる旅行ブログで構成されます。
AngularFire を使用してウェブアプリを構築し、Emulator Suite を使用してローカル テストを行い、Authentication を使用してユーザーデータを追跡し、Firestore と Storage を使用してデータとメディアを永続化します。これらは Cloud Functions によって強化され、最後に Firebase Hosting を使用してアプリをデプロイします。
学習内容
- Emulator Suite を使用して Firebase プロダクトをローカルで開発する方法
- AngularFire でウェブアプリを強化する方法
- Firestore でデータを永続化する方法
- Storage でメディアを永続化する方法
- Firebase Hosting にアプリをデプロイする方法
- Cloud Functions を使用してデータベースと API を操作する方法
必要なもの
- Node.js バージョン 10 以降
- Firebase プロジェクトの作成と管理に使用する Google アカウント
- Firebase CLI バージョン 11.14.2 以降
- 任意のブラウザ(Chrome など)
- Angular と Javascript に関する基本的な知識
2. サンプルコードを取得する
コマンドラインから、Codelab の GitHub リポジトリのクローンを作成します。
git clone https://github.com/firebase/codelab-friendlychat-web
または、git がインストールされていない場合は、リポジトリを ZIP ファイルとしてダウンロードすることもできます。
GitHub リポジトリには複数のプラットフォーム用のサンプル プロジェクトが含まれています。
この Codelab では、webframework リポジトリのみを使用します。
- 📁 webframework: この Codelab で作成する開始用コード。
依存関係のインストール
クローンを作成したら、ウェブアプリをビルドする前に、ルートフォルダと functions フォルダに依存関係をインストールします。
cd webframework && npm install
cd functions && npm install
Firebase CLI のインストール
ターミナルで次のコマンドを使用して Firebase CLI をインストールします。
npm install -g firebase-tools
次のコマンドを使用して、Firebase CLI のバージョンが 11.14.2 より大きいことを再確認します。
firebase  --version
バージョンが 11.14.2 より前の場合は、次のコマンドを使用して更新してください。
npm update firebase-tools
3. Firebase プロジェクトを作成して設定する
Firebase プロジェクトを作成する
- Google アカウントを使用して Firebase コンソールにログインします。
- ボタンをクリックして新しいプロジェクトを作成し、プロジェクト名(例: FriendlyChat)を入力します。
 
- [続行] をクリックします。
- Firebase の利用規約が表示されたら、内容を読み、同意して [続行] をクリックします。
- (省略可)Firebase コンソールで AI アシスタンス(「Gemini in Firebase」)を有効にします。
- この Codelab では Google アナリティクスは必要ないため、Google アナリティクスのオプションをオフに切り替えます。
- [プロジェクトを作成] をクリックし、プロジェクトのプロビジョニングが完了するまで待ってから、[続行] をクリックします。
プロジェクトに Firebase ウェブアプリを追加する
- ウェブアイコンをクリックして、新しい Firebase ウェブアプリを作成します。
- 次のステップでは、構成オブジェクトが表示されます。このオブジェクトの内容を environments/environment.tsファイルにコピーします。
Firebase プロダクトを設定する
これから構築するアプリケーションは、ウェブアプリに使用できる Firebase プロダクトを使用します。
- ユーザーがアプリに簡単にログインできるようにする Firebase Authentication。
- 構造化されたデータをクラウドに保存し、データが変更されたときに即座に通知を受け取る Cloud Firestore。
- ファイルをクラウドに保存する Cloud Storage for Firebase。
- アセットをホストして提供する Firebase Hosting。
- 内部 API と外部 API を操作する関数。
この中には、特別な設定が必要になるプロダクトや、Firebase コンソールを使用して有効化する必要があるプロダクトがあります。
Google ログインを Firebase Authentication 用に有効にする
ユーザーが Google アカウントでウェブアプリにログインできるように、Google ログインを使用します。
Google ログインを有効にするには:
- Firebase コンソールの左側のパネルで、[構築] セクションを見つけます。
- [Authentication] をクリックし、[Sign-in method] タブをクリックします(または、ここをクリックして直接移動します)。
- [Google] ログイン プロバイダを有効にして、[保存] をクリックします。
- アプリの一般公開名に「<プロジェクト名>」を設定し、プルダウン メニューから [プロジェクトのサポートメール] を選択します。
Cloud Firestore を有効にする
- Firebase コンソールの [構築] セクションで、[Firestore データベース] をクリックします
- [Cloud Firestore] ペインで [データベースを作成] をクリックします。
- Cloud Firestore データを保存するロケーションを設定します。デフォルトのままにするか、近くのリージョンを選択できます。
Cloud Storage を有効にする
このウェブアプリは Cloud Storage for Firebase を使用して画像ファイルを保存、アップロード、共有します。
- Firebase コンソールの [構築] セクションで、[ストレージ] をクリックします。
- [開始] ボタンが表示されない場合は、Cloud Storage がすでに
有効になっている場合は、以下の手順を行う必要はありません。
- [利用開始] をクリックします。
- Firebase プロジェクトのセキュリティ ルールに関する免責条項を確認し、[次へ] をクリックします。
- Cloud Storage のロケーションは、Cloud Firestore データベースに選択したリージョンと同じリージョンが事前に選択されています。[完了] をクリックして設定を完了します。
デフォルトのセキュリティ ルールでは、すべての認証済みユーザーは Cloud Storage に自由に書き込むことができます。この Codelab の後半で、ストレージのセキュリティを強化します。
4. Firebase プロジェクトに接続する
Firebase コマンドライン インターフェース(CLI)を使用すると、Firebase Hosting を使用してウェブアプリをローカルで提供したり、ウェブアプリを Firebase プロジェクトにデプロイしたりできます。
コマンドラインがアプリのローカル webframework ディレクトリにアクセスしていることを確認します。
ウェブアプリのコードを Firebase プロジェクトに接続します。まず、コマンドラインで Firebase CLI にログインします。
firebase login
次に、次のコマンドを実行してプロジェクト エイリアスを作成します。$YOUR_PROJECT_ID は、Firebase プロジェクトの ID に置き換えます。
firebase  use  $YOUR_PROJECT_ID
AngularFire を追加する
AngularFire をアプリに追加するには、次のコマンドを実行します。
ng add @angular/fire
次に、コマンドラインの指示に沿って、Firebase プロジェクトに存在する機能を選択します。
Firebase を初期化する
Firebase プロジェクトを初期化するには、次のコマンドを実行します。
firebase init
次に、コマンドラインのプロンプトに従って、Firebase プロジェクトで使用された機能とエミュレータを選択します。
エミュレータを起動する
webframework ディレクトリから、次のコマンドを実行してエミュレータを起動します。
firebase  emulators:start
最終的に次のような画面が表示されます。
$  firebase  emulators:start
i  emulators:  Starting  emulators:  auth,  functions,  firestore,  hosting,  functions
i  firestore:  Firestore  Emulator  logging  to  firestore-debug.log
i  hosting:  Serving  hosting  files  from:  public
✔  hosting:  Local  server:  http://localhost:5000
i  ui:  Emulator  UI  logging  to  ui-debug.log
i  functions:  Watching  "/functions"  for  Cloud  Functions...
✔  functions[updateMap]:  firestore  function  initialized.
  
┌─────────────────────────────────────────────────────────────┐
│  ✔  All  emulators  ready!  It  is  now  safe  to  connect  your  app.  │
│  i  View  Emulator  UI  at  http://localhost:4000  │
└─────────────────────────────────────────────────────────────┘
  
┌────────────────┬────────────────┬─────────────────────────────────┐
│  Emulator  │  Host:Port  │  View  in  Emulator  UI  │
├────────────────┼────────────────┼─────────────────────────────────┤
│  Authentication  │  localhost:9099  │  http://localhost:4000/auth  │
├────────────────┼────────────────┼─────────────────────────────────┤
│  Functions  │  localhost:5001  │  http://localhost:4000/functions  │
├────────────────┼────────────────┼─────────────────────────────────┤
│  Firestore  │  localhost:8080  │  http://localhost:4000/firestore  │
├────────────────┼────────────────┼─────────────────────────────────┤
│  Hosting  │  localhost:5000  │  n/a  │
└────────────────┴────────────────┴─────────────────────────────────┘
Emulator  Hub  running  at  localhost:4400
Other  reserved  ports:  4500
  
Issues?  Report  them  at  https://github.com/firebase/firebase-tools/issues  and  attach  the  *-debug.log  files.
✔All  emulators  ready! というメッセージが表示されたら、エミュレータを使用できます。
旅行アプリの UI が(まだ)機能していない状態で表示されます。
それでは、ビルドを開始しましょう。
5. ウェブアプリをエミュレータに接続する
エミュレータログの表に基づくと、Cloud Firestore エミュレータはポート 8080 でリッスンし、Authentication エミュレータはポート 9099 でリッスンしています。
EmulatorUI を開く
ウェブブラウザで http://127.0.0.1:4000/ に移動します。Emulator Suite UI が表示されます。
エミュレータを使用するようにアプリをルーティングする
src/app/app.module.ts で、次のコードを AppModule のインポート リストに追加します。
@NgModule({
	declarations: [...],
	imports: [
		provideFirebaseApp(() =>  initializeApp(environment.firebase)),
		provideAuth(() => {
			const  auth = getAuth();
			if (location.hostname === 'localhost') {
				connectAuthEmulator(auth, 'http://127.0.0.1:9099', { disableWarnings:  true });
			}
			return  auth;
		}),
		provideFirestore(() => {
			const  firestore = getFirestore();
			if (location.hostname === 'localhost') {
				connectFirestoreEmulator(firestore, '127.0.0.1', 8080);
			}
			return  firestore;
		}),
		provideFunctions(() => {
			const  functions = getFunctions();
			if (location.hostname === 'localhost') {
				connectFunctionsEmulator(functions, '127.0.0.1', 5001);
			}
			return  functions;
		}),
		provideStorage(() => {
			const  storage = getStorage();
			if (location.hostname === 'localhost') {
				connectStorageEmulator(storage, '127.0.0.1', 5001);
			}
			return  storage;
		}),
		...
	]
これで、ローカル エミュレータを使用するようにアプリが構成され、ローカルでテストと開発を行うことができるようになりました。
6. 認証の追加
アプリのエミュレータが設定されたので、各ユーザーがメッセージを投稿する前にログインしていることを確認する認証機能を追加できます。
そのためには、AngularFire から signin 関数を直接インポートし、authState 関数を使用してユーザーの認証状態を追跡します。ログインページの関数を変更して、ページが読み込み時にユーザー認証状態を確認するようにします。
AngularFire Auth の挿入
src/app/pages/login-page/login-page.component.ts で、@angular/fire/auth から Auth をインポートし、LoginPageComponent に挿入します。Google などの認証プロバイダや、signin、signout などの関数も、同じパッケージから直接インポートしてアプリで使用できます。
import { Auth, GoogleAuthProvider, signInWithPopup, signOut, user } from  '@angular/fire/auth';
export  class  LoginPageComponent  implements  OnInit {
	private  auth: Auth = inject(Auth);
	private  provider = new  GoogleAuthProvider();
	user$ = user(this.auth);
	constructor() {}  
	ngOnInit(): void {} 
	login() {
		signInWithPopup(this.auth, this.provider).then((result) => {
			const  credential = GoogleAuthProvider.credentialFromResult(result);
			return  credential;
		})
	}
	logout() {
		signOut(this.auth).then(() => {
			console.log('signed out');}).catch((error) => {
				console.log('sign out error: ' + error);
		})
	}
}
これでログインページが機能するようになりました。ログインを試して、認証エミュレータで結果を確認します。
7. Firestore の構成
このステップでは、Firestore に保存されている旅行ブログ投稿を投稿および更新する機能を追加します。
Authentication と同様に、Firestore 関数は AngularFire から事前にパッケージ化されています。各ドキュメントはコレクションに属し、各ドキュメントにはネストされたコレクションを含めることもできます。旅行ブログ投稿を作成して更新するには、Firestore のドキュメントの path を知る必要があります。
TravelService の実装
ウェブアプリでは、さまざまなページで Firestore ドキュメントの読み取りと更新が必要になるため、src/app/services/travel.service.ts で関数を実装して、各ページで同じ AngularFire 関数を繰り返し挿入しないようにします。
前の手順と同様に、Auth と Firestore をサービスに挿入することから始めます。現在の認証ステータスをリッスンするオブザーバブル user$ オブジェクトを定義することも有用です。
import { doc, docData, DocumentReference, Firestore, getDoc, setDoc, updateDoc, collection, addDoc, deleteDoc, collectionData, Timestamp } from  "@angular/fire/firestore";
export  class  TravelService {
	firestore: Firestore = inject(Firestore);
	auth: Auth = inject(Auth);
	user$ = authState(this.auth).pipe(filter(user  =>  user !== null), map(user  =>  user!));
	router: Router = inject(Router);
旅行の投稿を追加する
旅行に関する投稿は Firestore に保存されるドキュメントとして存在します。ドキュメントはコレクション内に存在する必要があるため、すべての旅行に関する投稿を含むコレクションの名前は travels になります。したがって、旅行に関する投稿のパスは travels/ となります。
AngularFire の addDoc 関数を使用すると、オブジェクトをコレクションに挿入できます。
async  addEmptyTravel(userId: String) {
	...
	addDoc(collection(this.firestore, 'travels'), travelData).then((travelRef) => {
		collection(this.firestore, `travels/${travelRef.id}/stops`);
		setDoc(travelRef, {... travelData, id:  travelRef.id})
		this.router.navigate(['edit', `${travelRef.id}`]);
		return  travelRef;
	})
}
データの更新と削除
旅行投稿の uid があれば、Firestore に保存されているドキュメントのパスを推測できます。このパスは、AngularFire の updateFoc 関数と deleteDoc 関数を使用して読み取り、更新、削除できます。
async  updateData(path: string, data: Partial<Travel | Stop>) {
	await  updateDoc(doc(this.firestore, path), data)
}
async  deleteData(path: string) {
	const  ref = doc(this.firestore, path);
	await  deleteDoc(ref)
}
オブザーバブルとしてデータを読み取る
旅行の投稿と途中の立ち寄り場所は作成後に変更できるため、ドキュメント オブジェクトをオブザーバブルとして取得し、行われた変更をサブスクライブする方が便利です。この機能は、@angular/fire/firestore の docData 関数と collectionData 関数によって提供されます。
getDocData(path: string) {
	return  docData(doc(this.firestore, path), {idField:  'id'}) as  Observable<Travel | Stop>
}
  
getCollectionData(path: string) {
	return  collectionData(collection(this.firestore, path), {idField:  'id'}) as  Observable<Travel[] | Stop[]>
}
旅行の投稿に経由地を追加する
旅行投稿のオペレーションが設定されたので、次は停留所について検討します。停留所は、次のように旅行投稿のサブコレクションに存在します。travels/
これは旅行投稿の作成とほぼ同じなので、自分で実装してみるか、以下の実装を確認してください。
async  addStop(travelId: string) {
	...
	const  ref = await  addDoc(collection(this.firestore, `travels/${travelId}/stops`), stopData)
	setDoc(ref, {...stopData, id:  ref.id})
}
いいですね。Firestore 関数が Travel サービスに実装されたので、動作を確認できます。
アプリで Firestore 関数を使用する
src/app/pages/my-travels/my-travels.component.ts に移動し、TravelService を挿入してその関数を使用します。
travelService = inject(TravelService);
travelsData$: Observable<Travel[]>;
stopsList$!: Observable<Stop[]>;
constructor() {
	this.travelsData$ = this.travelService.getCollectionData(`travels`) as  Observable<Travel[]>
}
コンストラクタで TravelService を呼び出して、すべての旅行の Observable 配列を取得します。
現在のユーザーの移動のみが必要な場合は、query 関数を使用します。
セキュリティを確保するその他の方法としては、セキュリティ ルールの実装や、Firestore での Cloud Functions の使用などがあります。以下の省略可能な手順で説明します。
次に、TravelService で実装された関数を呼び出します。
async  createTravel(userId: String) {
	this.travelService.addEmptyTravel(userId);
}
deleteTravel(travelId: String) {
	this.travelService.deleteData(`travels/${travelId}`)
}
これで [My Travels] ページが機能するようになりました。新しい旅行投稿を作成したときに Firestore エミュレータで何が起こるかを確認します。
次に、/src/app/pages/edit-travels/edit-travels.component.ts の更新関数に対して繰り返します。
travelService: TravelService = inject(TravelService)
travelId = this.activatedRoute.snapshot.paramMap.get('travelId');
travelData$: Observable<Travel>;
stopsData$: Observable<Stop[]>;
constructor() {
	this.travelData$ = this.travelService.getDocData(`travels/${this.travelId}`) as  Observable<Travel>
	this.stopsData$ = this.travelService.getCollectionData(`travels/${this.travelId}/stops`) as  Observable<Stop[]>
}
updateCurrentTravel(travel: Partial<Travel>) {
	this.travelService.updateData(`travels${this.travelId}`, travel)
}
  
updateCurrentStop(stop: Partial<Stop>) {
	stop.type = stop.type?.toString();
	this.travelService.updateData(`travels${this.travelId}/stops/${stop.id}`, stop)
}
  
addStop() {
	if (!this.travelId) return;
	this.travelService.addStop(this.travelId);
}
deleteStop(stopId: string) {
	if (!this.travelId || !stopId) {
		return;
	}
	this.travelService.deleteData(`travels${this.travelId}/stops/${stopId}`)
	this.stopsData$ = this.travelService.getCollectionData(`travels${this.travelId}/stops`) as  Observable<Stop[]>
}
8. ストレージの構成
次に、Storage を実装して、画像やその他の種類のメディアを保存します。
Cloud Firestore は、JSON オブジェクトなどの構造化データを保存するのに最適です。Cloud Storage は、ファイルまたは BLOB を保存するように設計されています。このアプリでは、ユーザーが旅行の写真を共有できるようにするために使用します。
Firestore と同様に、Storage でファイルを保存して更新するには、各ファイルに一意の識別子が必要です。
TraveService に関数を実装しましょう。
ファイルのアップロード
src/app/services/travel.service.ts に移動し、AngularFire から Storage を挿入します。
export  class  TravelService {
firestore: Firestore = inject(Firestore);
auth: Auth = inject(Auth);
storage: Storage = inject(Storage);
アップロード関数を実装します。
async  uploadToStorage(path: string, input: HTMLInputElement, contentType: any) {
	if (!input.files) return  null
	const  files: FileList = input.files;
		for (let  i = 0; i  <  files.length; i++) {
			const  file = files.item(i);
			if (file) {
				const  imagePath = `${path}/${file.name}`
				const  storageRef = ref(this.storage, imagePath);
				await  uploadBytesResumable(storageRef, file, contentType);
				return  await  getDownloadURL(storageRef);
			}
		}
	return  null;
}
Firestore からドキュメントにアクセスする場合と Cloud Storage からファイルにアクセスする場合の主な違いは、どちらもフォルダ構造のパスに従いますが、ベース URL とパスの組み合わせは getDownloadURL を介して取得され、
アプリで関数を使用する
src/app/components/edit-stop/edit-stop.component.ts に移動し、次のコードを使用してアップロード関数を呼び出します。
	async  uploadFile(file: HTMLInputElement, stop: Partial<Stop>) {
	const  path = `/travels/${this.travelId}/stops/${stop.id}`
	const  url = await  this.travelService.uploadToStorage(path, file, {contentType:  'image/png'});
	stop.image = url ? url : '';
	this.travelService.updateData(path, stop);
}
画像をアップロードすると、メディアファイル自体がストレージにアップロードされ、それに応じて URL が Firestore のドキュメントに保存されます。
9. アプリケーションをデプロイする
これで、アプリケーションをデプロイする準備が整いました。
src/environments/environment.ts から src/environments/environment.prod.ts に firebase 構成をコピーして、次のコマンドを実行します。
firebase deploy
次のように表示されます。
✔ Browser application bundle generation complete.
✔ Copying assets complete.
✔ Index html generation complete.
=== Deploying to 'friendly-travels-b6a4b'...
i  deploying storage, firestore, hosting
i  firebase.storage: checking storage.rules for compilation errors...
✔  firebase.storage: rules file storage.rules compiled successfully
i  firestore: reading indexes from firestore.indexes.json...
i  cloud.firestore: checking firestore.rules for compilation errors...
✔  cloud.firestore: rules file firestore.rules compiled successfully
i  storage: latest version of storage.rules already up to date, skipping upload...
i  firestore: deploying indexes...
i  firestore: latest version of firestore.rules already up to date, skipping upload...
✔  firestore: deployed indexes in firestore.indexes.json successfully for (default) database
i  hosting[friendly-travels-b6a4b]: beginning deploy...
i  hosting[friendly-travels-b6a4b]: found 6 files in .firebase/friendly-travels-b6a4b/hosting
✔  hosting[friendly-travels-b6a4b]: file upload complete
✔  storage: released rules storage.rules to firebase.storage
✔  firestore: released rules firestore.rules to cloud.firestore
i  hosting[friendly-travels-b6a4b]: finalizing version...
✔  hosting[friendly-travels-b6a4b]: version finalized
i  hosting[friendly-travels-b6a4b]: releasing new version...
✔  hosting[friendly-travels-b6a4b]: release complete
✔  Deploy complete!
Project Console: https://console.firebase.google.com/project/friendly-travels-b6a4b/overview
Hosting URL: https://friendly-travels-b6a4b.web.app
10. 完了
これで、アプリケーションが完成し、Firebase Hosting にデプロイされました。すべてのデータと分析に Firebase コンソールからアクセスできるようになります。
AngularFire、Functions、セキュリティ ルールに関するその他の機能については、以下の省略可能な手順と、その他の Firebase Codelab もご確認ください。
11. 省略可: AngularFire 認証ガード
AngularFire は Firebase Authentication とともに、ルートベースの認証ガードも提供します。これにより、アクセス権が不十分なユーザーをリダイレクトできます。これにより、保護されたデータにユーザーがアクセスするのを防ぐことができます。
src/app/app-routing.module.ts で、
import {AuthGuard, redirectLoggedInTo, redirectUnauthorizedTo} from  '@angular/fire/auth-guard'
特定のページでユーザーをリダイレクトするタイミングと場所を関数で定義できます。
const  redirectUnauthorizedToLogin = () =>  redirectUnauthorizedTo(['signin']);
const  redirectLoggedInToTravels = () =>  redirectLoggedInTo(['my-travels']);
次に、それらをルートに追加します。
const  routes: Routes = [
	{path:  '', component:  LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe:  redirectLoggedInToTravels}},
	{path:  'signin', component:  LoginPageComponent, canActivate: [AuthGuard], data: {authGuardPipe:  redirectLoggedInToTravels}},
	{path:  'my-travels', component:  MyTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe:  redirectUnauthorizedToLogin}},
	{path:  'edit/:travelId', component:  EditTravelsComponent, canActivate: [AuthGuard], data: {authGuardPipe:  redirectUnauthorizedToLogin}},
];
12. 省略可: セキュリティ ルール
Firestore と Cloud Storage はどちらも、セキュリティを適用してデータを検証するためにセキュリティ ルール(それぞれ firestore.rules と security.rules)を使用します。
現時点では、Firestore と Storage のデータは読み取りと書き込みに対してオープン アクセスになっていますが、他のユーザーの投稿を変更できないようにする必要があります。セキュリティ ルールを使用すると、コレクションとドキュメントへのアクセスを制限できます。
Firestore ルール
認証済みのユーザーのみが旅行の投稿を閲覧できるようにするには、firestore.rules ファイルに次を追加します。
rules_version  =  '2';
service  cloud.firestore  {
	match  /databases/{database}/travels  {
		allow  read:  if  request.auth.uid  !=  null;
		allow  write:
		if  request.auth.uid  ==  request.resource.data.userId;
	}
}
セキュリティ ルールは、データの検証にも使用できます。
rules_version  =  '2';
service  cloud.firestore  {
	match  /databases/{database}/posts  {
		allow  read:  if  request.auth.uid  !=  null;
		allow  write:
		if  request.auth.uid  ==  request.resource.data.userId;
		&&  "author"  in  request.resource.data
		&&  "text"  in  request.resource.data
		&&  "timestamp"  in  request.resource.data;
	}
}
ストレージ ルール
同様に、セキュリティ ルールを使用して、storage.rules のストレージ データベースへのアクセスを強制できます。より複雑なチェックには関数を使用することもできます。
rules_version  =  '2';
function  isImageBelowMaxSize(maxSizeMB)  {
	return  request.resource.size  <  maxSizeMB  *  1024  *  1024
		&&  request.resource.contentType.matches('image/.*');
}
 service  firebase.storage  {
	match  /b/{bucket}/o  {
		match  /{userId}/{postId}/{filename}  {
			allow  write:  if  request.auth  !=  null
			&&  request.auth.uid  ==  userId  &&  isImageBelowMaxSize(5);
			allow  read;
		}
	}
}