1
0
forked from baron/baron-sso

사용자 상태 세분화

This commit is contained in:
2026-05-20 10:17:15 +09:00
parent 9112c4fb36
commit 42b49674cc
33 changed files with 876 additions and 590 deletions

View File

@@ -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,

View File

@@ -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"})