Go(JWT認証)✖️RiverpodでTodoアプリを作ってみたPart2（Goロジック実装 編）

完全自分用の学習備忘録。
詳細な解説等はコード内でコメントアウトしております。

### ToDoモデルとUserモデルを作成

- models/todo.go
    
    ```go
    package models
    
    import (
    	"time"
    
    	"gorm.io/gorm"
    )
    
    type BasicModel struct {
    	ID        uint      `gorm:"primarykey" json:"id"`
    	CreatedAt time.Time `json:"created_at"`
    	UpdatedAt time.Time `json:"updated_at"`
    	DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
    }
    
    type Todo struct {
        BasicModel
        Title       string `gorm:"not null" json:"title"`
        IsCompleted bool   `gorm:"not null;default:false" json:"is_completed"` // 複数の制約はセミコロンを使用
        UserID      uint   `gorm:"not null" json:"user_id"` // リレーション設定 外部キーとして設定 （1対多）
    }
    ```
    
- models/user.go
    
    ```go
    package models
    
    import "gorm.io/gorm"
    
    type User struct {
        gorm.Model
        Username string `gorm:"not null" json:"username"`
        Email    string `gorm:"unique;not null" json:"email"`
        Password string `gorm:"not null" json:"password"`
        Todos    []Todo `gorm:"constraint:OnDelete:CASCADE" json:"todos"`  // リレーション設定 Userは複数のTodoを持つ
    }
    
    ```
    

### マイグレーション実行ファイル

モデルの作成、修正後、以下のコマンドを実行してマイグレーションを実行
 `go run migrations/migration.go`

- migrations/migration.go
    
    ```go
    package main
    
    import (
    	"go-gin-gorm-riverpod-todo-app/infra"
    	"go-gin-gorm-riverpod-todo-app/models"
    )
    
    // モデルの作成、修正後、以下のコマンドを実行してマイグレーションを実行
    // go run migrations/migration.go
    func main() {
    	infra.Initialize()
    	db := infra.SetupDB()
    
    	if error := db.AutoMigrate(&models.Todo{}, &models.User{}); error != nil {
    		panic("Failed to migrate database")
    	}
    }
    ```
    

### リクエストデータ受ける入れ物（DTO）を作成

flutterアプリからのリクエストデータを受け入れる入れ物（構造体）を用意

それぞれのフィールドにバリデーションを定義

- dto/auth_dto.go
    
    ```go
    package dto
    
    type SignUpInput struct {
    	Usermame string `json:"username" binding:"required"`
    	Email    string `json:"email" binding:"required,email"`
    	Password string `json:"password" binding:"required,min=8"`
    }
    
    type LoginInput struct {
    	Email    string `json:"email" binding:"required,email"`
    	Password string `json:"password" binding:"required,min=8"`
    } 
    ```
    
- dto/auth_todo.go
    
    ```go
    package dto
    
    type CreateToDoInput struct {
    	Title string `json:"title" binding:"required"`
    }
    
    // ポインタ型（*）でnullを許容
    // omitnilでnilの場合はそのフィールドのバリデーションはしない
    type UpdateTodoInput struct {
    	Title *string `json:"title" binding:"omitnil"`
    	IsCompleted *bool `json:"is_completed" binding:"omitnil"`
    }
    ```
    

### エントリーポイント（main関数）

以下の処理を実装する。

- .envファイルの読み込み処理
- DBの接続処理
- Repository → Service → Controller の順に依存性を注入
- APIエンドポイントを定義

3層アーキテクチャ構成
① Controller → リクエストデータのハンドリングやレスポンスの設定
② Service → 実現したい機能の実装（ビジネスロジック）
③ Repository → データの永続化やデータソースとのやりとり（メモリ上 or DB）

依存性の流れ
Controller → Service → Repository→ Database

Controller → Service
コントローラはリクエストを処理するが、ビジネスロジックはサービスに委ねる。依存性を注入することで、コントローラはサービスの具体的な実装に依存しない。

Service → Repository
サービスはビジネスロジックを管理するが、（メモリ or DB上の）データ操作はリポジトリに委ねる。依存性を注入することで、サービスはリポジトリの具体的な実装に依存しない。

