generated from taiseidev/flutter-base
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #14 from taiseidev/feature/create-signup
🚀 add: Add user registration API.
- Loading branch information
Showing
19 changed files
with
364 additions
and
137 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
gorm | ||
joho | ||
godotenv |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,3 +14,5 @@ build/ | |
|
||
# FVM Version Cache | ||
.fvm/ | ||
|
||
.env |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,16 @@ | ||
package routes | ||
|
||
import ( | ||
"camly-api/internal/user/handler" | ||
authHandler "camly-api/internal/auth/handler" | ||
|
||
"github.com/labstack/echo/v4" | ||
) | ||
|
||
// 全てのルートを定義 | ||
func RegisterRoutes(e *echo.Echo, userHandler *handler.UserHandler) { | ||
func RegisterRoutes(e *echo.Echo, authHandler *authHandler.AuthHandler) { | ||
v1 := e.Group("/api/v1") | ||
// ユーザー関連のルートを定義 | ||
userRoutes := v1.Group("/users") | ||
// TODO: Add necessary middleware | ||
// userRoutes.Use(middleware.JWT([]byte("secret"))) | ||
userRoutes.POST("", userHandler.CreateUser) | ||
// 認証関連のルートを定義 | ||
authRoutes := v1.Group("/auth") | ||
authRoutes.POST("/register", authHandler.SignUp) | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// auth/claims.go | ||
package auth | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/golang-jwt/jwt/v5" | ||
) | ||
|
||
type Claims struct { | ||
UserID uint `json:"user_id"` | ||
jwt.RegisteredClaims | ||
} | ||
|
||
// Duration for access token validity | ||
const AccessTokenExpiration = time.Minute * 7 | ||
|
||
// Duration for refresh token validity | ||
const RefreshTokenExpiration = time.Hour * 24 * 7 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package handler | ||
|
||
import ( | ||
authService "camly-api/internal/auth/service" | ||
"camly-api/internal/user/model" | ||
"camly-api/internal/utils" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
|
||
"github.com/labstack/echo/v4" | ||
) | ||
|
||
type AuthHandler struct { | ||
authService *authService.AuthService | ||
} | ||
|
||
func NewAuthHandler(authService *authService.AuthService) *AuthHandler { | ||
if authService == nil { | ||
panic("authService cannot be nil") | ||
} | ||
return &AuthHandler{ | ||
authService: authService, | ||
} | ||
} | ||
|
||
func (h *AuthHandler) SignUp(c echo.Context) error { | ||
var req model.User | ||
if err := c.Bind(&req); err != nil { | ||
return c.JSON(http.StatusBadRequest, map[string]string{ | ||
"error": "Invalid request format", | ||
"details": err.Error(), | ||
}) | ||
} | ||
|
||
ctx := c.Request().Context() | ||
|
||
// Validate input | ||
if err := validateUserInput(&req); err != nil { | ||
return c.JSON(http.StatusBadRequest, map[string]string{ | ||
"error": "Validation failed", | ||
"details": err.Error(), | ||
}) | ||
} | ||
|
||
// Serviceを呼び出し | ||
tokens, err := h.authService.SignUp(ctx, req) | ||
if err != nil { | ||
log.Printf("failed to create user: %v", err) | ||
return c.JSON(http.StatusInternalServerError, map[string]string{ | ||
"error": "User creation failed", | ||
"details": "An error occurred while processing your request", | ||
}) | ||
} | ||
|
||
// レスポンスとしてユーザーを返す | ||
return c.JSON(http.StatusOK, tokens) | ||
} | ||
|
||
func validateUserInput(user *model.User) error { | ||
if user.Name == "" { | ||
return fmt.Errorf("name is required") | ||
} | ||
if user.Email == "" { | ||
return fmt.Errorf("email is required") | ||
} | ||
if !utils.IsValidEmail(user.Email) { | ||
return fmt.Errorf("invalid email format") | ||
} | ||
if user.Password == "" { | ||
return fmt.Errorf("password is required") | ||
} | ||
if len(user.Password) < 8 { | ||
return fmt.Errorf("password must be at least 8 characters long") | ||
} | ||
// Additional password complexity checks can be added here | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package repository | ||
|
||
import ( | ||
"camly-api/internal/user/model" | ||
"context" | ||
"fmt" | ||
|
||
"gorm.io/gorm" | ||
) | ||
|
||
type AuthRepository struct { | ||
db *gorm.DB | ||
} | ||
|
||
// NOTE(onishi): multiple interfaces in the future | ||
type IAuthRepository interface { | ||
SaveUser(ctx context.Context, user *model.User) error | ||
} | ||
|
||
func NewAuthRepository(db *gorm.DB) IAuthRepository { | ||
if db == nil { | ||
panic("db cannot be nil") | ||
} | ||
return &AuthRepository{ | ||
db: db, | ||
} | ||
} | ||
|
||
func (r *AuthRepository) SaveUser(ctx context.Context, user *model.User) error { | ||
if user == nil { | ||
return fmt.Errorf("user cannot be nil") | ||
} | ||
|
||
// データの妥当性検証 | ||
if err := user.Validate(); err != nil { | ||
return fmt.Errorf("invalid user data: %w", err) | ||
} | ||
|
||
// ユーザー情報の挿入 | ||
if err := r.db.WithContext(ctx).Create(user).Error; err != nil { | ||
return fmt.Errorf("failed to save user: %w", err) | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package service | ||
|
||
import ( | ||
"camly-api/internal/auth" | ||
authRepository "camly-api/internal/auth/repository" | ||
"camly-api/internal/user/model" | ||
"context" | ||
"time" | ||
|
||
"golang.org/x/crypto/bcrypt" | ||
) | ||
|
||
type AuthService struct { | ||
authRepo authRepository.IAuthRepository | ||
} | ||
|
||
func NewAuthService(authRepo authRepository.IAuthRepository) *AuthService { | ||
return &AuthService{ | ||
authRepo: authRepo, | ||
} | ||
} | ||
|
||
type TokenResponse struct { | ||
AccessToken string `json:"access_token"` | ||
RefreshToken string `json:"refresh_token"` | ||
AccessTokenExpiration string `json:"access_token_expiration"` | ||
RefreshTokenExpiration string `json:"refresh_token_expiration"` | ||
} | ||
|
||
func (s *AuthService) SignUp(ctx context.Context, user model.User) (TokenResponse, error) { | ||
|
||
// パスワードをハッシュ化 | ||
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 10) | ||
|
||
if err != nil { | ||
return TokenResponse{}, err | ||
} | ||
|
||
newUser := model.User{Name: user.Name, Email: user.Email, Password: string(hash)} | ||
|
||
if err := s.authRepo.SaveUser(ctx, &newUser); err != nil { | ||
return TokenResponse{}, err | ||
} | ||
|
||
// アクセストークン生成 | ||
accessToken, err := auth.GenerateAccessToken(newUser.ID) | ||
if err != nil { | ||
return TokenResponse{}, err | ||
} | ||
|
||
// リフレッシュトークン生成 | ||
refreshToken, err := auth.GenerateRefreshToken(newUser.ID) | ||
if err != nil { | ||
return TokenResponse{}, err | ||
} | ||
|
||
// 有効期限を取得 | ||
accessTokenExpiration := time.Now().Add(auth.AccessTokenExpiration).Format(time.RFC3339) | ||
refreshTokenExpiration := time.Now().Add(auth.RefreshTokenExpiration).Format(time.RFC3339) | ||
|
||
tokenRes := TokenResponse{ | ||
AccessToken: accessToken, | ||
RefreshToken: refreshToken, | ||
AccessTokenExpiration: accessTokenExpiration, | ||
RefreshTokenExpiration: refreshTokenExpiration, | ||
} | ||
|
||
return tokenRes, nil | ||
} |
Oops, something went wrong.