Skip to content

Commit

Permalink
Merge pull request #30 from isd-sgcu/JOH-83/use-form-multipart
Browse files Browse the repository at this point in the history
[JOH-83] Use form multipart
  • Loading branch information
bookpanda authored Jan 29, 2024
2 parents c4daf1c + ee9c4e6 commit 2b7c8d7
Show file tree
Hide file tree
Showing 16 changed files with 138 additions and 39 deletions.
1 change: 1 addition & 0 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
APP_PORT=3001
APP_ENV=development
APP_MAX_FILE_SIZE=10

SERVICE_AUTH=localhost:3002
SERVICE_BACKEND=localhost:3003
Expand Down
1 change: 1 addition & 0 deletions docker-compose-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ services:
environment:
- APP_PORT=3001
- APP_ENV=development
- APP_MAX_FILE_SIZE=10
- SERVICE_AUTH=auth:3002
- SERVICE_BACKEND=backend:3003
- SERVICE_FILE=file:3004
Expand Down
2 changes: 2 additions & 0 deletions src/app/constant/error.constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ const InvalidArgumentMessage = "Invalid Argument"
const PetNotFoundMessage = "Pet not found"
const UserNotFoundMessage = "User not found"
const ImageNotFoundMessage = "Image not found"

const InvalidContentMessage = "Invalid content"
7 changes: 6 additions & 1 deletion src/app/dto/image.dto.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package dto

type DecomposedFile struct {
Filename string
Data []byte
}

