ToDoアプリの作成（User編）

<h2>はじめに</h2>
今回は，前回に続きユーザーの新規作成、ログイン、ログアウト機能を実装していきます。<br>

https://qiita.com/sawadashinji0526/items/06ce740776bfc919df8b

※動作確認にPgAdminとPostmanを使います。
<h2>リポジトリの実装</h2>
User関係のリポジトリインターフェースを実装していきます。<br>
このコードは、ユーザー情報を扱うためのリポジトリインターフェース IUserRepository と、そのインターフェースを実装する具体的なリポジトリ構造体 userRepository を定義しています。<br>
NewUserRepository 関数は、userRepository 構造体のインスタンスを作成するために使用されます。

```repository/user_repository.go
package repository

import (
	"go-rest-api/model"

	"gorm.io/gorm"
)

type IUserRepository interface {
    //このメソッドは、emailアドレスで指定されたユーザーの情報を取得します。
    //ユーザー情報をmodel.Userのポインタに格納します。
	GetUserByEmail(user *model.User, email string) error
    //このメソッドは、新しいユーザーを作成します。
    //user *model.Userは新規に作成するユーザーの情報を格納するmodel.User構造体のポインタです。
	CreateUser(user *model.User) error
}
type userRepository struct {
    //このフィールドは、データベース接続 (`gorm.DB` オブジェクト) を保持します。
	db *gorm.DB
}

func NewUserRepository(db *gorm.DB) IUserRepository {
    //新しい `userRepository` インスタンスを作成し、データベース接続を `db` フィールドに設定して返します。
	return &userRepository{db}
}

func (ur *userRepository) GetUserByEmail(user *model.User, email string) error {
    //gorm.DB の Where、First、Error メソッドを使って、メールアドレスでユーザーを取得します。
	if err := ur.db.Where("email=?", email).First(user).Error; err != nil {
		return err
	}
	return nil
}


func (ur *userRepository) CreateUser(user *model.User) error {
    //gorm.DB の Create メソッドを使って、新しいユーザーを作成します。
	if err := ur.db.Create(user).Error; err != nil {
		return err
	}
	return nil
}
```
<h2>ユースケースの実装</h2>
次はユースケースを作成していきます。<br>
下記のコードはユーザー登録（サインアップ）とログイン処理を行うユースケースを実装したものです。


```usecase/user_usecase.go
package usecase

import (
	"go-rest-api/model"
	"go-rest-api/repository"
	"os"
	"time"
    //go mod tidyコマンドを実行する
	"github.com/golang-jwt/jwt/v4"
	"golang.org/x/crypto/bcrypt"
)

//IUserUsecase: ユーザー関連のユースケースが実装すべきメソッドを定義しています。
type IUserUsecase interface {
    //ユーザー登録処理を実行するメソッドです。
	SignUp(user model.User) (model.UserResponse, error)
    //ユーザーログイン処理を実行するメソッドです。
	Login(user model.User) (string, error)
}

//ユーザーリポジトリへの依存関係を保持するフィールドです。
type userUsecase struct {
	ur repository.IUserRepository
}

//userUsecase 構造体のインスタンスを生成する関数です。
func NewUserUsecase(ur repository.IUserRepository) IUserUsecase {
	return &userUsecase{ur}
}

//ユーザー登録処理を実行するメソッドです。
func (uu *userUsecase) SignUp(user model.User) (model.UserResponse, error) {
    //パスワードをハッシュ化して、データベースに保存します。
	hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 10)
    //ハッシュ化処理中にエラーが発生した場合、エラーを返します。
	if err != nil {
		return model.UserResponse{}, err
	}
    //ハッシュ化されたパスワードとメールアドレスを使って、新しいユーザーの情報を格納する model.User 構造体を作成します。
	newUser := model.User{Email: user.Email, Password: string(hash)}
    //ユーザーリポジトリの CreateUser メソッドを呼び出して、新しいユーザーを作成します。
	if err := uu.ur.CreateUser(&newUser); err != nil {
		return model.UserResponse{}, err
	}
    //登録されたユーザーのIDとメールアドレスを使って、レスポンス構造体 model.UserResponse を作成します。
	resUser := model.UserResponse{
		ID:    newUser.ID,
		Email: newUser.Email,
	}
    //登録されたユーザーの情報を含む model.UserResponse とエラーオブジェクト nil を返します。
	return resUser, nil
}

//ログイン処理を実行するメソッドです。
func (uu *userUsecase) Login(user model.User) (string, error) {
    //データベースから取得したユーザー情報を格納する model.User 構造体を空で作成します。
	storedUser := model.User{}
    //ユーザーリポジトリの GetUserByEmail メソッドを呼び出して、メールアドレスでユーザーを取得します。
	if err := uu.ur.GetUserByEmail(&storedUser, user.Email); err != nil {
		return "", err
	}
    //bcrypt パッケージの CompareHashAndPassword 関数を使って、入力されたパスワードとデータベースに保存されたハッシュ化されたパスワードを比較します。
    //storedUser.Password は、データベースに保存されたハッシュ化されたパスワードです。
    //user.Password は、ユーザーがログイン時に入力したプレーンテキストのパスワードです。
	err := bcrypt.CompareHashAndPassword([]byte(storedUser.Password), []byte(user.Password))
	if err != nil {
		return "", err
	}
    //jwt パッケージの NewWithClaims 関数を使って、JWT トークンを作成します。
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"user_id": storedUser.ID,
        //jwtトークンの有効期限を12時間に設定
		"exp":     time.Now().Add(time.Hour * 12).Unix(),
	})
    //NewWithClaimsの返り値に対してSignedStringというメソッドを実行することによってjwtトークンを生成する
	tokenString, err := token.SignedString([]byte(os.Getenv("SECRET")))
	if err != nil {
		return "", err
	}
	return tokenString, nil
}
```

