Firebase Angular ウェブ フレームワークの Codelab

1. 作成する内容

この Codelab では、Angular ライブラリ(AngularFire)の最新機能を使用して、リアルタイムの共同編集が可能な地図を使用して、旅行ブログを作成します。完成したウェブアプリは、旅行ブログで構成され、訪れた各場所に画像をアップロードできます。

ウェブアプリの構築には AngularFire、ローカルテストには Emulator Suite、ユーザーデータを追跡する Authentication、Cloud Functions を利用してデータとメディアを保持する Firestore と Storage、アプリのデプロイには Firebase Hosting が使用されます。

学習内容

  • Emulator Suite を使用してローカルで Firebase プロダクトを使用して開発する方法
  • AngularFire でウェブアプリを強化する方法
  • Firestore でデータを保持する方法
  • ストレージにメディアを保存する方法
  • アプリを 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 では、ウェブフレームワーク リポジトリのみを使用します。

  • 📊? ウェブ フレームワーク: この 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 プロジェクトを作成する

  1. Firebase にログインします。
  2. Firebase コンソールで [プロジェクトを追加] をクリックし、Firebase プロジェクトに「<実際のプロジェクト>」という名前を付けます。Firebase プロジェクトのプロジェクト ID を覚えておいてください。
  3. [プロジェクトを作成] をクリックします。

重要: Firebase プロジェクトの名前は <your-project> ですが、<your-project>-1234 という形式の一意のプロジェクト ID が自動的に割り当てられます。この一意の識別子は、CLI を含め、プロジェクトが実際に識別される方法であり、<your-project> は単なる表示名です。

これから作成するアプリケーションでは、ウェブアプリで利用可能な Firebase プロダクトを使用します。

  • ユーザーがアプリに簡単にログインできるようにする Firebase Authentication
  • 構造化されたデータをクラウドに保存し、データが変更されたときに即座に通知を受け取る Cloud Firestore
  • ファイルをクラウドに保存する Cloud Storage for Firebase
  • アセットをホストして提供する Firebase Hosting
  • 関数: 内部 API および外部 API とやり取りします。

これらのプロダクトの一部は、特別な構成を必要とするか、Firebase コンソールを使用して有効にする必要があります。

Firebase ウェブアプリをプロジェクトに追加する

  1. ウェブアイコンをクリックして、新しい Firebase ウェブアプリを作成します。
  2. 次のステップで構成オブジェクトが表示されます。このオブジェクトのコンテンツを environments/environment.ts ファイルにコピーします。

Firebase Authentication に Google ログインを有効にする

ユーザーが Google アカウントでウェブアプリにログインできるようにするには、Google のログイン方法を使用します。

Google ログインを有効にするには:

  1. Firebase コンソールの左側のパネルで、[ビルド] セクションを見つけます。
  2. [Authentication] をクリックしてから、[Sign-in method] タブをクリックします(直接移動する場合は、こちらをクリックします)。
  3. [Google] ログイン プロバイダを有効にして、[保存] をクリックします。
  4. アプリの公開名を <your-project-name> に設定し、プルダウン メニューから [プロジェクトのサポートのメールアドレス] を選択します。

Cloud Firestore を有効にする

  1. Firebase コンソールの [構築] セクションで、[Firestore データベース] をクリックします
  2. [Cloud Firestore] ペインで [データベースを作成] をクリックします。
  3. Cloud Firestore データを保存するロケーションを設定します。これをデフォルトのままにすることも、近くのリージョンを選択することもできます。

Cloud Storage を有効にする

このウェブアプリは、Cloud Storage for Firebase を使用して画像を保存、アップロード、共有します。

  1. Firebase コンソールの [ビルド] セクションで、[Storage] をクリックします。
  2. [使ってみる] ボタンが表示されない場合は、Cloud Storage がすでに

以下の操作は不要です。

  1. [利用開始] をクリックします。
  2. Firebase プロジェクトのセキュリティ ルールに関する免責条項を読み、[次へ] をクリックします。
  3. 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. 認証の追加

アプリ用にエミュレータを設定したので、次は認証機能を追加して、各ユーザーがメッセージを投稿する前にログインしていることを確認します。

そのためには、signin 関数を AngularFire から直接インポートし、authState 関数でユーザーの認証状態を追跡します。ログインページの関数を変更して、ページが読み込み時にユーザー認証状態をチェックするようにします。

AngularFire 認証の挿入

src/app/pages/login-page/login-page.component.ts で、@angular/fire/auth から Auth をインポートし、LoginPageComponent に挿入します。Google などの認証プロバイダと、signinsignout などの関数は、同じパッケージから直接インポートして、アプリで使用することもできます。

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);
		})
	}
}

これでログインページが機能しました。ログインして、Authentication Emulator で結果を確認します。

7. Firestore の構成

このステップでは、Firestore に保存されている旅行のブログ投稿を投稿および更新する機能を追加します。

Authentication と同様に、Firestore の関数は AngularFire から事前にパッケージ化されています。各ドキュメントはコレクションに属し、各ドキュメントにはネストされたコレクションを含めることもできます。旅行に関するブログ投稿を作成および更新するには、Firestore のドキュメントの path を知っておく必要があります。

TravelService の実装

さまざまなページがウェブアプリで Firestore ドキュメントの読み取りと更新を行う必要があるため、src/app/services/travel.service.ts に関数を実装して、同じ AngularFire 関数がページごとに繰り返し挿入されないようにできます。

まず、前のステップと同様の AuthFirestore をサービスに挿入します。現在の認証ステータスをリッスンする監視可能な 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/firestoredocData 関数と 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//stops/ のように旅行投稿のサブコレクション内にあります。

これは、旅行に関する投稿を作成する場合とほぼ同じなので、自分で実装してみるか、以下の実装を確認してください。

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[]>
}

すべての移動の Observable 配列を取得するために、コンストラクタで TravelService が呼び出されます。

現在のユーザーの移動のみが必要な場合は、query 関数を使用します。

セキュリティを確保するその他の方法には、セキュリティ ルールの実装や、以下のオプションのステップで説明するように Cloud Functions と Firestore の使用があります。

次に、TravelService に実装されている関数を呼び出します。

async  createTravel(userId: String) {
	this.travelService.addEmptyTravel(userId);
}

deleteTravel(travelId: String) {
	this.travelService.deleteData(`travels/${travelId}`)
}

これで旅行に関するページが表示されるようになりました。新しい旅行投稿を作成した場合の Firestore エミュレータの動作をご確認ください。

次に、/src/app/pages/edit-travels/edit-travels.component.ts の update 関数に対して同じ手順を繰り返します。

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 からストレージを注入します。

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. アプリケーションのデプロイ

これで、アプリケーションをデプロイする準備が整いました。

firebase 構成を src/environments/environment.ts から src/environments/environment.prod.ts にコピーして、次のコマンドを実行します。

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.rulessecurity.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;
		}
	}
}