diff --git a/internal/appcontext/context_info.go b/internal/appcontext/context_info.go index 7261b74..63f6365 100644 --- a/internal/appcontext/context_info.go +++ b/internal/appcontext/context_info.go @@ -20,7 +20,7 @@ type ContextInfo struct { CorrelationID string UserID uuid.UUID OrganizationID uuid.UUID - OutletID string + OutletID uuid.UUID AppVersion string AppID string AppType string @@ -61,7 +61,7 @@ func FromGinContext(ctx context.Context) *ContextInfo { return &ContextInfo{ CorrelationID: value(ctx, CorrelationIDKey), UserID: uuidValue(ctx, UserIDKey), - OutletID: value(ctx, OutletIDKey), + OutletID: uuidValue(ctx, OutletIDKey), OrganizationID: uuidValue(ctx, OrganizationIDKey), AppVersion: value(ctx, AppVersionKey), AppID: value(ctx, AppIDKey), diff --git a/internal/contract/payment_method_contract.go b/internal/contract/payment_method_contract.go index 0fe2233..9153b7a 100644 --- a/internal/contract/payment_method_contract.go +++ b/internal/contract/payment_method_contract.go @@ -7,7 +7,8 @@ import ( ) type CreatePaymentMethodRequest struct { - OrganizationID uuid.UUID `json:"organization_id" validate:"required"` + OrganizationID uuid.UUID `json:"organization_id"` + OutletID uuid.UUID `json:"outlet_id""'` Name string `json:"name" validate:"required,min=1,max=100"` Type string `json:"type" validate:"required,oneof=cash card digital_wallet qr edc"` Processor *string `json:"processor,omitempty" validate:"omitempty,max=100"` diff --git a/internal/entities/user.go b/internal/entities/user.go index d69214b..f29332e 100644 --- a/internal/entities/user.go +++ b/internal/entities/user.go @@ -3,7 +3,6 @@ package entities import ( "database/sql/driver" "encoding/json" - "errors" "time" "github.com/google/uuid" @@ -13,15 +12,19 @@ import ( type UserRole string const ( - RoleAdmin UserRole = "admin" - RoleManager UserRole = "manager" - RoleCashier UserRole = "cashier" - RoleWaiter UserRole = "waiter" + RoleSuperAdmin UserRole = "superadmin" + RoleAdmin UserRole = "admin" + RoleManager UserRole = "manager" + RoleCashier UserRole = "cashier" + RoleWaiter UserRole = "waiter" ) type Permissions map[string]interface{} func (p Permissions) Value() (driver.Value, error) { + if p == nil { + return "{}", nil + } return json.Marshal(p) } @@ -31,12 +34,33 @@ func (p *Permissions) Scan(value interface{}) error { return nil } - bytes, ok := value.([]byte) - if !ok { - return errors.New("type assertion to []byte failed") + switch v := value.(type) { + case []byte: + if len(v) == 0 || string(v) == "{}" { + *p = make(Permissions) + return nil + } + // Try to unmarshal, if it fails, return empty permissions + if err := json.Unmarshal(v, p); err != nil { + *p = make(Permissions) + return nil + } + return nil + case string: + if v == "" || v == "{}" { + *p = make(Permissions) + return nil + } + // Try to unmarshal, if it fails, return empty permissions + if err := json.Unmarshal([]byte(v), p); err != nil { + *p = make(Permissions) + return nil + } + return nil + default: + *p = make(Permissions) + return nil } - - return json.Unmarshal(bytes, p) } type User struct { @@ -60,6 +84,16 @@ func (u *User) BeforeCreate(tx *gorm.DB) error { if u.ID == uuid.Nil { u.ID = uuid.New() } + if u.Permissions == nil { + u.Permissions = make(Permissions) + } + return nil +} + +func (u *User) BeforeUpdate(tx *gorm.DB) error { + if u.Permissions == nil { + u.Permissions = make(Permissions) + } return nil } diff --git a/internal/handler/payment_method_handler.go b/internal/handler/payment_method_handler.go index 571e0d9..0e24774 100644 --- a/internal/handler/payment_method_handler.go +++ b/internal/handler/payment_method_handler.go @@ -49,6 +49,9 @@ func (h *PaymentMethodHandler) CreatePaymentMethod(c *gin.Context) { return } + req.OrganizationID = contextInfo.OrganizationID + req.OutletID = contextInfo.OutletID + paymentMethodResponse := h.paymentMethodService.CreatePaymentMethod(ctx, contextInfo, &req) if paymentMethodResponse.HasErrors() { errorResp := paymentMethodResponse.GetErrors()[0] diff --git a/internal/handler/unit_handler.go b/internal/handler/unit_handler.go index eb17e2c..160a856 100644 --- a/internal/handler/unit_handler.go +++ b/internal/handler/unit_handler.go @@ -75,7 +75,6 @@ func (h *UnitHandler) GetAll(c *gin.Context) { ctx := c.Request.Context() contextInfo := appcontext.FromGinContext(ctx) - // Get query parameters pageStr := c.DefaultQuery("page", "1") limitStr := c.DefaultQuery("limit", "10") search := c.Query("search") diff --git a/internal/mappers/payment_method_mapper.go b/internal/mappers/payment_method_mapper.go index d4c8f5e..651e721 100644 --- a/internal/mappers/payment_method_mapper.go +++ b/internal/mappers/payment_method_mapper.go @@ -100,6 +100,7 @@ func PaymentMethodModelToEntity(model *models.PaymentMethod) *entities.PaymentMe func CreatePaymentMethodContractToModel(req *contract.CreatePaymentMethodRequest) *models.CreatePaymentMethodRequest { return &models.CreatePaymentMethodRequest{ OrganizationID: req.OrganizationID, + OutletID: req.OutletID, Name: req.Name, Type: constants.PaymentMethodType(req.Type), Processor: req.Processor, diff --git a/internal/models/payment_method.go b/internal/models/payment_method.go index 519d288..398586d 100644 --- a/internal/models/payment_method.go +++ b/internal/models/payment_method.go @@ -21,6 +21,7 @@ type PaymentMethod struct { type CreatePaymentMethodRequest struct { OrganizationID uuid.UUID `validate:"required"` + OutletID uuid.UUID `validate:"required"` Name string `validate:"required,min=1,max=100"` Type constants.PaymentMethodType `validate:"required"` Processor *string `validate:"omitempty,max=100"` diff --git a/internal/processor/payment_method_processor.go b/internal/processor/payment_method_processor.go index cac2996..952ab86 100644 --- a/internal/processor/payment_method_processor.go +++ b/internal/processor/payment_method_processor.go @@ -31,7 +31,6 @@ func NewPaymentMethodProcessorImpl(paymentMethodRepo repository.PaymentMethodRep } func (p *PaymentMethodProcessorImpl) CreatePaymentMethod(ctx context.Context, req *models.CreatePaymentMethodRequest) (*models.PaymentMethodResponse, error) { - // Check if payment method with same name already exists exists, err := p.paymentMethodRepo.ExistsByName(ctx, req.OrganizationID, req.Name, nil) if err != nil { return nil, fmt.Errorf("failed to check payment method name uniqueness: %w", err) @@ -40,21 +39,16 @@ func (p *PaymentMethodProcessorImpl) CreatePaymentMethod(ctx context.Context, re return nil, fmt.Errorf("payment method with name '%s' already exists for this organization", req.Name) } - // Map request to entity paymentMethodEntity := mappers.CreatePaymentMethodRequestToEntity(req) - - // Create payment method if err := p.paymentMethodRepo.Create(ctx, paymentMethodEntity); err != nil { return nil, fmt.Errorf("failed to create payment method: %w", err) } - // Get created payment method createdPaymentMethod, err := p.paymentMethodRepo.GetByID(ctx, paymentMethodEntity.ID) if err != nil { return nil, fmt.Errorf("failed to retrieve created payment method: %w", err) } - // Map entity to response response := mappers.PaymentMethodEntityToResponse(createdPaymentMethod) return response, nil } @@ -70,7 +64,6 @@ func (p *PaymentMethodProcessorImpl) GetPaymentMethodByID(ctx context.Context, i } func (p *PaymentMethodProcessorImpl) ListPaymentMethods(ctx context.Context, req *models.ListPaymentMethodsRequest) (*models.ListPaymentMethodsResponse, error) { - // Build filters filters := make(map[string]interface{}) if req.OrganizationID != nil { filters["organization_id"] = *req.OrganizationID @@ -85,10 +78,8 @@ func (p *PaymentMethodProcessorImpl) ListPaymentMethods(ctx context.Context, req filters["search"] = req.Search } - // Calculate offset offset := (req.Page - 1) * req.Limit - // Get payment methods paymentMethods, total, err := p.paymentMethodRepo.List(ctx, filters, req.Limit, offset) if err != nil { return nil, fmt.Errorf("failed to list payment methods: %w", err) diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go index a9e2747..402c4e3 100644 --- a/internal/repository/user_repository.go +++ b/internal/repository/user_repository.go @@ -1,6 +1,7 @@ package repository import ( + "apskel-pos-be/internal/logger" "context" "apskel-pos-be/internal/entities" @@ -25,19 +26,48 @@ func (r *UserRepositoryImpl) Create(ctx context.Context, user *entities.User) er func (r *UserRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.User, error) { var user entities.User - err := r.db.WithContext(ctx).First(&user, "id = ?", id).Error + + // Use raw SQL to avoid GORM scanning issues with JSONB + query := ` + SELECT id, organization_id, outlet_id, name, email, password_hash, role, + COALESCE(permissions, '{}'::jsonb) as permissions, is_active, created_at, updated_at + FROM users + WHERE id = ? + ` + + err := r.db.WithContext(ctx).Raw(query, id).Scan(&user).Error if err != nil { return nil, err } + + // Ensure permissions is properly initialized + if user.Permissions == nil { + user.Permissions = make(entities.Permissions) + } + return &user, nil } func (r *UserRepositoryImpl) GetByEmail(ctx context.Context, email string) (*entities.User, error) { var user entities.User - err := r.db.WithContext(ctx).Where("email = ?", email).First(&user).Error + + query := ` + SELECT id, organization_id, outlet_id, name, email, password_hash, role, + COALESCE(permissions, '{}'::jsonb) as permissions, is_active, created_at, updated_at + FROM users + WHERE email = ? + ` + + err := r.db.WithContext(ctx).Raw(query, email).Scan(&user).Error if err != nil { + logger.FromContext(ctx).WithError(err).Error("UserRepositoryImpl::GetByEmail -> failed to get user by email") return nil, err } + + if user.Permissions == nil { + user.Permissions = make(entities.Permissions) + } + return &user, nil } diff --git a/internal/service/payment_method_service.go b/internal/service/payment_method_service.go index 8df2366..d0ecd7d 100644 --- a/internal/service/payment_method_service.go +++ b/internal/service/payment_method_service.go @@ -31,15 +31,11 @@ func NewPaymentMethodService(paymentMethodProcessor processor.PaymentMethodProce } func (s *PaymentMethodServiceImpl) CreatePaymentMethod(ctx context.Context, contextInfo *appcontext.ContextInfo, req *contract.CreatePaymentMethodRequest) *contract.Response { - // Convert contract to model modelReq := mappers.CreatePaymentMethodContractToModel(req) - - // Set organization ID from context if not provided if modelReq.OrganizationID == uuid.Nil && contextInfo != nil { modelReq.OrganizationID = contextInfo.OrganizationID } - // Process request response, err := s.paymentMethodProcessor.CreatePaymentMethod(ctx, modelReq) if err != nil { return contract.BuildErrorResponse([]*contract.ResponseError{ @@ -47,7 +43,6 @@ func (s *PaymentMethodServiceImpl) CreatePaymentMethod(ctx context.Context, cont }) } - // Convert model to contract contractResponse := mappers.PaymentMethodResponseToContract(response) return contract.BuildSuccessResponse(contractResponse) } diff --git a/internal/validator/payment_method_validator.go b/internal/validator/payment_method_validator.go index 86763bf..8173abf 100644 --- a/internal/validator/payment_method_validator.go +++ b/internal/validator/payment_method_validator.go @@ -28,7 +28,6 @@ func (v *PaymentMethodValidatorImpl) ValidateCreatePaymentMethodRequest(req *con return err, constants.ValidationErrorCode } - // Additional business logic validation if req.Name == "" { return constants.ErrPaymentMethodNameRequired, constants.MissingFieldErrorCode } diff --git a/migrations/000032_fix_user_permissions.down.sql b/migrations/000032_fix_user_permissions.down.sql new file mode 100644 index 0000000..647f32c --- /dev/null +++ b/migrations/000032_fix_user_permissions.down.sql @@ -0,0 +1,2 @@ +-- Revert the permissions fix +ALTER TABLE users ALTER COLUMN permissions SET DEFAULT '{}'::jsonb; \ No newline at end of file diff --git a/migrations/000032_fix_user_permissions.up.sql b/migrations/000032_fix_user_permissions.up.sql new file mode 100644 index 0000000..99dc3f3 --- /dev/null +++ b/migrations/000032_fix_user_permissions.up.sql @@ -0,0 +1,7 @@ +-- Fix any existing users with invalid permissions data +UPDATE users +SET permissions = '{}'::jsonb +WHERE permissions IS NULL OR permissions = 'null'::jsonb OR permissions = '[]'::jsonb; + +-- Ensure all users have valid permissions +ALTER TABLE users ALTER COLUMN permissions SET DEFAULT '{}'::jsonb; \ No newline at end of file diff --git a/server b/server deleted file mode 100755 index a31e958..0000000 Binary files a/server and /dev/null differ