ES自動生成AIを作った　~コード解説編~　(SignUp)

今回は以下の拡張機能を作成したときのことについて書いていく。
[ES自動生成AIを作った](https://qiita.com/Yutosaki/items/ad92ccd9ee087fc9a7bb)

https://github.com/Yutosaki/es-writer/tree/main?tab=readme-ov-file

https://chromewebstore.google.com/detail/es-writer-extension/jkencchebhkbaomammmgbhnpalgkchkm?hl=ja

以下の機能がどの様に動いているのかについて、それぞれコードを追いながら解説していきたいと思う。(解説が少し長くなってしまったので、SignIn以降は別の記事に書こうと思う)

* SignUp(メールアドレスなどの認証も含む登録）　　（今回の記事)
* [SignIn(ログイン)](https://qiita.com/Yutosaki/items/c52da4b98cdd3a934271)
* [経歴入力(自己PR、経験、今まで作った作品](https://qiita.com/Yutosaki/items/b3c1a35a6e3c1306cd3f)
* [回答生成(埋め込み含む)](https://qiita.com/Yutosaki/items/b3c1a35a6e3c1306cd3f)
* [SignOut(ログアウト)](https://qiita.com/Yutosaki/items/c52da4b98cdd3a934271)


構成は以下の様になっている。
```bat:フロントエンド
frontend   (モジュールやビルドパッケージについては省略)
├── package.json
├── pnpm-lock.yaml
├── postcss.config.js
├── README.md
├── src
│   ├── components
│   │   ├── Profile.tsx
│   │   └── ProfileForm.tsx
│   ├── contents
│   │   └── index.tsx
│   ├── popup
│   │   ├── index.tsx
│   │   └── routes
│   │       ├── App.tsx
│   │       ├── checkEmail.tsx
│   │       ├── genAnswer.tsx
│   │       ├── generating.tsx
│   │       ├── home.tsx
│   │       ├── logOut.tsx
│   │       ├── openProfileForm.tsx
│   │       ├── signIn.tsx
│   │       └── signUp.tsx
│   └── tabs
│       ├── profile.html
│       └── profile.tsx
├── style.css
├── tailwind.config.js
└── tsconfig.json
```

```bat:バックエンド
backend
├── controller
│   ├── auth_controller.go
│   ├── controllerUtils
│   │   └── controllerUtils.go
│   ├── generate_controller.go
│   └── user_controller.go
├── db
│   └── db.go
├── docker-compose.yml
├── Dockerfile
├── docs
│   └── swagger.yml
├── go.mod
├── go.sum
├── handler
│   └── handler.go
├── infrastructure
│   └── infrastructure.go
├── main.go
├── middleware
│   ├── auth
│   │   └── auth.go
│   └── cors
│       └── cors.go
├── migrate
│   └── migrate.go
├── model
│   ├── generate.go
│   └── user.go
├── README.md
├── repository
│   ├── auth_repository.go
│   ├── generate_usecase.go
│   └── user_repository.go
├── start.sh
├── terraform
│   ├── alb.tf
│   ├── cognito.tf
│   ├── docker-compose.yml
│   ├── ec2.tf
│   ├── provider.tf
│   ├── rds.tf
│   ├── route53.tf
│   ├── variables.tf
│   └── vpc.tf
├── usecase
│   ├── auth_usecase.go
│   ├── generate_usecase.go
│   ├── usecaseUtils
│   │   └── usecaseUtils.go
│   └── user_usecase.go
└── validator
    └── validator.go
```

# SignUp (登録)
まずはフロントから見ていく。
拡張機能のボタンが押されると`home.tsx`が描画される。ここでは以下のように、現在がSignInしている状態かどうかを判別し、それぞれの状態に基づいて表示画面を判断している。
```tsx:home.tsx
async function fetchData(
  loginState: string | undefined,
  setLoginState: (loginState: string) => void
) {
  try {
    const response = await fetch(api_endpoint + "/auth/login", {
      method: "POST",
      credentials: "include"
    })

    if (response.ok) {
      setLoginState("logged-in")
    } else {
      if (
        typeof loginState === "undefined" ||
        loginState === "not-logged-in" ||
        loginState === "logged-in"
      ) {
        setLoginState("not-logged-in")
      }
    }
  } catch (error) {
    console.error("Fetch error:", error)
  }
}

function IndexPopup() {
  const navigate = useNavigate()

  const [loginState, setLoginState] = useStorage<string>("loginState")

  useEffect(() => {
    fetchData(loginState, setLoginState)
  }, [])

  if (loginState === "not-logged-in") {
      //ログインしていない人用の画面(SignUpボタンやSignInボタンなど)
  } else if (loginState === "logged-in") {
      //ログインしている人用の画面(回答生成ボタンや経歴入力ボタンなど)
  }
}
  ```

そして、バックエンドの`main.go`から順にファイルを参照し、SignInしているかどうかの確認をしていく。
`main.go`ではDBの初期化を行いそれに伴って各種リポジトリーやユースケース、ミドルウェアなどを初期化して`handler.go`というルーティングを行うところに渡す。
```go:main.go
package main

import (
	"es-app/controller"
	"es-app/db"
	"es-app/infrastructure"
	"es-app/middleware/auth"
	"es-app/repository"
	"es-app/handler"
	"es-app/usecase"
)

func main() {
	db := db.NewDB()
	authRepository := repository.NewAuthRepository(db)
	userRepository := repository.NewUserRepository(db)
	generateRepository := repository.NewGenerateRepository()
	infrastructure := infrastructure.NewInfrastructure()
	authUsecase := usecase.NewAuthUsecase(authRepository, infrastructure)
	userUsecase := usecase.NewUserUsecase(userRepository)
	generateUsecase := usecase.NewGenerateUsecase(generateRepository, userRepository)
	authController := controller.NewAuthController(authUsecase)
	userController := controller.NewUserController(userUsecase)
	generateController := controller.NewGenerateController(generateUsecase)
	authMiddleware := auth.NewAuthMiddleware(infrastructure)
	e := handler.NewRouter(authController, authMiddleware, userController, generateController)
	e.Logger.Fatal(e.Start(":8080"))
}
```
```go:handler.go
package handler

import (
	"es-app/controller"
	"es-app/middleware/auth"
	"es-app/middleware/cors"
	"es-app/validator"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
	"github.com/labstack/gommon/log"
)

func NewRouter(
	ac controller.IAuthController,
	am auth.IAuthMiddleware,
	uc controller.IUserController,
	gc controller.IGenerateController,
) *echo.Echo {
	e := echo.New()
	e.Validator = validator.NewValidator()
	e.Use(middleware.Logger())
	e.Logger.SetLevel(log.INFO)

	authGroup := e.Group("/auth")
	{
		authGroup.Use(cors.SetupAuthCORS())
		authGroup.POST("/signup", ac.SignUp)
		authGroup.POST("/checkEmail", ac.CheckEmail)
		authGroup.POST("/resendEmail", ac.ResendEmail)
		authGroup.POST("/login", ac.Login)
		authGroup.POST("/logout", ac.LogOut)
	}
	appGroup := e.Group("/app")
	{
		appGroup.Use(cors.SetupUserCORS())
		appGroup.Use(am.JwtMiddleware())
		userGroup := appGroup.Group("/profile")
		{
			userGroup.GET("/getProfile", uc.GetProfile)
			userGroup.PATCH("/updateProfile", uc.UpdateProfile)
		}
		generateGroup := appGroup.Group("/generate")
		{
			generateGroup.POST("/generateAnswers", gc.GenerateAnswers)
		}
	}
	return e
}
```

ここではEchoフレームワークを利用してインスタンスを初期化する。その後認証関連とログイン後に使用できるロジックとでエンドポイントをそれぞれグループ化して分けて、対応するエンドポイントに処理が流される。今回はフロントで`augh/login`を呼び出しているので、そこの処理を見ていく。

`handler.go`より、`controller層`の`auth_controller.go`の中にある、`Login`という以下の関数で確認していく。
```go:Login
func (ac *authController) Login(c echo.Context) error {
	loginUser := model.LoginUser{}
	accessToken, err := c.Cookie("accessToken")
	if err == nil {
		res, err := ac.authUsecase.AccessToken(c, accessToken.Value)
		if err == nil {
			c.Logger().Debug("🟡 Use access token")
			return c.JSON(http.StatusOK, res)
		}
	}

	refreshToken, err := c.Cookie("refreshToken")
	if err == nil {
		cookieRes, userRes, err := ac.authUsecase.RefreshToken(c, refreshToken.Value)
		if err == nil {
			c.Logger().Debug("🟡 Use refresh token")
			controllerUtils.SetLoginCookie(c, cookieRes.IDToken, cookieRes.AccessToken, cookieRes.RefreshToken)
			return c.JSON(http.StatusOK, userRes)
		}
	}

	if err := c.Bind(&loginUser); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}

	if err := c.Validate(loginUser); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}

	loginRes, err := ac.authUsecase.LogIn(c, loginUser)
	if err != nil {
		return echo.NewHTTPError(http.StatusForbidden, err.Error())
	}

	c.Logger().Debug("🟡 Use username and password")

	controllerUtils.SetLoginCookie(c, loginRes.IDToken, loginRes.AccessToken, loginRes.RefreshToken)

	return c.JSON(http.StatusOK, loginUser)
}
```

ここではまず、`Access Token`が有効かどうかを確認する。もし有効なら`status OK`を返し、ログインしていることになる。次にもし`Access Token`が無効、もしくは無かった場合は`Refresh Token`を確認する。もし有効な場合は`Refresh Token`を使用して新しい`Access Token`を作成し、もし成功したら `status OK`を返却する。そして、これらの両方がうまくいかなかった場合、例えば初回ログインなど(次の項で詳しく書く)、ユーザー名とパスワードでログインし、成功した場合はトークンが発行され、新しいトークンをCookieにセットして、`status OK`を返却する。そして、もし成功していない場合は途中の
```go
	if err := c.Bind(&loginUser); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}

	if err := c.Validate(loginUser); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}

	loginRes, err := ac.authUsecase.LogIn(c, loginUser)
	if err != nil {
		return echo.NewHTTPError(http.StatusForbidden, err.Error())
	}
 ```
ここの部分`Bad Request`なり`Forbidden`なりが返却され、ログインログインが出来ていない事を知らせる。今回は以下の様に`POST`メソッドだが、bodyに何も入っていないのでBadRequestが返却される。（Login関数でこれを行うのではなくて、CheckStateの様な別関数に分けてもいい気もする)
```tsx
const response = await fetch(api_endpoint + "/auth/login", {
      method: "POST",
      credentials: "include"
    })
```

そして、返却された値によりフロントで表示する画面が変わり、今回はログインしていない人用の画面が表示される。
そしてSignUpボタンが押されることでやっとSignUpの工程に進むことができる。

SignUpのボタンが押されるお`signUp.tsx`というファイルを読み込む。ここではパスワードの監視(大文字、小文字、記号、数字を含んでいるかや8文字以上かどうか)やその入力されたパスワードによって文字色を変えたりなどをしているが、今回は主要な部分のみ解説していくので、それらは省き、入力されたユーザーネームやパスワード、メールアドレスのチェックなどをどの様に行なっているかについて見ていく。

`signUp.tsx`では`username`、`password`、`Email`を入力してもらい、まずそれらのうち`username`と`Email`に被りがなければAWSのCognitoにデータを登録し、Cookieにもユーザー名などをセットする。その後、`Email`の確認として、確認コードをを送って、それを確認し、やっとSignUpが完了する。
では、実際のコードと共に流れを追っていく。
まず`signUp.tsx`で以下の様に`data`情報を`POST`する。
```tsx:signUp.tsx
const onSubmit = async (data) => {
    console.log("SignUp form submitted")
    const response = await fetch(api_endpoint + "/auth/signup", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data)
    })
    if (response.ok) {
      console.log("SignUp successful")
      setLoginState("checkEmail")
      navigate("/checkEmail")
    }else if (response.status === 409) {
        // メールやユーザーネームの重複
    }else if (response.status === 500) {
        // サーバーエラー
    }else {
        // サインアップに失敗
    }
```
ここで`data`とは`username` `email` `password`を含むオブジェクトである。
以下の様に`register関数`を使用して定義している。
```tsx:data
  return (
    <form
      onSubmit={handleSubmit(onSubmit)}
      className="flex flex-col space-y-1.5 w-60 h-auto items-center mb-2 mt-2">
      <input
        {...register("username", {
          required: "ユーザー名は必須です",
          pattern: {
            value: /^[\x20-\x7E]+$/,
            message: "使用可能文字は半角英数字・記号のみです"
          },
          maxLength: {
            value: 20,
            message: "ユーザー名は20文字以下にしてください"
          }
        })}
        type="text"
        placeholder="Username"
        className="border border-gray-300 rounded-md px-4 py-1 w-5/6"
      />
      {errors.username && typeof errors.username.message === "string" && (
        <span className="text-red-500 text-xs">{errors.username.message}</span>
      )}

      <input
        {...register("email", {
          required: "メールアドレスは必須です",
          pattern: {
            value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
            message: "正しいメールアドレスを入力してください"
          }
        })}
        type="text"
        placeholder="Email"
        className="border border-gray-300 rounded-md px-4 py-1 w-5/6"
      />
      {errors.email && typeof errors.email.message === "string" && (
        <span className="text-red-500 text-xs">{errors.email.message}</span>
      )}

      <input
        {...register("password", {
          required: "パスワードは必須です",
          pattern: {
            value:
              /^(?=.*\d)(?=.*[\^$*.[\]{}()?"!@#%&\/\\,><':;|_~`=+\-])(?=.*[a-z])(?=.*[A-Z])[\x20-\x7E]{8,}$/,
            message: "パスワードが不正です"
          }
        })}
        type="password"
        placeholder="Password"
        className="border border-gray-300 rounded-md px-4 py-1 w-5/6"
      />