<h2>コントローラの実装</h2>
下記のコードは、ユーザーのサインアップ、ログイン、ログアウト処理を行うためのコントローラを定義しています。

```controller/user_controller.go
package controller

import (
	"go-rest-api/model"
	"go-rest-api/usecase"
	"net/http"
	"os"
	"time"
    //go mod tidyコマンドを実行してパッケージをダウンロードしておく
	"github.com/labstack/echo/v4"
)

//ユーザーコントローラが実装すべきメソッドを定義するインターフェースです。
type IUserController interface {
    //ユーザー登録処理を実行するメソッドです。
	SignUp(c echo.Context) error
    //ユーザーログイン処理を実行するメソッドです。
	Login(c echo.Context) error
    //ユーザーログアウト処理を実行するメソッドです。
	LogOut(c echo.Context) error
}

type userController struct {
    //ユーザーユースケースへの依存関係を保持するフィールドです。
	uu usecase.IUserUsecase
}

    //ユーザーユースケースオブジェクトを受け取り、userController構造体のインスタンスを生成して返します。
func NewUserController(uu usecase.IUserUsecase) IUserController {
	return &userController{uu}
}

//ユーザー登録処理を実行するメソッドの実装です。
func (uc *userController) SignUp(c echo.Context) error {
    //ユーザーの情報を格納する model.User 構造体を空で作成します。
	user := model.User{}
    //リクエストからユーザー情報を取得する処理です。
    //リクエストからユーザー情報を取得して、user 構造体にバインドして格納します。
    //バインド処理中にエラーが発生した場合、エラーを返します。
    //エラーが発生した場合、HTTPステータスコード 400 (Bad Request) を返します。
	if err := c.Bind(&user); err != nil {
		return c.JSON(http.StatusBadRequest, err.Error())
	}
    //ユーザーユースケースの SignUp メソッドを呼び出して、ユーザー登録処理を実行します。
    //uu フィールドは、ユーザーユースケースへのポインタです。
    //SignUp メソッドは、ユーザー登録処理を実行します。
	userRes, err := uc.uu.SignUp(user)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, err.Error())
	}
    //ユーザー登録処理が成功した場合、userRes 変数には、ユーザーの情報を格納する 
    //ユーザー登録処理が成功した場合、HTTPステータスコード 201 (Created) と、ユーザーの情報を含む model.UserResponse オブジェクトを JSON形式で返します。
    //model.UserResponse 構造体が格納されます。
	return c.JSON(http.StatusCreated, userRes)
}

//ログイン処理を実行するメソッドの実装です。
func (uc *userController) Login(c echo.Context) error {
    //ユーザーの情報を格納する model.User 構造体を空で作成します。
	user := model.User{}
    //リクエストからユーザー情報を取得する処理です。
    //c.Bind(&user) は、リクエストからユーザー情報を取得して、user 構造体にバインドします。
    //if err != nil { ... } は、バインド処理中にエラーが発生した場合、エラーを返します。
    //エラーが発生した場合、HTTPステータスコード 400 (Bad Request) を返します。
	if err := c.Bind(&user); err != nil {
		return c.JSON(http.StatusBadRequest, err.Error())
	}
    //ユーザーユースケースの Login メソッドを呼び出して、ユーザーログイン処理を実行します。
    //ログイン処理が成功した場合、tokenString 変数には、JWTトークンが格納されます。
	tokenString, err := uc.uu.Login(user)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, err.Error())
	}
    //新しいクッキーを作成します。
	cookie := new(http.Cookie)
    //クッキーの名前を "token" に設定します。
	cookie.Name = "token"
    //クッキーの値を、生成されたJWTトークンに設定します。
	cookie.Value = tokenString
    //クッキーの有効期限を24時間後に設定します。
	cookie.Expires = time.Now().Add(24 * time.Hour)
    //クッキーのパスを "/" に設定します。
	cookie.Path = "/"
    //クッキーのドメインを、環境変数 API_DOMAIN から取得した値に設定します。
	cookie.Domain = os.Getenv("API_DOMAIN")
    //クッキーをHTTPのみで使用できるように設定します。
	cookie.HttpOnly = true
    // クッキーを SameSiteNoneMode に設定します。
    //http.SameSiteNoneMode は、クッキーがすべてのサイトからアクセスできることを示します。これは、CSRF攻撃に対する保護が弱いため、注意が必要です。
	cookie.SameSite = http.SameSiteNoneMode
    //クッキーをレスポンスに設定して、クライアントに送信します。
	c.SetCookie(cookie)
    //HTTPステータスコード 200 (OK) を返します。
	return c.NoContent(http.StatusOK)
}

//ログアウト処理を実行するメソッドの実装です。
func (uc *userController) LogOut(c echo.Context) error {
    //新しいクッキーを作成します。
	cookie := new(http.Cookie)
    //クッキーの名前を "token" に設定します。
	cookie.Name = "token"
    //クッキーの値を空文字列に設定します。
	cookie.Value = ""
    //クッキーの有効期限を現在時刻に設定します。これにより、クッキーはすぐに期限切れになり、削除されます。
	cookie.Expires = time.Now()
    //クッキーのパスを "/" に設定します。これは、クッキーがアプリケーションのすべてのページで使用できることを示します。
	cookie.Path = "/"
    //クッキーのドメインを、環境変数 API_DOMAIN から取得した値に設定します。
	cookie.Domain = os.Getenv("API_DOMAIN")
    //クッキーをHTTPのみで使用できるように設定します。これにより、JavaScriptからクッキーにアクセスできないように、セキュリティを強化します。
	cookie.HttpOnly = true
    //クッキーを SameSiteNoneMode に設定します。これは、クッキーがすべてのサイトからアクセスできることを示します。
	cookie.SameSite = http.SameSiteNoneMode
    //クッキーをレスポンスに設定して、クライアントに送信します。これにより、クライアントは、このクッキーを次回のアクセス時にサーバーに送信します。
	c.SetCookie(cookie)
    //HTTPステータスコード 200 (OK) を返します。
	return c.NoContent(http.StatusOK)
}
```


