first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:37:58 +03:00
commit 8b1fbdee99
104 changed files with 23398 additions and 0 deletions

View File

@@ -0,0 +1,240 @@
package services
import (
"errors"
"time"
"gauth-central/internal/database"
"gauth-central/internal/models"
"gauth-central/pkg/utils"
"github.com/markbates/goth"
"gorm.io/gorm"
)
type AuthService struct {
jwtService *JWTService
}
func NewAuthService() *AuthService {
return &AuthService{
jwtService: NewJWTService(),
}
}
func (s *AuthService) Register(username, email, password string) (*models.User, string, string, string, error) {
// Check if user exists (including soft-deleted users)
var count int64
database.DB.Model(&models.User{}).Unscoped().Where("email = ?", email).Count(&count)
if count > 0 {
return nil, "", "", "", errors.New("email already registered")
}
hashedPassword, err := utils.HashPassword(password)
if err != nil {
return nil, "", "", "", err
}
verifyToken, err := utils.GenerateSecureToken(32)
if err != nil {
return nil, "", "", "", err
}
// Email/password users must verify email; tokens are not issued until verified
falseBool := false
user := models.User{
UserName: username,
Email: email,
Password: hashedPassword,
EmailVerified: &falseBool, // Explicitly set to false
EmailVerifyToken: verifyToken,
}
// Create user - EmailVerified will be false by default or explicitly set
if err := database.DB.Create(&user).Error; err != nil {
// Fallback check for duplicate key error just in case race condition
if utils.IsDuplicateKeyError(err) {
return nil, "", "", "", errors.New("email already registered")
}
return nil, "", "", "", err
}
// Assign default "user" role
var userRole models.Role
if err := database.DB.Where("name = ?", "user").First(&userRole).Error; err == nil {
database.DB.Model(&user).Association("Roles").Append(&userRole)
}
// Reload user with roles (no JWT until email verified)
database.DB.Preload("Roles.Permissions").First(&user, user.ID)
return &user, "", "", verifyToken, nil
}
func (s *AuthService) Login(email, password string) (*models.User, string, string, error) {
var user models.User
// Preload Roles and Permissions
if err := database.DB.Preload("Roles.Permissions").Where("email = ?", email).First(&user).Error; err != nil {
return nil, "", "", errors.New("invalid credentials")
}
if !utils.CheckPasswordHash(password, user.Password) {
return nil, "", "", errors.New("invalid credentials")
}
if !user.IsEmailVerified() {
return nil, "", "", errors.New("email not verified")
}
accessToken, refreshToken, err := s.jwtService.GenerateTokenPair(user)
if err != nil {
return nil, "", "", err
}
return &user, accessToken, refreshToken, nil
}
func (s *AuthService) RefreshToken(refreshToken string) (string, string, error) {
claims, err := s.jwtService.ValidateToken(refreshToken)
if err != nil {
return "", "", err
}
// Here you might want to check against DB if user still exists or is banned
// Also you could implement token rotation (revoke used refresh token)
var user models.User
// Parse UUID from claims and preload permissions
if err := database.DB.Preload("Roles.Permissions").Where("id = ?", claims.UserID).First(&user).Error; err != nil {
return "", "", errors.New("user not found")
}
return s.jwtService.GenerateTokenPair(user)
}
func (s *AuthService) FindOrCreateSocialUser(gothUser goth.User, provider string) (*models.User, string, string, error) {
var socialAccount models.SocialAccount
var user models.User
// Check if social account exists
err := database.DB.Where("provider = ? AND provider_id = ?", provider, gothUser.UserID).First(&socialAccount).Error
if err == nil {
// Social account exists, get user
if err := database.DB.Preload("Roles.Permissions").First(&user, socialAccount.UserID).Error; err != nil {
return nil, "", "", err
}
// Update avatar if changed
if gothUser.AvatarURL != "" && user.Avatar != gothUser.AvatarURL {
database.DB.Model(&user).Update("avatar", gothUser.AvatarURL)
user.Avatar = gothUser.AvatarURL
}
} else if errors.Is(err, gorm.ErrRecordNotFound) {
// Check if user with same email exists
err := database.DB.Where("email = ?", gothUser.Email).First(&user).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
// Create new user - use name from provider or generate from email
username := gothUser.NickName
if username == "" {
username = gothUser.Name
}
if username == "" {
// Generate username from email (part before @)
for i, c := range gothUser.Email {
if c == '@' {
username = gothUser.Email[:i]
break
}
}
}
// OAuth providers have already verified email; no email verification required
trueBool := true
user = models.User{
UserName: username,
Email: gothUser.Email,
EmailVerified: &trueBool,
Avatar: gothUser.AvatarURL, // Save avatar from OAuth provider
}
if err := database.DB.Create(&user).Error; err != nil {
return nil, "", "", err
}
} else {
// Linking OAuth to existing user: treat as verified and update avatar
updates := map[string]interface{}{
"email_verified": true,
"email_verify_token": "",
}
if gothUser.AvatarURL != "" {
updates["avatar"] = gothUser.AvatarURL
}
database.DB.Model(&user).Updates(updates)
trueBool := true
user.EmailVerified = &trueBool
user.EmailVerifyToken = ""
if gothUser.AvatarURL != "" {
user.Avatar = gothUser.AvatarURL
}
}
// Create social account with avatar and name
socialAccount = models.SocialAccount{
UserID: user.ID,
Provider: provider,
ProviderID: gothUser.UserID,
Email: gothUser.Email,
Name: gothUser.Name,
AvatarURL: gothUser.AvatarURL,
}
if err := database.DB.Create(&socialAccount).Error; err != nil {
return nil, "", "", err
}
// Assign default "user" role
var userRole models.Role
if err := database.DB.Where("name = ?", "user").First(&userRole).Error; err == nil {
database.DB.Model(&user).Association("Roles").Append(&userRole)
}
} else {
return nil, "", "", err
}
// Make sure we have the user with all roles/permissions and social accounts loaded
database.DB.Preload("Roles.Permissions").Preload("SocialAccounts").First(&user, user.ID)
accessToken, refreshToken, err := s.jwtService.GenerateTokenPair(user)
if err != nil {
return nil, "", "", err
}
return &user, accessToken, refreshToken, nil
}
func (s *AuthService) GetUserByID(userID string) (*models.User, error) {
var user models.User
if err := database.DB.Preload("Roles.Permissions").Where("id = ?", userID).First(&user).Error; err != nil {
return nil, errors.New("user not found")
}
return &user, nil
}
// VerifyEmail marks the user as verified when the token matches. Only for email/password registrations.
func (s *AuthService) VerifyEmail(token string) error {
if token == "" {
return errors.New("invalid verification token")
}
var user models.User
if err := database.DB.Where("email_verify_token = ?", token).First(&user).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("invalid or expired verification token")
}
return err
}
now := time.Now()
return database.DB.Model(&user).Updates(map[string]interface{}{
"email_verified": true,
"email_verify_token": "",
"email_verified_at": &now,
}).Error
}

