Repository: saitamau-maximum/ctf-todo-app-hands-on-2025
Pull request #22: [Level 4] hinemosu1415
Author: login: hinemosu1415
Created: 2025-04-23T07:04:41Z | Merged: 2025-04-24T05:12:27Z

POSTを実装しました！レビューお願いします。


Diff:
diff --git a/hinemosu1415/client/index.html b/hinemosu1415/client/index.html
index d2cec0c..9fb111b 100644
--- a/hinemosu1415/client/index.html
+++ b/hinemosu1415/client/index.html
@@ -7,6 +7,13 @@
 <body>
   <h1>TODOリスト</h1>
   <ul id="todo-list"></ul>
+
+  <form id="todo-form">
+    <input type="text" id="todo-input" placeholder="新しいTODOを入力">
+    <input type="checkbox" id="todo-comp">
+    <button type="submit">追加</button>
+  </form>
+
   <script src="script.js"></script>
 </body>
 </html>
diff --git a/hinemosu1415/client/script.js b/hinemosu1415/client/script.js
index d570eba..6a68694 100644
--- a/hinemosu1415/client/script.js
+++ b/hinemosu1415/client/script.js
@@ -15,8 +15,10 @@ async function loadTodos() {
     }
 
     const list = document.getElementById('todo-list')
+    list.innerHTML = ''
+
     todos.forEach(todo => {
-      const li = document.createElement('p')
+      const li = document.createElement('li')
 
       const checkbox = document.createElement('input')
       checkbox.type = 'checkbox'
@@ -36,4 +38,44 @@ async function loadTodos() {
   }
 }
 
-window.addEventListener('DOMContentLoaded', loadTodos)
\ No newline at end of file
+  function setupAddButton() {
+    const input = document.getElementById('todo-input')
+    const check = document.getElementById('todo-comp')
+    const form = document.getElementById('todo-form')
+  
+    form.addEventListener('submit', (e) => {
+      e.preventDefault()
+      const title = input.value.trim()
+      const completed = check.checked
+
+      if (!title) return
+  
+      postTodo({ title, completed })
+        .then(() => {
+          input.value = ''
+          check.value = false
+          loadTodos()
+        })
+        .catch(err => {
+          console.error('送信に失敗しました:', err)
+        })
+    })
+  }
+  
+
+async function postTodo(data) {
+  const res = await fetch('http://localhost:8000/todo', {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(data)
+  })
+
+  if (!res.ok) {
+    throw new Error('POST失敗')
+  }
+}
+
+window.addEventListener('DOMContentLoaded', () => {
+  setupAddButton()
+  loadTodos()
+})
\ No newline at end of file
diff --git a/hinemosu1415/package-lock.json b/hinemosu1415/package-lock.json
index b67d073..3f71343 100644
--- a/hinemosu1415/package-lock.json
+++ b/hinemosu1415/package-lock.json
@@ -1,11 +1,13 @@
 {
-    "name": "hinemosu1415",
+    "name": "ctf-todo-app-hands-on-2025",
     "lockfileVersion": 3,
     "requires": true,
     "packages": {
         "": {
+            "name": "ctf-todo-app-hands-on-2025",
             "dependencies": {
                 "@hono/node-server": "^1.14.1",
+                "@hono/valibot-validator": "^0.5.2",
                 "hono": "^4.7.7"
             }
         },
@@ -21,6 +23,16 @@
                 "hono": "^4"
             }
         },
+        "node_modules/@hono/valibot-validator": {
+            "version": "0.5.2",
+            "resolved": "https://registry.npmjs.org/@hono/valibot-validator/-/valibot-validator-0.5.2.tgz",
+            "integrity": "sha512-WbTr8PCFNAME0ale62rmmq+E8bGp5a7VPiVNmNT+Ue12GJE3Ax+CWBGtHhR0TzAEiloPvJND3IbjHIgtNtsqhw==",
+            "license": "MIT",
+            "peerDependencies": {
+                "hono": ">=3.9.0",
+                "valibot": "^1.0.0 || ^1.0.0-beta.4 || ^1.0.0-rc"
+            }
+        },
         "node_modules/hono": {
             "version": "4.7.7",
             "resolved": "https://registry.npmjs.org/hono/-/hono-4.7.7.tgz",
@@ -29,6 +41,21 @@
             "engines": {
                 "node": ">=16.9.0"
             }
+        },
+        "node_modules/valibot": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0.tgz",
+            "integrity": "sha512-1Hc0ihzWxBar6NGeZv7fPLY0QuxFMyxwYR2sF1Blu7Wq7EnremwY2W02tit2ij2VJT8HcSkHAQqmFfl77f73Yw==",
+            "license": "MIT",
+            "peer": true,
+            "peerDependencies": {
+                "typescript": ">=5"
+            },
+            "peerDependenciesMeta": {
+                "typescript": {
+                    "optional": true
+                }
+            }
         }
     }
 }
diff --git a/hinemosu1415/package.json b/hinemosu1415/package.json
index 131fec8..ef204b7 100644
--- a/hinemosu1415/package.json
+++ b/hinemosu1415/package.json
@@ -6,6 +6,7 @@
     },
     "dependencies": {
         "@hono/node-server": "^1.14.1",
+        "@hono/valibot-validator": "^0.5.2",
         "hono": "^4.7.7"
     }
 }
diff --git a/hinemosu1415/src/server.js b/hinemosu1415/src/server.js
index 21cbd16..a2a6b05 100644
--- a/hinemosu1415/src/server.js
+++ b/hinemosu1415/src/server.js
@@ -1,6 +1,8 @@
 import { serve } from '@hono/node-server'
 import { Hono } from 'hono'
 import { cors } from 'hono/cors'