<h2>routerの実装</h2>
このコードは、NewRouter 関数を呼び出すことで、REST APIのルーティング設定を行います。<br>NewRouter 関数は、ユーザーコントローラーのインスタンス uc を引数として受け取ります。<br>この関数は、Echoフレームワークのインスタンスを作成し、signup, login, logout エンドポイントへのリクエストをそれぞれユーザーコントローラーの SignUp, Login, LogOut メソッドにマッピングします。

```router/router.go
package router

import (
	"go-rest-api/controller"
     //go mod tidyコマンドを実行してパッケージをダウンロードしておく
	"github.com/labstack/echo/v4"
)

func NewRouter(uc controller.IUserController) *echo.Echo {
    //Echoフレームワークの新しいインスタンスを作成します。
	e := echo.New()
    // `POST` メソッドで `/signup` エンドポイントにアクセスしたときに、`uc.SignUp` 関数を呼び出すように設定します。
	e.POST("/signup", uc.SignUp)
    //`POST` メソッドで `/login` エンドポイントにアクセスしたときに、`uc.Login` 関数を呼び出すように設定します。
	e.POST("/login", uc.Login)
    //`POST` メソッドで `/logout` エンドポイントにアクセスしたときに、`uc.LogOut` 関数を呼び出すように設定します。
	e.POST("/logout", uc.LogOut)
    //設定されたルーティング設定を持つ Echoフレームワークのインスタンスを返します。
	return e
}
```

