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"`
|
Province *string `json:"province,omitempty"`
|
||||||
City *string `json:"city,omitempty"`
|
City *string `json:"city,omitempty"`
|
||||||
District *string `json:"district,omitempty"`
|
District *string `json:"district,omitempty"`
|
||||||
|
ImageURL *string `json:"image_url"`
|
||||||
|
|
||||||
// Connection counts
|
// Connection counts
|
||||||
BackboneCount int `json:"backbone_count"`
|
BackboneCount int `json:"backbone_count"`
|
||||||
|
|
@ -44,7 +45,8 @@ type NearestDeviceDetailResponse struct {
|
||||||
Province *string `json:"province,omitempty"`
|
Province *string `json:"province,omitempty"`
|
||||||
City *string `json:"city,omitempty"`
|
City *string `json:"city,omitempty"`
|
||||||
District *string `json:"district,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
|
// Detailed connection information
|
||||||
Backbones []BackboneConnectionInfo `json:"backbones"`
|
Backbones []BackboneConnectionInfo `json:"backbones"`
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserRegisterDTO struct {
|
type UserRegisterDTO struct {
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required,min=2,max=100,alphaspace"`
|
||||||
Username string `json:"username" validate:"required"`
|
Username string `json:"username" validate:"required,min=3,max=50,alphanumunderscore,nospace"`
|
||||||
Password string `json:"password" validate:"required,min=6"`
|
Password string `json:"password" validate:"required,min=6,max=100"`
|
||||||
NomorInduk string `json:"nomor_induk" validate:"required"`
|
NomorInduk string `json:"nomor_induk" validate:"required,min=3,max=50,alphanum"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UserLoginDTO struct {
|
type UserLoginDTO struct {
|
||||||
|
|
|
||||||
|
|
@ -16,13 +16,13 @@ const (
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"`
|
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"`
|
RoleID uuid.UUID `json:"role_id" gorm:"type:uuid"`
|
||||||
Role Role `json:"role" gorm:"foreignKey:RoleID"`
|
Role Role `json:"role" gorm:"foreignKey:RoleID"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name" gorm:"unique"`
|
||||||
Username string `json:"username" gorm:"unique"`
|
Username string `json:"username" gorm:"unique"`
|
||||||
Password string `json:"password"`
|
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"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,10 @@ type UsersRepo interface {
|
||||||
Post(user entity.User) error
|
Post(user entity.User) error
|
||||||
GetRoleByDepartment(departmentName string) (entity.Role, error)
|
GetRoleByDepartment(departmentName string) (entity.Role, error)
|
||||||
GetUserByUsername(username string) (entity.User, error)
|
GetUserByUsername(username string) (entity.User, error)
|
||||||
GetUserByNomorInduk(nomorInduk string) (entity.User, error) // Add this
|
GetUserByNomorInduk(nomorInduk string) (entity.User, error)
|
||||||
CreateUserFromExternal(user entity.User) error // Add this
|
CreateUserFromExternal(user entity.User) error
|
||||||
UpdateUserRole(nomorInduk string, roleID uuid.UUID) error // Add this
|
UpdateUserRole(nomorInduk string, roleID uuid.UUID) error
|
||||||
GetAllUsers() ([]entity.User, error) // Add this
|
GetAllUsers() ([]entity.User, error)
|
||||||
RegisterUser(user entity.User) error
|
RegisterUser(user entity.User) error
|
||||||
GetPendingUsers() ([]entity.User, error)
|
GetPendingUsers() ([]entity.User, error)
|
||||||
UpdateUserStatus(userID uuid.UUID, status entity.UserStatus) error
|
UpdateUserStatus(userID uuid.UUID, status entity.UserStatus) error
|
||||||
|
|
@ -23,6 +23,7 @@ type UsersRepo interface {
|
||||||
CreateSuperAdminBypass(user entity.User) error
|
CreateSuperAdminBypass(user entity.User) error
|
||||||
UpdateUserRoleByID(userID uuid.UUID, roleID uuid.UUID) error
|
UpdateUserRoleByID(userID uuid.UUID, roleID uuid.UUID) error
|
||||||
UpdateUserRoleByUsername(username string, roleID uuid.UUID) error
|
UpdateUserRoleByUsername(username string, roleID uuid.UUID) error
|
||||||
|
GetUserByName(name string) (entity.User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type usersRepo struct {
|
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 {
|
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
|
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/model/entity"
|
||||||
"users_management/m/repository"
|
"users_management/m/repository"
|
||||||
"users_management/m/utils"
|
"users_management/m/utils"
|
||||||
|
"users_management/m/utils/validation"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -15,10 +16,10 @@ import (
|
||||||
type UsersUsecase interface {
|
type UsersUsecase interface {
|
||||||
GetRoleByDepartment(departmentName string) (uuid.UUID, error)
|
GetRoleByDepartment(departmentName string) (uuid.UUID, error)
|
||||||
GetUserByUsername(username string) (entity.User, error)
|
GetUserByUsername(username string) (entity.User, error)
|
||||||
GetUserByNomorInduk(nomorInduk string) (entity.User, error) // Add this
|
GetUserByNomorInduk(nomorInduk string) (entity.User, error)
|
||||||
CreateUserFromExternal(nomorInduk, name, roleName string) error // Add this
|
CreateUserFromExternal(nomorInduk, name, roleName string) error
|
||||||
UpdateUserRole(nomorInduk, roleName string) error // Add this
|
UpdateUserRole(nomorInduk, roleName string) error
|
||||||
GetAllUsers() ([]entity.User, error) // Add this
|
GetAllUsers() ([]entity.User, error)
|
||||||
RegisterUser(registerDTO dto.UserRegisterDTO) error
|
RegisterUser(registerDTO dto.UserRegisterDTO) error
|
||||||
GetPendingUsers() ([]dto.PendingUserResponse, error)
|
GetPendingUsers() ([]dto.PendingUserResponse, error)
|
||||||
ApproveUser(userID uuid.UUID) error
|
ApproveUser(userID uuid.UUID) error
|
||||||
|
|
@ -28,6 +29,7 @@ type UsersUsecase interface {
|
||||||
UpdateUserRoleByID(userID uuid.UUID, newRoleName string) error
|
UpdateUserRoleByID(userID uuid.UUID, newRoleName string) error
|
||||||
UpdateUserRoleByUsername(username, newRoleName string) error
|
UpdateUserRoleByUsername(username, newRoleName string) error
|
||||||
GetUserByID(userID uuid.UUID) (entity.User, 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 {
|
func NewUsersUsecase(userRepo repository.UsersRepo) UsersUsecase {
|
||||||
|
|
||||||
|
validate := validator.New()
|
||||||
|
validation.RegisterCustomValidators(validate)
|
||||||
return &usersUsecase{
|
return &usersUsecase{
|
||||||
userRepo: userRepo,
|
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 {
|
func (u *usersUsecase) CreateSuperAdminBypass(user entity.User) error {
|
||||||
return u.userRepo.CreateSuperAdminBypass(user)
|
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 {
|
if err := u.validate.Struct(registerDTO); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if username already exists
|
// Check if username already exists
|
||||||
existingUser, err := u.userRepo.GetUserByUsernameWithStatus(registerDTO.Username)
|
existingUser, err := u.userRepo.GetUserByUsernameWithStatus(registerDTO.Username)
|
||||||
if err == nil && existingUser.ID != uuid.Nil {
|
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
|
// Hash password
|
||||||
hashedPassword, err := utils.HashPassword(registerDTO.Password)
|
hashedPassword, err := utils.HashPassword(registerDTO.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -119,7 +132,6 @@ func (u *usersUsecase) RegisterUser(registerDTO dto.UserRegisterDTO) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Create user with pending status
|
// Create user with pending status
|
||||||
user := entity.User{
|
user := entity.User{
|
||||||
ID: uuid.New(),
|
ID: uuid.New(),
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ func ConvertToNearestDeviceResponses(devices []entity.DeviceWithDistance, repo r
|
||||||
Province: device.Province,
|
Province: device.Province,
|
||||||
City: device.City,
|
City: device.City,
|
||||||
District: device.District,
|
District: device.District,
|
||||||
|
ImageURL: device.ImageURL,
|
||||||
BackboneCount: backboneCount,
|
BackboneCount: backboneCount,
|
||||||
FishboneCount: fishboneCount,
|
FishboneCount: fishboneCount,
|
||||||
TowerCount: towerCount,
|
TowerCount: towerCount,
|
||||||
|
|
@ -95,6 +96,13 @@ func ConvertToNearestDeviceDetailResponse(device entity.Device, distance float64
|
||||||
fishboneInfos := convertToFishboneConnectionInfos(fishbones, device.ID)
|
fishboneInfos := convertToFishboneConnectionInfos(fishbones, device.ID)
|
||||||
towerInfos := convertToTowerConnectionInfos(towers, device.Latitude, device.Longitude)
|
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{
|
response := res.NearestDeviceDetailResponse{
|
||||||
ID: device.ID,
|
ID: device.ID,
|
||||||
DeviceCode: device.DeviceCode,
|
DeviceCode: device.DeviceCode,
|
||||||
|
|
@ -108,7 +116,7 @@ func ConvertToNearestDeviceDetailResponse(device entity.Device, distance float64
|
||||||
Province: device.Province,
|
Province: device.Province,
|
||||||
City: device.City,
|
City: device.City,
|
||||||
District: device.District,
|
District: device.District,
|
||||||
ImageURL: device.ImageURL,
|
ImageURLs: device.GetAllImageURLs(),
|
||||||
Backbones: backboneInfos,
|
Backbones: backboneInfos,
|
||||||
Fishbones: fishboneInfos,
|
Fishbones: fishboneInfos,
|
||||||
Towers: towerInfos,
|
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