Add Role Assignment Feature for Customers
- Introduced a new role assignment feature for customers, allowing roles to be assigned as either 'admin' or 'analyst'. - Updated the Customer entity to include a Role field, enhancing the data model to support role management. - Created new request and response forms for role assignment, ensuring proper validation and structured responses. - Implemented the AssignRole method in the customer service layer, handling role assignment logic and error management. - Added a new AssignRole handler to process role assignment requests via the API, complete with Swagger documentation for clarity. - Updated routes to include the new role assignment endpoint, improving the administrative capabilities of the system. - Enhanced logging for role assignment operations to ensure traceability and debugging ease.
This commit is contained in:
@@ -80,6 +80,7 @@ func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler
|
|||||||
customersGP.PUT("/:id", customerHandler.UpdateCustomer)
|
customersGP.PUT("/:id", customerHandler.UpdateCustomer)
|
||||||
customersGP.DELETE("/:id", customerHandler.DeleteCustomer)
|
customersGP.DELETE("/:id", customerHandler.DeleteCustomer)
|
||||||
customersGP.PUT("/:id/status", customerHandler.UpdateStatus)
|
customersGP.PUT("/:id/status", customerHandler.UpdateStatus)
|
||||||
|
customersGP.PATCH("/:id/role", customerHandler.AssignRole)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin Tenders Routes
|
// Admin Tenders Routes
|
||||||
|
|||||||
@@ -24,6 +24,14 @@ const (
|
|||||||
CustomerTypeGovernment CustomerType = "government"
|
CustomerTypeGovernment CustomerType = "government"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CustomerRole represents the role of customer
|
||||||
|
type CustomerRole string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CustomerRoleAdmin CustomerRole = "admin"
|
||||||
|
CustomerRoleAnalyst CustomerRole = "analyst"
|
||||||
|
)
|
||||||
|
|
||||||
// Customer represents a customer in the tender management system
|
// Customer represents a customer in the tender management system
|
||||||
// A customer can have an associated company for login purposes
|
// A customer can have an associated company for login purposes
|
||||||
type Customer struct {
|
type Customer struct {
|
||||||
@@ -34,6 +42,7 @@ type Customer struct {
|
|||||||
Password string `bson:"password"`
|
Password string `bson:"password"`
|
||||||
Status CustomerStatus `bson:"status"`
|
Status CustomerStatus `bson:"status"`
|
||||||
Type CustomerType `bson:"type"`
|
Type CustomerType `bson:"type"`
|
||||||
|
Role CustomerRole `bson:"role"`
|
||||||
Phone *string `bson:"phone"`
|
Phone *string `bson:"phone"`
|
||||||
CompanyIDs []string `bson:"company_ids"`
|
CompanyIDs []string `bson:"company_ids"`
|
||||||
LastLoginAt *int64 `bson:"last_login_at"`
|
LastLoginAt *int64 `bson:"last_login_at"`
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import "tm/pkg/response"
|
|||||||
type CreateCustomerForm struct {
|
type CreateCustomerForm struct {
|
||||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)" example:"User"`
|
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)" example:"User"`
|
||||||
Type string `json:"type" valid:"required,in(individual|company|government)" example:"individual"`
|
Type string `json:"type" valid:"required,in(individual|company|government)" example:"individual"`
|
||||||
|
Role string `json:"role" valid:"required,in(admin|analyst)" example:"analyst"`
|
||||||
CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"`
|
CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"`
|
||||||
Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"user"`
|
Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"user"`
|
||||||
Email string `json:"email" valid:"required,email" example:"user@opplens.com"`
|
Email string `json:"email" valid:"required,email" example:"user@opplens.com"`
|
||||||
@@ -17,6 +18,7 @@ type CreateCustomerForm struct {
|
|||||||
type UpdateCustomerForm struct {
|
type UpdateCustomerForm struct {
|
||||||
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)" example:"User"`
|
FullName *string `json:"full_name,omitempty" valid:"optional,length(2|100)" example:"User"`
|
||||||
Type string `json:"type" valid:"required,in(individual|company|government)" example:"individual"`
|
Type string `json:"type" valid:"required,in(individual|company|government)" example:"individual"`
|
||||||
|
Role string `json:"role" valid:"required,in(admin|analyst)" example:"analyst"`
|
||||||
CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"`
|
CompanyIDs []string `json:"company_ids,omitempty" valid:"optional"`
|
||||||
Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"user"`
|
Username string `json:"username" valid:"required,alphanum,length(3|30)" example:"user"`
|
||||||
Email string `json:"email" valid:"required,email" example:"user@opplens.com"`
|
Email string `json:"email" valid:"required,email" example:"user@opplens.com"`
|
||||||
@@ -52,6 +54,7 @@ type CustomerResponse struct {
|
|||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
Role string `json:"role"`
|
||||||
Phone *string `json:"phone"`
|
Phone *string `json:"phone"`
|
||||||
CompanyIDs []string `json:"company_ids"`
|
CompanyIDs []string `json:"company_ids"`
|
||||||
UpdatedAt int64 `json:"updated_at"`
|
UpdatedAt int64 `json:"updated_at"`
|
||||||
@@ -69,6 +72,7 @@ func (c *Customer) ToResponse(companies []*CompanySummary) *CustomerResponse {
|
|||||||
Email: c.Email,
|
Email: c.Email,
|
||||||
Type: string(c.Type),
|
Type: string(c.Type),
|
||||||
Status: string(c.Status),
|
Status: string(c.Status),
|
||||||
|
Role: string(c.Role),
|
||||||
Phone: c.Phone,
|
Phone: c.Phone,
|
||||||
CompanyIDs: c.CompanyIDs,
|
CompanyIDs: c.CompanyIDs,
|
||||||
UpdatedAt: c.UpdatedAt,
|
UpdatedAt: c.UpdatedAt,
|
||||||
@@ -154,3 +158,14 @@ type ResetPasswordResponse struct {
|
|||||||
Message string `json:"message" example:"Password reset successfully"`
|
Message string `json:"message" example:"Password reset successfully"`
|
||||||
Success bool `json:"success" example:"true"`
|
Success bool `json:"success" example:"true"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssignRoleForm represents the form for assigning a role to a customer
|
||||||
|
type AssignRoleForm struct {
|
||||||
|
Role string `json:"role" valid:"required,in(admin|analyst)" example:"analyst"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssignRoleResponse represents the response for role assignment
|
||||||
|
type AssignRoleResponse struct {
|
||||||
|
Message string `json:"message" example:"Role assigned successfully"`
|
||||||
|
Success bool `json:"success" example:"true"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -228,6 +228,43 @@ func (h *Handler) UpdateStatus(c echo.Context) error {
|
|||||||
}, "Customer status updated successfully")
|
}, "Customer status updated successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssignRole assigns a role to a customer (Web Panel)
|
||||||
|
// @Summary Assign role to customer
|
||||||
|
// @Description Assign a role (admin or analyst) to a customer
|
||||||
|
// @Tags Admin-Customers
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param id path string true "Customer ID"
|
||||||
|
// @Param role body AssignRoleForm true "Role assignment information"
|
||||||
|
// @Success 200 {object} response.APIResponse{data=AssignRoleResponse} "Role assigned successfully"
|
||||||
|
// @Failure 400 {object} response.APIResponse "Bad request - Invalid input data"
|
||||||
|
// @Failure 404 {object} response.APIResponse "Not found - Customer not found"
|
||||||
|
// @Failure 422 {object} response.APIResponse "Validation error - Invalid request data"
|
||||||
|
// @Failure 500 {object} response.APIResponse "Internal server error"
|
||||||
|
// @Security BearerAuth
|
||||||
|
// @Router /admin/v1/customers/{id}/role [patch]
|
||||||
|
func (h *Handler) AssignRole(c echo.Context) error {
|
||||||
|
id := c.Param("id")
|
||||||
|
|
||||||
|
form, err := response.Parse[AssignRoleForm](c)
|
||||||
|
if err != nil {
|
||||||
|
return response.ValidationError(c, "Invalid request data", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := h.service.AssignRole(c.Request().Context(), id, form)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == "customer not found" {
|
||||||
|
return response.NotFound(c, "Customer not found")
|
||||||
|
}
|
||||||
|
if err.Error() == "invalid role. Must be 'admin' or 'analyst'" {
|
||||||
|
return response.BadRequest(c, err.Error(), "")
|
||||||
|
}
|
||||||
|
return response.InternalServerError(c, "Failed to assign role to customer")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.Success(c, result, "Role assigned successfully")
|
||||||
|
}
|
||||||
|
|
||||||
// **************************************************
|
// **************************************************
|
||||||
// * PROFILE HANDLERS *
|
// * PROFILE HANDLERS *
|
||||||
// **************************************************
|
// **************************************************
|
||||||
|
|||||||
@@ -42,6 +42,9 @@ type Service interface {
|
|||||||
RequestResetPassword(ctx context.Context, form *RequestResetPasswordForm) (*RequestResetPasswordResponse, error)
|
RequestResetPassword(ctx context.Context, form *RequestResetPasswordForm) (*RequestResetPasswordResponse, error)
|
||||||
VerifyOTP(ctx context.Context, form *VerifyOTPForm) (*VerifyOTPResponse, error)
|
VerifyOTP(ctx context.Context, form *VerifyOTPForm) (*VerifyOTPResponse, error)
|
||||||
ResetPassword(ctx context.Context, form *ResetPasswordForm) (*ResetPasswordResponse, error)
|
ResetPassword(ctx context.Context, form *ResetPasswordForm) (*ResetPasswordResponse, error)
|
||||||
|
|
||||||
|
// Role assignment operations
|
||||||
|
AssignRole(ctx context.Context, customerID string, form *AssignRoleForm) (*AssignRoleResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// customerService implements the Service interface
|
// customerService implements the Service interface
|
||||||
@@ -102,6 +105,7 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm
|
|||||||
// Create customer
|
// Create customer
|
||||||
customer := &Customer{
|
customer := &Customer{
|
||||||
Type: CustomerType(form.Type),
|
Type: CustomerType(form.Type),
|
||||||
|
Role: CustomerRole(form.Role),
|
||||||
Status: CustomerStatusActive,
|
Status: CustomerStatusActive,
|
||||||
CompanyIDs: companyIDs,
|
CompanyIDs: companyIDs,
|
||||||
FullName: form.FullName,
|
FullName: form.FullName,
|
||||||
@@ -249,6 +253,11 @@ func (s *customerService) Update(ctx context.Context, id string, form *UpdateCus
|
|||||||
customer.Phone = form.Phone
|
customer.Phone = form.Phone
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update role if provided
|
||||||
|
if form.Role != "" {
|
||||||
|
customer.Role = CustomerRole(form.Role)
|
||||||
|
}
|
||||||
|
|
||||||
// Update in database
|
// Update in database
|
||||||
err = s.repository.Update(ctx, customer)
|
err = s.repository.Update(ctx, customer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -509,7 +518,7 @@ func (s *customerService) Login(ctx context.Context, form *LoginForm) (*AuthResp
|
|||||||
tokenPair, err := s.authService.GenerateTokenPair(
|
tokenPair, err := s.authService.GenerateTokenPair(
|
||||||
customer.ID.Hex(),
|
customer.ID.Hex(),
|
||||||
customer.Email,
|
customer.Email,
|
||||||
"customer", // Role for customers
|
string(customer.Role), // Use customer's actual role
|
||||||
companyID,
|
companyID,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1055,3 +1064,55 @@ func (s *customerService) loadCompaniesForCustomer(ctx context.Context, customer
|
|||||||
|
|
||||||
return companySummaries, nil
|
return companySummaries, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssignRole assigns a role to a customer
|
||||||
|
func (s *customerService) AssignRole(ctx context.Context, customerID string, form *AssignRoleForm) (*AssignRoleResponse, error) {
|
||||||
|
s.logger.Info("Assigning role to customer", map[string]interface{}{
|
||||||
|
"customer_id": customerID,
|
||||||
|
"role": form.Role,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get customer to check if exists
|
||||||
|
customer, err := s.repository.GetByID(ctx, customerID)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to get customer for role assignment", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"customer_id": customerID,
|
||||||
|
})
|
||||||
|
return nil, errors.New("customer not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate role
|
||||||
|
role := CustomerRole(form.Role)
|
||||||
|
if role != CustomerRoleAdmin && role != CustomerRoleAnalyst {
|
||||||
|
s.logger.Error("Invalid role provided", map[string]interface{}{
|
||||||
|
"customer_id": customerID,
|
||||||
|
"role": form.Role,
|
||||||
|
})
|
||||||
|
return nil, errors.New("invalid role. Must be 'admin' or 'analyst'")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update customer role
|
||||||
|
customer.Role = role
|
||||||
|
customer.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
err = s.repository.Update(ctx, customer)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("Failed to update customer role", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
"customer_id": customerID,
|
||||||
|
"role": form.Role,
|
||||||
|
})
|
||||||
|
return nil, errors.New("failed to assign role to customer")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Info("Role assigned to customer successfully", map[string]interface{}{
|
||||||
|
"customer_id": customerID,
|
||||||
|
"role": form.Role,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &AssignRoleResponse{
|
||||||
|
Message: "Role assigned successfully",
|
||||||
|
Success: true,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user