package handlers import ( "fmt" "io" "log" "strconv" "jd-book-uploader/models" "jd-book-uploader/services" "jd-book-uploader/utils" "github.com/gofiber/fiber/v2" ) // UploadBook handles book upload requests func UploadBook(c *fiber.Ctx) error { ctx := c.Context() // Parse multipart form form, err := c.MultipartForm() if err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Failed to parse form data", }) } // Extract image file imageFiles := form.File["image"] if len(imageFiles) == 0 { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Image file is required", }) } imageFile := imageFiles[0] // Validate file type allowedTypes := map[string]bool{ "image/png": true, "image/jpeg": true, "image/jpg": true, "image/webp": true, } if !allowedTypes[imageFile.Header.Get("Content-Type")] { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Invalid file type. Only PNG, JPEG, and WEBP are allowed", }) } // Validate file size (max 10MB) maxSize := int64(10 * 1024 * 1024) // 10MB if imageFile.Size > maxSize { return c.Status(fiber.StatusRequestEntityTooLarge).JSON(fiber.Map{ "success": false, "error": "File size exceeds 10MB limit", }) } // Read image file file, err := imageFile.Open() if err != nil { log.Printf("Failed to open image file: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "error": "Failed to read image file", }) } defer file.Close() imageData, err := io.ReadAll(file) if err != nil { log.Printf("Failed to read image data: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "error": "Failed to read image data", }) } // Extract form fields bookName := c.FormValue("book_name") costStr := c.FormValue("cost") priceStr := c.FormValue("price") discountStr := c.FormValue("discount") quantityStr := c.FormValue("quantity") publisherAuthor := c.FormValue("publisher_author") category := c.FormValue("category") description := c.FormValue("description") // Validate required fields if err := validateBookFields(bookName, costStr, priceStr, quantityStr, publisherAuthor, category); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": err.Error(), }) } // Parse numeric fields cost, err := strconv.ParseFloat(costStr, 64) if err != nil || cost <= 0 { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Invalid cost value", }) } price, err := strconv.ParseFloat(priceStr, 64) if err != nil || price <= 0 { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Invalid price value", }) } discount := 0.0 if discountStr != "" { discount, err = strconv.ParseFloat(discountStr, 64) if err != nil || discount < 0 { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Invalid discount value", }) } } quantity, err := strconv.Atoi(quantityStr) if err != nil || quantity <= 0 { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{ "success": false, "error": "Invalid quantity value", }) } // Generate slug from book name slug := utils.GenerateSlug(utils.CleanString(bookName)) // Check if slug already exists slugExists, err := services.BookSlugExists(ctx, slug) if err != nil { log.Printf("Failed to check slug existence: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "error": "Failed to validate product", }) } if slugExists { return c.Status(fiber.StatusConflict).JSON(fiber.Map{ "success": false, "error": fmt.Sprintf("A book with slug '%s' already exists", slug), }) } // Prepare filename: slug.png filename := fmt.Sprintf("%s.png", slug) // Upload image to Firebase Storage with correct path imageURL, err := services.UploadImage(ctx, imageData, "/jd-bookshop/books", filename) if err != nil { log.Printf("Firebase upload failed: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "error": "Failed to upload image", }) } // Create book request bookReq := &models.BookCreateRequest{ BookName: utils.CleanString(bookName), Cost: cost, Price: price, Discount: discount, Quantity: quantity, PublisherAuthor: utils.CleanString(publisherAuthor), Category: utils.CleanString(category), Description: utils.CleanString(description), ImageURL: imageURL, } // Create book in database book, err := services.CreateBook(ctx, bookReq) if err != nil { log.Printf("Database insert failed: %v", err) return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ "success": false, "error": "Failed to create book", }) } // Return success response return c.Status(fiber.StatusCreated).JSON(fiber.Map{ "success": true, "data": fiber.Map{ "book_code": book.BookCode, "book_name": book.BookName, "image_url": book.ImageURL, "slug": book.Slug, "created_at": book.CreatedAt, }, }) } // validateBookFields validates required book fields func validateBookFields(bookName, cost, price, quantity, publisherAuthor, category string) error { if bookName == "" { return fmt.Errorf("book_name is required") } if len(bookName) > 200 { return fmt.Errorf("book_name exceeds 200 characters") } if cost == "" { return fmt.Errorf("cost is required") } if price == "" { return fmt.Errorf("price is required") } if quantity == "" { return fmt.Errorf("quantity is required") } if publisherAuthor == "" { return fmt.Errorf("publisher_author is required") } if len(publisherAuthor) > 200 { return fmt.Errorf("publisher_author exceeds 200 characters") } if category == "" { return fmt.Errorf("category is required") } if len(category) > 100 { return fmt.Errorf("category exceeds 100 characters") } return nil }