Files
atahango/internal/database/db.go
Beyhan Oğur bbbf76b184 first commit
2026-04-26 21:35:24 +03:00

328 lines
9.0 KiB
Go

package database
import (
"log"
"time"
"gauth-central/config"
"gauth-central/internal/models"
"golang.org/x/crypto/bcrypt"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
var DB *gorm.DB
func ConnectDB() {
dsn := config.AppConfig.DBUrl
if dsn == "" {
log.Fatal("DB_URL is not set in .env")
}
// Configure GORM with optimized settings
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Error), // Only log errors, suppress SLOW SQL warnings
PrepareStmt: true, // Prepare statements for better performance
NowFunc: func() time.Time {
return time.Now().UTC()
},
})
if err != nil {
log.Fatal("Failed to connect to database:", err)
}
log.Println("Connected to Database successfully")
DB = db
// Enable UUID extension
enableUUIDExtension()
}
func enableUUIDExtension() {
// Enable uuid-ossp extension for uuid_generate_v4()
err := DB.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\"").Error
if err != nil {
log.Printf("Warning: Could not enable uuid-ossp extension: %v", err)
} else {
log.Println("UUID extension enabled")
}
}
func Migrate() {
// Manual migration for user_name column to handle existing data
migrateUserNameColumn()
err := DB.AutoMigrate(
&models.User{},
&models.SocialAccount{},
&models.Role{},
&models.Permission{},
&models.CorsWhitelist{},
&models.CorsBlacklist{},
&models.RateLimitSetting{},
&models.Contact{},
&models.Tag{}, // Added Tag model
&models.PostCategory{},
&models.PostTag{},
&models.Post{},
&models.PostComment{},
&models.PostCategoryView{},
&models.Home{},
&models.About{},
&models.Service{},
&models.ServiceTitle{},
&models.Resume{},
&models.Education{},
&models.Experience{},
&models.Skill{},
&models.Knowledge{},
&models.MainMenu{},
&models.Setting{},
&models.Banner{},
&models.SiteSettings{},
)
if err != nil {
log.Fatal("Database Migration Failed:", err)
}
log.Println("Database Migration Completed")
// Migration for email_verified column is disabled after initial setup
// New users will have email_verified=false by default for email/password registration
// migrateEmailVerifiedColumn()
seedRolesAndPermissions()
seedDefaultSettings()
// seedDefaultAdmin() - Removed from auto migration
}
func migrateEmailVerifiedColumn() {
// Fast check using pg_catalog instead of information_schema
var count int64
DB.Raw(`
SELECT COUNT(*)
FROM pg_attribute
WHERE attrelid = 'users'::regclass
AND attname = 'email_verified'
AND NOT attisdropped
`).Scan(&count)
if count == 0 {
return
}
// Only set existing users (created before email verification feature) as verified
// Users with no verify token AND created before a certain date are old users
// For simplicity: set all users without a verification token as verified (one-time migration)
var usersToVerify int64
DB.Model(&models.User{}).Where("(email_verify_token IS NULL OR email_verify_token = '') AND email_verified IS NULL").Count(&usersToVerify)
if usersToVerify > 0 {
DB.Exec("UPDATE users SET email_verified = true WHERE (email_verify_token IS NULL OR email_verify_token = '') AND email_verified IS NULL")
log.Printf("Email verification migration: %d existing users marked as verified", usersToVerify)
}
}
func migrateUserNameColumn() {
// Fast check using pg_catalog instead of information_schema
var count int64
DB.Raw(`
SELECT COUNT(*)
FROM pg_attribute
WHERE attrelid = 'users'::regclass
AND attname = 'user_name'
AND NOT attisdropped
`).Scan(&count)
if count == 0 {
// Column doesn't exist, add it
log.Println("Adding user_name column...")
DB.Exec("ALTER TABLE users ADD COLUMN user_name text")
// Update existing users with default usernames
DB.Exec("UPDATE users SET user_name = CONCAT('user_', SUBSTRING(id::text, 1, 8)) WHERE user_name IS NULL")
// Add NOT NULL constraint
DB.Exec("ALTER TABLE users ALTER COLUMN user_name SET NOT NULL")
log.Println("user_name column added successfully")
} else {
// Column exists, update null values
log.Println("Updating users with null usernames...")
DB.Exec("UPDATE users SET user_name = CONCAT('user_', SUBSTRING(id::text, 1, 8)) WHERE user_name IS NULL OR user_name = ''")
// Check if NOT NULL constraint exists using pg_catalog
var isNotNull bool
DB.Raw(`
SELECT attnotnull
FROM pg_attribute
WHERE attrelid = 'users'::regclass
AND attname = 'user_name'
`).Scan(&isNotNull)
if !isNotNull {
log.Println("Adding NOT NULL constraint to user_name...")
DB.Exec("ALTER TABLE users ALTER COLUMN user_name SET NOT NULL")
}
}
}
func seedRolesAndPermissions() {
// 1. Define Permissions
permissions := []models.Permission{
{Name: "user:read", Description: "Can read user data"},
{Name: "user:write", Description: "Can modify user data"},
{Name: "admin:access", Description: "Can access admin panel"},
}
for _, p := range permissions {
DB.FirstOrCreate(&models.Permission{}, models.Permission{Name: p.Name, Description: p.Description})
}
// 2. Define Roles
roles := []string{"admin", "user"}
for _, r := range roles {
DB.FirstOrCreate(&models.Role{}, models.Role{Name: r, Description: "Default " + r + " role"})
}
// 3. Assign Permissions to Admin Role
var adminRole models.Role
DB.Preload("Permissions").Where("name = ?", "admin").First(&adminRole)
// Fetch all permissions to assign to admin
var allPermissions []models.Permission
DB.Find(&allPermissions)
// Update association (append missing ones)
DB.Model(&adminRole).Association("Permissions").Replace(allPermissions)
// 4. Assign Basic Permissions to User Role
var userRole models.Role
DB.Preload("Permissions").Where("name = ?", "user").First(&userRole)
var userPermissions []models.Permission
DB.Where("name IN ?", []string{"user:read"}).Find(&userPermissions)
DB.Model(&userRole).Association("Permissions").Replace(userPermissions)
log.Println("Roles and Permissions seeded")
}
func seedDefaultSettings() {
// Seed default CORS whitelist
var whitelistCount int64
DB.Model(&models.CorsWhitelist{}).Count(&whitelistCount)
if whitelistCount == 0 {
defaultWhitelist := []models.CorsWhitelist{
{
Origin: "http://localhost:3000",
Description: "Default local frontend",
IsActive: true,
CreatedBy: "system",
},
{
Origin: "http://localhost:8080",
Description: "Backend self",
IsActive: true,
CreatedBy: "system",
},
}
for _, w := range defaultWhitelist {
DB.Create(&w)
}
log.Println("Default CORS whitelist seeded")
}
// Seed default rate limit settings
var rateLimitCount int64
DB.Model(&models.RateLimitSetting{}).Count(&rateLimitCount)
if rateLimitCount == 0 {
defaultRateLimits := []models.RateLimitSetting{
{
Name: "login",
Description: "Login endpoint rate limit",
MaxRequests: 5,
WindowSeconds: 60, // 1 minute
IsActive: true,
},
{
Name: "register",
Description: "Registration endpoint rate limit",
MaxRequests: 3,
WindowSeconds: 300, // 5 minutes
IsActive: true,
},
{
Name: "api",
Description: "General API rate limit",
MaxRequests: 100,
WindowSeconds: 60, // 1 minute
IsActive: true,
},
}
for _, r := range defaultRateLimits {
DB.Create(&r)
}
log.Println("Default rate limit settings seeded")
}
}
// SeedDefaultAdmin creates the default admin user if it doesn't exist
func SeedDefaultAdmin() {
// Check if admin user already exists (including soft-deleted)
var adminUser models.User
err := DB.Unscoped().Where("email = ?", "admin@gauth.local").First(&adminUser).Error
if err != nil {
// Admin user doesn't exist, create one
// Hash default password: "Admin@123"
hashedPassword, err := bcrypt.GenerateFromPassword([]byte("Admin@123"), bcrypt.DefaultCost)
if err != nil {
log.Printf("Failed to hash admin password: %v", err)
return
}
trueBool := true
adminUser = models.User{
Email: "admin@gauth.local",
UserName: "admin",
Password: string(hashedPassword),
EmailVerified: &trueBool,
}
if err := DB.Create(&adminUser).Error; err != nil {
log.Printf("Failed to create admin user: %v", err)
return
}
log.Println("✅ Default admin user created:")
log.Println(" Email: admin@gauth.local")
log.Println(" Password: Admin@123")
log.Println(" ⚠️ Please change this password after first login!")
} else {
// Admin user exists (possibly soft-deleted)
if adminUser.DeletedAt.Valid {
log.Println("Restoring deleted admin user...")
if err := DB.Model(&adminUser).Unscoped().Update("deleted_at", nil).Error; err != nil {
log.Printf("Failed to restore admin user: %v", err)
return
}
}
}
// Ensure admin role is assigned
var adminRole models.Role
if err := DB.Where("name = ?", "admin").First(&adminRole).Error; err != nil {
log.Printf("Admin role not found: %v", err)
return
}
if err := DB.Model(&adminUser).Association("Roles").Append(&adminRole); err != nil {
log.Printf("Failed to assign admin role: %v", err)
}
}