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);
},
);