From 0dba3b27cf9f569e6a951ab633cbdf8ad98a6f85 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Thu, 20 Nov 2025 19:18:51 +0700 Subject: [PATCH] feat: update fixing sales agent --- .../controller/sales_agents.controller.go | 17 ++++ .../mapper/sales_agents.mapper.go | 20 ++--- .../repository/sales_agents.repository.go | 7 ++ .../sales_agents/sales_agents.module.go | 1 + .../service/sales_agents.service.go | 79 +++++++++++++++++++ docs/swagger/docs.go | 56 +++++++++++++ docs/swagger/swagger.json | 56 +++++++++++++ docs/swagger/swagger.yaml | 36 +++++++++ 8 files changed, 263 insertions(+), 9 deletions(-) diff --git a/app/module/sales_agents/controller/sales_agents.controller.go b/app/module/sales_agents/controller/sales_agents.controller.go index 358fde3..e05d350 100644 --- a/app/module/sales_agents/controller/sales_agents.controller.go +++ b/app/module/sales_agents/controller/sales_agents.controller.go @@ -22,6 +22,7 @@ type SalesAgentsController interface { Save(c *fiber.Ctx) error Update(c *fiber.Ctx) error Delete(c *fiber.Ctx) error + Viewer(c *fiber.Ctx) error } func NewSalesAgentsController(salesAgentsService service.SalesAgentsService) SalesAgentsController { @@ -279,3 +280,19 @@ func (_i *salesAgentsController) Delete(c *fiber.Ctx) error { }) } +// Viewer SalesAgent +// @Summary Viewer SalesAgent +// @Description API for viewing SalesAgent profile picture file +// @Tags SalesAgents +// @Security Bearer +// @Param X-Client-Key header string true "Insert the X-Client-Key" +// @Param filename path string true "SalesAgent File Name (e.g., user_277788.png)" +// @Success 200 {file} file +// @Failure 400 {object} response.BadRequestError +// @Failure 401 {object} response.UnauthorizedError +// @Failure 500 {object} response.InternalServerError +// @Router /sales-agents/viewer/{filename} [get] +func (_i *salesAgentsController) Viewer(c *fiber.Ctx) error { + return _i.salesAgentsService.Viewer(c) +} + diff --git a/app/module/sales_agents/mapper/sales_agents.mapper.go b/app/module/sales_agents/mapper/sales_agents.mapper.go index 6e95d03..6f1918a 100644 --- a/app/module/sales_agents/mapper/sales_agents.mapper.go +++ b/app/module/sales_agents/mapper/sales_agents.mapper.go @@ -4,6 +4,7 @@ import ( "encoding/json" "jaecoo-be/app/database/entity" res "jaecoo-be/app/module/sales_agents/response" + "path/filepath" ) func SalesAgentsResponseMapper(agent *entity.SalesAgents, host string) *res.SalesAgentsResponse { @@ -17,22 +18,23 @@ func SalesAgentsResponseMapper(agent *entity.SalesAgents, host string) *res.Sale } response := &res.SalesAgentsResponse{ - ID: agent.ID, - Name: agent.Name, + ID: agent.ID, + Name: agent.Name, JobTitle: agent.JobTitle, - Phone: agent.Phone, - AgentType: agentType, + Phone: agent.Phone, + AgentType: agentType, ProfilePicturePath: agent.ProfilePicturePath, - IsActive: agent.IsActive, - CreatedAt: agent.CreatedAt, - UpdatedAt: agent.UpdatedAt, + IsActive: agent.IsActive, + CreatedAt: agent.CreatedAt, + UpdatedAt: agent.UpdatedAt, } if agent.ProfilePicturePath != nil && *agent.ProfilePicturePath != "" { - profilePictureUrl := host + "/sales-agents/profile-picture/viewer/" + *agent.ProfilePicturePath + // Extract filename from path + filename := filepath.Base(*agent.ProfilePicturePath) + profilePictureUrl := host + "/sales-agents/viewer/" + filename response.ProfilePictureUrl = &profilePictureUrl } return response } - diff --git a/app/module/sales_agents/repository/sales_agents.repository.go b/app/module/sales_agents/repository/sales_agents.repository.go index b709d70..e56f9be 100644 --- a/app/module/sales_agents/repository/sales_agents.repository.go +++ b/app/module/sales_agents/repository/sales_agents.repository.go @@ -21,6 +21,7 @@ type SalesAgentsRepository interface { Create(agent *entity.SalesAgents) (agentReturn *entity.SalesAgents, err error) Update(id uint, agent *entity.SalesAgents) (err error) Delete(id uint) (err error) + FindByProfilePicturePath(profilePicturePath string) (agent *entity.SalesAgents, err error) } func NewSalesAgentsRepository(db *database.Database, log zerolog.Logger) SalesAgentsRepository { @@ -91,3 +92,9 @@ func (_i *salesAgentsRepository) Delete(id uint) (err error) { return } +func (_i *salesAgentsRepository) FindByProfilePicturePath(profilePicturePath string) (agent *entity.SalesAgents, err error) { + agent = &entity.SalesAgents{} + err = _i.DB.DB.Where("profile_picture_path LIKE ? AND is_active = ?", "%"+profilePicturePath, true).First(agent).Error + return +} + diff --git a/app/module/sales_agents/sales_agents.module.go b/app/module/sales_agents/sales_agents.module.go index a993bc5..d6d1254 100644 --- a/app/module/sales_agents/sales_agents.module.go +++ b/app/module/sales_agents/sales_agents.module.go @@ -46,6 +46,7 @@ func (_i *SalesAgentsRouter) RegisterSalesAgentsRoutes() { // define routes _i.App.Route("/sales-agents", func(router fiber.Router) { router.Get("/", salesAgentsController.All) + router.Get("/viewer/:filename", salesAgentsController.Viewer) router.Get("/:id", salesAgentsController.Show) router.Post("/", salesAgentsController.Save) router.Put("/:id", salesAgentsController.Update) diff --git a/app/module/sales_agents/service/sales_agents.service.go b/app/module/sales_agents/service/sales_agents.service.go index b851b31..892b2fa 100644 --- a/app/module/sales_agents/service/sales_agents.service.go +++ b/app/module/sales_agents/service/sales_agents.service.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "io" "jaecoo-be/app/module/sales_agents/mapper" "jaecoo-be/app/module/sales_agents/repository" "jaecoo-be/app/module/sales_agents/request" @@ -12,6 +13,7 @@ import ( minioStorage "jaecoo-be/config/config" "jaecoo-be/utils/paginator" "math/rand" + "mime" "path/filepath" "strconv" "strings" @@ -36,6 +38,7 @@ type SalesAgentsService interface { Update(c *fiber.Ctx, id uint, req request.SalesAgentsUpdateRequest) (agent *response.SalesAgentsResponse, err error) Delete(id uint) (err error) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) + Viewer(c *fiber.Ctx) (err error) } func NewSalesAgentsService(repo repository.SalesAgentsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) SalesAgentsService { @@ -193,3 +196,79 @@ func (_i *salesAgentsService) Delete(id uint) (err error) { err = _i.Repo.Delete(id) return } + +func (_i *salesAgentsService) Viewer(c *fiber.Ctx) (err error) { + filename := c.Params("filename") + + // Find sales agent by filename (repository will search using LIKE pattern) + result, err := _i.Repo.FindByProfilePicturePath(filename) + if err != nil { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ + "error": true, + "msg": "Sales agent file not found", + }) + } + + if result.ProfilePicturePath == nil || *result.ProfilePicturePath == "" { + return c.Status(fiber.StatusNotFound).JSON(fiber.Map{ + "error": true, + "msg": "Sales agent profile picture path not found", + }) + } + + ctx := context.Background() + bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName + objectName := *result.ProfilePicturePath + + _i.Log.Info().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "SalesAgents:Viewer"). + Interface("data", objectName).Msg("") + + // Create minio connection + minioClient, err := _i.MinioStorage.ConnectMinio() + if err != nil { + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": err.Error(), + }) + } + + fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{}) + if err != nil { + _i.Log.Error().Str("timestamp", time.Now(). + Format(time.RFC3339)).Str("Service:Resource", "SalesAgents:Viewer"). + Interface("Error getting file", err).Msg("") + return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ + "error": true, + "msg": "Failed to retrieve file", + }) + } + defer fileContent.Close() + + // Determine Content-Type based on file extension + contentType := mime.TypeByExtension("." + getFileExtension(objectName)) + if contentType == "" { + contentType = "application/octet-stream" // fallback if no MIME type matches + } + + c.Set("Content-Type", contentType) + + if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil { + return err + } + + return +} + +func getFileExtension(filename string) string { + // split file name + parts := strings.Split(filename, ".") + + // jika tidak ada ekstensi, kembalikan string kosong + if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") { + return "" + } + + // ambil ekstensi terakhir + return parts[len(parts)-1] +} diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 5d8c84d..411817b 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -7324,6 +7324,62 @@ const docTemplate = `{ } } }, + "/sales-agents/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for viewing SalesAgent profile picture file", + "tags": [ + "SalesAgents" + ], + "summary": "Viewer SalesAgent", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "SalesAgent File Name (e.g., user_277788.png)", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "file" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/sales-agents/{id}": { "get": { "security": [ diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 956f0fc..07378dd 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -7313,6 +7313,62 @@ } } }, + "/sales-agents/viewer/{filename}": { + "get": { + "security": [ + { + "Bearer": [] + } + ], + "description": "API for viewing SalesAgent profile picture file", + "tags": [ + "SalesAgents" + ], + "summary": "Viewer SalesAgent", + "parameters": [ + { + "type": "string", + "description": "Insert the X-Client-Key", + "name": "X-Client-Key", + "in": "header", + "required": true + }, + { + "type": "string", + "description": "SalesAgent File Name (e.g., user_277788.png)", + "name": "filename", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "file" + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.BadRequestError" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.UnauthorizedError" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.InternalServerError" + } + } + } + } + }, "/sales-agents/{id}": { "get": { "security": [ diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index ce8c76b..d0bccf3 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -5579,6 +5579,42 @@ paths: summary: Update SalesAgent tags: - SalesAgents + /sales-agents/viewer/{filename}: + get: + description: API for viewing SalesAgent profile picture file + parameters: + - description: Insert the X-Client-Key + in: header + name: X-Client-Key + required: true + type: string + - description: SalesAgent File Name (e.g., user_277788.png) + in: path + name: filename + required: true + type: string + responses: + "200": + description: OK + schema: + type: file + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.BadRequestError' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.UnauthorizedError' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.InternalServerError' + security: + - Bearer: [] + summary: Viewer SalesAgent + tags: + - SalesAgents /user-levels: get: description: API for getting all UserLevels