From a406396cbe1cafeb95ea7ca85ffa42a9aecc61c7 Mon Sep 17 00:00:00 2001 From: moha Date: Mon, 6 Apr 2026 02:50:55 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=8F?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=83=AA=E3=83=B3=E3=82=B0=E3=83=BB=E3=83=AD?= =?UTF-8?q?=E3=82=B0=E3=83=BB=E7=92=B0=E5=A2=83=E5=A4=89=E6=95=B0=E3=83=BB?= =?UTF-8?q?DeleteList=E3=81=AE=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - テンプレート描画エラーを適切にハンドリングし、500レスポンスを返すよう修正 - log/slog による構造化ログを導入(fmt.Println からの移行) - 環境変数 PORT によるポート設定を可能にする(デフォルト: 8080) - rand.Read のエラーハンドリングを追加 - Item に UpdatedAt フィールドを追加し、変更時刻を記録 - Store に DeleteList メソッドを追加 - POST /lists/{token}/delete エンドポイントを追加 Closes #5 --- handler.go | 23 +++++++++++++++++++++-- main.go | 21 ++++++++++++++++----- model.go | 11 ++++++----- store.go | 27 ++++++++++++++++++++++----- 4 files changed, 65 insertions(+), 17 deletions(-) diff --git a/handler.go b/handler.go index c771bb9..814ea86 100644 --- a/handler.go +++ b/handler.go @@ -2,6 +2,7 @@ package main import ( "html/template" + "log/slog" "net/http" "strings" ) @@ -30,6 +31,7 @@ func registerRoutes(mux *http.ServeMux, store *Store) { mux.HandleFunc("POST /lists/{token}/items/{id}/toggle-required", handleToggleRequired(store)) mux.HandleFunc("POST /lists/{token}/items/{id}/assignee", handleUpdateAssignee(store)) mux.HandleFunc("POST /lists/{token}/items/{id}/delete", handleDeleteItem(store)) + mux.HandleFunc("POST /lists/{token}/delete", handleDeleteList(store)) } func handleIndex(w http.ResponseWriter, r *http.Request) { @@ -37,7 +39,10 @@ func handleIndex(w http.ResponseWriter, r *http.Request) { http.NotFound(w, r) return } - tmpl.ExecuteTemplate(w, "index.html", nil) + if err := tmpl.ExecuteTemplate(w, "index.html", nil); err != nil { + slog.Error("テンプレート描画エラー", "template", "index.html", "error", err) + http.Error(w, "テンプレートの描画に失敗しました", http.StatusInternalServerError) + } } func handleCreateList(store *Store) http.HandlerFunc { @@ -65,7 +70,10 @@ func handleShowList(store *Store) http.HandlerFunc { "List": l, "ShareURL": "http://" + r.Host + "/lists/" + l.ShareToken, } - tmpl.ExecuteTemplate(w, "list.html", data) + if err := tmpl.ExecuteTemplate(w, "list.html", data); err != nil { + slog.Error("テンプレート描画エラー", "template", "list.html", "error", err) + http.Error(w, "テンプレートの描画に失敗しました", http.StatusInternalServerError) + } } } @@ -120,3 +128,14 @@ func handleDeleteItem(store *Store) http.HandlerFunc { http.Redirect(w, r, "/lists/"+token, http.StatusSeeOther) } } + +func handleDeleteList(store *Store) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + token := r.PathValue("token") + if !store.DeleteList(token) { + http.NotFound(w, r) + return + } + http.Redirect(w, r, "/", http.StatusSeeOther) + } +} diff --git a/main.go b/main.go index 959b4b2..b4f58e8 100644 --- a/main.go +++ b/main.go @@ -1,17 +1,28 @@ package main import ( - "fmt" - "log" + "log/slog" "net/http" + "os" ) +// getEnv は環境変数を取得し、未設定の場合はデフォルト値を返す。 +func getEnv(key, defaultVal string) string { + if v := os.Getenv(key); v != "" { + return v + } + return defaultVal +} + func main() { store := NewStore() mux := http.NewServeMux() registerRoutes(mux, store) - addr := ":8080" - fmt.Println("BringIt server started on http://localhost" + addr) - log.Fatal(http.ListenAndServe(addr, mux)) + addr := ":" + getEnv("PORT", "8080") + slog.Info("BringItサーバーを起動しました", "addr", "http://localhost"+addr) + if err := http.ListenAndServe(addr, mux); err != nil { + slog.Error("サーバーの起動に失敗しました", "error", err) + os.Exit(1) + } } diff --git a/model.go b/model.go index 7734291..aecfdb4 100644 --- a/model.go +++ b/model.go @@ -14,9 +14,10 @@ type List struct { // Item represents a single item in a packing list. type Item struct { - ID string - Name string - Assignee string - Required bool - Prepared bool + ID string + Name string + Assignee string + Required bool + Prepared bool + UpdatedAt time.Time // アイテムの最終更新日時 } diff --git a/store.go b/store.go index 3b603e8..dafffe2 100644 --- a/store.go +++ b/store.go @@ -26,7 +26,9 @@ func (s *Store) genID() string { func generateToken() string { b := make([]byte, 8) - rand.Read(b) + if _, err := rand.Read(b); err != nil { + panic("乱数生成に失敗しました: " + err.Error()) + } return hex.EncodeToString(b) } @@ -61,10 +63,11 @@ func (s *Store) AddItem(token, name, assignee string, required bool) *Item { return nil } item := &Item{ - ID: s.genID(), - Name: name, - Assignee: assignee, - Required: required, + ID: s.genID(), + Name: name, + Assignee: assignee, + Required: required, + UpdatedAt: time.Now(), } l.Items = append(l.Items, item) return item @@ -88,6 +91,7 @@ func (s *Store) TogglePrepared(token, itemID string) { defer s.mu.Unlock() if it := s.findItem(token, itemID); it != nil { it.Prepared = !it.Prepared + it.UpdatedAt = time.Now() } } @@ -96,6 +100,7 @@ func (s *Store) ToggleRequired(token, itemID string) { defer s.mu.Unlock() if it := s.findItem(token, itemID); it != nil { it.Required = !it.Required + it.UpdatedAt = time.Now() } } @@ -104,7 +109,19 @@ func (s *Store) UpdateAssignee(token, itemID, assignee string) { defer s.mu.Unlock() if it := s.findItem(token, itemID); it != nil { it.Assignee = assignee + it.UpdatedAt = time.Now() + } +} + +// DeleteList はトークンに対応するリストを削除する。削除に成功した場合は true を返す。 +func (s *Store) DeleteList(token string) bool { + s.mu.Lock() + defer s.mu.Unlock() + if _, ok := s.lists[token]; !ok { + return false } + delete(s.lists, token) + return true } func (s *Store) DeleteItem(token, itemID string) {