Reputation: 21
I'm trying to upload images to Supabase storage from a React Native Expo app. The images are first compressed using expo-image-manipulator, but the upload fails with:
ERROR Error uploading images: No content provided ERROR Upload Media Error: {"error": "InvalidRequest", "message": "No content provided", "statusCode": "400"}
I am also not sure how to properly configure the RLS for Supabase storage bucket as I am using authentication from clerk.
import React, { useEffect, useState } from 'react';
import {
View,
Text,
StyleSheet,
Image,
TouchableOpacity,
ScrollView,
Alert,
ActivityIndicator,
} from 'react-native';
import { useRouter, useLocalSearchParams } from 'expo-router';
import * as Location from 'expo-location';
import { createClient, SupabaseClient } from '@supabase/supabase-js';
import * as ImageManipulator from 'expo-image-manipulator';
import { v4 as uuidv4 } from 'uuid';
import { useAuth, useUser } from '@clerk/clerk-expo';
import { supabaseUrl, supabaseAnonKey } from '@/config/supabaseClient';
import { jwtDecode } from 'jwt-decode'; // Corrected import
// Define the uploadMedia function
const uploadMedia = async (
uri: string,
mediaType: 'images' | 'videos',
supabaseClient: SupabaseClient
): Promise<string | null> => {
try {
const extension = uri.split('.').pop();
const filename = `${uuidv4()}.${extension}`;
const path = `${mediaType}/${filename}`;
// For local files on device, we need to prepend 'file://'
const formattedUri = uri.startsWith('file://') ? uri : `file://${uri}`;
const response = await fetch(formattedUri);
if (!response.ok) throw new Error(`Failed to fetch media: ${response.status}`);
const blob = await response.blob();
if (blob.size === 0) throw new Error('Blob is empty');
console.log('Uploading blob:', { size: blob.size, type: blob.type });
const { error } = await supabaseClient.storage
.from('food-listings-media')
.upload(path, blob);
if (error) throw error;
const { data: { publicUrl } } = supabaseClient.storage
.from('food-listings-media')
.getPublicUrl(path);
if (!publicUrl) throw new Error('Failed to get public URL');
return publicUrl;
} catch (error) {
console.error('Upload Media Error:', error);
return null;
}
};
export default function Summary() {
const router = useRouter();
const { newListing } = useLocalSearchParams();
const listing = typeof newListing === 'string' ? JSON.parse(newListing) : newListing;
const [address, setAddress] = useState<string | null>(null);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const { getToken } = useAuth();
const { user } = useUser(); // Get the user from Clerk
useEffect(() => {
// Create the Supabase client with Clerk token
const createClerkSupabaseClient = () => {
return createClient(supabaseUrl, supabaseAnonKey, {
global: {
fetch: async (url, options = {}) => {
const token = await getToken({ template: 'supabase' });
if (token) {
const decodedToken = jwtDecode(token);
console.log('User Subject (sub) from JWT:', decodedToken.sub); // Log the subject status
} else {
console.log('No token found for the user.');
}
const headers = new Headers(options?.headers);
headers.set('Authorization', `Bearer ${token}`);
return fetch(url, {
...options,
headers,
});
},
},
});
};
const handleSubmit = async () => {
try {
setLoading(true);
const supabaseClient = createClerkSupabaseClient();
// Upload images
const uploadedImages = await Promise.all(
images.map(async (imageUri: string) => {
const compressedImage = await ImageManipulator.manipulateAsync(
imageUri,
[{ resize: { width: 800 } }],
{ compress: 0.7, format: ImageManipulator.SaveFormat.JPEG }
);
const imageUrl = await uploadMedia(compressedImage.uri, 'images', supabaseClient);
if (!imageUrl) throw new Error('Image upload failed');
return imageUrl;
})
);
// Upload videos
const uploadedVideos = await Promise.all(
videos.map(async (videoUri: string) => {
const videoUrl = await uploadMedia(videoUri, 'videos', supabaseClient);
if (!videoUrl) throw new Error('Video upload failed');
return videoUrl;
})
);
// Prepare listing data with uploaded media URLs
const listingData = {
user_id: user?.id, // Use the original Clerk user ID here
title,
description,
cuisine_type: selectedCuisine,
is_vegan: false, // Set based on your app's logic
is_vegetarian: false, // Set based on your app's logic
max_guests: parseInt(maxGuests, 10),
price_per_meal: parseFloat(pricePerMeal),
discount_for_groups: null, // Set as needed
location: location
? { latitude: location.latitude, longitude: location.longitude }
: null,
rules,
availability,
images: uploadedImages,
videos: uploadedVideos,
gifs: null, // Set as needed
};
console.log('Listing Data to Insert:', listingData);
// Insert the data into the listings table
const { error: insertError } = await supabaseClient
.from('listings')
.insert([listingData]);
if (insertError) {
console.error('Error inserting data:', insertError);
Alert.alert('Error', 'Failed to submit the listing. Please try again.');
} else {
Alert.alert('Success', 'Your listing has been added successfully!');
router.push({ pathname: './(tabs)/index' }); // Updated to use correct path
}
} catch (error: unknown) {
const errorMessage =
error instanceof Error
? error.message
: 'An unexpected error occurred. Please try again.';
Alert.alert('Error', errorMessage);
console.error('Submission error:', error);
} finally {
setLoading(false);
}
};
Upvotes: 2
Views: 193