diff --git a/src/api/useGetPost.jsx b/src/api/useGetPost.jsx index 24ea8ac..700377c 100644 --- a/src/api/useGetPost.jsx +++ b/src/api/useGetPost.jsx @@ -1,27 +1,21 @@ +// useGetPost.jsx import api from "./config"; export const getPost = async (postId) => { try { const response = await api.get(`/api/v1/posts/${postId}`); - - // Log the entire response for debugging console.log("API Response:", response); - // Validate the response structure const data = response.data; if (!data || typeof data !== "object") { throw new Error("Invalid response format"); } console.log("Received data:", data); - - // Check if the data is nested inside a 'data' property const postData = data.data || data; - - // Log the structure of the data console.log("Post data structure:", Object.keys(postData)); - // Validate required fields + // 필수 필드 검증 const requiredFields = [ "id", "title", @@ -37,7 +31,7 @@ export const getPost = async (postId) => { } } - // Validate member object + // member 객체 검증 const memberFields = ["id", "role", "course", "name", "nameEnglish"]; for (const field of memberFields) { if (!(field in postData.member)) { @@ -45,14 +39,14 @@ export const getPost = async (postId) => { } } - // Transform the data to match our expected structure return { id: postData.id, title: postData.title, content: postData.content, createdAt: postData.createdAt, updatedAt: postData.updatedAt, - imageUrl: "", // 빈 문자열로 설정 + imageUrls: postData.cloudFrontPaths || [], // cloudFrontPath 사용 + s3ImageUrls: postData.s3ImagePaths || [], // s3ImagePaths 보관 likes: (postData.likes !== undefined ? postData.likes : 0).toString(), author: { id: postData.member.id, @@ -60,7 +54,7 @@ export const getPost = async (postId) => { course: postData.member.course, name: postData.member.name, nameEnglish: postData.member.nameEnglish, - profileImage: "", // 빈 문자열로 설정 + profileImage: postData.member.profileImage || "", }, }; } catch (error) { @@ -72,3 +66,57 @@ export const getPost = async (postId) => { throw error; } }; + +// useGetPosts.jsx +export const getPosts = async (pageNo = 0, pageSize = 10) => { + try { + const response = await api.get("/api/v1/posts", { + params: { + pageNo, + pageSize, + }, + }); + + const transformedData = { + content: response.data.content.map((post) => ({ + post_id: post.id, + post_title: post.title, + post_content: post.content, + created_at: post.createdAt, + updated_at: post.updatedAt, + member: { + member_id: post.member.id, + member_name: post.member.name, + member_name_english: post.member.nameEnglish, + course: post.member.course, + role: post.member.role, + }, + // cloudFrontPaths 사용 + imageUrls: post.cloudFrontPaths || [], + // 첫 번째 이미지를 대표 이미지로 사용 + mainImageUrl: post.cloudFrontPaths?.[0] || "", + // S3 URL도 보관 + s3ImageUrls: post.s3ImagePaths || [], + })), + totalPages: response.data.totalPages, + totalElements: response.data.totalElements, + size: response.data.size, + number: response.data.number, + first: response.data.first, + last: response.data.last, + }; + + return transformedData; + } catch (error) { + console.error("Error fetching posts:", error); + return { + content: [], + totalPages: 0, + totalElements: 0, + size: pageSize, + number: pageNo, + first: true, + last: true, + }; + } +}; diff --git a/src/api/useImageUpload.jsx b/src/api/useImageUpload.jsx new file mode 100644 index 0000000..3526479 --- /dev/null +++ b/src/api/useImageUpload.jsx @@ -0,0 +1,63 @@ +// useImageUpload.jsx +import api from "./config"; +import { getPresignedUrl, uploadToS3 } from "./useImageUrl"; + +export const useImageUpload = () => { + const handleImageUpload = async (postId, files, onProgress = () => {}) => { + try { + // 1. 파일 검증 + const validFiles = files.filter((file) => { + const maxSize = 5 * 1024 * 1024; // 5MB + const validTypes = ["image/jpeg", "image/png", "image/gif"]; + return file.size <= maxSize && validTypes.includes(file.type); + }); + + if (validFiles.length === 0) { + throw new Error("업로드할 수 있는 파일이 없습니다."); + } + + // 2. Presigned URL 요청 + const presignedData = await getPresignedUrl( + postId, + validFiles.map((f) => f.name) + ); + + if (!presignedData?.urls?.length) { + throw new Error("업로드 URL을 받지 못했습니다."); + } + + // 3. S3 업로드 + const uploadResults = []; + const totalFiles = presignedData.urls.length; + + for (let i = 0; i < totalFiles; i++) { + const urlInfo = presignedData.urls[i]; + const file = validFiles[i]; + + console.log(`Uploading file ${i + 1}/${totalFiles}: ${file.name}`); + + const s3Url = await uploadToS3(urlInfo.url, file, (progress) => { + const totalProgress = (i * 100 + progress) / totalFiles; + onProgress(totalProgress); + }); + + uploadResults.push({ + fileName: urlInfo.fileName, + url: s3Url, + }); + } + + // 4. 이미지 URL 저장 + const response = await api.post(`/api/v1/posts/${postId}/images`, { + urls: uploadResults, + }); + + return response.data.images; + } catch (error) { + console.error("Image upload process failed:", error); + throw error; + } + }; + + return { handleImageUpload }; +}; diff --git a/src/api/useImageUrl.jsx b/src/api/useImageUrl.jsx new file mode 100644 index 0000000..6663e6d --- /dev/null +++ b/src/api/useImageUrl.jsx @@ -0,0 +1,49 @@ +import axios from "axios"; +import api from "./config"; + +export const uploadToS3 = async (presignedUrl, file, onProgress = () => {}) => { + try { + console.log("Starting upload:", { + fileName: file.name, + fileType: file.type, + fileSize: file.size, + }); + + const response = await axios.put(presignedUrl, file, { + headers: { + "Content-Type": file.type, + }, + onUploadProgress: (progressEvent) => { + if (progressEvent.total) { + const percentCompleted = Math.round( + (progressEvent.loaded * 100) / progressEvent.total + ); + onProgress(percentCompleted); + } + }, + }); + + console.log("Upload response:", response); + + // 성공 시 S3 URL 반환 + const s3Url = presignedUrl.split("?")[0]; + return s3Url; + } catch (error) { + console.error("Upload error:", error); + throw new Error(`파일 업로드 실패: ${error.message}`); + } +}; + +export const getPresignedUrl = async (postId, fileNames) => { + try { + const response = await api.post( + `/api/v1/posts/${postId}/images/presigned-url`, + { fileNames } + ); + console.log("Presigned URL Response:", response.data); + return response.data; + } catch (error) { + console.error("Error getting presigned URL:", error); + throw new Error("사전 서명된 URL을 가져오는데 실패했습니다."); + } +}; diff --git a/src/api/useSaveImage.jsx b/src/api/useSaveImage.jsx new file mode 100644 index 0000000..3d7305a --- /dev/null +++ b/src/api/useSaveImage.jsx @@ -0,0 +1,21 @@ +// useSaveImage.jsx +import api from "./config"; + +export const saveImages = async (postId, imageUrls) => { + try { + console.log("Saving images to backend:", { postId, imageUrls }); + + const response = await api.post(`/api/v1/posts/${postId}/images`, { + urls: imageUrls.map((url) => ({ + fileName: url.fileName, + url: url.url, + })), + }); + + console.log("Backend save response:", response.data); + return response.data; + } catch (error) { + console.error("Failed to save image URLs:", error); + throw new Error("이미지 URL 저장 중 오류가 발생했습니다."); + } +}; diff --git a/src/component/Postlist.jsx b/src/component/Postlist.jsx index a3f1e32..35ffd28 100644 --- a/src/component/Postlist.jsx +++ b/src/component/Postlist.jsx @@ -6,7 +6,19 @@ const Postlist = ({ id, title, imageUrl, likes }) => { return (
{files.length}개의 파일이 선택됨
} + {files.length > 0 && ( +{files.length}개의 파일이 선택됨
+