forked from baron/baron-sso
사용자 상태 세분화
This commit is contained in:
@@ -3,6 +3,7 @@ package repository
|
||||
import (
|
||||
"baron-sso-backend/internal/domain"
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
@@ -45,24 +46,21 @@ func (r *userRepository) Create(ctx context.Context, user *domain.User) error {
|
||||
|
||||
func (r *userRepository) Update(ctx context.Context, user *domain.User) error {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// 1. Resolve email conflicts: If another user in the local DB has this email but a different ID,
|
||||
// we must remove the old local record because Kratos is the source of truth for ID <-> Email mapping.
|
||||
var existing domain.User
|
||||
if err := tx.Unscoped().Where("email = ?", user.Email).First(&existing).Error; err == nil {
|
||||
if existing.ID != user.ID {
|
||||
// Delete associated login IDs first to prevent FK constraint violation
|
||||
if strings.EqualFold(strings.TrimSpace(existing.Status), domain.UserStatusArchived) {
|
||||
return fmt.Errorf("email is reserved by archived user: %s", user.Email)
|
||||
}
|
||||
if err := tx.Unscoped().Where("user_id = ?", existing.ID).Delete(&domain.UserLoginID{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
// Different ID holds this email locally. Hard delete the old record to avoid constraint violation.
|
||||
if err := tx.Unscoped().Delete(&domain.User{}, "id = ?", existing.ID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Perform Upsert based on ID.
|
||||
// In GORM v2, true upsert requires Create() with OnConflict on the primary key.
|
||||
return tx.Clauses(clause.OnConflict{
|
||||
Columns: []clause.Column{{Name: "id"}},
|
||||
UpdateAll: true,
|
||||
|
||||
@@ -53,6 +53,36 @@ func TestUserRepository(t *testing.T) {
|
||||
assert.Equal(t, "010-1234-5678", found.Phone)
|
||||
})
|
||||
|
||||
t.Run("Update preserves archived email reservation", func(t *testing.T) {
|
||||
testDB.Exec("DELETE FROM user_login_ids")
|
||||
testDB.Exec("DELETE FROM users")
|
||||
|
||||
archived := &domain.User{
|
||||
ID: "00000000-0000-0000-0000-00000000a001",
|
||||
Email: "reserved@example.com",
|
||||
Name: "Archived User",
|
||||
Role: domain.RoleUser,
|
||||
Status: domain.UserStatusArchived,
|
||||
}
|
||||
replacement := &domain.User{
|
||||
ID: "00000000-0000-0000-0000-00000000a002",
|
||||
Email: "reserved@example.com",
|
||||
Name: "Replacement User",
|
||||
Role: domain.RoleUser,
|
||||
Status: domain.UserStatusActive,
|
||||
}
|
||||
require.NoError(t, repo.Create(ctx, archived))
|
||||
|
||||
err := repo.Update(ctx, replacement)
|
||||
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "archived user")
|
||||
found, err := repo.FindByEmail(ctx, archived.Email)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, archived.ID, found.ID)
|
||||
require.Equal(t, domain.UserStatusArchived, found.Status)
|
||||
})
|
||||
|
||||
t.Run("List Users with Search", func(t *testing.T) {
|
||||
// Add some users
|
||||
_ = repo.Create(ctx, &domain.User{Email: "alice@test.com", Name: "Alice", Role: "user"})
|
||||
|
||||
Reference in New Issue
Block a user