first commit
This commit is contained in:
46
database/config/mysql_db.go
Normal file
46
database/config/mysql_db.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
configs "ares/config"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var DB *gorm.DB
|
||||
|
||||
func ConnectDB() {
|
||||
dsn := configs.AppConfig.DBUrl
|
||||
if dsn == "" {
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Warn(".env dosyasında DB_URL ayarlı değil — veritabanı bağlantısı atlanıyor (geliştirme modu)")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Info("Yapılandırmada DB_URL bulundu, veritabanına bağlanılmaya çalışılıyor...")
|
||||
}
|
||||
|
||||
// GORM için MySQL konfigürasyonu
|
||||
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Warn), // Info seviyesi (performans etkileyebilir); üretimde Error seviyesine alınabilir
|
||||
PrepareStmt: true, // PrepareStmt performansını artırmak için
|
||||
NowFunc: func() time.Time {
|
||||
return time.Now().UTC()
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Sugar().Errorf("MySQL veritabanı bağlantısı kurulamadı: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Info("MySQL veritabanı bağlantısı kuruldu.")
|
||||
}
|
||||
DB = db
|
||||
}
|
||||
46
database/config/postgres_db.go
Normal file
46
database/config/postgres_db.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
configs "ares/config"
|
||||
"time"
|
||||
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var DBPg *gorm.DB
|
||||
|
||||
func ConnectDBPg() {
|
||||
dsn := configs.AppConfig.DBPGUrl
|
||||
if dsn == "" {
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Warn(".env dosyasında DB_URL ayarlı değil — veritabanı bağlantısı atlanıyor (geliştirme modu)")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Info("Yapılandırmada DB_URL_PG bulundu, veritabanına bağlanılmaya çalışılıyor...")
|
||||
}
|
||||
|
||||
// GORM için MySQL konfigürasyonu
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Warn), // Info seviyesi (performans etkileyebilir); üretimde Error seviyesine alınabilir
|
||||
PrepareStmt: true, // PrepareStmt performansını artırmak için
|
||||
NowFunc: func() time.Time {
|
||||
return time.Now().UTC()
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Sugar().Errorf("Postgres veritabanı bağlantısı kurulamadı: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Info("Postgres veritabanı bağlantısı kuruldu.")
|
||||
}
|
||||
DBPg = db
|
||||
}
|
||||
117
database/config/redis_db.go
Normal file
117
database/config/redis_db.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
configs "ares/config"
|
||||
"context"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"time"
|
||||
)
|
||||
|
||||
var RedisClient *redis.Client
|
||||
var RedisOptions *redis.Options
|
||||
var ctx = context.Background()
|
||||
|
||||
func ConnectRedis() {
|
||||
redisURL := configs.AppConfig.RedisUrl
|
||||
if redisURL == "" {
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Warn("Warning: REDIS_URL is not set, continuing without Redis cache")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
opt, err := redis.ParseURL(redisURL)
|
||||
if err != nil {
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Sugar().Warnf("Warning: Failed to parse Redis URL: %v, continuing without Redis cache", err)
|
||||
}
|
||||
RedisOptions = nil
|
||||
return
|
||||
}
|
||||
|
||||
RedisOptions = opt
|
||||
RedisClient = redis.NewClient(opt)
|
||||
|
||||
// Test connection
|
||||
_, err = RedisClient.Ping(ctx).Result()
|
||||
if err != nil {
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Sugar().Warnf("Warning: Failed to connect to Redis: %v, continuing without Redis cache", err)
|
||||
}
|
||||
RedisClient = nil
|
||||
RedisOptions = nil
|
||||
return
|
||||
}
|
||||
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Info("Connected to Redis successfully")
|
||||
}
|
||||
}
|
||||
|
||||
// Set stores a key-value pair in Redis with expiration
|
||||
func Set(key string, value interface{}, expiration time.Duration) error {
|
||||
if RedisClient == nil {
|
||||
return nil // Gracefully handle when Redis is not available
|
||||
}
|
||||
return RedisClient.Set(ctx, key, value, expiration).Err()
|
||||
}
|
||||
|
||||
// Get retrieves a value from Redis
|
||||
func Get(key string) (string, error) {
|
||||
if RedisClient == nil {
|
||||
return "", redis.Nil // Return Nil error when Redis is not available
|
||||
}
|
||||
return RedisClient.Get(ctx, key).Result()
|
||||
}
|
||||
|
||||
// Delete removes a key from Redis
|
||||
func Delete(key string) error {
|
||||
if RedisClient == nil {
|
||||
return nil
|
||||
}
|
||||
return RedisClient.Del(ctx, key).Err()
|
||||
}
|
||||
|
||||
// Exists checks if a key exists in Redis
|
||||
func Exists(key string) (bool, error) {
|
||||
if RedisClient == nil {
|
||||
return false, nil
|
||||
}
|
||||
count, err := RedisClient.Exists(ctx, key).Result()
|
||||
return count > 0, err
|
||||
}
|
||||
|
||||
// SetWithJSON stores a JSON-serializable value in Redis
|
||||
func SetEx(key string, value interface{}, seconds int) error {
|
||||
if RedisClient == nil {
|
||||
return nil
|
||||
}
|
||||
return RedisClient.Set(ctx, key, value, time.Duration(seconds)*time.Second).Err()
|
||||
}
|
||||
|
||||
// Increment increments a counter in Redis
|
||||
func Increment(key string) (int64, error) {
|
||||
if RedisClient == nil {
|
||||
return 0, nil
|
||||
}
|
||||
return RedisClient.Incr(ctx, key).Result()
|
||||
}
|
||||
|
||||
// Expire sets expiration time for a key
|
||||
func Expire(key string, expiration time.Duration) error {
|
||||
if RedisClient == nil {
|
||||
return nil
|
||||
}
|
||||
return RedisClient.Expire(ctx, key, expiration).Err()
|
||||
}
|
||||
|
||||
// FlushAll clears all keys in the current database
|
||||
func FlushAll() error {
|
||||
if RedisClient == nil {
|
||||
return nil
|
||||
}
|
||||
if configs.Logger != nil {
|
||||
configs.Logger.Info("🧹 Clearing Redis Cache...")
|
||||
}
|
||||
return RedisClient.FlushDB(ctx).Err()
|
||||
}
|
||||
162
database/migrate/migrate.go
Normal file
162
database/migrate/migrate.go
Normal file
@@ -0,0 +1,162 @@
|
||||
package migrate
|
||||
|
||||
import (
|
||||
configs "ares/config"
|
||||
database "ares/database/config"
|
||||
"ares/database/models"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Only run AutoMigrate if DB is initialized
|
||||
|
||||
func Migrate() {
|
||||
if database.DB != nil {
|
||||
if err := database.DB.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.SocialAccount{},
|
||||
&models.Profile{},
|
||||
&models.Hero{},
|
||||
&models.Setting{},
|
||||
&models.CorsWhitelist{},
|
||||
&models.CorsBlacklist{},
|
||||
&models.RateLimitSetting{},
|
||||
&models.Category{},
|
||||
&models.Tag{},
|
||||
&models.Post{},
|
||||
&models.CategoryView{},
|
||||
&models.Comment{},
|
||||
&models.ProductCategory{},
|
||||
&models.ProductTag{},
|
||||
&models.Product{},
|
||||
&models.ProductCategoryView{},
|
||||
&models.ProductComment{},
|
||||
&models.Cart{},
|
||||
&models.CartItem{},
|
||||
); err != nil {
|
||||
configs.Logger.Sugar().Errorf("AutoMigrate Yapılamadı !!: %v", err)
|
||||
}
|
||||
seedSecurityDefaults()
|
||||
configs.Logger.Info("AutoMigrate Yapıldı.")
|
||||
} else {
|
||||
configs.Logger.Info("DB not initialized: skipping AutoMigrate")
|
||||
}
|
||||
}
|
||||
|
||||
func MigratePg() {
|
||||
if database.DBPg != nil {
|
||||
if err := database.DBPg.AutoMigrate(
|
||||
&models.User{},
|
||||
&models.SocialAccount{},
|
||||
&models.Profile{},
|
||||
&models.Hero{},
|
||||
&models.Setting{},
|
||||
&models.CorsWhitelist{},
|
||||
&models.CorsBlacklist{},
|
||||
&models.RateLimitSetting{},
|
||||
&models.Category{},
|
||||
&models.Tag{},
|
||||
&models.Post{},
|
||||
&models.CategoryView{},
|
||||
&models.Comment{},
|
||||
&models.ProductCategory{},
|
||||
&models.ProductTag{},
|
||||
&models.Product{},
|
||||
&models.ProductCategoryView{},
|
||||
&models.ProductComment{},
|
||||
&models.Cart{},
|
||||
&models.CartItem{},
|
||||
); err != nil {
|
||||
configs.Logger.Sugar().Errorf("PG AutoMigrate Yapılamadı !!: %v", err)
|
||||
}
|
||||
seedSecurityDefaults()
|
||||
configs.Logger.Info("PG AutoMigrate Yapıldı.")
|
||||
} else {
|
||||
configs.Logger.Info("PG DB not initialized: skipping AutoMigrate")
|
||||
}
|
||||
}
|
||||
|
||||
func seedSecurityDefaults() {
|
||||
seedRateLimit("register", "Register endpoint default rate limit", 5, 60)
|
||||
seedRateLimit("login", "Login endpoint default rate limit", 10, 60)
|
||||
seedRateLimit("global", "Global endpoint default rate limit", 1000, 60)
|
||||
|
||||
for _, origin := range defaultWhitelistOrigins() {
|
||||
seedCorsWhitelist(origin, "default seeded whitelist")
|
||||
}
|
||||
}
|
||||
|
||||
func seedRateLimit(name, description string, maxRequests int64, windowSeconds int) {
|
||||
var existing models.RateLimitSetting
|
||||
if err := database.DB.Where("name = ?", name).First(&existing).Error; err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
item := models.RateLimitSetting{
|
||||
Name: name,
|
||||
Description: description,
|
||||
MaxRequests: maxRequests,
|
||||
WindowSeconds: windowSeconds,
|
||||
IsActive: true,
|
||||
UpdatedBy: "seed",
|
||||
}
|
||||
if err := database.DB.Create(&item).Error; err != nil {
|
||||
configs.Logger.Sugar().Errorf("RateLimit seed failed (%s): %v", name, err)
|
||||
return
|
||||
}
|
||||
configs.Logger.Sugar().Infof("RateLimit seed created: name=%s max=%d window=%ds", name, maxRequests, windowSeconds)
|
||||
}
|
||||
|
||||
func seedCorsWhitelist(origin, description string) {
|
||||
origin = strings.TrimSpace(origin)
|
||||
if origin == "" {
|
||||
return
|
||||
}
|
||||
|
||||
var existing models.CorsWhitelist
|
||||
if err := database.DB.Where("origin = ?", origin).First(&existing).Error; err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
item := models.CorsWhitelist{
|
||||
Origin: origin,
|
||||
Description: description,
|
||||
IsActive: true,
|
||||
CreatedBy: "seed",
|
||||
}
|
||||
if err := database.DB.Create(&item).Error; err != nil {
|
||||
configs.Logger.Sugar().Errorf("CorsWhitelist seed failed (%s): %v", origin, err)
|
||||
return
|
||||
}
|
||||
configs.Logger.Sugar().Infof("CorsWhitelist seed created: origin=%s", origin)
|
||||
}
|
||||
|
||||
func defaultWhitelistOrigins() []string {
|
||||
origins := []string{
|
||||
"http://localhost:3000",
|
||||
"http://localhost:5173",
|
||||
"http://localhost:8080",
|
||||
}
|
||||
|
||||
appURL := strings.TrimSpace(configs.AppConfig.AppURL)
|
||||
if appURL != "" {
|
||||
if parsed, err := url.Parse(appURL); err == nil && parsed.Scheme != "" && parsed.Host != "" {
|
||||
origins = append(origins, parsed.Scheme+"://"+parsed.Host)
|
||||
}
|
||||
}
|
||||
|
||||
uniq := make(map[string]struct{})
|
||||
out := make([]string, 0, len(origins))
|
||||
for _, origin := range origins {
|
||||
origin = strings.TrimSpace(origin)
|
||||
if origin == "" {
|
||||
continue
|
||||
}
|
||||
if _, ok := uniq[origin]; ok {
|
||||
continue
|
||||
}
|
||||
uniq[origin] = struct{}{}
|
||||
out = append(out, origin)
|
||||
}
|
||||
return out
|
||||
}
|
||||
53
database/models/blog.go
Normal file
53
database/models/blog.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Minimal, temiz GORM modelleri
|
||||
|
||||
type Category struct {
|
||||
gorm.Model
|
||||
Title string `gorm:"type:varchar(254);not null" json:"title"`
|
||||
Slug string `gorm:"type:varchar(254);not null;uniqueIndex" json:"slug"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Parent *Category `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;foreignKey:ParentID" json:"parent,omitempty"`
|
||||
Children []Category `gorm:"foreignKey:ParentID" json:"children,omitempty"`
|
||||
Posts []Post `gorm:"many2many:post_categories;" json:"posts,omitempty"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"type:varchar(254);not null" json:"name"`
|
||||
Posts []Post `gorm:"many2many:post_tags;" json:"posts,omitempty"`
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
gorm.Model
|
||||
Title string `gorm:"type:varchar(254);not null" json:"title" form:"title"`
|
||||
Images string `gorm:"type:text;not null" json:"images" form:"images"`
|
||||
ImagesMid string `gorm:"type:text;not null" json:"images_mid" form:"images_mid"`
|
||||
ImagesMin string `gorm:"type:text;not null" json:"images_min" form:"images_min"`
|
||||
Width int `gorm:"default:0" json:"width" form:"width"`
|
||||
Height int `gorm:"default:0" json:"height" form:"height"`
|
||||
Quality int `gorm:"default:0" json:"quality" form:"quality"`
|
||||
Format string `gorm:"type:varchar(10)" json:"format" form:"format" default:"avif"`
|
||||
Content string `gorm:"type:text" json:"content,omitempty" form:"content"`
|
||||
Slug string `gorm:"type:varchar(254);not null;uniqueIndex" json:"slug" form:"slug"`
|
||||
Categories []Category `gorm:"many2many:post_categories;" json:"categories,omitempty" form:"categories"`
|
||||
Tags []Tag `gorm:"many2many:post_tags;" json:"tags,omitempty" form:"tags"`
|
||||
}
|
||||
|
||||
type CategoryView struct {
|
||||
gorm.Model
|
||||
CategoryID uint `json:"category_id"`
|
||||
IPAddress string `gorm:"type:varchar(45)" json:"ip_address,omitempty"`
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
gorm.Model
|
||||
UserID uint `json:"user_id"`
|
||||
PostID uint `json:"post_id"`
|
||||
Body string `gorm:"type:text" json:"body,omitempty"`
|
||||
}
|
||||
20
database/models/cart.go
Normal file
20
database/models/cart.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Cart struct {
|
||||
gorm.Model
|
||||
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||
Items []CartItem `gorm:"foreignKey:CartID" json:"items,omitempty"`
|
||||
}
|
||||
|
||||
type CartItem struct {
|
||||
gorm.Model
|
||||
CartID uint `gorm:"not null;index" json:"cart_id"`
|
||||
Cart *Cart `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;foreignKey:CartID" json:"cart,omitempty"`
|
||||
ProductID uint `gorm:"not null;index" json:"product_id"`
|
||||
Product *Product `gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE;foreignKey:ProductID" json:"product,omitempty"`
|
||||
Quantity int `gorm:"default:1" json:"quantity"`
|
||||
}
|
||||
34
database/models/cors.go
Normal file
34
database/models/cors.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CorsWhitelist - CORS için izin verilen origin'ler
|
||||
type CorsWhitelist struct {
|
||||
gorm.Model
|
||||
Origin string `gorm:"type:varchar(255);uniqueIndex;not null" json:"origin"`
|
||||
Description string `gorm:"type:varchar(255)" json:"description"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
CreatedBy string `gorm:"type:varchar(255)" json:"created_by,omitempty"`
|
||||
}
|
||||
|
||||
// CorsBlacklist - CORS için yasaklanan origin'ler
|
||||
type CorsBlacklist struct {
|
||||
gorm.Model
|
||||
Origin string `gorm:"type:varchar(255);uniqueIndex;not null" json:"origin"`
|
||||
Reason string `gorm:"type:varchar(255)" json:"reason"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
CreatedBy string `gorm:"type:varchar(255)" json:"created_by,omitempty"`
|
||||
}
|
||||
|
||||
// RateLimitSetting - Rate limit ayarları
|
||||
type RateLimitSetting struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"type:varchar(100);uniqueIndex;not null" json:"name"` // e.g., "login", "register", "api"
|
||||
Description string `gorm:"type:varchar(255)" json:"description"`
|
||||
MaxRequests int64 `gorm:"not null" json:"max_requests"` // Max istek sayısı
|
||||
WindowSeconds int `gorm:"not null" json:"window_seconds"` // Zaman penceresi (saniye)
|
||||
IsActive bool `gorm:"default:true" json:"is_active"`
|
||||
UpdatedBy string `gorm:"type:varchar(255)" json:"updated_by,omitempty"`
|
||||
}
|
||||
96
database/models/docs_models.go
Normal file
96
database/models/docs_models.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package models
|
||||
|
||||
// Swagger-friendly (light) structs for documentation only.
|
||||
// These avoid embedding external types (gorm.Model) so `swag` can parse them.
|
||||
|
||||
type CategoryDoc struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Children []CategoryDoc `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type TagDoc struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type PostDoc struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Images []string `json:"images,omitempty"`
|
||||
Categories []CategoryDoc `json:"categories,omitempty"`
|
||||
Tags []TagDoc `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
type CommentDoc struct {
|
||||
ID uint `json:"id"`
|
||||
UserID uint `json:"user_id"`
|
||||
PostID uint `json:"post_id"`
|
||||
Body string `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
type CategoryViewDoc struct {
|
||||
ID uint `json:"id"`
|
||||
CategoryID uint `json:"category_id"`
|
||||
IPAddress string `json:"ip_address,omitempty"`
|
||||
}
|
||||
|
||||
type ProductCategoryDoc struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Slug string `json:"slug"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Keywords string `json:"keywords,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Children []ProductCategoryDoc `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
type ProductTagDoc struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ProductDoc struct {
|
||||
ID uint `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Images string `json:"images"`
|
||||
Price float64 `json:"price"`
|
||||
Width int `json:"width"`
|
||||
Height int `json:"height"`
|
||||
Quality int `json:"quality"`
|
||||
Format string `json:"format"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Slug string `json:"slug"`
|
||||
Categories []ProductCategoryDoc `json:"categories,omitempty"`
|
||||
Tags []ProductTagDoc `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
type CartItemDoc struct {
|
||||
ID uint `json:"id"`
|
||||
CartID uint `json:"cart_id"`
|
||||
ProductID uint `json:"product_id"`
|
||||
Product ProductDoc `json:"product,omitempty"`
|
||||
Quantity int `json:"quantity"`
|
||||
}
|
||||
|
||||
type CartDoc struct {
|
||||
ID uint `json:"id"`
|
||||
UserID uint `json:"user_id"`
|
||||
Items []CartItemDoc `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
type ProductCommentDoc struct {
|
||||
ID uint `json:"id"`
|
||||
UserID uint `json:"user_id"`
|
||||
ProductID uint `json:"product_id"`
|
||||
Body string `json:"body"`
|
||||
}
|
||||
|
||||
type ProductCategoryViewDoc struct {
|
||||
ID uint `json:"id"`
|
||||
CategoryID uint `json:"category_id"`
|
||||
IPAddress string `json:"ip_address,omitempty"`
|
||||
}
|
||||
23
database/models/hero.go
Normal file
23
database/models/hero.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Banner model structure
|
||||
// Represents a banner item with optional thumbnail.
|
||||
type Hero struct {
|
||||
gorm.Model
|
||||
Color string `gorm:"type:varchar(32);not null" json:"color" form:"color"`
|
||||
Title string `gorm:"type:varchar(254)" json:"title,omitempty" form:"title"`
|
||||
Text1 string `gorm:"type:varchar(254)" json:"text1,omitempty" form:"text1"`
|
||||
Text2 string `gorm:"type:varchar(254)" json:"text2,omitempty" form:"text2"`
|
||||
Text4 string `gorm:"type:varchar(254)" json:"text4,omitempty" form:"text4"`
|
||||
Text5 string `gorm:"type:varchar(254)" json:"text5,omitempty" form:"text5"`
|
||||
Image string `gorm:"type:varchar(254)" json:"image" form:"image"`
|
||||
IsActive bool `gorm:"default:true" json:"is_active" form:"is_active"`
|
||||
Width int `gorm:"default:0" json:"width" form:"width"`
|
||||
Height int `gorm:"default:0" json:"height" form:"height"`
|
||||
Quality int `gorm:"default:0" json:"quality" form:"quality"`
|
||||
Format string `gorm:"type:varchar(10)" json:"format" form:"format"`
|
||||
}
|
||||
53
database/models/product.go
Normal file
53
database/models/product.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Minimal, temiz GORM modelleri
|
||||
|
||||
type ProductCategory struct {
|
||||
gorm.Model
|
||||
Title string `gorm:"type:varchar(254);not null" json:"title"`
|
||||
Slug string `gorm:"type:varchar(254);not null;uniqueIndex" json:"slug"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Keywords string `json:"keywords,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Parent *ProductCategory `gorm:"constraint:OnUpdate:CASCADE,OnDelete:SET NULL;foreignKey:ParentID" json:"parent,omitempty"`
|
||||
Children []ProductCategory `gorm:"foreignKey:ParentID" json:"children,omitempty"`
|
||||
Products []Product `gorm:"many2many:product_product_categories;" json:"products,omitempty"`
|
||||
}
|
||||
|
||||
type ProductTag struct {
|
||||
gorm.Model
|
||||
Name string `gorm:"type:varchar(254);not null" json:"name"`
|
||||
Products []Product `gorm:"many2many:product_product_tags;" json:"products,omitempty"`
|
||||
}
|
||||
|
||||
type Product struct {
|
||||
gorm.Model
|
||||
Title string `gorm:"type:varchar(254);not null" json:"title" form:"title"`
|
||||
Images string `gorm:"type:text;not null" json:"images" form:"images"`
|
||||
Price float64 `gorm:"type:decimal(10,2);default:0.0" json:"price" form:"price"`
|
||||
Width int `gorm:"default:0" json:"width" form:"width"`
|
||||
Height int `gorm:"default:0" json:"height" form:"height"`
|
||||
Quality int `gorm:"default:0" json:"quality" form:"quality"`
|
||||
Format string `gorm:"type:varchar(10);default:avif" json:"format" form:"format"`
|
||||
Content string `gorm:"type:text" json:"content,omitempty" form:"content"`
|
||||
Slug string `gorm:"type:varchar(254);not null;uniqueIndex" json:"slug" form:"slug"`
|
||||
Categories []ProductCategory `gorm:"many2many:product_product_categories;" json:"categories,omitempty" form:"product_category"`
|
||||
Tags []ProductTag `gorm:"many2many:product_product_tags;" json:"tags,omitempty" form:"tags"`
|
||||
}
|
||||
|
||||
type ProductCategoryView struct {
|
||||
gorm.Model
|
||||
CategoryID uint `json:"category_id"`
|
||||
IPAddress string `gorm:"type:varchar(45)" json:"ip_address,omitempty"`
|
||||
}
|
||||
|
||||
type ProductComment struct {
|
||||
gorm.Model
|
||||
UserID uint `json:"user_id"`
|
||||
ProductID uint `json:"product_id"`
|
||||
Body string `gorm:"type:text" json:"body,omitempty"`
|
||||
}
|
||||
43
database/models/setting.go
Normal file
43
database/models/setting.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Setting model structure
|
||||
// Stores site-wide metadata and contact information.
|
||||
type Setting struct {
|
||||
gorm.Model
|
||||
Title string `gorm:"type:varchar(254);not null" json:"title" form:"title"`
|
||||
MetaTitle string `gorm:"type:varchar(254);not null" json:"meta_title" form:"meta_title"`
|
||||
MetaDescription string `gorm:"type:varchar(254);not null" json:"meta_description" form:"meta_description"`
|
||||
Phone string `gorm:"type:varchar(254);not null" json:"phone" form:"phone"`
|
||||
URL string `gorm:"type:varchar(254);not null" json:"url" form:"url"`
|
||||
Email string `gorm:"type:varchar(254);not null" json:"email" form:"email"`
|
||||
Facebook string `gorm:"type:varchar(254)" json:"facebook,omitempty" form:"facebook"`
|
||||
X string `gorm:"type:varchar(254)" json:"x,omitempty" form:"x"`
|
||||
Instagram string `gorm:"type:varchar(254)" json:"instagram,omitempty" form:"instagram"`
|
||||
Whatsapp string `gorm:"type:varchar(254)" json:"whatsapp,omitempty" form:"whatsapp"`
|
||||
Pinterest string `gorm:"type:varchar(254)" json:"pinterest,omitempty" form:"pinterest"`
|
||||
Linkedin string `gorm:"type:varchar(254)" json:"linkedin,omitempty" form:"linkedin"`
|
||||
Slogan string `gorm:"type:varchar(254)" json:"slogan,omitempty" form:"slogan"`
|
||||
Address string `gorm:"type:text" json:"address,omitempty" form:"address"`
|
||||
Copyright string `gorm:"type:varchar(254)" json:"copyright,omitempty" form:"copyright"`
|
||||
MapEmbed string `gorm:"type:text" json:"map_embed,omitempty" form:"map_embed"`
|
||||
WLogo string `gorm:"type:text" json:"w_logo,omitempty" form:"w_logo"`
|
||||
BLogo string `gorm:"type:text" json:"b_logo,omitempty" form:"b_logo"`
|
||||
IsActive bool `gorm:"default:false" json:"is_active" form:"is_active"`
|
||||
WWidth int `gorm:"default:0" json:"w_width" form:"w_width"`
|
||||
WHeight int `gorm:"default:0" json:"w_height" form:"w_height"`
|
||||
WQuality int `gorm:"default:0" json:"w_quality" form:"w_quality"`
|
||||
WFormat string `gorm:"type:varchar(10)" json:"w_format" form:"w_format"`
|
||||
BWidth int `gorm:"default:0" json:"b_width" form:"b_width"`
|
||||
BHeight int `gorm:"default:0" json:"b_height" form:"b_height"`
|
||||
BQuality int `gorm:"default:0" json:"b_quality" form:"b_quality"`
|
||||
BFormat string `gorm:"type:varchar(10)" json:"b_format" form:"b_format"`
|
||||
}
|
||||
|
||||
// TableName overrides the table name used by Setting to `settings`
|
||||
func (Setting) TableName() string {
|
||||
return "settings"
|
||||
}
|
||||
48
database/models/user.go
Normal file
48
database/models/user.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
UserName string `json:"username" gorm:"type:varchar(255)"`
|
||||
Email string `gorm:"uniqueIndex;not null;type:varchar(255)" json:"email"`
|
||||
Password string `json:"-" gorm:"type:varchar(255)"` // Password shouldn't be returned in JSON
|
||||
EmailVerified *bool `gorm:"default:false" json:"email_verified"` // default false for email/password registration
|
||||
EmailVerifyToken string `gorm:"index;type:varchar(255)" json:"-"`
|
||||
EmailVerifiedAt *time.Time `json:"email_verified_at,omitempty"`
|
||||
IsAdmin *bool `gorm:"default:false" json:"is_admin"`
|
||||
SocialAccounts []SocialAccount `gorm:"foreignKey:UserID" json:"social_accounts,omitempty"`
|
||||
Profile []Profile `gorm:"foreignKey:UserID" json:"profiles,omitempty"`
|
||||
}
|
||||
|
||||
// Email Veriyf i False Döndürüyor
|
||||
func (u *User) IsEmailVerified() bool {
|
||||
if u.EmailVerified == nil {
|
||||
return false
|
||||
}
|
||||
return *u.EmailVerified
|
||||
}
|
||||
|
||||
// SocialAccount model structure
|
||||
type SocialAccount struct {
|
||||
gorm.Model
|
||||
UserID uint64 `gorm:"type:bigint unsigned;not null;index" json:"user_id"`
|
||||
Provider string `gorm:"not null" json:"provider"` // google, github
|
||||
ProviderID string `gorm:"not null" json:"provider_id"`
|
||||
Email string `json:"email" gorm:"type:varchar(255)"`
|
||||
Name string `json:"name,omitempty" gorm:"type:varchar(255)"` // Full name from provider
|
||||
AvatarURL string `json:"avatar_url,omitempty" gorm:"type:varchar(255)"` // Avatar URL from provider
|
||||
|
||||
}
|
||||
type Profile struct {
|
||||
gorm.Model
|
||||
UserID uint64 `gorm:"type:bigint unsigned;not null;index" json:"user_id"`
|
||||
AvatarURL string `json:"avatar_url,omitempty" gorm:"type:varchar(255)"` // Avatar URL from provider
|
||||
FirstName string `json:"first_name" gorm:"type:varchar(255)"` // Full name from provider
|
||||
LastName string `json:"last_name" gorm:"type:varchar(255)"` // Full name from provider
|
||||
|
||||
}
|
||||
75
database/seeder/seeder.go
Normal file
75
database/seeder/seeder.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package seeder
|
||||
|
||||
import (
|
||||
dbConfig "ares/database/config"
|
||||
"ares/database/models"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// Seed checks for essential data and creates it if missing
|
||||
func Seed() {
|
||||
seedAdmin()
|
||||
}
|
||||
|
||||
func seedAdmin() {
|
||||
// Include soft-deleted records in lookup
|
||||
var existing models.User
|
||||
err := dbConfig.DB.Unscoped().Where("email = ?", "admin@example.com").First(&existing).Error
|
||||
if err == nil {
|
||||
// Found a user (could be soft-deleted)
|
||||
// If soft-deleted, restore it
|
||||
if existing.DeletedAt.Valid {
|
||||
// Restore (set deleted_at to NULL) and ensure admin/verified flags
|
||||
updateErr := dbConfig.DB.Unscoped().Model(&existing).Updates(map[string]interface{}{
|
||||
"deleted_at": nil,
|
||||
"is_admin": true,
|
||||
"email_verified": true,
|
||||
}).Error
|
||||
if updateErr != nil {
|
||||
fmt.Println("Admin restore hatası:", updateErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
// user exists or restored, nothing more to do
|
||||
return
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
fmt.Println("Admin seed lookup error:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// If not found at all, create
|
||||
password := "password123"
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
isTrue := true
|
||||
|
||||
admin := models.User{
|
||||
UserName: "Admin",
|
||||
Email: "admin@example.com",
|
||||
Password: string(hashedPassword),
|
||||
IsAdmin: &isTrue,
|
||||
EmailVerified: &isTrue,
|
||||
}
|
||||
|
||||
res := dbConfig.DB.Clauses(clause.OnConflict{DoNothing: true}).Create(&admin)
|
||||
if res.Error != nil {
|
||||
fmt.Println("Admin seed hatası:", res.Error)
|
||||
return
|
||||
}
|
||||
|
||||
if res.RowsAffected == 0 {
|
||||
// Another process likely created it concurrently
|
||||
fmt.Println("Admin kullanıcı zaten mevcut; seed atlandı.")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println("------------------------------------------------")
|
||||
fmt.Println("Admin kullanıcısı oluşturuldu:")
|
||||
fmt.Println("Email: admin@example.com")
|
||||
fmt.Println("Şifre: password123")
|
||||
fmt.Println("------------------------------------------------")
|
||||
}
|
||||
Reference in New Issue
Block a user