```

そして、`handler.go`でルーティング処理を行い、`auth_controller.go`のSignUpにアクセスされる。main.goはアプリケーションのエントリーポイントであり起動時に一度だけ実行される。そして依存性注入を利用して、初期化されたコンポーネントを`controller.go`に渡し、`controller.go`でルーティング処理を行うことで、次にアクセスするときは`main.go`を介さずにルーティング処理部分にアクセスできるため、毎回初期化されるという事を防げる。

そして`auth_controller.go`の`SignUp関数`では以下の様に`email`と`username`が使用されていないかを確認して`Cognito`に登録される。

```go:auth_controller.go
func (ac *authController) SignUp(c echo.Context) error {
	signUpUser := model.SignUpUser{}
	if err := c.Bind(&signUpUser); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}

	if err := c.Validate(signUpUser); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}

	// メールアドレスが既に登録されているか確認
	isAlreadyRegisteredEmail, err := ac.authUsecase.IsAlreadyRegisteredEmail(c, signUpUser.Email)
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
	}
	if isAlreadyRegisteredEmail {
		return echo.NewHTTPError(http.StatusConflict, "メールアドレスが既に登録されています")
	}

	userRes, err := ac.authUsecase.SignUp(c, signUpUser)
	if err != nil {
		// ユーザー名が既に登録されているか確認
		if httpError, ok := err.(*echo.HTTPError); ok && httpError.Code == http.StatusConflict {
			return echo.NewHTTPError(http.StatusConflict, "ユーザー名が既に登録されています")
		}
		return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
	}

	controllerUtils.SetSignupCookie(c, "username", signUpUser.Username, 10*time.Minute)

	return c.JSON(http.StatusCreated, userRes)
}
```

まず`signUpUser`を初期化する。（リクエストデータをバインドするため）
そして、以下のようにリクエストボディに入っているデータを`signUpUser`という構造体にバインド(HTTPリクエストのデータを構造体に変換すること)する。
```go:リクエストボディをバインド
if err := c.Bind(&signUpUser); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}
```
>補足 ~バインドとマッピングの違い~
>* バインドはHTTPリクエストのBodyを構造体に変換するプロセス
>* マッピングはあるデータ構造のフィールドから別のデータ構造のフィールドに結びつけるプロセス
> バインドはリクエストのデータを処理する際に使用され、マッピングはデータの変換や転送などに使用される

次にバインドされたデータ構造が正しい形をしているか確認する。
```go:SingUpUser
type SignUpUser struct {
	Username  string    `json:"username" validate:"required"`
	Email     string    `json:"email" validate:"required,email"`
	Password  string    `json:"password" validate:"required"`
	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}
