From 4e4a9aff80bfc6f0971f4adc682dc738b876fc87 Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Tue, 3 Feb 2026 16:42:25 -0500 Subject: [PATCH 1/6] feat: add sync-prototypes dependency to launchpad-dev and deploy-launchpad-hosting targets --- makefiles/launchpad.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/makefiles/launchpad.mk b/makefiles/launchpad.mk index 8dbe860f..87050e19 100644 --- a/makefiles/launchpad.mk +++ b/makefiles/launchpad.mk @@ -2,13 +2,13 @@ .PHONY: launchpad-dev deploy-launchpad-hosting -launchpad-dev: +launchpad-dev: sync-prototypes @echo "--> Starting local Launchpad server using Firebase Hosting emulator..." @echo " - Generating secure email hashes..." @node scripts/generate-allowed-hashes.js @firebase serve --only hosting:launchpad --project=$(FIREBASE_ALIAS) -deploy-launchpad-hosting: +deploy-launchpad-hosting: sync-prototypes @echo "--> Deploying Internal Launchpad to Firebase Hosting..." @echo " - Generating secure email hashes..." @node scripts/generate-allowed-hashes.js From cf2433774dcd8f928474a8e0b5a6e01163abc4d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:37:12 +0900 Subject: [PATCH 2/6] solving problem to move to search from home --- .../navigation/home_navigator.dart | 6 +- .../presentation/pages/worker_home_page.dart | 3 +- .../blocs/shifts/shifts_bloc.dart | 60 +++++++++++++++++++ .../blocs/shifts/shifts_event.dart | 2 + .../src/presentation/pages/shifts_page.dart | 21 +++++-- .../widgets/tabs/find_shifts_tab.dart | 6 ++ backend/dataconnect/functions/seed.gql | 4 +- 7 files changed, 92 insertions(+), 10 deletions(-) diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/navigation/home_navigator.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/navigation/home_navigator.dart index 9774cb07..cd9da6f6 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/navigation/home_navigator.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/navigation/home_navigator.dart @@ -31,9 +31,11 @@ extension HomeNavigator on IModularNavigator { /// Optionally provide a [tab] query param (e.g. `find`). void pushShifts({String? tab}) { if (tab == null) { - pushNamed('/worker-main/shifts'); + navigate('/worker-main/shifts'); } else { - pushNamed('/worker-main/shifts?tab=$tab'); + navigate('/worker-main/shifts', arguments: { + 'initialTab': tab, + }); } } diff --git a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart index a9b3f169..777cbf14 100644 --- a/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart +++ b/apps/mobile/packages/features/staff/home/lib/src/presentation/pages/worker_home_page.dart @@ -132,8 +132,7 @@ class WorkerHomePage extends StatelessWidget { EmptyStateWidget( message: emptyI18n.no_shifts_today, actionLink: emptyI18n.find_shifts_cta, - onAction: () => - Modular.to.pushShifts(tab: 'find'), + onAction: () => Modular.to.pushShifts(tab: 'find'), ) else Column( diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart index 40ab4f4d..7e3ded00 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart @@ -31,6 +31,7 @@ class ShiftsBloc extends Bloc { on(_onLoadShifts); on(_onLoadHistoryShifts); on(_onLoadAvailableShifts); + on(_onLoadFindFirst); on(_onLoadShiftsForRange); on(_onFilterAvailableShifts); } @@ -113,6 +114,65 @@ class ShiftsBloc extends Bloc { } } + Future _onLoadFindFirst( + LoadFindFirstEvent event, + Emitter emit, + ) async { + if (state is! ShiftsLoaded) { + emit(const ShiftsLoaded( + myShifts: [], + pendingShifts: [], + cancelledShifts: [], + availableShifts: [], + historyShifts: [], + availableLoading: false, + availableLoaded: false, + historyLoading: false, + historyLoaded: false, + searchQuery: '', + jobType: 'all', + )); + } + + final currentState = + state is ShiftsLoaded ? state as ShiftsLoaded : null; + if (currentState != null && currentState.availableLoaded) return; + + if (currentState != null) { + emit(currentState.copyWith(availableLoading: true)); + } + + try { + final availableResult = + await getAvailableShifts(const GetAvailableShiftsArguments()); + final loadedState = state is ShiftsLoaded + ? state as ShiftsLoaded + : const ShiftsLoaded( + myShifts: [], + pendingShifts: [], + cancelledShifts: [], + availableShifts: [], + historyShifts: [], + availableLoading: true, + availableLoaded: false, + historyLoading: false, + historyLoaded: false, + searchQuery: '', + jobType: 'all', + ); + emit(loadedState.copyWith( + availableShifts: _filterPastShifts(availableResult), + availableLoading: false, + availableLoaded: true, + )); + } catch (_) { + if (state is ShiftsLoaded) { + final current = state as ShiftsLoaded; + emit(current.copyWith(availableLoading: false)); + } + } + } + Future _onLoadShiftsForRange( LoadShiftsForRangeEvent event, Emitter emit, diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_event.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_event.dart index 7822a249..d25866e0 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_event.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_event.dart @@ -14,6 +14,8 @@ class LoadHistoryShiftsEvent extends ShiftsEvent {} class LoadAvailableShiftsEvent extends ShiftsEvent {} +class LoadFindFirstEvent extends ShiftsEvent {} + class LoadShiftsForRangeEvent extends ShiftsEvent { final DateTime start; final DateTime end; diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart index f42e6d65..052b5f0c 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart @@ -21,6 +21,7 @@ class ShiftsPage extends StatefulWidget { class _ShiftsPageState extends State { late String _activeTab; DateTime? _selectedDate; + bool _prioritizeFind = false; final ShiftsBloc _bloc = Modular.get(); @override @@ -28,12 +29,22 @@ class _ShiftsPageState extends State { super.initState(); _activeTab = widget.initialTab ?? 'myshifts'; _selectedDate = widget.selectedDate; - _bloc.add(LoadShiftsEvent()); + print('ShiftsPage init: initialTab=$_activeTab'); + _prioritizeFind = widget.initialTab == 'find'; + if (_prioritizeFind) { + _bloc.add(LoadFindFirstEvent()); + } else { + _bloc.add(LoadShiftsEvent()); + } if (_activeTab == 'history') { + print('ShiftsPage init: loading history tab'); _bloc.add(LoadHistoryShiftsEvent()); } if (_activeTab == 'find') { - _bloc.add(LoadAvailableShiftsEvent()); + print('ShiftsPage init: entering find tab (not loaded yet)'); + if (!_prioritizeFind) { + _bloc.add(LoadAvailableShiftsEvent()); + } } } @@ -43,6 +54,7 @@ class _ShiftsPageState extends State { if (widget.initialTab != null && widget.initialTab != _activeTab) { setState(() { _activeTab = widget.initialTab!; + _prioritizeFind = widget.initialTab == 'find'; }); } if (widget.selectedDate != null && widget.selectedDate != _selectedDate) { @@ -86,6 +98,7 @@ class _ShiftsPageState extends State { final bool historyLoaded = (state is ShiftsLoaded) ? state.historyLoaded : false; + final bool blockTabsForFind = _prioritizeFind && !availableLoaded; // Note: "filteredJobs" logic moved to FindShiftsTab // Note: Calendar logic moved to MyShiftsTab @@ -124,7 +137,7 @@ class _ShiftsPageState extends State { "My Shifts", UiIcons.calendar, myShifts.length, - enabled: true, + enabled: !blockTabsForFind, ), const SizedBox(width: 8), _buildTab( @@ -143,7 +156,7 @@ class _ShiftsPageState extends State { UiIcons.clock, historyShifts.length, showCount: historyLoaded, - enabled: baseLoaded, + enabled: !blockTabsForFind && baseLoaded, ), ], ), diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart index 3bb8c278..4e914b84 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/widgets/tabs/find_shifts_tab.dart @@ -22,6 +22,12 @@ class _FindShiftsTabState extends State { String _searchQuery = ''; String _jobType = 'all'; + @override + void initState() { + super.initState(); + print('FindShiftsTab init: tab entered, data pending'); + } + Widget _buildFilterTab(String id, String label) { final isSelected = _jobType == id; return GestureDetector( diff --git a/backend/dataconnect/functions/seed.gql b/backend/dataconnect/functions/seed.gql index f59eda6f..073861ae 100644 --- a/backend/dataconnect/functions/seed.gql +++ b/backend/dataconnect/functions/seed.gql @@ -5,7 +5,7 @@ mutation seedAll @transaction { data: { id: "dvpWnaBjT6UksS5lo04hfMTyq1q1" email: "legendary@krowd.com" - fullName: "Krow" + fullName: "Krow Payements" role: USER userRole: "BUSINESS" } @@ -26,7 +26,7 @@ mutation seedAll @transaction { id: "ef69e942-d6e5-48e5-a8bc-69d3faa63b2f" businessName: "Krow" userId: "dvpWnaBjT6UksS5lo04hfMTyq1q1" - contactName: "Krow Ops" + contactName: "Krow Payements" email: "legendary@krowd.com" phone: "+1-818-555-0148" address: "5000 San Jose Street, Granada Hills, CA, USA" From 0c44ec4a3940050dc867a00cb00a0ccb383e94d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:44:07 +0900 Subject: [PATCH 3/6] fixing bug of count my shift --- .../lib/src/presentation/blocs/shifts/shifts_bloc.dart | 10 +++++++++- .../src/presentation/blocs/shifts/shifts_state.dart | 5 +++++ .../shifts/lib/src/presentation/pages/shifts_page.dart | 4 ++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart index 7e3ded00..ff8dd4fd 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_bloc.dart @@ -63,6 +63,7 @@ class ShiftsBloc extends Bloc { availableLoaded: false, historyLoading: false, historyLoaded: false, + myShiftsLoaded: true, searchQuery: '', jobType: 'all', )); @@ -83,6 +84,7 @@ class ShiftsBloc extends Bloc { try { final historyResult = await getHistoryShifts(); emit(currentState.copyWith( + myShiftsLoaded: true, historyShifts: historyResult, historyLoading: false, historyLoaded: true, @@ -129,6 +131,7 @@ class ShiftsBloc extends Bloc { availableLoaded: false, historyLoading: false, historyLoaded: false, + myShiftsLoaded: false, searchQuery: '', jobType: 'all', )); @@ -157,6 +160,7 @@ class ShiftsBloc extends Bloc { availableLoaded: false, historyLoading: false, historyLoaded: false, + myShiftsLoaded: false, searchQuery: '', jobType: 'all', ); @@ -184,7 +188,10 @@ class ShiftsBloc extends Bloc { if (state is ShiftsLoaded) { final currentState = state as ShiftsLoaded; - emit(currentState.copyWith(myShifts: myShiftsResult)); + emit(currentState.copyWith( + myShifts: myShiftsResult, + myShiftsLoaded: true, + )); return; } @@ -198,6 +205,7 @@ class ShiftsBloc extends Bloc { availableLoaded: false, historyLoading: false, historyLoaded: false, + myShiftsLoaded: true, searchQuery: '', jobType: 'all', )); diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_state.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_state.dart index dc670b52..d32e3fba 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_state.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/blocs/shifts/shifts_state.dart @@ -22,6 +22,7 @@ class ShiftsLoaded extends ShiftsState { final bool availableLoaded; final bool historyLoading; final bool historyLoaded; + final bool myShiftsLoaded; final String searchQuery; final String jobType; @@ -35,6 +36,7 @@ class ShiftsLoaded extends ShiftsState { required this.availableLoaded, required this.historyLoading, required this.historyLoaded, + required this.myShiftsLoaded, required this.searchQuery, required this.jobType, }); @@ -49,6 +51,7 @@ class ShiftsLoaded extends ShiftsState { bool? availableLoaded, bool? historyLoading, bool? historyLoaded, + bool? myShiftsLoaded, String? searchQuery, String? jobType, }) { @@ -62,6 +65,7 @@ class ShiftsLoaded extends ShiftsState { availableLoaded: availableLoaded ?? this.availableLoaded, historyLoading: historyLoading ?? this.historyLoading, historyLoaded: historyLoaded ?? this.historyLoaded, + myShiftsLoaded: myShiftsLoaded ?? this.myShiftsLoaded, searchQuery: searchQuery ?? this.searchQuery, jobType: jobType ?? this.jobType, ); @@ -78,6 +82,7 @@ class ShiftsLoaded extends ShiftsState { availableLoaded, historyLoading, historyLoaded, + myShiftsLoaded, searchQuery, jobType, ]; diff --git a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart index 052b5f0c..3d86039d 100644 --- a/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart +++ b/apps/mobile/packages/features/staff/shifts/lib/src/presentation/pages/shifts_page.dart @@ -98,6 +98,9 @@ class _ShiftsPageState extends State { final bool historyLoaded = (state is ShiftsLoaded) ? state.historyLoaded : false; + final bool myShiftsLoaded = (state is ShiftsLoaded) + ? state.myShiftsLoaded + : false; final bool blockTabsForFind = _prioritizeFind && !availableLoaded; // Note: "filteredJobs" logic moved to FindShiftsTab @@ -137,6 +140,7 @@ class _ShiftsPageState extends State { "My Shifts", UiIcons.calendar, myShifts.length, + showCount: myShiftsLoaded, enabled: !blockTabsForFind, ), const SizedBox(width: 8), From 90bacf6695bb3e843b330b10288d9237b50cbfb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Wed, 4 Feb 2026 11:16:54 +0900 Subject: [PATCH 4/6] adding dialog box before delete a hub --- .../presentation/pages/client_hubs_page.dart | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/apps/mobile/packages/features/client/hubs/lib/src/presentation/pages/client_hubs_page.dart b/apps/mobile/packages/features/client/hubs/lib/src/presentation/pages/client_hubs_page.dart index 85f60930..ab56e3ae 100644 --- a/apps/mobile/packages/features/client/hubs/lib/src/presentation/pages/client_hubs_page.dart +++ b/apps/mobile/packages/features/client/hubs/lib/src/presentation/pages/client_hubs_page.dart @@ -95,10 +95,10 @@ class ClientHubsPage extends StatelessWidget { ).add( ClientHubsIdentifyDialogToggled(hub: hub), ), - onDeletePressed: () => - BlocProvider.of( - context, - ).add(ClientHubsDeleteRequested(hub.id)), + onDeletePressed: () => _confirmDeleteHub( + context, + hub, + ), ), ), ], @@ -221,4 +221,51 @@ class ClientHubsPage extends StatelessWidget { ), ); } + + Future _confirmDeleteHub(BuildContext context, Hub hub) async { + final String hubName = hub.name.isEmpty ? 'this hub' : hub.name; + return showDialog( + context: context, + barrierDismissible: false, + builder: (BuildContext dialogContext) { + return AlertDialog( + title: const Text('Confirm Hub Deletion'), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Are you sure you want to delete "$hubName"?'), + const SizedBox(height: UiConstants.space2), + const Text('This action cannot be undone.'), + const SizedBox(height: UiConstants.space2), + Text( + 'Note that if there are any shifts/orders assigned to this hub we shouldn\'t be able to delete the hub.', + style: UiTypography.footnote1r.copyWith( + color: UiColors.textSecondary, + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(), + child: const Text('Cancel'), + ), + TextButton( + onPressed: () { + Navigator.of(dialogContext).pop(); + BlocProvider.of( + context, + ).add(ClientHubsDeleteRequested(hub.id)); + }, + style: TextButton.styleFrom( + foregroundColor: UiColors.destructive, + ), + child: const Text('Delete'), + ), + ], + ); + }, + ); + } } From 2083b5dbff43fc93485714ab4b9b5f7213343b6f Mon Sep 17 00:00:00 2001 From: Achintha Isuru Date: Tue, 3 Feb 2026 22:59:00 -0500 Subject: [PATCH 5/6] refactor: replace Navigator with Modular for dialog dismissal in client hubs and settings actions --- .../hubs/lib/src/presentation/pages/client_hubs_page.dart | 4 ++-- .../widgets/client_settings_page/settings_actions.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/mobile/packages/features/client/hubs/lib/src/presentation/pages/client_hubs_page.dart b/apps/mobile/packages/features/client/hubs/lib/src/presentation/pages/client_hubs_page.dart index ab56e3ae..aa3de3e2 100644 --- a/apps/mobile/packages/features/client/hubs/lib/src/presentation/pages/client_hubs_page.dart +++ b/apps/mobile/packages/features/client/hubs/lib/src/presentation/pages/client_hubs_page.dart @@ -248,15 +248,15 @@ class ClientHubsPage extends StatelessWidget { ), actions: [ TextButton( - onPressed: () => Navigator.of(dialogContext).pop(), + onPressed: () => Modular.to.pop(), child: const Text('Cancel'), ), TextButton( onPressed: () { - Navigator.of(dialogContext).pop(); BlocProvider.of( context, ).add(ClientHubsDeleteRequested(hub.id)); + Modular.to.pop(); }, style: TextButton.styleFrom( foregroundColor: UiColors.destructive, diff --git a/apps/mobile/packages/features/client/settings/lib/src/presentation/widgets/client_settings_page/settings_actions.dart b/apps/mobile/packages/features/client/settings/lib/src/presentation/widgets/client_settings_page/settings_actions.dart index e3e99090..e044d1ec 100644 --- a/apps/mobile/packages/features/client/settings/lib/src/presentation/widgets/client_settings_page/settings_actions.dart +++ b/apps/mobile/packages/features/client/settings/lib/src/presentation/widgets/client_settings_page/settings_actions.dart @@ -83,7 +83,7 @@ class SettingsActions extends StatelessWidget { // Cancel button UiButton.secondary( text: t.common.cancel, - onPressed: () => Navigator.of(dialogContext).pop(), + onPressed: () => Modular.to.pop(), ), ], ), From ada3aa7ef3d9e4e25865671df27c606a7ad2e6e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Salazar?= <73718835+joshrs23@users.noreply.github.com> Date: Thu, 5 Feb 2026 11:50:52 +0900 Subject: [PATCH 6/6] new instance for db validation dataconnect --- backend/dataconnect/dataconnect.dev.yaml | 13 ++ .../dataconnect/dataconnect.validation.yaml | 13 ++ makefiles/dataconnect.mk | 202 +++++++++++++++--- 3 files changed, 197 insertions(+), 31 deletions(-) create mode 100644 backend/dataconnect/dataconnect.dev.yaml create mode 100644 backend/dataconnect/dataconnect.validation.yaml diff --git a/backend/dataconnect/dataconnect.dev.yaml b/backend/dataconnect/dataconnect.dev.yaml new file mode 100644 index 00000000..39e01fdb --- /dev/null +++ b/backend/dataconnect/dataconnect.dev.yaml @@ -0,0 +1,13 @@ +specVersion: "v1" +serviceId: "krow-workforce-db" +location: "us-central1" +schema: + source: "./schema" + datasource: + postgresql: + database: "krow_db" + cloudSql: + instanceId: "krow-sql" + # schemaValidation: "STRICT" # STRICT mode makes Postgres schema match Data Connect exactly. + # schemaValidation: "COMPATIBLE" # COMPATIBLE mode makes Postgres schema compatible with Data Connect. +connectorDirs: ["./connector"] diff --git a/backend/dataconnect/dataconnect.validation.yaml b/backend/dataconnect/dataconnect.validation.yaml new file mode 100644 index 00000000..9e1775d6 --- /dev/null +++ b/backend/dataconnect/dataconnect.validation.yaml @@ -0,0 +1,13 @@ +specVersion: "v1" +serviceId: "krow-workforce-db-validation" +location: "us-central1" +schema: + source: "./schema" + datasource: + postgresql: + database: "krow_db" + cloudSql: + instanceId: "krow-sql-validation" + # schemaValidation: "STRICT" # STRICT mode makes Postgres schema match Data Connect exactly. + # schemaValidation: "COMPATIBLE" # COMPATIBLE mode makes Postgres schema compatible with Data Connect. +connectorDirs: ["./connector"] diff --git a/makefiles/dataconnect.mk b/makefiles/dataconnect.mk index ba7298ef..c2db1fd6 100644 --- a/makefiles/dataconnect.mk +++ b/makefiles/dataconnect.mk @@ -1,6 +1,47 @@ # --- Data Connect / Backend --- -.PHONY: dataconnect-enable-apis dataconnect-init dataconnect-deploy dataconnect-sql-migrate dataconnect-generate-sdk dataconnect-sync dataconnect-bootstrap-db check-gcloud-beta dataconnect-clean +# Usage examples: +# make dataconnect-sync DC_ENV=dev +# make dataconnect-seed DC_ENV=validation +# make dataconnect-clean DC_ENV=validation +# make dataconnect-generate-sdk DC_ENV=dev +# +DC_ENV ?= dev + +DC_SERVICE_DEV := krow-workforce-db +DC_SERVICE_VALIDATION := krow-workforce-db-validation + +ifeq ($(DC_ENV),dev) + DC_SERVICE := $(DC_SERVICE_DEV) +else ifeq ($(DC_ENV),validation) + DC_SERVICE := $(DC_SERVICE_VALIDATION) +else + $(error Invalid DC_ENV '$(DC_ENV)'. Use DC_ENV=dev or DC_ENV=validation) +endif + +.PHONY: dataconnect-enable-apis dataconnect-init dataconnect-deploy dataconnect-sql-migrate dataconnect-generate-sdk dataconnect-sync dataconnect-bootstrap-db check-gcloud-beta dataconnect-clean dataconnect-bootstrap-validation-db dataconnect-file dataconnect-file-validation dataconnect-file-dev dataconnect-seed dataconnect-test + +#creation dataconnect file +dataconnect-file: + @echo "--> Starting creation Firebase Data Connect schema file for service [$(DC_SERVICE)]..." + @test -f backend/dataconnect/dataconnect.$(DC_ENV).yaml || (echo "❌ Missing backend/dataconnect/dataconnect.$(DC_ENV).yaml" && exit 1) + @cp backend/dataconnect/dataconnect.$(DC_ENV).yaml backend/dataconnect/dataconnect.yaml + @echo "✅ Creation Data Connect file completed." + +#creation dev dataconnect file +dataconnect-file-dev: + @echo "--> Starting creation Firebase Data Connect schema file for service [$(DC_SERVICE)]..." + @test -f backend/dataconnect/dataconnect.dev.yaml || (echo "❌ Missing backend/dataconnect/dataconnect.dev.yaml" && exit 1) + @cp backend/dataconnect/dataconnect.dev.yaml backend/dataconnect/dataconnect.yaml + @echo "✅ Creation Data Connect file completed." + + +#creation validation dataconnect file +dataconnect-file-validation: + @echo "--> Starting creation Firebase Data Connect schema file for service [$(DC_SERVICE)]..." + @test -f backend/dataconnect/dataconnect.validation.yaml || (echo "❌ Missing backend/dataconnect/dataconnect.validation.yaml" && exit 1) + @cp backend/dataconnect/dataconnect.validation.yaml backend/dataconnect/dataconnect.yaml + @echo "✅ Creation Data Connect file completed." # Enable all required APIs for Firebase Data Connect + Cloud SQL dataconnect-enable-apis: @@ -14,63 +55,94 @@ dataconnect-enable-apis: @echo "✅ APIs enabled for project [$(GCP_PROJECT_ID)]." # Initialize Firebase Data Connect (interactive wizard). +# use only once per project dataconnect-init: @echo "--> Initializing Firebase Data Connect for alias [$(FIREBASE_ALIAS)] (project: $(GCP_PROJECT_ID))..." @firebase init dataconnect --project $(FIREBASE_ALIAS) @echo "✅ Data Connect initialization command executed. Follow the interactive steps in the CLI." # Deploy Data Connect schemas (GraphQL → Cloud SQL) -dataconnect-deploy: - @echo "--> Deploying Firebase Data Connect schemas to [$(ENV)] (project: $(FIREBASE_ALIAS))..." - @firebase deploy --only dataconnect --project=$(FIREBASE_ALIAS) +dataconnect-deploy: dataconnect-file + @echo "--> Deploying Firebase Data Connect schemas to [$(ENV)] (service: $(DC_SERVICE)) (project: $(FIREBASE_ALIAS))..." + @firebase deploy --only dataconnect:$(DC_SERVICE) --project=$(FIREBASE_ALIAS) @echo "✅ Data Connect deployment completed for [$(ENV)]." # Apply pending SQL migrations for Firebase Data Connect -dataconnect-sql-migrate: - @echo "--> Applying Firebase Data Connect SQL migrations to [$(ENV)] (project: $(FIREBASE_ALIAS))..." +dataconnect-sql-migrate: dataconnect-file + @echo "--> Applying Firebase Data Connect SQL migrations to [$(ENV)] (service: $(DC_SERVICE)) (project: $(FIREBASE_ALIAS))..." @firebase dataconnect:sql:migrate --project=$(FIREBASE_ALIAS) @echo "✅ Data Connect SQL migration completed for [$(ENV)]." # Generate Data Connect client SDK for frontend-web and internal-api-harness -dataconnect-generate-sdk: +dataconnect-generate-sdk: dataconnect-file @echo "--> Generating Firebase Data Connect SDK for web frontend and API harness..." - @firebase dataconnect:sdk:generate --project=$(FIREBASE_ALIAS) + @firebase dataconnect:sdk:generate --project=$(FIREBASE_ALIAS) @echo "✅ Data Connect SDK generation completed for [$(ENV)]." # Unified backend schema update workflow (schema -> deploy -> SDK) -dataconnect-sync: - @echo "--> [1/3] Deploying Data Connect..." - @firebase deploy --only dataconnect --project=$(FIREBASE_ALIAS) - @echo "--> [2/3] Applying SQL migrations..." - @firebase dataconnect:sql:migrate --project=$(FIREBASE_ALIAS) - @echo "--> [3/3] Regenerating SDK..." +dataconnect-sync: dataconnect-file + @echo "--> [1/3] Deploying Data Connect [$(DC_SERVICE)]..." + @firebase deploy --only dataconnect:$(DC_SERVICE) --project=$(FIREBASE_ALIAS) + @echo "--> [2/3] Applying SQL migrations [$(DC_SERVICE)]..." + @firebase dataconnect:sql:migrate $(DC_SERVICE) --project=$(FIREBASE_ALIAS) + @echo "--> [3/3] Regenerating SDK [$(DC_SERVICE)]..." @firebase dataconnect:sdk:generate --project=$(FIREBASE_ALIAS) - @echo "✅ Data Connect SQL, deploy, and SDK generation completed for [$(ENV)]." + @echo "✅ Data Connect SQL, deploy, and SDK generation [$(ENV)]." # Execute seed in Firebase Data Connect -dataconnect-seed: - @echo "--> Exec seed in Firebase Data Connect..." - @firebase dataconnect:execute backend/dataconnect/functions/seed.gql --project=$(FIREBASE_ALIAS) +dataconnect-seed: dataconnect-file + @echo "--> Exec seed in Firebase Data Connect (service: $(DC_SERVICE))..." + @firebase dataconnect:execute backend/dataconnect/functions/seed.gql --project=$(FIREBASE_ALIAS) @echo "✅ Seed executed successfully." # Execute clean, to delete all the data in Firebase Data Connect -dataconnect-clean: - @echo "--> Exec clean all the data in Firebase Data Connect..." - @firebase dataconnect:execute backend/dataconnect/functions/clean.gql --project=$(FIREBASE_ALIAS) +dataconnect-clean: dataconnect-file + @echo "--> Exec clean all the data in Firebase Data Connect (service: $(DC_SERVICE))..." + @firebase dataconnect:execute backend/dataconnect/functions/clean.gql --project=$(FIREBASE_ALIAS) @echo "✅ Clean information executed successfully." # Run tests for Data Connect deployment and migrations -dataconnect-test: - @echo "--> Running Data Connect tests..." - @echo "--> [1/3] Deploying Data Connect..." - @firebase deploy --only dataconnect --project=$(FIREBASE_ALIAS) --dry-run - @echo "--> [2/3] Applying SQL migrations..." +dataconnect-test: dataconnect-file + @echo "--> Running Data Connect tests (service: $(DC_SERVICE))..." + @echo "--> [1/2] Deploying Data Connect..." + @firebase deploy --only dataconnect:$(DC_SERVICE) --project=$(FIREBASE_ALIAS) --dry-run + @echo "--> [2/2] Applying SQL migrations..." @firebase dataconnect:sql:diff --project=$(FIREBASE_ALIAS) @echo "✅ Data Connect tests completed." +dataconnect-backup-dev-to-validation: + @echo "🔍 Validating instances exist in [$(GCP_PROJECT_ID)]..." + @if ! gcloud sql instances describe krow-sql --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \ + echo "❌ Dev instance 'krow-sql' not found in project $(GCP_PROJECT_ID)."; \ + exit 1; \ + fi + @if ! gcloud sql instances describe krow-sql-validation --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \ + echo "❌ Validation instance 'krow-sql-validation' not found in project $(GCP_PROJECT_ID)."; \ + exit 1; \ + fi + @echo "✅ Instances found." + + @echo "🧰 Creating a backup on dev (krow-sql)..." + @gcloud sql backups create --instance=krow-sql --project=$(GCP_PROJECT_ID) >/dev/null + + @echo "🔎 Fetching latest backup ID..." + @LATEST_BACKUP_ID="$$(gcloud sql backups list --instance=krow-sql --project=$(GCP_PROJECT_ID) --sort-by=~endTime --limit=1 --format='value(id)')"; \ + if [ -z "$$LATEST_BACKUP_ID" ]; then \ + echo "❌ Could not find any backup ID for krow-sql."; \ + exit 1; \ + fi; \ + echo "✅ Latest backup ID: $$LATEST_BACKUP_ID"; \ + echo "⚠️ Restoring into validation..."; \ + gcloud sql backups restore $$LATEST_BACKUP_ID \ + --restore-instance=krow-sql-validation \ + --backup-instance=krow-sql \ + --project=$(GCP_PROJECT_ID) + + @echo "🎉 Done." + # ------------------------------------------------------------------- -# ONE-TIME FULL SETUP FOR CLOUD SQL + DATA CONNECT +# ONE-TIME FULL SETUP FOR CLOUD SQL + DATA CONNECT DEV # ------------------------------------------------------------------- # Check if gcloud and beta group are available @@ -85,7 +157,7 @@ check-gcloud-beta: } @echo "✅ gcloud CLI and 'gcloud beta' are available." -dataconnect-bootstrap-db: check-gcloud-beta +dataconnect-bootstrap-db: dataconnect-file-dev check-gcloud-beta @echo "🔍 Checking if Cloud SQL instance krow-sql already exists in [$(GCP_PROJECT_ID)]..." @if gcloud sql instances describe krow-sql --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \ echo "⚠️ Cloud SQL instance 'krow-sql' already exists in project $(GCP_PROJECT_ID)."; \ @@ -112,7 +184,7 @@ dataconnect-bootstrap-db: check-gcloud-beta @echo "⚠️ Creating Firebase Data Connect service identity..." gcloud beta services identity create \ --service=firebasedataconnect.googleapis.com \ - --project=$(GCP_PROJECT_ID) + --project=$(GCP_PROJECT_ID) @echo "⚠️ Enabling IAM authentication on Cloud SQL instance krow-sql..." gcloud sql instances patch krow-sql \ @@ -124,9 +196,77 @@ dataconnect-bootstrap-db: check-gcloud-beta firebase dataconnect:sql:setup krow-workforce-db --project=$(FIREBASE_ALIAS) @echo "⚠️ Deploying initial Data Connect configuration..." - @firebase deploy --only dataconnect --project=$(FIREBASE_ALIAS) + @firebase deploy --only dataconnect:$(DC_SERVICE_DEV) --project=$(FIREBASE_ALIAS) @echo "⚠️ Generating initial Data Connect SDK..." - @firebase dataconnect:sdk:generate --project=$(FIREBASE_ALIAS) + @firebase dataconnect:sdk:generate --project=$(FIREBASE_ALIAS) @echo "🎉 Cloud SQL + Data Connect bootstrap completed successfully!" + + +# ------------------------------------------------------------------- +# ONE-TIME FULL SETUP FOR CLOUD SQL + DATA CONNECT VALIDATEION +# ------------------------------------------------------------------- + +# Creates: krow-sql-validation + krow_db (inside it) + links service krow-workforce-db-validation +# Then clones data from krow-sql -> krow-sql-validation via backup/restore + +dataconnect-bootstrap-validation-database: dataconnect-file-validation + @echo "🔍 Checking if Cloud SQL instance krow-sql-validation already exists in [$(GCP_PROJECT_ID)]..." + @if gcloud sql instances describe krow-sql-validation --project=$(GCP_PROJECT_ID) >/dev/null 2>&1; then \ + echo "⚠️ Cloud SQL instance 'krow-sql-validation' already exists in project $(GCP_PROJECT_ID)."; \ + echo " If you need to recreate it, delete the instance manually first."; \ + exit 1; \ + fi + + @echo "⚠️ Creating Cloud SQL instance krow-sql-validation (tier: $(SQL_TIER))..." + gcloud sql instances create krow-sql-validation \ + --database-version=POSTGRES_15 \ + --tier=$(SQL_TIER) \ + --region=us-central1 \ + --storage-size=10 \ + --storage-auto-increase \ + --availability-type=zonal \ + --backup-start-time=03:00 \ + --project=$(GCP_PROJECT_ID) + + @echo "⚠️ Creating Cloud SQL database krow_db on krow-sql-validation..." + gcloud sql databases create krow_db \ + --instance=krow-sql-validation \ + --project=$(GCP_PROJECT_ID) + + @echo "⚠️ Enabling IAM authentication on Cloud SQL instance krow-sql-validation..." + gcloud sql instances patch krow-sql-validation \ + --project=$(GCP_PROJECT_ID) \ + --database-flags=cloudsql.iam_authentication=on \ + --quiet + + @echo "🔁 Creating a backup on dev instance (krow-sql) to clone data into validation..." + @echo " (Prereq: krow-sql must already exist and be stable.)" + gcloud sql backups create --instance=krow-sql --project=$(GCP_PROJECT_ID) + echo "✅ Backup creation started. Operation: $$BACKUP_OP" + + @echo "🔎 Fetching latest backup ID from krow-sql..." + @BACKUP_ID="$$(gcloud sql backups list --instance=krow-sql --project=$(GCP_PROJECT_ID) --limit=1 --sort-by=~endTime --format='value(id)')"; \ + if [ -z "$$BACKUP_ID" ]; then \ + echo "❌ Could not find a backup ID for krow-sql."; \ + exit 1; \ + fi; \ + echo "✅ Latest backup ID: $$BACKUP_ID"; \ + echo "⚠️ Restoring backup into krow-sql-validation..."; \ + gcloud sql backups restore $$BACKUP_ID \ + --restore-instance=krow-sql-validation \ + --backup-instance=krow-sql \ + --project=$(GCP_PROJECT_ID) + + @echo "⚠️ Linking Data Connect service (krow-workforce-db-validation) with Cloud SQL..." + @echo " When prompted, select instance: krow-sql-validation and database: krow_db" + firebase dataconnect:sql:setup krow-workforce-db-validation --project=$(FIREBASE_ALIAS) + + @echo "⚠️ Deploying Data Connect configuration ($(DC_SERVICE_VALIDATION))..." + @firebase deploy --only dataconnect:$(DC_SERVICE_VALIDATION) --project=$(FIREBASE_ALIAS) + + @echo "⚠️ Generating Data Connect SDK ($(DC_SERVICE))..." + @firebase dataconnect:sdk:generate --project=$(FIREBASE_ALIAS) + + @echo "🎉 Validation Cloud SQL + Data Connect bootstrap completed successfully!"