diff --git a/client/functions/[[path]].ts b/client/functions/[[path]].ts index f043acc..1180b30 100644 --- a/client/functions/[[path]].ts +++ b/client/functions/[[path]].ts @@ -17,7 +17,6 @@ interface ProjectData { }>; } -// og:title を書き換える HTMLRewriter class OGTitleRewriter { private title: string; @@ -32,7 +31,6 @@ class OGTitleRewriter { } } -// og:description を書き換える HTMLRewriter class OGDescriptionRewriter { private description: string; @@ -47,7 +45,20 @@ class OGDescriptionRewriter { } } -// 日付を YYYY/MM/DD 形式にフォーマット(日本時間) +class OGImageRewriter { + private imageUrl: string; + + constructor(imageUrl: string) { + this.imageUrl = imageUrl; + } + + element(element: Element) { + if (element.getAttribute("property") === "og:image") { + element.setAttribute("content", this.imageUrl); + } + } +} + function formatDate(dateString: string): string { const date = new Date(dateString); return new Intl.DateTimeFormat("ja-JP", { @@ -74,14 +85,12 @@ async function fetchProjectData(eventId: string, apiEndpoint: string): Promise

): Promise): Promise +import { ImageResponse } from "@cloudflare/pages-plugin-vercel-og/api"; +// biome-ignore lint/correctness/noUnusedImports: React is needed for JSX transform +import React from "react"; + +interface Env { + API_ENDPOINT: string; +} + +interface ProjectData { + id: string; + name: string; + startDate: string; + endDate: string; +} + +async function fetchProjectData(eventId: string, apiEndpoint: string): Promise { + try { + const response = await fetch(`${apiEndpoint}/projects/${eventId}`); + if (!response.ok) { + return null; + } + return await response.json(); + } catch (error) { + console.error("Failed to fetch project data:", error); + return null; + } +} + +function formatDate(dateString: string): string { + const date = new Date(dateString); + return new Intl.DateTimeFormat("ja-JP", { + year: "numeric", + month: "2-digit", + day: "2-digit", + timeZone: "Asia/Tokyo", + }) + .format(date) + .replace(/-/g, "/"); +} + +function truncateByWidth(text: string, maxWidth = 60): string { + let width = 0; + let result = ""; + + for (const char of text) { + // ASCII なら 1、それ以外は 2 + const charWidth = char.charCodeAt(0) < 128 ? 1 : 2; + + if (width + charWidth > maxWidth) { + break; + } + + width += charWidth; + result += char; + } + + if (result.length < text.length) { + result += "…"; + } + + return result; +} + +export const onRequestGet: PagesFunction = async (context) => { + const { env, params } = context; + const eventId = params.eventId; + + if (typeof eventId !== "string") { + console.error("eventId is not a string"); + return new Response("Invalid event ID", { status: 400 }); + } + + if (!eventId || !/^[A-Za-z0-9_-]{21}$/.test(eventId)) { + return new Response("Invalid event ID", { status: 400 }); + } + + const projectData = await fetchProjectData(eventId, env.API_ENDPOINT); + + if (!projectData) { + // プロジェクトがない場合はデフォルト画像にリダイレクト + const url = new URL(context.request.url); + return Response.redirect(`${url.origin}/og-image.jpg`, 302); + } + + const fontData = await fetch( + "https://unpkg.com/@fontsource/m-plus-1p@5.2.8/files/m-plus-1p-japanese-700-normal.woff", + ).then((res) => res.arrayBuffer()); + + const projectName = truncateByWidth(projectData.name, 72); + + const startDate = formatDate(projectData.startDate); + const endDate = formatDate(projectData.endDate); + const dateRange = startDate === endDate ? startDate : `${startDate} 〜 ${endDate}`; + + const response = new ImageResponse( +

