add user login
This commit is contained in:
parent
ebe9999793
commit
f65c06a053
@ -20,7 +20,7 @@ type ContextInfo struct {
|
|||||||
CorrelationID string
|
CorrelationID string
|
||||||
UserID uuid.UUID
|
UserID uuid.UUID
|
||||||
OrganizationID uuid.UUID
|
OrganizationID uuid.UUID
|
||||||
OutletID string
|
OutletID uuid.UUID
|
||||||
AppVersion string
|
AppVersion string
|
||||||
AppID string
|
AppID string
|
||||||
AppType string
|
AppType string
|
||||||
@ -61,7 +61,7 @@ func FromGinContext(ctx context.Context) *ContextInfo {
|
|||||||
return &ContextInfo{
|
return &ContextInfo{
|
||||||
CorrelationID: value(ctx, CorrelationIDKey),
|
CorrelationID: value(ctx, CorrelationIDKey),
|
||||||
UserID: uuidValue(ctx, UserIDKey),
|
UserID: uuidValue(ctx, UserIDKey),
|
||||||
OutletID: value(ctx, OutletIDKey),
|
OutletID: uuidValue(ctx, OutletIDKey),
|
||||||
OrganizationID: uuidValue(ctx, OrganizationIDKey),
|
OrganizationID: uuidValue(ctx, OrganizationIDKey),
|
||||||
AppVersion: value(ctx, AppVersionKey),
|
AppVersion: value(ctx, AppVersionKey),
|
||||||
AppID: value(ctx, AppIDKey),
|
AppID: value(ctx, AppIDKey),
|
||||||
|
|||||||
@ -7,7 +7,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type CreatePaymentMethodRequest struct {
|
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"`
|
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||||
Type string `json:"type" validate:"required,oneof=cash card digital_wallet qr edc"`
|
Type string `json:"type" validate:"required,oneof=cash card digital_wallet qr edc"`
|
||||||
Processor *string `json:"processor,omitempty" validate:"omitempty,max=100"`
|
Processor *string `json:"processor,omitempty" validate:"omitempty,max=100"`
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package entities
|
|||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
@ -13,6 +12,7 @@ import (
|
|||||||
type UserRole string
|
type UserRole string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
RoleSuperAdmin UserRole = "superadmin"
|
||||||
RoleAdmin UserRole = "admin"
|
RoleAdmin UserRole = "admin"
|
||||||
RoleManager UserRole = "manager"
|
RoleManager UserRole = "manager"
|
||||||
RoleCashier UserRole = "cashier"
|
RoleCashier UserRole = "cashier"
|
||||||
@ -22,6 +22,9 @@ const (
|
|||||||
type Permissions map[string]interface{}
|
type Permissions map[string]interface{}
|
||||||
|
|
||||||
func (p Permissions) Value() (driver.Value, error) {
|
func (p Permissions) Value() (driver.Value, error) {
|
||||||
|
if p == nil {
|
||||||
|
return "{}", nil
|
||||||
|
}
|
||||||
return json.Marshal(p)
|
return json.Marshal(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,12 +34,33 @@ func (p *Permissions) Scan(value interface{}) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
bytes, ok := value.([]byte)
|
switch v := value.(type) {
|
||||||
if !ok {
|
case []byte:
|
||||||
return errors.New("type assertion to []byte failed")
|
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 {
|
type User struct {
|
||||||
@ -60,6 +84,16 @@ func (u *User) BeforeCreate(tx *gorm.DB) error {
|
|||||||
if u.ID == uuid.Nil {
|
if u.ID == uuid.Nil {
|
||||||
u.ID = uuid.New()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -49,6 +49,9 @@ func (h *PaymentMethodHandler) CreatePaymentMethod(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.OrganizationID = contextInfo.OrganizationID
|
||||||
|
req.OutletID = contextInfo.OutletID
|
||||||
|
|
||||||
paymentMethodResponse := h.paymentMethodService.CreatePaymentMethod(ctx, contextInfo, &req)
|
paymentMethodResponse := h.paymentMethodService.CreatePaymentMethod(ctx, contextInfo, &req)
|
||||||
if paymentMethodResponse.HasErrors() {
|
if paymentMethodResponse.HasErrors() {
|
||||||
errorResp := paymentMethodResponse.GetErrors()[0]
|
errorResp := paymentMethodResponse.GetErrors()[0]
|
||||||
|
|||||||
@ -75,7 +75,6 @@ func (h *UnitHandler) GetAll(c *gin.Context) {
|
|||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
contextInfo := appcontext.FromGinContext(ctx)
|
contextInfo := appcontext.FromGinContext(ctx)
|
||||||
|
|
||||||
// Get query parameters
|
|
||||||
pageStr := c.DefaultQuery("page", "1")
|
pageStr := c.DefaultQuery("page", "1")
|
||||||
limitStr := c.DefaultQuery("limit", "10")
|
limitStr := c.DefaultQuery("limit", "10")
|
||||||
search := c.Query("search")
|
search := c.Query("search")
|
||||||
|
|||||||
@ -100,6 +100,7 @@ func PaymentMethodModelToEntity(model *models.PaymentMethod) *entities.PaymentMe
|
|||||||
func CreatePaymentMethodContractToModel(req *contract.CreatePaymentMethodRequest) *models.CreatePaymentMethodRequest {
|
func CreatePaymentMethodContractToModel(req *contract.CreatePaymentMethodRequest) *models.CreatePaymentMethodRequest {
|
||||||
return &models.CreatePaymentMethodRequest{
|
return &models.CreatePaymentMethodRequest{
|
||||||
OrganizationID: req.OrganizationID,
|
OrganizationID: req.OrganizationID,
|
||||||
|
OutletID: req.OutletID,
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
Type: constants.PaymentMethodType(req.Type),
|
Type: constants.PaymentMethodType(req.Type),
|
||||||
Processor: req.Processor,
|
Processor: req.Processor,
|
||||||
|
|||||||
@ -21,6 +21,7 @@ type PaymentMethod struct {
|
|||||||
|
|
||||||
type CreatePaymentMethodRequest struct {
|
type CreatePaymentMethodRequest struct {
|
||||||
OrganizationID uuid.UUID `validate:"required"`
|
OrganizationID uuid.UUID `validate:"required"`
|
||||||
|
OutletID uuid.UUID `validate:"required"`
|
||||||
Name string `validate:"required,min=1,max=100"`
|
Name string `validate:"required,min=1,max=100"`
|
||||||
Type constants.PaymentMethodType `validate:"required"`
|
Type constants.PaymentMethodType `validate:"required"`
|
||||||
Processor *string `validate:"omitempty,max=100"`
|
Processor *string `validate:"omitempty,max=100"`
|
||||||
|
|||||||
@ -31,7 +31,6 @@ func NewPaymentMethodProcessorImpl(paymentMethodRepo repository.PaymentMethodRep
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *PaymentMethodProcessorImpl) CreatePaymentMethod(ctx context.Context, req *models.CreatePaymentMethodRequest) (*models.PaymentMethodResponse, error) {
|
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)
|
exists, err := p.paymentMethodRepo.ExistsByName(ctx, req.OrganizationID, req.Name, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to check payment method name uniqueness: %w", err)
|
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)
|
return nil, fmt.Errorf("payment method with name '%s' already exists for this organization", req.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map request to entity
|
|
||||||
paymentMethodEntity := mappers.CreatePaymentMethodRequestToEntity(req)
|
paymentMethodEntity := mappers.CreatePaymentMethodRequestToEntity(req)
|
||||||
|
|
||||||
// Create payment method
|
|
||||||
if err := p.paymentMethodRepo.Create(ctx, paymentMethodEntity); err != nil {
|
if err := p.paymentMethodRepo.Create(ctx, paymentMethodEntity); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create payment method: %w", err)
|
return nil, fmt.Errorf("failed to create payment method: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get created payment method
|
|
||||||
createdPaymentMethod, err := p.paymentMethodRepo.GetByID(ctx, paymentMethodEntity.ID)
|
createdPaymentMethod, err := p.paymentMethodRepo.GetByID(ctx, paymentMethodEntity.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve created payment method: %w", err)
|
return nil, fmt.Errorf("failed to retrieve created payment method: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map entity to response
|
|
||||||
response := mappers.PaymentMethodEntityToResponse(createdPaymentMethod)
|
response := mappers.PaymentMethodEntityToResponse(createdPaymentMethod)
|
||||||
return response, nil
|
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) {
|
func (p *PaymentMethodProcessorImpl) ListPaymentMethods(ctx context.Context, req *models.ListPaymentMethodsRequest) (*models.ListPaymentMethodsResponse, error) {
|
||||||
// Build filters
|
|
||||||
filters := make(map[string]interface{})
|
filters := make(map[string]interface{})
|
||||||
if req.OrganizationID != nil {
|
if req.OrganizationID != nil {
|
||||||
filters["organization_id"] = *req.OrganizationID
|
filters["organization_id"] = *req.OrganizationID
|
||||||
@ -85,10 +78,8 @@ func (p *PaymentMethodProcessorImpl) ListPaymentMethods(ctx context.Context, req
|
|||||||
filters["search"] = req.Search
|
filters["search"] = req.Search
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate offset
|
|
||||||
offset := (req.Page - 1) * req.Limit
|
offset := (req.Page - 1) * req.Limit
|
||||||
|
|
||||||
// Get payment methods
|
|
||||||
paymentMethods, total, err := p.paymentMethodRepo.List(ctx, filters, req.Limit, offset)
|
paymentMethods, total, err := p.paymentMethodRepo.List(ctx, filters, req.Limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to list payment methods: %w", err)
|
return nil, fmt.Errorf("failed to list payment methods: %w", err)
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"apskel-pos-be/internal/logger"
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"apskel-pos-be/internal/entities"
|
"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) {
|
func (r *UserRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.User, error) {
|
||||||
var user entities.User
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure permissions is properly initialized
|
||||||
|
if user.Permissions == nil {
|
||||||
|
user.Permissions = make(entities.Permissions)
|
||||||
|
}
|
||||||
|
|
||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *UserRepositoryImpl) GetByEmail(ctx context.Context, email string) (*entities.User, error) {
|
func (r *UserRepositoryImpl) GetByEmail(ctx context.Context, email string) (*entities.User, error) {
|
||||||
var user entities.User
|
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 {
|
if err != nil {
|
||||||
|
logger.FromContext(ctx).WithError(err).Error("UserRepositoryImpl::GetByEmail -> failed to get user by email")
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if user.Permissions == nil {
|
||||||
|
user.Permissions = make(entities.Permissions)
|
||||||
|
}
|
||||||
|
|
||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,15 +31,11 @@ func NewPaymentMethodService(paymentMethodProcessor processor.PaymentMethodProce
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *PaymentMethodServiceImpl) CreatePaymentMethod(ctx context.Context, contextInfo *appcontext.ContextInfo, req *contract.CreatePaymentMethodRequest) *contract.Response {
|
func (s *PaymentMethodServiceImpl) CreatePaymentMethod(ctx context.Context, contextInfo *appcontext.ContextInfo, req *contract.CreatePaymentMethodRequest) *contract.Response {
|
||||||
// Convert contract to model
|
|
||||||
modelReq := mappers.CreatePaymentMethodContractToModel(req)
|
modelReq := mappers.CreatePaymentMethodContractToModel(req)
|
||||||
|
|
||||||
// Set organization ID from context if not provided
|
|
||||||
if modelReq.OrganizationID == uuid.Nil && contextInfo != nil {
|
if modelReq.OrganizationID == uuid.Nil && contextInfo != nil {
|
||||||
modelReq.OrganizationID = contextInfo.OrganizationID
|
modelReq.OrganizationID = contextInfo.OrganizationID
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process request
|
|
||||||
response, err := s.paymentMethodProcessor.CreatePaymentMethod(ctx, modelReq)
|
response, err := s.paymentMethodProcessor.CreatePaymentMethod(ctx, modelReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return contract.BuildErrorResponse([]*contract.ResponseError{
|
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)
|
contractResponse := mappers.PaymentMethodResponseToContract(response)
|
||||||
return contract.BuildSuccessResponse(contractResponse)
|
return contract.BuildSuccessResponse(contractResponse)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -28,7 +28,6 @@ func (v *PaymentMethodValidatorImpl) ValidateCreatePaymentMethodRequest(req *con
|
|||||||
return err, constants.ValidationErrorCode
|
return err, constants.ValidationErrorCode
|
||||||
}
|
}
|
||||||
|
|
||||||
// Additional business logic validation
|
|
||||||
if req.Name == "" {
|
if req.Name == "" {
|
||||||
return constants.ErrPaymentMethodNameRequired, constants.MissingFieldErrorCode
|
return constants.ErrPaymentMethodNameRequired, constants.MissingFieldErrorCode
|
||||||
}
|
}
|
||||||
|
|||||||
2
migrations/000032_fix_user_permissions.down.sql
Normal file
2
migrations/000032_fix_user_permissions.down.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
-- Revert the permissions fix
|
||||||
|
ALTER TABLE users ALTER COLUMN permissions SET DEFAULT '{}'::jsonb;
|
||||||
7
migrations/000032_fix_user_permissions.up.sql
Normal file
7
migrations/000032_fix_user_permissions.up.sql
Normal file
@ -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;
|
||||||
Loading…
x
Reference in New Issue
Block a user