Skip to content

Commit

Permalink
fix(supabase): pub validation and doc fixes (#444)
Browse files Browse the repository at this point in the history
  • Loading branch information
tshedor authored Sep 20, 2024
1 parent b925741 commit 3e182ec
Show file tree
Hide file tree
Showing 43 changed files with 378 additions and 326 deletions.
1 change: 1 addition & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
- [Model Config](offline_first/models.md)
- [Field Config](offline_first/fields.md)
- [Testing](offline_first/testing.md)
- [With Supabase](offline_first/offline_first_with_supabase_repository.md)
- [With GraphQL](offline_first/offline_first_with_graphql_repository.md)
- [With Rest](offline_first/offline_first_with_rest_repository.md)
- [Offline Queue](offline_first/offline_queue.md)
10 changes: 7 additions & 3 deletions docs/offline_first/offline_first_with_supabase_repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,14 @@ Field types of classes that `extends OfflineFirstWithSupabaseModel` will automat
```dart
class User extends OfflineFirstWithSupabaseModel {
// The foreign key is a relation to the `id` column of the Address table
@Supabase(name: 'address_id')
// Help the SQLite provider connect the association locally to the one provided from remote
@OfflineFirst(where: {'id': "data['address']['id']"})
@Supabase(foreignKey: 'address_id')
final Address address;
// If the association will be created by the app, specify
// a field that maps directly to the foreign key column
// so that Brick can notify Supabase of the association.
@Sqlite(ignore: true)
String get addressId => address.id;
}
class Address extends OfflineFirstWithSupabaseModel{
Expand Down
2 changes: 1 addition & 1 deletion docs/offline_first/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,4 @@ setUpAll() async {

## OfflineFirstWithSupabase

See [Supabase Testing](../supabase/testing.md)
See [Supabase Testing](https://getdutchie.github.io/brick/#/supabase/testing)
9 changes: 6 additions & 3 deletions docs/supabase/fields.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,15 @@ final String lastName;

?> By default, Brick renames fields to be snake case when translating to Supabase, but you can change this default in the `@SupabaseSerializable(fieldRename:)` annotation that [decorates models](models.md).

When the annotated field type extends the model's type, the Supabase column should be a foreign key.
!> **Do not use** `name` when annotating an association. Instead, use `foreignKey`.

### `@Supabase(foreignKey:)`

When the annotated field references a `OfflineFirstWithSupabaseModel`, a foreign key can be specified. Supabase's PostgREST API can usually determine the association without specifying the foreign key. However, [when multiple foreign keys exist](https://supabase.com/docs/guides/database/joins-and-nesting?queryGroups=language&language=dart#specifying-the-on-clause-for-joins-with-multiple-foreign-keys) to the same table, guiding Brick to use the right foreign key is required.

```dart
class User extends OfflineFirstWithSupabaseModel{
// The foreign key is a relation to the `id` column of the Address table
@Supabase(name: 'address_id')
@Supabase(foreignKey: 'address_id')
final Address address;
}
Expand Down
49 changes: 49 additions & 0 deletions example_supabase/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutterplugins
.flutterpluginsdependencies
.pubcache/
.pub/
/build/

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
windows/*
web/*
macos/*
linux/*
android/*
ios/*
45 changes: 45 additions & 0 deletions example_supabase/.metadata
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 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: "2663184aa79047d0a33a14a3b607954f8fdd8730"
channel: "stable"

project_type: app

# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: android
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: ios
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: linux
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: macos
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: web
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
- platform: windows
create_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730
base_revision: 2663184aa79047d0a33a14a3b607954f8fdd8730

# User provided section

# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- "lib/main.dart"
- "ios/Runner.xcodeproj/project.pbxproj"
38 changes: 21 additions & 17 deletions example_supabase/README.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,39 @@
# Brick with Supabase Example

This minimal example demonstrates how to use Brick with [Supabase](https://supabase.com/). Follow the instructions below to get started.

## Setting up the Supabase project

1. **Create the table**: Run the following SQL command in your Supabase SQL editor to create the customers table:
1. **Create the table**: Run the following SQL command in your Supabase SQL editor to create the customers and pizzas table:

```sql
CREATE TABLE customers (
id UUID PRIMARY KEY,
first_name text NOT NULL,
last_name text NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
id UUID PRIMARY KEY,
first_name text NOT NULL,
last_name text NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE pizzas (
id UUID PRIMARY KEY,
frozen boolean NOT NULL DEFAULT false,
customer_id UUID NOT NULL
);

ALTER TABLE pizzas ADD CONSTRAINT fk_customer FOREIGN KEY (customer_id) REFERENCES customers (id);
```

2. **Insert Dummy Data**: Insert some dummy data into the customers table by running the following SQL command
2. **Insert Dummy Data**: Insert some dummy data:

```sql
INSERT INTO customers (id, first_name, last_name, created_at) VALUES
('a8098c1a-f86e-11da-bd1a-00112444be1e', 'Bruce', 'Fortner', NOW()),
('b8098c1a-f86e-11da-bd1a-00112444be1e', 'Jane', 'Smith', NOW()),
('c8098c1a-f86e-11da-bd1a-00112444be1e', 'Alice', 'Johnson', NOW());
('a8098c1a-f86e-11da-bd1a-00112444be1e', 'Bruce', 'Fortner', NOW()),
('b8098c1a-f86e-11da-bd1a-00112444be1e', 'Jane', 'Smith', NOW()),
('c8098c1a-f86e-11da-bd1a-00112444be1e', 'Alice', 'Johnson', NOW());
INSERT INTO pizzas (id, frozen, customer_id) VALUES
('d8098c1a-f86e-11da-bd1a-00112444be1e', TRUE, 'a8098c1a-f86e-11da-bd1a-00112444be1e'),
('e8098c1a-f86e-11da-bd1a-00112444be1e', FALSE, 'b8098c1a-f86e-11da-bd1a-00112444be1e'),
('f8098c1a-f86e-11da-bd1a-00112444be1e', TRUE, 'c8098c1a-f86e-11da-bd1a-00112444be1e');
```

3. **Enable Anonymous Sign-Ins**: Go to your Supabase dashboard, navigate to Settings > Authentication > User Signups, and enable anonymous sign-ins.

## Setting up the Flutter example project

4. **Update Variables**: Update `main.dart` with your Supabase project URL and anonymous key. You can find these values in the Supabase dashboard under Settings > API.

## Running the Flutter app

5. **Run the Flutter Project**: This example supports iOS and Android. Make sure run `flutter create .` first.
73 changes: 6 additions & 67 deletions example_supabase/lib/brick/adapters/customer_adapter.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,22 @@ part of '../brick.g.dart';
Future<Customer> _$CustomerFromSupabase(Map<String, dynamic> data,
{required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async {
return Customer(
id: data['id'] as int?,
id: data['id'] as String,
firstName: data['first_name'] as String?,
lastName: data['last_name'] as String?,
pizzas: await Future.wait<Pizza>(data['pizzas']
?.map(
(d) => PizzaAdapter().fromSupabase(d, provider: provider, repository: repository))
.toList()
.cast<Future<Pizza>>() ??
[]));
lastName: data['last_name'] as String?);
}

Future<Map<String, dynamic>> _$CustomerToSupabase(Customer instance,
{required SupabaseProvider provider, OfflineFirstWithSupabaseRepository? repository}) async {
return {
'id': instance.id,
'first_name': instance.firstName,
'last_name': instance.lastName,
'pizzas': await Future.wait<Map<String, dynamic>>(instance.pizzas
.map((s) => PizzaAdapter().toSupabase(s, provider: provider, repository: repository))
.toList())
};
return {'id': instance.id, 'first_name': instance.firstName, 'last_name': instance.lastName};
}

Future<Customer> _$CustomerFromSqlite(Map<String, dynamic> data,
{required SqliteProvider provider, OfflineFirstWithSupabaseRepository? repository}) async {
return Customer(
id: data['id'] == null ? null : data['id'] as int?,
id: data['id'] as String,
firstName: data['first_name'] == null ? null : data['first_name'] as String?,
lastName: data['last_name'] == null ? null : data['last_name'] as String?,
pizzas: (await provider.rawQuery(
'SELECT DISTINCT `f_Pizza_brick_id` FROM `_brick_Customer_pizzas` WHERE l_Customer_brick_id = ?',
[data['_brick_id'] as int]).then((results) {
final ids = results.map((r) => r['f_Pizza_brick_id']);
return Future.wait<Pizza>(ids.map((primaryKey) => repository!
.getAssociation<Pizza>(
Query.where('primaryKey', primaryKey, limit1: true),
)
.then((r) => r!.first)));
}))
.toList()
.cast<Pizza>())
lastName: data['last_name'] == null ? null : data['last_name'] as String?)
..primaryKey = data['_brick_id'] as int;
}

Expand Down Expand Up @@ -74,12 +49,6 @@ class CustomerAdapter extends OfflineFirstWithSupabaseAdapter<Customer> {
'lastName': const RuntimeSupabaseColumnDefinition(
association: false,
columnName: 'last_name',
),
'pizzas': const RuntimeSupabaseColumnDefinition(
association: true,
columnName: 'pizzas',
associationType: Pizza,
associationIsNullable: false,
)
};
@override
Expand All @@ -98,7 +67,7 @@ class CustomerAdapter extends OfflineFirstWithSupabaseAdapter<Customer> {
association: false,
columnName: 'id',
iterable: false,
type: int,
type: String,
),
'firstName': const RuntimeSqliteColumnDefinition(
association: false,
Expand All @@ -111,12 +80,6 @@ class CustomerAdapter extends OfflineFirstWithSupabaseAdapter<Customer> {
columnName: 'last_name',
iterable: false,
type: String,
),
'pizzas': const RuntimeSqliteColumnDefinition(
association: true,
columnName: 'pizzas',
iterable: true,
type: Pizza,
)
};
@override
Expand All @@ -134,30 +97,6 @@ class CustomerAdapter extends OfflineFirstWithSupabaseAdapter<Customer> {

@override
final String tableName = 'Customer';
@override
Future<void> afterSave(instance, {required provider, repository}) async {
if (instance.primaryKey != null) {
final pizzasOldColumns = await provider.rawQuery(
'SELECT `f_Pizza_brick_id` FROM `_brick_Customer_pizzas` WHERE `l_Customer_brick_id` = ?',
[instance.primaryKey]);
final pizzasOldIds = pizzasOldColumns.map((a) => a['f_Pizza_brick_id']);
final pizzasNewIds = instance.pizzas.map((s) => s.primaryKey).whereType<int>();
final pizzasIdsToDelete = pizzasOldIds.where((id) => !pizzasNewIds.contains(id));

await Future.wait<void>(pizzasIdsToDelete.map((id) async {
return await provider.rawExecute(
'DELETE FROM `_brick_Customer_pizzas` WHERE `l_Customer_brick_id` = ? AND `f_Pizza_brick_id` = ?',
[instance.primaryKey, id]).catchError((e) => null);
}));

await Future.wait<int?>(instance.pizzas.map((s) async {
final id = s.primaryKey ?? await provider.upsert<Pizza>(s, repository: repository);
return await provider.rawInsert(
'INSERT OR IGNORE INTO `_brick_Customer_pizzas` (`l_Customer_brick_id`, `f_Pizza_brick_id`) VALUES (?, ?)',
[instance.primaryKey, id]);
}));
}
}

@override
Future<Customer> fromSupabase(Map<String, dynamic> input,
Expand Down
Loading

0 comments on commit 3e182ec

Please sign in to comment.