feat: update fixing sales agent

This commit is contained in:
hanif salafi 2025-11-20 19:18:51 +07:00
parent 62c44904c7
commit 0dba3b27cf
8 changed files with 263 additions and 9 deletions

View File

@ -22,6 +22,7 @@ type SalesAgentsController interface {
Save(c *fiber.Ctx) error Save(c *fiber.Ctx) error
Update(c *fiber.Ctx) error Update(c *fiber.Ctx) error
Delete(c *fiber.Ctx) error Delete(c *fiber.Ctx) error
Viewer(c *fiber.Ctx) error
} }
func NewSalesAgentsController(salesAgentsService service.SalesAgentsService) SalesAgentsController { 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)
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"jaecoo-be/app/database/entity" "jaecoo-be/app/database/entity"
res "jaecoo-be/app/module/sales_agents/response" res "jaecoo-be/app/module/sales_agents/response"
"path/filepath"
) )
func SalesAgentsResponseMapper(agent *entity.SalesAgents, host string) *res.SalesAgentsResponse { 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{ response := &res.SalesAgentsResponse{
ID: agent.ID, ID: agent.ID,
Name: agent.Name, Name: agent.Name,
JobTitle: agent.JobTitle, JobTitle: agent.JobTitle,
Phone: agent.Phone, Phone: agent.Phone,
AgentType: agentType, AgentType: agentType,
ProfilePicturePath: agent.ProfilePicturePath, ProfilePicturePath: agent.ProfilePicturePath,
IsActive: agent.IsActive, IsActive: agent.IsActive,
CreatedAt: agent.CreatedAt, CreatedAt: agent.CreatedAt,
UpdatedAt: agent.UpdatedAt, UpdatedAt: agent.UpdatedAt,
} }
if agent.ProfilePicturePath != nil && *agent.ProfilePicturePath != "" { 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 response.ProfilePictureUrl = &profilePictureUrl
} }
return response return response
} }

View File

@ -21,6 +21,7 @@ type SalesAgentsRepository interface {
Create(agent *entity.SalesAgents) (agentReturn *entity.SalesAgents, err error) Create(agent *entity.SalesAgents) (agentReturn *entity.SalesAgents, err error)
Update(id uint, agent *entity.SalesAgents) (err error) Update(id uint, agent *entity.SalesAgents) (err error)
Delete(id uint) (err error) Delete(id uint) (err error)
FindByProfilePicturePath(profilePicturePath string) (agent *entity.SalesAgents, err error)
} }
func NewSalesAgentsRepository(db *database.Database, log zerolog.Logger) SalesAgentsRepository { func NewSalesAgentsRepository(db *database.Database, log zerolog.Logger) SalesAgentsRepository {
@ -91,3 +92,9 @@ func (_i *salesAgentsRepository) Delete(id uint) (err error) {
return 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
}

View File

@ -46,6 +46,7 @@ func (_i *SalesAgentsRouter) RegisterSalesAgentsRoutes() {
// define routes // define routes
_i.App.Route("/sales-agents", func(router fiber.Router) { _i.App.Route("/sales-agents", func(router fiber.Router) {
router.Get("/", salesAgentsController.All) router.Get("/", salesAgentsController.All)
router.Get("/viewer/:filename", salesAgentsController.Viewer)
router.Get("/:id", salesAgentsController.Show) router.Get("/:id", salesAgentsController.Show)
router.Post("/", salesAgentsController.Save) router.Post("/", salesAgentsController.Save)
router.Put("/:id", salesAgentsController.Update) router.Put("/:id", salesAgentsController.Update)

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"io"
"jaecoo-be/app/module/sales_agents/mapper" "jaecoo-be/app/module/sales_agents/mapper"
"jaecoo-be/app/module/sales_agents/repository" "jaecoo-be/app/module/sales_agents/repository"
"jaecoo-be/app/module/sales_agents/request" "jaecoo-be/app/module/sales_agents/request"
@ -12,6 +13,7 @@ import (
minioStorage "jaecoo-be/config/config" minioStorage "jaecoo-be/config/config"
"jaecoo-be/utils/paginator" "jaecoo-be/utils/paginator"
"math/rand" "math/rand"
"mime"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@ -36,6 +38,7 @@ type SalesAgentsService interface {
Update(c *fiber.Ctx, id uint, req request.SalesAgentsUpdateRequest) (agent *response.SalesAgentsResponse, err error) Update(c *fiber.Ctx, id uint, req request.SalesAgentsUpdateRequest) (agent *response.SalesAgentsResponse, err error)
Delete(id uint) (err error) Delete(id uint) (err error)
UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, 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 { 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) err = _i.Repo.Delete(id)
return 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]
}

View File

@ -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}": { "/sales-agents/{id}": {
"get": { "get": {
"security": [ "security": [

View File

@ -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}": { "/sales-agents/{id}": {
"get": { "get": {
"security": [ "security": [

View File

@ -5579,6 +5579,42 @@ paths:
summary: Update SalesAgent summary: Update SalesAgent
tags: tags:
- SalesAgents - 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: /user-levels:
get: get:
description: API for getting all UserLevels description: API for getting all UserLevels