This commit is contained in:
aditya.siregar
2025-07-18 20:10:29 +07:00
parent 1bceae010b
commit 4f5950543e
511 changed files with 24132 additions and 34137 deletions
+122
View File
@@ -0,0 +1,122 @@
package validator
import (
"errors"
"strings"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
)
type CategoryValidator interface {
ValidateCreateCategoryRequest(req *contract.CreateCategoryRequest) (error, string)
ValidateUpdateCategoryRequest(req *contract.UpdateCategoryRequest) (error, string)
ValidateListCategoriesRequest(req *contract.ListCategoriesRequest) (error, string)
}
type CategoryValidatorImpl struct{}
func NewCategoryValidator() *CategoryValidatorImpl {
return &CategoryValidatorImpl{}
}
func (v *CategoryValidatorImpl) ValidateCreateCategoryRequest(req *contract.CreateCategoryRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
if strings.TrimSpace(req.Name) == "" {
return errors.New("name is required"), constants.MissingFieldErrorCode
}
if len(req.Name) < 1 || len(req.Name) > 255 {
return errors.New("name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
if req.Description != nil && len(*req.Description) > 1000 {
return errors.New("description cannot exceed 1000 characters"), constants.MalformedFieldErrorCode
}
if req.BusinessType != nil && strings.TrimSpace(*req.BusinessType) != "" {
validBusinessTypes := map[string]bool{
"restaurant": true,
"cafe": true,
"bar": true,
"fastfood": true,
"retail": true,
}
if !validBusinessTypes[*req.BusinessType] {
return errors.New("invalid business_type"), constants.MalformedFieldErrorCode
}
}
return nil, ""
}
func (v *CategoryValidatorImpl) ValidateUpdateCategoryRequest(req *contract.UpdateCategoryRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
// At least one field should be provided for update
if req.Name == nil && req.Description == nil && req.BusinessType == nil && req.Metadata == nil {
return errors.New("at least one field must be provided for update"), constants.MissingFieldErrorCode
}
if req.Name != nil {
if strings.TrimSpace(*req.Name) == "" {
return errors.New("name cannot be empty"), constants.MalformedFieldErrorCode
}
if len(*req.Name) < 1 || len(*req.Name) > 255 {
return errors.New("name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
}
if req.Description != nil && len(*req.Description) > 1000 {
return errors.New("description cannot exceed 1000 characters"), constants.MalformedFieldErrorCode
}
if req.BusinessType != nil && strings.TrimSpace(*req.BusinessType) != "" {
validBusinessTypes := map[string]bool{
"restaurant": true,
"cafe": true,
"bar": true,
"fastfood": true,
"retail": true,
}
if !validBusinessTypes[*req.BusinessType] {
return errors.New("invalid business_type"), constants.MalformedFieldErrorCode
}
}
return nil, ""
}
func (v *CategoryValidatorImpl) ValidateListCategoriesRequest(req *contract.ListCategoriesRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.MissingFieldErrorCode
}
if req.Page < 1 {
return errors.New("page must be at least 1"), constants.MalformedFieldErrorCode
}
if req.Limit < 1 || req.Limit > 100 {
return errors.New("limit must be between 1 and 100"), constants.MalformedFieldErrorCode
}
if req.BusinessType != "" {
validBusinessTypes := map[string]bool{
"restaurant": true,
"cafe": true,
"bar": true,
"fastfood": true,
"retail": true,
}
if !validBusinessTypes[req.BusinessType] {
return errors.New("invalid business_type"), constants.MalformedFieldErrorCode
}
}
return nil, ""
}
+176
View File
@@ -0,0 +1,176 @@
package validator
import (
"apskel-pos-be/internal/contract"
"errors"
"strings"
"github.com/go-playground/validator/v10"
)
type CustomerValidator interface {
ValidateCreateCustomerRequest(req *contract.CreateCustomerRequest) (error, string)
ValidateUpdateCustomerRequest(req *contract.UpdateCustomerRequest) (error, string)
ValidateListCustomersRequest(req *contract.ListCustomersRequest) (error, string)
ValidateSetDefaultCustomerRequest(req *contract.SetDefaultCustomerRequest) (error, string)
}
type CustomerValidatorImpl struct {
validate *validator.Validate
}
func NewCustomerValidator() *CustomerValidatorImpl {
return &CustomerValidatorImpl{
validate: validator.New(),
}
}
// ValidateCreateCustomer validates create customer request
func (v *CustomerValidatorImpl) ValidateCreateCustomerRequest(req *contract.CreateCustomerRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
// Additional custom validations
if req.Name != "" {
req.Name = strings.TrimSpace(req.Name)
if req.Name == "" {
return errors.New("name cannot be empty or whitespace only"), "INVALID_NAME"
}
if len(req.Name) > 255 {
return errors.New("name cannot exceed 255 characters"), "INVALID_NAME"
}
}
if req.Email != nil && *req.Email != "" {
*req.Email = strings.TrimSpace(strings.ToLower(*req.Email))
if *req.Email == "" {
return errors.New("email cannot be empty or whitespace only"), "INVALID_EMAIL"
}
if len(*req.Email) > 255 {
return errors.New("email cannot exceed 255 characters"), "INVALID_EMAIL"
}
}
if req.Phone != nil && *req.Phone != "" {
*req.Phone = strings.TrimSpace(*req.Phone)
if *req.Phone == "" {
return errors.New("phone cannot be empty or whitespace only"), "INVALID_PHONE"
}
if len(*req.Phone) > 20 {
return errors.New("phone cannot exceed 20 characters"), "INVALID_PHONE"
}
}
if req.Address != nil && *req.Address != "" {
*req.Address = strings.TrimSpace(*req.Address)
if *req.Address == "" {
return errors.New("address cannot be empty or whitespace only"), "INVALID_ADDRESS"
}
if len(*req.Address) > 500 {
return errors.New("address cannot exceed 500 characters"), "INVALID_ADDRESS"
}
}
return nil, ""
}
// ValidateUpdateCustomer validates update customer request
func (v *CustomerValidatorImpl) ValidateUpdateCustomerRequest(req *contract.UpdateCustomerRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
// Check if at least one field is provided
if req.Name == nil && req.Email == nil && req.Phone == nil && req.Address == nil && req.IsActive == nil {
return errors.New("at least one field must be provided for update"), "MISSING_FIELDS"
}
// Additional custom validations
if req.Name != nil && *req.Name != "" {
*req.Name = strings.TrimSpace(*req.Name)
if *req.Name == "" {
return errors.New("name cannot be empty or whitespace only"), "INVALID_NAME"
}
if len(*req.Name) > 255 {
return errors.New("name cannot exceed 255 characters"), "INVALID_NAME"
}
}
if req.Email != nil && *req.Email != "" {
*req.Email = strings.TrimSpace(strings.ToLower(*req.Email))
if *req.Email == "" {
return errors.New("email cannot be empty or whitespace only"), "INVALID_EMAIL"
}
if len(*req.Email) > 255 {
return errors.New("email cannot exceed 255 characters"), "INVALID_EMAIL"
}
}
if req.Phone != nil && *req.Phone != "" {
*req.Phone = strings.TrimSpace(*req.Phone)
if *req.Phone == "" {
return errors.New("phone cannot be empty or whitespace only"), "INVALID_PHONE"
}
if len(*req.Phone) > 20 {
return errors.New("phone cannot exceed 20 characters"), "INVALID_PHONE"
}
}
if req.Address != nil && *req.Address != "" {
*req.Address = strings.TrimSpace(*req.Address)
if *req.Address == "" {
return errors.New("address cannot be empty or whitespace only"), "INVALID_ADDRESS"
}
if len(*req.Address) > 500 {
return errors.New("address cannot exceed 500 characters"), "INVALID_ADDRESS"
}
}
return nil, ""
}
// ValidateListCustomersRequest validates list customers request
func (v *CustomerValidatorImpl) ValidateListCustomersRequest(req *contract.ListCustomersRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
// Additional custom validations
if req.Search != "" {
req.Search = strings.TrimSpace(req.Search)
if len(req.Search) > 100 {
return errors.New("search term cannot exceed 100 characters"), "INVALID_SEARCH"
}
}
if req.SortBy != "" {
req.SortBy = strings.ToLower(strings.TrimSpace(req.SortBy))
validSortFields := map[string]bool{
"name": true,
"email": true,
"created_at": true,
"updated_at": true,
}
if !validSortFields[req.SortBy] {
return errors.New("invalid sort field"), "INVALID_SORT_FIELD"
}
}
if req.SortOrder != "" {
req.SortOrder = strings.ToLower(strings.TrimSpace(req.SortOrder))
if req.SortOrder != "asc" && req.SortOrder != "desc" {
return errors.New("sort order must be 'asc' or 'desc'"), "INVALID_SORT_ORDER"
}
}
return nil, ""
}
// ValidateSetDefaultCustomer validates set default customer request
func (v *CustomerValidatorImpl) ValidateSetDefaultCustomerRequest(req *contract.SetDefaultCustomerRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, "VALIDATION_ERROR"
}
return nil, ""
}
+90
View File
@@ -0,0 +1,90 @@
package validator
import (
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"fmt"
"github.com/go-playground/validator/v10"
)
type FileValidator interface {
Validate(interface{}) error
}
type FileValidatorImpl struct {
validate *validator.Validate
}
func NewFileValidatorImpl() *FileValidatorImpl {
v := validator.New()
// Register custom validations
v.RegisterValidation("file_type", validateFileType)
return &FileValidatorImpl{
validate: v,
}
}
func (v *FileValidatorImpl) Validate(i interface{}) error {
return v.validate.Struct(i)
}
// Custom validation functions
func validateFileType(fl validator.FieldLevel) bool {
fileType, ok := fl.Field().Interface().(string)
if !ok {
return false
}
return constants.IsValidFileType(constants.FileType(fileType))
}
// Specific validation methods for different request types
func (v *FileValidatorImpl) ValidateUploadFileRequest(req *contract.UploadFileRequest) error {
if req == nil {
return fmt.Errorf("request cannot be nil")
}
// Validate file type
if req.FileType == "" {
return fmt.Errorf("file type is required")
}
if !constants.IsValidFileType(constants.FileType(req.FileType)) {
return fmt.Errorf("invalid file type: %s", req.FileType)
}
return nil
}
func (v *FileValidatorImpl) ValidateUpdateFileRequest(req *contract.UpdateFileRequest) error {
if req == nil {
return fmt.Errorf("request cannot be nil")
}
// Additional validation can be added here
return nil
}
func (v *FileValidatorImpl) ValidateListFilesRequest(req *contract.ListFilesRequest) error {
if req == nil {
return fmt.Errorf("request cannot be nil")
}
if req.Page < 1 {
return fmt.Errorf("page must be greater than 0")
}
if req.Limit < 1 || req.Limit > 100 {
return fmt.Errorf("limit must be between 1 and 100")
}
// Validate file type if provided
if req.FileType != nil && !constants.IsValidFileType(constants.FileType(*req.FileType)) {
return fmt.Errorf("invalid file type: %s", *req.FileType)
}
return nil
}
+113
View File
@@ -0,0 +1,113 @@
package validator
import (
"errors"
"strings"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"github.com/google/uuid"
)
type InventoryValidator interface {
ValidateCreateInventoryRequest(req *contract.CreateInventoryRequest) (error, string)
ValidateUpdateInventoryRequest(req *contract.UpdateInventoryRequest) (error, string)
ValidateListInventoryRequest(req *contract.ListInventoryRequest) (error, string)
ValidateAdjustInventoryRequest(req *contract.AdjustInventoryRequest) (error, string)
}
type InventoryValidatorImpl struct{}
func NewInventoryValidator() *InventoryValidatorImpl {
return &InventoryValidatorImpl{}
}
func (v *InventoryValidatorImpl) ValidateCreateInventoryRequest(req *contract.CreateInventoryRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
if req.OutletID == uuid.Nil {
return errors.New("outlet_id is required"), constants.MissingFieldErrorCode
}
if req.ProductID == uuid.Nil {
return errors.New("product_id is required"), constants.MissingFieldErrorCode
}
if req.Quantity < 0 {
return errors.New("quantity must be non-negative"), constants.MalformedFieldErrorCode
}
if req.ReorderLevel < 0 {
return errors.New("reorder_level must be non-negative"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *InventoryValidatorImpl) ValidateUpdateInventoryRequest(req *contract.UpdateInventoryRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
// At least one field should be provided for update
if req.Quantity == nil && req.ReorderLevel == nil {
return errors.New("at least one field must be provided for update"), constants.MissingFieldErrorCode
}
if req.Quantity != nil && *req.Quantity < 0 {
return errors.New("quantity must be non-negative"), constants.MalformedFieldErrorCode
}
if req.ReorderLevel != nil && *req.ReorderLevel < 0 {
return errors.New("reorder_level must be non-negative"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *InventoryValidatorImpl) ValidateListInventoryRequest(req *contract.ListInventoryRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.MissingFieldErrorCode
}
if req.Page < 1 {
return errors.New("page must be at least 1"), constants.MalformedFieldErrorCode
}
if req.Limit < 1 || req.Limit > 100 {
return errors.New("limit must be between 1 and 100"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *InventoryValidatorImpl) ValidateAdjustInventoryRequest(req *contract.AdjustInventoryRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
if req.ProductID == uuid.Nil {
return errors.New("product_id is required"), constants.MissingFieldErrorCode
}
if req.OutletID == uuid.Nil {
return errors.New("outlet_id is required"), constants.MissingFieldErrorCode
}
if req.Delta == 0 {
return errors.New("delta cannot be zero"), constants.MalformedFieldErrorCode
}
if strings.TrimSpace(req.Reason) == "" {
return errors.New("reason is required"), constants.MissingFieldErrorCode
}
if len(req.Reason) < 1 || len(req.Reason) > 255 {
return errors.New("reason must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
return nil, ""
}
+74
View File
@@ -0,0 +1,74 @@
package validator
import (
"github.com/go-playground/validator/v10"
)
type OrderValidator interface {
Validate(interface{}) error
}
type OrderValidatorImpl struct {
validate *validator.Validate
}
func NewOrderValidator() OrderValidator {
v := validator.New()
return &OrderValidatorImpl{
validate: v,
}
}
func (v *OrderValidatorImpl) Validate(i interface{}) error {
return v.validate.Struct(i)
}
func (v *OrderValidatorImpl) validateOrderType(fl validator.FieldLevel) bool {
orderType := fl.Field().String()
switch orderType {
case "dine_in", "takeaway", "delivery":
return true
default:
return false
}
}
func (v *OrderValidatorImpl) validateOrderStatus(fl validator.FieldLevel) bool {
status := fl.Field().String()
switch status {
case "pending", "preparing", "ready", "completed", "cancelled":
return true
default:
return false
}
}
func (v *OrderValidatorImpl) validatePaymentStatus(fl validator.FieldLevel) bool {
status := fl.Field().String()
switch status {
case "pending", "partially_paid", "completed", "refunded":
return true
default:
return false
}
}
func (v *OrderValidatorImpl) validatePaymentTransactionStatus(fl validator.FieldLevel) bool {
status := fl.Field().String()
switch status {
case "pending", "completed", "failed", "refunded", "cancelled":
return true
default:
return false
}
}
func (v *OrderValidatorImpl) validateOrderItemStatus(fl validator.FieldLevel) bool {
status := fl.Field().String()
switch status {
case "pending", "preparing", "ready", "completed", "cancelled":
return true
default:
return false
}
}
@@ -0,0 +1,165 @@
package validator
import (
"errors"
"strings"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
)
type OrganizationValidator interface {
ValidateCreateOrganizationRequest(req *contract.CreateOrganizationRequest) (error, string)
ValidateUpdateOrganizationRequest(req *contract.UpdateOrganizationRequest) (error, string)
ValidateListOrganizationsRequest(req *contract.ListOrganizationsRequest) (error, string)
}
type OrganizationValidatorImpl struct{}
func NewOrganizationValidator() *OrganizationValidatorImpl {
return &OrganizationValidatorImpl{}
}
func (v *OrganizationValidatorImpl) ValidateCreateOrganizationRequest(req *contract.CreateOrganizationRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
if strings.TrimSpace(req.OrganizationName) == "" {
return errors.New("organization_name is required"), constants.MissingFieldErrorCode
}
if len(req.OrganizationName) < 1 || len(req.OrganizationName) > 255 {
return errors.New("organization_name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
if req.OrganizationEmail != nil && strings.TrimSpace(*req.OrganizationEmail) != "" {
if !isValidEmail(*req.OrganizationEmail) {
return errors.New("organization_email format is invalid"), constants.MalformedFieldErrorCode
}
}
if req.OrganizationPhoneNumber != nil && strings.TrimSpace(*req.OrganizationPhoneNumber) != "" {
if !isValidPhone(*req.OrganizationPhoneNumber) {
return errors.New("organization_phone_number format is invalid"), constants.MalformedFieldErrorCode
}
}
if strings.TrimSpace(req.PlanType) == "" {
return errors.New("plan_type is required"), constants.MissingFieldErrorCode
}
if !isValidPlanType(req.PlanType) {
return errors.New("invalid plan_type"), constants.MalformedFieldErrorCode
}
if strings.TrimSpace(req.AdminName) == "" {
return errors.New("admin_name is required"), constants.MissingFieldErrorCode
}
if len(req.AdminName) < 1 || len(req.AdminName) > 255 {
return errors.New("admin_name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
if strings.TrimSpace(req.AdminEmail) == "" {
return errors.New("admin_email is required"), constants.MissingFieldErrorCode
}
if !isValidEmail(req.AdminEmail) {
return errors.New("admin_email format is invalid"), constants.MalformedFieldErrorCode
}
if strings.TrimSpace(req.AdminPassword) == "" {
return errors.New("admin_password is required"), constants.MissingFieldErrorCode
}
if len(req.AdminPassword) < 6 {
return errors.New("admin_password must be at least 6 characters"), constants.MalformedFieldErrorCode
}
if strings.TrimSpace(req.OutletName) == "" {
return errors.New("outlet_name is required"), constants.MissingFieldErrorCode
}
if len(req.OutletName) < 1 || len(req.OutletName) > 255 {
return errors.New("outlet_name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
if strings.TrimSpace(req.OutletCurrency) == "" {
return errors.New("outlet_currency is required"), constants.MissingFieldErrorCode
}
if len(req.OutletCurrency) != 3 {
return errors.New("outlet_currency must be exactly 3 characters"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *OrganizationValidatorImpl) ValidateUpdateOrganizationRequest(req *contract.UpdateOrganizationRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
// At least one field should be provided for update
if req.Name == nil && req.Email == nil && req.PhoneNumber == nil && req.PlanType == nil {
return errors.New("at least one field must be provided for update"), constants.MissingFieldErrorCode
}
if req.Name != nil {
if strings.TrimSpace(*req.Name) == "" {
return errors.New("name cannot be empty"), constants.MalformedFieldErrorCode
}
if len(*req.Name) < 1 || len(*req.Name) > 255 {
return errors.New("name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
}
if req.Email != nil {
if strings.TrimSpace(*req.Email) == "" {
return errors.New("email cannot be empty"), constants.MalformedFieldErrorCode
}
if !isValidEmail(*req.Email) {
return errors.New("email format is invalid"), constants.MalformedFieldErrorCode
}
}
if req.PhoneNumber != nil && strings.TrimSpace(*req.PhoneNumber) != "" {
if !isValidPhone(*req.PhoneNumber) {
return errors.New("phone_number format is invalid"), constants.MalformedFieldErrorCode
}
}
if req.PlanType != nil {
if strings.TrimSpace(*req.PlanType) == "" {
return errors.New("plan_type cannot be empty"), constants.MalformedFieldErrorCode
}
if !isValidPlanType(*req.PlanType) {
return errors.New("invalid plan_type"), constants.MalformedFieldErrorCode
}
}
return nil, ""
}
func (v *OrganizationValidatorImpl) ValidateListOrganizationsRequest(req *contract.ListOrganizationsRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.MissingFieldErrorCode
}
if req.Page < 1 {
return errors.New("page must be at least 1"), constants.MalformedFieldErrorCode
}
if req.Limit < 1 || req.Limit > 100 {
return errors.New("limit must be between 1 and 100"), constants.MalformedFieldErrorCode
}
if req.PlanType != "" && !isValidPlanType(req.PlanType) {
return errors.New("invalid plan_type"), constants.MalformedFieldErrorCode
}
return nil, ""
}
// Helper functions moved to validator_helpers.go
+95
View File
@@ -0,0 +1,95 @@
package validator
import (
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"fmt"
"github.com/go-playground/validator/v10"
)
type OutletValidator interface {
ValidateListOutletsRequest(req *contract.ListOutletsRequest) (error, string)
ValidateCreateOutletRequest(req *contract.CreateOutletRequest) (error, string)
ValidateUpdateOutletRequest(req *contract.UpdateOutletRequest) (error, string)
}
type OutletValidatorImpl struct {
validate *validator.Validate
}
func NewOutletValidator() *OutletValidatorImpl {
return &OutletValidatorImpl{
validate: validator.New(),
}
}
func (v *OutletValidatorImpl) ValidateListOutletsRequest(req *contract.ListOutletsRequest) (error, string) {
if req.Page < 1 {
return fmt.Errorf("page must be greater than 0"), constants.MalformedFieldErrorCode
}
if req.Limit < 1 {
return fmt.Errorf("limit must be greater than 0"), constants.MalformedFieldErrorCode
}
if req.Limit > 100 {
return fmt.Errorf("limit cannot exceed 100"), constants.MalformedFieldErrorCode
}
if req.BusinessType != nil {
if err := v.validate.Var(*req.BusinessType, "oneof=restaurant cafe bar fastfood retail"); err != nil {
return fmt.Errorf("invalid business type"), constants.MalformedFieldErrorCode
}
}
return nil, ""
}
func (v *OutletValidatorImpl) ValidateCreateOutletRequest(req *contract.CreateOutletRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, constants.MalformedFieldErrorCode
}
if req.TaxRate < 0 || req.TaxRate > 1 {
return fmt.Errorf("tax rate must be between 0 and 1"), constants.MalformedFieldErrorCode
}
if err := v.validate.Var(req.BusinessType, "oneof=restaurant cafe bar fastfood retail"); err != nil {
return fmt.Errorf("invalid business type"), constants.MalformedFieldErrorCode
}
if err := v.validate.Var(req.Currency, "len=3"); err != nil {
return fmt.Errorf("currency must be 3 characters"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *OutletValidatorImpl) ValidateUpdateOutletRequest(req *contract.UpdateOutletRequest) (error, string) {
if req.Name != nil {
if err := v.validate.Var(*req.Name, "required,min=1,max=255"); err != nil {
return fmt.Errorf("name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
}
if req.Address != nil {
if err := v.validate.Var(*req.Address, "required,min=1,max=500"); err != nil {
return fmt.Errorf("address must be between 1 and 500 characters"), constants.MalformedFieldErrorCode
}
}
if req.PhoneNumber != nil {
if err := v.validate.Var(*req.PhoneNumber, "e164"); err != nil {
return fmt.Errorf("invalid phone number format"), constants.MalformedFieldErrorCode
}
}
if req.TaxRate != nil {
if *req.TaxRate < 0 || *req.TaxRate > 1 {
return fmt.Errorf("tax rate must be between 0 and 1"), constants.MalformedFieldErrorCode
}
}
return nil, ""
}
@@ -0,0 +1,108 @@
package validator
import (
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"github.com/go-playground/validator/v10"
)
type PaymentMethodValidator interface {
ValidateCreatePaymentMethodRequest(req *contract.CreatePaymentMethodRequest) (error, string)
ValidateUpdatePaymentMethodRequest(req *contract.UpdatePaymentMethodRequest) (error, string)
ValidateListPaymentMethodsRequest(req *contract.ListPaymentMethodsRequest) (error, string)
}
type PaymentMethodValidatorImpl struct {
validate *validator.Validate
}
func NewPaymentMethodValidator() PaymentMethodValidator {
return &PaymentMethodValidatorImpl{
validate: validator.New(),
}
}
func (v *PaymentMethodValidatorImpl) ValidateCreatePaymentMethodRequest(req *contract.CreatePaymentMethodRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, constants.ValidationErrorCode
}
// Additional business logic validation
if req.Name == "" {
return constants.ErrPaymentMethodNameRequired, constants.MissingFieldErrorCode
}
if req.Type == "" {
return constants.ErrPaymentMethodTypeRequired, constants.MissingFieldErrorCode
}
// Validate payment method type
if !v.isValidPaymentMethodType(req.Type) {
return constants.ErrInvalidPaymentMethodType, constants.InvalidFieldErrorCode
}
return nil, ""
}
func (v *PaymentMethodValidatorImpl) ValidateUpdatePaymentMethodRequest(req *contract.UpdatePaymentMethodRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, constants.ValidationErrorCode
}
// Additional business logic validation
if req.Name != nil && *req.Name == "" {
return constants.ErrPaymentMethodNameRequired, constants.MissingFieldErrorCode
}
if req.Type != nil && *req.Type == "" {
return constants.ErrPaymentMethodTypeRequired, constants.MissingFieldErrorCode
}
// Validate payment method type if provided
if req.Type != nil && !v.isValidPaymentMethodType(*req.Type) {
return constants.ErrInvalidPaymentMethodType, constants.InvalidFieldErrorCode
}
return nil, ""
}
func (v *PaymentMethodValidatorImpl) ValidateListPaymentMethodsRequest(req *contract.ListPaymentMethodsRequest) (error, string) {
if err := v.validate.Struct(req); err != nil {
return err, constants.ValidationErrorCode
}
// Additional business logic validation
if req.Page < 1 {
return constants.ErrInvalidPageNumber, constants.InvalidFieldErrorCode
}
if req.Limit < 1 || req.Limit > 100 {
return constants.ErrInvalidLimit, constants.InvalidFieldErrorCode
}
// Validate payment method type if provided
if req.Type != nil && !v.isValidPaymentMethodType(*req.Type) {
return constants.ErrInvalidPaymentMethodType, constants.InvalidFieldErrorCode
}
return nil, ""
}
func (v *PaymentMethodValidatorImpl) isValidPaymentMethodType(paymentMethodType string) bool {
validTypes := []string{
string(constants.PaymentMethodTypeCash),
string(constants.PaymentMethodTypeCard),
string(constants.PaymentMethodTypeDigitalWallet),
string(constants.PaymentMethodTypeQR),
string(constants.PaymentMethodTypeEDC),
}
for _, validType := range validTypes {
if paymentMethodType == validType {
return true
}
}
return false
}
+126
View File
@@ -0,0 +1,126 @@
package validator
import (
"errors"
"strings"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"github.com/google/uuid"
)
type ProductValidator interface {
ValidateCreateProductRequest(req *contract.CreateProductRequest) (error, string)
ValidateUpdateProductRequest(req *contract.UpdateProductRequest) (error, string)
ValidateListProductsRequest(req *contract.ListProductsRequest) (error, string)
}
type ProductValidatorImpl struct{}
func NewProductValidator() *ProductValidatorImpl {
return &ProductValidatorImpl{}
}
func (v *ProductValidatorImpl) ValidateCreateProductRequest(req *contract.CreateProductRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
if req.CategoryID == uuid.Nil {
return errors.New("category_id is required"), constants.MissingFieldErrorCode
}
if strings.TrimSpace(req.Name) == "" {
return errors.New("name is required"), constants.MissingFieldErrorCode
}
if len(req.Name) < 1 || len(req.Name) > 255 {
return errors.New("name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
if req.Price < 0 {
return errors.New("price must be non-negative"), constants.MalformedFieldErrorCode
}
if req.Cost != nil && *req.Cost < 0 {
return errors.New("cost must be non-negative"), constants.MalformedFieldErrorCode
}
if req.SKU != nil && len(*req.SKU) > 100 {
return errors.New("sku cannot exceed 100 characters"), constants.MalformedFieldErrorCode
}
if req.Description != nil && len(*req.Description) > 1000 {
return errors.New("description cannot exceed 1000 characters"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *ProductValidatorImpl) ValidateUpdateProductRequest(req *contract.UpdateProductRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
// At least one field should be provided for update
if req.CategoryID == nil && req.SKU == nil && req.Name == nil && req.Description == nil &&
req.Price == nil && req.Cost == nil && req.BusinessType == nil && req.Metadata == nil && req.IsActive == nil {
return errors.New("at least one field must be provided for update"), constants.MissingFieldErrorCode
}
if req.Name != nil {
if strings.TrimSpace(*req.Name) == "" {
return errors.New("name cannot be empty"), constants.MalformedFieldErrorCode
}
if len(*req.Name) < 1 || len(*req.Name) > 255 {
return errors.New("name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
}
if req.Price != nil && *req.Price < 0 {
return errors.New("price must be non-negative"), constants.MalformedFieldErrorCode
}
if req.Cost != nil && *req.Cost < 0 {
return errors.New("cost must be non-negative"), constants.MalformedFieldErrorCode
}
if req.SKU != nil && len(*req.SKU) > 100 {
return errors.New("sku cannot exceed 100 characters"), constants.MalformedFieldErrorCode
}
if req.Description != nil && len(*req.Description) > 1000 {
return errors.New("description cannot exceed 1000 characters"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *ProductValidatorImpl) ValidateListProductsRequest(req *contract.ListProductsRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.MissingFieldErrorCode
}
if req.Page < 1 {
return errors.New("page must be at least 1"), constants.MalformedFieldErrorCode
}
if req.Limit < 1 || req.Limit > 100 {
return errors.New("limit must be between 1 and 100"), constants.MalformedFieldErrorCode
}
if req.MinPrice != nil && *req.MinPrice < 0 {
return errors.New("min_price must be non-negative"), constants.MalformedFieldErrorCode
}
if req.MaxPrice != nil && *req.MaxPrice < 0 {
return errors.New("max_price must be non-negative"), constants.MalformedFieldErrorCode
}
if req.MinPrice != nil && req.MaxPrice != nil && *req.MinPrice > *req.MaxPrice {
return errors.New("min_price cannot be greater than max_price"), constants.MalformedFieldErrorCode
}
return nil, ""
}
@@ -0,0 +1,64 @@
package validator
import (
"errors"
"strings"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"github.com/google/uuid"
)
type ProductVariantValidator interface {
ValidateCreateProductVariantRequest(req *contract.CreateProductVariantRequest) (error, string)
ValidateUpdateProductVariantRequest(req *contract.UpdateProductVariantRequest) (error, string)
}
type ProductVariantValidatorImpl struct{}
func NewProductVariantValidator() *ProductVariantValidatorImpl {
return &ProductVariantValidatorImpl{}
}
func (v *ProductVariantValidatorImpl) ValidateCreateProductVariantRequest(req *contract.CreateProductVariantRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
if req.ProductID == uuid.Nil {
return errors.New("product_id is required"), constants.MissingFieldErrorCode
}
if strings.TrimSpace(req.Name) == "" {
return errors.New("name is required"), constants.MissingFieldErrorCode
}
if len(req.Name) < 1 || len(req.Name) > 255 {
return errors.New("name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *ProductVariantValidatorImpl) ValidateUpdateProductVariantRequest(req *contract.UpdateProductVariantRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
// At least one field should be provided for update
if req.Name == nil && req.PriceModifier == nil && req.Metadata == nil {
return errors.New("at least one field must be provided for update"), constants.MissingFieldErrorCode
}
if req.Name != nil {
if strings.TrimSpace(*req.Name) == "" {
return errors.New("name cannot be empty"), constants.MalformedFieldErrorCode
}
if len(*req.Name) < 1 || len(*req.Name) > 255 {
return errors.New("name must be between 1 and 255 characters"), constants.MalformedFieldErrorCode
}
}
return nil, ""
}
+151
View File
@@ -0,0 +1,151 @@
package validator
import (
"errors"
"strings"
"apskel-pos-be/internal/constants"
"apskel-pos-be/internal/contract"
"github.com/google/uuid"
)
type UserValidatorImpl struct{}
func NewUserValidator() *UserValidatorImpl {
return &UserValidatorImpl{}
}
func (v *UserValidatorImpl) ValidateCreateUserRequest(req *contract.CreateUserRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
if req.OrganizationID == uuid.Nil {
return errors.New("organization_id is required"), constants.MissingFieldErrorCode
}
if strings.TrimSpace(req.Email) == "" {
return errors.New("email is required"), constants.MissingFieldErrorCode
}
if !isValidEmail(req.Email) {
return errors.New("email format is invalid"), constants.MalformedFieldErrorCode
}
if strings.TrimSpace(req.Password) == "" {
return errors.New("password is required"), constants.MissingFieldErrorCode
}
if len(req.Password) < 6 {
return errors.New("password must be at least 6 characters"), constants.MalformedFieldErrorCode
}
if strings.TrimSpace(req.Role) == "" {
return errors.New("role is required"), constants.MissingFieldErrorCode
}
if !isValidUserRole(req.Role) {
return errors.New("invalid user role"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *UserValidatorImpl) ValidateUpdateUserRequest(req *contract.UpdateUserRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
if req.Email == nil && req.Role == nil && req.OutletID == nil && req.IsActive == nil {
return errors.New("at least one field must be provided for update"), constants.MissingFieldErrorCode
}
if req.Email != nil {
if strings.TrimSpace(*req.Email) == "" {
return errors.New("email cannot be empty"), constants.MalformedFieldErrorCode
}
if !isValidEmail(*req.Email) {
return errors.New("email format is invalid"), constants.MalformedFieldErrorCode
}
}
if req.Role != nil {
if strings.TrimSpace(*req.Role) == "" {
return errors.New("role cannot be empty"), constants.MalformedFieldErrorCode
}
if !isValidUserRole(*req.Role) {
return errors.New("invalid user role"), constants.MalformedFieldErrorCode
}
}
return nil, ""
}
func (v *UserValidatorImpl) ValidateListUsersRequest(req *contract.ListUsersRequest) (error, string) {
if req == nil {
return errors.New("request is required"), constants.MissingFieldErrorCode
}
if req.Page <= 0 {
return errors.New("page must be greater than 0"), constants.MalformedFieldErrorCode
}
if req.Limit <= 0 {
return errors.New("limit must be greater than 0"), constants.MalformedFieldErrorCode
}
if req.Limit > 100 {
return errors.New("limit cannot exceed 100"), constants.MalformedFieldErrorCode
}
if req.Role != nil && !isValidUserRole(*req.Role) {
return errors.New("invalid user role filter"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *UserValidatorImpl) ValidateChangePasswordRequest(req *contract.ChangePasswordRequest) (error, string) {
if req == nil {
return errors.New("request body is required"), constants.MissingFieldErrorCode
}
if strings.TrimSpace(req.CurrentPassword) == "" {
return errors.New("current_password is required"), constants.MissingFieldErrorCode
}
if strings.TrimSpace(req.NewPassword) == "" {
return errors.New("new_password is required"), constants.MissingFieldErrorCode
}
if len(req.NewPassword) < 8 {
return errors.New("new_password must be at least 8 characters"), constants.MalformedFieldErrorCode
}
if req.CurrentPassword == req.NewPassword {
return errors.New("new password must be different from current password"), constants.MalformedFieldErrorCode
}
return nil, ""
}
func (v *UserValidatorImpl) ValidateUserID(userID uuid.UUID) (error, string) {
if userID == uuid.Nil {
return errors.New("user_id is required"), constants.MissingFieldErrorCode
}
return nil, ""
}
func isValidUserRole(role string) bool {
validRoles := map[string]bool{
string(constants.RoleAdmin): true,
string(constants.RoleManager): true,
string(constants.RoleCashier): true,
string(constants.RoleWaiter): true,
}
return validRoles[role]
}
+32
View File
@@ -0,0 +1,32 @@
package validator
import "regexp"
// Shared helper functions for validators
func isValidEmail(email string) bool {
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
return emailRegex.MatchString(email)
}
func isValidPhone(phone string) bool {
phoneRegex := regexp.MustCompile(`^\+?[1-9]\d{1,14}$`)
return phoneRegex.MatchString(phone)
}
func isValidRole(role string) bool {
validRoles := map[string]bool{
"admin": true,
"manager": true,
"cashier": true,
}
return validRoles[role]
}
func isValidPlanType(planType string) bool {
validPlanTypes := map[string]bool{
"basic": true,
"premium": true,
"enterprise": true,
}
return validPlanTypes[planType]
}