```
SinUpUser構造体は上記の様になっている。ここの`Username`, `Email`, `Password`の3つに対して、空でないかどうかと`Email`に対しては正しいメールアドレスの形式になっているかを`Validate`で確認する。(`requiredフィールド`は必須である事を示す)

その後`IsAlreadyRegisterdEmail関数`でメールアドレスに被りがないかを確認する。
まず`usecase層`の`IsAlreadyRegisterdEmail関数`に飛ぶ。
```go:auth_usecase.go
func (u *authUsecase) IsAlreadyRegisteredEmail(c echo.Context, email string) (bool, error) {
	email, err := u.authRepo.FindByEmail(c, email)
	if err != nil {
		return false, err
	}
	return email != "", nil
}
```
ここでは単に`repository層`の`FindByEmail関数`でDBにアクセスして返却されたものにエラーがないか確認しているだけである。`usecase層`と`repository層`を分けることでテストが容易になり、依存関係も減り、再利用性も高まる。ついでにそれぞれの層が何をしているのかについてまとめておく。
* controller層：
    ユーザーからのリクエストを受け取り、リクエストのパラメータを検証し、正しいusecase層を呼び出す
* usecase層：
    ビジネスロジックを実装する層。アプリケーションのユースケース、具体的な処理や流れを記載する。controller層からのリクエストに対して適切な処理を行う。
* repository層：
    データアクセスを担当する層。データベースにアクセスし、データの取得や作成などを行う。
* infrastructure層：
    アプリケーション外部とのやり取りを行う。外部APIの呼び出しなど、データアクセス以外の外部リソースとのやりとりを担当する。

では本題の`FindByEmail関数`について見ていく。
```go:auth_repository.go
type authRepository struct {
	db *gorm.DB
}