+import { vValidator } from '@hono/valibot-validator'
+import { object, string, number, boolean } from 'valibot'
 
 const app = new Hono()
 
@@ -12,10 +14,31 @@ const todos = [
     { id: 3, title: "学校の課題を提出する", completed: true }
 ];
 
+const TodoInputSchema = object({
+  title: string(),
+  completed: boolean()
+})
+
+app.post('/todo', vValidator('json', TodoInputSchema), async (c) => {
+  const data = c.req.valid('json')
+
+  const maxId = todos.length > 0 ? Math.max(...todos.map(t => t.id)) : 0
+
+  const newTodo = {
+    id: maxId + 1,
+    title: data.title,
+    completed: data.completed
+  }
+
+  todos.push(newTodo)
+
+  return c.json({ success: true, id: newTodo.id })
+})
+
 app.get('/todo', (c) => c.json(todos))
 
 const port = 8000
 serve({
   fetch: app.fetch,
   port
-})
+})
\ No newline at end of file


Reviews:
user: login: yukikamome316
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-23T08:07:16Z
user: login: yukikamome316
state: CHANGES_REQUESTED
author_association: MEMBER
submitted_at: 2025-04-24T04:25:16Z
user: login: yukikamome316
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-24T05:11:20Z

Review comments:
diff_hunk: @@ -12,6 +14,24 @@ const todos = [
     { id: 3, title: "学校の課題を提出する", completed: true }
 ];
 
+const todoInputSchema = object({
path: hinemosu1415/src/server.js
user: login: yukikamome316
body: ```suggestion
const TodoInputSchema = object({
```

Valibot の Schema は慣例的に PascalCase にした方が良さそう
created_at: 2025-04-23T07:05:48Z
author_association: MEMBER
diff_hunk: @@ -12,6 +14,24 @@ const todos = [
     { id: 3, title: "学校の課題を提出する", completed: true }
 ];
 
+const todoInputSchema = object({
+  title: string()
+})
+
+app.post('/todo', vValidator('json', todoInputSchema), async (c) => {
+  const data = c.req.valid('json')
+
+  const newTodo = {
+    id: todos.length + 1,
+    title: data.title,
+    completed: false
+  }
+
+  todos.push(newTodo)
+
+  return c.json(newTodo)
path: hinemosu1415/src/server.js
user: login: yukikamome316
body: レスポンスが問題文の指示と違うな
created_at: 2025-04-23T07:06:31Z
author_association: MEMBER
diff_hunk: @@ -36,4 +38,40 @@ async function loadTodos() {
   }
 }
 
-window.addEventListener('DOMContentLoaded', loadTodos)
\ No newline at end of file
+  function setupAddButton() {
+    const input = document.querySelector('#todo-input')
+    const form = document.querySelector('#todo-form')
path: hinemosu1415/client/script.js
user: login: yukikamome316
body: getElementById を使おう
created_at: 2025-04-23T07:08:08Z
author_association: MEMBER
diff_hunk: @@ -12,10 +14,46 @@ const todos = [
     { id: 3, title: "学校の課題を提出する", completed: true }
 ];
 
+const TodoInputSchema = object({
+  title: string(),
+  completed: boolean()
+})
+
+app.post('/todo', vValidator('json', TodoInputSchema), async (c) => {
+  const data = c.req.valid('json')
+
+  const newTodo = {
+    id: todos.length + 1,
+    title: data.title,
+    completed: data.completed
+  }
+
+  todos.push(newTodo)
+
+  return c.json({ success: true, id: newTodo.id })
+})
+
 app.get('/todo', (c) => c.json(todos))
 
 const port = 8000
 serve({
   fetch: app.fetch,
   port
 })
+
+
+const initialTodos = [
+  { title: "CTFのWeb問題に慣れる", completed: true }
+]
+
+initialTodos.forEach(todo => {
+  fetch(`http://localhost:8000/todo`, {
+    method: 'POST',
+    headers: { 'Content-Type': 'application/json' },
+    body: JSON.stringify(todo)
+  })
+    .then(res => {
+      if (!res.ok) throw new Error('初期TODOの送信に失敗しました')
+      return res.json()
+    })
+})
path: hinemosu1415/src/server.js
user: login: yukikamome316
body: エンドポイントを用意するだけでよくて、わざわざここで POST させる必要は無い
created_at: 2025-04-24T04:24:09Z
author_association: MEMBER
diff_hunk: @@ -12,10 +14,46 @@ const todos = [
     { id: 3, title: "学校の課題を提出する", completed: true }
 ];
 
+const TodoInputSchema = object({
+  title: string(),
+  completed: boolean()
+})
+
+app.post('/todo', vValidator('json', TodoInputSchema), async (c) => {
+  const data = c.req.valid('json')
+
+  const newTodo = {
+    id: todos.length + 1,
path: hinemosu1415/src/server.js
user: login: yukikamome316
body: 後で削除する機能を追加した時に、重複を起こす可能性があるね
id == 2 を消した後に POST したら id == 3 のアイテムが 3 つ存在することになる
created_at: 2025-04-24T04:25:05Z
author_association: MEMBER

PR comments:
user: login: hinemosu1415
created_at: 2025-04-24T03:47:53Z
body: 以下の部分を修正しました。
・PascalCase, getElemetbyIdへの変更
・レスポンスの修正
・最初に"CTFのWeb問題に慣れる"をPOST
author_association: CONTRIBUTOR
user: login: hinemosu1415
created_at: 2025-04-24T05:08:34Z
body: idをmax+1にserverの自動POSTを削除しました
author_association: CONTRIBUTOR