<h2>mainの実装</h2>
このコードは、データベース接続、ユーザーリポジトリ、ユーザーユースケース、ユーザーコントローラー、ルーティング設定などのコンポーネントを構築し、REST APIサーバーを起動します。

```main.go
package main

import (
	"go-rest-api/controller"
	"go-rest-api/db"
	"go-rest-api/repository"
	"go-rest-api/router"
	"go-rest-api/usecase"
)

func main() {
    //`db` パッケージの `NewDB` 関数を呼び出して、データベースとの接続を作成します。
	db := db.NewDB()
    //`repository` パッケージの `NewUserRepository` 関数を呼び出して、ユーザーリポジトリの新しいインスタンスを作成します。
	userRepository := repository.NewUserRepository(db)
    //`usecase` パッケージの `NewUserUsecase` 関数を呼び出して、ユーザーユースケースの新しいインスタンスを作成します。
	userUsecase := usecase.NewUserUsecase(userRepository)
    //controller パッケージの NewUserController 関数を呼び出して、ユーザーコントローラの新しいインスタンスを作成します。
	userController := controller.NewUserController(userUsecase)
    //`router` パッケージの `NewRouter` 関数を呼び出して、REST APIのルーティング設定を行います。
	e := router.NewRouter(userController)
    //Echoフレームワークの `Start` メソッドを呼び出して、ポート 8080 でREST APIサーバーを起動します。
	e.Logger.Fatal(e.Start(":8080"))
}
```

<h2>プログラムを起動</h2>
下記のコマンドでプログラムを起動していきます。

`GO_ENV=dev go run main.go`

起動できました！
![スクリーンショット 2024-10-28 18.42.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/814df47a-f518-a683-584a-f3e9e4cedcb2.png)

次はPostmanを使って動作確認します。
`http://localhost:8080/signup`のエンドポイントを入力します。
メソッドはPOSTを選択し、Bodyのところはrawを選択し、JSONを選択します。
emailとpasswordを入力しSendボタンを押下します。
成功しますと201 Createdのステータス、生成されたユーザーのidとemailが返ってきます。

![スクリーンショット 2024-10-28 18.47.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/b3e92a04-7a7c-980c-5c89-3c927aec8ced.png)

PgAdminを起動していきます。
Dockerで立ち上げてるPostgressのデータベースの中に作成したユーザー情報が登録されています。（usersテーブルを見ています）
パスワードはハッシュ化されたパスワードが登録されています。

![スクリーンショット 2024-10-28 18.58.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/ea426b72-b811-b206-01ea-164eb206665f.png)

エンドポイントを`http://localhost:8080/login`に変更してSendボタンを押下します。
200 OKのステータスが返ってきて、CookiesタブにCookieが設定されます。

![スクリーンショット 2024-10-28 19.02.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/408aa8e6-258c-0db5-1d82-69be1e9195cb.png)


エンドポイントを`http://localhost:8080/logout`に変更してSendボタンを押下します。
200 OKのステータスが返ってきて、Cookieが削除されます。

![スクリーンショット 2024-10-28 19.15.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/71ede372-4037-a6d8-9c38-a54548f34dfe.png)

<h2>まとめ</h2>
今回はユーザーの新規追加、ログイン、ログアウト機能を実装していきました。<br>
次回はタスク関係の処理を実装していきます。