Repository → Database
リポジトリはデータベース操作を管理する。外部（main関数）で生成したDBのインスタンスを依存性として注入する。

- main.go
    
    ```go
    package main
    
    import (
    	"go-gin-gorm-riverpod-todo-app/controllers"
    	"go-gin-gorm-riverpod-todo-app/infra"
    	"go-gin-gorm-riverpod-todo-app/middlwares"
    
    	// "go-gin-gorm-riverpod-todo-app/models"
    	"go-gin-gorm-riverpod-todo-app/repositories"
    	"go-gin-gorm-riverpod-todo-app/services"
    
    	"github.com/gin-gonic/gin"
    	"gorm.io/gorm"
    )
    
    func setupRouter(db *gorm.DB) *gin.Engine {
    	// メモリ上(NewTodoMemoryRepository)でデータ操作を行う場合、以下のサンプルデータを使用する
    	/*
    	// サンプルデータを作成
    	todos := []models.Todo{
    		{ID: 1, Title: "タイトル１", IsCompleted: false},
    		{ID: 2, Title: "タイトル２", IsCompleted: true},
    		{ID: 3, Title: "タイトル３", IsCompleted: false},
    	}
    	*/
    
    	// ファクトリ関数を用いてそれぞれ依存性を注入していく
    	
    	// 以下todoRespositoryを片方に切り替えることでControllerとServiceを修正することなく、容易にデータソースのやり取り先を変更できる
    	// todoRepository := repositories.NewTodoMemoryRepository(todos) // →　メモリ上でデータを操作
    	todoRepository := repositories.NewTodoRepository(db) //　→ DB上でデータを操作
    	todoService := services.NewTodoService(todoRepository)
    	todoController := controllers.NewTodoController(todoService)
    
    	authRepository := repositories.NewAuthRepository(db)
    	authService := services.NewAuthService(authRepository)
    	authController := controllers.NewAuthController(authService)
    
    	r := gin.Default()
    
    	// 共通のルートをグループ化
    	authRouter := r.Group("/auth")
    	//　AuthControllerでリクエストデータをハンドリングする前に、認証ミドルウェアを挟み、JWTToken認証を実施
    	todoRouterWithAuth := r.Group("/todos", middlwares.AuthMiddlware(authService))
    
    	authRouter.POST("/sign_up", authController.SignUp) // サインアップ
    	authRouter.POST("/login", authController.Login) // ログイン
    
    	todoRouterWithAuth.GET("", todoController.FindAll) // ログインしたユーザーに紐ずくtodoを全件取得
    	todoRouterWithAuth.POST("", todoController.Create) // todo新規作成
    	todoRouterWithAuth.PUT("/:id", todoController.Update) // todoの更新
    	todoRouterWithAuth.DELETE("/:id", todoController.Delete) // todoの削除
    
    	return r
    }
    
    // エントリーポイント
    func main() {
    	// envファイルを読み込む
    	infra.Initialize()
    
    	// DBへの接続
    	db := infra.SetupDB()
    
    	// APIルートを定義
    	r := setupRouter(db)
    	
    	r.Run("localhost:8080")
    }
    
    ```
    

### サインアップ、ログイン処理（JWT認証）

- repositories/auth_repository.go
    
    ```go
    package repositories
    
    import (
    	"errors"
    	"go-gin-gorm-riverpod-todo-app/models"
    
    	"gorm.io/gorm"
    )
    
    type IAuthRepository interface {
    	CreateUser(user models.User) error
    	FindUser(email string) (*models.User, error) 
    }
    
    type AuthRepository struct {
    	db *gorm.DB
    }
    
    func NewAuthRepository(db *gorm.DB) IAuthRepository {
    	return &AuthRepository{db: db}
    }
    
    func (r *AuthRepository) CreateUser(user models.User) error {
    	result := r.db.Create(&user)
    	if result.Error != nil {
    		return result.Error
    	}
    	return nil
    }
    
    func (r *AuthRepository) FindUser(email string) (*models.User, error) {
    	var user models.User
    	result := r.db.First(&user, "email = ?", email)
    	if result.Error != nil {
    		if result.Error.Error() == "record not found" {
    			return nil, errors.New("User not found")
    		}
    		return nil, result.Error
    	}
    	return &user, nil
    }
    
    ```
    
