package services import ( "errors" "fmt" "regexp" "strings" "gauth-central/internal/database" "gauth-central/internal/models" "github.com/google/uuid" "gorm.io/gorm" ) type PostService struct{} func NewPostService() *PostService { return &PostService{} } func (s *PostService) CreatePost( title string, content string, keywords string, image string, video string, categoryIDs []string, tagIDs []string, parentID *uuid.UUID, isActive bool, isFront bool, ) (*models.Post, error) { slug := s.generateUniqueSlug(slugifyPost(title), "") if slug == "" { return nil, errors.New("slug cannot be empty") } post := models.Post{ Title: title, Content: content, Keywords: keywords, Image: image, Video: video, Slug: slug, ParentID: parentID, IsActive: isActive, IsFront: isFront, } if len(categoryIDs) > 0 { categories, err := s.fetchCategoriesByIDs(categoryIDs) if err != nil { return nil, err } post.Categories = categories } if len(tagIDs) > 0 { tags, err := s.fetchTagsByIDs(tagIDs) if err != nil { return nil, err } post.Tags = tags } if err := database.DB.Create(&post).Error; err != nil { return nil, err } return s.GetPostByID(post.ID.String()) } func (s *PostService) GetAllPosts(onlyActive bool, onlyFront bool, page int, limit int) ([]models.Post, int64, error) { var posts []models.Post var total int64 query := database.DB.Model(&models.Post{}) if onlyActive { query = query.Where("is_active = ?", true) } if onlyFront { query = query.Where("is_front = ?", true) } if err := query.Count(&total).Error; err != nil { return nil, 0, err } offset := (page - 1) * limit if err := query.Preload("Categories").Preload("Tags"). Order("created_at desc"). Limit(limit). Offset(offset). Find(&posts).Error; err != nil { return nil, 0, err } return posts, total, nil } func (s *PostService) GetPostByID(id string) (*models.Post, error) { var post models.Post if err := database.DB.Preload("Categories").Preload("Tags").Where("id = ?", id).First(&post).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("post not found") } return nil, err } return &post, nil } func (s *PostService) GetPostBySlug(slug string, onlyActive bool) (*models.Post, error) { var post models.Post query := database.DB.Preload("Categories").Preload("Tags").Where("slug = ?", slug) if onlyActive { query = query.Where("is_active = ?", true) } if err := query.First(&post).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return nil, errors.New("post not found") } return nil, err } return &post, nil } func (s *PostService) UpdatePost( id string, title *string, content *string, keywords *string, image *string, video *string, categoryIDs *[]string, tagIDs *[]string, parentID *uuid.UUID, parentIDSet bool, slug *string, isActive *bool, isFront *bool, ) (*models.Post, error) { post, err := s.GetPostByID(id) if err != nil { return nil, err } updates := map[string]interface{}{} if title != nil { updates["title"] = *title } if content != nil { updates["content"] = *content } if keywords != nil { updates["keywords"] = *keywords } if image != nil { updates["image"] = *image } if video != nil { updates["video"] = *video } if parentIDSet { updates["parent_id"] = parentID } if slug != nil { clean := slugifyPost(*slug) if clean == "" { return nil, errors.New("slug cannot be empty") } if s.slugExists(clean, id) { return nil, errors.New("slug already exists") } updates["slug"] = clean } if isActive != nil { updates["is_active"] = *isActive } if isFront != nil { updates["is_front"] = *isFront } if len(updates) > 0 { if err := database.DB.Model(post).Updates(updates).Error; err != nil { return nil, err } } if categoryIDs != nil { categories, err := s.fetchCategoriesByIDs(*categoryIDs) if err != nil { return nil, err } if err := database.DB.Model(post).Association("Categories").Replace(categories); err != nil { return nil, err } } if tagIDs != nil { tags, err := s.fetchTagsByIDs(*tagIDs) if err != nil { return nil, err } if err := database.DB.Model(post).Association("Tags").Replace(tags); err != nil { return nil, err } } return s.GetPostByID(id) } func (s *PostService) DeletePost(id string) error { result := database.DB.Delete(&models.Post{}, "id = ?", id) if result.Error != nil { return result.Error } if result.RowsAffected == 0 { return errors.New("post not found") } return nil } func (s *PostService) fetchCategoriesByIDs(categoryIDs []string) ([]models.PostCategory, error) { var categories []models.PostCategory if err := database.DB.Where("id IN ?", categoryIDs).Find(&categories).Error; err != nil { return nil, err } if len(categories) != len(categoryIDs) { return nil, errors.New("one or more categories not found") } return categories, nil } func (s *PostService) fetchTagsByIDs(tagIDs []string) ([]models.PostTag, error) { var tags []models.PostTag if err := database.DB.Where("id IN ?", tagIDs).Find(&tags).Error; err != nil { return nil, err } if len(tags) != len(tagIDs) { return nil, errors.New("one or more tags not found") } return tags, nil } func (s *PostService) generateUniqueSlug(baseSlug string, excludeID string) string { slug := baseSlug counter := 1 for s.slugExists(slug, excludeID) { slug = fmt.Sprintf("%s-%d", baseSlug, counter) counter++ } return slug } func (s *PostService) slugExists(slug string, excludeID string) bool { var count int64 query := database.DB.Model(&models.Post{}).Where("slug = ?", slug) if excludeID != "" { query = query.Where("id <> ?", excludeID) } query.Count(&count) return count > 0 } func slugifyPost(input string) string { clean := strings.TrimSpace(input) if clean == "" { return "" } replacer := strings.NewReplacer( "ı", "i", "İ", "i", "ş", "s", "Ş", "s", "ğ", "g", "Ğ", "g", "ç", "c", "Ç", "c", "ö", "o", "Ö", "o", "ü", "u", "Ü", "u", ) clean = strings.ToLower(replacer.Replace(clean)) re := regexp.MustCompile(`[^a-z0-9]+`) clean = re.ReplaceAllString(clean, "-") clean = strings.Trim(clean, "-") if clean == "" { return "post" } return clean }