package image import ( "errors" "io" "mime/multipart" "os" "strconv" "clickandjoin.app/storageserver/modules/utils" gocnjhelper "git.clickandjoin.umbach.dev/ClickandJoin/go-cnj-helper" "github.com/h2non/bimg" ) // is specified in the form data of the frontend const FormFileKey = "file" const MaxAvatarSize = 2 * 1024 * 1024 // 2 MB // filename len = 36 + dot . 1 + longestFileType 4 // example: cf75ace7-da4c-434d-bf7d-0bdbcea5c57d.webp const MaxFileNameLen = 41 var validFileTypes = []string{"jpeg", "jpg", "png", "webp"} // represents the query parameter size for the dynamic image resolution // example ?size=1 -> 64x64 var ImageSizes = []int{32, 64, 150, 256, 512, 1024} const DefaultImageSize = 4 func FileTypeVerification(fileType string) bool { for _, validFileType := range validFileTypes { if fileType == "image/"+validFileType { return true } } return false } func createUserFolder(userId string) error { path := utils.GetUserStoragePath(userId) if _, err := os.Stat(path); errors.Is(err, os.ErrNotExist) { err = os.Mkdir(path, os.ModePerm) if err != nil && !errors.Is(err, os.ErrExist) { gocnjhelper.LogErrorf("Failed to create user folder, err: %s", err) return err } } return nil } func SaveImage(fileHeader *multipart.FileHeader, userId string, imagePath string, imageWidth int, imageHeight int, imageQuality int) (err error) { err = createUserFolder(userId) if err != nil { return err } f, err := fileHeader.Open() if err != nil { gocnjhelper.LogErrorf("Failed to open file header, err: %s", err) return err } fileData, err := io.ReadAll(f) if err != nil { gocnjhelper.LogErrorf("Failed to read file, err: %s", err) return err } // prevent images that have a small resolution from being made larger by the size specified size, err := bimg.NewImage(fileData).Size() if err != nil { gocnjhelper.LogErrorf("Failed to get image size, err: %s", err) return err } if size.Width < imageWidth { imageWidth = size.Width } if size.Height < imageHeight { imageHeight = size.Height } /* TODO: Remove this part of the conversion once a newer version of vips (>= vips-8.7.4-Sat) is installed (if moved to new server) All image formats that do not correspond to jpeg are converted, because with the currently installed version of vips the processing of other image formats does not work. Installing a new version of vips is too time consuming. This part must be removed once moved to the new server. */ newImage := fileData if fileHeader.Header.Get("Content-Type") != "image/jpeg" { newImage, err = bimg.NewImage(fileData).Convert(bimg.JPEG) if err != nil { gocnjhelper.LogErrorf("Failed to convert image to png, err: %s", err) return err } } newImage, err = bimg.NewImage(newImage).Process(bimg.Options{ Width: imageWidth, Height: imageHeight, Quality: imageQuality, }) if err != nil { gocnjhelper.LogErrorf("Failed to process image, err: %s", err) return err } err = bimg.Write(imagePath+".webp", newImage) if err != nil { gocnjhelper.LogErrorf("Failed to save image, err: %s", err) return err } return nil } func GetImage(userId string, fileName string, imageSize string) ([]byte, error) { imgSize, err := strconv.Atoi(imageSize) if err != nil { imgSize = DefaultImageSize } buffer, err := bimg.Read(utils.GetUserStoragePath(userId) + fileName) if err != nil { gocnjhelper.LogErrorf("Failed to read image, err: %s", err) return []byte{}, err } // check that the value has not been manipulated and is greater than the largest index value in the list if imgSize > len(ImageSizes)-1 { imgSize = DefaultImageSize } resolution := ImageSizes[imgSize] newImage, err := bimg.NewImage(buffer).ForceResize(resolution, resolution) if err != nil { gocnjhelper.LogErrorf("Failed to force resize, err: %s", err) return []byte{}, err } return newImage, nil } func DeleteOldAvatarImage(userId string) error { path := utils.GetUserStoragePath(userId) dir, err := os.ReadDir(path) if err != nil && !errors.Is(err, os.ErrNotExist) { gocnjhelper.LogErrorf("Failed to read dir, err: %s", err) return err } for _, file := range dir { os.Remove(path + file.Name()) } return nil }