Merge pull request 'feature/responses-v2' (#27) from feature/responses-v2 into dev
Reviewed-on: winter-access/backend_nam#27
This commit is contained in:
commit
bcb9ac21a0
|
|
@ -20,6 +20,7 @@ type NearestDeviceResponse struct {
|
|||
Province *string `json:"province,omitempty"`
|
||||
City *string `json:"city,omitempty"`
|
||||
District *string `json:"district,omitempty"`
|
||||
ImageURL *string `json:"image_url"`
|
||||
|
||||
// Connection counts
|
||||
BackboneCount int `json:"backbone_count"`
|
||||
|
|
@ -44,7 +45,8 @@ type NearestDeviceDetailResponse struct {
|
|||
Province *string `json:"province,omitempty"`
|
||||
City *string `json:"city,omitempty"`
|
||||
District *string `json:"district,omitempty"`
|
||||
ImageURL *string `json:"image_url,omitempty"`
|
||||
ImageURL *string `json:"image_url"`
|
||||
ImageURLs []string `json:"image_urls"`
|
||||
|
||||
// Detailed connection information
|
||||
Backbones []BackboneConnectionInfo `json:"backbones"`
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ const (
|
|||
)
|
||||
|
||||
type UserRegisterDTO struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
Password string `json:"password" validate:"required,min=6"`
|
||||
NomorInduk string `json:"nomor_induk" validate:"required"`
|
||||
Name string `json:"name" validate:"required,min=2,max=100,alphaspace"`
|
||||
Username string `json:"username" validate:"required,min=3,max=50,alphanumunderscore,nospace"`
|
||||
Password string `json:"password" validate:"required,min=6,max=100"`
|
||||
NomorInduk string `json:"nomor_induk" validate:"required,min=3,max=50,alphanum"`
|
||||
}
|
||||
|
||||
type UserLoginDTO struct {
|
||||
|
|
|
|||
|
|
@ -16,13 +16,13 @@ const (
|
|||
|
||||
type User struct {
|
||||
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
||||
NomorInduk *string `json:"nomor_induk,omitempty" gorm:"unique"` // Add this field
|
||||
NomorInduk *string `json:"nomor_induk,omitempty" gorm:"unique"`
|
||||
RoleID uuid.UUID `json:"role_id" gorm:"type:uuid"`
|
||||
Role Role `json:"role" gorm:"foreignKey:RoleID"`
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name" gorm:"unique"`
|
||||
Username string `json:"username" gorm:"unique"`
|
||||
Password string `json:"password"`
|
||||
Status UserStatus `json:"status" gorm:"type:varchar(20);default:'pending'"` // Add status field
|
||||
Status UserStatus `json:"status" gorm:"type:varchar(20);default:'pending'"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ type UsersRepo interface {
|
|||
Post(user entity.User) error
|
||||
GetRoleByDepartment(departmentName string) (entity.Role, error)
|
||||
GetUserByUsername(username string) (entity.User, error)
|
||||
GetUserByNomorInduk(nomorInduk string) (entity.User, error) // Add this
|
||||
CreateUserFromExternal(user entity.User) error // Add this
|
||||
UpdateUserRole(nomorInduk string, roleID uuid.UUID) error // Add this
|
||||
GetAllUsers() ([]entity.User, error) // Add this
|
||||
GetUserByNomorInduk(nomorInduk string) (entity.User, error)
|
||||
CreateUserFromExternal(user entity.User) error
|
||||
UpdateUserRole(nomorInduk string, roleID uuid.UUID) error
|
||||
GetAllUsers() ([]entity.User, error)
|
||||
RegisterUser(user entity.User) error
|
||||
GetPendingUsers() ([]entity.User, error)
|
||||
UpdateUserStatus(userID uuid.UUID, status entity.UserStatus) error
|
||||
|
|
@ -23,6 +23,7 @@ type UsersRepo interface {
|
|||
CreateSuperAdminBypass(user entity.User) error
|
||||
UpdateUserRoleByID(userID uuid.UUID, roleID uuid.UUID) error
|
||||
UpdateUserRoleByUsername(username string, roleID uuid.UUID) error
|
||||
GetUserByName(name string) (entity.User, error)
|
||||
}
|
||||
|
||||
type usersRepo struct {
|
||||
|
|
@ -35,6 +36,13 @@ func NewUsersRepo(db *gorm.DB) UsersRepo {
|
|||
}
|
||||
}
|
||||
|
||||
func (r *usersRepo) GetUserByName(name string) (entity.User, error) {
|
||||
var user entity.User
|
||||
// Use LOWER() function for case-insensitive comparison
|
||||
err := r.db.Where("LOWER(name) = LOWER(?)", name).Preload("Role").First(&user).Error
|
||||
return user, err
|
||||
}
|
||||
|
||||
func (r *usersRepo) UpdateUserRoleByID(userID uuid.UUID, roleID uuid.UUID) error {
|
||||
return r.db.Model(&entity.User{}).Where("id = ?", userID).Update("role_id", roleID).Error
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"users_management/m/model/entity"
|
||||
"users_management/m/repository"
|
||||
"users_management/m/utils"
|
||||
"users_management/m/utils/validation"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/google/uuid"
|
||||
|
|
@ -15,10 +16,10 @@ import (
|
|||
type UsersUsecase interface {
|
||||
GetRoleByDepartment(departmentName string) (uuid.UUID, error)
|
||||
GetUserByUsername(username string) (entity.User, error)
|
||||
GetUserByNomorInduk(nomorInduk string) (entity.User, error) // Add this
|
||||
CreateUserFromExternal(nomorInduk, name, roleName string) error // Add this
|
||||
UpdateUserRole(nomorInduk, roleName string) error // Add this
|
||||
GetAllUsers() ([]entity.User, error) // Add this
|
||||
GetUserByNomorInduk(nomorInduk string) (entity.User, error)
|
||||
CreateUserFromExternal(nomorInduk, name, roleName string) error
|
||||
UpdateUserRole(nomorInduk, roleName string) error
|
||||
GetAllUsers() ([]entity.User, error)
|
||||
RegisterUser(registerDTO dto.UserRegisterDTO) error
|
||||
GetPendingUsers() ([]dto.PendingUserResponse, error)
|
||||
ApproveUser(userID uuid.UUID) error
|
||||
|
|
@ -28,6 +29,7 @@ type UsersUsecase interface {
|
|||
UpdateUserRoleByID(userID uuid.UUID, newRoleName string) error
|
||||
UpdateUserRoleByUsername(username, newRoleName string) error
|
||||
GetUserByID(userID uuid.UUID) (entity.User, error)
|
||||
GetUserByName(name string) (entity.User, error)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -37,12 +39,19 @@ type usersUsecase struct {
|
|||
}
|
||||
|
||||
func NewUsersUsecase(userRepo repository.UsersRepo) UsersUsecase {
|
||||
|
||||
validate := validator.New()
|
||||
validation.RegisterCustomValidators(validate)
|
||||
return &usersUsecase{
|
||||
userRepo: userRepo,
|
||||
validate: validator.New(),
|
||||
validate: validate,
|
||||
}
|
||||
}
|
||||
|
||||
func (u *usersUsecase) GetUserByName(name string) (entity.User, error) {
|
||||
return u.userRepo.GetUserByName(name)
|
||||
}
|
||||
|
||||
|
||||
func (u *usersUsecase) CreateSuperAdminBypass(user entity.User) error {
|
||||
return u.userRepo.CreateSuperAdminBypass(user)
|
||||
|
|
@ -92,7 +101,6 @@ func (u *usersUsecase) RegisterUser(registerDTO dto.UserRegisterDTO) error {
|
|||
if err := u.validate.Struct(registerDTO); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if username already exists
|
||||
existingUser, err := u.userRepo.GetUserByUsernameWithStatus(registerDTO.Username)
|
||||
if err == nil && existingUser.ID != uuid.Nil {
|
||||
|
|
@ -107,6 +115,11 @@ func (u *usersUsecase) RegisterUser(registerDTO dto.UserRegisterDTO) error {
|
|||
}
|
||||
}
|
||||
|
||||
existingUserByName, err := u.userRepo.GetUserByName(registerDTO.Name)
|
||||
if err == nil && existingUserByName.ID != uuid.Nil {
|
||||
return errors.New("name already exists, please use a different name")
|
||||
}
|
||||
|
||||
// Hash password
|
||||
hashedPassword, err := utils.HashPassword(registerDTO.Password)
|
||||
if err != nil {
|
||||
|
|
@ -119,7 +132,6 @@ func (u *usersUsecase) RegisterUser(registerDTO dto.UserRegisterDTO) error {
|
|||
}
|
||||
|
||||
|
||||
|
||||
// Create user with pending status
|
||||
user := entity.User{
|
||||
ID: uuid.New(),
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ func ConvertToNearestDeviceResponses(devices []entity.DeviceWithDistance, repo r
|
|||
Province: device.Province,
|
||||
City: device.City,
|
||||
District: device.District,
|
||||
ImageURL: device.ImageURL,
|
||||
BackboneCount: backboneCount,
|
||||
FishboneCount: fishboneCount,
|
||||
TowerCount: towerCount,
|
||||
|
|
@ -95,6 +96,13 @@ func ConvertToNearestDeviceDetailResponse(device entity.Device, distance float64
|
|||
fishboneInfos := convertToFishboneConnectionInfos(fishbones, device.ID)
|
||||
towerInfos := convertToTowerConnectionInfos(towers, device.Latitude, device.Longitude)
|
||||
|
||||
allImageURLs := device.GetAllImageURLs()
|
||||
if len(allImageURLs) == 0 {
|
||||
allImageURLs = []string{}
|
||||
}
|
||||
|
||||
log.Printf("Device %s has %d images", device.DeviceCode, len(allImageURLs))
|
||||
|
||||
response := res.NearestDeviceDetailResponse{
|
||||
ID: device.ID,
|
||||
DeviceCode: device.DeviceCode,
|
||||
|
|
@ -108,7 +116,7 @@ func ConvertToNearestDeviceDetailResponse(device entity.Device, distance float64
|
|||
Province: device.Province,
|
||||
City: device.City,
|
||||
District: device.District,
|
||||
ImageURL: device.ImageURL,
|
||||
ImageURLs: device.GetAllImageURLs(),
|
||||
Backbones: backboneInfos,
|
||||
Fishbones: fishboneInfos,
|
||||
Towers: towerInfos,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package validation
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
// RegisterCustomValidators registers custom validation rules
|
||||
func RegisterCustomValidators(validate *validator.Validate) {
|
||||
validate.RegisterValidation("nospace", validateNoSpace)
|
||||
validate.RegisterValidation("alphanumunderscore", validateAlphaNumUnderscore)
|
||||
validate.RegisterValidation("alphaspace", validateAlphaSpace) // Add this
|
||||
validate.RegisterValidation("alphanum", validateAlphaNum) // Add this
|
||||
}
|
||||
|
||||
// validateNoSpace checks that the field contains no spaces
|
||||
func validateNoSpace(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
return !strings.Contains(value, " ")
|
||||
}
|
||||
|
||||
// validateAlphaNumUnderscore allows alphanumeric characters and underscores only
|
||||
func validateAlphaNumUnderscore(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
matched, _ := regexp.MatchString("^[a-zA-Z0-9_]+$", value)
|
||||
return matched
|
||||
}
|
||||
|
||||
// validateAlphaSpace allows alphabetic characters and spaces only (for names)
|
||||
func validateAlphaSpace(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
for _, char := range value {
|
||||
if !unicode.IsLetter(char) && char != ' ' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// validateAlphaNum allows alphanumeric characters only
|
||||
func validateAlphaNum(fl validator.FieldLevel) bool {
|
||||
value := fl.Field().String()
|
||||
matched, _ := regexp.MatchString("^[a-zA-Z0-9]+$", value)
|
||||
return matched
|
||||
}
|
||||
Loading…
Reference in New Issue