Add Supabase implementation for CRUD #238
							
								
								
									
										46
									
								
								packages/wyatt_crud_bloc/example/lib/main_supabase.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								packages/wyatt_crud_bloc/example/lib/main_supabase.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| // Copyright (C) 2022 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:crud_bloc_example/app.dart'; | ||||
| import 'package:crud_bloc_example/app_bloc_observer.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_bloc/flutter_bloc.dart'; | ||||
| import 'package:supabase_flutter/supabase_flutter.dart'; | ||||
| import 'package:wyatt_crud_bloc/wyatt_crud_bloc.dart'; | ||||
| import 'package:wyatt_crud_bloc_supabase/wyatt_crud_bloc_supabase.dart'; | ||||
| 
 | ||||
| const supabaseUrl = 'https://yarkjotsulnccxdkztiv.supabase.co'; | ||||
| const supabaseKey = String.fromEnvironment('SUPABASE_KEY'); | ||||
| 
 | ||||
| Future<void> main() async { | ||||
|   WidgetsFlutterBinding.ensureInitialized(); | ||||
|   Bloc.observer = AppBlocObserver(); | ||||
| 
 | ||||
|   await Supabase.initialize( | ||||
|     url: supabaseUrl, | ||||
|     anonKey: supabaseKey, | ||||
|   ); | ||||
| 
 | ||||
|   final CrudDataSource crudDataSource = CrudDataSourceSupabaseImpl( | ||||
|     'users', | ||||
|     id: (_, json) => json['id'] as String, | ||||
|     getIdKey: (_) => 'id', | ||||
|   ); | ||||
| 
 | ||||
|   runApp(MyApp( | ||||
|     crudDataSource: crudDataSource, | ||||
|   )); | ||||
| } | ||||
| @ -33,11 +33,14 @@ dependencies: | ||||
|   equatable: ^2.0.5 | ||||
|   firebase_core: ^2.22.0 | ||||
|   cloud_firestore: ^4.13.0 | ||||
|   supabase_flutter: ^2.3.4 | ||||
|   flutter_bloc: ^8.1.3 | ||||
|   wyatt_crud_bloc: | ||||
|     path: "../" | ||||
|   wyatt_crud_bloc_firestore: | ||||
|     path: "../wyatt_crud_bloc_firestore" | ||||
|   wyatt_crud_bloc_supabase: | ||||
|     path: "../wyatt_crud_bloc_supabase" | ||||
|    | ||||
|   wyatt_architecture: | ||||
|     hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ | ||||
|  | ||||
							
								
								
									
										1
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/.gitignore
									
									
									
									
										vendored
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/.gitignore
									
									
									
									
										vendored
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| ../../../.gitignore | ||||
							
								
								
									
										10
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/.metadata
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/.metadata
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| # This file tracks properties of this Flutter project. | ||||
| # Used by Flutter tool to assess capabilities and perform upgrades etc. | ||||
| # | ||||
| # This file should be version controlled and should not be manually edited. | ||||
| 
 | ||||
| version: | ||||
|   revision: "d211f42860350d914a5ad8102f9ec32764dc6d06" | ||||
|   channel: "stable" | ||||
| 
 | ||||
| project_type: package | ||||
							
								
								
									
										1
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/.pubignore
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/.pubignore
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| ../../../.pubignore | ||||
							
								
								
									
										1
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/AUTHORS
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/AUTHORS
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| ../../../AUTHORS | ||||
| @ -0,0 +1,3 @@ | ||||
| ## 0.0.1 | ||||
| 
 | ||||
| * TODO: Describe initial release. | ||||
							
								
								
									
										1
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/LICENSE
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/LICENSE
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| ../../../LICENSE | ||||
							
								
								
									
										39
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								packages/wyatt_crud_bloc/wyatt_crud_bloc_supabase/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,39 @@ | ||||
| <!-- | ||||
| This README describes the package. If you publish this package to pub.dev, | ||||
| this README's contents appear on the landing page for your package. | ||||
| 
 | ||||