func (r *authRepository) FindByEmail(c echo.Context, email string) (string, error) {
	var user model.User
	result := r.db.WithContext(c.Request().Context()).Select("email").Where("email = ?", email).First(&user)
	if result.Error != nil {
		if result.Error == gorm.ErrRecordNotFound {
			return "", nil
		}
		return "", result.Error
	}
	return email, nil
}
```

ここではGormを使用して、受け取った`email`が存在するかどうかを確認し、存在しない場合は空文字を返却し、存在する場合はその`email`をそのまま返している。Gormとは、Go言語用のORMライブラリである。ORM(Object-Relational Mapping)とはデータベースのテーブルをオブジェクトとして扱うことができるというもの。
>ORMのメリット
>https://baapuro.com/Django/eight/
これらによってもしメールアドレスがすでに登録されていた場合はそのメールアドレスが返され、auth_usecase.goの`ISAlreadyREgisterdEmail関数`で`contorller.go`に`true`が返却されフロントでそれを判別する。

これでメールアドレスの重複は出来た。

次にユーザー名の確認をして、登録を行う。今回は別関数を作ってユーザー名の確認をするのではなく、`Cognito`でユーザー名が被ったらそもそも登録できずにエラーが返される様になっているので、そこを使って処理した。
```go:auth_controller.go
userRes, err := ac.authUsecase.SignUp(c, signUpUser)
	if err != nil {
		// ユーザー名が既に登録されているか確認
		if httpError, ok := err.(*echo.HTTPError); ok && httpError.Code == http.StatusConflict {
			return echo.NewHTTPError(http.StatusConflict, "ユーザー名が既に登録されています")
		}
		return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
	}
