add user login

This commit is contained in:
Aditya Siregar 2025-08-03 23:11:31 +07:00
parent ebe9999793
commit f65c06a053
14 changed files with 94 additions and 31 deletions

View File

@ -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),

View File

@ -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"`

View File

@ -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
}

View File

@ -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]

View File

@ -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")

View File

@ -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,

View File

@ -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"`

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -0,0 +1,2 @@
-- Revert the permissions fix
ALTER TABLE users ALTER COLUMN permissions SET DEFAULT '{}'::jsonb;

View 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;

BIN
server

Binary file not shown.