| For information about how to write a good package README, see the guide for | ||||
| [writing package pages](https://dart.dev/guides/libraries/writing-package-pages). | ||||
| 
 | ||||
| For general information about developing packages, see the Dart guide for | ||||
| [creating packages](https://dart.dev/guides/libraries/create-library-packages) | ||||
| and the Flutter guide for | ||||
| [developing packages and plugins](https://flutter.dev/developing-packages). | ||||
| --> | ||||
| 
 | ||||
| TODO: Put a short description of the package here that helps potential users | ||||
| know whether this package might be useful for them. | ||||
| 
 | ||||
| ## Features | ||||
| 
 | ||||
| TODO: List what your package can do. Maybe include images, gifs, or videos. | ||||
| 
 | ||||
| ## Getting started | ||||
| 
 | ||||
| TODO: List prerequisites and provide or point to information on how to | ||||
| start using the package. | ||||
| 
 | ||||
| ## Usage | ||||
| 
 | ||||
| TODO: Include short and useful examples for package users. Add longer examples | ||||
| to `/example` folder. | ||||
| 
 | ||||
| ```dart | ||||
| const like = 'sample'; | ||||
| ``` | ||||
| 
 | ||||
| ## Additional information | ||||
| 
 | ||||
| TODO: Tell users more about the package: where to find more information, how to | ||||
| contribute to the package, how to file issues, what response they can expect | ||||
| from the package authors, and more. | ||||
| @ -0,0 +1 @@ | ||||
| include: package:wyatt_analysis/analysis_options.flutter.yaml | ||||
| @ -0,0 +1,255 @@ | ||||
| // Copyright (C) 2023 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| import 'package:supabase_flutter/supabase_flutter.dart'; | ||||
| import 'package:wyatt_crud_bloc/wyatt_crud_bloc.dart' as crud show Query; | ||||
| import 'package:wyatt_crud_bloc/wyatt_crud_bloc.dart' hide Query; | ||||
| 
 | ||||
| typedef QueryBuilder = PostgrestTransformBuilder<List<Map<String, dynamic>>>; | ||||
| typedef StreamBuilder = SupabaseStreamBuilder; | ||||
| 
 | ||||
| /// {@template crud_data_source_supabase_impl} | ||||
| /// A concrete implementation of [CrudDataSource] that uses | ||||
| /// [Supabase] as the data source. | ||||
| /// {@endtemplate} | ||||
| class CrudDataSourceSupabaseImpl extends CrudDataSource { | ||||
|   /// {@macro crud_data_source_supabase_impl} | ||||
|   CrudDataSourceSupabaseImpl( | ||||
|     String table, { | ||||
|     required super.id, | ||||
|     this.getIdKey, | ||||
|     SupabaseClient? supabase, | ||||
|   }) : _supabase = supabase ?? Supabase.instance.client { | ||||
|     _supabaseQueryBuilder = _supabase.from(table); | ||||
|   } | ||||
| 
 | ||||
|   final SupabaseClient _supabase; | ||||
|   late SupabaseQueryBuilder _supabaseQueryBuilder; | ||||
| 
 | ||||
|   /// Get the id field name depending on the operation type. | ||||
|   /// If null, 'id' will be used. | ||||
|   final String? Function( | ||||
|     OperationType operationType, | ||||
|   )? getIdKey; | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> create(Map<String, dynamic> object, {String? id}) { | ||||
|     if (id != null) { | ||||
|       return _supabaseQueryBuilder.upsert(object); | ||||
|     } else { | ||||
|       final String? id = this.id( | ||||
|         OperationType.create, | ||||
|         object, | ||||
|       ); | ||||
| 
 | ||||
|       if (id != null) { | ||||
|         return _supabaseQueryBuilder.upsert(object); | ||||
|       } | ||||
|       return _supabaseQueryBuilder.insert(object); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> delete(String id) { | ||||
|     final String idKey = getIdKey?.call(OperationType.delete) ?? 'id'; | ||||
|     return _supabaseQueryBuilder.delete().match({idKey: id}); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> deleteAll() async { | ||||
|     final List<Map<String, dynamic>> snapshots = | ||||
|         await _supabaseQueryBuilder.select(); | ||||
|     for (final Map<String, dynamic> snapshot in snapshots) { | ||||
|       final String idKey = getIdKey?.call(OperationType.delete) ?? 'id'; | ||||
|       await _supabaseQueryBuilder.delete().match({idKey: snapshot[idKey]}); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<Map<String, dynamic>?> get(String id) async { | ||||
|     final String idKey = getIdKey?.call(OperationType.read) ?? 'id'; | ||||
|     final List<Map<String, dynamic>> snapshots = | ||||
|         await _supabaseQueryBuilder.select().match({idKey: id}); | ||||
|     return snapshots.firstOrNull; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<List<Map<String, dynamic>?>> getAll() async => | ||||
|       _supabaseQueryBuilder.select(); | ||||
| 
 | ||||
|   @override | ||||
|   Future<List<Map<String, dynamic>?>> search( | ||||
|     List<crud.Query> conditions, | ||||
|   ) async { | ||||
|     final PostgrestFilterBuilder<List<Map<String, dynamic>>> query = | ||||
|         _supabaseQueryBuilder.select(); | ||||
| 
 | ||||
|     final filter = conditions.whereType<WhereQuery<dynamic>>().firstOrNull; | ||||
|     final modifiers = [ | ||||
|       ...conditions.whereType<LimitQuery>(), | ||||
|       ...conditions.whereType<OrderByQuery>(), | ||||
|     ]; | ||||
| 
 | ||||
|     PostgrestTransformBuilder<List<Map<String, dynamic>>>? queryTransform; | ||||
| 
 | ||||
|     if (filter != null) { | ||||
|       queryTransform = _queryFilterParser(filter, query); | ||||
|     } | ||||
| 
 | ||||
|     for (final condition in modifiers) { | ||||
|       queryTransform = _queryModifierParser(condition, queryTransform ?? query); | ||||
|     } | ||||
| 
 | ||||
|     return query; | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Stream<List<Map<String, dynamic>?>> stream({ | ||||
|     String? id, | ||||
|     List<crud.Query>? conditions, | ||||
|     bool includeMetadataChanges = false, | ||||
|   }) { | ||||
|     // TODO(hpcl): use conditions!! | ||||
|     final stream = _supabaseQueryBuilder | ||||
|         .stream(primaryKey: [getIdKey?.call(OperationType.read) ?? 'id']); | ||||
| 
 | ||||
|     if (id != null) { | ||||
|       return stream.eq( | ||||
|         getIdKey?.call(OperationType.read) ?? 'id', | ||||
|         id, | ||||
|       ); | ||||
|     } else { | ||||
|       return stream; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> update( | ||||
|     String id, { | ||||
|     Map<String, dynamic>? object, | ||||
|   }) async { | ||||
|     if (object != null) { | ||||
|       final String idKey = getIdKey?.call(OperationType.update) ?? 'id'; | ||||
|       return _supabaseQueryBuilder.update(object).match({idKey: id}); | ||||
|     } else { | ||||
|       throw Exception('You must provide data to update'); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> updateAll(Map<String, Object?>? data) async { | ||||
|     if (data == null) { | ||||
|       throw Exception('You must provide data to update'); | ||||
|     } | ||||
|     return _supabaseQueryBuilder.update(data); | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder _queryFilterParser( | ||||
|     crud.Query condition, | ||||
|     PostgrestFilterBuilder<List<Map<String, dynamic>>> query, | ||||
|   ) { | ||||
|     if (condition is WhereQuery) { | ||||
|       switch (condition.type) { | ||||
|         case WhereQueryType.isEqualTo: | ||||
|           return query.eq(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.isNotEqualTo: | ||||
|           return query.neq(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.isLessThan: | ||||
|           return query.lt(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.isLessThanOrEqualTo: | ||||
|           return query.lte(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.isGreaterThan: | ||||
|           return query.gt(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.isGreaterThanOrEqualTo: | ||||
|           return query.gte(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.arrayContains: | ||||
|           return query.contains(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.whereIn: | ||||
|           return query.inFilter( | ||||
|             condition.field, | ||||
|             condition.value as List<Object>, | ||||
|           ); | ||||
|         case WhereQueryType.whereNotIn: | ||||
|           return query.not( | ||||
|             condition.field, | ||||
|             'in', | ||||
|             condition.value as List<Object>, | ||||
|           ); | ||||
|         case WhereQueryType.isNull: | ||||
|           if (condition.value as bool) { | ||||
|             return query.isFilter(condition.field, null); | ||||
|           } else { | ||||
|             return query.not(condition.field, 'is', null); | ||||
|           } | ||||
|         case WhereQueryType.arrayContainsAny: | ||||
|           throw UnsupportedError( | ||||
|             '${condition.type} is not supported by Supabase', | ||||
|           ); | ||||
|       } | ||||
|     } | ||||
|     return query; | ||||
|   } | ||||
| 
 | ||||
|   StreamBuilder _streamFilterParser( | ||||
|     crud.Query condition, | ||||
|     SupabaseStreamFilterBuilder query, | ||||
|   ) { | ||||
|     if (condition is WhereQuery) { | ||||
|       switch (condition.type) { | ||||
|         case WhereQueryType.isEqualTo: | ||||
|           return query.eq(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.isNotEqualTo: | ||||
|           return query.neq(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.isLessThan: | ||||
|           return query.lt(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.isLessThanOrEqualTo: | ||||
|           return query.lte(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.isGreaterThan: | ||||
|           return query.gt(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.isGreaterThanOrEqualTo: | ||||
|           return query.gte(condition.field, condition.value as Object); | ||||
|         case WhereQueryType.whereIn: | ||||
|           return query.inFilter( | ||||
|             condition.field, | ||||
|             condition.value as List<Object>, | ||||
|           ); | ||||
|         case WhereQueryType.arrayContains: | ||||
|         case WhereQueryType.arrayContainsAny: | ||||
|         case WhereQueryType.whereNotIn: | ||||
|         case WhereQueryType.isNull: | ||||
|           throw UnsupportedError( | ||||
|             '${condition.type} is not supported in stream by Supabase', | ||||
|           ); | ||||
|       } | ||||
|     } | ||||
|     return query; | ||||
|   } | ||||
| 
 | ||||
|   QueryBuilder _queryModifierParser( | ||||
|     crud.Query condition, | ||||
|     QueryBuilder query, | ||||
|   ) { | ||||
|     if (condition is LimitQuery) { | ||||
|       return query.limit(condition.limit); | ||||
|     } else if (condition is OrderByQuery) { | ||||
|       return query.order( | ||||
|         condition.field, | ||||
|         ascending: condition.ascending, | ||||
|       ); | ||||
|     } | ||||
|     return query; | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,20 @@ | ||||
| // Copyright (C) 2023 WYATT GROUP | ||||
| // Please see the AUTHORS file for details. | ||||
| // | ||||
| // This program is free software: you can redistribute it and/or modify | ||||
| // it under the terms of the GNU General Public License as published by | ||||
| // the Free Software Foundation, either version 3 of the License, or | ||||
| // any later version. | ||||
| // | ||||
| // This program is distributed in the hope that it will be useful, | ||||
| // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||||
| // GNU General Public License for more details. | ||||
| // | ||||
| // You should have received a copy of the GNU General Public License | ||||
| // along with this program. If not, see <https://www.gnu.org/licenses/>. | ||||
| 
 | ||||
| /// Create/Read/Update/Delete BLoC implementation for Supabase. | ||||
| library wyatt_crud_bloc_supabase; | ||||
| 
 | ||||
| export 'src/data/crud_data_source_supabase_impl.dart'; | ||||
| @ -0,0 +1,35 @@ | ||||
| name: wyatt_crud_bloc_supabase | ||||
| description: Create/Read/Update/Delete implementation for Supabase | ||||
| repository: https://git.wyatt-studio.fr/Wyatt-FOSS/wyatt-packages/src/branch/master/packages/wyatt_crud_bloc | ||||
| version: 0.2.0 | ||||
| 
 | ||||
| publish_to: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub | ||||
| 
 | ||||
| environment: | ||||
|   sdk: ">=3.0.0 <4.0.0" | ||||
| 
 | ||||
| dependencies: | ||||
|   flutter: { sdk: flutter } | ||||
| 
 | ||||
|   flutter_bloc: ^8.1.1 | ||||
|   equatable: ^2.0.5 | ||||
|   supabase_flutter: ^2.3.4 | ||||
| 
 | ||||
|   wyatt_architecture: | ||||
|     hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ | ||||
|     version: ^0.2.0+1 | ||||
| 
 | ||||
|   wyatt_type_utils: | ||||
|     hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ | ||||
|     version: ^0.0.5 | ||||
|    | ||||
|   wyatt_crud_bloc: | ||||
|     hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ | ||||
|     version: ^0.2.0 | ||||
| 
 | ||||
| dev_dependencies: | ||||
|   flutter_test: { sdk: flutter } | ||||
|   bloc_test: ^9.1.0 | ||||
|   wyatt_analysis: | ||||
|     hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub | ||||
|     version: ^2.6.1 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user