From 432b52710167f59474d9516c9d689722b2dd1efb Mon Sep 17 00:00:00 2001 From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com> Date: Thu, 6 Nov 2025 21:41:57 +0900 Subject: [PATCH 1/7] Co-authored-by: Yuki Kobayashi --- .gitignore | 2 + bun.lock | 56 ++++++++++++++++++++++ package.json | 4 ++ prisma/migrations/migration_lock.toml | 3 ++ prisma/schema.prisma | 21 ++++++++ src/iframe/life-game.html | 4 +- src/iframe/life-game.js | 69 +++++++++++++++++++++++++++ src/routes/api/board/+server.ts | 36 ++++++++++++++ 8 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 prisma/migrations/migration_lock.toml create mode 100644 prisma/schema.prisma create mode 100644 src/routes/api/board/+server.ts diff --git a/.gitignore b/.gitignore index 3b462cb..b2240a9 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,5 @@ Thumbs.db # Vite vite.config.js.timestamp-* vite.config.ts.timestamp-* + +/../generated/prisma diff --git a/bun.lock b/bun.lock index d7feb7a..6dd91b9 100644 --- a/bun.lock +++ b/bun.lock @@ -3,6 +3,9 @@ "workspaces": { "": { "name": "life-code", + "dependencies": { + "@prisma/client": "^6.19.0", + }, "devDependencies": { "@eslint/compat": "^1.4.0", "@eslint/js": "^9.36.0", @@ -19,6 +22,7 @@ "lefthook": "^1.13.6", "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", + "prisma": "^6.19.0", "svelte": "^5.39.5", "svelte-check": "^4.3.2", "tailwindcss": "^4.1.14", @@ -193,6 +197,20 @@ "@poppinss/exception": ["@poppinss/exception@1.2.2", "", {}, "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg=="], + "@prisma/client": ["@prisma/client@6.19.0", "", { "peerDependencies": { "prisma": "*", "typescript": ">=5.1.0" }, "optionalPeers": ["prisma", "typescript"] }, "sha512-QXFT+N/bva/QI2qoXmjBzL7D6aliPffIwP+81AdTGq0FXDoLxLkWivGMawG8iM5B9BKfxLIXxfWWAF6wbuJU6g=="], + + "@prisma/config": ["@prisma/config@6.19.0", "", { "dependencies": { "c12": "3.1.0", "deepmerge-ts": "7.1.5", "effect": "3.18.4", "empathic": "2.0.0" } }, "sha512-zwCayme+NzI/WfrvFEtkFhhOaZb/hI+X8TTjzjJ252VbPxAl2hWHK5NMczmnG9sXck2lsXrxIZuK524E25UNmg=="], + + "@prisma/debug": ["@prisma/debug@6.19.0", "", {}, "sha512-8hAdGG7JmxrzFcTzXZajlQCidX0XNkMJkpqtfbLV54wC6LSSX6Vni25W/G+nAANwLnZ2TmwkfIuWetA7jJxJFA=="], + + "@prisma/engines": ["@prisma/engines@6.19.0", "", { "dependencies": { "@prisma/debug": "6.19.0", "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", "@prisma/fetch-engine": "6.19.0", "@prisma/get-platform": "6.19.0" } }, "sha512-pMRJ+1S6NVdXoB8QJAPIGpKZevFjxhKt0paCkRDTZiczKb7F4yTgRP8M4JdVkpQwmaD4EoJf6qA+p61godDokw=="], + + "@prisma/engines-version": ["@prisma/engines-version@6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", "", {}, "sha512-gV7uOBQfAFlWDvPJdQxMT1aSRur3a0EkU/6cfbAC5isV67tKDWUrPauyaHNpB+wN1ebM4A9jn/f4gH+3iHSYSQ=="], + + "@prisma/fetch-engine": ["@prisma/fetch-engine@6.19.0", "", { "dependencies": { "@prisma/debug": "6.19.0", "@prisma/engines-version": "6.19.0-26.2ba551f319ab1df4bc874a89965d8b3641056773", "@prisma/get-platform": "6.19.0" } }, "sha512-OOx2Lda0DGrZ1rodADT06ZGqHzr7HY7LNMaFE2Vp8dp146uJld58sRuasdX0OiwpHgl8SqDTUKHNUyzEq7pDdQ=="], + + "@prisma/get-platform": ["@prisma/get-platform@6.19.0", "", { "dependencies": { "@prisma/debug": "6.19.0" } }, "sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.52.4", "", { "os": "android", "cpu": "arm" }, "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.52.4", "", { "os": "android", "cpu": "arm64" }, "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w=="], @@ -335,6 +353,8 @@ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + "c12": ["c12@3.1.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^16.6.1", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.4.2", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^1.0.0", "pkg-types": "^2.2.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw=="], + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], @@ -343,6 +363,8 @@ "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="], + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], + "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="], "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="], @@ -355,6 +377,10 @@ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + "cookie": ["cookie@0.6.0", "", {}, "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw=="], "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], @@ -369,12 +395,22 @@ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="], + "deepmerge-ts": ["deepmerge-ts@7.1.5", "", {}, "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw=="], + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "devalue": ["devalue@5.3.2", "", {}, "sha512-UDsjUbpQn9kvm68slnrs+mfxwFkIflOhkanmyabZ8zOYk8SMEIbJ3TK+88g70hSIeytu4y18f0z/hYHMTrXIWw=="], + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], + + "effect": ["effect@3.18.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA=="], + + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + "enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="], "error-stack-parser-es": ["error-stack-parser-es@1.0.5", "", {}, "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA=="], @@ -411,6 +447,8 @@ "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], + "fast-check": ["fast-check@3.23.2", "", { "dependencies": { "pure-rand": "^6.1.0" } }, "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], @@ -435,6 +473,8 @@ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], "glob-to-regexp": ["glob-to-regexp@0.4.1", "", {}, "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw=="], @@ -561,6 +601,10 @@ "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + + "nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="], + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], @@ -579,10 +623,14 @@ "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + "perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="], + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "postcss-load-config": ["postcss-load-config@3.1.4", "", { "dependencies": { "lilconfig": "^2.0.5", "yaml": "^1.10.2" }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" }, "optionalPeers": ["postcss", "ts-node"] }, "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg=="], @@ -599,10 +647,16 @@ "prettier-plugin-svelte": ["prettier-plugin-svelte@3.4.0", "", { "peerDependencies": { "prettier": "^3.0.0", "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" } }, "sha512-pn1ra/0mPObzqoIQn/vUTR3ZZI6UuZ0sHqMK5x2jMLGrs53h0sXhkVuDcrlssHwIMk7FYrMjHBPoUSyyEEDlBQ=="], + "prisma": ["prisma@6.19.0", "", { "dependencies": { "@prisma/config": "6.19.0", "@prisma/engines": "6.19.0" }, "peerDependencies": { "typescript": ">=5.1.0" }, "optionalPeers": ["typescript"], "bin": { "prisma": "build/index.js" } }, "sha512-F3eX7K+tWpkbhl3l4+VkFtrwJlLXbAM+f9jolgoUZbFcm1DgHZ4cq9AgVEgUym2au5Ad/TDLN8lg83D+M10ycw=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="], + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "regexparam": ["regexparam@3.0.0", "", {}, "sha512-RSYAtP31mvYLkAHrOlh25pCNQ5hWnT106VukGaaFfuJrZFkGRX5GhUAdPqpSDXxOhA2c4akmRuplv1mRqnBn6Q=="], @@ -651,6 +705,8 @@ "tar": ["tar@7.5.1", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g=="], + "tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], diff --git a/package.json b/package.json index c62efc9..0fa3bf3 100644 --- a/package.json +++ b/package.json @@ -29,11 +29,15 @@ "lefthook": "^1.13.6", "prettier": "^3.6.2", "prettier-plugin-svelte": "^3.4.0", + "prisma": "^6.19.0", "svelte": "^5.39.5", "svelte-check": "^4.3.2", "tailwindcss": "^4.1.14", "typescript": "^5.9.2", "typescript-eslint": "^8.44.1", "vite": "^7.1.7" + }, + "dependencies": { + "@prisma/client": "^6.19.0" } } diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..0fc93ad --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,21 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client" + output = "../node_modules/.prisma/client" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} + +model BoardState { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + boardData Json +} \ No newline at end of file diff --git a/src/iframe/life-game.html b/src/iframe/life-game.html index c0099b7..2ebdc2e 100644 --- a/src/iframe/life-game.html +++ b/src/iframe/life-game.html @@ -12,8 +12,10 @@

