fix: update client log
This commit is contained in:
parent
95deebf106
commit
dda6f6fea6
|
|
@ -50,6 +50,7 @@ func (_i *ClientsRouter) RegisterClientsRoutes() {
|
||||||
_i.App.Route("/clients", func(router fiber.Router) {
|
_i.App.Route("/clients", func(router fiber.Router) {
|
||||||
router.Get("/", clientsController.All)
|
router.Get("/", clientsController.All)
|
||||||
router.Get("/profile", clientsController.ShowWithAuth)
|
router.Get("/profile", clientsController.ShowWithAuth)
|
||||||
|
router.Get("/check-name/:name", clientsController.CheckClientNameExists)
|
||||||
router.Get("/:id", clientsController.Show)
|
router.Get("/:id", clientsController.Show)
|
||||||
router.Post("/", clientsController.Save)
|
router.Post("/", clientsController.Save)
|
||||||
router.Post("/with-user", clientsController.CreateClientWithUser)
|
router.Post("/with-user", clientsController.CreateClientWithUser)
|
||||||
|
|
@ -61,5 +62,6 @@ func (_i *ClientsRouter) RegisterClientsRoutes() {
|
||||||
router.Post("/logo", clientsController.UploadLogo)
|
router.Post("/logo", clientsController.UploadLogo)
|
||||||
router.Delete("/:id/logo", clientsController.DeleteLogo)
|
router.Delete("/:id/logo", clientsController.DeleteLogo)
|
||||||
router.Get("/:id/logo/url", clientsController.GetLogoURL)
|
router.Get("/:id/logo/url", clientsController.GetLogoURL)
|
||||||
|
router.Get("/logo/:filename", clientsController.ViewLogo)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ type ClientsController interface {
|
||||||
All(c *fiber.Ctx) error
|
All(c *fiber.Ctx) error
|
||||||
Show(c *fiber.Ctx) error
|
Show(c *fiber.Ctx) error
|
||||||
ShowWithAuth(c *fiber.Ctx) error
|
ShowWithAuth(c *fiber.Ctx) error
|
||||||
|
CheckClientNameExists(c *fiber.Ctx) error
|
||||||
Save(c *fiber.Ctx) error
|
Save(c *fiber.Ctx) error
|
||||||
Update(c *fiber.Ctx) error
|
Update(c *fiber.Ctx) error
|
||||||
UpdateWithAuth(c *fiber.Ctx) error
|
UpdateWithAuth(c *fiber.Ctx) error
|
||||||
|
|
@ -42,6 +43,7 @@ type ClientsController interface {
|
||||||
UploadLogo(c *fiber.Ctx) error
|
UploadLogo(c *fiber.Ctx) error
|
||||||
DeleteLogo(c *fiber.Ctx) error
|
DeleteLogo(c *fiber.Ctx) error
|
||||||
GetLogoURL(c *fiber.Ctx) error
|
GetLogoURL(c *fiber.Ctx) error
|
||||||
|
ViewLogo(c *fiber.Ctx) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewClientsController(clientsService service.ClientsService, log zerolog.Logger) ClientsController {
|
func NewClientsController(clientsService service.ClientsService, log zerolog.Logger) ClientsController {
|
||||||
|
|
@ -172,6 +174,41 @@ func (_i *clientsController) ShowWithAuth(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckClientNameExists check if client name exists
|
||||||
|
// @Summary Check if client name exists
|
||||||
|
// @Description API for checking if client name exists (returns only exist status)
|
||||||
|
// @Tags Clients
|
||||||
|
// @Param name path string true "Client name to check"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /clients/check-name/{name} [get]
|
||||||
|
func (_i *clientsController) CheckClientNameExists(c *fiber.Ctx) error {
|
||||||
|
name := c.Params("name")
|
||||||
|
|
||||||
|
exists, err := _i.clientsService.CheckClientNameExists(name)
|
||||||
|
if err != nil {
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: false,
|
||||||
|
Messages: utilRes.Messages{err.Error()},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
message := "Client name is available"
|
||||||
|
if exists {
|
||||||
|
message = "Name has been used"
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{message},
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"name": name,
|
||||||
|
"exists": exists,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Save create Clients
|
// Save create Clients
|
||||||
// @Summary Create Clients
|
// @Summary Create Clients
|
||||||
// @Description API for create Clients
|
// @Description API for create Clients
|
||||||
|
|
@ -701,3 +738,35 @@ func (_i *clientsController) GetLogoURL(c *fiber.Ctx) error {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ViewLogo serves client logo file
|
||||||
|
// @Summary View client logo
|
||||||
|
// @Description API for viewing client logo file by filename
|
||||||
|
// @Tags Clients
|
||||||
|
// @Param filename path string true "Logo filename"
|
||||||
|
// @Success 200 {file} file
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 401 {object} response.UnauthorizedError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /clients/logo/{filename} [get]
|
||||||
|
func (_i *clientsController) ViewLogo(c *fiber.Ctx) error {
|
||||||
|
filename := c.Params("filename")
|
||||||
|
_i.Log.Info().Str("filename", filename).Msg("Viewing client logo")
|
||||||
|
|
||||||
|
// Get logo file from MinIO
|
||||||
|
data, contentType, err := _i.clientsService.ViewLogo(filename)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Str("filename", filename).Msg("Failed to get logo file")
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: false,
|
||||||
|
Messages: utilRes.Messages{err.Error()},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set content type and serve file
|
||||||
|
c.Set("Content-Type", contentType)
|
||||||
|
c.Set("Cache-Control", "public, max-age=3600") // Cache for 1 hour
|
||||||
|
|
||||||
|
return c.Send(data)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ type ClientsRepository interface {
|
||||||
GetAll(req request.ClientsQueryRequest) (clientss []*entity.Clients, paging paginator.Pagination, err error)
|
GetAll(req request.ClientsQueryRequest) (clientss []*entity.Clients, paging paginator.Pagination, err error)
|
||||||
FindOne(id uuid.UUID) (clients *entity.Clients, err error)
|
FindOne(id uuid.UUID) (clients *entity.Clients, err error)
|
||||||
FindOneByClientId(clientId *uuid.UUID) (clients *entity.Clients, err error)
|
FindOneByClientId(clientId *uuid.UUID) (clients *entity.Clients, err error)
|
||||||
|
FindByName(name string) (clients *entity.Clients, err error)
|
||||||
|
FindByImagePathName(name string) (clients *entity.Clients, err error)
|
||||||
Create(clients *entity.Clients) (clientsReturn *entity.Clients, err error)
|
Create(clients *entity.Clients) (clientsReturn *entity.Clients, err error)
|
||||||
Update(id uuid.UUID, clients *entity.Clients) (err error)
|
Update(id uuid.UUID, clients *entity.Clients) (err error)
|
||||||
Delete(id uuid.UUID) (err error)
|
Delete(id uuid.UUID) (err error)
|
||||||
|
|
@ -150,6 +152,22 @@ func (_i *clientsRepository) FindOneByClientId(clientId *uuid.UUID) (clients *en
|
||||||
return clients, nil
|
return clients, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (_i *clientsRepository) FindByName(name string) (clients *entity.Clients, err error) {
|
||||||
|
if err := _i.DB.DB.Where("name = ?", name).First(&clients).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (_i *clientsRepository) FindByImagePathName(name string) (clients *entity.Clients, err error) {
|
||||||
|
if err := _i.DB.DB.Where("logo_image_path like ?", "%"+name+"%").First(&clients).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return clients, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (_i *clientsRepository) Create(clients *entity.Clients) (clientsReturn *entity.Clients, err error) {
|
func (_i *clientsRepository) Create(clients *entity.Clients) (clientsReturn *entity.Clients, err error) {
|
||||||
result := _i.DB.DB.Create(clients)
|
result := _i.DB.DB.Create(clients)
|
||||||
return clients, result.Error
|
return clients, result.Error
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package request
|
package request
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"netidhub-saas-be/app/database/entity"
|
||||||
"netidhub-saas-be/utils/paginator"
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -76,6 +77,54 @@ type ClientsUpdateRequest struct {
|
||||||
Website *string `json:"website"` // Website resmi
|
Website *string `json:"website"` // Website resmi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToEntity converts ClientsUpdateRequest to entity.Clients
|
||||||
|
func (req *ClientsUpdateRequest) ToEntity() *entity.Clients {
|
||||||
|
entity := &entity.Clients{}
|
||||||
|
|
||||||
|
// Only set fields that are provided (not nil)
|
||||||
|
if req.Name != nil {
|
||||||
|
entity.Name = *req.Name
|
||||||
|
}
|
||||||
|
if req.Description != nil {
|
||||||
|
entity.Description = req.Description
|
||||||
|
}
|
||||||
|
if req.ClientType != nil {
|
||||||
|
entity.ClientType = *req.ClientType
|
||||||
|
}
|
||||||
|
if req.ParentClientId != nil {
|
||||||
|
entity.ParentClientId = req.ParentClientId
|
||||||
|
}
|
||||||
|
if req.MaxUsers != nil {
|
||||||
|
entity.MaxUsers = req.MaxUsers
|
||||||
|
}
|
||||||
|
if req.MaxStorage != nil {
|
||||||
|
entity.MaxStorage = req.MaxStorage
|
||||||
|
}
|
||||||
|
if req.Settings != nil {
|
||||||
|
entity.Settings = req.Settings
|
||||||
|
}
|
||||||
|
if req.IsActive != nil {
|
||||||
|
entity.IsActive = req.IsActive
|
||||||
|
}
|
||||||
|
if req.LogoUrl != nil {
|
||||||
|
entity.LogoUrl = req.LogoUrl
|
||||||
|
}
|
||||||
|
if req.LogoImagePath != nil {
|
||||||
|
entity.LogoImagePath = req.LogoImagePath
|
||||||
|
}
|
||||||
|
if req.Address != nil {
|
||||||
|
entity.Address = req.Address
|
||||||
|
}
|
||||||
|
if req.PhoneNumber != nil {
|
||||||
|
entity.PhoneNumber = req.PhoneNumber
|
||||||
|
}
|
||||||
|
if req.Website != nil {
|
||||||
|
entity.Website = req.Website
|
||||||
|
}
|
||||||
|
|
||||||
|
return entity
|
||||||
|
}
|
||||||
|
|
||||||
// ClientsQueryRequest for filtering and pagination
|
// ClientsQueryRequest for filtering and pagination
|
||||||
type ClientsQueryRequest struct {
|
type ClientsQueryRequest struct {
|
||||||
// Search filters
|
// Search filters
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"io"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -30,18 +30,29 @@ func NewClientLogoUploadService(minioStorage *config.MinioStorage, log zerolog.L
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadLogo uploads client logo to MinIO and returns the image path
|
// UploadLogo uploads client logo to MinIO and returns the image path
|
||||||
func (s *ClientLogoUploadService) UploadLogo(c *fiber.Ctx, clientId string) (string, error) {
|
func (s *ClientLogoUploadService) UploadLogo(c *fiber.Ctx, clientId, clientName string) (string, error) {
|
||||||
|
|
||||||
|
s.Log.Info().
|
||||||
|
Str("UploadLogo", clientId).
|
||||||
|
Msg("Client upload logo process")
|
||||||
|
|
||||||
// Get the uploaded file
|
// Get the uploaded file
|
||||||
file, err := c.FormFile("logo")
|
file, err := c.FormFile("logo")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to get uploaded file: %w", err)
|
return "", fmt.Errorf("failed to get uploaded file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.Log.Info().
|
||||||
|
Str("Upload Logo File", clientId).Interface("file", file).Msg("Client upload logo files")
|
||||||
|
|
||||||
// Validate file type
|
// Validate file type
|
||||||
if !s.isValidImageType(file.Filename) {
|
if !s.isValidImageType(file.Filename) {
|
||||||
return "", fmt.Errorf("invalid file type. Only jpg, jpeg, png, gif, webp are allowed")
|
return "", fmt.Errorf("invalid file type. Only jpg, jpeg, png, gif, webp are allowed")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.Log.Info().
|
||||||
|
Str("Upload Logo File", clientId).Interface("file", s.isValidImageType(file.Filename)).Msg("Client upload logo files")
|
||||||
|
|
||||||
// Validate file size (max 5MB)
|
// Validate file size (max 5MB)
|
||||||
const maxSize = 5 * 1024 * 1024 // 5MB
|
const maxSize = 5 * 1024 * 1024 // 5MB
|
||||||
if file.Size > maxSize {
|
if file.Size > maxSize {
|
||||||
|
|
@ -56,8 +67,11 @@ func (s *ClientLogoUploadService) UploadLogo(c *fiber.Ctx, clientId string) (str
|
||||||
|
|
||||||
bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||||
|
|
||||||
// Generate unique filename
|
s.Log.Info().
|
||||||
filename := s.generateUniqueFilename(file.Filename)
|
Str("Upload Logo bucketName", clientId).Interface("bucketName", bucketName).Msg("Client upload logo files")
|
||||||
|
|
||||||
|
// Generate unique filename using client name
|
||||||
|
filename := s.generateUniqueFilename(file.Filename, clientName)
|
||||||
objectName := fmt.Sprintf("clients/logos/%s/%s", clientId, filename)
|
objectName := fmt.Sprintf("clients/logos/%s/%s", clientId, filename)
|
||||||
|
|
||||||
// Open file
|
// Open file
|
||||||
|
|
@ -143,6 +157,46 @@ func (s *ClientLogoUploadService) GetLogoURL(imagePath string, expiry time.Durat
|
||||||
return url.String(), nil
|
return url.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetLogoFile retrieves logo file from MinIO and returns file data and content type
|
||||||
|
func (s *ClientLogoUploadService) GetLogoFile(clientId, filename string) ([]byte, string, error) {
|
||||||
|
if filename == "" {
|
||||||
|
return nil, "", fmt.Errorf("filename is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create MinIO connection
|
||||||
|
minioClient, err := s.MinioStorage.ConnectMinio()
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to connect to MinIO: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
|
||||||
|
|
||||||
|
objectName := fmt.Sprintf("clients/logos/%s/%s", clientId, filename)
|
||||||
|
// Get object from MinIO
|
||||||
|
object, err := minioClient.GetObject(context.Background(), bucketName, objectName, minio.GetObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to get object from MinIO: %w", err)
|
||||||
|
}
|
||||||
|
defer object.Close()
|
||||||
|
|
||||||
|
// Read object data
|
||||||
|
data, err := io.ReadAll(object)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to read object data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get content type based on file extension
|
||||||
|
contentType := s.getContentType(filename)
|
||||||
|
|
||||||
|
s.Log.Info().
|
||||||
|
Str("filename", filename).
|
||||||
|
Str("contentType", contentType).
|
||||||
|
Int("dataSize", len(data)).
|
||||||
|
Msg("Logo file retrieved successfully")
|
||||||
|
|
||||||
|
return data, contentType, nil
|
||||||
|
}
|
||||||
|
|
||||||
// isValidImageType checks if the file extension is a valid image type
|
// isValidImageType checks if the file extension is a valid image type
|
||||||
func (s *ClientLogoUploadService) isValidImageType(filename string) bool {
|
func (s *ClientLogoUploadService) isValidImageType(filename string) bool {
|
||||||
ext := strings.ToLower(filepath.Ext(filename))
|
ext := strings.ToLower(filepath.Ext(filename))
|
||||||
|
|
@ -156,26 +210,56 @@ func (s *ClientLogoUploadService) isValidImageType(filename string) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// generateUniqueFilename generates a unique filename with timestamp and random number
|
// generateUniqueFilename generates a unique filename using client name slug + Unix timestamp
|
||||||
func (s *ClientLogoUploadService) generateUniqueFilename(originalFilename string) string {
|
func (s *ClientLogoUploadService) generateUniqueFilename(originalFilename, clientName string) string {
|
||||||
ext := filepath.Ext(originalFilename)
|
ext := filepath.Ext(originalFilename)
|
||||||
nameWithoutExt := strings.TrimSuffix(filepath.Base(originalFilename), ext)
|
|
||||||
|
|
||||||
// Clean filename (remove spaces and special characters)
|
// Create slug from client name
|
||||||
nameWithoutExt = strings.ReplaceAll(nameWithoutExt, " ", "_")
|
clientSlug := s.createSlug(clientName)
|
||||||
nameWithoutExt = strings.ReplaceAll(nameWithoutExt, "-", "_")
|
|
||||||
|
|
||||||
// Generate unique suffix
|
// Get Unix timestamp
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
rand.Seed(now.UnixNano())
|
timestamp := now.Unix()
|
||||||
randomNum := rand.Intn(1000000)
|
|
||||||
|
|
||||||
// Format: originalname_timestamp_random.ext
|
// Format: clientname_unix_timestamp.ext
|
||||||
return fmt.Sprintf("%s_%d_%d%s",
|
return fmt.Sprintf("%s_%d%s", clientSlug, timestamp, ext)
|
||||||
nameWithoutExt,
|
}
|
||||||
now.Unix(),
|
|
||||||
randomNum,
|
// createSlug converts client name to URL-friendly slug
|
||||||
ext)
|
func (s *ClientLogoUploadService) createSlug(name string) string {
|
||||||
|
// Convert to lowercase
|
||||||
|
slug := strings.ToLower(name)
|
||||||
|
|
||||||
|
// Replace spaces with underscores
|
||||||
|
slug = strings.ReplaceAll(slug, " ", "_")
|
||||||
|
|
||||||
|
// Replace hyphens with underscores
|
||||||
|
slug = strings.ReplaceAll(slug, "-", "_")
|
||||||
|
|
||||||
|
// Remove special characters (keep only alphanumeric and underscores)
|
||||||
|
var result strings.Builder
|
||||||
|
for _, char := range slug {
|
||||||
|
if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == '_' {
|
||||||
|
result.WriteRune(char)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slug = result.String()
|
||||||
|
|
||||||
|
// Remove multiple consecutive underscores
|
||||||
|
for strings.Contains(slug, "__") {
|
||||||
|
slug = strings.ReplaceAll(slug, "__", "_")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove leading/trailing underscores
|
||||||
|
slug = strings.Trim(slug, "_")
|
||||||
|
|
||||||
|
// If empty after cleaning, use "client"
|
||||||
|
if slug == "" {
|
||||||
|
slug = "client"
|
||||||
|
}
|
||||||
|
|
||||||
|
return slug
|
||||||
}
|
}
|
||||||
|
|
||||||
// getContentType returns the MIME type based on file extension
|
// getContentType returns the MIME type based on file extension
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
usersRequest "netidhub-saas-be/app/module/users/request"
|
usersRequest "netidhub-saas-be/app/module/users/request"
|
||||||
usersService "netidhub-saas-be/app/module/users/service"
|
usersService "netidhub-saas-be/app/module/users/service"
|
||||||
"netidhub-saas-be/utils/paginator"
|
"netidhub-saas-be/utils/paginator"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
|
@ -34,6 +35,7 @@ type clientsService struct {
|
||||||
type ClientsService interface {
|
type ClientsService interface {
|
||||||
All(authToken string, req request.ClientsQueryRequest) (clients []*response.ClientsResponse, paging paginator.Pagination, err error)
|
All(authToken string, req request.ClientsQueryRequest) (clients []*response.ClientsResponse, paging paginator.Pagination, err error)
|
||||||
Show(id uuid.UUID) (clients *response.ClientsResponse, err error)
|
Show(id uuid.UUID) (clients *response.ClientsResponse, err error)
|
||||||
|
CheckClientNameExists(name string) (exists bool, err error)
|
||||||
ShowWithAuth(authToken string) (clients *response.ClientsResponse, err error)
|
ShowWithAuth(authToken string) (clients *response.ClientsResponse, err error)
|
||||||
Save(req request.ClientsCreateRequest, authToken string) (clients *entity.Clients, err error)
|
Save(req request.ClientsCreateRequest, authToken string) (clients *entity.Clients, err error)
|
||||||
Update(id uuid.UUID, req request.ClientsUpdateRequest) (err error)
|
Update(id uuid.UUID, req request.ClientsUpdateRequest) (err error)
|
||||||
|
|
@ -54,6 +56,7 @@ type ClientsService interface {
|
||||||
UploadLogo(authToken string, c *fiber.Ctx) (string, error)
|
UploadLogo(authToken string, c *fiber.Ctx) (string, error)
|
||||||
DeleteLogo(clientId uuid.UUID, imagePath string) error
|
DeleteLogo(clientId uuid.UUID, imagePath string) error
|
||||||
GetLogoURL(imagePath string) (string, error)
|
GetLogoURL(imagePath string) (string, error)
|
||||||
|
ViewLogo(filename string) ([]byte, string, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClientsService init ClientsService
|
// NewClientsService init ClientsService
|
||||||
|
|
@ -101,6 +104,28 @@ func (_i *clientsService) Show(id uuid.UUID) (clients *response.ClientsResponse,
|
||||||
return mapper.ClientsResponseMapper(result), nil
|
return mapper.ClientsResponseMapper(result), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (_i *clientsService) CheckClientNameExists(name string) (exists bool, err error) {
|
||||||
|
_i.Log.Info().Str("name", name).Msg("Checking if client name exists")
|
||||||
|
|
||||||
|
// Check if client name exists in repository
|
||||||
|
result, err := _i.Repo.FindByName(name)
|
||||||
|
if err != nil {
|
||||||
|
// If error is "record not found", name doesn't exist
|
||||||
|
if strings.Contains(err.Error(), "record not found") {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
_i.Log.Error().Err(err).Str("name", name).Msg("Failed to check client name existence")
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If result is not nil, name exists
|
||||||
|
if result != nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (_i *clientsService) ShowWithAuth(authToken string) (clients *response.ClientsResponse, err error) {
|
func (_i *clientsService) ShowWithAuth(authToken string) (clients *response.ClientsResponse, err error) {
|
||||||
// Extract clientId from authToken
|
// Extract clientId from authToken
|
||||||
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
|
||||||
|
|
@ -517,19 +542,29 @@ func (_i *clientsService) UploadLogo(authToken string, c *fiber.Ctx) (string, er
|
||||||
clientId := *user.ClientId
|
clientId := *user.ClientId
|
||||||
_i.Log.Info().Str("clientId", clientId.String()).Msg("Uploading client logo")
|
_i.Log.Info().Str("clientId", clientId.String()).Msg("Uploading client logo")
|
||||||
|
|
||||||
|
// Fetch client data to get client name
|
||||||
|
client, err := _i.Repo.FindOne(clientId)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Msg("Failed to fetch client data")
|
||||||
|
return "", fmt.Errorf("failed to fetch client data: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Upload logo using the upload service
|
// Upload logo using the upload service
|
||||||
imagePath, err := _i.ClientLogoUploadSvc.UploadLogo(c, clientId.String())
|
imagePath, err := _i.ClientLogoUploadSvc.UploadLogo(c, clientId.String(), client.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Msg("Failed to upload client logo")
|
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Msg("Failed to upload client logo")
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_i.Log.Info().
|
||||||
|
Str("Upload imagePath", imagePath).Msg("Client upload logo files")
|
||||||
|
|
||||||
// Update client with new logo image path
|
// Update client with new logo image path
|
||||||
updateReq := request.ClientsUpdateRequest{
|
updateReq := request.ClientsUpdateRequest{
|
||||||
LogoImagePath: &imagePath,
|
LogoImagePath: &imagePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.Update(clientId, updateReq)
|
err = _i.Repo.Update(clientId, updateReq.ToEntity())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Str("imagePath", imagePath).Msg("Failed to update client with logo path")
|
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Str("imagePath", imagePath).Msg("Failed to update client with logo path")
|
||||||
|
|
||||||
|
|
@ -562,7 +597,7 @@ func (_i *clientsService) DeleteLogo(clientId uuid.UUID, imagePath string) error
|
||||||
LogoImagePath: &emptyPath,
|
LogoImagePath: &emptyPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
err = _i.Update(clientId, updateReq)
|
err = _i.Repo.Update(clientId, updateReq.ToEntity())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Msg("Failed to clear logo path from client")
|
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Msg("Failed to clear logo path from client")
|
||||||
return fmt.Errorf("failed to clear logo path from client: %w", err)
|
return fmt.Errorf("failed to clear logo path from client: %w", err)
|
||||||
|
|
@ -590,3 +625,23 @@ func (_i *clientsService) GetLogoURL(imagePath string) (string, error) {
|
||||||
_i.Log.Info().Str("imagePath", imagePath).Str("url", url).Msg("Logo URL generated successfully")
|
_i.Log.Info().Str("imagePath", imagePath).Str("url", url).Msg("Logo URL generated successfully")
|
||||||
return url, nil
|
return url, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ViewLogo retrieves logo file from MinIO
|
||||||
|
func (_i *clientsService) ViewLogo(filename string) ([]byte, string, error) {
|
||||||
|
_i.Log.Info().Str("filename", filename).Msg("Retrieving logo file")
|
||||||
|
|
||||||
|
client, err := _i.Repo.FindByImagePathName(filename)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Str("filename", filename).Msg("Failed to find client by image path name")
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, contentType, err := _i.ClientLogoUploadSvc.GetLogoFile(client.ID.String(), filename)
|
||||||
|
if err != nil {
|
||||||
|
_i.Log.Error().Err(err).Str("filename", filename).Msg("Failed to retrieve logo file")
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
_i.Log.Info().Str("filename", filename).Str("contentType", contentType).Int("dataSize", len(data)).Msg("Logo file retrieved successfully")
|
||||||
|
return data, contentType, nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
_ "github.com/gofiber/fiber/v2"
|
|
||||||
|
|
||||||
utilRes "netidhub-saas-be/utils/response"
|
utilRes "netidhub-saas-be/utils/response"
|
||||||
utilVal "netidhub-saas-be/utils/validator"
|
utilVal "netidhub-saas-be/utils/validator"
|
||||||
|
|
@ -21,6 +20,7 @@ type UsersController interface {
|
||||||
All(c *fiber.Ctx) error
|
All(c *fiber.Ctx) error
|
||||||
Show(c *fiber.Ctx) error
|
Show(c *fiber.Ctx) error
|
||||||
ShowByUsername(c *fiber.Ctx) error
|
ShowByUsername(c *fiber.Ctx) error
|
||||||
|
CheckUsernameExists(c *fiber.Ctx) error
|
||||||
ShowInfo(c *fiber.Ctx) error
|
ShowInfo(c *fiber.Ctx) error
|
||||||
Save(c *fiber.Ctx) error
|
Save(c *fiber.Ctx) error
|
||||||
Update(c *fiber.Ctx) error
|
Update(c *fiber.Ctx) error
|
||||||
|
|
@ -156,6 +156,36 @@ func (_i *usersController) ShowByUsername(c *fiber.Ctx) error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckUsernameExists check if username exists
|
||||||
|
// @Summary Check if username exists
|
||||||
|
// @Description API for checking if username exists (returns only exist status)
|
||||||
|
// @Tags Users
|
||||||
|
// @Param username path string true "Username to check"
|
||||||
|
// @Success 200 {object} response.Response
|
||||||
|
// @Failure 400 {object} response.BadRequestError
|
||||||
|
// @Failure 500 {object} response.InternalServerError
|
||||||
|
// @Router /users/check-username/{username} [get]
|
||||||
|
func (_i *usersController) CheckUsernameExists(c *fiber.Ctx) error {
|
||||||
|
username := c.Params("username")
|
||||||
|
|
||||||
|
exists, err := _i.usersService.CheckUsernameExists(username)
|
||||||
|
if err != nil {
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: false,
|
||||||
|
Messages: utilRes.Messages{err.Error()},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return utilRes.Resp(c, utilRes.Response{
|
||||||
|
Success: true,
|
||||||
|
Messages: utilRes.Messages{"Username check completed"},
|
||||||
|
Data: map[string]interface{}{
|
||||||
|
"username": username,
|
||||||
|
"exists": exists,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ShowInfo Users
|
// ShowInfo Users
|
||||||
// @Summary ShowInfo Users
|
// @Summary ShowInfo Users
|
||||||
// @Description API for ShowUserInfo
|
// @Description API for ShowUserInfo
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ type UsersService interface {
|
||||||
All(authToken string, req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error)
|
All(authToken string, req request.UsersQueryRequest) (users []*response.UsersResponse, paging paginator.Pagination, err error)
|
||||||
Show(authToken string, id uint) (users *response.UsersResponse, err error)
|
Show(authToken string, id uint) (users *response.UsersResponse, err error)
|
||||||
ShowByUsername(authToken string, username string) (users *response.UsersResponse, err error)
|
ShowByUsername(authToken string, username string) (users *response.UsersResponse, err error)
|
||||||
|
CheckUsernameExists(username string) (exists bool, err error)
|
||||||
ShowUserInfo(authToken string) (users *response.UsersResponse, err error)
|
ShowUserInfo(authToken string) (users *response.UsersResponse, err error)
|
||||||
Save(authToken string, req request.UsersCreateRequest) (userReturn *users.Users, err error)
|
Save(authToken string, req request.UsersCreateRequest) (userReturn *users.Users, err error)
|
||||||
Login(req request.UserLogin) (res *gocloak.JWT, err error)
|
Login(req request.UserLogin) (res *gocloak.JWT, err error)
|
||||||
|
|
@ -142,6 +143,28 @@ func (_i *usersService) ShowByUsername(authToken string, username string) (users
|
||||||
return mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId), nil
|
return mapper.UsersResponseMapper(result, _i.UserLevelsRepo, clientId), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (_i *usersService) CheckUsernameExists(username string) (exists bool, err error) {
|
||||||
|
_i.Log.Info().Str("username", username).Msg("Checking if username exists")
|
||||||
|
|
||||||
|
// Check if username exists in repository
|
||||||
|
result, err := _i.Repo.FindByUsername(nil, username)
|
||||||
|
if err != nil {
|
||||||
|
// If error is "record not found", username doesn't exist
|
||||||
|
if strings.Contains(err.Error(), "record not found") {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
_i.Log.Error().Err(err).Str("username", username).Msg("Failed to check username existence")
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If result is not nil, username exists
|
||||||
|
if result != nil {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (_i *usersService) ShowUserInfo(authToken string) (users *response.UsersResponse, err error) {
|
func (_i *usersService) ShowUserInfo(authToken string) (users *response.UsersResponse, err error) {
|
||||||
// Extract clientId from authToken
|
// Extract clientId from authToken
|
||||||
var clientId *uuid.UUID
|
var clientId *uuid.UUID
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
package users
|
package users
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/gofiber/fiber/v2"
|
|
||||||
"go.uber.org/fx"
|
|
||||||
"netidhub-saas-be/app/module/users/controller"
|
"netidhub-saas-be/app/module/users/controller"
|
||||||
"netidhub-saas-be/app/module/users/repository"
|
"netidhub-saas-be/app/module/users/repository"
|
||||||
"netidhub-saas-be/app/module/users/service"
|
"netidhub-saas-be/app/module/users/service"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"go.uber.org/fx"
|
||||||
)
|
)
|
||||||
|
|
||||||
// struct of UsersRouter
|
// struct of UsersRouter
|
||||||
|
|
@ -47,6 +48,7 @@ func (_i *UsersRouter) RegisterUsersRoutes() {
|
||||||
router.Get("/", usersController.All)
|
router.Get("/", usersController.All)
|
||||||
router.Get("/detail/:id", usersController.Show)
|
router.Get("/detail/:id", usersController.Show)
|
||||||
router.Get("/username/:username", usersController.ShowByUsername)
|
router.Get("/username/:username", usersController.ShowByUsername)
|
||||||
|
router.Get("/check-username/:username", usersController.CheckUsernameExists)
|
||||||
router.Get("/info", usersController.ShowInfo)
|
router.Get("/info", usersController.ShowInfo)
|
||||||
router.Post("/", usersController.Save)
|
router.Post("/", usersController.Save)
|
||||||
router.Put("/:id", usersController.Update)
|
router.Put("/:id", usersController.Update)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,277 @@
|
||||||
|
# Client Name Check API Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
API endpoint untuk mengecek apakah nama client sudah ada atau belum dalam sistem. Endpoint ini mengembalikan status exist/not exist dengan pesan yang sesuai.
|
||||||
|
|
||||||
|
## Endpoint
|
||||||
|
|
||||||
|
### Check Client Name Exists
|
||||||
|
**GET** `/clients/check-name/{name}`
|
||||||
|
|
||||||
|
#### Description
|
||||||
|
Mengecek apakah nama client sudah ada dalam sistem atau belum.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
- **name** (path, required): Nama client yang akan dicek keberadaannya
|
||||||
|
|
||||||
|
#### Headers
|
||||||
|
- Tidak memerlukan authentication (public endpoint)
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
##### Success Response (200) - Name Available
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Client name is available"],
|
||||||
|
"data": {
|
||||||
|
"name": "My Company",
|
||||||
|
"exists": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Success Response (200) - Name Already Used
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Name has been used"],
|
||||||
|
"data": {
|
||||||
|
"name": "My Company",
|
||||||
|
"exists": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Response Fields
|
||||||
|
- **name** (string): Nama client yang dicek
|
||||||
|
- **exists** (boolean): Status apakah nama sudah ada (`true`) atau belum (`false`)
|
||||||
|
|
||||||
|
#### Error Responses
|
||||||
|
|
||||||
|
##### 500 Internal Server Error
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"messages": ["Database error message"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### cURL Example
|
||||||
|
```bash
|
||||||
|
# Check if client name exists
|
||||||
|
curl -X GET "http://localhost:8080/clients/check-name/My%20Company"
|
||||||
|
|
||||||
|
# Response if name exists
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Name has been used"],
|
||||||
|
"data": {
|
||||||
|
"name": "My Company",
|
||||||
|
"exists": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Response if name doesn't exist
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Client name is available"],
|
||||||
|
"data": {
|
||||||
|
"name": "My Company",
|
||||||
|
"exists": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript Example
|
||||||
|
```javascript
|
||||||
|
const checkClientNameExists = async (name) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/clients/check-name/${encodeURIComponent(name)}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
return {
|
||||||
|
exists: data.data.exists,
|
||||||
|
message: data.messages[0]
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
console.error('Check failed:', data.messages);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking client name:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
const result = await checkClientNameExists('My Company');
|
||||||
|
if (result) {
|
||||||
|
if (result.exists) {
|
||||||
|
console.log('Name is already used:', result.message);
|
||||||
|
} else {
|
||||||
|
console.log('Name is available:', result.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### React Hook Example
|
||||||
|
```javascript
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const useClientNameCheck = (name) => {
|
||||||
|
const [exists, setExists] = useState(null);
|
||||||
|
const [message, setMessage] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkName = async () => {
|
||||||
|
if (!name || name.length < 2) {
|
||||||
|
setExists(null);
|
||||||
|
setMessage('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const response = await fetch(`/clients/check-name/${encodeURIComponent(name)}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
setExists(data.data.exists);
|
||||||
|
setMessage(data.messages[0]);
|
||||||
|
} else {
|
||||||
|
setError(data.messages[0]);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to check client name');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Debounce the check
|
||||||
|
const timeoutId = setTimeout(checkName, 500);
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
|
}, [name]);
|
||||||
|
|
||||||
|
return { exists, message, loading, error };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Usage in component
|
||||||
|
const ClientNameInput = () => {
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
const { exists, message, loading, error } = useClientNameCheck(name);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={name}
|
||||||
|
onChange={(e) => setName(e.target.value)}
|
||||||
|
placeholder="Enter client name"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{loading && <span>Checking...</span>}
|
||||||
|
{error && <span style={{color: 'red'}}>Error: {error}</span>}
|
||||||
|
{exists === true && <span style={{color: 'red'}}>{message}</span>}
|
||||||
|
{exists === false && <span style={{color: 'green'}}>{message}</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Form Validation Example
|
||||||
|
```javascript
|
||||||
|
const validateClientName = async (name) => {
|
||||||
|
if (!name || name.length < 2) {
|
||||||
|
return { valid: false, message: 'Client name must be at least 2 characters' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.length > 100) {
|
||||||
|
return { valid: false, message: 'Client name must be less than 100 characters' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/clients/check-name/${encodeURIComponent(name)}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
if (data.data.exists) {
|
||||||
|
return { valid: false, message: data.messages[0] };
|
||||||
|
} else {
|
||||||
|
return { valid: true, message: data.messages[0] };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { valid: false, message: 'Unable to check client name availability' };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { valid: false, message: 'Network error occurred' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Usage in form submission
|
||||||
|
const handleSubmit = async (formData) => {
|
||||||
|
const nameValidation = await validateClientName(formData.name);
|
||||||
|
if (!nameValidation.valid) {
|
||||||
|
setError(nameValidation.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed with form submission
|
||||||
|
submitForm(formData);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
1. **Client Registration**: Mengecek ketersediaan nama client saat registrasi
|
||||||
|
2. **Real-time Validation**: Validasi nama client secara real-time saat user mengetik
|
||||||
|
3. **Client Name Suggestion**: Memberikan saran nama client alternatif jika nama sudah ada
|
||||||
|
4. **Profile Update**: Mengecek nama client baru saat user ingin mengubah nama client
|
||||||
|
5. **Admin Panel**: Admin mengecek ketersediaan nama client untuk client baru
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
- Endpoint ini tidak memerlukan authentication, sehingga lebih cepat
|
||||||
|
- Menggunakan query database yang efisien untuk pengecekan keberadaan
|
||||||
|
- Response yang minimal (hanya status exist/not exist) untuk performa optimal
|
||||||
|
- Cocok untuk real-time validation dengan debouncing
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
- Endpoint ini bersifat public dan tidak memerlukan authentication
|
||||||
|
- Tidak mengembalikan data sensitif client
|
||||||
|
- Hanya mengembalikan status boolean exist/not exist
|
||||||
|
- Nama client di-validate untuk mencegah injection attacks
|
||||||
|
|
||||||
|
## Message Responses
|
||||||
|
|
||||||
|
| Status | Message | Description |
|
||||||
|
|--------|---------|-------------|
|
||||||
|
| `exists: false` | "Client name is available" | Nama client tersedia untuk digunakan |
|
||||||
|
| `exists: true` | "Name has been used" | Nama client sudah digunakan |
|
||||||
|
|
||||||
|
## Comparison with Other Endpoints
|
||||||
|
|
||||||
|
| Endpoint | Purpose | Authentication | Response |
|
||||||
|
|----------|---------|----------------|----------|
|
||||||
|
| `/clients/check-name/{name}` | Check client name exists | Not required | Boolean status only |
|
||||||
|
| `/clients/{id}` | Get client by ID | Required | Full client data |
|
||||||
|
| `/clients/profile` | Get current client profile | Required | Current client data |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Endpoint ini menggunakan method `FindByName` untuk pencarian berdasarkan nama
|
||||||
|
- Jika terjadi error database, akan mengembalikan error 500
|
||||||
|
- Nama client case-sensitive (mengikuti konvensi sistem)
|
||||||
|
- Cocok untuk digunakan dalam form validation dan real-time checking
|
||||||
|
- URL encoding diperlukan untuk nama yang mengandung spasi atau karakter khusus
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
# Username Check API Documentation
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
API endpoint untuk mengecek apakah username sudah ada atau belum dalam sistem. Endpoint ini hanya mengembalikan status exist/not exist tanpa mengembalikan data user lengkap.
|
||||||
|
|
||||||
|
## Endpoint
|
||||||
|
|
||||||
|
### Check Username Exists
|
||||||
|
**GET** `/users/check-username/{username}`
|
||||||
|
|
||||||
|
#### Description
|
||||||
|
Mengecek apakah username sudah ada dalam sistem atau belum.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
- **username** (path, required): Username yang akan dicek keberadaannya
|
||||||
|
|
||||||
|
#### Headers
|
||||||
|
- Tidak memerlukan authentication (public endpoint)
|
||||||
|
|
||||||
|
#### Response
|
||||||
|
|
||||||
|
##### Success Response (200)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Username check completed"],
|
||||||
|
"data": {
|
||||||
|
"username": "john_doe",
|
||||||
|
"exists": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Response Fields
|
||||||
|
- **username** (string): Username yang dicek
|
||||||
|
- **exists** (boolean): Status apakah username sudah ada (`true`) atau belum (`false`)
|
||||||
|
|
||||||
|
#### Error Responses
|
||||||
|
|
||||||
|
##### 500 Internal Server Error
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": false,
|
||||||
|
"messages": ["Database error message"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage Examples
|
||||||
|
|
||||||
|
### cURL Example
|
||||||
|
```bash
|
||||||
|
# Check if username exists
|
||||||
|
curl -X GET "http://localhost:8080/users/check-username/john_doe"
|
||||||
|
|
||||||
|
# Response if username exists
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Username check completed"],
|
||||||
|
"data": {
|
||||||
|
"username": "john_doe",
|
||||||
|
"exists": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Response if username doesn't exist
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"messages": ["Username check completed"],
|
||||||
|
"data": {
|
||||||
|
"username": "john_doe",
|
||||||
|
"exists": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript Example
|
||||||
|
```javascript
|
||||||
|
const checkUsernameExists = async (username) => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/users/check-username/${username}`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
return data.data.exists;
|
||||||
|
} else {
|
||||||
|
console.error('Check failed:', data.messages);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error checking username:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Usage
|
||||||
|
const usernameExists = await checkUsernameExists('john_doe');
|
||||||
|
if (usernameExists === true) {
|
||||||
|
console.log('Username already exists');
|
||||||
|
} else if (usernameExists === false) {
|
||||||
|
console.log('Username is available');
|
||||||
|
} else {
|
||||||
|
console.log('Error occurred while checking');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### React Hook Example
|
||||||
|
```javascript
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
const useUsernameCheck = (username) => {
|
||||||
|
const [exists, setExists] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const checkUsername = async () => {
|
||||||
|
if (!username || username.length < 3) {
|
||||||
|
setExists(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const response = await fetch(`/users/check-username/${username}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
setExists(data.data.exists);
|
||||||
|
} else {
|
||||||
|
setError(data.messages[0]);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setError('Failed to check username');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Debounce the check
|
||||||
|
const timeoutId = setTimeout(checkUsername, 500);
|
||||||
|
return () => clearTimeout(timeoutId);
|
||||||
|
}, [username]);
|
||||||
|
|
||||||
|
return { exists, loading, error };
|
||||||
|
};
|
||||||
|
|
||||||
|
// Usage in component
|
||||||
|
const UsernameInput = () => {
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const { exists, loading, error } = useUsernameCheck(username);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
value={username}
|
||||||
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
|
placeholder="Enter username"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{loading && <span>Checking...</span>}
|
||||||
|
{error && <span style={{color: 'red'}}>Error: {error}</span>}
|
||||||
|
{exists === true && <span style={{color: 'red'}}>Username already exists</span>}
|
||||||
|
{exists === false && <span style={{color: 'green'}}>Username is available</span>}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
### Form Validation Example
|
||||||
|
```javascript
|
||||||
|
const validateUsername = async (username) => {
|
||||||
|
if (!username || username.length < 3) {
|
||||||
|
return { valid: false, message: 'Username must be at least 3 characters' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!/^[a-zA-Z0-9_]+$/.test(username)) {
|
||||||
|
return { valid: false, message: 'Username can only contain letters, numbers, and underscores' };
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/users/check-username/${username}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.success) {
|
||||||
|
if (data.data.exists) {
|
||||||
|
return { valid: false, message: 'Username already exists' };
|
||||||
|
} else {
|
||||||
|
return { valid: true, message: 'Username is available' };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { valid: false, message: 'Unable to check username availability' };
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return { valid: false, message: 'Network error occurred' };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Usage in form submission
|
||||||
|
const handleSubmit = async (formData) => {
|
||||||
|
const usernameValidation = await validateUsername(formData.username);
|
||||||
|
if (!usernameValidation.valid) {
|
||||||
|
setError(usernameValidation.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proceed with form submission
|
||||||
|
submitForm(formData);
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
1. **Registration Form**: Mengecek ketersediaan username saat user mendaftar
|
||||||
|
2. **Real-time Validation**: Validasi username secara real-time saat user mengetik
|
||||||
|
3. **Username Suggestion**: Memberikan saran username alternatif jika username sudah ada
|
||||||
|
4. **Profile Update**: Mengecek username baru saat user ingin mengubah username
|
||||||
|
5. **Admin Panel**: Admin mengecek ketersediaan username untuk user baru
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
- Endpoint ini tidak memerlukan authentication, sehingga lebih cepat
|
||||||
|
- Menggunakan query database yang efisien untuk pengecekan keberadaan
|
||||||
|
- Response yang minimal (hanya status exist/not exist) untuk performa optimal
|
||||||
|
- Cocok untuk real-time validation dengan debouncing
|
||||||
|
|
||||||
|
## Security Notes
|
||||||
|
- Endpoint ini bersifat public dan tidak memerlukan authentication
|
||||||
|
- Tidak mengembalikan data sensitif user
|
||||||
|
- Hanya mengembalikan status boolean exist/not exist
|
||||||
|
- Username di-validate untuk mencegah injection attacks
|
||||||
|
|
||||||
|
## Comparison with Other Endpoints
|
||||||
|
|
||||||
|
| Endpoint | Purpose | Authentication | Response |
|
||||||
|
|----------|---------|----------------|----------|
|
||||||
|
| `/users/check-username/{username}` | Check username exists | Not required | Boolean status only |
|
||||||
|
| `/users/username/{username}` | Get user by username | Required | Full user data |
|
||||||
|
| `/users/info` | Get current user info | Required | Current user data |
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- Endpoint ini menggunakan method `FindByUsername` dengan `clientId = nil` untuk pencarian global
|
||||||
|
- Jika terjadi error database, akan mengembalikan error 500
|
||||||
|
- Username case-sensitive (mengikuti konvensi sistem)
|
||||||
|
- Cocok untuk digunakan dalam form validation dan real-time checking
|
||||||
|
|
@ -9744,6 +9744,44 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/clients/check-name/{name}": {
|
||||||
|
"get": {
|
||||||
|
"description": "API for checking if client name exists (returns only exist status)",
|
||||||
|
"tags": [
|
||||||
|
"Clients"
|
||||||
|
],
|
||||||
|
"summary": "Check if client name exists",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client name to check",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.BadRequestError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.InternalServerError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/clients/logo": {
|
"/clients/logo": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -9800,6 +9838,50 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/clients/logo/{filename}": {
|
||||||
|
"get": {
|
||||||
|
"description": "API for viewing client logo file by filename",
|
||||||
|
"tags": [
|
||||||
|
"Clients"
|
||||||
|
],
|
||||||
|
"summary": "View client logo",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Logo filename",
|
||||||
|
"name": "filename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/clients/profile": {
|
"/clients/profile": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -15760,6 +15842,44 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/users/check-username/{username}": {
|
||||||
|
"get": {
|
||||||
|
"description": "API for checking if username exists (returns only exist status)",
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Check if username exists",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username to check",
|
||||||
|
"name": "username",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.BadRequestError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.InternalServerError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/users/detail/{id}": {
|
"/users/detail/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
|
||||||
|
|
@ -9733,6 +9733,44 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/clients/check-name/{name}": {
|
||||||
|
"get": {
|
||||||
|
"description": "API for checking if client name exists (returns only exist status)",
|
||||||
|
"tags": [
|
||||||
|
"Clients"
|
||||||
|
],
|
||||||
|
"summary": "Check if client name exists",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Client name to check",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.BadRequestError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.InternalServerError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/clients/logo": {
|
"/clients/logo": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -9789,6 +9827,50 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/clients/logo/{filename}": {
|
||||||
|
"get": {
|
||||||
|
"description": "API for viewing client logo file by filename",
|
||||||
|
"tags": [
|
||||||
|
"Clients"
|
||||||
|
],
|
||||||
|
"summary": "View client logo",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Logo filename",
|
||||||
|
"name": "filename",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/clients/profile": {
|
"/clients/profile": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -15749,6 +15831,44 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/users/check-username/{username}": {
|
||||||
|
"get": {
|
||||||
|
"description": "API for checking if username exists (returns only exist status)",
|
||||||
|
"tags": [
|
||||||
|
"Users"
|
||||||
|
],
|
||||||
|
"summary": "Check if username exists",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Username to check",
|
||||||
|
"name": "username",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.Response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Bad Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.BadRequestError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/response.InternalServerError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/users/detail/{id}": {
|
"/users/detail/{id}": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
|
||||||
|
|
@ -8240,6 +8240,31 @@ paths:
|
||||||
summary: Bulk create sub-clients
|
summary: Bulk create sub-clients
|
||||||
tags:
|
tags:
|
||||||
- Clients
|
- Clients
|
||||||
|
/clients/check-name/{name}:
|
||||||
|
get:
|
||||||
|
description: API for checking if client name exists (returns only exist status)
|
||||||
|
parameters:
|
||||||
|
- description: Client name to check
|
||||||
|
in: path
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.BadRequestError'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.InternalServerError'
|
||||||
|
summary: Check if client name exists
|
||||||
|
tags:
|
||||||
|
- Clients
|
||||||
/clients/logo:
|
/clients/logo:
|
||||||
post:
|
post:
|
||||||
description: API for uploading client logo image to MinIO (uses client ID from
|
description: API for uploading client logo image to MinIO (uses client ID from
|
||||||
|
|
@ -8277,6 +8302,35 @@ paths:
|
||||||
summary: Upload client logo
|
summary: Upload client logo
|
||||||
tags:
|
tags:
|
||||||
- Clients
|
- Clients
|
||||||
|
/clients/logo/{filename}:
|
||||||
|
get:
|
||||||
|
description: API for viewing client logo file by filename
|
||||||
|
parameters:
|
||||||
|
- description: Logo filename
|
||||||
|
in: path
|
||||||
|
name: filename
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
"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'
|
||||||
|
summary: View client logo
|
||||||
|
tags:
|
||||||
|
- Clients
|
||||||
/clients/profile:
|
/clients/profile:
|
||||||
get:
|
get:
|
||||||
description: API for getting Clients detail using client ID from auth token
|
description: API for getting Clients detail using client ID from auth token
|
||||||
|
|
@ -11817,6 +11871,31 @@ paths:
|
||||||
summary: update Users
|
summary: update Users
|
||||||
tags:
|
tags:
|
||||||
- Users
|
- Users
|
||||||
|
/users/check-username/{username}:
|
||||||
|
get:
|
||||||
|
description: API for checking if username exists (returns only exist status)
|
||||||
|
parameters:
|
||||||
|
- description: Username to check
|
||||||
|
in: path
|
||||||
|
name: username
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.Response'
|
||||||
|
"400":
|
||||||
|
description: Bad Request
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.BadRequestError'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/response.InternalServerError'
|
||||||
|
summary: Check if username exists
|
||||||
|
tags:
|
||||||
|
- Users
|
||||||
/users/detail/{id}:
|
/users/detail/{id}:
|
||||||
get:
|
get:
|
||||||
description: API for getting one Users
|
description: API for getting one Users
|
||||||
|
|
|
||||||
Binary file not shown.
Loading…
Reference in New Issue