```
これで`usecase層`の`SignUp関数`を呼び出して登録可能かどうかを判別していく。
```go:auth_usecase.go
func (au *authUsecase) SignUp(c echo.Context, signUpUser model.SignUpUser) (model.User, error) {
	user, err := au.infrastructure.SignUp(c, signUpUser)
	if err != nil {
		return model.User{}, err
	}

	if err := au.authRepo.CreateUser(c, user); err != nil {
		return model.User{}, err
	}

	return user, nil
}
```

ここでは`infrastructure層`の`SinUp関数`で外部API、`Cognito`にアクセスして登録を行う。`infrastructure層`が何をしているかは先ほど述べたとおりだ。
```go:infrastructure.go
func (i *infrastructure) SignUp(c echo.Context, signUpUser model.SignUpUser) (model.User, error) {
	svc, err := i.CreateCognitoClient(c)
	if err != nil {
		return model.User{}, err
	}

	signUpInput := &cognitoidentityprovider.SignUpInput{
		ClientId: aws.String(i.clientID),
		Username: aws.String(signUpUser.Username),
		Password: aws.String(signUpUser.Password),
		UserAttributes: []types.AttributeType{
			{
				Name:  aws.String("email"),
				Value: aws.String(signUpUser.Email),
			},
		},
	}

	signUpOutput, err := svc.SignUp(c.Request().Context(), signUpInput)
	if err != nil {
		var ae *types.UsernameExistsException
		if errors.As(err, &ae) {
			return model.User{}, echo.NewHTTPError(http.StatusConflict, "ユーザーネームが既に存在します")
		}
		return model.User{}, err
	}

	userID := *signUpOutput.UserSub
	createdAt := time.Now()

	user := model.User{
		UserID:    userID,
		Username:  signUpUser.Username,
		Email:     signUpUser.Email,
		CreatedAt: createdAt,
		UpdatedAt: createdAt,
	}

	return user, nil
}
```

ここでは、まず`Cognito`へのサインアップの入力データを作成する。(SignUpInput)
それを`Cognito`の`SignUp関数`へ渡して登録する。その際`Password`はハッシュ化されて登録されるのでこちらでハッシュ化してから渡す必要はない。

```go:ユーザー名の重複チェック
signUpOutput, err := svc.SignUp(c.Request().Context(), signUpInput)
	if err != nil {
		var ae *types.UsernameExistsException
		if errors.As(err, &ae) {
			return model.User{}, echo.NewHTTPError(http.StatusConflict, "ユーザーネームが既に存在します")
		}
		return model.User{}, err
	}
 ```
 
 もし`Cognito`の登録の際にユーザーネームに被りがあったら、上記のように`err`に`UsernameExistsException`というエラーが返ってきてユーザー名の重複を知らせてくれる。

そしてもし正しく登録できた場合は、`UserID`や`メールアドレス`などの情報を、呼び出し元の`auth_usecase.go`の`SignUp関数`で受け取り、それを`auth_controller.go`の`SignUp関数`に返却する。もし、重複があった場合はそれをフロント側に`Status Code`で伝え、適切なメッセージを出す。特に問題なく`Cognito`に登録できていた場合は`ユーザー名`をCookieに保存する。これはこの後のメールアドレスを認証するときに`Cognito`に`username`と`verification code`の両方を送らなきゃいけないためである。登録したすぐ直後にメールアドレスの認証を行うのだが、わざわざもう一度`username`を打たせるのは面倒なのでCookieにセットした。

これでやっと`Cognito`への登録が完了する。

# Check Email (メールアドレスの確認)
次にメールアドレスのチェクを行う。この操作を持ってSignUpが完了する。メールアドレスのチェックとは、`Cognito`に登録した際のメールアドレスに`VerificationCode`という6桁の数字を送り、それを入力してもらって正しい確認し、もし正しければメールアドレスを含めたすべての登録が完了するという手順である。

```ts:SignUp.tsx
const onSubmit = async (data) => {
    console.log("SignUp form submitted")
    const response = await fetch(api_endpoint + "/auth/signup", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify(data)
    })
    if (response.ok) {
      console.log("SignUp successful")
      setLoginState("checkEmail")
      navigate("/checkEmail")
    } else //エラーだった場合