- services/auth_service.go
    
    ```go
    package services
    
    import (
    	"fmt"
    	"go-gin-gorm-riverpod-todo-app/models"
    	"go-gin-gorm-riverpod-todo-app/repositories"
    	"os"
    	"time"
    
    	"github.com/golang-jwt/jwt/v5"
    	"golang.org/x/crypto/bcrypt"
    )
    
    type IAuthService interface {
    	SignUp(username string, email string, password string) (*string, error)
    	Login(email string, password string) (*string, error)
    	GetUserFromToken(tokenString string) (*models.User, error)
    }
    
    type AuthService struct {
    	repository repositories.IAuthRepository
    }
    
    func NewAuthService(respository repositories.IAuthRepository) IAuthService {
    	return &AuthService{repository: respository}
    }
    
    func (s *AuthService) SignUp(username string, email string, password string) (*string, error) {
    	// パスワードのハッシュ化
    	hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    	if err != nil {
    		return nil, err
    	}
    
    	user := models.User{
    		Username: username,
    		Email: email,
    		Password: string(hashedPassword),
    	}
    	createUserErr := s.repository.CreateUser(user)
    	if createUserErr != nil {
    		return nil, createUserErr
    	}
    
    	foundUser, err := s.repository.FindUser(email)
    	if err != nil {
    		return nil,  err
    	}
    
    	// JWTTokenを生成
    	token, err := CreateToken(foundUser.ID, foundUser.Email)
    	if err != nil {
    		return nil, err
    	}
    
    	return token,  nil
    }
    
    func (s *AuthService) Login(email string, password string) (*string, error) {
    	foundUser, err := s.repository.FindUser(email)
    	if err != nil {
    		return nil,  err
    	}
    
    	err = bcrypt.CompareHashAndPassword([]byte(foundUser.Password), []byte(password))
    	// パスワードが一致しない場合
    	if  err != nil {
    		return nil,  err
    	}
    
    	token, err := CreateToken(foundUser.ID, foundUser.Email)
    	if err != nil {
    		return nil, err
    	}
    
    	return token, nil
    }
    
    func CreateToken(userId uint, email string) (*string, error) {
    	// tokenの生成
    	// Claimsはtokenに含める様々な情報を指す
    	// sub（subject）→ ユーザー識別子
    	// exp → tokenの有効期限
    	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
    		"sub": userId,
    		"email": email,
    		"exp": time.Now().Add(time.Hour).Unix(), // 生成から1時間後に期限を設定
    	})
    
    	// .envファイルに定義した秘密鍵を使用して著名を行う
    	tokenString, error := token.SignedString([]byte(os.Getenv("SECRET_KEY")))
    	if error != nil {
    		return nil, error
    	}
    	return &tokenString, nil
    }
    
    // トークンに含まれる情報を基にユーザー情報を取得
    func (s *AuthService) GetUserFromToken(tokenString string) (*models.User, error) {
    	token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    		// トークンの署名を検証するコールバック関数内で、トークンの解析を行う
    
    		// 署名がHMACか否か確認
    		if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
    			return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
    		}
    		return []byte(os.Getenv("SECRET_KEY")), nil
    	})
    	if err != nil {
    		return nil, err
    	}
    
    	var user *models.User
    	// token.Claims: トークン内に含まれるデータ
    	// クレームが正しい形式（MapClaims）であるか確認
    	if claims, ok := token.Claims.(jwt.MapClaims); ok {
    		if float64(time.Now().Unix()) > claims["exp"].(float64) {
    			return nil, jwt.ErrTokenExpired
    		}
    		// メールアドレスを基にデータベースからユーザー情報を取得
    		user, err = s.repository.FindUser(claims["email"].(string))
    		if err != nil {
    			return nil, err
    		}
    	}
    	return user, nil
    }
    ```
    