type ImageResponse struct {
Id string `json:"id"`
Url string `json:"url"`
Expand All @@ -8,7 +13,7 @@ type ImageResponse struct {

type UploadImageRequest struct {
Filename string `json:"filename" validate:"required"`
Data []byte `json:"data" validate:"required"`
File []byte `json:"file" validate:"required"`
PetId string `json:"pet_id"`
}

Expand Down
45 changes: 25 additions & 20 deletions src/app/handler/image/image.handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,64 @@ 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"
"github.com/isd-sgcu/johnjud-gateway/src/constant/file"
imageSvc "github.com/isd-sgcu/johnjud-gateway/src/pkg/service/image"
"github.com/rs/zerolog/log"
)

type Handler struct {
service imageSvc.Service
validate validator.IDtoValidator
service imageSvc.Service
validate validator.IDtoValidator
maxFileSize int64
}

func NewHandler(service imageSvc.Service, validate validator.IDtoValidator) *Handler {
return &Handler{service, validate}
func NewHandler(service imageSvc.Service, validate validator.IDtoValidator, maxFileSize int64) *Handler {
return &Handler{
service: service,
validate: validate,
maxFileSize: int64(maxFileSize * 1024 * 1024),
}
}

// Upload is a function for uploading image to bucket
// @Summary Upload image
// @Description Returns the data of image. If updating pet, add petId. If creating pet, petId is not specified, but keep the imageId.
// @Param image body dto.UploadImageRequest true "upload image request dto"
// @Tags image
// @Accept json
// @Accept multipart/form-data
// @Produce json
// @Success 201 {object} dto.ImageResponse
// @Failure 400 {object} dto.ResponseBadRequestErr "Invalid request body"
// @Failure 500 {object} dto.ResponseInternalErr "Internal service error"
// @Failure 503 {object} dto.ResponseServiceDownErr "Service is down"
// @Router /v1/images [post]
func (h *Handler) Upload(c *router.FiberCtx) {
request := &dto.UploadImageRequest{}
err := c.Bind(request)
filename := c.GetFormData("filename")
petId := c.GetFormData("petId")
file, err := c.File("file", file.AllowContentType, h.maxFileSize)
if err != nil {
log.Error().
Err(err).
Str("service", "image").
Str("module", "upload").
Msg("Invalid content")
c.JSON(http.StatusBadRequest, dto.ResponseErr{
StatusCode: http.StatusBadRequest,
Message: constant.BindingRequestErrorMessage + err.Error(),
Message: constant.InvalidContentMessage,
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
request := &dto.UploadImageRequest{
Filename: filename,
File: file.Data,
PetId: petId,
}

response, respErr := h.service.Upload(request)
Expand Down
43 changes: 43 additions & 0 deletions src/app/router/context.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package router

import (
"bytes"
"errors"
"fmt"
"io"
"strings"

"github.com/gofiber/fiber/v2"
"github.com/google/uuid"
"github.com/isd-sgcu/johnjud-gateway/src/app/dto"
"github.com/isd-sgcu/johnjud-gateway/src/app/utils"
)

type IContext interface {
Expand All @@ -20,6 +26,8 @@ type IContext interface {
StoreValue(string, string)
Next() error
Queries() map[string]string
File(string, map[string]struct{}, int64) (*dto.DecomposedFile, error)
GetFormData(string) string
}

type FiberCtx struct {
Expand Down Expand Up @@ -96,6 +104,41 @@ func (c *FiberCtx) Queries() map[string]string {
return c.Ctx.Queries()
}

func (c *FiberCtx) File(key string, allowContent map[string]struct{}, maxSize int64) (*dto.DecomposedFile, error) {
file, err := c.Ctx.FormFile(key)
if err != nil {
return nil, err
}

if !utils.IsExisted(allowContent, file.Header["Content-Type"][0]) {
return nil, errors.New("Not allow content")
}

if file.Size > maxSize {
return nil, errors.New(fmt.Sprintf("Max file size is %v", maxSize))
}
content, err := file.Open()
if err != nil {
return nil, errors.New("Cannot read file")
}

defer content.Close()

buf := bytes.NewBuffer(nil)
if _, err := io.Copy(buf, content); err != nil {
return nil, err
}

return &dto.DecomposedFile{
Filename: file.Filename,
Data: buf.Bytes(),
}, nil
}

func (c *FiberCtx) GetFormData(key string) string {
return c.Ctx.FormValue(key)
}

//func (c *FiberCtx) Next() {
// err := c.Ctx.Next()
// fmt.Println(c.Route().Path)
Expand Down
5 changes: 4 additions & 1 deletion src/app/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,10 @@ func NewAPIv1(r *FiberRouter, conf config.App) *fiber.App {
}

func NewFiberRouter(authGuard IGuard, conf config.App) *FiberRouter {
r := fiber.New(fiber.Config{})
r := fiber.New(fiber.Config{
AppName: "JohnJud API",
BodyLimit: int(conf.MaxFileSize * 1024 * 1024),
})

r.Use(cors.New(cors.Config{
AllowOrigins: "*",
Expand Down
2 changes: 1 addition & 1 deletion src/app/utils/image/image.utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func ProtoToDtoList(in []*imageProto.Image) []*dto.ImageResponse {
func CreateDtoToProto(in *dto.UploadImageRequest) *imageProto.UploadImageRequest {
return &imageProto.UploadImageRequest{
Filename: in.Filename,
Data: in.Data,
Data: in.File,
PetId: in.PetId,
}
}
Expand Down
5 changes: 3 additions & 2 deletions src/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import (
)

type App struct {
Port int `mapstructure:"PORT"`
Env string `mapstructure:"ENV"`
Port int `mapstructure:"PORT"`
Env string `mapstructure:"ENV"`
MaxFileSize int64 `mapstructure:"MAX_FILE_SIZE"`
}

type Service struct {
Expand Down
8 changes: 8 additions & 0 deletions src/constant/file/file.constant.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package file

var AllowContentType = map[string]struct{}{
"image/jpeg": {},
"image/jpg": {},
"image/png": {},
"image/gif": {},
}
6 changes: 3 additions & 3 deletions src/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ const docTemplate = `{
"post": {
"description": "Returns the data of image. If updating pet, add petId. If creating pet, petId is not specified, but keep the imageId.",
"consumes": [
"application/json"
"multipart/form-data"
],
"produces": [
"application/json"
Expand Down Expand Up @@ -1492,11 +1492,11 @@ const docTemplate = `{
"dto.UploadImageRequest": {
"type": "object",
"required": [
"data",
"file",
"filename"
],
"properties": {
"data": {
"file": {
"type": "array",
"items": {
"type": "integer"
Expand Down
6 changes: 3 additions & 3 deletions src/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@
"post": {
"description": "Returns the data of image. If updating pet, add petId. If creating pet, petId is not specified, but keep the imageId.",
"consumes": [
"application/json"
"multipart/form-data"
],
"produces": [
"application/json"
Expand Down Expand Up @@ -1488,11 +1488,11 @@
"dto.UploadImageRequest": {
"type": "object",
"required": [
"data",
"file",
"filename"
],
"properties": {
"data": {
"file": {
"type": "array",
"items": {
"type": "integer"
Expand Down
6 changes: 3 additions & 3 deletions src/docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,7 @@ definitions:
type: object
dto.UploadImageRequest:
properties:
data:
file:
items:
type: integer
type: array
Expand All @@ -375,7 +375,7 @@ definitions:
pet_id:
type: string
required:
- data
- file
- filename
type: object
dto.User:
Expand Down Expand Up @@ -626,7 +626,7 @@ paths:
/v1/images:
post:
consumes:
- application/json
- multipart/form-data
description: Returns the data of image. If updating pet, add petId. If creating
pet, petId is not specified, but keep the imageId.
parameters:
Expand Down
2 changes: 1 addition & 1 deletion src/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func main() {

imageClient := imageProto.NewImageServiceClient(fileConn)
imageService := imageSvc.NewService(imageClient)
imageHandler := imageHdr.NewHandler(imageService, v)
imageHandler := imageHdr.NewHandler(imageService, v, conf.App.MaxFileSize)

petClient := petProto.NewPetServiceClient(backendConn)
petService := petSvc.NewService(petClient)
Expand Down
30 changes: 30 additions & 0 deletions src/mocks/router/context.mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions src/mocks/service/auth/auth.mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2b7c8d7

Please sign in to comment.