View File

@@ -0,0 +1,204 @@
package services
import (
"encoding/json"
"gauth-central/internal/database"
"gauth-central/internal/models"
"time"
"github.com/redis/go-redis/v9"
)
type CacheService struct{}
func NewCacheService() *CacheService {
return &CacheService{}
}
// User Cache
func (s *CacheService) SetUser(userID string, user *models.User, expiration time.Duration) error {
userData, err := json.Marshal(user)
if err != nil {
return err
}
return database.Set("user:"+userID, userData, expiration)
}
func (s *CacheService) GetUser(userID string) (*models.User, error) {
data, err := database.Get("user:" + userID)
if err != nil {
return nil, err
}
var user models.User
err = json.Unmarshal([]byte(data), &user)
if err != nil {
return nil, err
}
return &user, nil
}
func (s *CacheService) DeleteUser(userID string) error {
return database.Delete("user:" + userID)
}
// Session Management
func (s *CacheService) SetSession(token string, userID string, expiration time.Duration) error {
return database.Set("session:"+token, userID, expiration)
}
func (s *CacheService) GetSession(token string) (string, error) {
return database.Get("session:" + token)
}
func (s *CacheService) DeleteSession(token string) error {
return database.Delete("session:" + token)
}
// Rate Limiting
func (s *CacheService) IncrementRateLimit(key string, expiration time.Duration) (int64, error) {
count, err := database.Increment("ratelimit:" + key)
if err != nil {
return 0, err
}
// Set expiration only for first increment
if count == 1 {
database.Expire("ratelimit:"+key, expiration)
}
return count, nil
}
func (s *CacheService) GetRateLimit(key string) (string, error) {
return database.Get("ratelimit:" + key)
}
// Token Blacklist (for logout)
func (s *CacheService) BlacklistToken(token string, expiration time.Duration) error {
return database.Set("blacklist:"+token, "1", expiration)
}
func (s *CacheService) IsTokenBlacklisted(token string) (bool, error) {
return database.Exists("blacklist:" + token)
}
// Email Verification Token Cache
func (s *CacheService) SetEmailVerification(email string, token string, expiration time.Duration) error {
return database.Set("email_verify:"+email, token, expiration)
}
func (s *CacheService) GetEmailVerification(email string) (string, error) {
return database.Get("email_verify:" + email)
}
func (s *CacheService) DeleteEmailVerification(email string) error {
return database.Delete("email_verify:" + email)
}
// Password Reset Token Cache
func (s *CacheService) SetPasswordReset(email string, token string, expiration time.Duration) error {
return database.Set("password_reset:"+email, token, expiration)
}
func (s *CacheService) GetPasswordReset(email string) (string, error) {
return database.Get("password_reset:" + email)
}
func (s *CacheService) DeletePasswordReset(email string) error {
return database.Delete("password_reset:" + email)
}
// CORS Whitelist Cache
func (s *CacheService) SetCorsWhitelist(origins []string, expiration time.Duration) error {
data, err := json.Marshal(origins)
if err != nil {
return err
}
return database.Set("cors:whitelist", data, expiration)
}
func (s *CacheService) GetCorsWhitelist() ([]string, error) {
data, err := database.Get("cors:whitelist")
if err != nil {
if err == redis.Nil {
return nil, nil
}
return nil, err
}
var origins []string
err = json.Unmarshal([]byte(data), &origins)
if err != nil {
return nil, err
}
return origins, nil
}
func (s *CacheService) InvalidateCorsWhitelist() error {
return database.Delete("cors:whitelist")
}
// CORS Blacklist Cache
func (s *CacheService) SetCorsBlacklist(origins []string, expiration time.Duration) error {
data, err := json.Marshal(origins)
if err != nil {
return err
}
return database.Set("cors:blacklist", data, expiration)
}
func (s *CacheService) GetCorsBlacklist() ([]string, error) {
data, err := database.Get("cors:blacklist")
if err != nil {
if err == redis.Nil {
return nil, nil
}
return nil, err
}
var origins []string
err = json.Unmarshal([]byte(data), &origins)
if err != nil {
return nil, err
}
return origins, nil
}
func (s *CacheService) InvalidateCorsBlacklist() error {
return database.Delete("cors:blacklist")
}
// Rate Limit Settings Cache
func (s *CacheService) SetRateLimitSettings(settings map[string]*models.RateLimitSetting, expiration time.Duration) error {
data, err := json.Marshal(settings)
if err != nil {
return err
}
return database.Set("settings:ratelimit", data, expiration)
}
func (s *CacheService) GetRateLimitSettings() (map[string]*models.RateLimitSetting, error) {
data, err := database.Get("settings:ratelimit")
if err != nil {
if err == redis.Nil {
return nil, nil
}
return nil, err
}
var settings map[string]*models.RateLimitSetting
err = json.Unmarshal([]byte(data), &settings)
if err != nil {
return nil, err
}
return settings, nil
}
func (s *CacheService) InvalidateRateLimitSettings() error {
return database.Delete("settings:ratelimit")
}

