Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions demos/MAUITodo/Data/Config.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using dotenv.net;

namespace MAUITodo.Data;

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
public class EnvConfig
{
public string SupabaseUrl { get; set; }
public string SupabaseKey { get; set; }
public string SupabaseStorageUrl { get; set; }
public string PowerSyncUrl { get; set; }
public string BackendUrl { get; set; }
public string SupabaseUsername { get; set; }
public string SupabasePassword { get; set; }
public bool UseSupabase { get; set; }

public EnvConfig()
{
DotEnv.Load();
Console.WriteLine($"Current directory: {Directory.GetCurrentDirectory()}");

// Parse boolean value first
string useSupabaseStr = Environment.GetEnvironmentVariable("USE_SUPABASE") ?? "false";
if (!bool.TryParse(useSupabaseStr, out bool useSupabase))
{
throw new InvalidOperationException("USE_SUPABASE environment variable is not a valid boolean.");
}
UseSupabase = useSupabase;

if (UseSupabase)
Console.WriteLine("Using Supabase");
else
Console.WriteLine("Using Node");

PowerSyncUrl = GetRequiredEnv("POWERSYNC_URL");

if (UseSupabase)
{
SupabaseUrl = GetRequiredEnv("SUPABASE_URL");
SupabaseKey = GetRequiredEnv("SUPABASE_KEY");
SupabaseUsername = GetRequiredEnv("SUPABASE_USERNAME");
SupabasePassword = GetRequiredEnv("SUPABASE_PASSWORD");
}
else
{
BackendUrl = GetRequiredEnv("BACKEND_URL");
}
}

private static string GetRequiredEnv(string key)
{
return Environment.GetEnvironmentVariable(key)
?? throw new InvalidOperationException($"{key} environment variable is not set.");
}
}
127 changes: 0 additions & 127 deletions demos/MAUITodo/Data/NodeConnector.cs

This file was deleted.

178 changes: 178 additions & 0 deletions demos/MAUITodo/Data/SupabaseConnector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
namespace MAUITodo.Data;

using MAUITodo.Models;
using MAUITodo.Config;
using MAUITodo.Helpers;

using Newtonsoft.Json;

using PowerSync.Common.Client;
using PowerSync.Common.Client.Connection;
using PowerSync.Common.DB.Crud;

using Supabase;
using Supabase.Gotrue;
using Supabase.Postgrest.Exceptions;
using Supabase.Postgrest.Interfaces;

public class SupabaseConnector : IPowerSyncBackendConnector
{
private readonly Supabase.Client _supabase;
private readonly EnvConfig _envConfig;
private Session? _currentSession;

public Session? CurrentSession
{
get => _currentSession;
set
{
_currentSession = value;

if (_currentSession?.User?.Id != null)
{
UserID = _currentSession.User.Id;
}
}
}

public string UserID { get; private set; } = "";

public bool Ready { get; private set; }

public SupabaseConnector(EnvConfig envConfig)
{
_envConfig = envConfig;
_supabase = new Supabase.Client(envConfig.SupabaseUrl, envConfig.SupabaseKey, new SupabaseOptions
{
AutoConnectRealtime = true
});

_ = _supabase.InitializeAsync();
}

public async Task Login(string email, string password)
{
var response = await _supabase.Auth.SignInWithPassword(email, password);
if (response?.User == null || response.AccessToken == null)
{
throw new Exception("Login failed.");
}

CurrentSession = response;
}

public Task<PowerSyncCredentials?> FetchCredentials()
{
PowerSyncCredentials? credentials = null;

var sessionResponse = _supabase.Auth.CurrentSession;
if (sessionResponse?.AccessToken != null)
{
credentials = new PowerSyncCredentials(_envConfig.PowerSyncUrl, sessionResponse.AccessToken);
}

return Task.FromResult(credentials);
}

public async Task UploadData(IPowerSyncDatabase database)
{
var transaction = await database.GetNextCrudTransaction();
if (transaction == null) return;

try
{
foreach (var op in transaction.Crud)
{
switch (op.Op)
{
case UpdateType.PUT:
if (op.Table.ToLower().Trim() == "lists")
{
var model = JsonConvert.DeserializeObject<TodoList>(JsonConvert.SerializeObject(op.OpData)) ?? throw new InvalidOperationException("Model is null.");
model.ID = op.Id;

await _supabase.From<TodoList>().Upsert(model);
}
else if (op.Table.ToLower().Trim() == "todos")
{
var model = JsonConvert.DeserializeObject<TodoItem>(JsonConvert.SerializeObject(op.OpData)) ?? throw new InvalidOperationException("Model is null.");
model.ID = op.Id;

await _supabase.From<TodoItem>().Upsert(model);
}
break;

case UpdateType.PATCH:
if (op.OpData is null || op.OpData.Count == 0)
{
Console.WriteLine("PATCH skipped: No data to update.");
break;
}

if (op.Table.ToLower().Trim() == "lists")
{
// Create an update query for the 'TodoItem' table where the 'ID' matches 'op.Id'
IPostgrestTable<TodoList> updateQuery = _supabase
.From<TodoList>()
.Where(x => x.ID == op.Id);

// Loop through each key-value pair in the operation data (op.OpData) to apply updates dynamically
foreach (var kvp in op.OpData)
{
// Apply the "SET" operation for each key-value pair.
// The key represents the JSON property name and the value is the new value to be set
updateQuery = SupabasePatchHelper.ApplySet(updateQuery, kvp.Key, kvp.Value);
}

_ = await updateQuery.Update();
}
else if (op.Table.ToLower().Trim() == "todos")
{
// Create an update query for the 'TodoItem' table where the 'ID' matches 'op.Id'
IPostgrestTable<TodoItem> updateQuery = _supabase
.From<TodoItem>()
.Where(x => x.ID == op.Id);

// Loop through each key-value pair in the operation data (op.OpData) to apply updates dynamically
foreach (var kvp in op.OpData)
{
// Apply the "SET" operation for each key-value pair.
// The key represents the JSON property name and the value is the new value to be set
updateQuery = SupabasePatchHelper.ApplySet(updateQuery, kvp.Key, kvp.Value);
}

_ = await updateQuery.Update();
}
break;

case UpdateType.DELETE:
if (op.Table.ToLower().Trim() == "lists")
{
await _supabase
.From<TodoList>()
.Where(x => x.ID == op.Id)
.Delete();
}
else if (op.Table.ToLower().Trim() == "todos")
{
await _supabase
.From<TodoItem>()
.Where(x => x.ID == op.Id)
.Delete();
}
break;

default:
throw new InvalidOperationException("Unknown operation type.");
}
}

await transaction.Complete();
}
catch (PostgrestException ex)
{
Console.WriteLine($"Error during upload: {ex.Message}");
throw;
}
}
}
Loading
Loading