```
先ほどのSignUpが上手くいった場合は`/checkEmail`という画面に自動的に遷移する。

```tsx:checkEmail.tsx
...

const onSubmit = async (data) => {
    console.log("Check Email form submitted")
    const response = await fetch(api_endpoint + "/auth/checkEmail", {
      method: "POST",
      headers: {
        "Content-Type": "application/json"
      },
      body: JSON.stringify({ verificationCode: data.verificationCode })
    })
    if (response.ok) {
      console.log("Check Email successful")
      navigate("/signin")
    } else {
      console.error("Check Email failed")
      alert("認証コードが違います")
    }
  }

...

  onSubmit={handleSubmit(onSubmit)}
      <input
        type="text"
        placeholder="VerificationCode"
        {...register("verificationCode", {
          required: "コードを入力してください",
          minLength: { value: 6, message: "正しいコードを入力してください" },
          maxLength: { value: 6, message: "正しいコードを入力してください" }
        })}
        
...
```

このように6文字でのみ認証コードを受け取る様にしている。

それを`onsubmit`でバックエンドに`POST`リクエストを飛ばし、正しいコードかどうかを確認する。

そして再び`handler.go`により、`controller層`の`auth_controller.go`に流されるので、その後の流れを順を追ってみていく。
```go:auth_controller.go
func (ac *authController) CheckEmail(c echo.Context) error {
	checkStruct := model.CheckEmail{}
	if err := c.Bind(&checkStruct); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}

	if err := c.Validate(checkStruct); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, err.Error())
	}

	usernameCookie, err := c.Cookie("username")
	if err != nil {
		return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
	}
	checkStruct.Username = usernameCookie.Value

	checkRes, err := ac.authUsecase.CheckEmail(c, checkStruct)
	if err != nil {
		return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
	}

	return c.JSON(http.StatusOK, checkRes)
}
```

ここでも先ほどと同じように構造体にバインドして、それが正しいかどうかを`Validate関数`で確かめる
```go:model / user.go
type CheckEmail struct {
	Username         string `json:"username"`
	VerificationCode string `json:"verificationCode" validate:"required"`
}
```

今回は`VerificationCode`を確かめている。
今回はフロントで6文字かどうか確かめているのにわざわざ`Validate関数`で確認する必要ある？と思うかもしれないが、実際のところブラウザ上での確認はユーザーが開発者ツールを使えば簡単にスキップできてしまうし、コードの一貫性や整合性を持たせるために毎回きちんとValidationを行なっている。

そしてValidationが終わったらCookieからusernameを取得し(先ほど`SignUp`の際に仕込んだ)、それらの二つを`usecase層`の`CheckEmail関数`で本当に正しいかどうか確かめる。

`usecase層`では以下のように`infrastructure層`に促すだけである。`Cognito`はDBではなく外部アプリケーションなので`infrastructure層`に記載。(認証、認可に特化したAWSのサービス)
```go:auth_usecase.go
func (au *authUsecase) CheckEmail(c echo.Context, checkEmail model.CheckEmail) (bool, error) {
	res, err := au.infrastructure.CheckEmail(c, checkEmail)
	if err != nil {
		return false, err
	}

	return res, nil
}
```

実際のチェックは`infrastructure.go`で行っている。
```go:infrastructure.go
func (i *infrastructure) CheckEmail(c echo.Context, checkEmail model.CheckEmail) (bool, error) {
	svc, err := i.CreateCognitoClient(c)
	if err != nil {
		return false, err
	}

	confirmSignUpInput := &cognitoidentityprovider.ConfirmSignUpInput{
		ClientId:         aws.String(i.clientID),
		Username:         aws.String(checkEmail.Username),
		ConfirmationCode: aws.String(checkEmail.VerificationCode),
	}

	_, err = svc.ConfirmSignUp(c.Request().Context(), confirmSignUpInput)
	if err != nil {
		return false, err
	}

	return true, nil
}
```

と言っても、内容は難しくなく、`Cognito`に必要な情報(clientIDはどのアプリケーションからのリクエストか判別するために必要らしい)を渡すだけだ。aws.Stringは文字列をポインタにしている。（AWS SDKの多くのAPIはポインタ型を要求するため覚えておくと良い)

 これでもし正しければ、trueが返却され、フロントエンドに200 Okが返却され、メールアドレスのチェックが完了し、サインインの画面へ自動で遷移する。

# Resend Email (メールアドレスにVerificationコードを再送)
 ついでに、Resend Emailを少しだけ触れておく。
 ```go:infrastructure.go
 func (i *infrastructure) ResendEmail(c echo.Context, resendEmail model.ResendEmail) (bool, error) {
	svc, err := i.CreateCognitoClient(c)
	if err != nil {
		return false, err
	}

	resendInput := &cognitoidentityprovider.ResendConfirmationCodeInput{
		ClientId: aws.String(i.clientID),
		Username: aws.String(resendEmail.Username),
	}

	_, err = svc.ResendConfirmationCode(c.Request().Context(), resendInput)
	if err != nil {
		return false, err
	}

	return true, nil
}
```

まぁ見ての通りなのだが、受け取ったusernameに紐付いているemailに`Cognito`が自動的にメールを再送してくれるというだけだ。

# 終わりに
これにて登録の全ての手順が終了した。難しくてわからない、というところがないようになるべく丁寧に解説したつもりだ。内容自体はそこまで難しくないが、クリーンアーキテクチャに沿っていて層が分かれていてコード上では少し追いずらい、などがあるかもしれない。しかし、それぞれの層が何をしているのかについてきちんと理解すれば、少しは読みやすくなり、保守性、運用性が高いことが見て取れるだろう。
次回以降でSignInや回答生成などの別機能について解説していく。