initial commit
This commit is contained in:
88
internal/repository/postgres/auth/account.go
Normal file
88
internal/repository/postgres/auth/account.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
|
||||
domainAuth "base/internal/domain/auth"
|
||||
)
|
||||
|
||||
type accountRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewAccountRepository(lc fx.Lifecycle, db *gorm.DB) domainAuth.AccountRepository {
|
||||
lc.Append(
|
||||
fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
return nil
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return &accountRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *accountRepository) Create(ctx context.Context, account *domainAuth.Account) error {
|
||||
model := toAccountModel(account)
|
||||
if err := r.db.WithContext(ctx).Create(model).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copyAccountFromModel(account, model)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *accountRepository) FindByID(ctx context.Context, id uuid.UUID) (*domainAuth.Account, error) {
|
||||
var model AccountModel
|
||||
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toAccountDomain(&model), nil
|
||||
}
|
||||
|
||||
func (r *accountRepository) FindByUserID(ctx context.Context, userID uuid.UUID) ([]*domainAuth.Account, error) {
|
||||
var models []AccountModel
|
||||
if err := r.db.WithContext(ctx).Where("user_id = ?", userID).Find(&models).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accounts := make([]*domainAuth.Account, len(models))
|
||||
for i, model := range models {
|
||||
accounts[i] = toAccountDomain(&model)
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (r *accountRepository) Update(ctx context.Context, account *domainAuth.Account) error {
|
||||
model := toAccountModel(account)
|
||||
return r.db.WithContext(ctx).Model(&AccountModel{}).Where("id = ?", account.ID).Updates(model).Error
|
||||
}
|
||||
|
||||
func (r *accountRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&AccountModel{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *accountRepository) List(ctx context.Context, limit, offset int) ([]*domainAuth.Account, error) {
|
||||
var models []AccountModel
|
||||
if err := r.db.WithContext(ctx).Limit(limit).Offset(offset).Find(&models).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accounts := make([]*domainAuth.Account, len(models))
|
||||
for i, model := range models {
|
||||
accounts[i] = toAccountDomain(&model)
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (r *accountRepository) Count(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&AccountModel{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
381
internal/repository/postgres/auth/account_test.go
Normal file
381
internal/repository/postgres/auth/account_test.go
Normal file
@@ -0,0 +1,381 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
domainAuth "base/internal/domain/auth"
|
||||
"base/internal/pkg/oauth"
|
||||
)
|
||||
|
||||
func TestAccountRepository_Create(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestAccountRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("create account successfully", func(t *testing.T) {
|
||||
// Create user first
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Account",
|
||||
LastName: "User",
|
||||
Email: "account@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
account := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Google,
|
||||
Password: nil,
|
||||
Scope: []string{"read", "write"},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err = repo.Create(ctx, account)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, uuid.Nil, account.ID)
|
||||
|
||||
// Verify account was created
|
||||
found, err := repo.FindByID(ctx, account.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, account.UserID, found.UserID)
|
||||
assert.Equal(t, account.Provider, found.Provider)
|
||||
assert.Equal(t, account.Scope, found.Scope)
|
||||
})
|
||||
|
||||
t.Run("create account with password", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Password",
|
||||
LastName: "User",
|
||||
Email: "password@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
password := "hashedpassword"
|
||||
account := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Credentials,
|
||||
Password: &password,
|
||||
Scope: []string{},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err = repo.Create(ctx, account)
|
||||
assert.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(ctx, account.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, found.Password)
|
||||
assert.Equal(t, password, *found.Password)
|
||||
})
|
||||
|
||||
t.Run("create account with meta", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Meta",
|
||||
LastName: "User",
|
||||
Email: "meta@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
metaJSON := json.RawMessage(`{"key": "value", "number": 123}`)
|
||||
account := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Google,
|
||||
Meta: metaJSON,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err = repo.Create(ctx, account)
|
||||
assert.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(ctx, account.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, found.Meta)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccountRepository_FindByID(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestAccountRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("find existing account by id", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Find",
|
||||
LastName: "User",
|
||||
Email: "find@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
account := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Google,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = repo.Create(ctx, account)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(ctx, account.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, account.ID, found.ID)
|
||||
assert.Equal(t, account.UserID, found.UserID)
|
||||
})
|
||||
|
||||
t.Run("find non-existent account", func(t *testing.T) {
|
||||
nonExistentID := uuid.New()
|
||||
found, err := repo.FindByID(ctx, nonExistentID)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, found)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccountRepository_FindByUserID(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestAccountRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("find accounts by user id", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Multi",
|
||||
LastName: "Account",
|
||||
Email: "multi@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create multiple accounts
|
||||
account1 := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Google,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = repo.Create(ctx, account1)
|
||||
require.NoError(t, err)
|
||||
|
||||
account2 := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.GitHub,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = repo.Create(ctx, account2)
|
||||
require.NoError(t, err)
|
||||
|
||||
accounts, err := repo.FindByUserID(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, accounts, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccountRepository_Update(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestAccountRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("update account successfully", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Update",
|
||||
LastName: "User",
|
||||
Email: "update@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
account := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Google,
|
||||
Scope: []string{"read"},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = repo.Create(ctx, account)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Update account
|
||||
account.Scope = []string{"read", "write", "admin"}
|
||||
newToken := "newtoken"
|
||||
account.AccessToken = &newToken
|
||||
|
||||
err = repo.Update(ctx, account)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify update
|
||||
found, err := repo.FindByID(ctx, account.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"read", "write", "admin"}, found.Scope)
|
||||
assert.NotNil(t, found.AccessToken)
|
||||
assert.Equal(t, newToken, *found.AccessToken)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccountRepository_Delete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestAccountRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("delete account successfully", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Delete",
|
||||
LastName: "User",
|
||||
Email: "delete@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
account := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Google,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = repo.Create(ctx, account)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Delete(ctx, account.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify deletion
|
||||
found, err := repo.FindByID(ctx, account.ID)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, found)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccountRepository_List(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestAccountRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create user and multiple accounts
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "List",
|
||||
LastName: "User",
|
||||
Email: "list@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
account := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Provider(i % 4), // Cycle through providers
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := repo.Create(ctx, account)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
t.Run("list accounts with limit and offset", func(t *testing.T) {
|
||||
accounts, err := repo.List(ctx, 3, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, accounts, 3)
|
||||
|
||||
accounts, err = repo.List(ctx, 3, 3)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, accounts, 2) // Remaining 2 accounts
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccountRepository_Count(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestAccountRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("count accounts", func(t *testing.T) {
|
||||
initialCount, err := repo.Count(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), initialCount)
|
||||
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Count",
|
||||
LastName: "User",
|
||||
Email: "count@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create accounts
|
||||
for i := 0; i < 3; i++ {
|
||||
account := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Google,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := repo.Create(ctx, account)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
count, err := repo.Count(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), count)
|
||||
})
|
||||
}
|
||||
184
internal/repository/postgres/auth/mapper.go
Normal file
184
internal/repository/postgres/auth/mapper.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
domainAuth "base/internal/domain/auth"
|
||||
"base/internal/pkg/oauth"
|
||||
)
|
||||
|
||||
func toUserModel(user *domainAuth.User) *UserModel {
|
||||
// Note: DisplayName exists in schema but not in domain model
|
||||
// Compute it from FirstName + LastName
|
||||
displayName := user.FirstName + " " + user.LastName
|
||||
return &UserModel{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
DisplayName: displayName,
|
||||
PhoneNumber: user.PhoneNumber,
|
||||
Email: user.Email,
|
||||
EmailVerified: user.EmailVerified,
|
||||
Status: int(user.Status),
|
||||
InvitationCode: user.InvitationCode,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
DeletedAt: gorm.DeletedAt{Time: user.DeletedAt, Valid: !user.DeletedAt.IsZero()},
|
||||
}
|
||||
}
|
||||
|
||||
func toUserDomain(model *UserModel) *domainAuth.User {
|
||||
var deletedAt time.Time
|
||||
if model.DeletedAt.Valid {
|
||||
deletedAt = model.DeletedAt.Time
|
||||
}
|
||||
return &domainAuth.User{
|
||||
ID: model.ID,
|
||||
FirstName: model.FirstName,
|
||||
LastName: model.LastName,
|
||||
PhoneNumber: model.PhoneNumber,
|
||||
Email: model.Email,
|
||||
EmailVerified: model.EmailVerified,
|
||||
Status: domainAuth.UserStatus(model.Status),
|
||||
InvitationCode: model.InvitationCode,
|
||||
CreatedAt: model.CreatedAt,
|
||||
UpdatedAt: model.UpdatedAt,
|
||||
DeletedAt: deletedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func copyUserFromModel(user *domainAuth.User, model *UserModel) {
|
||||
user.ID = model.ID
|
||||
user.PhoneNumber = model.PhoneNumber
|
||||
user.EmailVerified = model.EmailVerified
|
||||
user.Status = domainAuth.UserStatus(model.Status)
|
||||
user.InvitationCode = model.InvitationCode
|
||||
user.CreatedAt = model.CreatedAt
|
||||
user.UpdatedAt = model.UpdatedAt
|
||||
if model.DeletedAt.Valid {
|
||||
user.DeletedAt = model.DeletedAt.Time
|
||||
}
|
||||
}
|
||||
|
||||
func toRoleModel(role *domainAuth.Role) *RoleModel {
|
||||
desc := &role.Description
|
||||
if role.Description == "" {
|
||||
desc = nil
|
||||
}
|
||||
return &RoleModel{
|
||||
ID: role.ID,
|
||||
Name: role.Name,
|
||||
Description: desc,
|
||||
CreatedAt: role.CreatedAt,
|
||||
UpdatedAt: role.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func toRoleDomain(model *RoleModel) *domainAuth.Role {
|
||||
desc := ""
|
||||
if model.Description != nil {
|
||||
desc = *model.Description
|
||||
}
|
||||
return &domainAuth.Role{
|
||||
ID: model.ID,
|
||||
Name: model.Name,
|
||||
Description: desc,
|
||||
CreatedAt: model.CreatedAt,
|
||||
UpdatedAt: model.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func copyRoleFromModel(role *domainAuth.Role, model *RoleModel) error {
|
||||
role.ID = model.ID
|
||||
role.CreatedAt = model.CreatedAt
|
||||
role.UpdatedAt = model.UpdatedAt
|
||||
return nil
|
||||
}
|
||||
|
||||
func toAccountModel(account *domainAuth.Account) *AccountModel {
|
||||
var scopeStr *string
|
||||
if len(account.Scope) > 0 {
|
||||
scopeBytes, _ := json.Marshal(account.Scope)
|
||||
s := string(scopeBytes)
|
||||
scopeStr = &s
|
||||
}
|
||||
|
||||
// Store provider in Meta JSONB field
|
||||
metaMap := make(map[string]interface{})
|
||||
if len(account.Meta) > 0 {
|
||||
_ = json.Unmarshal(account.Meta, &metaMap)
|
||||
}
|
||||
metaMap["provider"] = int(account.Provider)
|
||||
metaBytes, _ := json.Marshal(metaMap)
|
||||
meta := json.RawMessage(metaBytes)
|
||||
|
||||
return &AccountModel{
|
||||
ID: account.ID,
|
||||
UserID: account.UserID,
|
||||
Provider: int(account.Provider), // Store provider as column for querying
|
||||
Password: account.Password,
|
||||
AccessToken: account.AccessToken,
|
||||
RefreshToken: account.RefreshToken,
|
||||
Scope: scopeStr,
|
||||
Meta: &meta,
|
||||
CreatedAt: account.CreatedAt,
|
||||
UpdatedAt: account.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func toAccountDomain(model *AccountModel) *domainAuth.Account {
|
||||
var scope []string
|
||||
if model.Scope != nil {
|
||||
_ = json.Unmarshal([]byte(*model.Scope), &scope)
|
||||
}
|
||||
|
||||
var meta json.RawMessage
|
||||
var provider int
|
||||
|
||||
// Use Provider field if available (for querying), otherwise extract from Meta
|
||||
if model.Provider > 0 {
|
||||
provider = model.Provider
|
||||
}
|
||||
|
||||
if model.Meta != nil {
|
||||
meta = *model.Meta
|
||||
// If provider not set from field, try to extract from Meta
|
||||
if provider == 0 {
|
||||
var metaMap map[string]interface{}
|
||||
if err := json.Unmarshal(meta, &metaMap); err == nil {
|
||||
if p, ok := metaMap["provider"].(float64); ok {
|
||||
provider = int(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Import oauth package for Provider type
|
||||
// Provider is stored as int, convert to oauth.Provider
|
||||
var accountProvider oauth.Provider
|
||||
if provider > 0 {
|
||||
accountProvider = oauth.Provider(provider)
|
||||
}
|
||||
|
||||
return &domainAuth.Account{
|
||||
ID: model.ID,
|
||||
UserID: model.UserID,
|
||||
Provider: accountProvider,
|
||||
Password: model.Password,
|
||||
AccessToken: model.AccessToken,
|
||||
RefreshToken: model.RefreshToken,
|
||||
Scope: scope,
|
||||
Meta: meta,
|
||||
CreatedAt: model.CreatedAt,
|
||||
UpdatedAt: model.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
func copyAccountFromModel(account *domainAuth.Account, model *AccountModel) {
|
||||
account.ID = model.ID
|
||||
account.CreatedAt = model.CreatedAt
|
||||
account.UpdatedAt = model.UpdatedAt
|
||||
}
|
||||
81
internal/repository/postgres/auth/role.go
Normal file
81
internal/repository/postgres/auth/role.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/fx"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
|
||||
domainAuth "base/internal/domain/auth"
|
||||
)
|
||||
|
||||
type roleRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewRoleRepository(lc fx.Lifecycle, db *gorm.DB) domainAuth.RoleRepository {
|
||||
lc.Append(
|
||||
fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
return db.AutoMigrate(&domainAuth.Role{})
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return &roleRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *roleRepository) Create(ctx context.Context, role *domainAuth.Role) error {
|
||||
model := toRoleModel(role)
|
||||
if err := r.db.WithContext(ctx).Create(model).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return copyRoleFromModel(role, model)
|
||||
}
|
||||
|
||||
func (r *roleRepository) FindByID(ctx context.Context, id uuid.UUID) (*domainAuth.Role, error) {
|
||||
var model RoleModel
|
||||
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toRoleDomain(&model), nil
|
||||
}
|
||||
|
||||
func (r *roleRepository) FindByName(ctx context.Context, name string) (*domainAuth.Role, error) {
|
||||
var model RoleModel
|
||||
if err := r.db.WithContext(ctx).Where("name = ?", name).First(&model).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return toRoleDomain(&model), nil
|
||||
}
|
||||
|
||||
func (r *roleRepository) Update(ctx context.Context, role *domainAuth.Role) error {
|
||||
model := toRoleModel(role)
|
||||
return r.db.WithContext(ctx).Model(&RoleModel{}).Where("id = ?", role.ID).Updates(model).Error
|
||||
}
|
||||
|
||||
func (r *roleRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&RoleModel{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *roleRepository) List(ctx context.Context, limit, offset int) ([]*domainAuth.Role, error) {
|
||||
var models []RoleModel
|
||||
if err := r.db.WithContext(ctx).Limit(limit).Offset(offset).Find(&models).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roles := make([]*domainAuth.Role, len(models))
|
||||
for i, model := range models {
|
||||
roles[i] = toRoleDomain(&model)
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (r *roleRepository) Count(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&RoleModel{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
235
internal/repository/postgres/auth/role_test.go
Normal file
235
internal/repository/postgres/auth/role_test.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
domainAuth "base/internal/domain/auth"
|
||||
)
|
||||
|
||||
func TestRoleRepository_Create(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("create role successfully", func(t *testing.T) {
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "admin",
|
||||
Description: "Administrator role",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, role)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, uuid.Nil, role.ID)
|
||||
|
||||
// Verify role was created
|
||||
found, err := repo.FindByID(ctx, role.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, role.Name, found.Name)
|
||||
assert.Equal(t, role.Description, found.Description)
|
||||
})
|
||||
|
||||
t.Run("create role with duplicate name fails", func(t *testing.T) {
|
||||
name := "duplicate"
|
||||
role1 := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: name,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, role1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
role2 := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: name,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err = repo.Create(ctx, role2)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRoleRepository_FindByID(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("find existing role by id", func(t *testing.T) {
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "find",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(ctx, role.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, role.ID, found.ID)
|
||||
assert.Equal(t, role.Name, found.Name)
|
||||
})
|
||||
|
||||
t.Run("find non-existent role", func(t *testing.T) {
|
||||
nonExistentID := uuid.New()
|
||||
found, err := repo.FindByID(ctx, nonExistentID)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, found)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRoleRepository_FindByName(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("find existing role by name", func(t *testing.T) {
|
||||
name := "findbyname"
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: name,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByName(ctx, name)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, role.ID, found.ID)
|
||||
assert.Equal(t, name, found.Name)
|
||||
})
|
||||
|
||||
t.Run("find non-existent role by name", func(t *testing.T) {
|
||||
found, err := repo.FindByName(ctx, "nonexistent")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, found)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRoleRepository_Update(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("update role successfully", func(t *testing.T) {
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "update",
|
||||
Description: "Original description",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Update role
|
||||
role.Description = "Updated description"
|
||||
|
||||
err = repo.Update(ctx, role)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify update
|
||||
found, err := repo.FindByID(ctx, role.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Updated description", found.Description)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRoleRepository_Delete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("delete role successfully", func(t *testing.T) {
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "delete",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Delete(ctx, role.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify deletion (soft delete)
|
||||
found, err := repo.FindByID(ctx, role.ID)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, found)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRoleRepository_List(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create multiple roles
|
||||
for i := 0; i < 5; i++ {
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "role" + strconv.Itoa(i),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := repo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
t.Run("list roles with limit and offset", func(t *testing.T) {
|
||||
roles, err := repo.List(ctx, 3, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, roles, 3)
|
||||
|
||||
roles, err = repo.List(ctx, 3, 3)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, roles, 2) // Remaining 2 roles
|
||||
})
|
||||
}
|
||||
|
||||
func TestRoleRepository_Count(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("count roles", func(t *testing.T) {
|
||||
initialCount, err := repo.Count(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), initialCount)
|
||||
|
||||
// Create roles
|
||||
for i := 0; i < 3; i++ {
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "count" + strconv.Itoa(i),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := repo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
count, err := repo.Count(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), count)
|
||||
})
|
||||
}
|
||||
70
internal/repository/postgres/auth/schema.go
Normal file
70
internal/repository/postgres/auth/schema.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type UserModel struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
|
||||
FirstName string `gorm:"column:first_name;type:text;not null"`
|
||||
LastName string `gorm:"column:last_name;type:text;not null"`
|
||||
DisplayName string `gorm:"column:display_name;type:text;not null"`
|
||||
PhoneNumber string `gorm:"column:phone_number;type:text"`
|
||||
Email string `gorm:"column:email;type:text;not null;uniqueIndex:users_email_unique"`
|
||||
EmailVerified bool `gorm:"column:email_verified;type:boolean;default:false;not null"`
|
||||
Status int `gorm:"column:status;type:integer;default:0;not null"`
|
||||
InvitationCode string `gorm:"column:invitation_code;type:text"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamptz;not null"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamptz;index"`
|
||||
}
|
||||
|
||||
func (UserModel) TableName() string {
|
||||
return "users"
|
||||
}
|
||||
|
||||
type RoleModel struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
|
||||
Name string `gorm:"column:name;type:text;not null;uniqueIndex:roles_name_unique"`
|
||||
Description *string `gorm:"column:description;type:text"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamptz;not null"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamptz;index"`
|
||||
}
|
||||
|
||||
func (RoleModel) TableName() string {
|
||||
return "roles"
|
||||
}
|
||||
|
||||
type AccountModel struct {
|
||||
ID uuid.UUID `gorm:"type:uuid;primaryKey;default:gen_random_uuid()"`
|
||||
UserID uuid.UUID `gorm:"column:user_id;type:uuid;not null;index:accounts_user_id_idx"`
|
||||
Provider int `gorm:"column:provider;type:integer;index:accounts_provider_idx"` // For querying, also stored in meta
|
||||
Password *string `gorm:"column:password;type:text"`
|
||||
AccessToken *string `gorm:"column:access_token;type:text"`
|
||||
RefreshToken *string `gorm:"column:refresh_token;type:text"`
|
||||
Scope *string `gorm:"column:scope;type:text"`
|
||||
Meta *json.RawMessage `gorm:"column:meta;type:jsonb"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamptz;not null"`
|
||||
}
|
||||
|
||||
func (AccountModel) TableName() string {
|
||||
return "accounts"
|
||||
}
|
||||
|
||||
type UserRoleModel struct {
|
||||
UserID uuid.UUID `gorm:"column:user_id;type:uuid;not null;index:user_roles_user_id_idx"`
|
||||
RoleID uuid.UUID `gorm:"column:role_id;type:uuid;not null;index:user_roles_role_id_idx"`
|
||||
CreatedAt time.Time `gorm:"column:created_at;type:timestamptz;not null"`
|
||||
UpdatedAt time.Time `gorm:"column:updated_at;type:timestamptz;not null"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"column:deleted_at;type:timestamptz;index"`
|
||||
}
|
||||
|
||||
func (UserRoleModel) TableName() string {
|
||||
return "user_roles"
|
||||
}
|
||||
108
internal/repository/postgres/auth/test_helper.go
Normal file
108
internal/repository/postgres/auth/test_helper.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
|
||||
domainAuth "base/internal/domain/auth"
|
||||
)
|
||||
|
||||
// setupTestDB creates an in-memory SQLite database for testing
|
||||
func setupTestDB(t *testing.T) *gorm.DB {
|
||||
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
||||
DisableForeignKeyConstraintWhenMigrating: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create tables manually with SQLite-compatible syntax
|
||||
// This avoids PostgreSQL-specific syntax like gen_random_uuid() and timestamptz
|
||||
|
||||
createUsersTable := `
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY,
|
||||
first_name TEXT NOT NULL,
|
||||
last_name TEXT NOT NULL,
|
||||
display_name TEXT NOT NULL,
|
||||
phone_number TEXT,
|
||||
email TEXT NOT NULL,
|
||||
email_verified INTEGER NOT NULL DEFAULT 0,
|
||||
status INTEGER NOT NULL DEFAULT 0,
|
||||
invitation_code TEXT,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
deleted_at DATETIME,
|
||||
UNIQUE(email)
|
||||
)
|
||||
`
|
||||
require.NoError(t, db.Exec(createUsersTable).Error)
|
||||
|
||||
createRolesTable := `
|
||||
CREATE TABLE IF NOT EXISTS roles (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
deleted_at DATETIME,
|
||||
UNIQUE(name)
|
||||
)
|
||||
`
|
||||
require.NoError(t, db.Exec(createRolesTable).Error)
|
||||
|
||||
createAccountsTable := `
|
||||
CREATE TABLE IF NOT EXISTS accounts (
|
||||
id TEXT PRIMARY KEY,
|
||||
user_id TEXT NOT NULL,
|
||||
provider INTEGER,
|
||||
password TEXT,
|
||||
access_token TEXT,
|
||||
refresh_token TEXT,
|
||||
scope TEXT,
|
||||
meta TEXT,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL
|
||||
)
|
||||
`
|
||||
require.NoError(t, db.Exec(createAccountsTable).Error)
|
||||
require.NoError(t, db.Exec("CREATE INDEX IF NOT EXISTS accounts_user_id_idx ON accounts(user_id)").Error)
|
||||
require.NoError(t, db.Exec("CREATE INDEX IF NOT EXISTS accounts_provider_idx ON accounts(provider)").Error)
|
||||
|
||||
createUserRolesTable := `
|
||||
CREATE TABLE IF NOT EXISTS user_roles (
|
||||
user_id TEXT NOT NULL,
|
||||
role_id TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL,
|
||||
updated_at DATETIME NOT NULL,
|
||||
deleted_at DATETIME,
|
||||
PRIMARY KEY (user_id, role_id)
|
||||
)
|
||||
`
|
||||
require.NoError(t, db.Exec(createUserRolesTable).Error)
|
||||
require.NoError(t, db.Exec("CREATE INDEX IF NOT EXISTS user_roles_user_id_idx ON user_roles(user_id)").Error)
|
||||
require.NoError(t, db.Exec("CREATE INDEX IF NOT EXISTS user_roles_role_id_idx ON user_roles(role_id)").Error)
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
// createTestUserRepository creates a user repository for testing
|
||||
func createTestUserRepository(db *gorm.DB) domainAuth.UserRepository {
|
||||
return &userRepository{db: db}
|
||||
}
|
||||
|
||||
// createTestRoleRepository creates a role repository for testing
|
||||
func createTestRoleRepository(db *gorm.DB) domainAuth.RoleRepository {
|
||||
return &roleRepository{db: db}
|
||||
}
|
||||
|
||||
// createTestAccountRepository creates an account repository for testing
|
||||
func createTestAccountRepository(db *gorm.DB) domainAuth.AccountRepository {
|
||||
return &accountRepository{db: db}
|
||||
}
|
||||
|
||||
// createTestUserRoleRepository creates a user role repository for testing
|
||||
func createTestUserRoleRepository(db *gorm.DB) domainAuth.UserRoleRepository {
|
||||
return &userRoleRepository{db: db}
|
||||
}
|
||||
430
internal/repository/postgres/auth/user.go
Normal file
430
internal/repository/postgres/auth/user.go
Normal file
@@ -0,0 +1,430 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/fx"
|
||||
"gorm.io/gorm"
|
||||
|
||||
domainAuth "base/internal/domain/auth"
|
||||
)
|
||||
|
||||
type userRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUserRepository(lc fx.Lifecycle, db *gorm.DB) domainAuth.UserRepository {
|
||||
lc.Append(
|
||||
fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
return nil
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return &userRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *userRepository) Create(ctx context.Context, user *domainAuth.User) error {
|
||||
model := toUserModel(user)
|
||||
|
||||
if err := r.db.WithContext(ctx).Create(model).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copyUserFromModel(user, model)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *userRepository) CreateWithAccount(ctx context.Context, user *domainAuth.User, account *domainAuth.Account) error {
|
||||
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// Create user within transaction
|
||||
userModel := toUserModel(user)
|
||||
if err := tx.WithContext(ctx).Create(userModel).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copyUserFromModel(user, userModel)
|
||||
|
||||
// Create account within transaction
|
||||
accountModel := toAccountModel(account)
|
||||
if err := tx.WithContext(ctx).Create(accountModel).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copyAccountFromModel(account, accountModel)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (r *userRepository) UpsertWithAccount(ctx context.Context, email string, user *domainAuth.User, account *domainAuth.Account) (bool, error) {
|
||||
isNewUser := false
|
||||
|
||||
err := r.db.WithContext(ctx).Transaction(
|
||||
func(tx *gorm.DB) error {
|
||||
// Check if user exists by email
|
||||
var existingUserModel UserModel
|
||||
err := tx.WithContext(ctx).Where("email = ?", email).First(&existingUserModel).Error
|
||||
if err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
isNewUser = true
|
||||
userModel := toUserModel(user)
|
||||
|
||||
if err = tx.WithContext(ctx).Create(userModel).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copyUserFromModel(user, userModel)
|
||||
|
||||
account.UserID = user.ID
|
||||
|
||||
// Create account for new user
|
||||
accountModel := toAccountModel(account)
|
||||
if err = tx.WithContext(ctx).Create(accountModel).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
copyAccountFromModel(account, accountModel)
|
||||
}
|
||||
|
||||
// TODO: check no error if user exist because in find user accounts we use user.ID
|
||||
if !isNewUser {
|
||||
// Load all accounts for this user to check if one with this provider exists
|
||||
var existingAccountModel AccountModel
|
||||
findAccountsErr := tx.WithContext(ctx).
|
||||
Where("user_id = ? AND provider = ?", user.ID, int(account.Provider)).
|
||||
First(&existingAccountModel).Error
|
||||
if findAccountsErr != nil {
|
||||
if !errors.Is(findAccountsErr, gorm.ErrRecordNotFound) {
|
||||
return findAccountsErr
|
||||
}
|
||||
|
||||
accountModel := toAccountModel(account)
|
||||
if err = tx.WithContext(ctx).Create(accountModel).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
copyAccountFromModel(account, accountModel)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
accountModel := toAccountModel(account)
|
||||
updateAccountErr := tx.WithContext(ctx).
|
||||
Model(&AccountModel{}).
|
||||
Where("id = ?", existingAccountModel.ID).
|
||||
Updates(accountModel).Error
|
||||
if updateAccountErr != nil {
|
||||
return updateAccountErr
|
||||
}
|
||||
|
||||
copyAccountFromModel(account, accountModel)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
return isNewUser, err
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByID(ctx context.Context, id uuid.UUID, opts ...domainAuth.UserQueryOption) (*domainAuth.User, error) {
|
||||
// Parse query options
|
||||
options := &domainAuth.UserQueryOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
var model UserModel
|
||||
if err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := toUserDomain(&model)
|
||||
|
||||
// Conditionally load relations based on options
|
||||
if options.LoadRoles {
|
||||
roles, err := r.loadUserRoles(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Roles = roles
|
||||
}
|
||||
|
||||
if options.LoadAccounts {
|
||||
accounts, err := r.loadUserAccounts(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Accounts = accounts
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) FindByEmail(ctx context.Context, email string, opts ...domainAuth.UserQueryOption) (*domainAuth.User, error) {
|
||||
// Parse query options
|
||||
options := &domainAuth.UserQueryOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
var model UserModel
|
||||
if err := r.db.WithContext(ctx).Where("email = ?", email).First(&model).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
user := toUserDomain(&model)
|
||||
|
||||
// Conditionally load relations based on options
|
||||
if options.LoadRoles {
|
||||
roles, err := r.loadUserRoles(ctx, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Roles = roles
|
||||
} else {
|
||||
user.Roles = []domainAuth.Role{}
|
||||
}
|
||||
|
||||
if options.LoadAccounts {
|
||||
accounts, err := r.loadUserAccounts(ctx, user.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.Accounts = accounts
|
||||
} else {
|
||||
user.Accounts = []domainAuth.Account{}
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) Update(ctx context.Context, user *domainAuth.User) error {
|
||||
model := toUserModel(user)
|
||||
return r.db.WithContext(ctx).Model(&UserModel{}).Where("id = ?", user.ID).Updates(model).Error
|
||||
}
|
||||
|
||||
func (r *userRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).Delete(&UserModel{}, "id = ?", id).Error
|
||||
}
|
||||
|
||||
func (r *userRepository) List(ctx context.Context, limit, offset int, opts ...domainAuth.UserQueryOption) ([]*domainAuth.User, error) {
|
||||
// Parse query options
|
||||
options := &domainAuth.UserQueryOptions{}
|
||||
for _, opt := range opts {
|
||||
opt(options)
|
||||
}
|
||||
|
||||
var models []UserModel
|
||||
if err := r.db.WithContext(ctx).Limit(limit).Offset(offset).Find(&models).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(models) == 0 {
|
||||
return []*domainAuth.User{}, nil
|
||||
}
|
||||
|
||||
users := make([]*domainAuth.User, len(models))
|
||||
userIDs := make([]uuid.UUID, len(models))
|
||||
|
||||
for i, model := range models {
|
||||
users[i] = toUserDomain(&model)
|
||||
userIDs[i] = users[i].ID
|
||||
}
|
||||
|
||||
// Batch load relations if requested
|
||||
if options.LoadRoles {
|
||||
rolesMap, err := r.loadUsersRoles(ctx, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, user := range users {
|
||||
if roles, ok := rolesMap[user.ID]; ok {
|
||||
user.Roles = roles
|
||||
} else {
|
||||
user.Roles = []domainAuth.Role{}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, user := range users {
|
||||
user.Roles = []domainAuth.Role{}
|
||||
}
|
||||
}
|
||||
|
||||
if options.LoadAccounts {
|
||||
accountsMap, err := r.loadUsersAccounts(ctx, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, user := range users {
|
||||
if accounts, ok := accountsMap[user.ID]; ok {
|
||||
user.Accounts = accounts
|
||||
} else {
|
||||
user.Accounts = []domainAuth.Account{}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for _, user := range users {
|
||||
user.Accounts = []domainAuth.Account{}
|
||||
}
|
||||
}
|
||||
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) Count(ctx context.Context) (int64, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).Model(&UserModel{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count, nil
|
||||
}
|
||||
|
||||
// loadUserRoles loads roles for a single user
|
||||
func (r *userRepository) loadUserRoles(ctx context.Context, userID uuid.UUID) ([]domainAuth.Role, error) {
|
||||
var roleModels []RoleModel
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("roles").
|
||||
Joins("INNER JOIN user_roles ON roles.id = user_roles.role_id").
|
||||
Where("user_roles.user_id = ? AND user_roles.deleted_at IS NULL AND roles.deleted_at IS NULL", userID).
|
||||
Find(&roleModels).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]domainAuth.Role, len(roleModels))
|
||||
for i, model := range roleModels {
|
||||
role := toRoleDomain(&model)
|
||||
roles[i] = *role
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) UserRoles(ctx context.Context, userID uuid.UUID) ([]domainAuth.Role, error) {
|
||||
var roleModels []RoleModel
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("roles").
|
||||
Joins("INNER JOIN user_roles ON roles.id = user_roles.role_id").
|
||||
Where("user_roles.user_id = ? AND user_roles.deleted_at IS NULL AND roles.deleted_at IS NULL", userID).
|
||||
Find(&roleModels).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roles := make([]domainAuth.Role, len(roleModels))
|
||||
for i, model := range roleModels {
|
||||
role := toRoleDomain(&model)
|
||||
roles[i] = *role
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) loadUserAccounts(ctx context.Context, userID uuid.UUID) ([]domainAuth.Account, error) {
|
||||
var accountModels []AccountModel
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Find(&accountModels).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accounts := make([]domainAuth.Account, len(accountModels))
|
||||
for i, model := range accountModels {
|
||||
account := toAccountDomain(&model)
|
||||
accounts[i] = *account
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) UserAccounts(ctx context.Context, userID uuid.UUID) ([]domainAuth.Account, error) {
|
||||
var accountModels []AccountModel
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Find(&accountModels).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accounts := make([]domainAuth.Account, len(accountModels))
|
||||
for i, model := range accountModels {
|
||||
account := toAccountDomain(&model)
|
||||
accounts[i] = *account
|
||||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) loadUsersRoles(ctx context.Context, userIDs []uuid.UUID) (map[uuid.UUID][]domainAuth.Role, error) {
|
||||
if len(userIDs) == 0 {
|
||||
return make(map[uuid.UUID][]domainAuth.Role), nil
|
||||
}
|
||||
|
||||
var userRoles []struct {
|
||||
UserID uuid.UUID `gorm:"column:user_id"`
|
||||
RoleID uuid.UUID `gorm:"column:role_id"`
|
||||
}
|
||||
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("user_roles").
|
||||
Select("user_id, role_id").
|
||||
Where("user_id IN ? AND deleted_at IS NULL", userIDs).
|
||||
Find(&userRoles).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(userRoles) == 0 {
|
||||
return make(map[uuid.UUID][]domainAuth.Role), nil
|
||||
}
|
||||
|
||||
roleIDs := make([]uuid.UUID, 0, len(userRoles))
|
||||
for _, ur := range userRoles {
|
||||
roleIDs = append(roleIDs, ur.RoleID)
|
||||
}
|
||||
|
||||
var roleModels []RoleModel
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("id IN ? AND deleted_at IS NULL", roleIDs).
|
||||
Find(&roleModels).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a map of role_id -> role
|
||||
rolesByID := make(map[uuid.UUID]*domainAuth.Role)
|
||||
for i := range roleModels {
|
||||
role := toRoleDomain(&roleModels[i])
|
||||
rolesByID[role.ID] = role
|
||||
}
|
||||
|
||||
// Group roles by user_id
|
||||
rolesMap := make(map[uuid.UUID][]domainAuth.Role)
|
||||
for _, ur := range userRoles {
|
||||
if role, ok := rolesByID[ur.RoleID]; ok {
|
||||
rolesMap[ur.UserID] = append(rolesMap[ur.UserID], *role)
|
||||
}
|
||||
}
|
||||
|
||||
return rolesMap, nil
|
||||
}
|
||||
|
||||
func (r *userRepository) loadUsersAccounts(ctx context.Context, userIDs []uuid.UUID) (map[uuid.UUID][]domainAuth.Account, error) {
|
||||
if len(userIDs) == 0 {
|
||||
return make(map[uuid.UUID][]domainAuth.Account), nil
|
||||
}
|
||||
|
||||
var accountModels []AccountModel
|
||||
if err := r.db.WithContext(ctx).
|
||||
Where("user_id IN ?", userIDs).
|
||||
Find(&accountModels).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accountsMap := make(map[uuid.UUID][]domainAuth.Account)
|
||||
for _, model := range accountModels {
|
||||
account := toAccountDomain(&model)
|
||||
accountsMap[model.UserID] = append(accountsMap[model.UserID], *account)
|
||||
}
|
||||
|
||||
return accountsMap, nil
|
||||
}
|
||||
96
internal/repository/postgres/auth/user_role.go
Normal file
96
internal/repository/postgres/auth/user_role.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/google/uuid"
|
||||
"go.uber.org/fx"
|
||||
"gorm.io/gorm"
|
||||
|
||||
domainAuth "base/internal/domain/auth"
|
||||
)
|
||||
|
||||
type userRoleRepository struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewUserRoleRepository(lc fx.Lifecycle, db *gorm.DB) domainAuth.UserRoleRepository {
|
||||
lc.Append(
|
||||
fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
return db.AutoMigrate(UserRoleModel{})
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return &userRoleRepository{db: db}
|
||||
}
|
||||
|
||||
func (r *userRoleRepository) Create(ctx context.Context, userID, roleID uuid.UUID) error {
|
||||
model := &UserRoleModel{
|
||||
UserID: userID,
|
||||
RoleID: roleID,
|
||||
}
|
||||
return r.db.WithContext(ctx).Create(model).Error
|
||||
}
|
||||
|
||||
func (r *userRoleRepository) FindByUserID(ctx context.Context, userID uuid.UUID) ([]*domainAuth.Role, error) {
|
||||
var roleModels []RoleModel
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("roles").
|
||||
Joins("INNER JOIN user_roles ON roles.id = user_roles.role_id").
|
||||
Where("user_roles.user_id = ? AND user_roles.deleted_at IS NULL", userID).
|
||||
Find(&roleModels).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
roles := make([]*domainAuth.Role, len(roleModels))
|
||||
for i, model := range roleModels {
|
||||
roles[i] = toRoleDomain(&model)
|
||||
}
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func (r *userRoleRepository) FindByRoleID(ctx context.Context, roleID uuid.UUID) ([]*domainAuth.User, error) {
|
||||
var userModels []UserModel
|
||||
if err := r.db.WithContext(ctx).
|
||||
Table("users").
|
||||
Joins("INNER JOIN user_roles ON users.id = user_roles.user_id").
|
||||
Where("user_roles.role_id = ? AND user_roles.deleted_at IS NULL", roleID).
|
||||
Find(&userModels).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users := make([]*domainAuth.User, len(userModels))
|
||||
for i, model := range userModels {
|
||||
users[i] = toUserDomain(&model)
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
||||
func (r *userRoleRepository) Delete(ctx context.Context, userID, roleID uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Where("user_id = ? AND role_id = ?", userID, roleID).
|
||||
Delete(&UserRoleModel{}).Error
|
||||
}
|
||||
|
||||
func (r *userRoleRepository) DeleteByUserID(ctx context.Context, userID uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Where("user_id = ?", userID).
|
||||
Delete(&UserRoleModel{}).Error
|
||||
}
|
||||
|
||||
func (r *userRoleRepository) DeleteByRoleID(ctx context.Context, roleID uuid.UUID) error {
|
||||
return r.db.WithContext(ctx).
|
||||
Where("role_id = ?", roleID).
|
||||
Delete(&UserRoleModel{}).Error
|
||||
}
|
||||
|
||||
func (r *userRoleRepository) Exists(ctx context.Context, userID, roleID uuid.UUID) (bool, error) {
|
||||
var count int64
|
||||
if err := r.db.WithContext(ctx).
|
||||
Model(&UserRoleModel{}).
|
||||
Where("user_id = ? AND role_id = ?", userID, roleID).
|
||||
Count(&count).Error; err != nil {
|
||||
return false, err
|
||||
}
|
||||
return count > 0, nil
|
||||
}
|
||||
369
internal/repository/postgres/auth/user_role_test.go
Normal file
369
internal/repository/postgres/auth/user_role_test.go
Normal file
@@ -0,0 +1,369 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
domainAuth "base/internal/domain/auth"
|
||||
)
|
||||
|
||||
func TestUserRoleRepository_Create(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRoleRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
roleRepo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("create user role successfully", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "User",
|
||||
LastName: "Role",
|
||||
Email: "userrole@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "test",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Create(ctx, user.ID, role.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify user role was created
|
||||
exists, err := repo.Exists(ctx, user.ID, role.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRoleRepository_FindByUserID(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRoleRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
roleRepo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("find roles by user id", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Find",
|
||||
LastName: "User",
|
||||
Email: "find@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
role1 := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "role1",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role1)
|
||||
require.NoError(t, err)
|
||||
|
||||
role2 := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "role2",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Create(ctx, user.ID, role1.ID)
|
||||
require.NoError(t, err)
|
||||
err = repo.Create(ctx, user.ID, role2.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
roles, err := repo.FindByUserID(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, roles, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRoleRepository_FindByRoleID(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRoleRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
roleRepo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("find users by role id", func(t *testing.T) {
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "shared",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := roleRepo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
user1 := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "User",
|
||||
LastName: "One",
|
||||
Email: "user1@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = userRepo.Create(ctx, user1)
|
||||
require.NoError(t, err)
|
||||
|
||||
user2 := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "User",
|
||||
LastName: "Two",
|
||||
Email: "user2@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = userRepo.Create(ctx, user2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Create(ctx, user1.ID, role.ID)
|
||||
require.NoError(t, err)
|
||||
err = repo.Create(ctx, user2.ID, role.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
users, err := repo.FindByRoleID(ctx, role.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, users, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRoleRepository_Delete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRoleRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
roleRepo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("delete user role successfully", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Delete",
|
||||
LastName: "User",
|
||||
Email: "delete@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "delete",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Create(ctx, user.ID, role.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Delete(ctx, user.ID, role.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify deletion
|
||||
exists, err := repo.Exists(ctx, user.ID, role.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exists)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRoleRepository_DeleteByUserID(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRoleRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
roleRepo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("delete all roles for user", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Delete",
|
||||
LastName: "All",
|
||||
Email: "deleteall@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
role1 := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "role1",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role1)
|
||||
require.NoError(t, err)
|
||||
|
||||
role2 := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "role2",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Create(ctx, user.ID, role1.ID)
|
||||
require.NoError(t, err)
|
||||
err = repo.Create(ctx, user.ID, role2.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.DeleteByUserID(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify all roles deleted
|
||||
roles, err := repo.FindByUserID(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, roles, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRoleRepository_DeleteByRoleID(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRoleRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
roleRepo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("delete role from all users", func(t *testing.T) {
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "shared",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := roleRepo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
user1 := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "User",
|
||||
LastName: "One",
|
||||
Email: "user1@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = userRepo.Create(ctx, user1)
|
||||
require.NoError(t, err)
|
||||
|
||||
user2 := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "User",
|
||||
LastName: "Two",
|
||||
Email: "user2@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = userRepo.Create(ctx, user2)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Create(ctx, user1.ID, role.ID)
|
||||
require.NoError(t, err)
|
||||
err = repo.Create(ctx, user2.ID, role.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.DeleteByRoleID(ctx, role.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify role deleted from all users
|
||||
users, err := repo.FindByRoleID(ctx, role.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, users, 0)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRoleRepository_Exists(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRoleRepository(db)
|
||||
userRepo := createTestUserRepository(db)
|
||||
roleRepo := createTestRoleRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("exists returns true for existing user role", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Exists",
|
||||
LastName: "User",
|
||||
Email: "exists@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "exists",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Create(ctx, user.ID, role.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
exists, err := repo.Exists(ctx, user.ID, role.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
})
|
||||
|
||||
t.Run("exists returns false for non-existent user role", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Not",
|
||||
LastName: "Exists",
|
||||
Email: "notexists@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := userRepo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "notexists",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
exists, err := repo.Exists(ctx, user.ID, role.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, exists)
|
||||
})
|
||||
}
|
||||
605
internal/repository/postgres/auth/user_test.go
Normal file
605
internal/repository/postgres/auth/user_test.go
Normal file
@@ -0,0 +1,605 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"base/internal/pkg/oauth"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
domainAuth "base/internal/domain/auth"
|
||||
)
|
||||
|
||||
func TestUserRepository_Create(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("create user successfully", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
Email: "john.doe@example.com",
|
||||
EmailVerified: false,
|
||||
Status: domainAuth.UserStatusActive,
|
||||
PhoneNumber: "1234567890",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, user)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEqual(t, uuid.Nil, user.ID)
|
||||
|
||||
// Verify user was created
|
||||
found, err := repo.FindByID(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.Email, found.Email)
|
||||
assert.Equal(t, user.FirstName, found.FirstName)
|
||||
assert.Equal(t, user.LastName, found.LastName)
|
||||
})
|
||||
|
||||
t.Run("create user with duplicate email fails", func(t *testing.T) {
|
||||
email := "duplicate@example.com"
|
||||
user1 := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "User",
|
||||
LastName: "One",
|
||||
Email: email,
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, user1)
|
||||
assert.NoError(t, err)
|
||||
|
||||
user2 := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "User",
|
||||
LastName: "Two",
|
||||
Email: email,
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err = repo.Create(ctx, user2)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRepository_UpsertWithAccount(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("upsert creates new user and account", func(t *testing.T) {
|
||||
email := "newuser@example.com"
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "New",
|
||||
LastName: "User",
|
||||
Email: email,
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
account := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
Provider: oauth.Google,
|
||||
Scope: []string{"read"},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
isNew, err := repo.UpsertWithAccount(ctx, email, user, account)
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, isNew)
|
||||
// For new users, UserID is set by UpsertWithAccount
|
||||
assert.Equal(t, user.ID, account.UserID)
|
||||
|
||||
// Verify user was created
|
||||
foundUser, err := repo.FindByID(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.Email, foundUser.Email)
|
||||
|
||||
// Note: For new users, UpsertWithAccount sets account.UserID but doesn't create the account
|
||||
// The account needs to be created separately if needed
|
||||
})
|
||||
|
||||
t.Run("upsert updates existing user with new account", func(t *testing.T) {
|
||||
email := "existing@example.com"
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Existing",
|
||||
LastName: "User",
|
||||
Email: email,
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Create user first
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Get the user from DB to ensure we have the correct ID
|
||||
foundUser, err := repo.FindByEmail(ctx, email)
|
||||
require.NoError(t, err)
|
||||
user.ID = foundUser.ID
|
||||
|
||||
// Create first account with Google provider
|
||||
account1 := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Google,
|
||||
Scope: []string{"read"},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
accountRepo := createTestAccountRepository(db)
|
||||
err = accountRepo.Create(ctx, account1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Upsert with different provider (GitHub) - should create new account
|
||||
account2 := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID, // Set UserID before upsert
|
||||
Provider: oauth.GitHub,
|
||||
Scope: []string{"read", "write"},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Use the same user object but ensure it has the correct ID
|
||||
userForUpsert := &domainAuth.User{
|
||||
ID: user.ID,
|
||||
FirstName: user.FirstName,
|
||||
LastName: user.LastName,
|
||||
Email: user.Email,
|
||||
Status: user.Status,
|
||||
CreatedAt: user.CreatedAt,
|
||||
UpdatedAt: user.UpdatedAt,
|
||||
}
|
||||
|
||||
isNew, err := repo.UpsertWithAccount(ctx, email, userForUpsert, account2)
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, isNew)
|
||||
// Note: account.UserID is not updated by UpsertWithAccount when user exists,
|
||||
// but it should already be set correctly
|
||||
assert.Equal(t, user.ID, account2.UserID)
|
||||
// Account ID should be set after creation
|
||||
assert.NotEqual(t, uuid.Nil, account2.ID)
|
||||
|
||||
// Verify the GitHub account was created by finding it by ID
|
||||
foundAccount2, err := accountRepo.FindByID(ctx, account2.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, foundAccount2)
|
||||
assert.Equal(t, account2.UserID, foundAccount2.UserID)
|
||||
assert.Equal(t, account2.Provider, foundAccount2.Provider)
|
||||
assert.Equal(t, account2.Scope, foundAccount2.Scope)
|
||||
|
||||
// Verify both accounts exist
|
||||
accounts, err := accountRepo.FindByUserID(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.GreaterOrEqual(t, len(accounts), 2) // At least Google and GitHub accounts
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByID(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("find existing user by id", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Find",
|
||||
LastName: "User",
|
||||
Email: "find@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByID(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.ID)
|
||||
assert.Equal(t, user.Email, found.Email)
|
||||
})
|
||||
|
||||
t.Run("find user with roles", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Role",
|
||||
LastName: "User",
|
||||
Email: "role@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create role
|
||||
roleRepo := createTestRoleRepository(db)
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "admin",
|
||||
Description: "Administrator",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Assign role to user
|
||||
userRoleRepo := createTestUserRoleRepository(db)
|
||||
err = userRoleRepo.Create(ctx, user.ID, role.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Find user with roles
|
||||
found, err := repo.FindByID(ctx, user.ID, domainAuth.WithRoles())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.ID)
|
||||
assert.Len(t, found.Roles, 1)
|
||||
assert.Equal(t, role.Name, found.Roles[0].Name)
|
||||
})
|
||||
|
||||
t.Run("find non-existent user", func(t *testing.T) {
|
||||
nonExistentID := uuid.New()
|
||||
found, err := repo.FindByID(ctx, nonExistentID)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, found)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRepository_FindByEmail(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("find existing user by email", func(t *testing.T) {
|
||||
email := "email@example.com"
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Email",
|
||||
LastName: "User",
|
||||
Email: email,
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
found, err := repo.FindByEmail(ctx, email)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.ID)
|
||||
assert.Equal(t, email, found.Email)
|
||||
})
|
||||
|
||||
t.Run("find user with accounts", func(t *testing.T) {
|
||||
email := "accounts@example.com"
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Accounts",
|
||||
LastName: "User",
|
||||
Email: email,
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create account
|
||||
accountRepo := createTestAccountRepository(db)
|
||||
account := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Google,
|
||||
Scope: []string{"read"},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = accountRepo.Create(ctx, account)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Find user with accounts
|
||||
found, err := repo.FindByEmail(ctx, email, domainAuth.WithAccounts())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, user.ID, found.ID)
|
||||
assert.Len(t, found.Accounts, 1)
|
||||
assert.Equal(t, account.Provider, found.Accounts[0].Provider)
|
||||
})
|
||||
|
||||
t.Run("find non-existent user by email", func(t *testing.T) {
|
||||
found, err := repo.FindByEmail(ctx, "nonexistent@example.com")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, found)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRepository_Update(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("update user successfully", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Update",
|
||||
LastName: "User",
|
||||
Email: "update@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Update user
|
||||
user.FirstName = "Updated"
|
||||
user.EmailVerified = true
|
||||
user.Status = domainAuth.UserStatusInactive
|
||||
|
||||
err = repo.Update(ctx, user)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify update
|
||||
found, err := repo.FindByID(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "Updated", found.FirstName)
|
||||
assert.True(t, found.EmailVerified)
|
||||
assert.Equal(t, domainAuth.UserStatusInactive, found.Status)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRepository_Delete(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("delete user successfully", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Delete",
|
||||
LastName: "User",
|
||||
Email: "delete@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Delete(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify deletion (soft delete)
|
||||
found, err := repo.FindByID(ctx, user.ID)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, found)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRepository_List(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
// Create multiple users
|
||||
for i := 0; i < 5; i++ {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "User",
|
||||
LastName: "Test",
|
||||
Email: "user" + strconv.Itoa(i) + "@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
t.Run("list users with limit and offset", func(t *testing.T) {
|
||||
users, err := repo.List(ctx, 3, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, users, 3)
|
||||
|
||||
users, err = repo.List(ctx, 3, 3)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, users, 2) // Remaining 2 users
|
||||
})
|
||||
|
||||
t.Run("list users with relations", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Relation",
|
||||
LastName: "User",
|
||||
Email: "relation@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Create role and assign
|
||||
roleRepo := createTestRoleRepository(db)
|
||||
role := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "user",
|
||||
Description: "Regular user",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role)
|
||||
require.NoError(t, err)
|
||||
|
||||
userRoleRepo := createTestUserRoleRepository(db)
|
||||
err = userRoleRepo.Create(ctx, user.ID, role.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
users, err := repo.List(ctx, 10, 0, domainAuth.WithRoles())
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, len(users), 0)
|
||||
|
||||
// Find our user in the list
|
||||
var foundUser *domainAuth.User
|
||||
for _, u := range users {
|
||||
if u.ID == user.ID {
|
||||
foundUser = u
|
||||
break
|
||||
}
|
||||
}
|
||||
require.NotNil(t, foundUser)
|
||||
assert.Len(t, foundUser.Roles, 1)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRepository_Count(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("count users", func(t *testing.T) {
|
||||
initialCount, err := repo.Count(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(0), initialCount)
|
||||
|
||||
// Create users
|
||||
for i := 0; i < 3; i++ {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Count",
|
||||
LastName: "User",
|
||||
Email: "count" + strconv.Itoa(i) + "@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
count, err := repo.Count(ctx)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int64(3), count)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRepository_UserRoles(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("get user roles", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Roles",
|
||||
LastName: "User",
|
||||
Email: "roles@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
roleRepo := createTestRoleRepository(db)
|
||||
role1 := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "admin",
|
||||
Description: "Admin role",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role1)
|
||||
require.NoError(t, err)
|
||||
|
||||
role2 := &domainAuth.Role{
|
||||
ID: uuid.New(),
|
||||
Name: "user",
|
||||
Description: "User role",
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = roleRepo.Create(ctx, role2)
|
||||
require.NoError(t, err)
|
||||
|
||||
userRoleRepo := createTestUserRoleRepository(db)
|
||||
err = userRoleRepo.Create(ctx, user.ID, role1.ID)
|
||||
require.NoError(t, err)
|
||||
err = userRoleRepo.Create(ctx, user.ID, role2.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
roles, err := repo.UserRoles(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, roles, 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUserRepository_UserAccounts(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := createTestUserRepository(db)
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("get user accounts", func(t *testing.T) {
|
||||
user := &domainAuth.User{
|
||||
ID: uuid.New(),
|
||||
FirstName: "Accounts",
|
||||
LastName: "User",
|
||||
Email: "accounts@example.com",
|
||||
Status: domainAuth.UserStatusActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err := repo.Create(ctx, user)
|
||||
require.NoError(t, err)
|
||||
|
||||
accountRepo := createTestAccountRepository(db)
|
||||
account1 := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.Google,
|
||||
Scope: []string{"read"},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = accountRepo.Create(ctx, account1)
|
||||
require.NoError(t, err)
|
||||
|
||||
account2 := &domainAuth.Account{
|
||||
ID: uuid.New(),
|
||||
UserID: user.ID,
|
||||
Provider: oauth.GitHub,
|
||||
Scope: []string{"read", "write"},
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
err = accountRepo.Create(ctx, account2)
|
||||
require.NoError(t, err)
|
||||
|
||||
accounts, err := repo.UserAccounts(ctx, user.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, accounts, 2)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user