開始之前我們調整一下之前在adapter\google\oauth.go的OAuthLoginURL(),把原本寫死的第一個參數”state-token”改成外部代入的lineID,這個lineID可以在驗證完成後被帶到api/v1/ouath-login,也就是在處理Redirect URL時跟OAuth token一起取出。
// internal\adapter\google\oauth.go
func (oa *GoogleOAuth) OAuthLoginURL(lineID string) (oauthURL string) {
	oauthURL = oa.Config.AuthCodeURL(lineID, oauth2.AccessTypeOffline, oauth2.ApprovalForce) // oauth2.ApprovalForce
	return oauthURL
}
adapter的interface也要記得更新
// internal\adapter\google\interface.go
type GoogleOAuthI interface {
	OAuthLoginURL(lineID string) (oauthURL string)
	UserOAuthToken(authCode string) (*oauth2.Token, error)
	NewGoogleDrive(ctx context.Context, tok *oauth2.Token) (*GoogleDrive, error)
}
接著到Service的interface.go,把OAuthLoginURL的地方換一下之後,為了要將OAuth Token存到Dynamodb中,我們得在drive Service中能調用Dynamodb,所以這邊做一個DriveServiceDynamodbI,放入要使用的方法。
// internal\app\service\drive\interface.go
package drive
import (
	"context"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb"
	"github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
	dynamodbAdapter "github.com/onepiece010938/Line2GoogleDriveBot/internal/adapter/dynamodb"
	"github.com/onepiece010938/Line2GoogleDriveBot/internal/adapter/google"
	"golang.org/x/oauth2"
)
type DriveServiceGoogleOAuthI interface {
	OAuthLoginURL(lineID string) (oauthURL string)
	UserOAuthToken(authCode string) (*oauth2.Token, error)
	NewGoogleDrive(ctx context.Context, tok *oauth2.Token) (*google.GoogleDrive, error)
}
type DriveServiceDynamodbI interface {
	GetGoogleOAuthToken(line_userid string) (dynamodbAdapter.GoogleOAuthToken, error)
	CreateGoogleOAuthTable() (*types.TableDescription, error)
	AddGoogleOAuthToken(tok dynamodbAdapter.GoogleOAuthToken) error
	TxUpdateGoogleOAuthToken(tok dynamodbAdapter.GoogleOAuthToken) (*dynamodb.TransactWriteItemsOutput, error)
}
接著更新service.go,讓NewGoogleDriveService的時候能把Dynamodb放進GoogleDriveService中,讓GoogleDriveService能直接調用到Dynamodb
// internal\app\service\drive\service.go
package drive
import "context"
type GoogleDriveService struct {
	driveServiceGoogleOA DriveServiceGoogleOAuthI
	driveServiceDynamodb DriveServiceDynamodbI
}
type GoogleDriveServiceParam struct {
	DriveServiceGoogleOA DriveServiceGoogleOAuthI
	DriveServiceDynamodb DriveServiceDynamodbI
}
func NewGoogleDriveService(_ context.Context, param GoogleDriveServiceParam) *GoogleDriveService {
	return &GoogleDriveService{
		driveServiceGoogleOA: param.DriveServiceGoogleOA,
		driveServiceDynamodb: param.DriveServiceDynamodb,
	}
}
最後到application.go,新增DriveServiceDynamodb: dynamodb,注入到DriveService裡面
// internal\app\application.go
func NewApplication(ctx context.Context, dynamodb dynamodb.DynamodbI, oauth google.GoogleOAuthI, lineBotClient *linebot.Client) *Application {
	app := &Application{
		LineBotClient: lineBotClient,
		SampleService: serviceSample.NewSampleService(ctx, serviceSample.SampleServiceParam{
			SampleServiceDynamodb: dynamodb,
		}),
		DriveService: serviceDrive.NewGoogleDriveService(ctx, serviceDrive.GoogleDriveServiceParam{
			DriveServiceGoogleOA: oauth,
			DriveServiceDynamodb: dynamodb,
		}),
	}
	return app
}
接著我們在drive service下面,建立一個login_service.go專門處理登入相關的操作。LoginURL將傳入的lineID代入state,生成對應的Redirect URL;Login會把從Redirect URL中解出的authCode和lineID,拿去取得OAuthToken後製作成dynamodb定義的格式,最後調用AddGoogleOAuthToken把製作好的token存進去。
// internal\app\service\drive\login_service.go
package drive
import (
	"context"
	"github.com/onepiece010938/Line2GoogleDriveBot/internal/adapter/dynamodb"
)
func (dr *GoogleDriveService) Login(ctx context.Context, lineID string, authCode string) error {
	tok, err := dr.driveServiceGoogleOA.UserOAuthToken(authCode)
	if err != nil {
		return err
	}
	dToken := dynamodb.GoogleOAuthToken{
		PK:           lineID,
		AccessToken:  tok.AccessToken,
		TokenType:    tok.TokenType,
		RefreshToken: tok.RefreshToken,
		Expiry:       tok.Expiry.String(),
	}
	err = dr.driveServiceDynamodb.AddGoogleOAuthToken(dToken)
	if err != nil {
		return err
	}
	return nil
}
func (dr *GoogleDriveService) LoginURL(ctx context.Context, lineID string) string {
	oauthURL := dr.driveServiceGoogleOA.OAuthLoginURL(lineID)
	return oauthURL
}
最後我們回到router層把callback和OAuthLogin改成調用app中對應的service
// internal\router\api\v1\callback.go
for _, event := range events {
			if event.Type == linebot.EventTypeMessage {
				switch message := event.Message.(type) {
				case *linebot.TextMessage:
					if message.Text == "login" {
						lineID := event.Source.UserID
						authURL := app.DriveService.LoginURL(ctx, lineID)
						if _, err = app.LineBotClient.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(authURL)).Do(); err != nil {
							log.Println(err)
						}
						return
					}
...
// internal\router\api\v1\oauth.go
func OAuthLogin(app *app.Application) gin.HandlerFunc {
	return func(c *gin.Context) {
		authCode := c.Query("code")
		lineID := c.Query("state") //get lineid
		err := app.DriveService.Login(c.Request.Context(), lineID, authCode)
		if err != nil {
			log.Printf("Unable to retrieve DriveService.Login %v", err)
			c.String(http.StatusInternalServerError, "Unable to retrieve DriveService.Login")
			return
		}
		_, err = c.Writer.Write([]byte("<html><title>Login</title> <body> Authorized successfully, please close this window</body></html>"))
		if err != nil {
			log.Printf("Unable to write HTML: %v", err)
		}
	}
}
登入後打開Dynamodb admin,就可以看到成功存進去囉~


那今天我們就先寫到這邊,明天再見囉~