package models import ( "errors" "fmt" "path/filepath" "strings" "time" "gorm.io/gorm" ) // Note: This file maps Django models to GORM models for MySQL. // Image fields are stored as file path strings. Thumbnail generation and image processing // should be handled elsewhere (e.g., during upload) — TODO: integrate with image processing service. // Category represents post categories. type Category struct { ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` Title string `gorm:"size:254;not null" json:"title"` Keywords string `gorm:"size:254" json:"keywords"` Desc string `gorm:"size:254" json:"description"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` IsActive bool `gorm:"default:true;index" json:"is_active"` Order int `gorm:"default:1;index" json:"order"` Slug string `gorm:"size:250;not null;index" json:"slug"` ParentID *uint64 `gorm:"type:bigint unsigned;index" json:"parent_id"` Parent *Category `gorm:"foreignKey:ParentID" json:"parent,omitempty"` Children []*Category `gorm:"foreignKey:ParentID" json:"children,omitempty"` Image string `gorm:"size:1024" json:"image"` } func (Category) TableName() string { return "categories" } // BeforeCreate hook to set slug func (c *Category) BeforeCreate(tx *gorm.DB) (err error) { if c.Slug == "" { c.Slug, err = generateUniqueSlugForCategory(tx, c.Title) return err } return nil } // BeforeUpdate hook ensures slug exists func (c *Category) BeforeUpdate(tx *gorm.DB) (err error) { if c.Slug == "" { c.Slug, err = generateUniqueSlugForCategory(tx, c.Title) return err } return nil } func generateUniqueSlugForCategory(db *gorm.DB, title string) (string, error) { slug := normalizeSlug(title) base := slug var count int64 try := 1 for { db.Model(&Category{}).Where("slug = ?", slug).Count(&count) if count == 0 { return slug, nil } slug = fmt.Sprintf("%s-%d", base, try) try++ if try > 1000 { return "", errors.New("unable to generate unique slug") } } } // Tags model type Tag struct { ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` Tag string `gorm:"size:254;not null" json:"tag"` Slug string `gorm:"size:250;not null;index" json:"slug"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` IsActive bool `gorm:"default:true;index" json:"is_active"` } func (Tag) TableName() string { return "tags" } func (t *Tag) BeforeCreate(tx *gorm.DB) (err error) { if t.Slug == "" { t.Slug, err = generateUniqueSlugForTag(tx, t.Tag) return err } return nil } func (t *Tag) BeforeUpdate(tx *gorm.DB) (err error) { if t.Slug == "" { t.Slug, err = generateUniqueSlugForTag(tx, t.Tag) return err } return nil } func generateUniqueSlugForTag(db *gorm.DB, tag string) (string, error) { slug := normalizeSlug(tag) base := slug var count int64 try := 1 for { db.Model(&Tag{}).Where("slug = ?", slug).Count(&count) if count == 0 { return slug, nil } slug = fmt.Sprintf("%s-%d", base, try) try++ if try > 1000 { return "", errors.New("unable to generate unique slug") } } } // Post model type Post struct { ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` Title string `gorm:"size:254;not null" json:"title"` UserID *uint64 `gorm:"type:bigint unsigned;index" json:"user_id"` User *User `gorm:"foreignKey:UserID" json:"user,omitempty"` Content string `gorm:"type:text" json:"content"` Categories []*Category `gorm:"many2many:post_categories;" json:"categories"` Keywords string `gorm:"size:254" json:"keywords"` Tags []*Tag `gorm:"many2many:post_tags;" json:"tags"` Image string `gorm:"size:1024" json:"image"` Thumb string `gorm:"size:1024" json:"thumb"` Video string `gorm:"size:254;default:'none'" json:"video"` Slug string `gorm:"size:250;not null;index" json:"slug"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` IsActive bool `gorm:"default:true;index" json:"is_active"` IsFront bool `gorm:"default:true;index" json:"is_front"` ParentID *uint64 `gorm:"type:bigint unsigned;index" json:"parent_id"` Parent *Post `gorm:"foreignKey:ParentID" json:"parent,omitempty"` Children []*Post `gorm:"foreignKey:ParentID" json:"children,omitempty"` } func (Post) TableName() string { return "posts" } func (p *Post) BeforeCreate(tx *gorm.DB) (err error) { if p.Slug == "" { p.Slug, err = generateUniqueSlugForPost(tx, p.Title) if err != nil { return err } } // Note: Thumbnail generation should be handled in the upload flow. return nil } func (p *Post) BeforeUpdate(tx *gorm.DB) (err error) { if p.Slug == "" { p.Slug, err = generateUniqueSlugForPost(tx, p.Title) return err } return nil } func generateUniqueSlugForPost(db *gorm.DB, title string) (string, error) { slug := normalizeSlug(title) base := slug var count int64 try := 1 for { db.Model(&Post{}).Where("slug = ?", slug).Count(&count) if count == 0 { return slug, nil } slug = fmt.Sprintf("%s-%d", base, try) try++ if try > 1000 { return "", errors.New("unable to generate unique slug") } } } // CategoryView model type CategoryView struct { ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` CategoryID uint64 `gorm:"type:bigint unsigned;index" json:"category_id"` Category *Category `gorm:"foreignKey:CategoryID" json:"category"` IPAddress string `gorm:"size:45;index" json:"ip_address"` UserAgent string `gorm:"type:text" json:"user_agent"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` } func (CategoryView) TableName() string { return "category_views" } // Comment model type Comment struct { ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` UserID uint64 `gorm:"type:bigint unsigned;index" json:"user_id"` ProductID uint64 `gorm:"type:bigint unsigned;index" json:"product_id"` Product Post `gorm:"foreignKey:ProductID" json:"product"` Title string `gorm:"size:254" json:"title"` Body string `gorm:"type:text" json:"body"` CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` IsActive bool `gorm:"default:true;index" json:"is_active"` Slug string `gorm:"size:250;index" json:"slug"` ParentID *uint64 `gorm:"type:bigint unsigned;index" json:"parent_id"` Parent *Comment `gorm:"foreignKey:ParentID" json:"parent,omitempty"` Children []*Comment `gorm:"foreignKey:ParentID" json:"children,omitempty"` } func (Comment) TableName() string { return "comments" } func (c *Comment) BeforeCreate(tx *gorm.DB) (err error) { if c.Slug == "" { c.Slug, err = generateUniqueSlugForComment(tx, c.Title) return err } return nil } func (c *Comment) BeforeUpdate(tx *gorm.DB) (err error) { if c.Slug == "" { c.Slug, err = generateUniqueSlugForComment(tx, c.Title) return err } return nil } func generateUniqueSlugForComment(db *gorm.DB, title string) (string, error) { slug := normalizeSlug(title) base := slug var count int64 try := 1 for { db.Model(&Comment{}).Where("slug = ?", slug).Count(&count) if count == 0 { return slug, nil } slug = fmt.Sprintf("%s-%d", base, try) try++ if try > 1000 { return "", errors.New("unable to generate unique slug") } } } // normalizeSlug replaces Turkish characters, lowercases and makes a basic slug. func normalizeSlug(s string) string { replacer := strings.NewReplacer( "ı", "i", "İ", "i", "ç", "c", "Ç", "c", "ş", "s", "Ş", "s", "ö", "o", "Ö", "o", "ü", "u", "Ü", "u", " ", "-", ) s = replacer.Replace(s) s = strings.ToLower(s) s = strings.TrimSpace(s) // remove multiple dashes for strings.Contains(s, "--") { s = strings.ReplaceAll(s, "--", "-") } // remove extension-like parts s = strings.Trim(s, "-._") // sanitize file-like chars s = filepath.Clean(s) return s }