Initialize base

This commit is contained in:
hdbar
2025-07-27 16:20:21 +03:30
commit ad9db7bcce
31 changed files with 8268 additions and 0 deletions
+179
View File
@@ -0,0 +1,179 @@
package service
import (
"context"
"errors"
"github.com/google/uuid"
"tm/internal/domain"
"tm/pkg/logger"
)
// CustomerServiceImpl implements the CustomerService interface
type CustomerServiceImpl struct {
customerRepo domain.CustomerRepository
logger logger.Logger
}
// NewCustomerService creates a new customer service
func NewCustomerService(
customerRepo domain.CustomerRepository,
logger logger.Logger,
) domain.CustomerService {
return &CustomerServiceImpl{
customerRepo: customerRepo,
logger: logger,
}
}
// GetCustomers retrieves customers with pagination (for panel users)
func (s *CustomerServiceImpl) GetCustomers(ctx context.Context, limit, offset int) ([]*domain.Customer, error) {
s.logger.Info("Retrieving customers", map[string]interface{}{
"limit": limit,
"offset": offset,
})
customers, err := s.customerRepo.List(ctx, limit, offset)
if err != nil {
s.logger.Error("Failed to retrieve customers", map[string]interface{}{
"error": err.Error(),
"limit": limit,
"offset": offset,
})
return nil, errors.New("failed to retrieve customers")
}
s.logger.Info("Customers retrieved successfully", map[string]interface{}{
"count": len(customers),
"limit": limit,
"offset": offset,
})
return customers, nil
}
// GetCustomerByID retrieves a customer by ID (for panel users)
func (s *CustomerServiceImpl) GetCustomerByID(ctx context.Context, id uuid.UUID) (*domain.Customer, error) {
s.logger.Info("Retrieving customer by ID", map[string]interface{}{
"customer_id": id.String(),
})
customer, err := s.customerRepo.GetByID(ctx, id)
if err != nil {
s.logger.Error("Failed to retrieve customer", map[string]interface{}{
"error": err.Error(),
"customer_id": id.String(),
})
return nil, errors.New("customer not found")
}
s.logger.Info("Customer retrieved successfully", map[string]interface{}{
"customer_id": customer.ID.String(),
"email": customer.Email,
})
return customer, nil
}
// GetCustomersByCompany retrieves customers by company ID with pagination (for panel users)
func (s *CustomerServiceImpl) GetCustomersByCompany(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*domain.Customer, error) {
s.logger.Info("Retrieving customers by company", map[string]interface{}{
"company_id": companyID.String(),
"limit": limit,
"offset": offset,
})
customers, err := s.customerRepo.GetByCompanyID(ctx, companyID, limit, offset)
if err != nil {
s.logger.Error("Failed to retrieve customers by company", map[string]interface{}{
"error": err.Error(),
"company_id": companyID.String(),
"limit": limit,
"offset": offset,
})
return nil, errors.New("failed to retrieve customers")
}
s.logger.Info("Customers by company retrieved successfully", map[string]interface{}{
"count": len(customers),
"company_id": companyID.String(),
"limit": limit,
"offset": offset,
})
return customers, nil
}
// UpdateCustomerStatus updates customer active status (for panel users)
func (s *CustomerServiceImpl) UpdateCustomerStatus(ctx context.Context, customerID uuid.UUID, isActive bool, updatedBy uuid.UUID) error {
s.logger.Info("Updating customer status", map[string]interface{}{
"customer_id": customerID.String(),
"is_active": isActive,
"updated_by": updatedBy.String(),
})
// Get customer
customer, err := s.customerRepo.GetByID(ctx, customerID)
if err != nil {
s.logger.Error("Customer not found for status update", map[string]interface{}{
"error": err.Error(),
"customer_id": customerID.String(),
})
return errors.New("customer not found")
}
// Update status
customer.IsActive = isActive
// Save changes
if err := s.customerRepo.Update(ctx, customer); err != nil {
s.logger.Error("Failed to update customer status", map[string]interface{}{
"error": err.Error(),
"customer_id": customerID.String(),
"is_active": isActive,
"updated_by": updatedBy.String(),
})
return errors.New("failed to update customer status")
}
s.logger.Info("Customer status updated successfully", map[string]interface{}{
"customer_id": customerID.String(),
"is_active": isActive,
"updated_by": updatedBy.String(),
})
return nil
}
// UpdateCustomerPreferences updates customer preferences (for panel users)
func (s *CustomerServiceImpl) UpdateCustomerPreferences(ctx context.Context, customerID uuid.UUID, preferences *domain.CustomerPreferences) error {
s.logger.Info("Updating customer preferences", map[string]interface{}{
"customer_id": customerID.String(),
})
// Verify customer exists
_, err := s.customerRepo.GetByID(ctx, customerID)
if err != nil {
s.logger.Error("Customer not found for preferences update", map[string]interface{}{
"error": err.Error(),
"customer_id": customerID.String(),
})
return errors.New("customer not found")
}
// Update preferences
if err := s.customerRepo.UpdatePreferences(ctx, customerID, preferences); err != nil {
s.logger.Error("Failed to update customer preferences", map[string]interface{}{
"error": err.Error(),
"customer_id": customerID.String(),
})
return errors.New("failed to update customer preferences")
}
s.logger.Info("Customer preferences updated successfully", map[string]interface{}{
"customer_id": customerID.String(),
})
return nil
}
+497
View File
@@ -0,0 +1,497 @@
package service
import (
"context"
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"tm/internal/domain"
"tm/internal/infrastructure"
"tm/pkg/logger"
)
// CustomerAuthServiceImpl implements the CustomerAuthService interface
type CustomerAuthServiceImpl struct {
customerRepo domain.CustomerRepository
companyRepo domain.CompanyRepository
config *infrastructure.AuthConfig
logger logger.Logger
}
// NewCustomerAuthService creates a new customer authentication service
func NewCustomerAuthService(
customerRepo domain.CustomerRepository,
companyRepo domain.CompanyRepository,
config *infrastructure.AuthConfig,
logger logger.Logger,
) domain.CustomerAuthService {
return &CustomerAuthServiceImpl{
customerRepo: customerRepo,
companyRepo: companyRepo,
config: config,
logger: logger,
}
}
// Register creates a new customer account
func (s *CustomerAuthServiceImpl) Register(ctx context.Context, req domain.CustomerRegisterRequest) (*domain.CustomerAuthResponse, error) {
s.logger.Info("Registering new customer", map[string]interface{}{
"email": req.Email,
})
// Check if customer already exists
existingCustomer, err := s.customerRepo.GetByEmail(ctx, req.Email)
if err == nil && existingCustomer != nil {
return nil, errors.New("customer already exists")
}
// Hash password
hashedPassword, err := s.hashPassword(req.Password)
if err != nil {
s.logger.Error("Failed to hash password", map[string]interface{}{
"error": err.Error(),
})
return nil, errors.New("failed to process password")
}
// Handle company assignment
var companyID uuid.UUID
if req.CompanyID != "" {
// Parse existing company ID
companyID, err = uuid.Parse(req.CompanyID)
if err != nil {
return nil, errors.New("invalid company ID")
}
// Verify company exists
_, err = s.companyRepo.GetByID(ctx, companyID)
if err != nil {
return nil, errors.New("company not found")
}
} else {
// Create new company for customer
company := &domain.Company{
ID: uuid.New(),
Name: req.FirstName + " " + req.LastName + "'s Company",
Description: "Personal company profile",
Industry: "Not specified",
Country: "Not specified",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := s.companyRepo.Create(ctx, company); err != nil {
s.logger.Error("Failed to create company for customer", map[string]interface{}{
"error": err.Error(),
"email": req.Email,
})
return nil, errors.New("failed to create company")
}
companyID = company.ID
}
// Create customer
customer := &domain.Customer{
ID: uuid.New(),
Email: req.Email,
Password: hashedPassword,
FirstName: req.FirstName,
LastName: req.LastName,
Phone: req.Phone,
Role: domain.CustomerRoleUser,
CompanyID: companyID,
IsActive: true,
IsVerified: false, // Require email verification
DeviceTokens: make([]string, 0),
Preferences: domain.CustomerPreferences{
Language: "en",
NotificationSettings: domain.NotificationSettings{
NewTenders: true,
DeadlineReminder: true,
StatusUpdates: true,
SystemAlerts: true,
},
TenderAlerts: true,
EmailNotifications: true,
PushNotifications: true,
PreferredCategories: make([]string, 0),
},
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if err := s.customerRepo.Create(ctx, customer); err != nil {
s.logger.Error("Failed to create customer", map[string]interface{}{
"error": err.Error(),
"email": req.Email,
})
return nil, errors.New("failed to create customer")
}
// Generate tokens
accessToken, err := s.generateAccessToken(customer)
if err != nil {
s.logger.Error("Failed to generate access token", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID.String(),
})
return nil, errors.New("failed to generate access token")
}
refreshToken, err := s.generateRefreshToken(customer)
if err != nil {
s.logger.Error("Failed to generate refresh token", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID.String(),
})
return nil, errors.New("failed to generate refresh token")
}
s.logger.Info("Customer registered successfully", map[string]interface{}{
"customer_id": customer.ID.String(),
"email": customer.Email,
"company_id": customer.CompanyID.String(),
})
// TODO: Send verification email
s.sendVerificationEmail(ctx, customer)
return &domain.CustomerAuthResponse{
Customer: customer,
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()),
}, nil
}
// Login authenticates a customer and returns tokens
func (s *CustomerAuthServiceImpl) Login(ctx context.Context, req domain.LoginRequest) (*domain.CustomerAuthResponse, error) {
s.logger.Info("Customer login attempt", map[string]interface{}{
"email": req.Email,
})
// Get customer by email
customer, err := s.customerRepo.GetByEmail(ctx, req.Email)
if err != nil {
s.logger.Warn("Customer login failed - customer not found", map[string]interface{}{
"email": req.Email,
})
return nil, errors.New("invalid credentials")
}
// Check if customer is active
if !customer.IsActive {
s.logger.Warn("Customer login failed - account inactive", map[string]interface{}{
"email": req.Email,
"customer_id": customer.ID.String(),
})
return nil, errors.New("account is inactive")
}
// Verify password
if !s.verifyPassword(req.Password, customer.Password) {
s.logger.Warn("Customer login failed - invalid password", map[string]interface{}{
"email": req.Email,
"customer_id": customer.ID.String(),
})
return nil, errors.New("invalid credentials")
}
// Update last login time
customer.LastLoginAt = &time.Time{}
*customer.LastLoginAt = time.Now()
customer.UpdatedAt = time.Now()
if err := s.customerRepo.Update(ctx, customer); err != nil {
s.logger.Warn("Failed to update customer last login", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID.String(),
})
// Don't fail login for this
}
// Generate tokens
accessToken, err := s.generateAccessToken(customer)
if err != nil {
s.logger.Error("Failed to generate access token", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID.String(),
})
return nil, errors.New("failed to generate access token")
}
refreshToken, err := s.generateRefreshToken(customer)
if err != nil {
s.logger.Error("Failed to generate refresh token", map[string]interface{}{
"error": err.Error(),
"customer_id": customer.ID.String(),
})
return nil, errors.New("failed to generate refresh token")
}
s.logger.Info("Customer logged in successfully", map[string]interface{}{
"customer_id": customer.ID.String(),
"email": customer.Email,
"is_verified": customer.IsVerified,
})
return &domain.CustomerAuthResponse{
Customer: customer,
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()),
}, nil
}
// RefreshToken generates new tokens using refresh token
func (s *CustomerAuthServiceImpl) RefreshToken(ctx context.Context, refreshToken string) (*domain.CustomerAuthResponse, error) {
// Parse and validate refresh token
token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("invalid signing method")
}
return []byte(s.config.JWT.Secret), nil
})
if err != nil || !token.Valid {
return nil, errors.New("invalid refresh token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("invalid token claims")
}
// Verify token type
tokenType, ok := claims["type"].(string)
if !ok || tokenType != "refresh" {
return nil, errors.New("invalid token type")
}
// Verify user type
userType, ok := claims["user_type"].(string)
if !ok || userType != "customer" {
return nil, errors.New("invalid user type")
}
customerIDStr, ok := claims["customer_id"].(string)
if !ok {
return nil, errors.New("invalid customer ID in token")
}
customerID, err := uuid.Parse(customerIDStr)
if err != nil {
return nil, errors.New("invalid customer ID format")
}
// Get customer from database
customer, err := s.customerRepo.GetByID(ctx, customerID)
if err != nil {
return nil, errors.New("customer not found")
}
if !customer.IsActive {
return nil, errors.New("account is inactive")
}
// Generate new tokens
accessToken, err := s.generateAccessToken(customer)
if err != nil {
return nil, errors.New("failed to generate access token")
}
newRefreshToken, err := s.generateRefreshToken(customer)
if err != nil {
return nil, errors.New("failed to generate refresh token")
}
return &domain.CustomerAuthResponse{
Customer: customer,
AccessToken: accessToken,
RefreshToken: newRefreshToken,
ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()),
}, nil
}
// ValidateToken validates a JWT token and returns the customer
func (s *CustomerAuthServiceImpl) ValidateToken(ctx context.Context, tokenString string) (*domain.Customer, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("invalid signing method")
}
return []byte(s.config.JWT.Secret), nil
})
if err != nil || !token.Valid {
return nil, errors.New("invalid token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("invalid token claims")
}
// Verify token type
tokenType, ok := claims["type"].(string)
if !ok || tokenType != "access" {
return nil, errors.New("invalid token type")
}
// Verify user type
userType, ok := claims["user_type"].(string)
if !ok || userType != "customer" {
return nil, errors.New("invalid user type")
}
customerIDStr, ok := claims["customer_id"].(string)
if !ok {
return nil, errors.New("invalid customer ID in token")
}
customerID, err := uuid.Parse(customerIDStr)
if err != nil {
return nil, errors.New("invalid customer ID format")
}
customer, err := s.customerRepo.GetByID(ctx, customerID)
if err != nil {
return nil, errors.New("customer not found")
}
if !customer.IsActive {
return nil, errors.New("account is inactive")
}
return customer, nil
}
// ChangePassword changes customer password
func (s *CustomerAuthServiceImpl) ChangePassword(ctx context.Context, customerID uuid.UUID, oldPassword, newPassword string) error {
customer, err := s.customerRepo.GetByID(ctx, customerID)
if err != nil {
return errors.New("customer not found")
}
// Verify old password
if !s.verifyPassword(oldPassword, customer.Password) {
return errors.New("invalid old password")
}
// Hash new password
hashedPassword, err := s.hashPassword(newPassword)
if err != nil {
return errors.New("failed to process new password")
}
// Update password
customer.Password = hashedPassword
customer.UpdatedAt = time.Now()
if err := s.customerRepo.Update(ctx, customer); err != nil {
s.logger.Error("Failed to update customer password", map[string]interface{}{
"error": err.Error(),
"customer_id": customerID.String(),
})
return errors.New("failed to update password")
}
s.logger.Info("Customer password changed successfully", map[string]interface{}{
"customer_id": customerID.String(),
})
return nil
}
// ResetPassword initiates password reset process
func (s *CustomerAuthServiceImpl) ResetPassword(ctx context.Context, email string) error {
// TODO: Implement password reset logic
// This would typically involve:
// 1. Generate reset token
// 2. Store token with expiration
// 3. Send reset email
return errors.New("password reset not implemented")
}
// VerifyEmail verifies customer's email address
func (s *CustomerAuthServiceImpl) VerifyEmail(ctx context.Context, customerID uuid.UUID, token string) error {
// TODO: Implement email verification logic
// This would typically involve:
// 1. Validate verification token
// 2. Update customer verification status
// 3. Send welcome email
return errors.New("email verification not implemented")
}
// ResendVerification resends verification email
func (s *CustomerAuthServiceImpl) ResendVerification(ctx context.Context, email string) error {
customer, err := s.customerRepo.GetByEmail(ctx, email)
if err != nil {
return errors.New("customer not found")
}
if customer.IsVerified {
return errors.New("email already verified")
}
// TODO: Send verification email
s.sendVerificationEmail(ctx, customer)
return nil
}
// Private helper methods
func (s *CustomerAuthServiceImpl) hashPassword(password string) (string, error) {
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashedBytes), nil
}
func (s *CustomerAuthServiceImpl) verifyPassword(password, hashedPassword string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return err == nil
}
func (s *CustomerAuthServiceImpl) generateAccessToken(customer *domain.Customer) (string, error) {
claims := jwt.MapClaims{
"customer_id": customer.ID.String(),
"email": customer.Email,
"role": customer.Role,
"company_id": customer.CompanyID.String(),
"user_type": "customer",
"exp": time.Now().Add(s.config.JWT.AccessTokenDuration).Unix(),
"iat": time.Now().Unix(),
"type": "access",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(s.config.JWT.Secret))
}
func (s *CustomerAuthServiceImpl) generateRefreshToken(customer *domain.Customer) (string, error) {
claims := jwt.MapClaims{
"customer_id": customer.ID.String(),
"user_type": "customer",
"exp": time.Now().Add(s.config.JWT.RefreshTokenDuration).Unix(),
"iat": time.Now().Unix(),
"type": "refresh",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(s.config.JWT.Secret))
}
func (s *CustomerAuthServiceImpl) sendVerificationEmail(ctx context.Context, customer *domain.Customer) {
// TODO: Implement email sending logic
s.logger.Info("Verification email would be sent", map[string]interface{}{
"customer_id": customer.ID.String(),
"email": customer.Email,
})
}
+517
View File
@@ -0,0 +1,517 @@
package service
import (
"context"
"errors"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"golang.org/x/crypto/bcrypt"
"tm/internal/domain"
"tm/internal/infrastructure"
"tm/pkg/logger"
)
// UserAuthServiceImpl implements the UserAuthService interface
type UserAuthServiceImpl struct {
userRepo domain.UserRepository
config *infrastructure.AuthConfig
logger logger.Logger
}
// NewUserAuthService creates a new panel user authentication service
func NewUserAuthService(
userRepo domain.UserRepository,
config *infrastructure.AuthConfig,
logger logger.Logger,
) domain.UserAuthService {
return &UserAuthServiceImpl{
userRepo: userRepo,
config: config,
logger: logger,
}
}
// Login authenticates a panel user and returns tokens
func (s *UserAuthServiceImpl) Login(ctx context.Context, req domain.LoginRequest) (*domain.UserAuthResponse, error) {
s.logger.Info("Panel user login attempt", map[string]interface{}{
"email": req.Email,
})
// Get user by email
user, err := s.userRepo.GetByEmail(ctx, req.Email)
if err != nil {
s.logger.Warn("Panel user login failed - user not found", map[string]interface{}{
"email": req.Email,
})
return nil, errors.New("invalid credentials")
}
// Check if user is active
if !user.IsActive {
s.logger.Warn("Panel user login failed - account inactive", map[string]interface{}{
"email": req.Email,
"user_id": user.ID.String(),
})
return nil, errors.New("account is inactive")
}
// Verify password
if !s.verifyPassword(req.Password, user.Password) {
s.logger.Warn("Panel user login failed - invalid password", map[string]interface{}{
"email": req.Email,
"user_id": user.ID.String(),
})
return nil, errors.New("invalid credentials")
}
// Update last login time
user.LastLoginAt = &time.Time{}
*user.LastLoginAt = time.Now()
if err := s.userRepo.Update(ctx, user); err != nil {
s.logger.Warn("Failed to update user last login", map[string]interface{}{
"error": err.Error(),
"user_id": user.ID.String(),
})
// Don't fail login for this
}
// Generate tokens
accessToken, err := s.generateAccessToken(user)
if err != nil {
s.logger.Error("Failed to generate access token", map[string]interface{}{
"error": err.Error(),
"user_id": user.ID.String(),
})
return nil, errors.New("failed to generate access token")
}
refreshToken, err := s.generateRefreshToken(user)
if err != nil {
s.logger.Error("Failed to generate refresh token", map[string]interface{}{
"error": err.Error(),
"user_id": user.ID.String(),
})
return nil, errors.New("failed to generate refresh token")
}
s.logger.Info("Panel user logged in successfully", map[string]interface{}{
"user_id": user.ID.String(),
"email": user.Email,
"role": user.Role,
"department": user.Department,
})
return &domain.UserAuthResponse{
User: user,
AccessToken: accessToken,
RefreshToken: refreshToken,
ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()),
}, nil
}
// RefreshToken generates new tokens using refresh token
func (s *UserAuthServiceImpl) RefreshToken(ctx context.Context, refreshToken string) (*domain.UserAuthResponse, error) {
// Parse and validate refresh token
token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("invalid signing method")
}
return []byte(s.config.JWT.Secret), nil
})
if err != nil || !token.Valid {
return nil, errors.New("invalid refresh token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("invalid token claims")
}
// Verify token type
tokenType, ok := claims["type"].(string)
if !ok || tokenType != "refresh" {
return nil, errors.New("invalid token type")
}
// Verify user type
userType, ok := claims["user_type"].(string)
if !ok || userType != "user" {
return nil, errors.New("invalid user type")
}
userIDStr, ok := claims["user_id"].(string)
if !ok {
return nil, errors.New("invalid user ID in token")
}
userID, err := uuid.Parse(userIDStr)
if err != nil {
return nil, errors.New("invalid user ID format")
}
// Get user from database
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return nil, errors.New("user not found")
}
if !user.IsActive {
return nil, errors.New("account is inactive")
}
// Generate new tokens
accessToken, err := s.generateAccessToken(user)
if err != nil {
return nil, errors.New("failed to generate access token")
}
newRefreshToken, err := s.generateRefreshToken(user)
if err != nil {
return nil, errors.New("failed to generate refresh token")
}
return &domain.UserAuthResponse{
User: user,
AccessToken: accessToken,
RefreshToken: newRefreshToken,
ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()),
}, nil
}
// ValidateToken validates a JWT token and returns the user
func (s *UserAuthServiceImpl) ValidateToken(ctx context.Context, tokenString string) (*domain.User, error) {
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("invalid signing method")
}
return []byte(s.config.JWT.Secret), nil
})
if err != nil || !token.Valid {
return nil, errors.New("invalid token")
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("invalid token claims")
}
// Verify token type
tokenType, ok := claims["type"].(string)
if !ok || tokenType != "access" {
return nil, errors.New("invalid token type")
}
// Verify user type
userType, ok := claims["user_type"].(string)
if !ok || userType != "user" {
return nil, errors.New("invalid user type")
}
userIDStr, ok := claims["user_id"].(string)
if !ok {
return nil, errors.New("invalid user ID in token")
}
userID, err := uuid.Parse(userIDStr)
if err != nil {
return nil, errors.New("invalid user ID format")
}
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return nil, errors.New("user not found")
}
if !user.IsActive {
return nil, errors.New("account is inactive")
}
return user, nil
}
// ChangePassword changes panel user password
func (s *UserAuthServiceImpl) ChangePassword(ctx context.Context, userID uuid.UUID, oldPassword, newPassword string) error {
user, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return errors.New("user not found")
}
// Verify old password
if !s.verifyPassword(oldPassword, user.Password) {
return errors.New("invalid old password")
}
// Hash new password
hashedPassword, err := s.hashPassword(newPassword)
if err != nil {
return errors.New("failed to process new password")
}
// Update password
user.Password = hashedPassword
if err := s.userRepo.Update(ctx, user); err != nil {
s.logger.Error("Failed to update user password", map[string]interface{}{
"error": err.Error(),
"user_id": userID.String(),
})
return errors.New("failed to update password")
}
s.logger.Info("Panel user password changed successfully", map[string]interface{}{
"user_id": userID.String(),
})
return nil
}
// CreatePanelUser creates a new panel user (admin only)
func (s *UserAuthServiceImpl) CreatePanelUser(ctx context.Context, req domain.CreateUserRequest, createdBy uuid.UUID) (*domain.User, error) {
s.logger.Info("Creating new panel user", map[string]interface{}{
"email": req.Email,
"role": req.Role,
"created_by": createdBy.String(),
})
// Check if user already exists
existingUser, err := s.userRepo.GetByEmail(ctx, req.Email)
if err == nil && existingUser != nil {
return nil, errors.New("user already exists")
}
// Validate role
if !s.isValidRole(req.Role) {
return nil, errors.New("invalid role")
}
// Hash password
hashedPassword, err := s.hashPassword(req.Password)
if err != nil {
s.logger.Error("Failed to hash password", map[string]interface{}{
"error": err.Error(),
})
return nil, errors.New("failed to process password")
}
// Create user
user := &domain.User{
ID: uuid.New(),
Email: req.Email,
Password: hashedPassword,
FirstName: req.FirstName,
LastName: req.LastName,
Role: req.Role,
Department: req.Department,
IsActive: true,
Permissions: s.getDefaultPermissions(req.Role),
CreatedBy: &createdBy,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
// Add custom permissions if provided
if len(req.Permissions) > 0 {
user.Permissions = append(user.Permissions, req.Permissions...)
user.Permissions = s.removeDuplicatePermissions(user.Permissions)
}
if err := s.userRepo.Create(ctx, user); err != nil {
s.logger.Error("Failed to create panel user", map[string]interface{}{
"error": err.Error(),
"email": req.Email,
"created_by": createdBy.String(),
})
return nil, errors.New("failed to create user")
}
s.logger.Info("Panel user created successfully", map[string]interface{}{
"user_id": user.ID.String(),
"email": user.Email,
"role": user.Role,
"created_by": createdBy.String(),
})
// Remove password from response
user.Password = ""
return user, nil
}
// UpdateUserPermissions updates panel user permissions (admin only)
func (s *UserAuthServiceImpl) UpdateUserPermissions(ctx context.Context, userID uuid.UUID, permissions []string, updatedBy uuid.UUID) error {
// Check if user exists
_, err := s.userRepo.GetByID(ctx, userID)
if err != nil {
return errors.New("user not found")
}
// Validate permissions
for _, permission := range permissions {
if !s.isValidPermission(permission) {
return errors.New("invalid permission: " + permission)
}
}
// Update permissions
if err := s.userRepo.UpdatePermissions(ctx, userID, permissions); err != nil {
s.logger.Error("Failed to update user permissions", map[string]interface{}{
"error": err.Error(),
"user_id": userID.String(),
"updated_by": updatedBy.String(),
})
return errors.New("failed to update permissions")
}
s.logger.Info("Panel user permissions updated successfully", map[string]interface{}{
"user_id": userID.String(),
"permissions": permissions,
"updated_by": updatedBy.String(),
})
return nil
}
// Private helper methods
func (s *UserAuthServiceImpl) hashPassword(password string) (string, error) {
hashedBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", err
}
return string(hashedBytes), nil
}
func (s *UserAuthServiceImpl) verifyPassword(password, hashedPassword string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
return err == nil
}
func (s *UserAuthServiceImpl) generateAccessToken(user *domain.User) (string, error) {
claims := jwt.MapClaims{
"user_id": user.ID.String(),
"email": user.Email,
"role": user.Role,
"department": user.Department,
"permissions": user.Permissions,
"user_type": "user",
"exp": time.Now().Add(s.config.JWT.AccessTokenDuration).Unix(),
"iat": time.Now().Unix(),
"type": "access",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(s.config.JWT.Secret))
}
func (s *UserAuthServiceImpl) generateRefreshToken(user *domain.User) (string, error) {
claims := jwt.MapClaims{
"user_id": user.ID.String(),
"user_type": "user",
"exp": time.Now().Add(s.config.JWT.RefreshTokenDuration).Unix(),
"iat": time.Now().Unix(),
"type": "refresh",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(s.config.JWT.Secret))
}
func (s *UserAuthServiceImpl) isValidRole(role domain.UserRole) bool {
validRoles := []domain.UserRole{
domain.UserRoleAdmin,
domain.UserRoleModerator,
domain.UserRoleSupport,
domain.UserRoleAnalyst,
}
for _, validRole := range validRoles {
if role == validRole {
return true
}
}
return false
}
func (s *UserAuthServiceImpl) getDefaultPermissions(role domain.UserRole) []string {
switch role {
case domain.UserRoleAdmin:
return []string{
"manage_users",
"manage_tenders",
"manage_companies",
"manage_customers",
"view_reports",
"manage_system",
"manage_sources",
}
case domain.UserRoleModerator:
return []string{
"manage_tenders",
"manage_companies",
"view_customers",
"view_reports",
"manage_sources",
}
case domain.UserRoleSupport:
return []string{
"view_customers",
"view_tenders",
"view_companies",
"create_tickets",
}
case domain.UserRoleAnalyst:
return []string{
"view_reports",
"view_tenders",
"view_customers",
"view_companies",
"export_data",
}
default:
return []string{}
}
}
func (s *UserAuthServiceImpl) isValidPermission(permission string) bool {
validPermissions := []string{
"manage_users",
"manage_tenders",
"manage_companies",
"manage_customers",
"view_customers",
"view_tenders",
"view_companies",
"view_reports",
"manage_system",
"manage_sources",
"create_tickets",
"export_data",
}
for _, validPermission := range validPermissions {
if permission == validPermission {
return true
}
}
return false
}
func (s *UserAuthServiceImpl) removeDuplicatePermissions(permissions []string) []string {
seen := make(map[string]bool)
result := []string{}
for _, permission := range permissions {
if !seen[permission] {
seen[permission] = true
result = append(result, permission)
}
}
return result
}