From 981f9e8ba95af8129cf0da2426d5407b22285285 Mon Sep 17 00:00:00 2001 From: JokerTrickster Date: Sat, 26 Oct 2024 05:28:14 +0900 Subject: [PATCH] =?UTF-8?q?{feat}=20-=20=EC=9D=8C=EC=8B=9D=20=EC=9D=B4?= =?UTF-8?q?=EB=AF=B8=EC=A7=80=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20api=20=EA=B5=AC=ED=98=84=20\n=20#196?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/docs/docs.go | 56 +++++++++++++ src/docs/swagger.json | 56 +++++++++++++ src/docs/swagger.yaml | 49 +++++++++++ .../handler/checkImageUploadFoodHandler.go | 62 ++++++++++++++ src/features/food/handler/index.go | 1 + .../food/model/interface/IFoodHandler.go | 3 + .../food/model/interface/IFoodRepository.go | 5 +- .../food/model/interface/IFoodUseCase.go | 4 + .../model/request/checkImageUploadFood.go | 6 ++ .../checkImageUploadFoodRepository.go | 11 +++ src/features/food/repository/repository.go | 4 + .../usecase/checkImageUploadFoodUseCase.go | 29 +++++++ src/utils/aws/ses.go | 24 ++++-- src/utils/aws/template/foodUploadReport.html | 84 +++++++++++++++++++ src/utils/aws/template/foodUploadReport.json | 8 ++ 15 files changed, 396 insertions(+), 6 deletions(-) create mode 100644 src/features/food/handler/checkImageUploadFoodHandler.go create mode 100644 src/features/food/model/request/checkImageUploadFood.go create mode 100644 src/features/food/repository/checkImageUploadFoodRepository.go create mode 100644 src/features/food/usecase/checkImageUploadFoodUseCase.go create mode 100644 src/utils/aws/template/foodUploadReport.html create mode 100644 src/utils/aws/template/foodUploadReport.json diff --git a/src/docs/docs.go b/src/docs/docs.go index a6eb1f0..1fdf26f 100644 --- a/src/docs/docs.go +++ b/src/docs/docs.go @@ -653,6 +653,45 @@ const docTemplate = `{ } } }, + "/v0.1/foods/images/upload-check": { + "post": { + "description": "■ errCode with 400\nPARAM_BAD : 파라미터 오류\nUSER_NOT_FOUND : 유저가 존재하지 않음\n■ errCode with 401\nINVALID_AUTH_CODE : 인증 코드 검증 실패\nTOKEN_BAD : 잘못된 토큰\nINVALID_ACCESS_TOKEN : 잘못된 액세스 토큰\n\n■ errCode with 500\nINTERNAL_SERVER : 내부 로직 처리 실패\nINTERNAL_DB : DB 처리 실패\nGEMINI_INTERNAL_SERVER : Gemini 서버 내부 오류", + "produces": [ + "application/json" + ], + "tags": [ + "food" + ], + "summary": "음식 이미지 업로드 체크하기", + "parameters": [ + { + "description": "type", + "name": "type", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ReqCheckImageUploadFood" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, "/v0.1/foods/meta": { "get": { "description": "■ errCode with 400\nPARAM_BAD : 파라미터 오류\nUSER_NOT_FOUND : 유저가 존재하지 않음\n■ errCode with 401\nINVALID_AUTH_CODE : 인증 코드 검증 실패\nTOKEN_BAD : 잘못된 토큰\nINVALID_ACCESS_TOKEN : 잘못된 액세스 토큰\n\n■ errCode with 500\nINTERNAL_SERVER : 내부 로직 처리 실패\nINTERNAL_DB : DB 처리 실패\nGEMINI_INTERNAL_SERVER : Gemini 서버 내부 오류", @@ -1052,6 +1091,23 @@ const docTemplate = `{ } }, "definitions": { + "request.ReqCheckImageUploadFood": { + "type": "object", + "properties": { + "failedFoodList": { + "type": "array", + "items": { + "type": "string" + } + }, + "successFoodList": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "request.ReqKakaoOauth": { "type": "object", "properties": { diff --git a/src/docs/swagger.json b/src/docs/swagger.json index 86bd82c..9b4474d 100644 --- a/src/docs/swagger.json +++ b/src/docs/swagger.json @@ -642,6 +642,45 @@ } } }, + "/v0.1/foods/images/upload-check": { + "post": { + "description": "■ errCode with 400\nPARAM_BAD : 파라미터 오류\nUSER_NOT_FOUND : 유저가 존재하지 않음\n■ errCode with 401\nINVALID_AUTH_CODE : 인증 코드 검증 실패\nTOKEN_BAD : 잘못된 토큰\nINVALID_ACCESS_TOKEN : 잘못된 액세스 토큰\n\n■ errCode with 500\nINTERNAL_SERVER : 내부 로직 처리 실패\nINTERNAL_DB : DB 처리 실패\nGEMINI_INTERNAL_SERVER : Gemini 서버 내부 오류", + "produces": [ + "application/json" + ], + "tags": [ + "food" + ], + "summary": "음식 이미지 업로드 체크하기", + "parameters": [ + { + "description": "type", + "name": "type", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/request.ReqCheckImageUploadFood" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + }, + "400": { + "description": "Bad Request", + "schema": {} + }, + "500": { + "description": "Internal Server Error", + "schema": {} + } + } + } + }, "/v0.1/foods/meta": { "get": { "description": "■ errCode with 400\nPARAM_BAD : 파라미터 오류\nUSER_NOT_FOUND : 유저가 존재하지 않음\n■ errCode with 401\nINVALID_AUTH_CODE : 인증 코드 검증 실패\nTOKEN_BAD : 잘못된 토큰\nINVALID_ACCESS_TOKEN : 잘못된 액세스 토큰\n\n■ errCode with 500\nINTERNAL_SERVER : 내부 로직 처리 실패\nINTERNAL_DB : DB 처리 실패\nGEMINI_INTERNAL_SERVER : Gemini 서버 내부 오류", @@ -1041,6 +1080,23 @@ } }, "definitions": { + "request.ReqCheckImageUploadFood": { + "type": "object", + "properties": { + "failedFoodList": { + "type": "array", + "items": { + "type": "string" + } + }, + "successFoodList": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, "request.ReqKakaoOauth": { "type": "object", "properties": { diff --git a/src/docs/swagger.yaml b/src/docs/swagger.yaml index e3014a3..e3fb4da 100644 --- a/src/docs/swagger.yaml +++ b/src/docs/swagger.yaml @@ -1,4 +1,15 @@ definitions: + request.ReqCheckImageUploadFood: + properties: + failedFoodList: + items: + type: string + type: array + successFoodList: + items: + type: string + type: array + type: object request.ReqKakaoOauth: properties: token: @@ -1045,6 +1056,44 @@ paths: summary: 음식 이미지 업로드하기 tags: - food + /v0.1/foods/images/upload-check: + post: + description: |- + ■ errCode with 400 + PARAM_BAD : 파라미터 오류 + USER_NOT_FOUND : 유저가 존재하지 않음 + ■ errCode with 401 + INVALID_AUTH_CODE : 인증 코드 검증 실패 + TOKEN_BAD : 잘못된 토큰 + INVALID_ACCESS_TOKEN : 잘못된 액세스 토큰 + + ■ errCode with 500 + INTERNAL_SERVER : 내부 로직 처리 실패 + INTERNAL_DB : DB 처리 실패 + GEMINI_INTERNAL_SERVER : Gemini 서버 내부 오류 + parameters: + - description: type + in: body + name: type + required: true + schema: + $ref: '#/definitions/request.ReqCheckImageUploadFood' + produces: + - application/json + responses: + "200": + description: OK + schema: + type: boolean + "400": + description: Bad Request + schema: {} + "500": + description: Internal Server Error + schema: {} + summary: 음식 이미지 업로드 체크하기 + tags: + - food /v0.1/foods/meta: get: description: |- diff --git a/src/features/food/handler/checkImageUploadFoodHandler.go b/src/features/food/handler/checkImageUploadFoodHandler.go new file mode 100644 index 0000000..72ad322 --- /dev/null +++ b/src/features/food/handler/checkImageUploadFoodHandler.go @@ -0,0 +1,62 @@ +package handler + +import ( + "context" + _interface "main/features/food/model/interface" + "main/features/food/model/request" + "main/utils" + + "net/http" + + "github.com/labstack/echo/v4" +) + +type CheckImageUploadFoodHandler struct { + UseCase _interface.ICheckImageUploadFoodUseCase +} + +func NewCheckImageUploadFoodHandler(c *echo.Echo, useCase _interface.ICheckImageUploadFoodUseCase) _interface.ICheckImageUploadFoodHandler { + handler := &CheckImageUploadFoodHandler{ + UseCase: useCase, + } + c.POST("/v0.1/foods/images/upload-check", handler.CheckImageUpload) + return handler +} + +// 음식 이미지 업로드 체크하기 +// @Router /v0.1/foods/images/upload-check [post] +// @Summary 음식 이미지 업로드 체크하기 +// @Description +// @Description ■ errCode with 400 +// @Description PARAM_BAD : 파라미터 오류 +// @Description USER_NOT_FOUND : 유저가 존재하지 않음 +// @Description ■ errCode with 401 +// @Description INVALID_AUTH_CODE : 인증 코드 검증 실패 +// @Description TOKEN_BAD : 잘못된 토큰 +// @Description INVALID_ACCESS_TOKEN : 잘못된 액세스 토큰 +// @Description +// @Description ■ errCode with 500 +// @Description INTERNAL_SERVER : 내부 로직 처리 실패 +// @Description INTERNAL_DB : DB 처리 실패 +// @Description GEMINI_INTERNAL_SERVER : Gemini 서버 내부 오류 +// @Param type body request.ReqCheckImageUploadFood true "type" +// @Produce json +// @Success 200 {object} bool +// @Failure 400 {object} error +// @Failure 500 {object} error +// @Tags food +func (d *CheckImageUploadFoodHandler) CheckImageUpload(c echo.Context) error { + ctx := context.Background() + req := &request.ReqCheckImageUploadFood{} + if err := utils.ValidateReq(c, req); err != nil { + return err + } + + //business logic + err := d.UseCase.CheckImageUpload(ctx, req) + if err != nil { + return err + } + + return c.JSON(http.StatusOK, true) +} diff --git a/src/features/food/handler/index.go b/src/features/food/handler/index.go index e99b9d7..49400bc 100644 --- a/src/features/food/handler/index.go +++ b/src/features/food/handler/index.go @@ -19,4 +19,5 @@ func NewFoodHandler(c *echo.Echo) { NewEmptyImageFoodHandler(c, usecase.NewEmptyImageFoodUseCase(repository.NewEmptyImageFoodRepository(mysql.GormMysqlDB), mysql.DBTimeOut)) NewDailyRecommendFoodHandler(c, usecase.NewDailyRecommendFoodUseCase(repository.NewDailyRecommendFoodRepository(mysql.GormMysqlDB), mysql.DBTimeOut)) NewSaveFoodHandler(c, usecase.NewSaveFoodUseCase(repository.NewSaveFoodRepository(mysql.GormMysqlDB), mysql.DBTimeOut)) + NewCheckImageUploadFoodHandler(c, usecase.NewCheckImageUploadFoodUseCase(repository.NewCheckImageUploadFoodRepository(mysql.GormMysqlDB), mysql.DBTimeOut)) } diff --git a/src/features/food/model/interface/IFoodHandler.go b/src/features/food/model/interface/IFoodHandler.go index fed88a7..77c8403 100644 --- a/src/features/food/model/interface/IFoodHandler.go +++ b/src/features/food/model/interface/IFoodHandler.go @@ -33,3 +33,6 @@ type IDailyRecommendFoodHandler interface { type ISaveFoodHandler interface { Save(c echo.Context) error } +type ICheckImageUploadFoodHandler interface { + CheckImageUpload(c echo.Context) error +} \ No newline at end of file diff --git a/src/features/food/model/interface/IFoodRepository.go b/src/features/food/model/interface/IFoodRepository.go index 907f0f0..5ee2074 100644 --- a/src/features/food/model/interface/IFoodRepository.go +++ b/src/features/food/model/interface/IFoodRepository.go @@ -36,7 +36,7 @@ type IRankingFoodRepository interface { } type IImageUploadFoodRepository interface { - FindOneAndUpdateFoodImages(ctx context.Context,foodName, fileName string) error + FindOneAndUpdateFoodImages(ctx context.Context, foodName, fileName string) error } type IEmptyImageFoodRepository interface { @@ -52,3 +52,6 @@ type ISaveFoodRepository interface { SaveFood(ctx context.Context, foodDTO *mysql.Foods) error FindOneOrCreateFoodImage(ctx context.Context, foodImageDTO *mysql.FoodImages) (*mysql.FoodImages, error) } + +type ICheckImageUploadFoodRepository interface { +} diff --git a/src/features/food/model/interface/IFoodUseCase.go b/src/features/food/model/interface/IFoodUseCase.go index 054f04e..53efce0 100644 --- a/src/features/food/model/interface/IFoodUseCase.go +++ b/src/features/food/model/interface/IFoodUseCase.go @@ -40,3 +40,7 @@ type IDailyRecommendFoodUseCase interface { type ISaveFoodUseCase interface { Save(c context.Context, req *request.ReqSaveFood) error } + +type ICheckImageUploadFoodUseCase interface { + CheckImageUpload(c context.Context, req *request.ReqCheckImageUploadFood) error +} diff --git a/src/features/food/model/request/checkImageUploadFood.go b/src/features/food/model/request/checkImageUploadFood.go new file mode 100644 index 0000000..d901df2 --- /dev/null +++ b/src/features/food/model/request/checkImageUploadFood.go @@ -0,0 +1,6 @@ +package request + +type ReqCheckImageUploadFood struct { + FailedFoodList []string `json:"failedFoodList"` + SuccessFoodList []string `json:"successFoodList"` +} diff --git a/src/features/food/repository/checkImageUploadFoodRepository.go b/src/features/food/repository/checkImageUploadFoodRepository.go new file mode 100644 index 0000000..ca2a4b8 --- /dev/null +++ b/src/features/food/repository/checkImageUploadFoodRepository.go @@ -0,0 +1,11 @@ +package repository + +import ( + _interface "main/features/food/model/interface" + + "gorm.io/gorm" +) + +func NewCheckImageUploadFoodRepository(gormDB *gorm.DB) _interface.ICheckImageUploadFoodRepository { + return &CheckImageUploadFoodRepository{GormDB: gormDB} +} diff --git a/src/features/food/repository/repository.go b/src/features/food/repository/repository.go index f416ee0..165b781 100644 --- a/src/features/food/repository/repository.go +++ b/src/features/food/repository/repository.go @@ -39,3 +39,7 @@ type DailyRecommendFoodRepository struct { type SaveFoodRepository struct { GormDB *gorm.DB } + +type CheckImageUploadFoodRepository struct { + GormDB *gorm.DB +} diff --git a/src/features/food/usecase/checkImageUploadFoodUseCase.go b/src/features/food/usecase/checkImageUploadFoodUseCase.go new file mode 100644 index 0000000..c19abb3 --- /dev/null +++ b/src/features/food/usecase/checkImageUploadFoodUseCase.go @@ -0,0 +1,29 @@ +package usecase + +import ( + "context" + + _interface "main/features/food/model/interface" + "main/features/food/model/request" + "main/utils/aws" + + "time" +) + +type CheckImageUploadFoodUseCase struct { + Repository _interface.ICheckImageUploadFoodRepository + ContextTimeout time.Duration +} + +func NewCheckImageUploadFoodUseCase(repo _interface.ICheckImageUploadFoodRepository, timeout time.Duration) _interface.ICheckImageUploadFoodUseCase { + return &CheckImageUploadFoodUseCase{Repository: repo, ContextTimeout: timeout} +} + +func (d *CheckImageUploadFoodUseCase) CheckImageUpload(c context.Context, req *request.ReqCheckImageUploadFood) error { + _, cancel := context.WithTimeout(c, d.ContextTimeout) + defer cancel() + + aws.EmailSendFoodUploadReport(req.SuccessFoodList, req.FailedFoodList) + + return nil +} diff --git a/src/utils/aws/ses.go b/src/utils/aws/ses.go index 1a58495..5ce7ded 100644 --- a/src/utils/aws/ses.go +++ b/src/utils/aws/ses.go @@ -15,11 +15,12 @@ import ( type emailType string const ( - emailTypePassword = emailType("password") - emailTypeAuth = emailType("authCode") - emailTypeReport = emailType("report") - emailTypeSignup = emailType("signup") - emailTypeFoodNameReport = emailType("foodNameReport") + emailTypePassword = emailType("password") + emailTypeAuth = emailType("authCode") + emailTypeReport = emailType("report") + emailTypeSignup = emailType("signup") + emailTypeFoodNameReport = emailType("foodNameReport") + emailTypeFoodUploadReport = emailType("foodUploadReport") ) type sesMailData struct { @@ -35,6 +36,19 @@ type ReqReportSES struct { Reason string } +func EmailSendFoodUploadReport(successFoodList, failedFoodList []string) { + templateDataMap := map[string]string{ + "successFoodList": strings.Join(successFoodList, ", "), + "failedFoodList": strings.Join(failedFoodList, ", "), + } + templateDataJson, err := json.Marshal(templateDataMap) + if err != nil { + fmt.Println("Error marshaling template data:", err) + return + } + //"dtw7225@naver.com" + emailSend([]string{"pkjhj485@gmail.com"}, emailTypeFoodNameReport, string(templateDataJson), "foodUploadReport") +} func EmailSendFoodNameReport(foodNames []string) { currentDate := time.Now().Format("01-02") templateDataMap := map[string]string{ diff --git a/src/utils/aws/template/foodUploadReport.html b/src/utils/aws/template/foodUploadReport.html new file mode 100644 index 0000000..4eebeac --- /dev/null +++ b/src/utils/aws/template/foodUploadReport.html @@ -0,0 +1,84 @@ + + + + + + + Food Upload Status + + + + +
+
아래는 음식 이미지 업로드 결과입니다.
+
확인해서 음식 업로드 실패한 음식 이름은 음식 이름을 추가해주세요~
+ +
+

업로드 성공한 음식

+
{{successFoodList}}
+
+ +
+

업로드 실패한 음식

+
{{failedFoodList}}
+
+
+ + + \ No newline at end of file diff --git a/src/utils/aws/template/foodUploadReport.json b/src/utils/aws/template/foodUploadReport.json new file mode 100644 index 0000000..032cf81 --- /dev/null +++ b/src/utils/aws/template/foodUploadReport.json @@ -0,0 +1,8 @@ +{ + "Template": { + "TemplateName": "foodUploadReport", + "SubjectPart": "푸드픽 음식 이미지 업로드 결과 리포트", + "TextPart": "아래는 음식 이미지 업로드 결과입니다.\n확인해서 음식 업로드 실패한 음식 이름은 음식 이름을 추가해주세요~\n\n업로드 성공한 음식:\n{{successFoodList}}\n\n업로드 실패한 음식:\n{{failedFoodList}}", + "HtmlPart": "Food Upload Status
아래는 음식 이미지 업로드 결과입니다.
확인해서 음식 업로드 실패한 음식 이름은 음식 이름을 추가해주세요~

업로드 성공한 음식

{{successFoodList}}

업로드 실패한 음식

{{failedFoodList}}
" + } +} \ No newline at end of file