diff --git a/gateway/core/models/publicBook.go b/gateway/core/models/publicBook.go index d73bf48..4d0d9c1 100644 --- a/gateway/core/models/publicBook.go +++ b/gateway/core/models/publicBook.go @@ -1,6 +1,7 @@ package models import ( + "github.com/abaldeweg/warehouse-server/gateway/cover" "github.com/google/uuid" "gorm.io/gorm" ) @@ -36,6 +37,9 @@ type PublicBook struct { Removed bool `json:"-" gorm:"default:false"` Reserved bool `json:"-" gorm:"default:false"` Recommendation bool `json:"-" gorm:"default:false"` + CoverS string `json:"cover_s" gorm:"default:null"` + CoverM string `json:"cover_m" gorm:"default:null"` + CoverL string `json:"cover_l" gorm:"default:null"` } // TableName overrides the default table name for PublicBook model. @@ -60,5 +64,8 @@ func (book *PublicBook) AfterFind(tx *gorm.DB) (err error) { book.Cond = book.Condition.Name book.FormatName = book.Format.Name book.BranchCart = book.Branch.Cart + book.CoverS = cover.ShowCover("s", book.ID) + book.CoverM = cover.ShowCover("m", book.ID) + book.CoverL = cover.ShowCover("l", book.ID) return } diff --git a/gateway/cover/cover.go b/gateway/cover/cover.go index bdd8129..b67c784 100644 --- a/gateway/cover/cover.go +++ b/gateway/cover/cover.go @@ -1,156 +1,28 @@ package cover import ( - "bytes" "fmt" - "image" - "image/jpeg" - "image/png" - "mime/multipart" - "net/http" "os" "path/filepath" - - "github.com/disintegration/imaging" - "github.com/gin-gonic/gin" - "golang.org/x/image/webp" ) -const uploadsDir = "uploads" - -// SaveCover saves the uploaded cover image in different sizes. -func SaveCover(c *gin.Context, imageUUID string) { - imageData, err := c.FormFile("cover") - if err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": "Image upload required"}) - return - } - - if err := saveResizedImages(c, imageData, imageUUID); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"}) - return - } - - c.Status(http.StatusOK) -} - -func saveResizedImages(c *gin.Context, imageData *multipart.FileHeader, imageUUID string) error { - imagePath, err := saveUploadedImage(c, imageData, imageUUID) - if err != nil { - return fmt.Errorf("failed to save uploaded image: %w", err) - } - defer os.Remove(imagePath) - - sizes := []struct { - width int - suffix string - }{ - {400, "l"}, - {200, "m"}, - {100, "s"}, - } - - for _, size := range sizes { - resizedImagePath := filepath.Join(uploadsDir, fmt.Sprintf("%s-%s%s", imageUUID, size.suffix, ".jpg")) - - if err := resizeAndSaveImage(imagePath, resizedImagePath, size.width); err != nil { - return fmt.Errorf("failed to resize image: %w", err) - } - } +const ( + Quality = 75 +) - return nil +var Sizes = map[string]int{ + "l": 400, + "m": 200, + "s": 100, } -func saveUploadedImage(c *gin.Context, imageData *multipart.FileHeader, imageUUID string) (string, error) { - imageFilename := fmt.Sprintf("%s%s", imageUUID, filepath.Ext(imageData.Filename)) +func getPath() (string, error) { currentDir, _ := os.Getwd() - uploadsDirPath := filepath.Join(currentDir, uploadsDir) + uploadsDirPath := filepath.Join(currentDir, "uploads") if err := os.MkdirAll(uploadsDirPath, 0755); err != nil { return "", fmt.Errorf("failed to create uploads directory") } - imagePath := filepath.Join(uploadsDirPath, imageFilename) - if err := c.SaveUploadedFile(imageData, imagePath); err != nil { - return "", fmt.Errorf("failed to save image") - } - - return imagePath, nil -} - -func resizeAndSaveImage(imagePath string, resizedImagePath string, width int) error { - file, err := os.Open(imagePath) - if err != nil { - return fmt.Errorf("failed to open image: %w", err) - } - defer file.Close() - - var img image.Image - - fileHeader := make([]byte, 512) - if _, err := file.Read(fileHeader); err != nil { - return fmt.Errorf("failed to read file header: %w", err) - } - - mimeType := http.DetectContentType(fileHeader) - _, err = file.Seek(0, 0) - if err != nil { - return fmt.Errorf("failed to reset file pointer: %w", err) - } - - switch mimeType { - case "image/jpeg": - img, err = jpeg.Decode(file) - case "image/png": - img, err = png.Decode(file) - case "image/webp": - img, err = convertWebp(file) - default: - return fmt.Errorf("unsupported image format") - } - - if err != nil { - return fmt.Errorf("failed to decode image: %w", err) - } - - originalBounds := img.Bounds() - aspectRatio := float64(originalBounds.Dx()) / float64(originalBounds.Dy()) - height := int(float64(width) / aspectRatio) - - resizedImage := imaging.Resize(img, width, height, imaging.Lanczos) - - outFile, err := os.Create(resizedImagePath) - if err != nil { - return fmt.Errorf("failed to create resized image file: %w", err) - } - defer outFile.Close() - - err = jpeg.Encode(outFile, resizedImage, nil) - if err != nil { - return fmt.Errorf("failed to encode resized image: %w", err) - } - - return nil -} - -func convertWebp(file *os.File) (image.Image, error) { - var err error - var img image.Image - img, err = webp.Decode(file) - if err != nil { - return nil, err - } - - buf := new(bytes.Buffer) - err = jpeg.Encode(buf, img, nil) - if err != nil { - return nil, err - } - - img, err = jpeg.Decode(buf) - if err != nil { - return nil, err - } - - return img, nil + return uploadsDirPath, nil } diff --git a/gateway/cover/cover_save.go b/gateway/cover/cover_save.go new file mode 100644 index 0000000..bdd8129 --- /dev/null +++ b/gateway/cover/cover_save.go @@ -0,0 +1,156 @@ +package cover + +import ( + "bytes" + "fmt" + "image" + "image/jpeg" + "image/png" + "mime/multipart" + "net/http" + "os" + "path/filepath" + + "github.com/disintegration/imaging" + "github.com/gin-gonic/gin" + "golang.org/x/image/webp" +) + +const uploadsDir = "uploads" + +// SaveCover saves the uploaded cover image in different sizes. +func SaveCover(c *gin.Context, imageUUID string) { + imageData, err := c.FormFile("cover") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Image upload required"}) + return + } + + if err := saveResizedImages(c, imageData, imageUUID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Internal Server Error"}) + return + } + + c.Status(http.StatusOK) +} + +func saveResizedImages(c *gin.Context, imageData *multipart.FileHeader, imageUUID string) error { + imagePath, err := saveUploadedImage(c, imageData, imageUUID) + if err != nil { + return fmt.Errorf("failed to save uploaded image: %w", err) + } + defer os.Remove(imagePath) + + sizes := []struct { + width int + suffix string + }{ + {400, "l"}, + {200, "m"}, + {100, "s"}, + } + + for _, size := range sizes { + resizedImagePath := filepath.Join(uploadsDir, fmt.Sprintf("%s-%s%s", imageUUID, size.suffix, ".jpg")) + + if err := resizeAndSaveImage(imagePath, resizedImagePath, size.width); err != nil { + return fmt.Errorf("failed to resize image: %w", err) + } + } + + return nil +} + +func saveUploadedImage(c *gin.Context, imageData *multipart.FileHeader, imageUUID string) (string, error) { + imageFilename := fmt.Sprintf("%s%s", imageUUID, filepath.Ext(imageData.Filename)) + currentDir, _ := os.Getwd() + uploadsDirPath := filepath.Join(currentDir, uploadsDir) + + if err := os.MkdirAll(uploadsDirPath, 0755); err != nil { + return "", fmt.Errorf("failed to create uploads directory") + } + + imagePath := filepath.Join(uploadsDirPath, imageFilename) + if err := c.SaveUploadedFile(imageData, imagePath); err != nil { + return "", fmt.Errorf("failed to save image") + } + + return imagePath, nil +} + +func resizeAndSaveImage(imagePath string, resizedImagePath string, width int) error { + file, err := os.Open(imagePath) + if err != nil { + return fmt.Errorf("failed to open image: %w", err) + } + defer file.Close() + + var img image.Image + + fileHeader := make([]byte, 512) + if _, err := file.Read(fileHeader); err != nil { + return fmt.Errorf("failed to read file header: %w", err) + } + + mimeType := http.DetectContentType(fileHeader) + _, err = file.Seek(0, 0) + if err != nil { + return fmt.Errorf("failed to reset file pointer: %w", err) + } + + switch mimeType { + case "image/jpeg": + img, err = jpeg.Decode(file) + case "image/png": + img, err = png.Decode(file) + case "image/webp": + img, err = convertWebp(file) + default: + return fmt.Errorf("unsupported image format") + } + + if err != nil { + return fmt.Errorf("failed to decode image: %w", err) + } + + originalBounds := img.Bounds() + aspectRatio := float64(originalBounds.Dx()) / float64(originalBounds.Dy()) + height := int(float64(width) / aspectRatio) + + resizedImage := imaging.Resize(img, width, height, imaging.Lanczos) + + outFile, err := os.Create(resizedImagePath) + if err != nil { + return fmt.Errorf("failed to create resized image file: %w", err) + } + defer outFile.Close() + + err = jpeg.Encode(outFile, resizedImage, nil) + if err != nil { + return fmt.Errorf("failed to encode resized image: %w", err) + } + + return nil +} + +func convertWebp(file *os.File) (image.Image, error) { + var err error + var img image.Image + img, err = webp.Decode(file) + if err != nil { + return nil, err + } + + buf := new(bytes.Buffer) + err = jpeg.Encode(buf, img, nil) + if err != nil { + return nil, err + } + + img, err = jpeg.Decode(buf) + if err != nil { + return nil, err + } + + return img, nil +} diff --git a/gateway/cover/cover_test.go b/gateway/cover/cover_save_test.go similarity index 100% rename from gateway/cover/cover_test.go rename to gateway/cover/cover_save_test.go diff --git a/gateway/cover/cover_show.go b/gateway/cover/cover_show.go new file mode 100644 index 0000000..78ff0c2 --- /dev/null +++ b/gateway/cover/cover_show.go @@ -0,0 +1,24 @@ +package cover + +import ( + "encoding/base64" + "os" + "path/filepath" + + "github.com/google/uuid" +) + +func ShowCover(size string, bookID uuid.UUID) string { + path, _ := getPath() + filename := filepath.Join(path, bookID.String()+"-"+size+".jpg") + + if _, err := os.Stat(filename); err != nil { + filename = filepath.Join(path, "none.jpg") + } + + data, err := os.ReadFile(filename) + if err != nil { + return "" + } + return "data:image/jpeg;base64," + base64.StdEncoding.EncodeToString(data) +} diff --git a/gateway/uploads/none.jpg b/gateway/uploads/none.jpg new file mode 100644 index 0000000..c3ba5e9 Binary files /dev/null and b/gateway/uploads/none.jpg differ