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) {
|
||||
router.Get("/", clientsController.All)
|
||||
router.Get("/profile", clientsController.ShowWithAuth)
|
||||
router.Get("/check-name/:name", clientsController.CheckClientNameExists)
|
||||
router.Get("/:id", clientsController.Show)
|
||||
router.Post("/", clientsController.Save)
|
||||
router.Post("/with-user", clientsController.CreateClientWithUser)
|
||||
|
|
@ -61,5 +62,6 @@ func (_i *ClientsRouter) RegisterClientsRoutes() {
|
|||
router.Post("/logo", clientsController.UploadLogo)
|
||||
router.Delete("/:id/logo", clientsController.DeleteLogo)
|
||||
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
|
||||
Show(c *fiber.Ctx) error
|
||||
ShowWithAuth(c *fiber.Ctx) error
|
||||
CheckClientNameExists(c *fiber.Ctx) error
|
||||
Save(c *fiber.Ctx) error
|
||||
Update(c *fiber.Ctx) error
|
||||
UpdateWithAuth(c *fiber.Ctx) error
|
||||
|
|
@ -42,6 +43,7 @@ type ClientsController interface {
|
|||
UploadLogo(c *fiber.Ctx) error
|
||||
DeleteLogo(c *fiber.Ctx) error
|
||||
GetLogoURL(c *fiber.Ctx) error
|
||||
ViewLogo(c *fiber.Ctx) error
|
||||
}
|
||||
|
||||
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
|
||||
// @Summary 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)
|
||||
FindOne(id 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)
|
||||
Update(id uuid.UUID, clients *entity.Clients) (err error)
|
||||
Delete(id uuid.UUID) (err error)
|
||||
|
|
@ -150,6 +152,22 @@ func (_i *clientsRepository) FindOneByClientId(clientId *uuid.UUID) (clients *en
|
|||
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) {
|
||||
result := _i.DB.DB.Create(clients)
|
||||
return clients, result.Error
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"netidhub-saas-be/app/database/entity"
|
||||
"netidhub-saas-be/utils/paginator"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -76,6 +77,54 @@ type ClientsUpdateRequest struct {
|
|||
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
|
||||
type ClientsQueryRequest struct {
|
||||
// Search filters
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package service
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -30,18 +30,29 @@ func NewClientLogoUploadService(minioStorage *config.MinioStorage, log zerolog.L
|
|||
}
|
||||
|
||||
// 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
|
||||
file, err := c.FormFile("logo")
|
||||
if err != nil {
|
||||
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
|
||||
if !s.isValidImageType(file.Filename) {
|
||||
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)
|
||||
const maxSize = 5 * 1024 * 1024 // 5MB
|
||||
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
|
||||
|
||||
// Generate unique filename
|
||||
filename := s.generateUniqueFilename(file.Filename)
|
||||
s.Log.Info().
|
||||
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)
|
||||
|
||||
// Open file
|
||||
|
|
@ -143,6 +157,46 @@ func (s *ClientLogoUploadService) GetLogoURL(imagePath string, expiry time.Durat
|
|||
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
|
||||
func (s *ClientLogoUploadService) isValidImageType(filename string) bool {
|
||||
ext := strings.ToLower(filepath.Ext(filename))
|
||||
|
|
@ -156,26 +210,56 @@ func (s *ClientLogoUploadService) isValidImageType(filename string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// generateUniqueFilename generates a unique filename with timestamp and random number
|
||||
func (s *ClientLogoUploadService) generateUniqueFilename(originalFilename string) string {
|
||||
// generateUniqueFilename generates a unique filename using client name slug + Unix timestamp
|
||||
func (s *ClientLogoUploadService) generateUniqueFilename(originalFilename, clientName string) string {
|
||||
ext := filepath.Ext(originalFilename)
|
||||
nameWithoutExt := strings.TrimSuffix(filepath.Base(originalFilename), ext)
|
||||
|
||||
// Clean filename (remove spaces and special characters)
|
||||
nameWithoutExt = strings.ReplaceAll(nameWithoutExt, " ", "_")
|
||||
nameWithoutExt = strings.ReplaceAll(nameWithoutExt, "-", "_")
|
||||
// Create slug from client name
|
||||
clientSlug := s.createSlug(clientName)
|
||||
|
||||
// Generate unique suffix
|
||||
// Get Unix timestamp
|
||||
now := time.Now()
|
||||
rand.Seed(now.UnixNano())
|
||||
randomNum := rand.Intn(1000000)
|
||||
timestamp := now.Unix()
|
||||
|
||||
// Format: originalname_timestamp_random.ext
|
||||
return fmt.Sprintf("%s_%d_%d%s",
|
||||
nameWithoutExt,
|
||||
now.Unix(),
|
||||
randomNum,
|
||||
ext)
|
||||
// Format: clientname_unix_timestamp.ext
|
||||
return fmt.Sprintf("%s_%d%s", clientSlug, timestamp, ext)
|
||||
}
|
||||
|
||||
// createSlug converts client name to URL-friendly slug
|
||||
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
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
usersRequest "netidhub-saas-be/app/module/users/request"
|
||||
usersService "netidhub-saas-be/app/module/users/service"
|
||||
"netidhub-saas-be/utils/paginator"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
|
@ -34,6 +35,7 @@ type clientsService struct {
|
|||
type ClientsService interface {
|
||||
All(authToken string, req request.ClientsQueryRequest) (clients []*response.ClientsResponse, paging paginator.Pagination, 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)
|
||||
Save(req request.ClientsCreateRequest, authToken string) (clients *entity.Clients, 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)
|
||||
DeleteLogo(clientId uuid.UUID, imagePath string) error
|
||||
GetLogoURL(imagePath string) (string, error)
|
||||
ViewLogo(filename string) ([]byte, string, error)
|
||||
}
|
||||
|
||||
// NewClientsService init ClientsService
|
||||
|
|
@ -101,6 +104,28 @@ func (_i *clientsService) Show(id uuid.UUID) (clients *response.ClientsResponse,
|
|||
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) {
|
||||
// Extract clientId from 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
|
||||
_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
|
||||
imagePath, err := _i.ClientLogoUploadSvc.UploadLogo(c, clientId.String())
|
||||
imagePath, err := _i.ClientLogoUploadSvc.UploadLogo(c, clientId.String(), client.Name)
|
||||
if err != nil {
|
||||
_i.Log.Error().Err(err).Str("clientId", clientId.String()).Msg("Failed to upload client logo")
|
||||
return "", err
|
||||
}
|
||||
|
||||
_i.Log.Info().
|
||||
Str("Upload imagePath", imagePath).Msg("Client upload logo files")
|
||||
|
||||
// Update client with new logo image path
|
||||
updateReq := request.ClientsUpdateRequest{
|
||||
LogoImagePath: &imagePath,
|
||||
}
|
||||
|
||||
err = _i.Update(clientId, updateReq)
|
||||
err = _i.Repo.Update(clientId, updateReq.ToEntity())
|
||||
if err != nil {
|
||||
_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,
|
||||
}
|
||||
|
||||
err = _i.Update(clientId, updateReq)
|
||||
err = _i.Repo.Update(clientId, updateReq.ToEntity())
|
||||
if err != nil {
|
||||
_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)
|
||||
|
|
@ -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")
|
||||
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"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
_ "github.com/gofiber/fiber/v2"
|
||||
|
||||
utilRes "netidhub-saas-be/utils/response"
|
||||
utilVal "netidhub-saas-be/utils/validator"
|
||||
|
|
@ -21,6 +20,7 @@ type UsersController interface {
|
|||
All(c *fiber.Ctx) error
|
||||
Show(c *fiber.Ctx) error
|
||||
ShowByUsername(c *fiber.Ctx) error
|
||||
CheckUsernameExists(c *fiber.Ctx) error
|
||||
ShowInfo(c *fiber.Ctx) error
|
||||
Save(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
|
||||
// @Summary ShowInfo Users
|
||||
// @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)
|
||||
Show(authToken string, id uint) (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)
|
||||
Save(authToken string, req request.UsersCreateRequest) (userReturn *users.Users, 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
|
||||
}
|
||||
|
||||
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) {
|
||||
// Extract clientId from authToken
|
||||
var clientId *uuid.UUID
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package users
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
"netidhub-saas-be/app/module/users/controller"
|
||||
"netidhub-saas-be/app/module/users/repository"
|
||||
"netidhub-saas-be/app/module/users/service"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
// struct of UsersRouter
|
||||
|
|
@ -47,6 +48,7 @@ func (_i *UsersRouter) RegisterUsersRoutes() {
|
|||
router.Get("/", usersController.All)
|
||||
router.Get("/detail/:id", usersController.Show)
|
||||
router.Get("/username/:username", usersController.ShowByUsername)
|
||||
router.Get("/check-username/:username", usersController.CheckUsernameExists)
|
||||
router.Get("/info", usersController.ShowInfo)
|
||||
router.Post("/", usersController.Save)
|
||||
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": {
|
||||
"post": {
|
||||
"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": {
|
||||
"get": {
|
||||
"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}": {
|
||||
"get": {
|
||||
"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": {
|
||||
"post": {
|
||||
"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": {
|
||||
"get": {
|
||||
"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}": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
|
|
|||
|
|
@ -8240,6 +8240,31 @@ paths:
|
|||
summary: Bulk create sub-clients
|
||||
tags:
|
||||
- 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:
|
||||
post:
|
||||
description: API for uploading client logo image to MinIO (uses client ID from
|
||||
|
|
@ -8277,6 +8302,35 @@ paths:
|
|||
summary: Upload client logo
|
||||
tags:
|
||||
- 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:
|
||||
get:
|
||||
description: API for getting Clients detail using client ID from auth token
|
||||
|
|
@ -11817,6 +11871,31 @@ paths:
|
|||
summary: update Users
|
||||
tags:
|
||||
- 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}:
|
||||
get:
|
||||
description: API for getting one Users
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in New Issue