diff --git a/cmd/main.go b/cmd/main.go index 0145c7b..a2c3cfc 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "github.com/davidbyttow/govips/v2/vips" log "github.com/sirupsen/logrus" "imageStorage/config" "imageStorage/internal/app" @@ -13,11 +14,16 @@ import ( ) func main() { - c := config.NewConfig() - //c := config.DevConfig() + //c := config.NewConfig() + c := config.DevConfig() config.LogSetup(c.App.LogLevel) log.Infof("Log level: %s", c.App.LogLevel) + vips.Startup(&vips.Config{ + MaxCacheMem: 100 << 20, + }) + defer vips.Shutdown() + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer cancel() diff --git a/config.env b/config.env index c56fe47..29b52e7 100644 --- a/config.env +++ b/config.env @@ -2,4 +2,5 @@ HOST= HTTP_PORT= GRPC_PORT= DOMAIN= +VOLUME= LOG_LEVEL=Info \ No newline at end of file diff --git a/config/config.go b/config/config.go index 6c5b2e9..1d02a23 100644 --- a/config/config.go +++ b/config/config.go @@ -9,6 +9,7 @@ type AppConfig struct { HttpPort string GrpcPort string Domain string + Volume string LogLevel string } @@ -19,6 +20,7 @@ func NewConfig() *Config { HttpPort: getEnv("HTTP_PORT", ""), GrpcPort: getEnv("GRPC_PORT", ""), Domain: getEnv("DOMAIN", ""), + Volume: getEnv("VOLUME", ""), LogLevel: getEnv("LOG_LEVEL", ""), }, } diff --git a/config/devConfig.go b/config/devConfig.go index c6b142f..5c591ff 100644 --- a/config/devConfig.go +++ b/config/devConfig.go @@ -7,6 +7,7 @@ func DevConfig() *Config { HttpPort: getEnv("HTTP_PORT", "9280"), GrpcPort: getEnv("GRPC_PORT", "9200"), Domain: getEnv("DOMAIN", "http://localhost:9280"), + Volume: getEnv("VOLUME", ""), LogLevel: getEnv("LOG_LEVEL", "Debug"), }, } diff --git a/internal/convert/service.go b/internal/convert/service.go index 82c3ec9..66265a7 100644 --- a/internal/convert/service.go +++ b/internal/convert/service.go @@ -1,15 +1,48 @@ package convert +import ( + "github.com/davidbyttow/govips/v2/vips" + "io" +) + type service struct{} func newService() *service { return &service{} } -func (s *service) ConvertToJpeg() error { - return nil -} +func (s *service) ConvertAndSave(imageData []byte, fullWriter, thumbWriter io.Writer) error { + img, err := vips.NewImageFromBuffer(imageData) + if err != nil { + return err + } + defer img.Close() + + //force convert even it is already a jpeg image + converted, _, err := img.ExportJpeg(&vips.JpegExportParams{ + StripMetadata: true, + Quality: 90, + }) + + if _, err = fullWriter.Write(converted); err != nil { + return err + } + + if err = img.Thumbnail(300, 300, vips.InterestingNone); err != nil { + return err + } + + thumbnail, _, err := img.ExportJpeg(&vips.JpegExportParams{ + StripMetadata: true, + Quality: 80, + }) + if err != nil { + return err + } + + if _, err = thumbWriter.Write(thumbnail); err != nil { + return err + } -func (s *service) MakeThumbnail() error { return nil } diff --git a/internal/interfaces/convert.go b/internal/interfaces/convert.go index d4a75a3..122990d 100644 --- a/internal/interfaces/convert.go +++ b/internal/interfaces/convert.go @@ -1,6 +1,7 @@ package interfaces +import "io" + type Converter interface { - MakeThumbnail() error - ConvertToJpeg() error + ConvertAndSave(imageData []byte, fullWriter, thumbWriter io.Writer) error } diff --git a/internal/mainHandler/handler.go b/internal/mainHandler/handler.go index 5bba8ad..478e09b 100644 --- a/internal/mainHandler/handler.go +++ b/internal/mainHandler/handler.go @@ -9,16 +9,22 @@ import ( type ImageHandler struct { pb.UnimplementedImageStorageServer converter interfaces.Converter + domain string + volume string } type Deps struct { Converter interfaces.Converter + Domain string + Volume string } func NewHandler(deps Deps) *grpc.Server { srv := grpc.NewServer() imgSrv := ImageHandler{ converter: deps.Converter, + domain: deps.Domain, + volume: deps.Volume, } pb.RegisterImageStorageServer(srv, &imgSrv) diff --git a/internal/mainHandler/service.go b/internal/mainHandler/service.go index 215dada..14d2bd1 100644 --- a/internal/mainHandler/service.go +++ b/internal/mainHandler/service.go @@ -2,17 +2,103 @@ package mainHandler import ( "context" + "fmt" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" pb "imageStorage/pkg/proto/imageStorage" + "os" + "path/filepath" + "strings" ) -func (i *ImageHandler) UploadImage(ctx context.Context, req *pb.UploadImageRequest) (*pb.UploadImageResponse, error) { - return nil, nil +func (i *ImageHandler) UploadImage(ctx context.Context, req *pb.UploadMerchImageRequest) (*pb.UploadMerchImageResponse, error) { + if len(req.ImageData) == 0 { + return nil, status.Errorf(codes.InvalidArgument, "image data is empty") + } + + path := i._makeMerchImagePath(req.UserUuid, req.MerchUuid) + if err := os.MkdirAll(path, 0777); err != nil { + return nil, err + } + full := filepath.Join(path, "full.jpg") + thumbnail := filepath.Join(path, "thumbnail.jpg") + + fullFile, err := os.Create(full) + if err != nil { + return nil, err + } + defer fullFile.Close() + + thumbnailFile, err := os.Create(thumbnail) + if err != nil { + return nil, err + } + defer thumbnailFile.Close() + + if err = i.converter.ConvertAndSave(req.ImageData, fullFile, thumbnailFile); err != nil { + return nil, err + } + + response := &pb.UploadMerchImageResponse{ + FullImage: i._makeMerchImageURL(req.UserUuid, req.MerchUuid, full), + Thumbnail: i._makeMerchImageURL(req.UserUuid, req.MerchUuid, thumbnail), + } + + return response, nil } func (i *ImageHandler) GetImage(ctx context.Context, req *pb.GetImageRequest) (*pb.GetImageResponse, error) { - return nil, nil + path := i._makeMerchImagePath(req.UserUuid, req.MerchUuid) + unk := status.Error(codes.InvalidArgument, "unknown image type") + + switch req.ImgType { + case pb.ImageType_UNKNOWN: + return nil, unk + case pb.ImageType_FULL: + return &pb.GetImageResponse{ + Url: fmt.Sprintf("%s/%s", i.domain, filepath.Join(path, "full.jpg")), + Etag: "", + }, nil + case pb.ImageType_THUMBNAIL: + return &pb.GetImageResponse{ + Url: fmt.Sprintf("%s/%s", i.domain, filepath.Join(path, "thumbnail.jpg")), + Etag: "", + }, nil + + default: + return nil, unk + } } -func (i *ImageHandler) DeleteImage(ctx context.Context, req *pb.DeleteImageRequest) (*pb.GetImageResponse, error) { - return nil, nil +func (i *ImageHandler) DeleteImage(ctx context.Context, req *pb.DeleteImageRequest) (*emptypb.Empty, error) { + path := i._makeMerchImagePath(req.UserUuid, req.MerchUuid) + + entries, err := os.ReadDir(path) + if err != nil { + if os.IsNotExist(err) { + return &emptypb.Empty{}, nil + } + return nil, err + } + + for _, entry := range entries { + if !entry.IsDir() { + filePath := filepath.Join(path, entry.Name()) + if err := os.Remove(filePath); err != nil { + return nil, err + } + } + } + + return &emptypb.Empty{}, nil +} + +func (i *ImageHandler) _makeMerchImagePath(u, m string) string { + return fmt.Sprintf("%s/merchImages/%s/%s", i.volume, u, m) +} + +func (i *ImageHandler) _makeMerchImageURL(u, m, n string) string { + d := strings.TrimSuffix(i.domain, "/") + return fmt.Sprintf("%s/merchImages/%s/%s/%s", d, u, m, n) }