From b5a41dfe3b5967c3af2e3bdab905cb0bcd8905fc Mon Sep 17 00:00:00 2001 From: Erick Date: Thu, 12 Jan 2023 11:23:20 -0300 Subject: [PATCH] feat: adding production environment config (#317) * feat: adding production config files * better handling prod and dev environments * small fixes and changes regarding prod * fixing domains * feat: fixing config functions host * feat: properly loading firebase options depending on the env * adding final prod domain url * fix: lint --- .firebaserc | 10 ++- .github/workflows/deploy_app_dev.yaml | 2 +- .github/workflows/deploy_app_production.yaml | 26 ++++++++ .github/workflows/deploy_app_staging.yaml | 2 +- README.md | 2 +- analysis_options.yaml | 3 +- firebase.json | 50 +++++++++++++++ functions/src/config.ts | 14 ++-- functions/src/convert/index.spec.ts | 6 +- functions/src/convert/index.ts | 4 +- functions/src/share/index.spec.ts | 2 +- lib/{main.dart => bootstrap.dart} | 10 +-- ...options.dart => firebase_options_dev.dart} | 0 lib/firebase_options_prod.dart | 64 +++++++++++++++++++ lib/main_dev.dart | 10 +++ lib/main_prod.dart | 10 +++ storage.rules | 6 +- 17 files changed, 195 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/deploy_app_production.yaml rename lib/{main.dart => bootstrap.dart} (90%) rename lib/{firebase_options.dart => firebase_options_dev.dart} (100%) create mode 100644 lib/firebase_options_prod.dart create mode 100644 lib/main_dev.dart create mode 100644 lib/main_prod.dart diff --git a/.firebaserc b/.firebaserc index 7d4b0146..39ccc3b5 100644 --- a/.firebaserc +++ b/.firebaserc @@ -1,8 +1,16 @@ { "projects": { - "default": "io-photobooth-dev" + "default": "io-photobooth-dev", + "production": "holobooth-prod" }, "targets": { + "holobooth-prod": { + "hosting": { + "app_prod": [ + "0cf34931-6ab3-8cd9-57b5-f38b5fa5ba1e" + ] + } + }, "io-photobooth-dev": { "hosting": { "app_dev": [ diff --git a/.github/workflows/deploy_app_dev.yaml b/.github/workflows/deploy_app_dev.yaml index b15db261..fd03a14a 100644 --- a/.github/workflows/deploy_app_dev.yaml +++ b/.github/workflows/deploy_app_dev.yaml @@ -16,7 +16,7 @@ jobs: flutter-version: "3.6.0-0.1.pre" channel: "beta" - run: flutter packages get - - run: flutter build web --web-renderer canvaskit + - run: flutter build web --web-renderer canvaskit -t lib/main_dev.dart - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/deploy_app_production.yaml b/.github/workflows/deploy_app_production.yaml new file mode 100644 index 00000000..8dcacb2b --- /dev/null +++ b/.github/workflows/deploy_app_production.yaml @@ -0,0 +1,26 @@ +name: deploy_app_production + +on: + workflow_dispatch: + +jobs: + deploy-dev: + runs-on: ubuntu-latest + name: Deploy App Production + steps: + - uses: actions/checkout@v2 + - uses: subosito/flutter-action@v2 + with: + flutter-version: "3.6.0-0.1.pre" + channel: "beta" + - run: flutter packages get + - run: flutter build web --web-renderer canvaskit -t lib/main_prod.dart + - uses: FirebaseExtended/action-hosting-deploy@v0 + with: + repoToken: "${{ secrets.GITHUB_TOKEN }}" + firebaseServiceAccount: "${{ secrets.FIREBASE_SERVICE_ACCOUNT_PHOTOBOOTH_PRODUCTION }}" + projectId: holobooth-prod + target: app_prod + expires: 30d + channelId: live + diff --git a/.github/workflows/deploy_app_staging.yaml b/.github/workflows/deploy_app_staging.yaml index 3e0143fb..c6a5aed9 100644 --- a/.github/workflows/deploy_app_staging.yaml +++ b/.github/workflows/deploy_app_staging.yaml @@ -14,7 +14,7 @@ jobs: flutter-version: "3.6.0-0.1.pre" channel: "beta" - run: flutter packages get - - run: flutter build web --web-renderer canvaskit + - run: flutter build web --web-renderer canvaskit -t lib/main_dev.dart - uses: FirebaseExtended/action-hosting-deploy@v0 with: repoToken: "${{ secrets.GITHUB_TOKEN }}" diff --git a/README.md b/README.md index 400d8334..9fac1eea 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ A Photo Booth built with [Flutter][flutter_link] and [Firebase][firebase_link] f To run the desired project either use the launch configuration in VSCode/Android Studio or use the following commands: ```sh -$ flutter run -d chrome +$ flutter run -d chrome -t lib/main_dev.dart ``` _\*I/O Photo Booth works on Web._ diff --git a/analysis_options.yaml b/analysis_options.yaml index 5b5bc053..d0a1173f 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,7 +1,8 @@ include: package:very_good_analysis/analysis_options.3.1.0.yaml analyzer: exclude: - - lib/firebase_options.dart + - lib/firebase_options_dev.dart + - lib/firebase_options_prod.dart - lib/**/*.gen.dart linter: rules: diff --git a/firebase.json b/firebase.json index 6d7b3bb7..8c93cc1d 100644 --- a/firebase.json +++ b/firebase.json @@ -99,6 +99,56 @@ } ], "predeploy": [] + }, + { + "target": "app_prod", + "public": "build/web", + "cleanUrls": true, + "trailingSlash": false, + "ignore": [ + ".firebase", + "firebase.json", + "functions/node_modules", + "functions/src", + "__/**" + ], + "rewrites": [ + { "source": "/share/**", "function": "shareImage" }, + { + "source": "**", + "destination": "/index.html" + } + ], + "headers": [ + { + "source": "**/*.@(eot|otf|ttf|ttc|woff|font.css)", + "headers": [ + { + "key": "Access-Control-Allow-Origin", + "value": "*" + } + ] + }, + { + "source": "**/*.@(jpg|jpeg|gif|png)", + "headers": [ + { + "key": "Cache-Control", + "value": "max-age=3600" + } + ] + }, + { + "source": "**", + "headers": [ + { + "key": "Cache-Control", + "value": "no-cache, no-store, must-revalidate" + } + ] + } + ], + "predeploy": [] } ], "functions": { diff --git a/functions/src/config.ts b/functions/src/config.ts index d97fa330..20c9d4ee 100644 --- a/functions/src/config.ts +++ b/functions/src/config.ts @@ -2,11 +2,11 @@ export const ENV = process.env.NODE_ENV; export const UPLOAD_PATH = 'uploads'; export const SHARE_PATH = 'share'; export const ALLOWED_HOSTS = [ - 'localhost:5001', - 'io-photobooth-dev.web.app', - 'io-photo-booth.web.app', - 'us-central1-io-photobooth-dev.cloudfunctions.net', - 'us-central1-io-photo-booth.cloudfunctions.net', - 'photobooth.flutter.dev', - 'convert-it4sycsdja-uc.a.run.app', + 'http://localhost:5001', + 'https://io-photobooth-dev.web.app', + 'https://io-photo-booth.web.app', + 'https://photobooth.flutter.dev', + 'https://holobooth-prod.web.app', + 'https://0cf34931-6ab3-8cd9-57b5-f38b5fa5ba1e.web.app', + 'https://holobooth.flutter.dev', ]; diff --git a/functions/src/convert/index.spec.ts b/functions/src/convert/index.spec.ts index 607efc5b..59f9cd8b 100644 --- a/functions/src/convert/index.spec.ts +++ b/functions/src/convert/index.spec.ts @@ -205,7 +205,7 @@ jest.mock('firebase-admin', () => { describe('convert', () => { it('returns response with status 200 on success', async () => { const mockRequest = mockDeep(); - mockRequest.get.mockReturnValue('localhost:5001'); + mockRequest.get.mockReturnValue('http://localhost:5001'); mockRequest.protocol = 'https'; setUpBusboy('finish', 'end'); setUpFfmpeg('end'); @@ -228,7 +228,7 @@ describe('convert', () => { it('returns status 500 on error', async () => { const mockRequest = mockDeep(); - mockRequest.get.mockReturnValue('localhost:5001'); + mockRequest.get.mockReturnValue('http://localhost:5001'); mockRequest.protocol = 'https'; setUpBusboy('error', 'error'); @@ -254,7 +254,7 @@ describe('convertImages', () => { it('returns response with status 200 and file url on success', async () => { const mockRequest = mockDeep(); - mockRequest.get.mockReturnValue('localhost:5001'); + mockRequest.get.mockReturnValue('http://localhost:5001'); mockRequest.protocol = 'https'; setUpBusboy('finish', 'end'); diff --git a/functions/src/convert/index.ts b/functions/src/convert/index.ts index 936ab128..6bb9db7c 100644 --- a/functions/src/convert/index.ts +++ b/functions/src/convert/index.ts @@ -51,13 +51,11 @@ export async function convertImages( let tempDir: string | null = null; try { - const host = req.get('host') ?? ''; - const baseUrl = `${req.protocol}://${host}`; + const host = req.get('origin') ?? ''; if (!ALLOWED_HOSTS.includes(host)) { functions.logger.log('Bad host', { host, - baseUrl, }); throw new Error('Bad host'); } diff --git a/functions/src/share/index.spec.ts b/functions/src/share/index.spec.ts index c5149f35..8dc56ea6 100644 --- a/functions/src/share/index.spec.ts +++ b/functions/src/share/index.spec.ts @@ -24,7 +24,7 @@ describe('Share API', () => { path: '', protocol: 'http', get(_: string) { - return 'localhost:5001'; + return 'http://localhost:5001'; }, } as functions.https.Request; diff --git a/lib/main.dart b/lib/bootstrap.dart similarity index 90% rename from lib/main.dart rename to lib/bootstrap.dart index 625d3278..9f2369ff 100644 --- a/lib/main.dart +++ b/lib/bootstrap.dart @@ -12,11 +12,13 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:io_photobooth/app/app.dart'; import 'package:io_photobooth/app/app_bloc_observer.dart'; -import 'package:io_photobooth/firebase_options.dart'; import 'package:io_photobooth/landing/loading_indicator_io.dart' if (dart.library.html) 'package:io_photobooth/landing/loading_indicator_web.dart'; -Future main() async { +Future bootstrap({ + required String convertUrl, + required FirebaseOptions firebaseOptions, +}) async { WidgetsFlutterBinding.ensureInitialized(); Bloc.observer = AppBlocObserver(); FlutterError.onError = (details) { @@ -25,7 +27,7 @@ Future main() async { }; await Firebase.initializeApp( - options: DefaultFirebaseOptions.currentPlatform, + options: firebaseOptions, ); final authenticationRepository = AuthenticationRepository( @@ -35,7 +37,7 @@ Future main() async { final avatarDetectorRepository = AvatarDetectorRepository(); final convertRepository = ConvertRepository( - url: 'https://convert-it4sycsdja-uc.a.run.app', + url: convertUrl, ); runZonedGuarded( diff --git a/lib/firebase_options.dart b/lib/firebase_options_dev.dart similarity index 100% rename from lib/firebase_options.dart rename to lib/firebase_options_dev.dart diff --git a/lib/firebase_options_prod.dart b/lib/firebase_options_prod.dart new file mode 100644 index 00000000..4633a617 --- /dev/null +++ b/lib/firebase_options_prod.dart @@ -0,0 +1,64 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for android - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.iOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for ios - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.macOS: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for macos - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyAJJhkSV4Mn2Yk4XhGc2Keitfy-iGRTXfQ', + appId: '1:2414555761:web:e13eb13e7dfd4b277561d0', + messagingSenderId: '2414555761', + projectId: 'holobooth-prod', + authDomain: 'holobooth-prod.firebaseapp.com', + storageBucket: 'holobooth-prod.appspot.com', + measurementId: 'G-KWF3BR55Z5', + ); +} diff --git a/lib/main_dev.dart b/lib/main_dev.dart new file mode 100644 index 00000000..1a6e2951 --- /dev/null +++ b/lib/main_dev.dart @@ -0,0 +1,10 @@ +import 'package:io_photobooth/bootstrap.dart'; +import 'package:io_photobooth/firebase_options_dev.dart'; + +Future main() async { + const convertUrl = 'https://convert-it4sycsdja-uc.a.run.app'; + await bootstrap( + convertUrl: convertUrl, + firebaseOptions: DefaultFirebaseOptions.currentPlatform, + ); +} diff --git a/lib/main_prod.dart b/lib/main_prod.dart new file mode 100644 index 00000000..226e5f72 --- /dev/null +++ b/lib/main_prod.dart @@ -0,0 +1,10 @@ +import 'package:io_photobooth/bootstrap.dart'; +import 'package:io_photobooth/firebase_options_prod.dart'; + +Future main() async { + const convertUrl = 'https://convert-fge4q4vwia-uc.a.run.app'; + await bootstrap( + convertUrl: convertUrl, + firebaseOptions: DefaultFirebaseOptions.currentPlatform, + ); +} diff --git a/storage.rules b/storage.rules index 2712d01f..9dc92819 100644 --- a/storage.rules +++ b/storage.rules @@ -1,9 +1,9 @@ rules_version = '2'; service firebase.storage { match /b/{bucket}/o { - match /{folder}/{imageId} { - allow read: if imageId.matches(".*\\.png") || imageId.matches(".*\\.jpg"); - allow write: if false; + match /{allPaths=**} { + allow create: if request.auth != null; + allow read: if true; } } }