diff --git a/src/app/constant/error.constant.go b/src/app/constant/error.constant.go index 6b9a24a..5e378d3 100644 --- a/src/app/constant/error.constant.go +++ b/src/app/constant/error.constant.go @@ -13,3 +13,4 @@ const InvalidArgumentMessage = "Invalid Argument" const PetNotFoundMessage = "Pet not found" const UserNotFoundMessage = "User not found" +const ImageNotFoundMessage = "Image not found" diff --git a/src/app/dto/image.dto.go b/src/app/dto/image.dto.go index f30947a..98414f1 100644 --- a/src/app/dto/image.dto.go +++ b/src/app/dto/image.dto.go @@ -1,7 +1,26 @@ package dto -type ImageDto struct { +type ImageResponse struct { + Id string `json:"id"` + Url string `json:"url"` + ObjectKey string `json:"object_key"` +} + +type UploadImageRequest struct { Filename string `json:"filename" validate:"required"` Data []byte `json:"data" validate:"required"` PetId string `json:"pet_id" validate:"required"` } + +type DeleteImageResponse struct { + Success bool `json:"success"` +} + +type AssignPetRequest struct { + Ids []string `json:"ids" validate:"required"` + PetId string `json:"pet_id" validate:"required"` +} + +type AssignPetResponse struct { + Success bool `json:"success"` +} diff --git a/src/app/dto/pet.dto.go b/src/app/dto/pet.dto.go index 24c9fd0..39a77ff 100644 --- a/src/app/dto/pet.dto.go +++ b/src/app/dto/pet.dto.go @@ -4,11 +4,6 @@ import ( "github.com/isd-sgcu/johnjud-gateway/src/constant/pet" ) -type ImageResponse struct { - Id string `json:"id"` - Url string `json:"url"` -} - type PetResponse struct { Id string `json:"id"` Type string `json:"type"` diff --git a/src/app/handler/image/image.handler.go b/src/app/handler/image/image.handler.go index 7e496cb..143fc9e 100644 --- a/src/app/handler/image/image.handler.go +++ b/src/app/handler/image/image.handler.go @@ -1,6 +1,11 @@ -package auth +package image import ( + "net/http" + "strings" + + "github.com/isd-sgcu/johnjud-gateway/src/app/constant" + "github.com/isd-sgcu/johnjud-gateway/src/app/dto" "github.com/isd-sgcu/johnjud-gateway/src/app/router" "github.com/isd-sgcu/johnjud-gateway/src/app/validator" imageSvc "github.com/isd-sgcu/johnjud-gateway/src/pkg/service/image" @@ -8,21 +13,121 @@ import ( type Handler struct { service imageSvc.Service - validate *validator.DtoValidator + validate validator.IDtoValidator } -func NewHandler(service imageSvc.Service, validate *validator.DtoValidator) *Handler { +func NewHandler(service imageSvc.Service, validate validator.IDtoValidator) *Handler { return &Handler{service, validate} } func (h *Handler) FindByPetId(c *router.FiberCtx) { + id, err := c.ID() + if err != nil { + c.JSON(http.StatusBadRequest, dto.ResponseErr{ + StatusCode: http.StatusInternalServerError, + Message: constant.InvalidIDMessage, + Data: nil, + }) + return + } + + response, respErr := h.service.FindByPetId(id) + if respErr != nil { + c.JSON(respErr.StatusCode, respErr) + return + } + c.JSON(http.StatusOK, response) + return } func (h *Handler) Upload(c *router.FiberCtx) { + request := &dto.UploadImageRequest{} + err := c.Bind(request) + if err != nil { + c.JSON(http.StatusBadRequest, dto.ResponseErr{ + StatusCode: http.StatusBadRequest, + Message: constant.BindingRequestErrorMessage + err.Error(), + Data: nil, + }) + return + } + + if err := h.validate.Validate(request); err != nil { + var errorMessage []string + for _, reqErr := range err { + errorMessage = append(errorMessage, reqErr.Message) + } + c.JSON(http.StatusBadRequest, dto.ResponseErr{ + StatusCode: http.StatusBadRequest, + Message: constant.InvalidRequestBodyMessage + strings.Join(errorMessage, ", "), + Data: nil, + }) + return + } + response, respErr := h.service.Upload(request) + if respErr != nil { + c.JSON(respErr.StatusCode, respErr) + return + } + + c.JSON(http.StatusCreated, response) + return } func (h *Handler) Delete(c *router.FiberCtx) { + id, err := c.ID() + if err != nil { + c.JSON(http.StatusBadRequest, dto.ResponseErr{ + StatusCode: http.StatusBadRequest, + Message: err.Error(), + Data: nil, + }) + return + } + + res, errRes := h.service.Delete(id) + if errRes != nil { + c.JSON(errRes.StatusCode, errRes) + return + } + + c.JSON(http.StatusOK, res.Success) + return +} + +func (h *Handler) AssignPet(c *router.FiberCtx) { + request := &dto.AssignPetRequest{} + err := c.Bind(request) + if err != nil { + c.JSON(http.StatusBadRequest, dto.ResponseErr{ + StatusCode: http.StatusBadRequest, + Message: constant.BindingRequestErrorMessage + err.Error(), + Data: nil, + }) + return + } + + if err := h.validate.Validate(request); err != nil { + var errorMessage []string + for _, reqErr := range err { + errorMessage = append(errorMessage, reqErr.Message) + } + c.JSON(http.StatusBadRequest, dto.ResponseErr{ + StatusCode: http.StatusBadRequest, + Message: constant.InvalidRequestBodyMessage + strings.Join(errorMessage, ", "), + Data: nil, + }) + return + } + + response, respErr := h.service.AssignPet(request) + if respErr != nil { + c.JSON(respErr.StatusCode, respErr) + return + } + c.JSON(http.StatusOK, response) + return } diff --git a/src/app/handler/pet/pet.handler_test.go b/src/app/handler/pet/pet.handler_test.go index 1039e89..26318e6 100644 --- a/src/app/handler/pet/pet.handler_test.go +++ b/src/app/handler/pet/pet.handler_test.go @@ -16,6 +16,7 @@ import ( errConst "github.com/isd-sgcu/johnjud-gateway/src/app/constant" utils "github.com/isd-sgcu/johnjud-gateway/src/app/utils/pet" + petConst "github.com/isd-sgcu/johnjud-gateway/src/constant/pet" petProto "github.com/isd-sgcu/johnjud-go-proto/johnjud/backend/pet/v1" imgProto "github.com/isd-sgcu/johnjud-go-proto/johnjud/file/image/v1" diff --git a/src/app/router/image.router.go b/src/app/router/image.router.go index e696190..bebbc0c 100644 --- a/src/app/router/image.router.go +++ b/src/app/router/image.router.go @@ -15,3 +15,10 @@ func (r *FiberRouter) DeleteImage(path string, h func(ctx *FiberCtx)) { return nil }) } + +func (r *FiberRouter) GetImage(path string, h func(ctx *FiberCtx)) { + r.image.Delete(path, func(c *fiber.Ctx) error { + h(NewFiberCtx(c)) + return nil + }) +} diff --git a/src/app/router/router.go b/src/app/router/router.go index 2a2bb91..0f55c33 100644 --- a/src/app/router/router.go +++ b/src/app/router/router.go @@ -51,7 +51,7 @@ func NewFiberRouter(authGuard IGuard, conf config.App) *FiberRouter { user := GroupWithAuthMiddleware(r, "/user", authGuard.Use) pet := GroupWithAuthMiddleware(r, "/pets", authGuard.Use) - image := GroupWithAuthMiddleware(r, "/image", authGuard.Use) + image := GroupWithAuthMiddleware(r, "/images", authGuard.Use) like := GroupWithAuthMiddleware(r, "/likes", authGuard.Use) return &FiberRouter{r, auth, user, pet, image, like} diff --git a/src/app/service/image/image.service.go b/src/app/service/image/image.service.go index 6ff8668..ec00042 100644 --- a/src/app/service/image/image.service.go +++ b/src/app/service/image/image.service.go @@ -1,8 +1,17 @@ package image import ( + "context" + "net/http" + "time" + + "github.com/isd-sgcu/johnjud-gateway/src/app/constant" "github.com/isd-sgcu/johnjud-gateway/src/app/dto" + utils "github.com/isd-sgcu/johnjud-gateway/src/app/utils/image" proto "github.com/isd-sgcu/johnjud-go-proto/johnjud/file/image/v1" + "github.com/rs/zerolog/log" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" ) type Service struct { @@ -15,14 +24,165 @@ func NewService(client proto.ImageServiceClient) *Service { } } -func (s *Service) FindByPetId(string) ([]*proto.Image, *dto.ResponseErr) { - return nil, nil +func (s *Service) FindByPetId(petId string) ([]*dto.ImageResponse, *dto.ResponseErr) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + res, errRes := s.client.FindByPetId(ctx, &proto.FindImageByPetIdRequest{PetId: petId}) + if errRes != nil { + st, _ := status.FromError(errRes) + log.Error(). + Str("service", "image"). + Str("module", "find by pet id"). + Msg(st.Message()) + switch st.Code() { + case codes.NotFound: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusNotFound, + Message: constant.PetNotFoundMessage, + Data: nil, + } + case codes.Unavailable: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusServiceUnavailable, + Message: constant.UnavailableServiceMessage, + Data: nil, + } + default: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusInternalServerError, + Message: constant.InternalErrorMessage, + Data: nil, + } + } + } + return utils.ProtoToDtoList(res.Images), nil } -func (s *Service) Upload(in *dto.ImageDto) (*proto.Image, *dto.ResponseErr) { - return nil, nil +func (s *Service) Upload(in *dto.UploadImageRequest) (*dto.ImageResponse, *dto.ResponseErr) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + request := utils.CreateDtoToProto(in) + res, errRes := s.client.Upload(ctx, request) + if errRes != nil { + st, _ := status.FromError(errRes) + log.Error(). + Err(errRes). + Str("service", "image"). + Str("module", "upload"). + Msg(st.Message()) + switch st.Code() { + case codes.InvalidArgument: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusBadRequest, + Message: constant.InvalidArgumentMessage, + Data: nil, + } + case codes.Unavailable: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusServiceUnavailable, + Message: constant.UnavailableServiceMessage, + Data: nil, + } + default: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusInternalServerError, + Message: constant.InternalErrorMessage, + Data: nil, + } + } + } + return utils.ProtoToDto(res.Image), nil } -func (s *Service) Delete(id string) (bool, *dto.ResponseErr) { - return false, nil +func (s *Service) Delete(id string) (*dto.DeleteImageResponse, *dto.ResponseErr) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + request := &proto.DeleteImageRequest{ + Id: id, + } + + res, errRes := s.client.Delete(ctx, request) + if errRes != nil { + st, _ := status.FromError(errRes) + log.Error(). + Err(errRes). + Str("service", "image"). + Str("module", "delete"). + Msg(st.Message()) + switch st.Code() { + case codes.NotFound: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusNotFound, + Message: constant.ImageNotFoundMessage, + Data: nil, + } + case codes.Unavailable: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusServiceUnavailable, + Message: constant.UnavailableServiceMessage, + Data: nil, + } + default: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusInternalServerError, + Message: constant.InternalErrorMessage, + Data: nil, + } + } + } + return &dto.DeleteImageResponse{ + Success: res.Success, + }, nil +} + +func (s *Service) AssignPet(in *dto.AssignPetRequest) (*dto.AssignPetResponse, *dto.ResponseErr) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + request := &proto.AssignPetRequest{ + Ids: in.Ids, + PetId: in.PetId, + } + + res, errRes := s.client.AssignPet(ctx, request) + if errRes != nil { + st, _ := status.FromError(errRes) + log.Error(). + Err(errRes). + Str("service", "image"). + Str("module", "assign pet"). + Msg(st.Message()) + switch st.Code() { + case codes.InvalidArgument: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusBadRequest, + Message: constant.InvalidArgumentMessage, + Data: nil, + } + case codes.NotFound: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusNotFound, + Message: constant.PetNotFoundMessage, + Data: nil, + } + case codes.Unavailable: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusServiceUnavailable, + Message: constant.UnavailableServiceMessage, + Data: nil, + } + default: + return nil, &dto.ResponseErr{ + StatusCode: http.StatusInternalServerError, + Message: constant.InternalErrorMessage, + Data: nil, + } + } + } + return &dto.AssignPetResponse{ + Success: res.Success, + }, nil } diff --git a/src/app/utils/image/image.utils.go b/src/app/utils/image/image.utils.go new file mode 100644 index 0000000..705c34f --- /dev/null +++ b/src/app/utils/image/image.utils.go @@ -0,0 +1,54 @@ +package image + +import ( + "fmt" + + "github.com/isd-sgcu/johnjud-gateway/src/app/dto" + imageProto "github.com/isd-sgcu/johnjud-go-proto/johnjud/file/image/v1" +) + +func ProtoToDto(in *imageProto.Image) *dto.ImageResponse { + return &dto.ImageResponse{ + Id: in.Id, + Url: in.ImageUrl, + ObjectKey: in.ObjectKey, + } +} + +func ProtoToDtoList(in []*imageProto.Image) []*dto.ImageResponse { + var res []*dto.ImageResponse + for _, i := range in { + res = append(res, &dto.ImageResponse{ + Id: i.Id, + Url: i.ImageUrl, + ObjectKey: i.ObjectKey, + }) + } + return res +} + +func CreateDtoToProto(in *dto.UploadImageRequest) *imageProto.UploadImageRequest { + return &imageProto.UploadImageRequest{ + Filename: in.Filename, + Data: in.Data, + PetId: in.PetId, + } +} + +func MockImageList(n int) [][]*imageProto.Image { + var imagesList [][]*imageProto.Image + for i := 0; i <= n; i++ { + var images []*imageProto.Image + for j := 0; j <= 3; j++ { + images = append(images, &imageProto.Image{ + Id: fmt.Sprintf("%v%v", i, j), + PetId: fmt.Sprintf("%v%v", i, j), + ImageUrl: fmt.Sprintf("%v%v", i, j), + ObjectKey: fmt.Sprintf("%v%v", i, j), + }) + } + imagesList = append(imagesList, images) + } + + return imagesList +} diff --git a/src/app/utils/pet/pet.utils.go b/src/app/utils/pet/pet.utils.go index 434f820..56829bc 100644 --- a/src/app/utils/pet/pet.utils.go +++ b/src/app/utils/pet/pet.utils.go @@ -101,30 +101,6 @@ func UpdateDtoToProto(id string, in *dto.UpdatePetRequest) *petproto.UpdatePetRe }, } - if in.IsClubPet == nil { - req.Pet.IsClubPet = false - } else { - req.Pet.IsClubPet = *in.IsClubPet - } - - if in.IsSterile == nil { - req.Pet.IsSterile = false - } else { - req.Pet.IsSterile = *in.IsSterile - } - - if in.IsVaccinated == nil { - req.Pet.IsVaccinated = false - } else { - req.Pet.IsVaccinated = *in.IsVaccinated - } - - if in.IsVisible == nil { - req.Pet.IsVisible = false - } else { - req.Pet.IsVisible = *in.IsVisible - } - return req } diff --git a/src/constant/auth/auth.constant.go b/src/constant/auth/auth.constant.go index e25d157..281fc8c 100644 --- a/src/constant/auth/auth.constant.go +++ b/src/constant/auth/auth.constant.go @@ -10,12 +10,15 @@ var ExcludePath = map[string]struct{}{ } var AdminPath = map[string]struct{}{ - "DELETE /user/:id": {}, - "POST /pets": {}, - "PUT /pets/:id": {}, - "PUT /pets/:id/visible": {}, - "DELETE /pets/:id": {}, - //need to add image upload, delete, assignpet + "DELETE /user/:id": {}, + "POST /pets": {}, + "PUT /pets/:id": {}, + "PUT /pets/:id/visible": {}, + "DELETE /pets/:id": {}, + "POST /images/assign/:pet_id": {}, + "DELETE /images/:id": {}, + "POST /images/": {}, + "GET /images/:id": {}, } var VersionList = map[string]struct{}{ diff --git a/src/main.go b/src/main.go index f180270..8e785da 100644 --- a/src/main.go +++ b/src/main.go @@ -12,6 +12,7 @@ import ( authHdr "github.com/isd-sgcu/johnjud-gateway/src/app/handler/auth" healthcheck "github.com/isd-sgcu/johnjud-gateway/src/app/handler/healthcheck" + imageHdr "github.com/isd-sgcu/johnjud-gateway/src/app/handler/image" likeHdr "github.com/isd-sgcu/johnjud-gateway/src/app/handler/like" petHdr "github.com/isd-sgcu/johnjud-gateway/src/app/handler/pet" userHdr "github.com/isd-sgcu/johnjud-gateway/src/app/handler/user" @@ -107,6 +108,7 @@ func main() { imageClient := imageProto.NewImageServiceClient(fileConn) imageService := imageSvc.NewService(imageClient) + imageHandler := imageHdr.NewHandler(imageService, v) petClient := petProto.NewPetServiceClient(backendConn) petService := petSvc.NewService(petClient) @@ -142,6 +144,10 @@ func main() { r.PostLike("", likeHandler.Create) r.DeleteLike("/:id", likeHandler.Delete) + r.PostImage("/", imageHandler.Upload) + r.DeleteImage("/:id", imageHandler.Delete) + r.PostImage("/assign", imageHandler.AssignPet) + v1 := router.NewAPIv1(r, conf.App) go func() { diff --git a/src/mocks/service/image/image.mock.go b/src/mocks/service/image/image.mock.go index c09925a..6e7fb6b 100644 --- a/src/mocks/service/image/image.mock.go +++ b/src/mocks/service/image/image.mock.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: ./src/pkg/service/image/image.service.go +// +// Generated by this command: +// +// mockgen -source ./src/pkg/service/image/image.service.go -destination ./src/mocks/service/image/image.mock.go +// // Package mock_image is a generated GoMock package. package mock_image @@ -7,9 +12,8 @@ package mock_image import ( reflect "reflect" - gomock "github.com/golang/mock/gomock" dto "github.com/isd-sgcu/johnjud-gateway/src/app/dto" - v1 "github.com/isd-sgcu/johnjud-go-proto/johnjud/file/image/v1" + gomock "github.com/golang/mock/gomock" ) // MockService is a mock of Service interface. @@ -35,47 +39,62 @@ func (m *MockService) EXPECT() *MockServiceMockRecorder { return m.recorder } +// AssignPet mocks base method. +func (m *MockService) AssignPet(arg0 *dto.AssignPetRequest) (*dto.AssignPetResponse, *dto.ResponseErr) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AssignPet", arg0) + ret0, _ := ret[0].(*dto.AssignPetResponse) + ret1, _ := ret[1].(*dto.ResponseErr) + return ret0, ret1 +} + +// AssignPet indicates an expected call of AssignPet. +func (mr *MockServiceMockRecorder) AssignPet(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AssignPet", reflect.TypeOf((*MockService)(nil).AssignPet), arg0) +} + // Delete mocks base method. -func (m *MockService) Delete(arg0 string) (bool, *dto.ResponseErr) { +func (m *MockService) Delete(arg0 string) (*dto.DeleteImageResponse, *dto.ResponseErr) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Delete", arg0) - ret0, _ := ret[0].(bool) + ret0, _ := ret[0].(*dto.DeleteImageResponse) ret1, _ := ret[1].(*dto.ResponseErr) return ret0, ret1 } // Delete indicates an expected call of Delete. -func (mr *MockServiceMockRecorder) Delete(arg0 interface{}) *gomock.Call { +func (mr *MockServiceMockRecorder) Delete(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockService)(nil).Delete), arg0) } // FindByPetId mocks base method. -func (m *MockService) FindByPetId(arg0 string) ([]*v1.Image, *dto.ResponseErr) { +func (m *MockService) FindByPetId(arg0 string) ([]*dto.ImageResponse, *dto.ResponseErr) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "FindByPetId", arg0) - ret0, _ := ret[0].([]*v1.Image) + ret0, _ := ret[0].([]*dto.ImageResponse) ret1, _ := ret[1].(*dto.ResponseErr) return ret0, ret1 } // FindByPetId indicates an expected call of FindByPetId. -func (mr *MockServiceMockRecorder) FindByPetId(arg0 interface{}) *gomock.Call { +func (mr *MockServiceMockRecorder) FindByPetId(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByPetId", reflect.TypeOf((*MockService)(nil).FindByPetId), arg0) } // Upload mocks base method. -func (m *MockService) Upload(arg0 *dto.ImageDto) (*v1.Image, *dto.ResponseErr) { +func (m *MockService) Upload(arg0 *dto.UploadImageRequest) (*dto.ImageResponse, *dto.ResponseErr) { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Upload", arg0) - ret0, _ := ret[0].(*v1.Image) + ret0, _ := ret[0].(*dto.ImageResponse) ret1, _ := ret[1].(*dto.ResponseErr) return ret0, ret1 } // Upload indicates an expected call of Upload. -func (mr *MockServiceMockRecorder) Upload(arg0 interface{}) *gomock.Call { +func (mr *MockServiceMockRecorder) Upload(arg0 any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upload", reflect.TypeOf((*MockService)(nil).Upload), arg0) } diff --git a/src/pkg/service/image/image.service.go b/src/pkg/service/image/image.service.go index 4b68d29..e7ff7a1 100644 --- a/src/pkg/service/image/image.service.go +++ b/src/pkg/service/image/image.service.go @@ -2,11 +2,11 @@ package image import ( "github.com/isd-sgcu/johnjud-gateway/src/app/dto" - proto "github.com/isd-sgcu/johnjud-go-proto/johnjud/file/image/v1" ) type Service interface { - FindByPetId(string) ([]*proto.Image, *dto.ResponseErr) - Upload(*dto.ImageDto) (*proto.Image, *dto.ResponseErr) - Delete(string) (bool, *dto.ResponseErr) + FindByPetId(string) ([]*dto.ImageResponse, *dto.ResponseErr) + Upload(*dto.UploadImageRequest) (*dto.ImageResponse, *dto.ResponseErr) + Delete(string) (*dto.DeleteImageResponse, *dto.ResponseErr) + AssignPet(*dto.AssignPetRequest) (*dto.AssignPetResponse, *dto.ResponseErr) }