-        + + +
diff --git a/src/iframe/life-game.js b/src/iframe/life-game.js index fa05c5c..333a107 100644 --- a/src/iframe/life-game.js +++ b/src/iframe/life-game.js @@ -31,6 +31,8 @@ const generation = document.getElementById("generation"); //世代を表す文 const randomButton = document.getElementById("randombutton"); const resetButton = document.getElementById("resetbutton"); const sizeChangeButton = document.getElementById("sizeChangeButton"); +const saveButton = document.getElementById("saveButton"); +const loadButton = document.getElementById("loadButton"); //サイズ入力欄 const sizeInput = document.getElementById("sizeInput"); const sizeLabel = document.getElementById("sizeLabel"); @@ -187,3 +189,70 @@ sizeChangeButton.onclick = () => { stop(); updatePatternButtons(); }; + +saveButton.onclick = async () => { + console.log("保存ボタンが押されました"); + try { + // Phase 1 で作ったAPI(/api/board)に、 + // 'POST' メソッドで現在の盤面(board)をJSON形式で送信 + const response = await fetch("/api/board", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(board), // + }); + + if (!response.ok) { + // サーバーがエラーを返した場合 + throw new Error("サーバーとの通信に失敗しました。"); + } + + alert("盤面を保存しました!"); + } catch (err) { + console.error("保存エラー:", err); + alert("保存に失敗しました。"); + } +}; + +/** + * 「DBから読込」ボタンが押された時の処理 + */ +loadButton.onclick = async () => { + console.log("読込ボタンが押されました"); + try { + // Phase 1 で作ったAPI(/api/board)に、 + // 'GET' メソッド(デフォルト)でデータを要求 + const response = await fetch("/api/board"); + + if (!response.ok) { + if (response.status === 404) { + // Phase 1 のAPIで、データが1件もない場合に404を返すようにしたため + alert("保存されているデータがありません。"); + } else { + throw new Error("サーバーとの通信に失敗しました。"); + } + return; // 処理を中断 + } + + // サーバーから返ってきたJSONデータを取得 + const loadedBoard = await response.json(); + + // + // 取得したデータで、現在の盤面(board)を上書きする + // + board = loadedBoard; + + // + // 画面を更新し、ゲームの状態をリセットする + // + renderBoard(); // 新しい盤面を画面に描画 + generationChange(0); // 世代カウントをリセット + stop(); // + + alert("盤面を読み込みました!"); + } catch (err) { + console.error("読込エラー:", err); + alert("読み込みに失敗しました。"); + } +}; diff --git a/src/routes/api/board/+server.ts b/src/routes/api/board/+server.ts new file mode 100644 index 0000000..04b9aea --- /dev/null +++ b/src/routes/api/board/+server.ts @@ -0,0 +1,36 @@ +import { PrismaClient } from "@prisma/client"; +import { json } from "@sveltejs/kit"; + +const prisma = new PrismaClient(); + +// POSTリクエスト(=保存)の処理 +export async function POST({ request }) { + const boardData = await request.json(); // 送られてきた盤面データを取得 + + // データベースに新しいレコードとして保存 + const newState = await prisma.boardState.create({ + data: { + boardData: boardData, // boardData カラムに JSON データを保存 + }, + }); + + return json(newState, { status: 201 }); // 保存成功を通知 +} + +// GETリクエスト(=読み込み)の処理 +export async function GET() { + // データベースから一番「最後」に保存されたデータを1件だけ探す + const latestState = await prisma.boardState.findFirst({ + orderBy: { + createdAt: "desc", // 作成日時(createdAt)の降順(desc)で並び替え + }, + }); + + if (!latestState) { + // もしデータがなければ、エラーではなく「何もない」ことを通知 + return json({ message: "No state found" }, { status: 404 }); + } + + // 見つかった盤面データを返す + return json(latestState.boardData); +} From 48727941fe88456efd9a3b65b56178c981bd4096 Mon Sep 17 00:00:00 2001 From: coelacanth657 <210202793+coelacanth657@users.noreply.github.com> Date: Thu, 6 Nov 2025 22:35:20 +0900 Subject: [PATCH 2/7] =?UTF-8?q?=E7=9B=A4=E9=9D=A2=E3=81=AE=E5=8F=8D?= =?UTF-8?q?=E6=98=A0=E7=A2=BA=E8=AA=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 +- .prettierignore | 3 ++ package.json | 2 +- prisma/schema.prisma | 2 +- src/iframe/life-game.js | 71 ++++++++------------------------- src/routes/+page.svelte | 30 ++++++++++++++ src/routes/api.ts | 49 +++++++++++++++++++++++ src/routes/api/board/+server.ts | 2 +- 8 files changed, 103 insertions(+), 59 deletions(-) create mode 100644 src/routes/api.ts diff --git a/.gitignore b/.gitignore index b2240a9..17d3fb1 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,5 @@ Thumbs.db vite.config.js.timestamp-* vite.config.ts.timestamp-* -/../generated/prisma +# prisma +/generated/ diff --git a/.prettierignore b/.prettierignore index 7d74fe2..c5d6f99 100644 --- a/.prettierignore +++ b/.prettierignore @@ -5,5 +5,8 @@ yarn.lock bun.lock bun.lockb +# generated files +/generated + # Miscellaneous /static/ diff --git a/package.json b/package.json index 0fa3bf3..eedfa6a 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.1", "type": "module", "scripts": { - "dev": "vite dev", + "dev": "bun --env-file=.env vite dev", "build": "vite build", "preview": "vite preview", "prepare": "svelte-kit sync; lefthook install; true", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 0fc93ad..0894d86 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -6,7 +6,7 @@ generator client { provider = "prisma-client" - output = "../node_modules/.prisma/client" + output = "../generated/prisma" } datasource db { diff --git a/src/iframe/life-game.js b/src/iframe/life-game.js index 333a107..0033ed7 100644 --- a/src/iframe/life-game.js +++ b/src/iframe/life-game.js @@ -192,27 +192,7 @@ sizeChangeButton.onclick = () => { saveButton.onclick = async () => { console.log("保存ボタンが押されました"); - try { - // Phase 1 で作ったAPI(/api/board)に、 - // 'POST' メソッドで現在の盤面(board)をJSON形式で送信 - const response = await fetch("/api/board", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(board), // - }); - - if (!response.ok) { - // サーバーがエラーを返した場合 - throw new Error("サーバーとの通信に失敗しました。"); - } - - alert("盤面を保存しました!"); - } catch (err) { - console.error("保存エラー:", err); - alert("保存に失敗しました。"); - } + window.parent.postMessage({ type: "save_board", data: board }, "*"); }; /** @@ -220,39 +200,20 @@ saveButton.onclick = async () => { */ loadButton.onclick = async () => { console.log("読込ボタンが押されました"); - try { - // Phase 1 で作ったAPI(/api/board)に、 - // 'GET' メソッド(デフォルト)でデータを要求 - const response = await fetch("/api/board"); - - if (!response.ok) { - if (response.status === 404) { - // Phase 1 のAPIで、データが1件もない場合に404を返すようにしたため - alert("保存されているデータがありません。"); - } else { - throw new Error("サーバーとの通信に失敗しました。"); - } - return; // 処理を中断 - } - - // サーバーから返ってきたJSONデータを取得 - const loadedBoard = await response.json(); - - // - // 取得したデータで、現在の盤面(board)を上書きする - // - board = loadedBoard; - - // - // 画面を更新し、ゲームの状態をリセットする - // - renderBoard(); // 新しい盤面を画面に描画 - generationChange(0); // 世代カウントをリセット - stop(); // + window.parent.postMessage({ type: "request:load_board" }, "*"); +}; - alert("盤面を読み込みました!"); - } catch (err) { - console.error("読込エラー:", err); - alert("読み込みに失敗しました。"); - } +on.load_board = (loadedBoard) => { + console.log("on.load_board"); + // + // 取得したデータで、現在の盤面(board)を上書きする + // + board = loadedBoard; + + // + // 画面を更新し、ゲームの状態をリセットする + // + renderBoard(); // 新しい盤面を画面に描画 + generationChange(0); // 世代カウントをリセット + stop(); // }; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5bd3234..1ce62a1 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -3,10 +3,12 @@ import lifeGameJS from "@/iframe/life-game.js?raw"; import placetemplate from "@/iframe/place_template.js?raw"; import event from "@/iframe/event.js?raw"; + import * as events from "svelte/events"; import * as icons from "$lib/icons/index.ts"; import patterns from "$lib/board-templates"; import { onMount } from "svelte"; + import { loadBoard, saveBoard } from "./api.ts"; let code = $state(lifeGameJS); @@ -40,6 +42,34 @@ function sendEvent(event: string, message?: unknown) { preview_iframe?.contentWindow?.postMessage({ type: event, data: message }, "*"); } + + onMount(() => { + const handler = async (event: MessageEvent) => { + console.log("handler call"); + const data = event.data as + | { type: "unknown event" } + | { type: "save_board"; data: boolean[][] } + | { type: "request:load_board" }; + if (data.type === "save_board") { + console.log("board saved!"); + await saveBoard(data.data); + return; + } + + if (data.type === "request:load_board") { + console.log("loaded board"); + const board = await loadBoard(); + if (board) { + sendEvent("load_board", board); + alert("盤面を読み込みました!"); + } + return; + } + }; + + window.addEventListener("message", handler); + return () => window.removeEventListener("message", handler); + });