I'm working on the delete functionality for my SvelteKit app with Supabase integration. The issue I'm facing is that I can only delete an entry when RLS (Row Level Security) is disabled. When RLS is enabled, the delete operation fails with no errors.
Current Setup:
- SvelteKit + Supabase app.
- There's a policy on the songs table that allows only the owner of the song to delete it. The owner_id column in the songs table is a foreign key that references the id column in the profile table.
Here’s the policy I’ve created:
CREATE POLICY "Allow delete only if owner" ON songsFOR DELETE TO publicUSING (owner_id = auth.uid());
And my +page.svelte
:
<script lang="ts"> import Button from '$lib/components/ui/button/button.svelte'; import { Card, CardContent, CardHeader } from '$lib/components/ui/card'; import { Input } from '$lib/components/ui/input'; import Label from '$lib/components/ui/label/label.svelte'; import { supabase } from '$lib/supabaseClient'; import { toast } from 'svelte-sonner'; import { ScrollArea } from '$lib/components/ui/scroll-area/index.js'; const { data: propsData } = $props(); const { session } = propsData; console.log(session?.user); let title = $state(''); let artist = $state(''); let album = $state(''); let year = $state(''); let tags = $state(''); let url = $state(''); let toastState = $state(false); const handleSubmit = async (event: Event) => { event.preventDefault(); if ( title.trim() === '' || artist.trim() === '' || album.trim() === '' || year.trim() === '' || tags.trim() === '' || url.trim() === '' ) { toast.error('A field was left empty.'); return null; } const songData = { title, artist, album, year, tags: tags.split(',').map((tag) => tag.trim()), url, owner_id: session?.user.id }; try { const response = await fetch('/add', { method: 'POST', headers: {'Content-Type': 'application/json' }, body: JSON.stringify(songData) }); const result = await response.json(); if (result.success) { toastState = true; toast.success('Song added successfully!'); title = ''; artist = ''; album = ''; year = ''; tags = ''; url = ''; } else { toast('Failed to add song.'); } } catch (error) { console.error(error); toast('An error occurred while adding the song.'); } }; $effect(() => { if (toastState) { setTimeout(() => { toastState = false; }, 1000); } }); let songs: { id: string; title: string; artist: string; album: string; year: string; tags: string[]; url: string; }[] = $state([]); const fetchSongs = async () => { const { data } = await supabase.from('songs').select('*'); songs = data ?? []; }; const updateAndFetchSongs = async () => { supabase .channel('public:songs') .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'songs' }, (payload: any) => { console.log('New song inserted:', payload); fetchSongs(); } ) .on('postgres_changes', { event: 'UPDATE', schema: 'public', table: 'songs' }, (payload: any) => { console.log('Song updated:', payload); fetchSongs(); } ) .on('postgres_changes', { event: 'DELETE', schema: 'public', table: 'songs' }, (payload: any) => { console.log('Song deleted:', payload); fetchSongs(); } ) .subscribe(); await fetchSongs(); }; const deleteSong = async (id: string) => { console.log('Attempting delete with:', { songId: id, userId: session?.user?.id }); if (!session?.user?.id) { toast.error('You must be logged in to delete songs'); return; } const { error } = await supabase .from('songs') .delete() .eq('id', id) .eq('owner_id', session.user.id); if (error) { console.error('Error deleting song:', error); toast.error('Failed to delete song'); } else { toast.success('Song deleted successfully'); await fetchSongs(); } }; updateAndFetchSongs();</script>
The delete button correctly logs both the song ID and the user ID when clicked as well as my toast saying Song deleted successfully
. The Delete
button HTML is as so:
<button onclick={() => deleteSong(song.id)}>Delete</button>
Any help is appreciated!