import 'package:flutter/material.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'dart:convert'; import 'package:shared_preferences/shared_preferences.dart'; /// Utility class to fix the fields table schema class FixFieldsTableUtil { /// Fix the fields table schema static Future fixFieldsTable(BuildContext context) async { try { // Show loading dialog showDialog( context: context, barrierDismissible: false, builder: (context) => const AlertDialog( content: Column( mainAxisSize: MainAxisSize.min, children: [ CircularProgressIndicator(), SizedBox(height: 16), Text('Memperbaiki struktur tabel fields...'), ], ), ), ); final client = Supabase.instance.client; // Clear cache first await _clearSupabaseCache(); // Step 1: Check if updated_at column exists final columnsResult = await client .rpc( 'check_column_exists', params: {'table_name': 'fields', 'column_name': 'updated_at'}, ) .timeout(const Duration(seconds: 10)); print('Column check result: $columnsResult'); // Step 2: Add updated_at column if it doesn't exist if (columnsResult == false) { await client .rpc( 'execute_sql', params: { 'sql_statement': 'ALTER TABLE public.fields ADD COLUMN updated_at TIMESTAMP WITH TIME ZONE DEFAULT now()', }, ) .timeout(const Duration(seconds: 10)); print('Added updated_at column'); } // Step 3: Create or replace the update trigger function await client .rpc( 'execute_sql', params: { 'sql_statement': 'CREATE OR REPLACE FUNCTION update_fields_updated_at() RETURNS TRIGGER AS \$\$ BEGIN NEW.updated_at = now(); RETURN NEW; END; \$\$ LANGUAGE plpgsql;', }, ) .timeout(const Duration(seconds: 10)); print('Created trigger function'); // Step 4: Create the trigger if it doesn't exist await client .rpc( 'execute_sql', params: { 'sql_statement': 'DROP TRIGGER IF EXISTS trigger_fields_updated_at ON public.fields; CREATE TRIGGER trigger_fields_updated_at BEFORE UPDATE ON public.fields FOR EACH ROW EXECUTE PROCEDURE update_fields_updated_at();', }, ) .timeout(const Duration(seconds: 10)); print('Created trigger'); // Step 5: Update all fields to set updated_at = created_at where null await client .rpc( 'execute_sql', params: { 'sql_statement': 'UPDATE public.fields SET updated_at = created_at WHERE updated_at IS NULL', }, ) .timeout(const Duration(seconds: 10)); print('Updated null updated_at values'); // Step 6: Fix RLS policies to avoid recursion issues await _fixRLSPolicies(client); // Close the dialog Navigator.pop(context); // Show success message ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Struktur tabel fields berhasil diperbaiki'), backgroundColor: Colors.green, ), ); } catch (e) { print('Error fixing fields table: $e'); // Close the dialog if it's open Navigator.pop(context); // Show error message ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( 'Gagal memperbaiki struktur tabel fields: ${e.toString()}', ), backgroundColor: Colors.red, ), ); } } /// Fix RLS policies to avoid recursion issues static Future _fixRLSPolicies(SupabaseClient client) async { try { // Fix user_roles policies await client .rpc( 'execute_sql', params: { 'sql_statement': ''' -- Remove existing policies from user_roles DROP POLICY IF EXISTS "Users can view their own roles" ON public.user_roles; DROP POLICY IF EXISTS "Users can insert their own roles" ON public.user_roles; DROP POLICY IF EXISTS "Users can update their own roles" ON public.user_roles; DROP POLICY IF EXISTS "Users can delete their own roles" ON public.user_roles; -- Create simplified policies for user_roles CREATE POLICY "Enable read access for authenticated users" ON public.user_roles FOR SELECT USING (auth.role() = 'authenticated'); CREATE POLICY "Enable insert access for authenticated users" ON public.user_roles FOR INSERT WITH CHECK (auth.role() = 'authenticated'); CREATE POLICY "Enable update for users based on user_id" ON public.user_roles FOR UPDATE USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id); CREATE POLICY "Enable delete for users based on user_id" ON public.user_roles FOR DELETE USING (auth.uid() = user_id); ''', }, ) .timeout(const Duration(seconds: 10)); // Fix fields policies await client .rpc( 'execute_sql', params: { 'sql_statement': ''' -- Remove existing policies from fields DROP POLICY IF EXISTS "Users can view their own fields" ON public.fields; DROP POLICY IF EXISTS "Users can insert their own fields" ON public.fields; DROP POLICY IF EXISTS "Users can update their own fields" ON public.fields; DROP POLICY IF EXISTS "Users can delete their own fields" ON public.fields; -- Create simplified policies for fields CREATE POLICY "Enable read access for authenticated users" ON public.fields FOR SELECT USING (auth.role() = 'authenticated'); CREATE POLICY "Enable insert access for authenticated users" ON public.fields FOR INSERT WITH CHECK (auth.uid() = user_id); CREATE POLICY "Enable update for users based on user_id" ON public.fields FOR UPDATE USING (auth.uid() = user_id) WITH CHECK (auth.uid() = user_id); CREATE POLICY "Enable delete for users based on user_id" ON public.fields FOR DELETE USING (auth.uid() = user_id); ''', }, ) .timeout(const Duration(seconds: 10)); print('Fixed RLS policies'); } catch (e) { print('Error fixing RLS policies: $e'); rethrow; } } /// Clear Supabase cache to help with stuck issues static Future _clearSupabaseCache() async { try { // Clear SharedPreferences cache final prefs = await SharedPreferences.getInstance(); final keys = prefs .getKeys() .where( (key) => key.startsWith('supabase') || key.contains('auth') || key.contains('fields') || key.contains('cache'), ) .toList(); for (var key in keys) { await prefs.remove(key); } print('Cleared ${keys.length} cache entries'); // Force refresh auth session try { await Supabase.instance.client.auth.refreshSession(); print('Auth session refreshed'); } catch (e) { print('Error refreshing auth session: $e'); // Continue even if this fails } } catch (e) { print('Error clearing cache: $e'); // Continue even if this fails } } /// Public method to clear cache static Future clearCache() async { await _clearSupabaseCache(); } }