package admin import ( "gobeyhan/config" "gobeyhan/database" "gobeyhan/database/models" "gobeyhan/pkg/utils" "gobeyhan/views/admin/blog" "log" "net/http" "path/filepath" "strconv" "github.com/gin-gonic/gin" ) type BlogHandler struct{} func NewBlogHandler() *BlogHandler { return &BlogHandler{} } // List displays all blog posts func (h *BlogHandler) List(c *gin.Context) { var posts []models.Post err := database.DB. Preload("User"). Preload("Categories"). Preload("Tags"). Order("created_at DESC"). Find(&posts).Error if err != nil { log.Printf("[BlogHandler.List] Error fetching posts: %v", err) c.String(http.StatusInternalServerError, "Error fetching posts") return } blog.List(posts).Render(c.Request.Context(), c.Writer) } // New displays the create form func (h *BlogHandler) New(c *gin.Context) { var categories []models.Category var tags []models.Tag database.DB.Where("is_active = ?", true).Find(&categories) database.DB.Where("is_active = ?", true).Find(&tags) blog.Create(categories, tags, nil).Render(c.Request.Context(), c.Writer) } // Create handles post creation func (h *BlogHandler) Create(c *gin.Context) { title := c.PostForm("title") content := c.PostForm("content") keywords := c.PostForm("keywords") isActive := c.PostForm("is_active") == "on" isFront := c.PostForm("is_front") == "on" log.Printf("[BlogHandler.Create] Received: Title=%s, ContentSize=%d, Active=%v", title, len(content), isActive) // Validation errors := make(map[string]string) if title == "" { errors["title"] = "Title is required" } if content == "" { errors["content"] = "Content is required" } if len(errors) > 0 { log.Printf("[BlogHandler.Create] Validation failed: %v", errors) var categories []models.Category var tags []models.Tag database.DB.Where("is_active = ?", true).Find(&categories) database.DB.Where("is_active = ?", true).Find(&tags) blog.Create(categories, tags, errors).Render(c.Request.Context(), c.Writer) return } // Handle image upload imagePath := c.PostForm("image") // Fallback to manual link if provided file, err := c.FormFile("image_file") if err == nil && file != nil { opts := &utils.ImageOptions{ Width: config.AppConfig.PostImageWidth, Height: config.AppConfig.PostImageHeight, Quality: float32(config.AppConfig.PostImageQuality), Format: config.AppConfig.PostImageFormat, Mode: config.AppConfig.PostImageMode, } path, err := utils.SaveOptimizedImage(file, filepath.Join("uploads", "blog"), "post", opts) if err == nil { imagePath = path log.Printf("[BlogHandler.Create] Image saved to: %s", imagePath) } else { log.Printf("[BlogHandler.Create] Image upload error: %v", err) } } // Create post post := models.Post{ Title: title, Content: content, Keywords: keywords, Image: imagePath, IsActive: isActive, IsFront: isFront, } if err := database.DB.Create(&post).Error; err != nil { log.Printf("[BlogHandler.Create] DB Create error: %v", err) errors["general"] = "Error creating post: " + err.Error() var categories []models.Category var tags []models.Tag database.DB.Where("is_active = ?", true).Find(&categories) database.DB.Where("is_active = ?", true).Find(&tags) blog.Create(categories, tags, errors).Render(c.Request.Context(), c.Writer) return } log.Printf("[BlogHandler.Create] Post created successfully: ID=%d", post.ID) // Handle categories categoryIDs := c.PostFormArray("category_ids") if len(categoryIDs) > 0 { var categories []*models.Category for _, idStr := range categoryIDs { id, _ := strconv.ParseUint(idStr, 10, 64) categories = append(categories, &models.Category{ID: id}) } if err := database.DB.Model(&post).Association("Categories").Replace(categories); err != nil { log.Printf("[BlogHandler.Create] Error associating categories: %v", err) } } // Handle tags tagIDs := c.PostFormArray("tag_ids") if len(tagIDs) > 0 { var tags []*models.Tag for _, idStr := range tagIDs { id, _ := strconv.ParseUint(idStr, 10, 64) tags = append(tags, &models.Tag{ID: id}) } if err := database.DB.Model(&post).Association("Tags").Replace(tags); err != nil { log.Printf("[BlogHandler.Create] Error associating tags: %v", err) } } c.Redirect(http.StatusSeeOther, "/admin/blog") } // Edit displays the edit form func (h *BlogHandler) Edit(c *gin.Context) { idStr := c.Param("id") id, err := strconv.ParseUint(idStr, 10, 64) if err != nil { c.String(http.StatusBadRequest, "Invalid ID") return } var post models.Post err = database.DB. Preload("Categories"). Preload("Tags"). First(&post, id).Error if err != nil { log.Printf("[BlogHandler.Edit] Post not found: ID=%d, error=%v", id, err) c.String(http.StatusNotFound, "Post not found") return } var categories []models.Category var tags []models.Tag database.DB.Where("is_active = ?", true).Find(&categories) database.DB.Where("is_active = ?", true).Find(&tags) blog.Edit(&post, categories, tags, nil).Render(c.Request.Context(), c.Writer) } // Update handles post updates func (h *BlogHandler) Update(c *gin.Context) { id := c.Param("id") idUint, _ := strconv.ParseUint(id, 10, 64) title := c.PostForm("title") content := c.PostForm("content") keywords := c.PostForm("keywords") isActive := c.PostForm("is_active") == "on" isFront := c.PostForm("is_front") == "on" log.Printf("[BlogHandler.Update] Received update for ID=%s: Title=%s, ContentSize=%d", id, title, len(content)) // Validation errors := make(map[string]string) if title == "" { errors["title"] = "Title is required" } if content == "" { errors["content"] = "Content is required" } if len(errors) > 0 { log.Printf("[BlogHandler.Update] Validation failed: %v", errors) var post models.Post database.DB.Preload("Categories").Preload("Tags").First(&post, idUint) var categories []models.Category var tags []models.Tag database.DB.Where("is_active = ?", true).Find(&categories) database.DB.Where("is_active = ?", true).Find(&tags) blog.Edit(&post, categories, tags, errors).Render(c.Request.Context(), c.Writer) return } // Handle image upload imagePath := c.PostForm("image") // Keep existing or use manual link file, err := c.FormFile("image_file") if err == nil && file != nil { opts := &utils.ImageOptions{ Width: config.AppConfig.PostImageWidth, Height: config.AppConfig.PostImageHeight, Quality: float32(config.AppConfig.PostImageQuality), Format: config.AppConfig.PostImageFormat, Mode: config.AppConfig.PostImageMode, } path, err := utils.SaveOptimizedImage(file, filepath.Join("uploads", "blog"), "post", opts) if err == nil { imagePath = path log.Printf("[BlogHandler.Update] Image updated to: %s", imagePath) } else { log.Printf("[BlogHandler.Update] Image upload error: %v", err) } } // Update post updates := map[string]interface{}{ "title": title, "content": content, "keywords": keywords, "image": imagePath, "is_active": isActive, "is_front": isFront, } if err := database.DB.Model(&models.Post{}).Where("id = ?", id).Updates(updates).Error; err != nil { log.Printf("[BlogHandler.Update] DB Update error: %v", err) errors["general"] = "Error updating post" var post models.Post database.DB.Preload("Categories").Preload("Tags").First(&post, idUint) var categories []models.Category var tags []models.Tag database.DB.Where("is_active = ?", true).Find(&categories) database.DB.Where("is_active = ?", true).Find(&tags) blog.Edit(&post, categories, tags, errors).Render(c.Request.Context(), c.Writer) return } // Update categories and tags separately (GORM Associations) var post models.Post if err := database.DB.First(&post, idUint).Error; err == nil { // Categories categoryIDs := c.PostFormArray("category_ids") var categories []*models.Category for _, idStr := range categoryIDs { catID, _ := strconv.ParseUint(idStr, 10, 64) categories = append(categories, &models.Category{ID: catID}) } database.DB.Model(&post).Association("Categories").Replace(categories) // Tags tagIDs := c.PostFormArray("tag_ids") var tags []*models.Tag for _, idStr := range tagIDs { tagID, _ := strconv.ParseUint(idStr, 10, 64) tags = append(tags, &models.Tag{ID: tagID}) } database.DB.Model(&post).Association("Tags").Replace(tags) } log.Printf("[BlogHandler.Update] Post updated successfully: ID=%s", id) c.Redirect(http.StatusSeeOther, "/admin/blog") } // Delete handles post deletion func (h *BlogHandler) Delete(c *gin.Context) { id := c.Param("id") log.Printf("[BlogHandler.Delete] Deleting ID=%s", id) if err := database.DB.Delete(&models.Post{}, "id = ?", id).Error; err != nil { log.Printf("[BlogHandler.Delete] Error: %v", err) c.String(http.StatusInternalServerError, "Error deleting post") return } c.Redirect(http.StatusSeeOther, "/admin/blog") } // ============================================ // CATEGORY HANDLERS // ============================================ func (h *BlogHandler) ListCategories(c *gin.Context) { var categories []models.Category database.DB.Order("`order` ASC").Find(&categories) blog.CategoryList(categories).Render(c.Request.Context(), c.Writer) } func (h *BlogHandler) NewCategory(c *gin.Context) { var categories []models.Category database.DB.Find(&categories) blog.CategoryForm(&models.Category{}, categories, nil).Render(c.Request.Context(), c.Writer) } func (h *BlogHandler) CreateCategory(c *gin.Context) { var cat models.Category cat.Title = c.PostForm("title") cat.Slug = c.PostForm("slug") cat.Desc = c.PostForm("description") cat.Keywords = c.PostForm("keywords") cat.IsActive = c.PostForm("is_active") == "on" order, _ := strconv.Atoi(c.PostForm("order")) cat.Order = order parentIDStr := c.PostForm("parent_id") if parentIDStr != "" { pID, _ := strconv.ParseUint(parentIDStr, 10, 64) cat.ParentID = &pID } if cat.Title == "" { errors := map[string]string{"title": "Title is required"} var categories []models.Category database.DB.Find(&categories) blog.CategoryForm(&cat, categories, errors).Render(c.Request.Context(), c.Writer) return } if err := database.DB.Create(&cat).Error; err != nil { errors := map[string]string{"general": err.Error()} var categories []models.Category database.DB.Find(&categories) blog.CategoryForm(&cat, categories, errors).Render(c.Request.Context(), c.Writer) return } c.Redirect(http.StatusSeeOther, "/admin/blog/categories") } func (h *BlogHandler) EditCategory(c *gin.Context) { id := c.Param("id") var cat models.Category if err := database.DB.First(&cat, id).Error; err != nil { c.String(http.StatusNotFound, "Category not found") return } var categories []models.Category database.DB.Find(&categories) blog.CategoryForm(&cat, categories, nil).Render(c.Request.Context(), c.Writer) } func (h *BlogHandler) UpdateCategory(c *gin.Context) { id := c.Param("id") var cat models.Category if err := database.DB.First(&cat, id).Error; err != nil { c.String(http.StatusNotFound, "Category not found") return } cat.Title = c.PostForm("title") cat.Slug = c.PostForm("slug") cat.Desc = c.PostForm("description") cat.Keywords = c.PostForm("keywords") cat.IsActive = c.PostForm("is_active") == "on" order, _ := strconv.Atoi(c.PostForm("order")) cat.Order = order parentIDStr := c.PostForm("parent_id") if parentIDStr != "" { pID, _ := strconv.ParseUint(parentIDStr, 10, 64) cat.ParentID = &pID } else { cat.ParentID = nil } if cat.Title == "" { errors := map[string]string{"title": "Title is required"} var categories []models.Category database.DB.Find(&categories) blog.CategoryForm(&cat, categories, errors).Render(c.Request.Context(), c.Writer) return } if err := database.DB.Save(&cat).Error; err != nil { errors := map[string]string{"general": err.Error()} var categories []models.Category database.DB.Find(&categories) blog.CategoryForm(&cat, categories, errors).Render(c.Request.Context(), c.Writer) return } c.Redirect(http.StatusSeeOther, "/admin/blog/categories") } func (h *BlogHandler) DeleteCategory(c *gin.Context) { id := c.Param("id") database.DB.Delete(&models.Category{}, id) c.Redirect(http.StatusSeeOther, "/admin/blog/categories") } // ============================================ // TAG HANDLERS // ============================================ func (h *BlogHandler) ListTags(c *gin.Context) { var tags []models.Tag database.DB.Order("tag ASC").Find(&tags) blog.TagList(tags).Render(c.Request.Context(), c.Writer) } func (h *BlogHandler) NewTag(c *gin.Context) { blog.TagForm(&models.Tag{}, nil).Render(c.Request.Context(), c.Writer) } func (h *BlogHandler) CreateTag(c *gin.Context) { var tag models.Tag tag.Tag = c.PostForm("tag") tag.Slug = c.PostForm("slug") tag.IsActive = true // Default if tag.Tag == "" { errors := map[string]string{"tag": "Tag name is required"} blog.TagForm(&tag, errors).Render(c.Request.Context(), c.Writer) return } if err := database.DB.Create(&tag).Error; err != nil { errors := map[string]string{"general": err.Error()} blog.TagForm(&tag, errors).Render(c.Request.Context(), c.Writer) return } c.Redirect(http.StatusSeeOther, "/admin/blog/tags") } func (h *BlogHandler) EditTag(c *gin.Context) { id := c.Param("id") var tag models.Tag if err := database.DB.First(&tag, id).Error; err != nil { c.String(http.StatusNotFound, "Tag not found") return } blog.TagForm(&tag, nil).Render(c.Request.Context(), c.Writer) } func (h *BlogHandler) UpdateTag(c *gin.Context) { id := c.Param("id") var tag models.Tag if err := database.DB.First(&tag, id).Error; err != nil { c.String(http.StatusNotFound, "Tag not found") return } tag.Tag = c.PostForm("tag") tag.Slug = c.PostForm("slug") if tag.Tag == "" { errors := map[string]string{"tag": "Tag name is required"} blog.TagForm(&tag, errors).Render(c.Request.Context(), c.Writer) return } if err := database.DB.Save(&tag).Error; err != nil { errors := map[string]string{"general": err.Error()} blog.TagForm(&tag, errors).Render(c.Request.Context(), c.Writer) return } c.Redirect(http.StatusSeeOther, "/admin/blog/tags") } func (h *BlogHandler) DeleteTag(c *gin.Context) { id := c.Param("id") database.DB.Delete(&models.Tag{}, id) c.Redirect(http.StatusSeeOther, "/admin/blog/tags") } // ============================================ // COMMENT HANDLERS // ============================================ func (h *BlogHandler) ListComments(c *gin.Context) { var comments []models.Comment database.DB.Preload("Product").Order("created_at DESC").Find(&comments) blog.CommentList(comments).Render(c.Request.Context(), c.Writer) } func (h *BlogHandler) EditComment(c *gin.Context) { id := c.Param("id") var comment models.Comment if err := database.DB.Preload("Product").First(&comment, id).Error; err != nil { c.String(http.StatusNotFound, "Comment not found") return } blog.CommentForm(&comment, nil).Render(c.Request.Context(), c.Writer) } func (h *BlogHandler) UpdateComment(c *gin.Context) { id := c.Param("id") var comment models.Comment if err := database.DB.First(&comment, id).Error; err != nil { c.String(http.StatusNotFound, "Comment not found") return } comment.Body = c.PostForm("body") comment.IsActive = c.PostForm("is_active") == "on" if err := database.DB.Save(&comment).Error; err != nil { errors := map[string]string{"general": err.Error()} blog.CommentForm(&comment, errors).Render(c.Request.Context(), c.Writer) return } c.Redirect(http.StatusSeeOther, "/admin/blog/comments") } func (h *BlogHandler) DeleteComment(c *gin.Context) { id := c.Param("id") database.DB.Delete(&models.Comment{}, id) c.Redirect(http.StatusSeeOther, "/admin/blog/comments") }