From 6d10172dd3849dfa460c12f550e91112e24241e2 Mon Sep 17 00:00:00 2001 From: MurakawaTakuya Date: Sun, 29 Dec 2024 16:58:18 +0900 Subject: [PATCH 1/6] =?UTF-8?q?storage=E3=81=AB=E3=82=A2=E3=83=83=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=83=89=E3=81=99=E3=82=8B=E7=94=BB=E5=83=8F?= =?UTF-8?q?=E3=81=AE=E3=83=A1=E3=82=BF=E3=83=87=E3=83=BC=E3=82=BF=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Components/Account/LoggedInView.tsx | 4 +- src/Components/Loader/Loader.tsx | 5 +- src/Components/PostModal/PostModal.tsx | 7 +- src/utils/API/User/fetchUser.ts | 2 +- src/utils/Uploader.ts | 88 +++++++++++++++++++------ src/utils/UserContext.tsx | 16 +++-- 6 files changed, 89 insertions(+), 33 deletions(-) diff --git a/src/Components/Account/LoggedInView.tsx b/src/Components/Account/LoggedInView.tsx index 671424a..4f24fd6 100644 --- a/src/Components/Account/LoggedInView.tsx +++ b/src/Components/Account/LoggedInView.tsx @@ -43,7 +43,9 @@ export default function LoggedInView() { return ( <> {user.loginType === "Guest" ? ( - <>ゲストとしてログイン中 + + ゲストとしてログイン中 + ) : ( <> diff --git a/src/Components/Loader/Loader.tsx b/src/Components/Loader/Loader.tsx index e2495fa..ad44cca 100644 --- a/src/Components/Loader/Loader.tsx +++ b/src/Components/Loader/Loader.tsx @@ -18,10 +18,9 @@ export const Loader = ({ children }: LoaderProps) => { redirect("/account"); } - // 10秒経ったらボタンを表示 setTimeout(() => { setShowErrorButton(true); - }, 6000); + }, 10000); return ( <> @@ -54,7 +53,7 @@ export const Loader = ({ children }: LoaderProps) => { > 認証エラーが発生した可能性があります。
diff --git a/src/Components/PostModal/PostModal.tsx b/src/Components/PostModal/PostModal.tsx index 6ef879a..80551a9 100644 --- a/src/Components/PostModal/PostModal.tsx +++ b/src/Components/PostModal/PostModal.tsx @@ -2,7 +2,7 @@ import { showSnackBar } from "@/Components/SnackBar/SnackBar"; import { PostWithGoalId } from "@/types/types"; import { createPost, handleCreatePostError } from "@/utils/API/Post/createPost"; -import { uploadImage } from "@/utils/Uploader"; +import { removeImageMetadata, uploadImage } from "@/utils/Uploader"; import { useUser } from "@/utils/UserContext"; import { Add } from "@mui/icons-material"; import AddAPhotoIcon from "@mui/icons-material/AddAPhoto"; @@ -43,7 +43,7 @@ export default function PostModal({ setText(event.target.value); }; - const handleImageChange = (event: ChangeEvent) => { + const handleImageChange = async (event: ChangeEvent) => { const selectedFile = event.target.files?.[0]; if (!selectedFile) { @@ -56,7 +56,8 @@ export default function PostModal({ // ファイルサイズの上限を設定 const maxSize = 8; // 上限8MB - const fileSizeMB = selectedFile.size / (1024 * 1024); + const fileWithoutMetadata = await removeImageMetadata(selectedFile); // モーションフォトの動画を除いたサイズを取得 + const fileSizeMB = fileWithoutMetadata.size / (1024 * 1024); if (fileSizeMB > maxSize) { showSnackBar({ message: `最大ファイルサイズは${maxSize}MBです。`, diff --git a/src/utils/API/User/fetchUser.ts b/src/utils/API/User/fetchUser.ts index f90002d..5a9c832 100644 --- a/src/utils/API/User/fetchUser.ts +++ b/src/utils/API/User/fetchUser.ts @@ -35,7 +35,7 @@ export const handleFetchUserError = (error: unknown) => { if (error instanceof Error) { console.error("Fetch error:", error.message); if (error.message.includes("404")) { - snackBarMessage = "ユーザーが見つかりませんでした"; + snackBarMessage = "ユーザー情報が登録されていません"; } if (error.message.includes("500")) { snackBarMessage = "サーバーエラーが発生しました"; diff --git a/src/utils/Uploader.ts b/src/utils/Uploader.ts index 7737f03..13b441f 100644 --- a/src/utils/Uploader.ts +++ b/src/utils/Uploader.ts @@ -10,24 +10,74 @@ export const uploadImage = ( throw new Error("ファイルが選択されていません"); } - // cryptoモジュールを使用してユニークなIDを生成 - const uniqueId = crypto.randomUUID(); - const storageRef = ref(storage, `post/${uniqueId}`); - const uploadTask = uploadBytesResumable(storageRef, file); + // メタデータを削除してからFirebaseにアップロード + removeImageMetadata(file) + .then((cleanFile) => { + const uniqueId = crypto.randomUUID(); + const storageRef = ref(storage, `post/${uniqueId}`); + const uploadTask = uploadBytesResumable(storageRef, cleanFile); - uploadTask.on( - "state_changed", - (snapshot) => { - const percent = (snapshot.bytesTransferred / snapshot.totalBytes) * 100; - onProgress(percent); - }, - (error) => { - throw new Error("ファイルアップに失敗しました。エラー: " + error.message); - }, - () => { - getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { - onSuccess(downloadURL, uniqueId); // 完了時にURLとIDを返す - }); - } - ); + uploadTask.on( + "state_changed", + (snapshot) => { + const percent = + (snapshot.bytesTransferred / snapshot.totalBytes) * 100; + onProgress(percent); + }, + (error) => { + throw new Error( + "ファイルアップに失敗しました。エラー: " + error.message + ); + }, + () => { + getDownloadURL(uploadTask.snapshot.ref).then((downloadURL) => { + onSuccess(downloadURL, uniqueId); // 完了時にURLとIDを返す + }); + } + ); + }) + .catch((error) => { + throw new Error( + "画像のメタデータ削除に失敗しました。エラー: " + error.message + ); + }); +}; + +// 画像のメタデータを削除する関数 +// モーションフォトの動画も削除できる +export const removeImageMetadata = (file: File): Promise => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = (event) => { + const img = new Image(); + img.onload = () => { + const canvas = document.createElement("canvas"); + canvas.width = img.width; + canvas.height = img.height; + + const ctx = canvas.getContext("2d"); + if (ctx) { + ctx.drawImage(img, 0, 0); + + canvas.toBlob((blob) => { + if (blob) { + const cleanFile = new File([blob], file.name, { + type: file.type, + }); + resolve(cleanFile); + } else { + reject(new Error("画像処理に失敗しました。")); + } + }, file.type); + } else { + reject(new Error("Canvas のコンテキストを取得できません。")); + } + }; + img.src = event.target?.result as string; + }; + reader.onerror = () => { + reject(new Error("画像を読み込めませんでした。")); + }; + reader.readAsDataURL(file); + }); }; diff --git a/src/utils/UserContext.tsx b/src/utils/UserContext.tsx index f75b4b1..25f628f 100644 --- a/src/utils/UserContext.tsx +++ b/src/utils/UserContext.tsx @@ -1,7 +1,11 @@ "use client"; import { auth } from "@/app/firebase"; +import { showSnackBar } from "@/Components/SnackBar/SnackBar"; import { LoginType, User } from "@/types/types"; -import { fetchUserById } from "@/utils/API/User/fetchUser"; +import { + fetchUserById, + handleFetchUserError, +} from "@/utils/API/User/fetchUser"; import { User as FirebaseUser, onAuthStateChanged } from "firebase/auth"; import { createContext, @@ -96,11 +100,11 @@ export const UserProvider = ({ children }: Props) => { } } catch (error: unknown) { console.error("ユーザーデータの取得に失敗しました:", error); - // const message = handleFetchUserError(error); - // showSnackBar({ - // message, - // type: "warning", - // }); + const message = handleFetchUserError(error); + showSnackBar({ + message, + type: "warning", + }); } } ); From 6d1a1cb3b2407800614b26dbd69b21c63e600e64 Mon Sep 17 00:00:00 2001 From: MurakawaTakuya Date: Sun, 29 Dec 2024 18:01:38 +0900 Subject: [PATCH 2/6] =?UTF-8?q?storage=E3=81=AE=E4=BF=9D=E5=AD=98=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E3=81=AE=E5=A4=89=E6=9B=B4=E3=81=A8post=E5=89=8A?= =?UTF-8?q?=E9=99=A4=E6=99=82=E3=81=ABstorage=E3=81=AE=E7=94=BB=E5=83=8F?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Documents/API.md | 8 +++--- functions/src/index.ts | 2 +- functions/src/routers/goalRouter.ts | 35 ++++++++++++++++++++------ functions/src/routers/postRouter.ts | 28 +++++++++++++++++---- functions/src/routers/resultRouter.ts | 4 +-- functions/src/routers/userRouter.ts | 23 +++++++++++------ functions/src/{routers => }/status.ts | 0 functions/src/{routers => }/types.ts | 0 src/Components/PostModal/PostModal.tsx | 4 +-- src/Components/Progress/Progress.tsx | 23 ++++++++++++++--- 10 files changed, 95 insertions(+), 32 deletions(-) rename functions/src/{routers => }/status.ts (100%) rename functions/src/{routers => }/types.ts (100%) diff --git a/Documents/API.md b/Documents/API.md index 4dc986d..61e5a63 100644 --- a/Documents/API.md +++ b/Documents/API.md @@ -108,7 +108,7 @@ API is provided by Firebase Cloud Functions. Database is provided by Firestore. "text": "hoge fuga", "post": { "userId": "Vlx6GCtq90ag3lxgh0pcCKGp5ba0", - "storedURL": "hogehoge URL", + "storedURL": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", "text": "数学の勉強したよ^^", "submittedAt": { "_seconds": 1735603199, @@ -158,7 +158,7 @@ API is provided by Firebase Cloud Functions. Database is provided by Firestore. { "goalId": "RXlHJiv3GtpzSDHhfljS", "text": "今日は勉強をがんばった", - "storedURL": "hogehoge URL", + "storedURL": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", "submittedAt": "2024-12-31T23:59:59.000Z" } ``` @@ -182,7 +182,7 @@ API is provided by Firebase Cloud Functions. Database is provided by Firestore. "goalId": "9fgWJA6wMN54EkxIC2WD", "userId": "IK0Zc2hoUYaYjXoqzmCl", "text": "今日は勉強をがんばった", - "storedURL": "hogehoge URL", + "storedURL": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", "goalId": "RXlHJiv3GtpzSDHhfljS", "submittedAt": "2024-12-31T23:59:59.000Z" } @@ -215,7 +215,7 @@ Use Create Post API to update post. "text": "Duolingoやる", "post": { "text": "フランス語したよ", - "storedURL": "hogehoge URL", + "storedURL": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", "submittedAt": "2024-12-28T09:45:10.718Z" }, "userData": { diff --git a/functions/src/index.ts b/functions/src/index.ts index e892a40..f89aeca 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -9,7 +9,7 @@ import serviceAccount from "./serviceAccountKey.json"; admin.initializeApp({ credential: admin.credential.cert(serviceAccount as admin.ServiceAccount), - storageBucket: "todo-real-c28fa.appspot.com", + storageBucket: "todo-real-c28fa.firebasestorage.app", }); import goalRouter from "./routers/goalRouter"; diff --git a/functions/src/routers/goalRouter.ts b/functions/src/routers/goalRouter.ts index 60ac04e..051a53f 100644 --- a/functions/src/routers/goalRouter.ts +++ b/functions/src/routers/goalRouter.ts @@ -1,7 +1,7 @@ import express, { Request, Response } from "express"; import admin from "firebase-admin"; import { logger } from "firebase-functions"; -import { Goal, GoalWithId } from "./types"; +import { Goal, GoalWithId } from "../types"; const router = express.Router(); const db = admin.firestore(); @@ -147,14 +147,35 @@ router.put("/:goalId", async (req: Request, res: Response) => { // DELETE: 目標を削除 router.delete("/:goalId", async (req: Request, res: Response) => { - const goalId = req.params.goalId; + try { + const goalId = req.params.goalId; - if (!goalId) { - return res.status(400).json({ message: "Goal ID is required" }); - } + if (!goalId) { + return res.status(400).json({ message: "Goal ID is required" }); + } - try { - await db.collection("goal").doc(goalId).delete(); + const goalRef = db.collection("goal").doc(goalId); + const goalDoc = await goalRef.get(); + + if (!goalDoc.exists) { + return res.status(404).json({ message: "Goal not found" }); + } + + // storageから画像を削除 + const storedId = goalDoc.data()?.post?.storedId; + if (storedId) { + try { + const bucket = admin.storage().bucket(); + const file = bucket.file(`post/${storedId}`); + await file.delete(); + logger.info("Image deleted successfully:", storedId); + } catch (error) { + logger.error("Error deleting image:", error); + return res.status(500).json({ message: "Error deleting image" }); + } + } + + await goalRef.delete(); return res.json({ message: "Goal deleted successfully", goalId }); } catch (error) { logger.error(error); diff --git a/functions/src/routers/postRouter.ts b/functions/src/routers/postRouter.ts index 45ab8a2..d8e41a1 100644 --- a/functions/src/routers/postRouter.ts +++ b/functions/src/routers/postRouter.ts @@ -1,8 +1,8 @@ import express, { Request, Response } from "express"; import admin from "firebase-admin"; import { logger } from "firebase-functions"; -import { updateStreak } from "./status"; -import { PostWithGoalId } from "./types"; +import { updateStreak } from "../status"; +import { PostWithGoalId } from "../types"; const router = express.Router(); const db = admin.firestore(); @@ -135,9 +135,13 @@ router.post("/", async (req: Request, res: Response) => { // DELETE: 投稿を削除 router.delete("/:goalId", async (req: Request, res: Response) => { - const goalId = req.params.goalId; - try { + const goalId = req.params.goalId; + + if (!goalId) { + return res.status(400).json({ message: "Goal ID is required" }); + } + const goalRef = db.collection("goal").doc(goalId); const goalDoc = await goalRef.get(); @@ -145,8 +149,22 @@ router.delete("/:goalId", async (req: Request, res: Response) => { return res.status(404).json({ message: "Goal not found" }); } + // Storageから画像を削除 + const storedURL = goalDoc.data()?.post?.storedURL; + if (storedURL) { + try { + const bucket = admin.storage().bucket(); + const file = bucket.file(`post/${storedURL}`); + await file.delete(); + logger.info("Image deleted successfully:", storedURL); + } catch (error) { + logger.error("Error deleting image:", error); + return res.status(500).json({ message: "Error deleting image" }); + } + } + await goalRef.update({ - post: admin.firestore.FieldValue.delete(), + post: null, }); return res.json({ message: "Post deleted successfully" }); diff --git a/functions/src/routers/resultRouter.ts b/functions/src/routers/resultRouter.ts index f5b1300..fec7661 100644 --- a/functions/src/routers/resultRouter.ts +++ b/functions/src/routers/resultRouter.ts @@ -1,8 +1,8 @@ import express, { Request, Response } from "express"; import admin from "firebase-admin"; import { logger } from "firebase-functions"; -import { countCompletedGoals, countFailedGoals, getStreak } from "./status"; -import { GoalWithIdAndUserData, User } from "./types"; +import { countCompletedGoals, countFailedGoals, getStreak } from "../status"; +import { GoalWithIdAndUserData, User } from "../types"; const router = express.Router(); const db = admin.firestore(); diff --git a/functions/src/routers/userRouter.ts b/functions/src/routers/userRouter.ts index 8aaff72..893ffac 100644 --- a/functions/src/routers/userRouter.ts +++ b/functions/src/routers/userRouter.ts @@ -1,8 +1,8 @@ import express, { Request, Response } from "express"; import admin from "firebase-admin"; import { logger } from "firebase-functions"; -import { countCompletedGoals, countFailedGoals, getStreak } from "./status"; -import { User } from "./types"; +import { countCompletedGoals, countFailedGoals, getStreak } from "../status"; +import { User } from "../types"; const router = express.Router(); const db = admin.firestore(); @@ -179,14 +179,21 @@ router.put("/:userId", async (req: Request, res: Response) => { // DELETE: ユーザーを削除 router.delete("/:userId", async (req: Request, res: Response) => { - const userId = req.params.userId; + try { + const userId = req.params.userId; - if (!userId) { - return res.status(400).json({ message: "User ID is required" }); - } + if (!userId) { + return res.status(400).json({ message: "User ID is required" }); + } - try { - await db.collection("user").doc(userId).delete(); + const userRef = db.collection("user").doc(userId); + const userDoc = await userRef.get(); + + if (!userDoc.exists) { + return res.status(404).json({ message: "User not found" }); + } + + await userRef.delete(); return res.json({ message: "User deleted successfully", userId }); } catch (error) { logger.error(error); diff --git a/functions/src/routers/status.ts b/functions/src/status.ts similarity index 100% rename from functions/src/routers/status.ts rename to functions/src/status.ts diff --git a/functions/src/routers/types.ts b/functions/src/types.ts similarity index 100% rename from functions/src/routers/types.ts rename to functions/src/types.ts diff --git a/src/Components/PostModal/PostModal.tsx b/src/Components/PostModal/PostModal.tsx index 80551a9..59eaa76 100644 --- a/src/Components/PostModal/PostModal.tsx +++ b/src/Components/PostModal/PostModal.tsx @@ -92,10 +92,10 @@ export default function PostModal({ await uploadImage( image, (percent) => setProgress(percent), - async (url) => { + async (url, id) => { const postData: PostWithGoalId = { userId: user?.userId as string, - storedURL: url, + storedURL: id, text: text, goalId: goalId, submittedAt: new Date(), diff --git a/src/Components/Progress/Progress.tsx b/src/Components/Progress/Progress.tsx index 20fdace..0c7a723 100644 --- a/src/Components/Progress/Progress.tsx +++ b/src/Components/Progress/Progress.tsx @@ -12,6 +12,7 @@ import StepIndicator, { stepIndicatorClasses } from "@mui/joy/StepIndicator"; import Stepper from "@mui/joy/Stepper"; import Typography, { typographyClasses } from "@mui/joy/Typography"; import { Divider } from "@mui/material"; +import { getDownloadURL, getStorage, ref } from "firebase/storage"; import { ReactNode, useState } from "react"; import DeleteGoalModal from "../DeleteGoalModal/DeleteGoalModal"; import DeletePostModal from "../DeletePostModal/DeletePostModal"; @@ -115,11 +116,27 @@ const SuccessStep = ({ result: GoalWithIdAndUserData; user: User; }) => { + const [imageURL, setImageURL] = useState(""); + const [imageLoading, setImageLoading] = useState(true); + const post = result.post; if (!post) { return null; } + const storage = getStorage(); + const imageRef = ref(storage, `post/${post.storedURL}`); + + getDownloadURL(imageRef) + .then((url) => { + console.log("Image URL:", url); + setImageURL(url); + setImageLoading(false); + }) + .catch((error) => { + console.error("Error fetching image URL:", error); + }); + return ( - {post.storedURL && ( + {!imageLoading && ( Date: Sun, 29 Dec 2024 18:55:21 +0900 Subject: [PATCH 3/6] =?UTF-8?q?=E7=94=BB=E5=83=8F=E3=81=AE=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=83=87=E3=82=A3=E3=83=B3=E3=82=B0=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Components/DashBoard/DashBoard.tsx | 2 +- src/Components/Progress/Progress.tsx | 76 ++++++++++++++++++-------- src/styles/globals.scss | 2 +- 3 files changed, 56 insertions(+), 24 deletions(-) diff --git a/src/Components/DashBoard/DashBoard.tsx b/src/Components/DashBoard/DashBoard.tsx index e89df23..6706203 100644 --- a/src/Components/DashBoard/DashBoard.tsx +++ b/src/Components/DashBoard/DashBoard.tsx @@ -76,7 +76,7 @@ export default function DashBoard({ }} /> ) : noResult ? ( - + +ボタンから目標を作成しましょう! ) : ( diff --git a/src/Components/Progress/Progress.tsx b/src/Components/Progress/Progress.tsx index 0c7a723..1b86486 100644 --- a/src/Components/Progress/Progress.tsx +++ b/src/Components/Progress/Progress.tsx @@ -5,13 +5,14 @@ import { useUser } from "@/utils/UserContext"; import AppRegistrationRoundedIcon from "@mui/icons-material/AppRegistrationRounded"; import CheckRoundedIcon from "@mui/icons-material/CheckRounded"; import CloseIcon from "@mui/icons-material/Close"; +import { CssVarsProvider, Divider, extendTheme } from "@mui/joy"; import Card from "@mui/joy/Card"; import CardContent from "@mui/joy/CardContent"; +import Skeleton from "@mui/joy/Skeleton"; import Step, { stepClasses } from "@mui/joy/Step"; import StepIndicator, { stepIndicatorClasses } from "@mui/joy/StepIndicator"; import Stepper from "@mui/joy/Stepper"; import Typography, { typographyClasses } from "@mui/joy/Typography"; -import { Divider } from "@mui/material"; import { getDownloadURL, getStorage, ref } from "firebase/storage"; import { ReactNode, useState } from "react"; import DeleteGoalModal from "../DeleteGoalModal/DeleteGoalModal"; @@ -117,7 +118,7 @@ const SuccessStep = ({ user: User; }) => { const [imageURL, setImageURL] = useState(""); - const [imageLoading, setImageLoading] = useState(true); + const [imageLoaded, setImageLoaded] = useState(false); const post = result.post; if (!post) { @@ -129,14 +130,22 @@ const SuccessStep = ({ getDownloadURL(imageRef) .then((url) => { - console.log("Image URL:", url); setImageURL(url); - setImageLoading(false); }) .catch((error) => { console.error("Error fetching image URL:", error); }); + const theme = extendTheme({ + components: { + JoySkeleton: { + defaultProps: { + animation: "wave", + }, + }, + }, + }); + return ( - {!imageLoading && ( - + + + {imageURL && ( + setImageLoaded(true)} + /> + )} + + + + +
- )} - -
+ > {formatStringToDate(post.submittedAt)}に完了 @@ -340,7 +366,13 @@ const GoalCard = ({ }} > -
+
{formatStringToDate(deadline)}までに @@ -379,7 +411,7 @@ const StepperBlock = ({ sx={{ width: "87%", margin: "10px auto", - padding: "13px", + padding: "10px 13px", borderRadius: "8px", border: "1px solid", borderColor: diff --git a/src/styles/globals.scss b/src/styles/globals.scss index 8206f44..76ef4cb 100644 --- a/src/styles/globals.scss +++ b/src/styles/globals.scss @@ -18,5 +18,5 @@ body { main { background: white; - padding-bottom: 100px; + padding-bottom: 130px; } From e6b6b307699706767ea4574e53a21131ffd8eb37 Mon Sep 17 00:00:00 2001 From: MurakawaTakuya Date: Sun, 29 Dec 2024 19:24:40 +0900 Subject: [PATCH 4/6] =?UTF-8?q?storedURL=E3=82=92storedId=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Documents/API.md | 10 +++++----- functions/src/routers/postRouter.ts | 22 +++++++++++----------- functions/src/routers/resultRouter.ts | 2 +- functions/src/types.ts | 2 +- src/Components/PostModal/PostModal.tsx | 2 +- src/Components/Progress/Progress.tsx | 2 +- src/types/types.ts | 2 +- src/utils/API/User/fetchUser.ts | 2 +- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/Documents/API.md b/Documents/API.md index 61e5a63..a8491f5 100644 --- a/Documents/API.md +++ b/Documents/API.md @@ -108,7 +108,7 @@ API is provided by Firebase Cloud Functions. Database is provided by Firestore. "text": "hoge fuga", "post": { "userId": "Vlx6GCtq90ag3lxgh0pcCKGp5ba0", - "storedURL": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", + "storedId": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", "text": "数学の勉強したよ^^", "submittedAt": { "_seconds": 1735603199, @@ -151,14 +151,14 @@ API is provided by Firebase Cloud Functions. Database is provided by Firestore. - Body (form-data) - goalId: string - text: string - - storedURL: string (画像のストレージパス、/post/{storedURL}/image) + - storedId: string (画像のストレージパス、/post/{storedId}/image) - submittedAt: Date - Example ```json { "goalId": "RXlHJiv3GtpzSDHhfljS", "text": "今日は勉強をがんばった", - "storedURL": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", + "storedId": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", "submittedAt": "2024-12-31T23:59:59.000Z" } ``` @@ -182,7 +182,7 @@ API is provided by Firebase Cloud Functions. Database is provided by Firestore. "goalId": "9fgWJA6wMN54EkxIC2WD", "userId": "IK0Zc2hoUYaYjXoqzmCl", "text": "今日は勉強をがんばった", - "storedURL": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", + "storedId": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", "goalId": "RXlHJiv3GtpzSDHhfljS", "submittedAt": "2024-12-31T23:59:59.000Z" } @@ -215,7 +215,7 @@ Use Create Post API to update post. "text": "Duolingoやる", "post": { "text": "フランス語したよ", - "storedURL": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", + "storedId": "0f9a84ed-8ae8-44b0-a6f5-5ac5ca517948", "submittedAt": "2024-12-28T09:45:10.718Z" }, "userData": { diff --git a/functions/src/routers/postRouter.ts b/functions/src/routers/postRouter.ts index d8e41a1..d17c11f 100644 --- a/functions/src/routers/postRouter.ts +++ b/functions/src/routers/postRouter.ts @@ -25,7 +25,7 @@ router.get("/", async (req: Request, res: Response) => { goalId: goalDoc.id, userId: goalData.userId, text: goalData.post.text, - storedURL: goalData.post.storedURL, + storedId: goalData.post.storedId, submittedAt: goalData.post.submittedAt.toDate(), }); } @@ -69,7 +69,7 @@ router.get("/:userId", async (req: Request, res: Response) => { goalId: goalDoc.id, userId: goalData.userId, text: goalData.post.text, - storedURL: goalData.post.storedURL, + storedId: goalData.post.storedId, submittedAt: goalData.post.submittedAt.toDate(), }); } @@ -86,19 +86,19 @@ router.get("/:userId", async (req: Request, res: Response) => { router.post("/", async (req: Request, res: Response) => { let goalId: PostWithGoalId["goalId"]; let text: PostWithGoalId["text"]; - let storedURL: PostWithGoalId["storedURL"]; + let storedId: PostWithGoalId["storedId"]; let submittedAt: PostWithGoalId["submittedAt"]; try { - ({ goalId, text = "", storedURL, submittedAt } = req.body); + ({ goalId, text = "", storedId, submittedAt } = req.body); } catch (error) { logger.error(error); return res.status(400).json({ message: "Invalid request body" }); } - if (!goalId || !storedURL || !submittedAt) { + if (!goalId || !storedId || !submittedAt) { return res.status(400).json({ - message: "userId, storedURL, goalId, and submittedAt are required", + message: "userId, storedId, goalId, and submittedAt are required", }); } @@ -119,7 +119,7 @@ router.post("/", async (req: Request, res: Response) => { await goalRef.update({ post: { text, - storedURL, + storedId, submittedAt: new Date(submittedAt), }, }); @@ -150,13 +150,13 @@ router.delete("/:goalId", async (req: Request, res: Response) => { } // Storageから画像を削除 - const storedURL = goalDoc.data()?.post?.storedURL; - if (storedURL) { + const storedId = goalDoc.data()?.post?.storedId; + if (storedId) { try { const bucket = admin.storage().bucket(); - const file = bucket.file(`post/${storedURL}`); + const file = bucket.file(`post/${storedId}`); await file.delete(); - logger.info("Image deleted successfully:", storedURL); + logger.info("Image deleted successfully:", storedId); } catch (error) { logger.error("Error deleting image:", error); return res.status(500).json({ message: "Error deleting image" }); diff --git a/functions/src/routers/resultRouter.ts b/functions/src/routers/resultRouter.ts index fec7661..4d2bfd5 100644 --- a/functions/src/routers/resultRouter.ts +++ b/functions/src/routers/resultRouter.ts @@ -49,7 +49,7 @@ const getResults = async ( text: data.text, post: post && { text: post.text, - storedURL: post.storedURL, + storedId: post.storedId, submittedAt: post.submittedAt.toDate(), }, }; diff --git a/functions/src/types.ts b/functions/src/types.ts index bdd99b8..c067b2c 100644 --- a/functions/src/types.ts +++ b/functions/src/types.ts @@ -24,7 +24,7 @@ export interface GoalWithIdAndUserData extends Goal { export interface Post { userId?: string; - storedURL: string; + storedId: string; text: string; submittedAt: Date; } diff --git a/src/Components/PostModal/PostModal.tsx b/src/Components/PostModal/PostModal.tsx index 59eaa76..eaae85b 100644 --- a/src/Components/PostModal/PostModal.tsx +++ b/src/Components/PostModal/PostModal.tsx @@ -95,7 +95,7 @@ export default function PostModal({ async (url, id) => { const postData: PostWithGoalId = { userId: user?.userId as string, - storedURL: id, + storedId: id, text: text, goalId: goalId, submittedAt: new Date(), diff --git a/src/Components/Progress/Progress.tsx b/src/Components/Progress/Progress.tsx index 1b86486..e83a1f3 100644 --- a/src/Components/Progress/Progress.tsx +++ b/src/Components/Progress/Progress.tsx @@ -126,7 +126,7 @@ const SuccessStep = ({ } const storage = getStorage(); - const imageRef = ref(storage, `post/${post.storedURL}`); + const imageRef = ref(storage, `post/${post.storedId}`); getDownloadURL(imageRef) .then((url) => { diff --git a/src/types/types.ts b/src/types/types.ts index a78304f..c7fe0df 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -25,7 +25,7 @@ export interface GoalWithIdAndUserData extends Goal { export interface Post { userId: string; - storedURL: string; + storedId: string; text: string; submittedAt: Date | string; } diff --git a/src/utils/API/User/fetchUser.ts b/src/utils/API/User/fetchUser.ts index 5a9c832..2416897 100644 --- a/src/utils/API/User/fetchUser.ts +++ b/src/utils/API/User/fetchUser.ts @@ -30,7 +30,7 @@ export const fetchUserById = async (userId: string): Promise => { * @return {*} */ export const handleFetchUserError = (error: unknown) => { - let snackBarMessage = "ユーザー情報の取得に失敗しました"; + let snackBarMessage = "初回ログインかユーザーデータが見つかりません"; if (error instanceof Error) { console.error("Fetch error:", error.message); From a7c9dc673aa87660f0facd7ae536f46c57a46a7e Mon Sep 17 00:00:00 2001 From: MurakawaTakuya Date: Sun, 29 Dec 2024 22:23:43 +0900 Subject: [PATCH 5/6] =?UTF-8?q?result=E3=81=AE=E3=82=AF=E3=82=A8=E3=83=AA?= =?UTF-8?q?=E3=82=92=E6=9C=80=E9=81=A9=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Documents/API.md | 4 +- functions/src/index.ts | 8 +- functions/src/routers/resultRouter.ts | 158 +++++++++++++------------ functions/src/routers/userRouter.ts | 4 +- src/Components/DashBoard/DashBoard.tsx | 4 +- src/utils/API/Result/fetchResult.ts | 34 ++++-- 6 files changed, 118 insertions(+), 94 deletions(-) diff --git a/Documents/API.md b/Documents/API.md index a8491f5..96f815f 100644 --- a/Documents/API.md +++ b/Documents/API.md @@ -201,8 +201,10 @@ Use Create Post API to update post. - URL: /result/:?userId - Empty userId will return all results. - Parameters - - limit?: number - The maximum number of results to return.(Default is 50) + - limit?: number - The maximum number of results to return. (Default is 50) - offset?: number - The number of results to skip before starting to collect the result set. + - onlyPending?: boolean - If true, only pending goals will be returned. (Default is false) + - onlyCompleted?: boolean - If true, only completed or failed goals will be returned. (Default is false) - Method: GET - Response ```json diff --git a/functions/src/index.ts b/functions/src/index.ts index f89aeca..077b26d 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -61,22 +61,22 @@ if (process.env.NODE_ENV === "production") { }); } -// 10分間で最大300回に制限 +// 10分間で最大100回に制限 app.use( rateLimit({ windowMs: 10 * 60 * 1000, - max: 300, + max: 100, keyGenerator: (req) => { const key = req.headers["x-forwarded-for"] || req.ip || "unknown"; return Array.isArray(key) ? key[0] : key; }, }) ); -// 1時間で最大1000回に制限 +// 1時間で最大300回に制限 app.use( rateLimit({ windowMs: 60 * 60 * 1000, - max: 1000, + max: 300, keyGenerator: (req) => { const key = req.headers["x-forwarded-for"] || req.ip || "unknown"; return Array.isArray(key) ? key[0] : key; diff --git a/functions/src/routers/resultRouter.ts b/functions/src/routers/resultRouter.ts index 4d2bfd5..99b755f 100644 --- a/functions/src/routers/resultRouter.ts +++ b/functions/src/routers/resultRouter.ts @@ -3,72 +3,89 @@ import admin from "firebase-admin"; import { logger } from "firebase-functions"; import { countCompletedGoals, countFailedGoals, getStreak } from "../status"; import { GoalWithIdAndUserData, User } from "../types"; +import { getUserFromId } from "./userRouter"; const router = express.Router(); const db = admin.firestore(); const getResults = async ( + res: Response, limit: number, offset: number, userId?: string, - includeSuccess = true, - includeFailed = true, - includePending = true + onlyPending = false, + onlyFinished = false ) => { - let goalQuery = db.collection("goal").limit(limit).offset(offset); + let baseQuery = db.collection("goal").limit(limit).offset(offset); + if (userId) { - goalQuery = goalQuery.where("userId", "==", userId); + const userDoc = await getUserFromId(userId); + if (!userDoc.exists) { + return res.status(404).json({ message: "User not found" }); + } + baseQuery = baseQuery.where("userId", "==", userId); } - if (!includeSuccess) { - goalQuery = goalQuery.where("post", "==", null); + if (onlyPending && onlyFinished) { + return res.status(400).json({ + message: + "Cannot set both 'onlyPending' and 'onlyFinished'. Please set only one of 'onlyPending' or 'onlyFinished', or leave both false.", + }); } - if (!includeFailed) { - goalQuery = goalQuery - .where("post", "!=", null) - .where("deadline", ">", new Date()); - } + const now = admin.firestore.Timestamp.now(); + + const pendingResults: GoalWithIdAndUserData[] = []; + const successResults: GoalWithIdAndUserData[] = []; + const failedResults: GoalWithIdAndUserData[] = []; - if (!includePending) { - goalQuery = goalQuery + const userList = new Map(); // ユーザー情報のキャッシュ + + if (onlyPending || (!onlyPending && !onlyFinished)) { + const pendingSnapshot = await baseQuery .where("post", "==", null) - .where("deadline", "<=", new Date()); + .where("deadline", ">", now) + .get(); + + const pendingGoals = await processGoals(pendingSnapshot.docs, userList); + pendingResults.push(...pendingGoals); } - const goalSnapshot = await goalQuery.get(); + if (onlyFinished || (!onlyPending && !onlyFinished)) { + const completedSnapshot = await baseQuery.where("post", "!=", null).get(); - const goals = goalSnapshot.docs.map((doc) => { - const data = doc.data(); - const post = data.post; + const failedSnapshot = await baseQuery + .where("post", "==", null) + .where("deadline", "<=", now) + .get(); - return { - goalId: doc.id, - userId: data.userId, - deadline: data.deadline.toDate(), - text: data.text, - post: post && { - text: post.text, - storedId: post.storedId, - submittedAt: post.submittedAt.toDate(), - }, - }; - }) as GoalWithIdAndUserData[]; + const completedResults = await processGoals( + completedSnapshot.docs, + userList + ); + const failedResultsTemp = await processGoals(failedSnapshot.docs, userList); - if (!goals || goals.length === 0) { - return { successResults: [], failedResults: [], pendingResults: [] }; + successResults.push(...completedResults); + failedResults.push(...failedResultsTemp); } - const successResults: GoalWithIdAndUserData[] = []; - const failedResults: GoalWithIdAndUserData[] = []; - const pendingResults: GoalWithIdAndUserData[] = []; + return { + successResults, + failedResults, + pendingResults, + }; +}; - // mapでのリストを作成し、userNameをキャッシュする - const userList = new Map(); +const processGoals = async ( + docs: FirebaseFirestore.QueryDocumentSnapshot[], + userList: Map +) => { + const results: GoalWithIdAndUserData[] = []; + + for (const doc of docs) { + const data = doc.data(); + const userId = data.userId; - for (const goal of goals) { - // userListにあるならば、userNameを取得し、無いならばfirestoreから取得してキャッシュする - const userId = goal.userId; let userData = userList.get(userId); if (!userData) { const userDoc = await db.collection("user").doc(userId).get(); @@ -86,57 +103,44 @@ const getResults = async ( userList.set(userId, userData); } - const post = goal.post; - if (post) { - if (post.submittedAt > goal.deadline) { - failedResults.push({ - ...goal, - userData, - }); - } else { - successResults.push({ - ...goal, - userData, - }); - } - } else if (goal.deadline < new Date()) { - failedResults.push({ - ...goal, - userData, - }); - } else { - pendingResults.push({ - ...goal, - userData, - }); - } + const post = data.post; + results.push({ + goalId: doc.id, + userId: data.userId, + deadline: data.deadline.toDate(), + text: data.text, + post: post && { + text: post.text, + storedId: post.storedId, + submittedAt: post.submittedAt.toDate(), + }, + userData, + }); } - return { - successResults, - failedResults, - pendingResults, - }; + return results; }; // GET: 全ての目標または特定のユーザーの目標に対する結果を取得 router.get("/:userId?", async (req: Request, res: Response) => { const userId = req.params.userId; - const limit = parseInt(req.query.limit as string) || 100; // TODO: デフォルト値を適切に設定 + let limit = parseInt(req.query.limit as string) || 100; + if (limit < 1 || limit > 100) { + limit = 100; + } const offset = parseInt(req.query.offset as string) || 0; - const includeSuccess = req.query.success !== "false"; - const includeFailed = req.query.failed !== "false"; - const includePending = req.query.pending !== "false"; + const onlyPending = req.query.onlyPending === "true"; // デフォルト: false + const onlyFinished = req.query.onlyFinished === "true"; // デフォルト: false try { const results = await getResults( + res, limit, offset, userId, - includeSuccess, - includeFailed, - includePending + onlyPending, + onlyFinished ); return res.json(results); } catch (error) { diff --git a/functions/src/routers/userRouter.ts b/functions/src/routers/userRouter.ts index 893ffac..c9de9f0 100644 --- a/functions/src/routers/userRouter.ts +++ b/functions/src/routers/userRouter.ts @@ -204,10 +204,10 @@ router.delete("/:userId", async (req: Request, res: Response) => { export default router; // ユーザー名からユーザー情報を取得 -const getUserFromName = async (userName: string) => { +export const getUserFromName = async (userName: string) => { return await db.collection("user").where("name", "==", userName).get(); }; -const getUserFromId = async (userId: string) => { +export const getUserFromId = async (userId: string) => { return await db.collection("user").doc(userId).get(); }; diff --git a/src/Components/DashBoard/DashBoard.tsx b/src/Components/DashBoard/DashBoard.tsx index 6706203..7ba87f3 100644 --- a/src/Components/DashBoard/DashBoard.tsx +++ b/src/Components/DashBoard/DashBoard.tsx @@ -38,7 +38,7 @@ export default function DashBoard({ const [isLoading, setIsLoading] = useState(true); useEffect(() => { - fetchResult({ userId }) + fetchResult({ userId, success, failed, pending }) .then((data) => { setSuccessResults(data.successResults); setFailedResults(data.failedResults); @@ -54,7 +54,7 @@ export default function DashBoard({ type: "warning", }); }); - }, [userId]); + }, [userId, success, failed, pending]); useEffect(() => { // 表示したい項目にデータがない場合はnoResultをtrueにする diff --git a/src/utils/API/Result/fetchResult.ts b/src/utils/API/Result/fetchResult.ts index 05b6a4c..ccf3ac5 100644 --- a/src/utils/API/Result/fetchResult.ts +++ b/src/utils/API/Result/fetchResult.ts @@ -9,14 +9,32 @@ import { appCheckToken, functionsEndpoint } from "@/app/firebase"; */ export const fetchResult = async ({ userId = "", -}: { userId?: string } = {}) => { - const response = await fetch(`${functionsEndpoint}/result/${userId}`, { - method: "GET", - headers: { - "X-Firebase-AppCheck": appCheckToken, - "Content-Type": "application/json", - }, - }); + success = true, + failed = true, + pending = true, +}: { + userId?: string; + success?: boolean; + failed?: boolean; + pending?: boolean; +} = {}) => { + const queryParams = new URLSearchParams(); + if (!success && !failed && pending) { + queryParams.append("onlyPending", "true"); + } else if (success && failed && !pending) { + queryParams.append("onlyFinished", "true"); + } + + const response = await fetch( + `${functionsEndpoint}/result/${userId}?${queryParams.toString()}`, + { + method: "GET", + headers: { + "X-Firebase-AppCheck": appCheckToken, + "Content-Type": "application/json", + }, + } + ); if (!response.ok) { throw new Error("Network response was not ok"); From 90315c2e554cadddba230dffb540dbec26cbb855 Mon Sep 17 00:00:00 2001 From: MurakawaTakuya Date: Sun, 29 Dec 2024 22:34:24 +0900 Subject: [PATCH 6/6] =?UTF-8?q?dashboard=E3=82=92=E8=87=AA=E5=8B=95?= =?UTF-8?q?=E3=81=A7=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- functions/src/index.ts | 10 ++++++++++ functions/src/routers/resultRouter.ts | 2 +- src/Components/DashBoard/DashBoard.tsx | 17 ++++++++++++++--- .../DeleteGoalModal/DeleteGoalModal.tsx | 2 ++ .../DeletePostModal/DeletePostModal.tsx | 2 ++ src/Components/GoalModal/CreateGoalModal.tsx | 2 ++ src/Components/NameUpdate/NameUpdate.tsx | 4 +++- src/utils/API/Goal/createGoal.ts | 4 +++- src/utils/API/Post/createPost.ts | 4 +++- src/utils/API/Result/fetchResult.ts | 5 ++++- src/utils/API/User/createUser.ts | 5 ++++- src/utils/API/User/fetchUser.ts | 8 +++++++- 12 files changed, 55 insertions(+), 10 deletions(-) diff --git a/functions/src/index.ts b/functions/src/index.ts index 077b26d..cfb43c0 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -70,6 +70,11 @@ app.use( const key = req.headers["x-forwarded-for"] || req.ip || "unknown"; return Array.isArray(key) ? key[0] : key; }, + handler: (req, res) => { + return res + .status(429) + .json({ message: "Too many requests, please try again later." }); + }, }) ); // 1時間で最大300回に制限 @@ -81,6 +86,11 @@ app.use( const key = req.headers["x-forwarded-for"] || req.ip || "unknown"; return Array.isArray(key) ? key[0] : key; }, + handler: (req, res) => { + return res + .status(429) + .json({ message: "Too many requests, please try again later." }); + }, }) ); diff --git a/functions/src/routers/resultRouter.ts b/functions/src/routers/resultRouter.ts index 99b755f..e7c6be4 100644 --- a/functions/src/routers/resultRouter.ts +++ b/functions/src/routers/resultRouter.ts @@ -125,7 +125,7 @@ const processGoals = async ( router.get("/:userId?", async (req: Request, res: Response) => { const userId = req.params.userId; - let limit = parseInt(req.query.limit as string) || 100; + let limit = parseInt(req.query.limit as string) || 10; if (limit < 1 || limit > 100) { limit = 100; } diff --git a/src/Components/DashBoard/DashBoard.tsx b/src/Components/DashBoard/DashBoard.tsx index 7ba87f3..40d3265 100644 --- a/src/Components/DashBoard/DashBoard.tsx +++ b/src/Components/DashBoard/DashBoard.tsx @@ -11,7 +11,9 @@ import { useEffect, useState } from "react"; import Progress from "../Progress/Progress"; import styles from "./DashBoard.module.scss"; -// 投稿を取得してProgress +// eslint-disable-next-line @typescript-eslint/no-empty-function +let rerenderDashBoard: () => void = () => {}; + export default function DashBoard({ userId = "", success = true, @@ -37,7 +39,8 @@ export default function DashBoard({ const [noResult, setNoResult] = useState(false); const [isLoading, setIsLoading] = useState(true); - useEffect(() => { + const fetchData = () => { + setIsLoading(true); fetchResult({ userId, success, failed, pending }) .then((data) => { setSuccessResults(data.successResults); @@ -54,10 +57,14 @@ export default function DashBoard({ type: "warning", }); }); + }; + + useEffect(() => { + rerenderDashBoard = fetchData; + fetchData(); }, [userId, success, failed, pending]); useEffect(() => { - // 表示したい項目にデータがない場合はnoResultをtrueにする setNoResult( ((success && successResults.length === 0) || !success) && ((failed && failedResults.length === 0) || !failed) && @@ -92,3 +99,7 @@ export default function DashBoard({ ); } + +export function triggerDashBoardRerender() { + rerenderDashBoard(); +} diff --git a/src/Components/DeleteGoalModal/DeleteGoalModal.tsx b/src/Components/DeleteGoalModal/DeleteGoalModal.tsx index ca681b9..cb79308 100644 --- a/src/Components/DeleteGoalModal/DeleteGoalModal.tsx +++ b/src/Components/DeleteGoalModal/DeleteGoalModal.tsx @@ -1,5 +1,6 @@ "use client"; import { appCheckToken, functionsEndpoint } from "@/app/firebase"; +import { triggerDashBoardRerender } from "@/Components/DashBoard/DashBoard"; // インポートパスを修正 import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import { DialogContent, DialogTitle, Modal, ModalDialog } from "@mui/joy"; import JoyButton from "@mui/joy/Button"; @@ -31,6 +32,7 @@ export default function DeleteGoalModal({ goalId }: { goalId: string }) { message: "目標を削除しました", type: "success", }); + triggerDashBoardRerender(); } }; diff --git a/src/Components/DeletePostModal/DeletePostModal.tsx b/src/Components/DeletePostModal/DeletePostModal.tsx index 162ff45..9c708aa 100644 --- a/src/Components/DeletePostModal/DeletePostModal.tsx +++ b/src/Components/DeletePostModal/DeletePostModal.tsx @@ -6,6 +6,7 @@ import JoyButton from "@mui/joy/Button"; import Button from "@mui/material/Button"; import Stack from "@mui/material/Stack"; import { useState } from "react"; +import { triggerDashBoardRerender } from "../DashBoard/DashBoard"; import { showSnackBar } from "../SnackBar/SnackBar"; export default function DeletePostModal({ @@ -37,6 +38,7 @@ export default function DeletePostModal({ message: "目標を削除しました", type: "success", }); + triggerDashBoardRerender(); } }; diff --git a/src/Components/GoalModal/CreateGoalModal.tsx b/src/Components/GoalModal/CreateGoalModal.tsx index 9c83f48..35380e8 100644 --- a/src/Components/GoalModal/CreateGoalModal.tsx +++ b/src/Components/GoalModal/CreateGoalModal.tsx @@ -15,6 +15,7 @@ import { Typography, } from "@mui/joy"; import React, { useEffect, useState } from "react"; +import { triggerDashBoardRerender } from "../DashBoard/DashBoard"; export default function CreateGoalModal({ open, @@ -81,6 +82,7 @@ export default function CreateGoalModal({ message: "目標を作成しました", type: "success", }); + triggerDashBoardRerender(); setText(""); setDueDate(""); diff --git a/src/Components/NameUpdate/NameUpdate.tsx b/src/Components/NameUpdate/NameUpdate.tsx index 5cd722e..4a9c911 100644 --- a/src/Components/NameUpdate/NameUpdate.tsx +++ b/src/Components/NameUpdate/NameUpdate.tsx @@ -38,7 +38,9 @@ export default function NameUpdate() { ); if (!response.ok) { - throw new Error("Network response was not ok"); + const status = response.status; + const data = await response.json(); + throw new Error(`Error ${status}: ${data.message}`); } // Firebase AuthenticationのdisplayNameを更新 diff --git a/src/utils/API/Goal/createGoal.ts b/src/utils/API/Goal/createGoal.ts index 6639db0..259f7a0 100644 --- a/src/utils/API/Goal/createGoal.ts +++ b/src/utils/API/Goal/createGoal.ts @@ -18,7 +18,9 @@ export const createGoal = async (postData: Goal) => { }); if (!response.ok) { - throw new Error(`Network response was not ok: ${response.statusText}`); + const status = response.status; + const data = await response.json(); + throw new Error(`Error ${status}: ${data.message}`); } return await response.json(); diff --git a/src/utils/API/Post/createPost.ts b/src/utils/API/Post/createPost.ts index 0c341c7..158e9d1 100644 --- a/src/utils/API/Post/createPost.ts +++ b/src/utils/API/Post/createPost.ts @@ -18,7 +18,9 @@ export const createPost = async (postData: PostWithGoalId) => { }); if (!response.ok) { - throw new Error(`Network response was not ok: ${response.statusText}`); + const status = response.status; + const data = await response.json(); + throw new Error(`Error ${status}: ${data.message}`); } return await response.json(); diff --git a/src/utils/API/Result/fetchResult.ts b/src/utils/API/Result/fetchResult.ts index ccf3ac5..247bc69 100644 --- a/src/utils/API/Result/fetchResult.ts +++ b/src/utils/API/Result/fetchResult.ts @@ -37,8 +37,11 @@ export const fetchResult = async ({ ); if (!response.ok) { - throw new Error("Network response was not ok"); + const status = response.status; + const data = await response.json(); + throw new Error(`Error ${status}: ${data.message}`); } + const data = await response.json(); return data; }; diff --git a/src/utils/API/User/createUser.ts b/src/utils/API/User/createUser.ts index 93176a7..08e434f 100644 --- a/src/utils/API/User/createUser.ts +++ b/src/utils/API/User/createUser.ts @@ -18,8 +18,11 @@ export const createUser = async (name: string, userId: string) => { }); if (!response.ok) { - throw new Error("Network response was not ok"); + const status = response.status; + const data = await response.json(); + throw new Error(`Error ${status}: ${data.message}`); } + const data = await response.json(); console.log("Success:", data); }; diff --git a/src/utils/API/User/fetchUser.ts b/src/utils/API/User/fetchUser.ts index 2416897..d061cf2 100644 --- a/src/utils/API/User/fetchUser.ts +++ b/src/utils/API/User/fetchUser.ts @@ -17,8 +17,11 @@ export const fetchUserById = async (userId: string): Promise => { }); if (!response.ok) { - throw new Error("Network response was not ok"); + const status = response.status; + const data = await response.json(); + throw new Error(`Error ${status}: ${data.message}`); } + const data = await response.json(); return data; }; @@ -40,6 +43,9 @@ export const handleFetchUserError = (error: unknown) => { if (error.message.includes("500")) { snackBarMessage = "サーバーエラーが発生しました"; } + if (error.message.includes("429")) { + snackBarMessage = "リクエストが多すぎます。数分後に再度お試しください"; + } } else { console.error("An unknown error occurred"); snackBarMessage = "不明なエラーが発生しました";