- controllers/auth_controller.go
    
    ```go
    package controllers
    
    import (
    	"go-gin-gorm-riverpod-todo-app/dto"
    	"go-gin-gorm-riverpod-todo-app/services"
    	"net/http"
    
    	"github.com/gin-gonic/gin"
    )
    
    type IAuthController interface{
    	SignUp(ctx *gin.Context)
    	Login(ctx *gin.Context)
    }
    
    type AuthController struct {
    	service services.IAuthService
    }
    
    func NewAuthController(service services.IAuthService) IAuthController {
    	return &AuthController{service: service}
    }
    
    // サインアップ
    func (c *AuthController) SignUp(ctx *gin.Context) {
    	// リクエストデータを格納する変数を用意
    	var input dto.SignUpInput
    
    	 // リクエストデータをinput変数にバインド ここでリクエストデータのバリデーションが行わる
    	if err := ctx.ShouldBindJSON(&input); err != nil {
    		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    		return
    	}
    
    	token, err := c.service.SignUp(input.Usermame, input.Email, input.Password)
    	if err != nil {
    		ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user"})
    		return
    	}
    
    	// ネスト化したjsonレスポンスデータを返す
    	ctx.JSON(http.StatusOK, gin.H{
    		"data": gin.H{
    			"username": input.Usermame,
    			"email":    input.Email,
    			"password": input.Password,
    			"jwt_token": token, // 生成したJWTToken
    		},
    	})
    }
    
    // ログイン
    func (c *AuthController) Login(ctx *gin.Context) {
    	// リクエストデータを格納する変数を用意
    	var input dto.LoginInput
    
    	// リクエストデータをinput変数にバインド ここでリクエストデータのバリデーションが行わる
    	if err := ctx.ShouldBindJSON(&input); err != nil {
    		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    		return
    	}
    
    	token, err := c.service.Login(input.Email, input.Password)
    	if err != nil {
    		if err.Error() == "User not found" {
    			ctx.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
    			return
    		}
    		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
    		return
    	}
    
    	// ネスト化したjsonレスポンスデータを返す
    	ctx.JSON(http.StatusOK, gin.H{
    		"data": gin.H{
    			"username": "",
    			"email":    input.Email,
    			"password": input.Password,
    			"jwt_token": token, // 生成したJWTToken
    		},
    	})
    }
    ```
    

### 認証ミドルウェア

ミドルウェアでJWT認証を行い、リクエストコンテキストにユーザー情報をセットする

- middlwares/auth_middlware
    
    ```go
    package middlwares
    
    import (
    	"go-gin-gorm-riverpod-todo-app/services"
    	"net/http"
    	"strings"
    
    	"github.com/gin-gonic/gin"
    )
    
    // gin.HandlerFunc型を返す→ type HandlerFunc func(*Context)
    func AuthMiddlware(authService services.IAuthService) gin.HandlerFunc {
    	return func(ctx *gin.Context) {
    		header := ctx.GetHeader("Authorization") // Authorizationヘッダー取得
    		if header == "" {
    			ctx.AbortWithStatus(http.StatusUnauthorized)
    			return
    		}
    
    		// Authorizationヘッダーが、「"Bearer "」で始まることを確認
    		if !strings.HasPrefix(header, "Bearer ") {
    			ctx.AbortWithStatus(http.StatusUnauthorized)
    			return
    		}
    
    		// 文字列 header から先頭にある "Bearer " を取り除いて、トークン部分だけを取得
    		tokenString := strings.TrimPrefix(header, "Bearer ")
    
    		// トークンに含まれる情報を基にユーザー情報を取得
    		user, error := authService.GetUserFromToken(tokenString)
    		if error != nil {
    			ctx.AbortWithStatus(http.StatusUnauthorized)
    			return 
    		}
    
    		// ユーザー情報をリクエストコンテキストにセット（"user"キーで値を取り出す）
    		ctx.Set("user", user)
    
    		// 処理フローを次のmiddlwareまたは、目的の処理に移す
    		ctx.Next()
    	}
    }
    ```
    

### TodoのCRUD処理

以下の機能を実装する。

