0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

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

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

  • models/todo.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

    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

    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

    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

    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

    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: "タイトル1", IsCompleted: false},
    		{ID: 2, Title: "タイトル2", IsCompleted: true},
    		{ID: 3, Title: "タイトル3", 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

    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

    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

    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

    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

    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

    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

    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})
    }
    
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?