From 1f3c14efc2158ec47dd9b1c45f03d3ebd72921d1 Mon Sep 17 00:00:00 2001 From: hyochan Date: Tue, 17 Dec 2024 03:14:29 +0900 Subject: [PATCH] feat: sync image uploads --- app/(home)/post/[id]/replies.tsx | 33 ++++++++---------- app/(home)/post/[id]/update.tsx | 16 +++++---- app/(home)/post/write.tsx | 16 +++++---- src/apis/postQueries.ts | 51 ++++++++++++++++++++++++---- src/components/uis/ImageCarousel.tsx | 2 +- src/supabase/index.ts | 10 +++--- 6 files changed, 82 insertions(+), 46 deletions(-) diff --git a/app/(home)/post/[id]/replies.tsx b/app/(home)/post/[id]/replies.tsx index 3a28254..c665232 100644 --- a/app/(home)/post/[id]/replies.tsx +++ b/app/(home)/post/[id]/replies.tsx @@ -9,10 +9,7 @@ import {t} from '../../../../src/STRINGS'; import ReplyItem from '../../../../src/components/uis/ReplyItem'; import ReplyInput from '../../../../src/components/uis/ReplyInput'; import {ImagePickerAsset} from 'expo-image-picker'; -import { - getPublicUrlFromPath, - uploadFileToSupabase, -} from '../../../../src/supabase'; +import {uploadFileToSupabase} from '../../../../src/supabase'; import { fetchCreateReply, fetchReplyPagination, @@ -20,7 +17,7 @@ import { import {useRecoilValue} from 'recoil'; import {authRecoilState} from '../../../../src/recoil/atoms'; import useSWR from 'swr'; -import {ReplyWithJoins} from '../../../../src/types'; +import {Image, ReplyWithJoins} from '../../../../src/types'; import FallbackComponent from '../../../../src/components/uis/FallbackComponent'; import {toggleLike} from '../../../../src/apis/likeQueries'; import ErrorBoundary from 'react-native-error-boundary'; @@ -87,7 +84,11 @@ export default function Replies({ }); }); - const images = await Promise.all(imageUploadPromises); + const images = (await Promise.all(imageUploadPromises)) + .filter((el) => !!el) + .map((el) => ({ + ...el, + })); const newReply = await fetchCreateReply({ supabase, @@ -96,21 +97,17 @@ export default function Replies({ user_id: authId, post_id: postId, }, - images: images - .filter((el) => !!el) - .map((el) => ({ - ...el, - image_url: el?.image_url - ? getPublicUrlFromPath({ - path: el.image_url, - supabase, - }) - : undefined, - })), + images, }); if (newReply) { - setReplies((prevReplies) => [newReply, ...prevReplies]); + setReplies((prevReplies) => [ + { + ...newReply, + images: images as Image[], + }, + ...prevReplies, + ]); } } catch (error) { if (__DEV__) console.error('Failed to create reply:', error); diff --git a/app/(home)/post/[id]/update.tsx b/app/(home)/post/[id]/update.tsx index 45605d4..13e3845 100644 --- a/app/(home)/post/[id]/update.tsx +++ b/app/(home)/post/[id]/update.tsx @@ -117,7 +117,7 @@ export default function PostUpdate(): JSX.Element { }, ); - const images = await Promise.all(imageUploadPromises); + const imageUploads = await Promise.all(imageUploadPromises); const initialImageUrls = post?.images?.map((el) => el?.image_url as string) || []; @@ -128,17 +128,19 @@ export default function PostUpdate(): JSX.Element { (element) => !imageUris.includes(element) && element.startsWith('http'), ); + const images = imageUploads + .filter((el) => !!el) + .map((el) => ({ + ...el, + image_url: el?.image_url || undefined, + })); + const updatedPost = await fetchUpdatePost({ postId: id, title: data.title, content: data.content, url: data.url || null, - images: images - .filter((el) => !!el) - .map((el) => ({ - ...el, - image_url: el?.image_url || undefined, - })), + images, imageUrlsToDelete: deleteImageUrls, supabase, }); diff --git a/app/(home)/post/write.tsx b/app/(home)/post/write.tsx index e67440e..581cb9a 100644 --- a/app/(home)/post/write.tsx +++ b/app/(home)/post/write.tsx @@ -77,7 +77,14 @@ export default function PostWrite(): JSX.Element { }); }); - const images = await Promise.all(imageUploadPromises); + const imageUploads = await Promise.all(imageUploadPromises); + + const images = imageUploads + .filter((el) => !!el) + .map((el) => ({ + ...el, + image_url: el?.image_url || undefined, + })); const newPost = await fetchCreatePost({ supabase, @@ -85,12 +92,7 @@ export default function PostWrite(): JSX.Element { title: data.title, content: data.content, user_id: authId, - images: images - .filter((el) => !!el) - .map((el) => ({ - ...el, - image_url: el?.image_url || undefined, - })), + images, }, }); diff --git a/src/apis/postQueries.ts b/src/apis/postQueries.ts index 8564599..02b284d 100644 --- a/src/apis/postQueries.ts +++ b/src/apis/postQueries.ts @@ -127,7 +127,6 @@ export const fetchUpdatePost = async ({ user:user_id ( * ), - images (*), replies ( id, deleted_at @@ -142,24 +141,49 @@ export const fetchUpdatePost = async ({ } if (imageUrlsToDelete.length > 0) { - await supabase + const {error: deleteError} = await supabase .from('images') .update({deleted_at: new Date().toISOString()}) .in('image_url', imageUrlsToDelete); + + if (deleteError) { + throw new Error(deleteError.message); + } } if (images && images.length > 0) { const imageInsertPromises = images.map((image) => supabase.from('images').insert({ ...image, - post_id: post.id, + post_id: postId, }), ); - await Promise.all(imageInsertPromises); + const imageInsertResults = await Promise.all(imageInsertPromises); + + imageInsertResults.forEach(({error}) => { + if (error) { + throw new Error(error.message); + } + }); } - return post as unknown as PostWithJoins; + const {data: updatedImages, error: fetchImagesError} = await supabase + .from('images') + .select('*') + .eq('post_id', postId) + .is('deleted_at', null); + + if (fetchImagesError) { + throw new Error(fetchImagesError.message); + } + + const postWithImages = { + ...post, + images: updatedImages, + }; + + return postWithImages as unknown as PostWithJoins; }; export const fetchDeletePost = async ({ @@ -226,7 +250,6 @@ export const fetchCreatePost = async ({ user:user_id ( * ), - images (*), replies ( id, deleted_at @@ -251,13 +274,27 @@ export const fetchCreatePost = async ({ await Promise.all(imageInsertPromises); } + const {data: images, error: imagesError} = await supabase + .from('images') + .select('*') + .eq('post_id', data.id); + + if (imagesError) { + throw new Error(imagesError.message); + } + + const postWithImages = { + ...data, + images, + }; + sendNotificationsToAllUsers({ title: post.title, body: post.content, supabase, }); - return data as unknown as PostWithJoins; + return postWithImages as unknown as PostWithJoins; }; /* diff --git a/src/components/uis/ImageCarousel.tsx b/src/components/uis/ImageCarousel.tsx index 3afcbcb..6272ee0 100644 --- a/src/components/uis/ImageCarousel.tsx +++ b/src/components/uis/ImageCarousel.tsx @@ -10,7 +10,7 @@ import {Image as ExpoImage} from 'expo-image'; import {useRouter} from 'expo-router'; import {Image} from '../../types'; import {generateThumbnailFromVideo} from '../../utils/common'; -import { delayPressIn } from '../../utils/constants'; +import {delayPressIn} from '../../utils/constants'; type Styles = { container?: ViewStyle; diff --git a/src/supabase/index.ts b/src/supabase/index.ts index 28e8953..e28d006 100644 --- a/src/supabase/index.ts +++ b/src/supabase/index.ts @@ -33,11 +33,10 @@ export const uploadFileToSupabase = async ({ throw error; } + const image_url = getPublicUrlFromPath({path: destPath, supabase}); + return { - image_url: getPublicUrlFromPath({ - path: data.fullPath, - supabase, - }), + image_url, url: data.fullPath, id: data.id, type: fileType || null, @@ -79,8 +78,7 @@ export const getPublicUrlFromPath = ({ throw new Error('Failed to get signed URL'); } - //! Note: supabase returns wrong url based on platform - return data.publicUrl.replace(/(\/images)(\/images)+/, '/images'); + return data.publicUrl; }; export const getSignedUrlFromUploadFile = async ({