first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 21:43:40 +03:00
commit f34e54c5a5
100 changed files with 27342 additions and 0 deletions

274
database/models/blog.go Normal file
View File

@@ -0,0 +1,274 @@
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
}

View File

@@ -0,0 +1,40 @@
package models
import (
"time"
)
// CorsWhitelist - CORS için izin verilen origin'ler
type CorsWhitelist struct {
ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"`
Origin string `gorm:"type:varchar(255);uniqueIndex;not null" json:"origin"`
Description string `gorm:"type:text" json:"description"`
IsActive bool `gorm:"default:true" json:"is_active"`
CreatedBy string `gorm:"type:varchar(255)" json:"created_by,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// CorsBlacklist - CORS için yasaklanan origin'ler
type CorsBlacklist struct {
ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"`
Origin string `gorm:"type:varchar(255);uniqueIndex;not null" json:"origin"`
Reason string `gorm:"type:text" json:"reason"`
IsActive bool `gorm:"default:true" json:"is_active"`
CreatedBy string `gorm:"type:varchar(255)" json:"created_by,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// RateLimitSetting - Rate limit ayarları
type RateLimitSetting struct {
ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"`
Name string `gorm:"type:varchar(100);uniqueIndex;not null" json:"name"` // e.g., "login", "register", "api"
Description string `gorm:"type:text" 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"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}

14
database/models/role.go Normal file
View File

@@ -0,0 +1,14 @@
package models
type Role struct {
ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"`
Name string `gorm:"uniqueIndex;not null" json:"name"` // admin, user
Description string `json:"description"`
Permissions []Permission `gorm:"many2many:role_permissions;" json:"permissions"`
}
type Permission struct {
ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"`
Name string `gorm:"uniqueIndex;not null" json:"name"` // user:read, user:write
Description string `json:"description"`
}

51
database/models/user.go Normal file
View File

@@ -0,0 +1,51 @@
package models
import (
"time"
"gorm.io/gorm"
)
// User model structure
type User struct {
ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"`
UserName string `json:"username"`
Email string `gorm:"uniqueIndex;not null" json:"email"`
Password string `json:"-"` // Password shouldn't be returned in JSON
Avatar string `gorm:"type:varchar(500)" json:"avatar,omitempty"` // Avatar URL from OAuth or uploaded
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
// Email verification: only required for email/password registration; OAuth users are treated as verified
// Changed to *bool to handle false values correctly with GORM defaults
EmailVerified *bool `gorm:"default:false" json:"email_verified"` // default false for email/password registration
EmailVerifyToken string `gorm:"index" json:"-"`
EmailVerifiedAt *time.Time `json:"email_verified_at,omitempty"`
SocialAccounts []SocialAccount `gorm:"foreignKey:UserID" json:"social_accounts,omitempty"`
Roles []Role `gorm:"many2many:user_roles;" json:"roles"`
}
// Helper to safely get EmailVerified status
func (u *User) IsEmailVerified() bool {
if u.EmailVerified == nil {
return false
}
return *u.EmailVerified
}
// SocialAccount model structure
type SocialAccount struct {
ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"`
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"`
Name string `json:"name,omitempty"` // Full name from provider
AvatarURL string `json:"avatar_url,omitempty"` // Avatar URL from provider
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// Hooks can be added here if needed