Remove timezone from User model and pass as request parameter
Timezone is now sent from the client on each request that needs it, instead of storing it in the database. This simplifies the model and allows timezone to be dynamic (useful for traveling users). Changes: - Remove timezone field from User entity - Remove timezone from user registration - GET /habits/today now requires ?timezone= query param - Add migration to drop timezone column from database - Update related tests
This commit is contained in:
@@ -48,7 +48,7 @@ func TestDeleteUserHandler_Success(t *testing.T) {
|
||||
|
||||
repo := &mockDeleteUserRepo{
|
||||
findByIDFunc: func(ctx context.Context, id string) (*entities.User, error) {
|
||||
user := entities.NewUser("test@example.com", "hashedPassword", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashedPassword")
|
||||
user.ID = id
|
||||
return user, nil
|
||||
},
|
||||
@@ -112,7 +112,7 @@ func TestDeleteUserHandler_DeleteError(t *testing.T) {
|
||||
|
||||
repo := &mockDeleteUserRepo{
|
||||
findByIDFunc: func(ctx context.Context, id string) (*entities.User, error) {
|
||||
user := entities.NewUser("test@example.com", "hashedPassword", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashedPassword")
|
||||
user.ID = id
|
||||
return user, nil
|
||||
},
|
||||
|
||||
@@ -17,7 +17,6 @@ import (
|
||||
type RegisterUserCommand struct {
|
||||
Email string
|
||||
Password string
|
||||
Timezone string
|
||||
}
|
||||
|
||||
type RegisterUserResult struct {
|
||||
@@ -57,7 +56,7 @@ func (h *RegisterUserHandler) Handle(ctx context.Context, cmd RegisterUserComman
|
||||
return nil, errors.ErrRegistrationClosed
|
||||
}
|
||||
|
||||
if err := validation.ValidateRegistration(cmd.Email, cmd.Password, cmd.Timezone); err != nil {
|
||||
if err := validation.ValidateRegistration(cmd.Email, cmd.Password); err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", errors.ErrInvalidInput, err)
|
||||
}
|
||||
|
||||
@@ -71,7 +70,7 @@ func (h *RegisterUserHandler) Handle(ctx context.Context, cmd RegisterUserComman
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := entities.NewUser(cmd.Email, hashedPassword, cmd.Timezone)
|
||||
user := entities.NewUser(cmd.Email, hashedPassword)
|
||||
|
||||
emailVerificationRequired := false
|
||||
if h.emailService != nil {
|
||||
|
||||
@@ -74,7 +74,6 @@ func TestRegisterUserHandler_Success(t *testing.T) {
|
||||
cmd := RegisterUserCommand{
|
||||
Email: "test@example.com",
|
||||
Password: "Secure123!",
|
||||
Timezone: "UTC",
|
||||
}
|
||||
|
||||
result, err := handler.Handle(context.Background(), cmd)
|
||||
@@ -97,10 +96,6 @@ func TestRegisterUserHandler_Success(t *testing.T) {
|
||||
if createdUser.Email != cmd.Email {
|
||||
t.Errorf("expected email %q, got %q", cmd.Email, createdUser.Email)
|
||||
}
|
||||
|
||||
if createdUser.Timezone != cmd.Timezone {
|
||||
t.Errorf("expected timezone %q, got %q", cmd.Timezone, createdUser.Timezone)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterUserHandler_InvalidEmail(t *testing.T) {
|
||||
@@ -125,7 +120,6 @@ func TestRegisterUserHandler_InvalidEmail(t *testing.T) {
|
||||
cmd := RegisterUserCommand{
|
||||
Email: tt.email,
|
||||
Password: "Secure123!",
|
||||
Timezone: "UTC",
|
||||
}
|
||||
|
||||
_, err := handler.Handle(context.Background(), cmd)
|
||||
@@ -160,38 +154,6 @@ func TestRegisterUserHandler_InvalidPassword(t *testing.T) {
|
||||
cmd := RegisterUserCommand{
|
||||
Email: "test@example.com",
|
||||
Password: tt.password,
|
||||
Timezone: "UTC",
|
||||
}
|
||||
|
||||
_, err := handler.Handle(context.Background(), cmd)
|
||||
if !errors.Is(err, appErrors.ErrInvalidInput) {
|
||||
t.Errorf("expected ErrInvalidInput, got %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegisterUserHandler_InvalidTimezone(t *testing.T) {
|
||||
repo := &mockUserRepo{}
|
||||
hasher := &mockPasswordHasher{}
|
||||
handler := NewRegisterUserHandler(repo, hasher, nil, "", "open", false)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
timezone string
|
||||
}{
|
||||
{"empty timezone", ""},
|
||||
{"invalid timezone", "InvalidTimezone"},
|
||||
{"numeric format", "GMT+1"},
|
||||
{"partial timezone", "America"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cmd := RegisterUserCommand{
|
||||
Email: "test@example.com",
|
||||
Password: "Secure123!",
|
||||
Timezone: tt.timezone,
|
||||
}
|
||||
|
||||
_, err := handler.Handle(context.Background(), cmd)
|
||||
@@ -203,7 +165,7 @@ func TestRegisterUserHandler_InvalidTimezone(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRegisterUserHandler_EmailAlreadyExists(t *testing.T) {
|
||||
existingUser := entities.NewUser("test@example.com", "hashed", "UTC")
|
||||
existingUser := entities.NewUser("test@example.com", "hashed")
|
||||
repo := &mockUserRepo{
|
||||
findByEmailFunc: func(ctx context.Context, email string) (*entities.User, error) {
|
||||
return existingUser, nil
|
||||
@@ -215,7 +177,6 @@ func TestRegisterUserHandler_EmailAlreadyExists(t *testing.T) {
|
||||
cmd := RegisterUserCommand{
|
||||
Email: "test@example.com",
|
||||
Password: "Secure123!",
|
||||
Timezone: "UTC",
|
||||
}
|
||||
|
||||
_, err := handler.Handle(context.Background(), cmd)
|
||||
@@ -237,7 +198,6 @@ func TestRegisterUserHandler_PasswordHashingError(t *testing.T) {
|
||||
cmd := RegisterUserCommand{
|
||||
Email: "test@example.com",
|
||||
Password: "Secure123!",
|
||||
Timezone: "UTC",
|
||||
}
|
||||
|
||||
_, err := handler.Handle(context.Background(), cmd)
|
||||
@@ -259,7 +219,6 @@ func TestRegisterUserHandler_RepositoryError(t *testing.T) {
|
||||
cmd := RegisterUserCommand{
|
||||
Email: "test@example.com",
|
||||
Password: "Secure123!",
|
||||
Timezone: "UTC",
|
||||
}
|
||||
|
||||
_, err := handler.Handle(context.Background(), cmd)
|
||||
@@ -283,7 +242,6 @@ func TestRegisterUserHandler_EdgeCases(t *testing.T) {
|
||||
RegisterUserCommand{
|
||||
Email: "user+tag@example.com",
|
||||
Password: "Secure123!",
|
||||
Timezone: "UTC",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
@@ -292,16 +250,6 @@ func TestRegisterUserHandler_EdgeCases(t *testing.T) {
|
||||
RegisterUserCommand{
|
||||
Email: "user@mail.example.com",
|
||||
Password: "Secure123!",
|
||||
Timezone: "UTC",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"complex timezone",
|
||||
RegisterUserCommand{
|
||||
Email: "user@example.com",
|
||||
Password: "Secure123!",
|
||||
Timezone: "America/Argentina/Buenos_Aires",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
@@ -310,7 +258,6 @@ func TestRegisterUserHandler_EdgeCases(t *testing.T) {
|
||||
RegisterUserCommand{
|
||||
Email: "user@example.com",
|
||||
Password: "Sëcure123!",
|
||||
Timezone: "UTC",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
@@ -319,7 +266,6 @@ func TestRegisterUserHandler_EdgeCases(t *testing.T) {
|
||||
RegisterUserCommand{
|
||||
Email: "user@example.com",
|
||||
Password: "ValidP@ss1" + string(make([]byte, 100)),
|
||||
Timezone: "UTC",
|
||||
},
|
||||
nil,
|
||||
},
|
||||
@@ -342,7 +288,6 @@ func TestRegisterUserHandler_ClosedRegistration(t *testing.T) {
|
||||
cmd := RegisterUserCommand{
|
||||
Email: "test@example.com",
|
||||
Password: "Secure123!",
|
||||
Timezone: "UTC",
|
||||
}
|
||||
|
||||
_, err := handler.Handle(context.Background(), cmd)
|
||||
|
||||
@@ -98,7 +98,7 @@ func (m *mockResetPasswordHasher) Compare(hashedPassword, password string) error
|
||||
}
|
||||
|
||||
func TestResetPasswordHandler_Success(t *testing.T) {
|
||||
user := entities.NewUser("test@example.com", "old_hash", "UTC")
|
||||
user := entities.NewUser("test@example.com", "old_hash")
|
||||
user.ID = "user-123"
|
||||
|
||||
resetToken := entities.NewPasswordResetToken(
|
||||
@@ -340,7 +340,7 @@ func TestResetPasswordHandler_UserNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestResetPasswordHandler_HashingError(t *testing.T) {
|
||||
user := entities.NewUser("test@example.com", "old_hash", "UTC")
|
||||
user := entities.NewUser("test@example.com", "old_hash")
|
||||
user.ID = "user-123"
|
||||
|
||||
resetToken := entities.NewPasswordResetToken(
|
||||
|
||||
@@ -67,7 +67,7 @@ func TestVerifyEmailHandler_Success(t *testing.T) {
|
||||
token := "valid-token"
|
||||
expiry := time.Now().Add(24 * time.Hour)
|
||||
|
||||
user := entities.NewUser("test@example.com", "hashedPassword", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashedPassword")
|
||||
user.ID = "user-123"
|
||||
user.EmailVerified = false
|
||||
user.EmailVerificationToken = &token
|
||||
@@ -148,7 +148,7 @@ func TestVerifyEmailHandler_AlreadyVerified(t *testing.T) {
|
||||
token := "valid-token"
|
||||
expiry := time.Now().Add(24 * time.Hour)
|
||||
|
||||
user := entities.NewUser("test@example.com", "hashedPassword", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashedPassword")
|
||||
user.ID = "user-123"
|
||||
user.EmailVerified = true
|
||||
user.EmailVerificationToken = &token
|
||||
@@ -176,7 +176,7 @@ func TestVerifyEmailHandler_ExpiredToken(t *testing.T) {
|
||||
token := "expired-token"
|
||||
expiry := time.Now().Add(-1 * time.Hour)
|
||||
|
||||
user := entities.NewUser("test@example.com", "hashedPassword", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashedPassword")
|
||||
user.ID = "user-123"
|
||||
user.EmailVerified = false
|
||||
user.EmailVerificationToken = &token
|
||||
@@ -203,7 +203,7 @@ func TestVerifyEmailHandler_ExpiredToken(t *testing.T) {
|
||||
func TestVerifyEmailHandler_NilExpiry(t *testing.T) {
|
||||
token := "valid-token"
|
||||
|
||||
user := entities.NewUser("test@example.com", "hashedPassword", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashedPassword")
|
||||
user.ID = "user-123"
|
||||
user.EmailVerified = false
|
||||
user.EmailVerificationToken = &token
|
||||
@@ -231,7 +231,7 @@ func TestVerifyEmailHandler_WithWelcomeEmail(t *testing.T) {
|
||||
token := "valid-token"
|
||||
expiry := time.Now().Add(24 * time.Hour)
|
||||
|
||||
user := entities.NewUser("test@example.com", "hashedPassword", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashedPassword")
|
||||
user.ID = "user-123"
|
||||
user.EmailVerified = false
|
||||
user.EmailVerificationToken = &token
|
||||
@@ -277,7 +277,7 @@ func TestVerifyEmailHandler_WithoutWelcomeEmail(t *testing.T) {
|
||||
token := "valid-token"
|
||||
expiry := time.Now().Add(24 * time.Hour)
|
||||
|
||||
user := entities.NewUser("test@example.com", "hashedPassword", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashedPassword")
|
||||
user.ID = "user-123"
|
||||
user.EmailVerified = false
|
||||
user.EmailVerificationToken = &token
|
||||
@@ -310,7 +310,7 @@ func TestVerifyEmailHandler_UpdateError(t *testing.T) {
|
||||
token := "valid-token"
|
||||
expiry := time.Now().Add(24 * time.Hour)
|
||||
|
||||
user := entities.NewUser("test@example.com", "hashedPassword", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashedPassword")
|
||||
user.ID = "user-123"
|
||||
user.EmailVerified = false
|
||||
user.EmailVerificationToken = &token
|
||||
|
||||
@@ -14,9 +14,8 @@ type LoginUserQuery struct {
|
||||
}
|
||||
|
||||
type LoginUserResult struct {
|
||||
UserID string
|
||||
Email string
|
||||
Timezone string
|
||||
UserID string
|
||||
Email string
|
||||
}
|
||||
|
||||
type LoginUserHandler struct {
|
||||
@@ -50,8 +49,7 @@ func (h *LoginUserHandler) Handle(ctx context.Context, query LoginUserQuery) (*L
|
||||
}
|
||||
|
||||
return &LoginUserResult{
|
||||
UserID: user.ID,
|
||||
Email: user.Email,
|
||||
Timezone: user.Timezone,
|
||||
UserID: user.ID,
|
||||
Email: user.Email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func (m *mockLoginPasswordHasher) Compare(hashedPassword, password string) error
|
||||
}
|
||||
|
||||
func TestLoginUserHandler_Success(t *testing.T) {
|
||||
user := entities.NewUser("test@example.com", "hashed_password", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashed_password")
|
||||
user.ID = "user-123"
|
||||
user.EmailVerified = true
|
||||
|
||||
@@ -150,7 +150,7 @@ func TestLoginUserHandler_UserNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoginUserHandler_InvalidPassword(t *testing.T) {
|
||||
user := entities.NewUser("test@example.com", "hashed_password", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashed_password")
|
||||
user.ID = "user-123"
|
||||
user.EmailVerified = true
|
||||
|
||||
@@ -180,7 +180,7 @@ func TestLoginUserHandler_InvalidPassword(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoginUserHandler_EmailNotVerified(t *testing.T) {
|
||||
user := entities.NewUser("test@example.com", "hashed_password", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hashed_password")
|
||||
user.ID = "user-123"
|
||||
user.EmailVerified = false
|
||||
|
||||
|
||||
@@ -17,9 +17,8 @@ type RefreshTokenQuery struct {
|
||||
}
|
||||
|
||||
type RefreshTokenResult struct {
|
||||
UserID string
|
||||
Email string
|
||||
Timezone string
|
||||
UserID string
|
||||
Email string
|
||||
}
|
||||
|
||||
type RefreshTokenHandler struct {
|
||||
@@ -57,9 +56,8 @@ func (h *RefreshTokenHandler) Handle(ctx context.Context, query RefreshTokenQuer
|
||||
}
|
||||
|
||||
return &RefreshTokenResult{
|
||||
UserID: user.ID,
|
||||
Email: user.Email,
|
||||
Timezone: user.Timezone,
|
||||
UserID: user.ID,
|
||||
Email: user.Email,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestRefreshTokenHandler_Success(t *testing.T) {
|
||||
|
||||
userRepo := &mockUserRepositoryForRefresh{
|
||||
findByIDFunc: func(ctx context.Context, id string) (*entities.User, error) {
|
||||
user := entities.NewUser("test@example.com", "hash", "UTC")
|
||||
user := entities.NewUser("test@example.com", "hash")
|
||||
user.ID = id
|
||||
return user, nil
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user