View File

@@ -0,0 +1,146 @@
package services
import (
"errors"
"time"
"gauth-central/config"
"gauth-central/internal/models"
"github.com/golang-jwt/jwt/v5"
)
type JWTClaim struct {
UserID string `json:"sub"`
Email string `json:"email"`
Permissions []string `json:"permissions,omitempty"`
jwt.RegisteredClaims
}
type JWTService struct{}
func NewJWTService() *JWTService {
return &JWTService{}
}
func (s *JWTService) GenerateToken(user models.User) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := &JWTClaim{
UserID: user.ID.String(),
Email: user.Email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
Issuer: "gauth-central",
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(config.AppConfig.JWTSecret))
}
func (s *JWTService) GenerateTokenPair(user models.User) (string, string, error) {
// Extract permissions
permissionMap := make(map[string]bool)
for _, role := range user.Roles {
for _, perm := range role.Permissions {
permissionMap[perm.Name] = true
}
}
var permissions []string
for p := range permissionMap {
permissions = append(permissions, p)
}
// Access Token (15 min)
accessTokenExp := time.Now().Add(15 * time.Minute)
accessClaims := &JWTClaim{
UserID: user.ID.String(),
Email: user.Email,
Permissions: permissions,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(accessTokenExp),
Issuer: "gauth-central",
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
signedAccessToken, err := accessToken.SignedString([]byte(config.AppConfig.JWTSecret))
if err != nil {
return "", "", err
}
// Refresh Token (7 days)
refreshTokenExp := time.Now().Add(7 * 24 * time.Hour)
refreshClaims := &JWTClaim{
UserID: user.ID.String(),
Email: user.Email,
Permissions: nil, // Refresh token doesn't need permissions usually, or keep them if needed
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(refreshTokenExp),
Issuer: "gauth-central",
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
signedRefreshToken, err := refreshToken.SignedString([]byte(config.AppConfig.JWTSecret))
if err != nil {
return "", "", err
}
return signedAccessToken, signedRefreshToken, nil
}
func (s *JWTService) ValidateToken(signedToken string) (*JWTClaim, error) {
token, err := jwt.ParseWithClaims(
signedToken,
&JWTClaim{},
func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("unexpected signing method")
}
return []byte(config.AppConfig.JWTSecret), nil
},
)
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*JWTClaim)
if !ok {
return nil, errors.New("could not parse claims")
}
if claims.ExpiresAt.Time.Before(time.Now()) {
return nil, errors.New("token expired")
}
return claims, nil
}
// GenerateVerificationToken generates a JWT token for email verification (24 hours expiry)
func (s *JWTService) GenerateVerificationToken(userID, email string) (string, error) {
expirationTime := time.Now().Add(24 * time.Hour)
claims := &JWTClaim{
UserID: userID,
Email: email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(expirationTime),
Issuer: "gauth-central",
IssuedAt: jwt.NewNumericDate(time.Now()),
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(config.AppConfig.JWTSecret))
}
// ValidateVerificationToken validates a verification token and returns user ID and email
func (s *JWTService) ValidateVerificationToken(tokenString string) (string, string, error) {
claims, err := s.ValidateToken(tokenString)
if err != nil {
return "", "", err
}
return claims.UserID, claims.Email, nil
}