- 新規作成
- 更新
- 全件取得
- 削除
- repositories/to_do_repository.go
    
    ```go
    package repositories
    
    import (
    	"errors"
    	"go-gin-gorm-riverpod-todo-app/models"
    
    	"gorm.io/gorm"
    )
    
    type ITodoRepository interface {
    	FindAll(userId uint) (*[]models.Todo, error)
    	FindById(todoId uint, userId uint) (*models.Todo, error)
    	Create(newTodo models.Todo) (*models.Todo, error)
    	Update(updateTodo models.Todo) (*models.Todo, error)
    	Delete(todoId uint, userId uint) error
    }
    
    type TodoMemoryRepository struct {
    	todos []models.Todo
    }
    
    func NewTodoMemoryRepository(todos []models.Todo) ITodoRepository {
    	return &TodoMemoryRepository{todos: todos}
    }
    
    func (r *TodoMemoryRepository) FindAll(userId uint) (*[]models.Todo, error) {
    	return &r.todos, nil
    }
    
    func (r *TodoMemoryRepository) FindById(todoId uint, userId uint) (*models.Todo, error) {
    	for _, v := range r.todos {
    		if v.ID == todoId {
    			return &v, nil
    		}
    	}
    	return nil, errors.New("Todo not found")
    }
    
    func (r *TodoMemoryRepository) Create(newItem models.Todo) (*models.Todo, error) {
    	newItem.ID = uint(len(r.todos) + 1)
    	r.todos = append(r.todos, newItem)
    	return &newItem, nil
    }
    
    func (r *TodoMemoryRepository) Update(updateTodo models.Todo) (*models.Todo, error) {
    	for i, v := range r.todos {
    		if v.ID == updateTodo.ID {
    			r.todos[i] = updateTodo
    			return &r.todos[i], nil
    		}
    	}
    	return nil, errors.New("Unexpected error")
    }
    
    func (r *TodoMemoryRepository) Delete(todoId uint, userId uint) error {
    	for i, v := range r.todos {
    		if v.ID == todoId {
    			r.todos = append(r.todos[:i], r.todos[i+1:]...)
    			return nil
    		}
    	}
    	return errors.New("Todo not found")
    }
    
    // ↑ メモリ上でデータを操作
    // =================================================================================================================================================================================
    // ↓ DBを用いてデータ操作
    
    type TodoRepository struct {
    	db *gorm.DB
    }
    
    // ファクトリ関数
    func NewTodoRepository(db *gorm.DB) ITodoRepository {
    	// ToDoRepository構造体がITodoRepositoryインターフェースを満たしており、ITodoRepositoryは具体的な値の型情報とポインタ情報を持つ
    	return &TodoRepository{db: db}
    }
    
    // ===== ToDoRepository構造体がITodoRepositoryインターフェースを満たすようにインターフェースに定義されたメソッドを実装する =====
    
    // 新規作成
    func (r *TodoRepository) Create(newTodo models.Todo) (*models.Todo, error) {
    	result := r.db.Create(&newTodo) // 参照を渡す
    	if result.Error != nil {
    		return nil, result.Error
    	}
    	return &newTodo, nil
    }
    
    // 削除
    func (r *TodoRepository) Delete(todoId uint, userId uint) error {
    	deleteTodo, error := r.FindById(todoId, userId)
    	if error != nil {
    		return error
    	}
    
    	result := r.db.Delete(&deleteTodo)
    	if result.Error != nil {
    		return result.Error
    	}
    	return nil // 削除成功でnilを返す
    }
    
    // 全件取得
    func (r *TodoRepository) FindAll(userId uint) (*[]models.Todo, error) {
    	var todos []models.Todo
    	result := r.db.Find(&todos, "user_id = ?", userId)
    	if result.Error != nil {
    		return nil, result.Error
    	}
    	return &todos, nil
    }
    
    // idに一致するデータを取得
    func (r *TodoRepository) FindById(todoId uint, userId uint) (*models.Todo, error) {
    	// models.Todo型の値を格納する変数を定義
    	var todo models.Todo
    
    	// Fisrt()で最初にヒットした一件のみ取得
    	// 第一引数には検索結果を格納するモデルの参照、第二引数には検索条件を渡す
    	result := r.db.First(&todo, "id = ? AND user_id = ?", todoId, userId)
    	if result.Error != nil {
    		if result.Error.Error() == "record not found" {
    			return nil, errors.New("Todo not found")
    		}
    		return nil, result.Error
    	}
    	return &todo, nil
    }
    
    // 更新
    func (r *TodoRepository) Update(updateTodo models.Todo) (*models.Todo, error) {
    	result := r.db.Save(&updateTodo)
    	if result.Error != nil {
    		return nil, result.Error
    	}
    	return &updateTodo, nil
    }
    ```
    
