Add Supabase implementation for CRUD #238

Open
hugo wants to merge 1 commits from feat/add-supabase-support into master
13 changed files with 416 additions and 0 deletions

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

View File

@ -33,11 +33,14 @@ dependencies:
equatable: ^2.0.5 equatable: ^2.0.5
firebase_core: ^2.22.0 firebase_core: ^2.22.0
cloud_firestore: ^4.13.0 cloud_firestore: ^4.13.0
supabase_flutter: ^2.3.4
flutter_bloc: ^8.1.3 flutter_bloc: ^8.1.3
wyatt_crud_bloc: wyatt_crud_bloc:
path: "../" path: "../"
wyatt_crud_bloc_firestore: wyatt_crud_bloc_firestore:
path: "../wyatt_crud_bloc_firestore" path: "../wyatt_crud_bloc_firestore"
wyatt_crud_bloc_supabase:
path: "../wyatt_crud_bloc_supabase"
wyatt_architecture: wyatt_architecture:
hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/ hosted: https://git.wyatt-studio.fr/api/packages/Wyatt-FOSS/pub/

View File

@ -0,0 +1 @@
../../../.gitignore

View 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

View File

@ -0,0 +1 @@
../../../.pubignore

View File

@ -0,0 +1 @@
../../../AUTHORS

View File

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

View File

@ -0,0 +1 @@
../../../LICENSE

View 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.

View File

@ -0,0 +1 @@
include: package:wyatt_analysis/analysis_options.flutter.yaml

View File

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

View File

@ -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';

View File

@ -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