View File

@@ -0,0 +1,236 @@
package services
import (
"gauth-central/internal/database"
"gauth-central/internal/models"
"time"
)
type SettingsService struct {
cacheService *CacheService
}
func NewSettingsService() *SettingsService {
return &SettingsService{
cacheService: NewCacheService(),
}
}
// ==================== CORS WHITELIST ====================
func (s *SettingsService) GetAllCorsWhitelist() ([]models.CorsWhitelist, error) {
var whitelists []models.CorsWhitelist
err := database.DB.Where("is_active = ?", true).Order("created_at DESC").Find(&whitelists).Error
return whitelists, err
}
func (s *SettingsService) GetActiveWhitelistOrigins() ([]string, error) {
// Try cache first
cached, err := s.cacheService.GetCorsWhitelist()
if err == nil && cached != nil {
return cached, nil
}
// Fetch from database
var whitelists []models.CorsWhitelist
err = database.DB.Where("is_active = ?", true).Find(&whitelists).Error
if err != nil {
return nil, err
}
origins := make([]string, len(whitelists))
for i, w := range whitelists {
origins[i] = w.Origin
}
// Cache for 1 hour
s.cacheService.SetCorsWhitelist(origins, 1*time.Hour)
return origins, nil
}
func (s *SettingsService) CreateCorsWhitelist(whitelist *models.CorsWhitelist) error {
err := database.DB.Create(whitelist).Error
if err != nil {
return err
}
// Invalidate cache
s.cacheService.InvalidateCorsWhitelist()
return nil
}
func (s *SettingsService) UpdateCorsWhitelist(id string, updates map[string]interface{}) error {
err := database.DB.Model(&models.CorsWhitelist{}).Where("id = ?", id).Updates(updates).Error
if err != nil {
return err
}
// Invalidate cache
s.cacheService.InvalidateCorsWhitelist()
return nil
}
func (s *SettingsService) DeleteCorsWhitelist(id string) error {
err := database.DB.Delete(&models.CorsWhitelist{}, "id = ?", id).Error
if err != nil {
return err
}
// Invalidate cache
s.cacheService.InvalidateCorsWhitelist()
return nil
}
// ==================== CORS BLACKLIST ====================
func (s *SettingsService) GetAllCorsBlacklist() ([]models.CorsBlacklist, error) {
var blacklists []models.CorsBlacklist
err := database.DB.Where("is_active = ?", true).Order("created_at DESC").Find(&blacklists).Error
return blacklists, err
}
func (s *SettingsService) GetActiveBlacklistOrigins() ([]string, error) {
// Try cache first
cached, err := s.cacheService.GetCorsBlacklist()
if err == nil && cached != nil {
return cached, nil
}
// Fetch from database
var blacklists []models.CorsBlacklist
err = database.DB.Where("is_active = ?", true).Find(&blacklists).Error
if err != nil {
return nil, err
}
origins := make([]string, len(blacklists))
for i, b := range blacklists {
origins[i] = b.Origin
}
// Cache for 1 hour
s.cacheService.SetCorsBlacklist(origins, 1*time.Hour)
return origins, nil
}
func (s *SettingsService) CreateCorsBlacklist(blacklist *models.CorsBlacklist) error {
err := database.DB.Create(blacklist).Error
if err != nil {
return err
}
// Invalidate cache
s.cacheService.InvalidateCorsBlacklist()
return nil
}
func (s *SettingsService) UpdateCorsBlacklist(id string, updates map[string]interface{}) error {
err := database.DB.Model(&models.CorsBlacklist{}).Where("id = ?", id).Updates(updates).Error
if err != nil {
return err
}
// Invalidate cache
s.cacheService.InvalidateCorsBlacklist()
return nil
}
func (s *SettingsService) DeleteCorsBlacklist(id string) error {
err := database.DB.Delete(&models.CorsBlacklist{}, "id = ?", id).Error
if err != nil {
return err
}
// Invalidate cache
s.cacheService.InvalidateCorsBlacklist()
return nil
}
// ==================== RATE LIMIT SETTINGS ====================
func (s *SettingsService) GetAllRateLimitSettings() ([]models.RateLimitSetting, error) {
var settings []models.RateLimitSetting
err := database.DB.Order("name ASC").Find(&settings).Error
return settings, err
}
func (s *SettingsService) GetRateLimitSettingsMap() (map[string]*models.RateLimitSetting, error) {
// Try cache first
cached, err := s.cacheService.GetRateLimitSettings()
if err == nil && cached != nil {
return cached, nil
}
// Fetch from database
var settings []models.RateLimitSetting
err = database.DB.Where("is_active = ?", true).Find(&settings).Error
if err != nil {
return nil, err
}
settingsMap := make(map[string]*models.RateLimitSetting)
for i := range settings {
settingsMap[settings[i].Name] = &settings[i]
}
// Cache for 1 hour
s.cacheService.SetRateLimitSettings(settingsMap, 1*time.Hour)
return settingsMap, nil
}
func (s *SettingsService) GetRateLimitSettingByName(name string) (*models.RateLimitSetting, error) {
settingsMap, err := s.GetRateLimitSettingsMap()
if err != nil {
return nil, err
}
setting, exists := settingsMap[name]
if !exists {
return nil, nil
}
return setting, nil
}
func (s *SettingsService) UpdateRateLimitSetting(id string, updates map[string]interface{}) error {
err := database.DB.Model(&models.RateLimitSetting{}).Where("id = ?", id).Updates(updates).Error
if err != nil {
return err
}
// Invalidate cache
s.cacheService.InvalidateRateLimitSettings()
return nil
}
// Check if origin is allowed
func (s *SettingsService) IsOriginAllowed(origin string) (bool, error) {
// Check blacklist first
blacklist, err := s.GetActiveBlacklistOrigins()
if err != nil {
return false, err
}
for _, blocked := range blacklist {
if blocked == origin {
return false, nil
}
}
// Check whitelist
whitelist, err := s.GetActiveWhitelistOrigins()
if err != nil {
return false, err
}
for _, allowed := range whitelist {
if allowed == origin || allowed == "*" {
return true, nil
}
}
return false, nil
}