- services/to_do_service.go
    
    ```go
    package services
    
    import (
    	"go-gin-gorm-riverpod-todo-app/dto"
    	"go-gin-gorm-riverpod-todo-app/models"
    	"go-gin-gorm-riverpod-todo-app/repositories"
    )
    
    type ITodoService interface {
    	FindAll(userId uint) (*[]models.Todo, error)
    	FindById(todoId uint, userId uint) (*models.Todo, error)
    	Create(createTodoInput dto.CreateToDoInput, userId uint) (*models.Todo, error)
    	Update(todoId uint, userId uint, updateItemInput dto.UpdateTodoInput) (*models.Todo, error)
    	Delete(todoId uint, userId uint) error
    }
    
    type TodoService struct {
    	repository repositories.ITodoRepository
    }
    
    // ファクトリ関数
    func NewTodoService(repository repositories.ITodoRepository) ITodoService {
    	// ITodoRepositoryは代入される具体的な値の型情報とポインタ情報を持つ
    
    	// TodoService構造体はITodoServiceインターフェースを満たしており、ITodoServiceは具体的な値の型情報とポインタ情報を持つ
    	return &TodoService{repository: repository}
    }
    
    // ===== TodoService構造体がITodoServiceインターフェースを満たすようにインターフェースに定義されたメソッドを実装する =====
    
    // 全件取得
    func (s *TodoService) FindAll(userId uint) (*[]models.Todo, error) {
    	return s.repository.FindAll(userId)
    }
    
    // idに一致するデータを取得
    func (s *TodoService) FindById(todoId uint, userId uint) (*models.Todo, error) {
    	return s.repository.FindById(todoId, userId)
    }
    
    // 新規作成
    func (s *TodoService) Create(createTodoInput dto.CreateToDoInput, userId uint) (*models.Todo, error) {
    	newTodo := models.Todo{
    		Title: createTodoInput.Title,
    		IsCompleted: false,
    		UserID: userId,
    	}
    	return s.repository.Create(newTodo)
    }
    
    // 更新
    func (s *TodoService) Update(todoId uint, userId uint, updateTodoInput dto.UpdateTodoInput) (*models.Todo, error) {
    	targetItem, error := s.FindById(todoId, userId)
    	if error != nil {
    		return nil, error
    	}
    
    	// 対象のtodoのタイトルを更新
    	if updateTodoInput.Title != nil {
    		targetItem.Title = *updateTodoInput.Title
    	}
    	// 対象のtodoの完了フラグを更新
    	if updateTodoInput.IsCompleted != nil {
    		targetItem.IsCompleted = *updateTodoInput.IsCompleted
    	}
    	return s.repository.Update(*targetItem)
    }
    
    // 削除
    func (s *TodoService) Delete(todoId uint, userId uint) error {
    	return s.repository.Delete(todoId, userId)
    }
    ```
    
