```typescript
import { createClient, SupabaseClient } from "@supabase/supabase-js";
// ============================================
// Types (Database definitions)
// ============================================
export type Json =
| string
| number
| boolean
| null
| { [key: string]: Json | undefined }
| Json[];
export interface Database {
public: {
Tables: {
todos: {
Row: {
id: number;
user_id: string;
task: string;
is_completed: boolean;
inserted_at: string;
};
Insert: {
id?: number;
user_id: string;
task: string;
is_completed?: boolean;
inserted_at?: string;
};
Update: {
id?: number;
user_id?: string;
task?: string;
is_completed?: boolean;
inserted_at?: string;
};
};
profiles: {
Row: {
id: string;
username: string | null;
avatar_url: string | null;
updated_at: string | null;
};
Insert: {
id: string;
username?: string | null;
avatar_url?: string | null;
updated_at?: string | null;
};
Update: {
id?: string;
username?: string | null;
avatar_url?: string | null;
updated_at?: string | null;
};
};
};
};
}
// ============================================
// Client Setup
// ============================================
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
// Simple client (client-side)
export const supabase = createClient<Database>(supabaseUrl, supabaseAnonKey);
// Service role client (server-side only - bypasses RLS)
// export const supabaseAdmin = createClient<Database>(
// supabaseUrl,
// process.env.SUPABASE_SERVICE_ROLE_KEY!
// )
// ============================================
// Auth Helpers
// ============================================
export const authService = {
async signInWithEmail(email: string) {
// Magic link login
const { data, error } = await supabase.auth.signInWithOtp({
email,
options: {
emailRedirectTo: "http://localhost:3000/auth/callback",
},
});
return { data, error };
},
async signInWithGoogle() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: "google",
});
return { data, error };
},
async signOut() {
const { error } = await supabase.auth.signOut();
return { error };
},
async getUser() {
const {
data: { user },
} = await supabase.auth.getUser();
return user;
},
};
// ============================================
// Data Services
// ============================================
export const todoService = {
async getTodos() {
const { data, error } = await supabase
.from("todos")
.select("*")
.order("is_completed", { ascending: true })
.order("inserted_at", { ascending: false });
if (error) throw error;
return data;
},
async createTodo(task: string) {
const user = await authService.getUser();
if (!user) throw new Error("Not authenticated");
const { data, error } = await supabase
.from("todos")
.insert({ task, user_id: user.id })
.select()
.single();
if (error) throw error;
return data;
},
async toggleTodo(id: number, isCompleted: boolean) {
const { data, error } = await supabase
.from("todos")
.update({ is_completed: isCompleted })
.eq("id", id)
.select()
.single();
if (error) throw error;
return data;
},
async deleteTodo(id: number) {
const { error } = await supabase.from("todos").delete().eq("id", id);
if (error) throw error;
return true;
},
};
// ============================================
// Real-time Subscriptions
// ============================================
export function subscribeToTodos(callback: (payload: any) => void) {
const subscription = supabase
.channel("public:todos")
.on(
"postgres_changes",
{ event: "*", schema: "public", table: "todos" },
callback,
)
.subscribe();
return () => {
supabase.removeChannel(subscription);
};
}
// ============================================
// Storage Helpers
// ============================================
export const storageService = {
async uploadAvatar(file: File) {
const user = await authService.getUser();
if (!user) throw new Error("Not authenticated");
const fileExt = file.name.split(".").pop();
const filePath = `${user.id}.${fileExt}`;
const { error: uploadError } = await supabase.storage
.from("avatars")
.upload(filePath, file, { upsert: true });
if (uploadError) throw uploadError;
// Get public URL
const { data } = supabase.storage.from("avatars").getPublicUrl(filePath);
return data.publicUrl;
},
};
```