+
+
+ + {projectName} + + + {dateRange} + +
+
+ +
イツヒマ
+
+
+
, + { + width: 1200, + height: 630, + fonts: [ + { + name: "M PLUS 1p", + data: fontData, + weight: 700, + style: "normal", + }, + ], + }, + ); + + return response; +}; diff --git a/client/package.json b/client/package.json index 0c05ba8..f75ad3e 100644 --- a/client/package.json +++ b/client/package.json @@ -12,14 +12,12 @@ "preview": "vite preview" }, "dependencies": { - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.0", + "@cloudflare/pages-plugin-vercel-og": "^0.1.2", "@fullcalendar/core": "^6.1.15", "@fullcalendar/interaction": "^6.1.15", "@fullcalendar/react": "^6.1.15", "@fullcalendar/timegrid": "^6.1.15", "@hookform/resolvers": "^4.1.3", - "@mui/material": "^6.4.7", "@tailwindcss/vite": "^4.0.13", "dayjs": "^1.11.13", "react": "^19.0.0", @@ -37,7 +35,6 @@ "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", "daisyui": "^5.0.3", - "globals": "^15.15.0", "hono": "^4.9.6", "typescript": "~5.7.2", "vite": "^6.3.5" diff --git a/client/src/pages/Home.tsx b/client/src/pages/Home.tsx index 258b0f9..0430380 100644 --- a/client/src/pages/Home.tsx +++ b/client/src/pages/Home.tsx @@ -31,8 +31,9 @@ export default function HomePage() { let errorMessage = "イベントの取得に失敗しました。"; try { const data = await res.json(); - if (data && typeof data.message === "string" && data.message.trim()) { - errorMessage = data.message.trim(); + const err = data as unknown as { message: string }; // Middleware のレスポンスは Hono RPC の型に乗らない + if (typeof err.message === "string" && err.message.trim()) { + errorMessage = err.message.trim(); } } catch (_) { // レスポンスがJSONでない場合は無視 diff --git a/client/src/pages/Project.tsx b/client/src/pages/Project.tsx index 5b6be0d..b7230a4 100644 --- a/client/src/pages/Project.tsx +++ b/client/src/pages/Project.tsx @@ -93,6 +93,7 @@ export default function ProjectPage() { projectName: string; } | null>(null); + // TODO: グローバルにしないと、delete の際は遷移を伴うので表示されない const [toast, setToast] = useState<{ message: string; variant: "success" | "error"; @@ -215,9 +216,8 @@ export default function ProjectPage() { projectName: name, }); } else { - const { message } = await res.json(); setToast({ - message, + message: "イベントの作成に失敗しました。", variant: "error", }); setTimeout(() => setToast(null), 3000); @@ -604,6 +604,7 @@ export default function ProjectPage() { if (!res.ok) { throw new Error("削除に失敗しました。"); } + // TODO: トーストをグローバルにする navigate("/home"); setToast({ message: "イベントを削除しました。", diff --git a/package-lock.json b/package-lock.json index b3bf946..add9790 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,14 +19,12 @@ "client": { "version": "0.0.0", "dependencies": { - "@emotion/react": "^11.14.0", - "@emotion/styled": "^11.14.0", + "@cloudflare/pages-plugin-vercel-og": "^0.1.2", "@fullcalendar/core": "^6.1.15", "@fullcalendar/interaction": "^6.1.15", "@fullcalendar/react": "^6.1.15", "@fullcalendar/timegrid": "^6.1.15", "@hookform/resolvers": "^4.1.3", - "@mui/material": "^6.4.7", "@tailwindcss/vite": "^4.0.13", "dayjs": "^1.11.13", "react": "^19.0.0", @@ -44,7 +42,6 @@ "@types/react-dom": "^19.0.4", "@vitejs/plugin-react": "^4.3.4", "daisyui": "^5.0.3", - "globals": "^15.15.0", "hono": "^4.9.6", "typescript": "~5.7.2", "vite": "^6.3.5" @@ -92,6 +89,7 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", @@ -154,6 +152,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.27.0", @@ -187,6 +186,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.9", @@ -228,6 +228,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -237,6 +238,7 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -270,6 +272,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.27.0" @@ -313,22 +316,11 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/runtime": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", - "license": "MIT", - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/template": { "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", @@ -343,6 +335,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.26.2", @@ -361,6 +354,7 @@ "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, "license": "MIT", "engines": { "node": ">=4" @@ -370,6 +364,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -533,158 +528,18 @@ "node": ">=14.21.3" } }, + "node_modules/@cloudflare/pages-plugin-vercel-og": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@cloudflare/pages-plugin-vercel-og/-/pages-plugin-vercel-og-0.1.2.tgz", + "integrity": "sha512-aWA2pHu1R2UbOJIcSASCuViyYVEhYkZ3tWTrTnr+yLosBfksOzjecHQkCkPv/jy75hGPrn1BbtsQR+oWVFkp1g==", + "license": "MIT" + }, "node_modules/@cloudflare/workers-types": { "version": "4.20250610.0", "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20250610.0.tgz", "integrity": "sha512-HxnUoey3QxCEfy07pUm7J42jBi9YPHq/hA3fw6JmOqYLHdviHI28OA8lup+2RUaHwDzh6q1DSfrBvvDqde645A==", "dev": true }, - "node_modules/@emotion/babel-plugin": { - "version": "11.13.5", - "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", - "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.16.7", - "@babel/runtime": "^7.18.3", - "@emotion/hash": "^0.9.2", - "@emotion/memoize": "^0.9.0", - "@emotion/serialize": "^1.3.3", - "babel-plugin-macros": "^3.1.0", - "convert-source-map": "^1.5.0", - "escape-string-regexp": "^4.0.0", - "find-root": "^1.1.0", - "source-map": "^0.5.7", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/cache": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", - "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.9.0", - "@emotion/sheet": "^1.4.0", - "@emotion/utils": "^1.4.2", - "@emotion/weak-memoize": "^0.4.0", - "stylis": "4.2.0" - } - }, - "node_modules/@emotion/hash": { - "version": "0.9.2", - "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", - "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==", - "license": "MIT" - }, - "node_modules/@emotion/is-prop-valid": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", - "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", - "license": "MIT", - "dependencies": { - "@emotion/memoize": "^0.9.0" - } - }, - "node_modules/@emotion/memoize": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", - "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==", - "license": "MIT" - }, - "node_modules/@emotion/react": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", - "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.13.5", - "@emotion/cache": "^11.14.0", - "@emotion/serialize": "^1.3.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", - "@emotion/utils": "^1.4.2", - "@emotion/weak-memoize": "^0.4.0", - "hoist-non-react-statics": "^3.3.1" - }, - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/serialize": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", - "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", - "license": "MIT", - "dependencies": { - "@emotion/hash": "^0.9.2", - "@emotion/memoize": "^0.9.0", - "@emotion/unitless": "^0.10.0", - "@emotion/utils": "^1.4.2", - "csstype": "^3.0.2" - } - }, - "node_modules/@emotion/sheet": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", - "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==", - "license": "MIT" - }, - "node_modules/@emotion/styled": { - "version": "11.14.0", - "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.0.tgz", - "integrity": "sha512-XxfOnXFffatap2IyCeJyNov3kiDQWoR08gPUQxvbL7fxKryGBKUZUkG6Hz48DZwVrJSVh9sJboyV1Ds4OW6SgA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.18.3", - "@emotion/babel-plugin": "^11.13.5", - "@emotion/is-prop-valid": "^1.3.0", - "@emotion/serialize": "^1.3.3", - "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", - "@emotion/utils": "^1.4.2" - }, - "peerDependencies": { - "@emotion/react": "^11.0.0-rc.0", - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@emotion/unitless": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", - "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==", - "license": "MIT" - }, - "node_modules/@emotion/use-insertion-effect-with-fallbacks": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", - "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/@emotion/utils": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", - "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==", - "license": "MIT" - }, - "node_modules/@emotion/weak-memoize": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", - "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", - "license": "MIT" - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz", @@ -1198,6 +1053,7 @@ "version": "0.3.8", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -1212,6 +1068,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1221,6 +1078,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -1230,232 +1088,20 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@mui/core-downloads-tracker": { - "version": "6.4.11", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.11.tgz", - "integrity": "sha512-CzAQs9CTzlwbsF9ZYB4o4lLwBv1/qNE264NjuYao+ctAXsmlPtYa8RtER4UsUXSMxNN9Qi+aQdYcKl2sUpnmAw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - } - }, - "node_modules/@mui/material": { - "version": "6.4.11", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.11.tgz", - "integrity": "sha512-k2D3FLJS+/qD0qnd6ZlAjGFvaaxe1Dl10NyvpeDzIebMuYdn8VqYe6XBgGueEAtnzSJM4V03VD9kb5Fi24dnTA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/core-downloads-tracker": "^6.4.11", - "@mui/system": "^6.4.11", - "@mui/types": "~7.2.24", - "@mui/utils": "^6.4.9", - "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.12", - "clsx": "^2.1.1", - "csstype": "^3.1.3", - "prop-types": "^15.8.1", - "react-is": "^19.0.0", - "react-transition-group": "^4.4.5" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@mui/material-pigment-css": "^6.4.11", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@mui/material-pigment-css": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/private-theming": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.4.9.tgz", - "integrity": "sha512-LktcVmI5X17/Q5SkwjCcdOLBzt1hXuc14jYa7NPShog0GBDCDvKtcnP0V7a2s6EiVRlv7BzbWEJzH6+l/zaCxw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/utils": "^6.4.9", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/styled-engine": { - "version": "6.4.11", - "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.4.11.tgz", - "integrity": "sha512-74AUmlHXaGNbyUqdK/+NwDJOZqgRQw6BcNvhoWYLq3LGbLTkE+khaJ7soz6cIabE4CPYqO2/QAIU1Z/HEjjpcw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@emotion/cache": "^11.13.5", - "@emotion/serialize": "^1.3.3", - "@emotion/sheet": "^1.4.0", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.4.1", - "@emotion/styled": "^11.3.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - } - } - }, - "node_modules/@mui/system": { - "version": "6.4.11", - "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.11.tgz", - "integrity": "sha512-gibtsrZEwnDaT5+I/KloOj/yHluX5G8heknuxBpQOdEQ3Gc0avjSImn5hSeKp8D4thiwZiApuggIjZw1dQguUA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/private-theming": "^6.4.9", - "@mui/styled-engine": "^6.4.11", - "@mui/types": "~7.2.24", - "@mui/utils": "^6.4.9", - "clsx": "^2.1.1", - "csstype": "^3.1.3", - "prop-types": "^15.8.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@emotion/react": "^11.5.0", - "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@emotion/react": { - "optional": true - }, - "@emotion/styled": { - "optional": true - }, - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/types": { - "version": "7.2.24", - "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.24.tgz", - "integrity": "sha512-3c8tRt/CbWZ+pEg7QpSwbdxOk36EfmhbKf6AGZsD1EcLDLTSZoxxJ86FVtcjxvjuhdyBiWKSTGZFaXCnidO2kw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@mui/utils": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.4.9.tgz", - "integrity": "sha512-Y12Q9hbK9g+ZY0T3Rxrx9m2m10gaphDuUMgWxyV5kNJevVxXYCLclYUCC9vXaIk1/NdNDTcW2Yfr2OGvNFNmHg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@mui/types": "~7.2.24", - "@types/prop-types": "^15.7.14", - "clsx": "^2.1.1", - "prop-types": "^15.8.1", - "react-is": "^19.0.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mui-org" - }, - "peerDependencies": { - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@prisma/client": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.6.0.tgz", @@ -2120,22 +1766,11 @@ "undici-types": "~6.21.0" } }, - "node_modules/@types/parse-json": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", - "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==", - "license": "MIT" - }, - "node_modules/@types/prop-types": { - "version": "15.7.14", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", - "integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==", - "license": "MIT" - }, "node_modules/@types/react": { "version": "19.1.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", + "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -2151,15 +1786,6 @@ "@types/react": "^19.0.0" } }, - "node_modules/@types/react-transition-group": { - "version": "4.4.12", - "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", - "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*" - } - }, "node_modules/@vitejs/plugin-react": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", @@ -2180,21 +1806,6 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" } }, - "node_modules/babel-plugin-macros": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", - "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5", - "cosmiconfig": "^7.0.0", - "resolve": "^1.19.0" - }, - "engines": { - "node": ">=10", - "npm": ">=6" - } - }, "node_modules/browserslist": { "version": "4.24.4", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", @@ -2228,15 +1839,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-lite": { "version": "1.0.30001715", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001715.tgz", @@ -2268,54 +1870,15 @@ "resolved": "client", "link": true }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/common": { "resolved": "common", "link": true }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, - "node_modules/cosmiconfig": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", - "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", - "license": "MIT", - "dependencies": { - "@types/parse-json": "^4.0.0", - "import-fresh": "^3.2.1", - "parse-json": "^5.0.0", - "path-type": "^4.0.0", - "yaml": "^1.10.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cosmiconfig/node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, "node_modules/csstype": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "dev": true, "license": "MIT" }, "node_modules/daisyui": { @@ -2338,6 +1901,7 @@ "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "devOptional": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -2360,16 +1924,6 @@ "node": ">=8" } }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, "node_modules/dotenv": { "version": "16.5.0", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", @@ -2402,15 +1956,6 @@ "node": ">=10.13.0" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, "node_modules/esbuild": { "version": "0.25.3", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.3.tgz", @@ -2474,24 +2019,6 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-root": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", - "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", - "license": "MIT" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2506,15 +2033,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2538,52 +2056,12 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "license": "ISC" }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/hoist-non-react-statics": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", - "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", - "license": "BSD-3-Clause", - "dependencies": { - "react-is": "^16.7.0" - } - }, - "node_modules/hoist-non-react-statics/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/hono": { "version": "4.10.6", "resolved": "https://registry.npmjs.org/hono/-/hono-4.10.6.tgz", @@ -2593,43 +2071,6 @@ "node": ">=16.9.0" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/jiti": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", @@ -2643,12 +2084,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -2657,12 +2100,6 @@ "node": ">=6" } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -2904,24 +2341,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -2936,6 +2355,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "devOptional": true, "license": "MIT" }, "node_modules/nanoid": { @@ -2963,60 +2383,6 @@ "dev": true, "license": "MIT" }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -3108,23 +2474,6 @@ } } }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, - "node_modules/prop-types/node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, "node_modules/react": { "version": "19.1.0", "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", @@ -3171,12 +2520,6 @@ "react": "*" } }, - "node_modules/react-is": { - "version": "19.1.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.0.tgz", - "integrity": "sha512-Oe56aUPnkHyyDxxkvqtd7KkdQP5uIUfHxd5XTb3wE9d/kRnZLmKbDB0GWk919tdQ+mxxPtG6EAs6RMT6i1qtHg==", - "license": "MIT" - }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -3233,57 +2576,6 @@ "react-dom": ">=16.14.0" } }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", - "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -3359,15 +2651,6 @@ "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", "license": "MIT" }, - "node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -3377,24 +2660,6 @@ "node": ">=0.10.0" } }, - "node_modules/stylis": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", - "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==", - "license": "MIT" - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/tailwindcss": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.4.tgz", @@ -3538,9 +2803,10 @@ } }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", + "license": "MIT", "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/server/src/main.ts b/server/src/main.ts index 480fc95..8eb6867 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -3,16 +3,23 @@ import { PrismaClient } from "@prisma/client"; import dotenv from "dotenv"; import { Hono } from "hono"; import { cors } from "hono/cors"; +import { customAlphabet } from "nanoid"; import { browserIdMiddleware } from "./middleware/browserId.js"; import projectsRoutes from "./routes/projects.js"; dotenv.config(); +/** + * ハイフン・アンダースコアを含まない Nano ID 形式。 + */ +export const nanoid = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 21); + export const prisma = new PrismaClient(); -const port = process.env.PORT || 3000; + +const port = Number(process.env.PORT) || 3000; const allowedOrigins = process.env.CORS_ALLOW_ORIGINS?.split(",") || []; -type AppVariables = { +export type AppVariables = { browserId: string; }; @@ -28,12 +35,16 @@ const app = new Hono<{ Variables: AppVariables }>() .get("/", (c) => { return c.json({ message: "Hello! イツヒマ?" }); }) - .route("/projects", projectsRoutes); + .route("/projects", projectsRoutes) + .onError((err, c) => { + console.error(err); + return c.json({ message: "Internal Server Error" }, 500); + }); serve( { fetch: app.fetch, - port: Number(port), + port, hostname: "0.0.0.0", }, () => { @@ -45,10 +56,9 @@ const isProduction = process.env.NODE_ENV === "prod"; export const cookieOptions = { path: "/", - domain: process.env.DOMAIN, httpOnly: true, secure: isProduction, - sameSite: isProduction ? "none" : "lax", + sameSite: "lax", maxAge: 60 * 60 * 24 * 365, // Express だとミリ秒だったが、Hono では秒らしい } as const; diff --git a/server/src/routes/projects.ts b/server/src/routes/projects.ts index 948ce5d..4938a80 100644 --- a/server/src/routes/projects.ts +++ b/server/src/routes/projects.ts @@ -1,284 +1,249 @@ import { zValidator } from "@hono/zod-validator"; import dotenv from "dotenv"; import { Hono } from "hono"; -import { customAlphabet } from "nanoid"; import { z } from "zod"; import { editReqSchema, projectReqSchema, submitReqSchema } from "../../../common/validators.js"; -import { prisma } from "../main.js"; +import { type AppVariables, nanoid, prisma } from "../main.js"; dotenv.config(); -/** - * ハイフン・アンダースコアを含まない Nano ID 形式。 - */ -const nanoid = customAlphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", 21); - const projectIdParamsSchema = z.object({ projectId: z.string().length(21) }); -type AppVariables = { - browserId: string; -}; - const router = new Hono<{ Variables: AppVariables }>() // プロジェクト作成 .post("/", zValidator("json", projectReqSchema), async (c) => { const browserId = c.get("browserId"); - try { - const data = c.req.valid("json"); - - const event = await prisma.project.create({ - data: { - id: nanoid(), - name: data.name, - description: data.description.trim() || null, - startDate: new Date(data.startDate), - endDate: new Date(data.endDate), - allowedRanges: { - create: data.allowedRanges.map((range) => ({ - startTime: new Date(range.startTime), - endTime: new Date(range.endTime), - })), - }, - hosts: { - create: { - browserId, - }, - }, - participationOptions: { - create: data.participationOptions.map((opt) => ({ - id: opt.id, - label: opt.label, - color: opt.color, - })), + const input = c.req.valid("json"); + + const project = await prisma.project.create({ + data: { + id: nanoid(), + name: input.name, + description: input.description.trim() || null, + startDate: new Date(input.startDate), + endDate: new Date(input.endDate), + allowedRanges: { + create: input.allowedRanges.map((range) => ({ + startTime: new Date(range.startTime), + endTime: new Date(range.endTime), + })), + }, + hosts: { + create: { + browserId, }, }, - include: { hosts: true, participationOptions: true }, - }); - - return c.json({ id: event.id, name: event.name }, 201); - } catch (_err) { - return c.json({ message: "イベント作成時にエラーが発生しました" }, 500); - } + participationOptions: { + create: input.participationOptions.map((opt) => ({ + id: opt.id, + label: opt.label, + color: opt.color, + })), + }, + }, + select: { + id: true, + name: true, + }, + }); + + return c.json({ id: project.id, name: project.name }, 201); }) // 自分が関連するプロジェクト取得 .get("/mine", async (c) => { const browserId = c.get("browserId"); - try { - const involvedProjects = await prisma.project.findMany({ - where: { - OR: [ - { hosts: { some: { browserId } } }, - { - guests: { - some: { browserId }, - }, - }, - ], - }, - select: { - id: true, - name: true, - description: true, - startDate: true, - endDate: true, - hosts: { - select: { - browserId: true, + const projects = await prisma.project.findMany({ + where: { + OR: [ + { hosts: { some: { browserId } } }, + { + guests: { + some: { browserId }, }, }, + ], + }, + include: { + hosts: { + select: { browserId: true }, }, - }); - - return c.json( - involvedProjects.map((p) => ({ - id: p.id, - name: p.name, - description: p.description ?? "", - startDate: p.startDate, - endDate: p.endDate, - isHost: p.hosts.some((host) => host.browserId === browserId), - })), - 200, - ); - } catch (error) { - console.error(error); - return c.json({ message: "エラーが発生しました。" }, 500); - } + }, + }); + + return c.json( + projects.map((p) => ({ + id: p.id, + name: p.name, + description: p.description ?? "", + startDate: p.startDate, + endDate: p.endDate, + isHost: p.hosts.some((host) => host.browserId === browserId), + })), + 200, + ); }) // プロジェクト取得 .get("/:projectId", zValidator("param", projectIdParamsSchema), async (c) => { const browserId = c.get("browserId"); - - try { - const { projectId } = c.req.valid("param"); - const projectRow = await prisma.project.findUnique({ - where: { id: projectId }, - include: { - allowedRanges: true, - participationOptions: true, - guests: { - include: { - slots: true, // slots 全部欲しいなら select より include - }, + const { projectId } = c.req.valid("param"); + + const project = await prisma.project.findUnique({ + where: { id: projectId }, + include: { + allowedRanges: true, + participationOptions: true, + guests: { + include: { + slots: true, }, - hosts: true, // 全部欲しいなら select 省略 }, - }); - - if (!projectRow) { - return c.json({ message: "イベントが見つかりません。" }, 404); - } + hosts: true, + }, + }); - const data = { - ...projectRow, - description: projectRow.description ?? "", - hosts: projectRow.hosts.map((h) => { - const { browserId: _, ...rest } = h; - return rest; - }), - guests: projectRow.guests.map((g) => { - const { browserId: _, ...rest } = g; - return rest; - }), - isHost: projectRow.hosts.some((h) => h.browserId === browserId), - meAsGuest: projectRow.guests.find((g) => g.browserId === browserId) ?? null, - }; - return c.json(data, 200); - } catch (error) { - console.error("イベント取得エラー:", error); - return c.json({ message: "イベント取得中にエラーが発生しました。" }, 500); + if (!project) { + return c.json({ message: "イベントが見つかりません。" }, 404); } + + const guest = project.guests.find((g) => g.browserId === browserId); + const meAsGuest = guest ? (({ browserId, ...rest }) => rest)(guest) : null; + + return c.json( + { + id: project.id, + name: project.name, + description: project.description ?? "", + startDate: project.startDate, + endDate: project.endDate, + allowedRanges: project.allowedRanges, + participationOptions: project.participationOptions, + hosts: project.hosts.map(({ browserId, ...rest }) => rest), + guests: project.guests.map(({ browserId, ...rest }) => rest), + isHost: project.hosts.some((h) => h.browserId === browserId), + meAsGuest, + }, + 200, + ); }) // プロジェクト編集 .put("/:projectId", zValidator("param", projectIdParamsSchema), zValidator("json", editReqSchema), async (c) => { const browserId = c.get("browserId"); - try { - const { projectId } = c.req.valid("param"); - const data = c.req.valid("json"); + const { projectId } = c.req.valid("param"); + const input = c.req.valid("json"); - // ホスト認証とゲスト存在確認を一括取得 - const [host, existingGuest] = await Promise.all([ - prisma.host.findFirst({ - where: { - browserId, - projectId: projectId, - }, - }), - prisma.guest.findFirst({ - where: { projectId: projectId }, - }), - ]); + const [host, existingGuest] = await Promise.all([ + prisma.host.findFirst({ + where: { + browserId, + projectId: projectId, + }, + }), + prisma.guest.findFirst({ + where: { projectId: projectId }, + }), + ]); + + if (!host) { + return c.json({ message: "アクセス権限がありません。" }, 403); + } - // ホストが存在しなければ403 - if (!host) { - return c.json({ message: "アクセス権限がありません。" }, 403); + // 参加形態の更新 + if (input.participationOptions) { + if (input.participationOptions.length === 0) { + return c.json({ message: "参加形態は最低1つ必要です。" }, 400); } - // 参加形態の更新 - if (data.participationOptions) { - // 最低1つの参加形態が必要 - if (data.participationOptions.length === 0) { - return c.json({ message: "参加形態は最低1つ必要です。" }, 400); - } - - // 削除対象の参加形態に Slot が紐づいているかチェック - const existingOptions = await prisma.participationOption.findMany({ - where: { projectId }, - include: { slots: { select: { id: true } } }, - }); - - const newOptionIds = data.participationOptions.map((o) => o.id); - const optionsToDelete = existingOptions.filter((o) => !newOptionIds.includes(o.id)); - const undeletableOptions = optionsToDelete.filter((o) => o.slots.length > 0); - - if (undeletableOptions.length > 0) { - const labels = undeletableOptions.map((o) => o.label).join(", "); - return c.json( - { - message: `以下の参加形態は日程が登録されているため削除できません: ${labels}`, - }, - 400, - ); - } - - await prisma.$transaction([ - // 既存の参加形態で、新しいリストにないものを削除 - prisma.participationOption.deleteMany({ - where: { - projectId, - id: { - notIn: newOptionIds, - }, + // 削除対象の参加形態に Slot が紐づいているかチェック + const existingOptions = await prisma.participationOption.findMany({ + where: { projectId }, + include: { slots: { select: { id: true } } }, + }); + const newOptionIds = input.participationOptions.map((o) => o.id); + const optionsToDelete = existingOptions.filter((o) => !newOptionIds.includes(o.id)); + const undeletableOptions = optionsToDelete.filter((o) => o.slots.length > 0); + if (undeletableOptions.length > 0) { + const labels = undeletableOptions.map((o) => o.label).join(", "); + return c.json( + { + message: `以下の参加形態は日程が登録されているため削除できません: ${labels}`, + }, + 400, + ); + } + + await prisma.$transaction([ + // 既存の参加形態で、新しいリストにないものを削除 + prisma.participationOption.deleteMany({ + where: { + projectId, + id: { + notIn: newOptionIds, }, + }, + }), + // 既存の参加形態を更新または新規作成 + ...input.participationOptions.map((opt) => + prisma.participationOption.upsert({ + where: { id: opt.id }, + update: { label: opt.label, color: opt.color }, + create: { id: opt.id, label: opt.label, color: opt.color, projectId }, }), - // 既存の参加形態を更新または新規作成 - ...data.participationOptions.map((opt) => - prisma.participationOption.upsert({ - where: { id: opt.id }, - update: { label: opt.label, color: opt.color }, - create: { id: opt.id, label: opt.label, color: opt.color, projectId }, - }), - ), - ]); - } + ), + ]); + } - // 更新処理 - const updatedEvent = await prisma.project.update({ - where: { id: projectId }, - data: existingGuest - ? { - name: data.name, - description: data.description?.trim() || null, - } // ゲストがいれば名前と説明だけ - : { - name: data.name, - description: data.description?.trim() || null, - startDate: data.startDate ? new Date(data.startDate) : undefined, - endDate: data.endDate ? new Date(data.endDate) : undefined, - allowedRanges: { - deleteMany: {}, // 既存削除 - create: data.allowedRanges?.map((r) => ({ - startTime: new Date(r.startTime), - endTime: new Date(r.endTime), - })), - }, + const updatedProject = await prisma.project.update({ + where: { id: projectId }, + data: existingGuest + ? { + name: input.name, + description: input.description?.trim() || null, + } + : { + name: input.name, + description: input.description?.trim() || null, + startDate: input.startDate ? new Date(input.startDate) : undefined, + endDate: input.endDate ? new Date(input.endDate) : undefined, + allowedRanges: { + deleteMany: {}, // 既存削除 + create: input.allowedRanges?.map((r) => ({ + startTime: new Date(r.startTime), + endTime: new Date(r.endTime), + })), }, - include: { allowedRanges: true, participationOptions: true }, - }); + }, + include: { allowedRanges: true, participationOptions: true }, + }); - return c.json({ event: updatedEvent }, 200); - } catch (error) { - console.error("イベント更新エラー:", error); - return c.json({ message: "イベント更新中にエラーが発生しました。" }, 500); - } + return c.json({ event: updatedProject }, 200); }) // プロジェクト削除 .delete("/:projectId", zValidator("param", projectIdParamsSchema), async (c) => { const browserId = c.get("browserId"); - try { - const { projectId } = c.req.valid("param"); - // Host 認証 - const host = await prisma.host.findFirst({ - where: { projectId, browserId }, - }); + const { projectId } = c.req.valid("param"); - if (!host) { - return c.json({ message: "削除権限がありません。" }, 403); - } - // 関連データを削除(Cascade を使っていない場合) - await prisma.project.delete({ - where: { id: projectId }, - }); - return c.json({ message: "イベントを削除しました。" }, 200); - } catch (error) { - console.error("イベント削除エラー:", error); - return c.json({ message: "イベント削除中にエラーが発生しました。" }, 500); + const host = await prisma.host.findUnique({ + where: { + browserId_projectId: { + browserId, + projectId, + }, + }, + }); + + if (!host) { + return c.json({ message: "削除権限がありません。" }, 403); } + + await prisma.project.delete({ + where: { id: projectId }, + }); + return c.json(204); }) // 日程の提出。 @@ -287,40 +252,39 @@ const router = new Hono<{ Variables: AppVariables }>() zValidator("param", projectIdParamsSchema), zValidator("json", submitReqSchema), async (c) => { - const { projectId } = c.req.valid("param"); const browserId = c.get("browserId"); + const { projectId } = c.req.valid("param"); + const { name, slots } = c.req.valid("json"); - const existingGuest = await prisma.guest.findFirst({ - where: { projectId, browserId }, + const existingGuest = await prisma.guest.findUnique({ + where: { + browserId_projectId: { + browserId, + projectId, + }, + }, }); if (existingGuest) { - return c.json({ message: "すでに登録済みです" }, 403); + return c.json({ message: "提出済みです。" }, 403); } - const { name, slots } = c.req.valid("json"); - - try { - await prisma.guest.create({ - data: { - name, - browserId, - project: { connect: { id: projectId } }, - slots: { - create: slots?.map((slot) => ({ - from: slot.start, - to: slot.end, - projectId, - participationOptionId: slot.participationOptionId, - })), - }, + await prisma.guest.create({ + data: { + name, + browserId, + project: { connect: { id: projectId } }, + slots: { + create: slots?.map((slot) => ({ + from: slot.start, + to: slot.end, + projectId, + participationOptionId: slot.participationOptionId, + })), }, - include: { slots: true }, - }); - return c.json("日時が登録されました!", 201); - } catch (error) { - console.error("登録エラー:", error); - return c.json({ message: "サーバーエラーが発生しました" }, 500); - } + }, + include: { slots: true }, + }); + return c.json("日程が提出されました。", 201); }, ) @@ -330,44 +294,37 @@ const router = new Hono<{ Variables: AppVariables }>() zValidator("param", projectIdParamsSchema), zValidator("json", submitReqSchema), async (c) => { - const { projectId } = c.req.valid("param"); const browserId = c.get("browserId"); - + const { projectId } = c.req.valid("param"); const { name, slots } = c.req.valid("json"); - try { - const existingGuest = await prisma.guest.findFirst({ - where: { projectId, browserId }, - include: { slots: true }, - }); - - if (!existingGuest) { - return c.json({ message: "ゲスト情報が見つかりません。" }, 404); - } - const slotData = slots?.map((slot) => ({ - from: slot.start, - to: slot.end, - projectId, - participationOptionId: slot.participationOptionId, - })); + const existingGuest = await prisma.guest.findUnique({ + where: { browserId_projectId: { browserId, projectId } }, + include: { slots: true }, + }); - await prisma.slot.deleteMany({ where: { guestId: existingGuest.id } }); + if (!existingGuest) { + return c.json({ message: "既存の日程が見つかりません。" }, 404); + } + const slotData = slots?.map((slot) => ({ + from: slot.start, + to: slot.end, + projectId, + participationOptionId: slot.participationOptionId, + })); - // ゲスト情報更新 - const guest = await prisma.guest.update({ - where: { id: existingGuest.id }, - data: { - slots: { create: slotData }, - name, - }, - include: { slots: true }, - }); + await prisma.slot.deleteMany({ where: { guestId: existingGuest.id } }); - return c.json({ message: "ゲスト情報が更新されました!", guest }, 200); - } catch (error) { - console.error("処理中のエラー:", error); - return c.json({ message: "サーバーエラーが発生しました" }, 500); - } + const guest = await prisma.guest.update({ + where: { id: existingGuest.id }, + data: { + slots: { create: slotData }, + name, + }, + include: { slots: true }, + }); + + return c.json({ message: "日程が更新されました。", guest }, 200); }, );