- controllers/to_do_controller.go
    
    ```go
    package controllers
    
    import (
    	"go-gin-gorm-riverpod-todo-app/dto"
    	"go-gin-gorm-riverpod-todo-app/models"
    	"go-gin-gorm-riverpod-todo-app/services"
    	"net/http"
    	"strconv"
    
    	"github.com/gin-gonic/gin"
    )
    
    type ITodoController interface {
    	FindAll(ctx *gin.Context)
    	Create(ctx *gin.Context)
    	Update(ctx *gin.Context)
    	Delete(ctx *gin.Context)
    }
    
    type TodoController struct {
    	service services.ITodoService
    }
    
    func NewTodoController(service services.ITodoService) ITodoController {
    	// ITodoServiceは代入される具体的な値の型情報とポインタ情報を持つ
    
    	// TodoController構造体はITodoControllerインターフェースを満たしており、ITodoServiceは具体的な値の型情報とポインタ情報を持つ
    	return &TodoController{service: service}
    }
    
    // ===== TodoController構造体がITodoControllerインターフェースを満たすようにインターフェースに定義されたメソッドを実装する =====
    
    // 全件取得
    func (c *TodoController) FindAll(ctx *gin.Context) {
    	user, exists := ctx.Get("user") // AuthMiddlewareにてSet()で設定した"user"キーで値を取り出す
    	if !exists {
    		ctx.AbortWithStatus(http.StatusUnauthorized)
    		return
    	}
    
    	// userはany型のため、型アサーションを行う
    	userId := user.(*models.User).ID
    
    	todos, err := c.service.FindAll(userId)
    	if err != nil {
    		ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Unexpected error"})
    		return
    	}
    
    	// ネスト化したjsonレスポンスデータを送る
    	ctx.JSON(http.StatusOK, gin.H{
    		"data": gin.H{
    			"todos": todos,
    		},
    	})
    }
    
    // 新規作成
    func (c *TodoController) Create(ctx *gin.Context) {
    	user, exists := ctx.Get("user") // AuthMiddlewareにてSet()で設定した"user"キーで値を取り出す
    	if !exists {
    		ctx.AbortWithStatus(http.StatusUnauthorized)
    		return
    	}
    
    	// userはany型のため、型アサーションを行う
    	userId := user.(*models.User).ID
    
    	// リクエストデータを受け取る変数を用意
    	var input dto.CreateToDoInput
    	// リクエストデータをinput変数にバインド ここでリクエストデータのバリデーションが行われる
    	if err := ctx.ShouldBindJSON(&input); err != nil {
    		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    		return
    	}
    
    	newTodo, err := c.service.Create(input, userId)
    	if err != nil {
    		ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
    		return
    	}
    
    	ctx.JSON(http.StatusCreated, gin.H{"data": newTodo})
    }
    
    func (c *TodoController) Update(ctx *gin.Context) {
    	user, exists := ctx.Get("user") // AuthMiddlewareにてSet()で設定した"user"キーで値を取り出す
    	if !exists {
    		ctx.AbortWithStatus(http.StatusUnauthorized)
    		return
    	}
    
    	// userはany型のため、型アサーションを行う
    	userId := user.(*models.User).ID
    
    	todoId, err := strconv.ParseInt(ctx.Param("id"), 10, 64)
    	if err != nil {
    		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid id"})
    		return
    	}
    
    	// リクエストデータを受け取る変数を用意
    	var input dto.UpdateTodoInput
    	// リクエストデータをinput変数にバインド ここでリクエストデータのバリデーションが行われる
    	if err := ctx.ShouldBindJSON(&input); err != nil {
    		ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    		return
    	}
    
    	updatedTodo, err := c.service.Update(uint(todoId), userId, input)
    	if err != nil {
    		if err.Error() == "Todo not found" {
    			ctx.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
    			return
    		}
    		ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Unexpected error"})
    		return
    	}
    
    	ctx.JSON(http.StatusOK, gin.H{"data": updatedTodo})
    }
    
    func (c *TodoController) Delete(ctx *gin.Context) {
    	user, exists := ctx.Get("user") // AuthMiddlewareにてSet()で設定した"user"キーで値を取り出す
    	if !exists {
    		ctx.AbortWithStatus(http.StatusUnauthorized)
    		return
    	}
    
    	// userはany型のため、型アサーションを行う
    	userId := user.(*models.User).ID
    
    	todoId, error := strconv.ParseInt(ctx.Param("id"), 10, 64)
    	if error != nil {
    		ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid id"})
    		return
    	}
    
    	error = c.service.Delete(uint(todoId), userId)
    	if error != nil {
    		if error.Error() == "Item not found" {
    			ctx.JSON(http.StatusNotFound, gin.H{"error": error.Error()})
    			return
    		}
    		ctx.JSON(http.StatusInternalServerError, gin.H{"error": "Unexpected error"})
    		return
    	}
    
    	ctx.JSON(http.StatusOK, gin.H{"data": nil})
    }
    ```