View File

@@ -0,0 +1,237 @@
package services
import (
"errors"
"gauth-central/internal/database"
"gauth-central/internal/models"
"golang.org/x/crypto/bcrypt"
)
type UserManagementService struct{}
func NewUserManagementService() *UserManagementService {
return &UserManagementService{}
}
// GetAllUsers - Tüm kullanıcıları getir (admin için)
func (s *UserManagementService) GetAllUsers(page, limit int) ([]models.User, int64, error) {
var users []models.User
var total int64
// Count total users
database.DB.Model(&models.User{}).Count(&total)
// Calculate offset
offset := (page - 1) * limit
// Fetch users with pagination and preload roles
err := database.DB.
Preload("Roles").
Preload("SocialAccounts").
Offset(offset).
Limit(limit).
Order("created_at DESC").
Find(&users).Error
return users, total, err
}
// GetUserByID - ID'ye göre kullanıcı getir
func (s *UserManagementService) GetUserByID(userID string) (*models.User, error) {
var user models.User
err := database.DB.
Preload("Roles").
Preload("Roles.Permissions").
Preload("SocialAccounts").
Where("id = ?", userID).
First(&user).Error
if err != nil {
return nil, err
}
return &user, nil
}
// CreateUser - Yeni kullanıcı oluştur (admin tarafından)
func (s *UserManagementService) CreateUser(email, password, userName string, emailVerified bool, roleNames []string) (*models.User, error) {
// Hash password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
user := &models.User{
Email: email,
UserName: userName,
Password: string(hashedPassword),
EmailVerified: &emailVerified,
}
// Create user
if err := database.DB.Create(user).Error; err != nil {
return nil, err
}
// Assign roles
if len(roleNames) > 0 {
var roles []models.Role
database.DB.Where("name IN ?", roleNames).Find(&roles)
if len(roles) > 0 {
database.DB.Model(user).Association("Roles").Append(roles)
}
} else {
// Assign default "user" role
var userRole models.Role
database.DB.Where("name = ?", "user").First(&userRole)
database.DB.Model(user).Association("Roles").Append(&userRole)
}
// Reload user with roles
database.DB.Preload("Roles").Where("id = ?", user.ID).First(user)
return user, nil
}
// UpdateUser - Kullanıcı bilgilerini güncelle
func (s *UserManagementService) UpdateUser(userID string, updates map[string]interface{}) error {
// Check if user exists
var user models.User
if err := database.DB.Where("id = ?", userID).First(&user).Error; err != nil {
return err
}
// If password is being updated, hash it
if password, ok := updates["password"].(string); ok && password != "" {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return err
}
updates["password"] = string(hashedPassword)
}
// Use Updates with Select to update specific fields including zero values
return database.DB.Model(&user).Updates(updates).Error
}
// DeleteUser - Kullanıcıyı sil (soft delete, hard=true ise kalıcı silme)
func (s *UserManagementService) DeleteUser(userID string, hardDelete bool) error {
if hardDelete {
// Hard delete - ilişkili kayıtları da sil
var user models.User
if err := database.DB.Where("id = ?", userID).First(&user).Error; err != nil {
return err
}
// Delete relations first
database.DB.Exec("DELETE FROM user_roles WHERE user_id = ?", userID)
database.DB.Exec("DELETE FROM social_accounts WHERE user_id = ?", userID)
// Permanently delete user
return database.DB.Unscoped().Delete(&models.User{}, "id = ?", userID).Error
}
// Soft delete
return database.DB.Delete(&models.User{}, "id = ?", userID).Error
}
// AssignRoles - Kullanıcıya roller ata
func (s *UserManagementService) AssignRoles(userID string, roleNames []string) error {
var user models.User
if err := database.DB.Where("id = ?", userID).First(&user).Error; err != nil {
return err
}
var roles []models.Role
if err := database.DB.Where("name IN ?", roleNames).Find(&roles).Error; err != nil {
return err
}
if len(roles) == 0 {
return errors.New("no valid roles found")
}
return database.DB.Model(&user).Association("Roles").Replace(roles)
}
// RemoveRole - Kullanıcıdan rol kaldır
func (s *UserManagementService) RemoveRole(userID string, roleName string) error {
var user models.User
if err := database.DB.Where("id = ?", userID).First(&user).Error; err != nil {
return err
}
var role models.Role
if err := database.DB.Where("name = ?", roleName).First(&role).Error; err != nil {
return err
}
return database.DB.Model(&user).Association("Roles").Delete(&role)
}
// SearchUsers - Kullanıcı ara (email, username)
func (s *UserManagementService) SearchUsers(query string, page, limit int) ([]models.User, int64, error) {
var users []models.User
var total int64
searchQuery := "%" + query + "%"
// Count total matching users
database.DB.Model(&models.User{}).
Where("email ILIKE ? OR user_name ILIKE ?", searchQuery, searchQuery).
Count(&total)
// Calculate offset
offset := (page - 1) * limit
// Fetch users
err := database.DB.
Preload("Roles").
Preload("SocialAccounts").
Where("email ILIKE ? OR user_name ILIKE ?", searchQuery, searchQuery).
Offset(offset).
Limit(limit).
Order("created_at DESC").
Find(&users).Error
return users, total, err
}
// GetDeletedUsers - Soft delete edilmiş kullanıcıları getir
func (s *UserManagementService) GetDeletedUsers(page, limit int) ([]models.User, int64, error) {
var users []models.User
var total int64
// Count total deleted users
database.DB.Model(&models.User{}).Unscoped().Where("deleted_at IS NOT NULL").Count(&total)
// Calculate offset
offset := (page - 1) * limit
// Fetch deleted users with pagination
err := database.DB.Unscoped().
Preload("Roles").
Preload("SocialAccounts").
Where("deleted_at IS NOT NULL").
Offset(offset).
Limit(limit).
Order("deleted_at DESC").
Find(&users).Error
return users, total, err
}
// RestoreUser - Soft delete edilmiş kullanıcıyı geri yükle
func (s *UserManagementService) RestoreUser(userID string) error {
var user models.User
// Find soft deleted user
if err := database.DB.Unscoped().Where("id = ? AND deleted_at IS NOT NULL", userID).First(&user).Error; err != nil {
return errors.New("deleted user not found")
}
// Restore user (set deleted_at to NULL)
return database.DB.Unscoped().Model(&user).Update("deleted_at", nil).Error
}