Refactor customer domain structure and implement core entities, services, and handlers
- Introduced a flat structure for the customer domain, consolidating entities, aggregates, repositories, services, forms, and handlers into a single package. - Added core entities: Customer and CustomerAggregate. - Implemented request/response forms for customer authentication (Register, Login, Refresh Token, Change Password). - Created CustomerService to handle business logic and data access through the Repository interface. - Established CustomerHandler for HTTP request handling related to customer authentication. - Removed previous domain structure artifacts to streamline the codebase.
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CustomerAggregate struct {
|
||||
ID uuid.UUID `json:"_id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Mobile string `json:"mobile"`
|
||||
Email string `json:"email"`
|
||||
IsActive bool `json:"is_active"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
LastLoginAt *int64 `json:"last_login_at,omitempty"` // Unix timestamp
|
||||
CreatedAt int64 `json:"created_at"` // Unix timestamp
|
||||
UpdatedAt int64 `json:"updated_at"` // Unix timestamp
|
||||
}
|
||||
|
||||
func NewCustomerAggregate(c Customer) CustomerAggregate {
|
||||
customer := CustomerAggregate{
|
||||
ID: c.ID,
|
||||
FirstName: c.FirstName,
|
||||
LastName: c.LastName,
|
||||
Mobile: c.Mobile,
|
||||
Email: c.Email,
|
||||
IsActive: c.IsActive,
|
||||
IsVerified: c.IsVerified,
|
||||
CreatedAt: c.CreatedAt,
|
||||
UpdatedAt: c.UpdatedAt,
|
||||
}
|
||||
|
||||
if c.LastLoginAt != nil {
|
||||
customer.LastLoginAt = c.LastLoginAt
|
||||
}
|
||||
|
||||
return customer
|
||||
}
|
||||
|
||||
// AuthResponse defines authentication response (kept for backward compatibility)
|
||||
type AuthorizationResponse struct {
|
||||
Customer CustomerAggregate `json:"customer"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package aggregate
|
||||
|
||||
import (
|
||||
"tm/internal/customer/domain/entity"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Customer struct {
|
||||
ID uuid.UUID `json:"_id"`
|
||||
FirstName string `json:"first_name"`
|
||||
LastName string `json:"last_name"`
|
||||
Mobile string `json:"mobile"`
|
||||
Email string `json:"email"`
|
||||
IsActive bool `json:"is_active"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
}
|
||||
|
||||
func NewCustomer(c entity.Customer) Customer {
|
||||
return Customer{
|
||||
ID: c.ID,
|
||||
FirstName: c.FirstName,
|
||||
LastName: c.LastName,
|
||||
Mobile: c.Mobile,
|
||||
Email: c.Email,
|
||||
IsActive: c.IsActive,
|
||||
IsVerified: c.IsVerified,
|
||||
}
|
||||
}
|
||||
|
||||
// AuthResponse defines authentication response (kept for backward compatibility)
|
||||
type AuthorizationResponse struct {
|
||||
Customer Customer `json:"customer"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
ExpiresIn int64 `json:"expires_in"`
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Customer represents a mobile app user (customer)
|
||||
type Customer struct {
|
||||
ID uuid.UUID `bson:"_id"`
|
||||
Email string `bson:"email"`
|
||||
Password string `bson:"password"` // Never serialize password
|
||||
FirstName string `bson:"first_name"`
|
||||
LastName string `bson:"last_name"`
|
||||
Mobile string `bson:"mobile"`
|
||||
CompanyID uuid.UUID `bson:"company_id"`
|
||||
IsActive bool `bson:"is_active"`
|
||||
IsVerified bool `bson:"is_verified"`
|
||||
DeviceTokens []string `bson:"device_tokens"` // For push notifications
|
||||
LastLoginAt *time.Time `bson:"last_login_at,omitempty"`
|
||||
CreatedAt time.Time `bson:"created_at"`
|
||||
UpdatedAt time.Time `bson:"updated_at"`
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"tm/internal/customer/domain/entity"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CustomerRepository defines methods for customer (mobile user) data access
|
||||
type Repository interface {
|
||||
Create(ctx context.Context, customer *entity.Customer) error
|
||||
GetByID(ctx context.Context, id uuid.UUID) (*entity.Customer, error)
|
||||
GetByEmail(ctx context.Context, email string) (*entity.Customer, error)
|
||||
Update(ctx context.Context, customer *entity.Customer) error
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
List(ctx context.Context, limit, offset int) ([]*entity.Customer, error)
|
||||
GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*entity.Customer, error)
|
||||
AddDeviceToken(ctx context.Context, id uuid.UUID, token string) error
|
||||
RemoveDeviceToken(ctx context.Context, id uuid.UUID, token string) error
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Customer represents a mobile app user (customer)
|
||||
type Customer struct {
|
||||
ID uuid.UUID `bson:"_id"`
|
||||
Email string `bson:"email"`
|
||||
Password string `bson:"password"` // Never serialize password
|
||||
FirstName string `bson:"first_name"`
|
||||
LastName string `bson:"last_name"`
|
||||
Mobile string `bson:"mobile"`
|
||||
CompanyID uuid.UUID `bson:"company_id"`
|
||||
IsActive bool `bson:"is_active"`
|
||||
IsVerified bool `bson:"is_verified"`
|
||||
DeviceTokens []string `bson:"device_tokens"` // For push notifications
|
||||
LastLoginAt *int64 `bson:"last_login_at,omitempty"` // Unix timestamp
|
||||
CreatedAt int64 `bson:"created_at"` // Unix timestamp
|
||||
UpdatedAt int64 `bson:"updated_at"` // Unix timestamp
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package customer
|
||||
|
||||
// Customer Authentication DTOs
|
||||
type RegisterForm struct {
|
||||
FirstName string `json:"first_name" valid:"required,alpha,length(2|50)"`
|
||||
LastName string `json:"last_name" valid:"required,alpha,length(2|50)"`
|
||||
Email string `json:"email" valid:"required,email"`
|
||||
Mobile string `json:"mobile,omitempty" valid:"optional,length(10|15)"`
|
||||
Password string `json:"password" valid:"required,length(8|128)"`
|
||||
}
|
||||
|
||||
type LoginForm struct {
|
||||
Email string `json:"email" valid:"required,email"`
|
||||
Password string `json:"password" valid:"required"`
|
||||
}
|
||||
|
||||
type RefreshTokenForm struct {
|
||||
RefreshToken string `json:"refresh_token" valid:"required"`
|
||||
}
|
||||
|
||||
type ChangePasswordForm struct {
|
||||
OldPassword string `json:"old_password" valid:"required"`
|
||||
NewPassword string `json:"new_password" valid:"required,length(8|128)"`
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package v1
|
||||
package customer
|
||||
|
||||
import (
|
||||
service "tm/internal/customer/service"
|
||||
"tm/pkg/logger"
|
||||
"tm/pkg/response"
|
||||
|
||||
@@ -11,13 +10,13 @@ import (
|
||||
|
||||
// CustomerHandler handles authentication related HTTP requests for mobile customers
|
||||
type CustomerHandler struct {
|
||||
service service.CustomerService
|
||||
service CustomerService
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewCustomerHandler creates a new customer authentication handler
|
||||
func NewHandler(
|
||||
customerAuthService service.CustomerService,
|
||||
customerAuthService CustomerService,
|
||||
logger logger.Logger,
|
||||
) *CustomerHandler {
|
||||
return &CustomerHandler{
|
||||
@@ -39,7 +38,7 @@ func NewHandler(
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/mobile/auth/register [post]
|
||||
func (h *CustomerHandler) Register(c echo.Context) error {
|
||||
var req service.RegisterForm
|
||||
var req RegisterForm
|
||||
|
||||
// Bind request body
|
||||
if err := c.Bind(&req); err != nil {
|
||||
@@ -73,7 +72,7 @@ func (h *CustomerHandler) Register(c echo.Context) error {
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/mobile/auth/login [post]
|
||||
func (h *CustomerHandler) Login(c echo.Context) error {
|
||||
var req service.LoginForm
|
||||
var req LoginForm
|
||||
|
||||
// Bind request body
|
||||
if err := c.Bind(&req); err != nil {
|
||||
@@ -107,7 +106,7 @@ func (h *CustomerHandler) Login(c echo.Context) error {
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/mobile/auth/refresh [post]
|
||||
func (h *CustomerHandler) RefreshToken(c echo.Context) error {
|
||||
var req service.RefreshTokenForm
|
||||
var req RefreshTokenForm
|
||||
|
||||
// Bind request body
|
||||
if err := c.Bind(&req); err != nil {
|
||||
@@ -142,7 +141,7 @@ func (h *CustomerHandler) RefreshToken(c echo.Context) error {
|
||||
// @Failure 500 {object} response.APIResponse
|
||||
// @Router /api/mobile/auth/change-password [post]
|
||||
func (h *CustomerHandler) ChangePassword(c echo.Context) error {
|
||||
var req service.ChangePasswordForm
|
||||
var req ChangePasswordForm
|
||||
|
||||
// Bind request body
|
||||
if err := c.Bind(&req); err != nil {
|
||||
@@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
"tm/internal/customer/domain/entity"
|
||||
"tm/pkg/logger"
|
||||
|
||||
"github.com/google/uuid"
|
||||
@@ -13,6 +12,19 @@ import (
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
// Repository defines methods for customer (mobile user) data access
|
||||
type Repository interface {
|
||||
Create(ctx context.Context, customer *Customer) error
|
||||
GetByID(ctx context.Context, id uuid.UUID) (*Customer, error)
|
||||
GetByEmail(ctx context.Context, email string) (*Customer, error)
|
||||
Update(ctx context.Context, customer *Customer) error
|
||||
Delete(ctx context.Context, id uuid.UUID) error
|
||||
List(ctx context.Context, limit, offset int) ([]*Customer, error)
|
||||
GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error)
|
||||
AddDeviceToken(ctx context.Context, id uuid.UUID, token string) error
|
||||
RemoveDeviceToken(ctx context.Context, id uuid.UUID, token string) error
|
||||
}
|
||||
|
||||
// customerRepository implements the Repository interface
|
||||
type customerRepository struct {
|
||||
collection *mongo.Collection
|
||||
@@ -68,9 +80,9 @@ func NewCustomerRepository(db *mongo.Database, logger logger.Logger) Repository
|
||||
}
|
||||
|
||||
// Create creates a new customer
|
||||
func (r *customerRepository) Create(ctx context.Context, customer *entity.Customer) error {
|
||||
// Set created/updated timestamps
|
||||
now := time.Now()
|
||||
func (r *customerRepository) Create(ctx context.Context, customer *Customer) error {
|
||||
// Set created/updated timestamps using Unix timestamps
|
||||
now := time.Now().Unix()
|
||||
customer.CreatedAt = now
|
||||
customer.UpdatedAt = now
|
||||
|
||||
@@ -97,8 +109,8 @@ func (r *customerRepository) Create(ctx context.Context, customer *entity.Custom
|
||||
}
|
||||
|
||||
// GetByID retrieves a customer by ID
|
||||
func (r *customerRepository) GetByID(ctx context.Context, id uuid.UUID) (*entity.Customer, error) {
|
||||
var customer entity.Customer
|
||||
func (r *customerRepository) GetByID(ctx context.Context, id uuid.UUID) (*Customer, error) {
|
||||
var customer Customer
|
||||
|
||||
filter := bson.M{"_id": id}
|
||||
err := r.collection.FindOne(ctx, filter).Decode(&customer)
|
||||
@@ -118,8 +130,8 @@ func (r *customerRepository) GetByID(ctx context.Context, id uuid.UUID) (*entity
|
||||
}
|
||||
|
||||
// GetByEmail retrieves a customer by email
|
||||
func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*entity.Customer, error) {
|
||||
var customer entity.Customer
|
||||
func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*Customer, error) {
|
||||
var customer Customer
|
||||
|
||||
filter := bson.M{"email": email}
|
||||
err := r.collection.FindOne(ctx, filter).Decode(&customer)
|
||||
@@ -139,9 +151,9 @@ func (r *customerRepository) GetByEmail(ctx context.Context, email string) (*ent
|
||||
}
|
||||
|
||||
// Update updates a customer
|
||||
func (r *customerRepository) Update(ctx context.Context, customer *entity.Customer) error {
|
||||
// Set updated timestamp
|
||||
customer.UpdatedAt = time.Now()
|
||||
func (r *customerRepository) Update(ctx context.Context, customer *Customer) error {
|
||||
// Set updated timestamp using Unix timestamp
|
||||
customer.UpdatedAt = time.Now().Unix()
|
||||
|
||||
filter := bson.M{"_id": customer.ID}
|
||||
update := bson.M{"$set": customer}
|
||||
@@ -173,7 +185,7 @@ func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"is_active": false,
|
||||
"updated_at": time.Now(),
|
||||
"updated_at": time.Now().Unix(),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -198,8 +210,8 @@ func (r *customerRepository) Delete(ctx context.Context, id uuid.UUID) error {
|
||||
}
|
||||
|
||||
// List retrieves customers with pagination
|
||||
func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*entity.Customer, error) {
|
||||
var customers []*entity.Customer
|
||||
func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*Customer, error) {
|
||||
var customers []*Customer
|
||||
|
||||
// Build options
|
||||
opts := options.Find()
|
||||
@@ -232,8 +244,8 @@ func (r *customerRepository) List(ctx context.Context, limit, offset int) ([]*en
|
||||
}
|
||||
|
||||
// GetByCompanyID retrieves customers by company ID with pagination
|
||||
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*entity.Customer, error) {
|
||||
var customers []*entity.Customer
|
||||
func (r *customerRepository) GetByCompanyID(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*Customer, error) {
|
||||
var customers []*Customer
|
||||
|
||||
// Build options
|
||||
opts := options.Find()
|
||||
@@ -275,7 +287,7 @@ func (r *customerRepository) AddDeviceToken(ctx context.Context, id uuid.UUID, t
|
||||
filter := bson.M{"_id": id}
|
||||
update := bson.M{
|
||||
"$addToSet": bson.M{"device_tokens": token}, // Use $addToSet to avoid duplicates
|
||||
"$set": bson.M{"updated_at": time.Now()},
|
||||
"$set": bson.M{"updated_at": time.Now().Unix()},
|
||||
}
|
||||
|
||||
result, err := r.collection.UpdateOne(ctx, filter, update)
|
||||
@@ -305,7 +317,7 @@ func (r *customerRepository) RemoveDeviceToken(ctx context.Context, id uuid.UUID
|
||||
filter := bson.M{"_id": id}
|
||||
update := bson.M{
|
||||
"$pull": bson.M{"device_tokens": token}, // Use $pull to remove the token
|
||||
"$set": bson.M{"updated_at": time.Now()},
|
||||
"$set": bson.M{"updated_at": time.Now().Unix()},
|
||||
}
|
||||
|
||||
result, err := r.collection.UpdateOne(ctx, filter, update)
|
||||
@@ -4,17 +4,155 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"time"
|
||||
infrastructure "tm/infra"
|
||||
"tm/pkg/logger"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"tm/internal/customer/domain/aggregate"
|
||||
"tm/internal/customer/domain/entity"
|
||||
)
|
||||
|
||||
// CustomerService implements the CustomerService interface
|
||||
type CustomerService struct {
|
||||
customerRepo Repository
|
||||
config *infrastructure.AuthConfig
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewCustomerService creates a new customer service
|
||||
func NewCustomerService(
|
||||
customerRepo Repository,
|
||||
config *infrastructure.AuthConfig,
|
||||
logger logger.Logger,
|
||||
) CustomerService {
|
||||
return CustomerService{
|
||||
customerRepo: customerRepo,
|
||||
config: config,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCustomers retrieves customers with pagination (for panel users)
|
||||
func (s *CustomerService) GetCustomers(ctx context.Context, limit, offset int) ([]*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 *CustomerService) GetCustomerByID(ctx context.Context, id uuid.UUID) (*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 *CustomerService) GetCustomersByCompany(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*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 *CustomerService) 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
|
||||
}
|
||||
|
||||
// Register creates a new customer account
|
||||
func (s *CustomerService) Register(ctx context.Context, req RegisterForm) (*aggregate.AuthorizationResponse, error) {
|
||||
func (s *CustomerService) Register(ctx context.Context, req RegisterForm) (*AuthorizationResponse, error) {
|
||||
s.logger.Info("Registering new customer", map[string]interface{}{
|
||||
"email": req.Email,
|
||||
})
|
||||
@@ -35,7 +173,7 @@ func (s *CustomerService) Register(ctx context.Context, req RegisterForm) (*aggr
|
||||
}
|
||||
|
||||
// Create customer
|
||||
customer := &entity.Customer{
|
||||
customer := &Customer{
|
||||
ID: uuid.New(),
|
||||
Email: req.Email,
|
||||
Password: hashedPassword,
|
||||
@@ -45,8 +183,8 @@ func (s *CustomerService) Register(ctx context.Context, req RegisterForm) (*aggr
|
||||
IsActive: true,
|
||||
IsVerified: false, // Require email verification
|
||||
DeviceTokens: make([]string, 0),
|
||||
UpdatedAt: time.Now(),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}
|
||||
|
||||
if err := s.customerRepo.Create(ctx, customer); err != nil {
|
||||
@@ -85,8 +223,8 @@ func (s *CustomerService) Register(ctx context.Context, req RegisterForm) (*aggr
|
||||
// TODO: Send verification email
|
||||
s.sendVerificationEmail(ctx, customer)
|
||||
|
||||
return &aggregate.AuthorizationResponse{
|
||||
Customer: aggregate.NewCustomer(*customer),
|
||||
return &AuthorizationResponse{
|
||||
Customer: NewCustomerAggregate(*customer),
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()),
|
||||
@@ -94,7 +232,7 @@ func (s *CustomerService) Register(ctx context.Context, req RegisterForm) (*aggr
|
||||
}
|
||||
|
||||
// Login authenticates a customer and returns tokens
|
||||
func (s *CustomerService) Login(ctx context.Context, req LoginForm) (*aggregate.AuthorizationResponse, error) {
|
||||
func (s *CustomerService) Login(ctx context.Context, req LoginForm) (*AuthorizationResponse, error) {
|
||||
s.logger.Info("Customer login attempt", map[string]interface{}{
|
||||
"email": req.Email,
|
||||
})
|
||||
@@ -127,9 +265,9 @@ func (s *CustomerService) Login(ctx context.Context, req LoginForm) (*aggregate.
|
||||
}
|
||||
|
||||
// Update last login time
|
||||
customer.LastLoginAt = &time.Time{}
|
||||
*customer.LastLoginAt = time.Now()
|
||||
customer.UpdatedAt = time.Now()
|
||||
now := time.Now().Unix()
|
||||
customer.LastLoginAt = &now
|
||||
customer.UpdatedAt = now
|
||||
|
||||
if err := s.customerRepo.Update(ctx, customer); err != nil {
|
||||
s.logger.Warn("Failed to update customer last login", map[string]interface{}{
|
||||
@@ -164,8 +302,8 @@ func (s *CustomerService) Login(ctx context.Context, req LoginForm) (*aggregate.
|
||||
"is_verified": customer.IsVerified,
|
||||
})
|
||||
|
||||
return &aggregate.AuthorizationResponse{
|
||||
Customer: aggregate.NewCustomer(*customer),
|
||||
return &AuthorizationResponse{
|
||||
Customer: NewCustomerAggregate(*customer),
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()),
|
||||
@@ -173,7 +311,7 @@ func (s *CustomerService) Login(ctx context.Context, req LoginForm) (*aggregate.
|
||||
}
|
||||
|
||||
// RefreshToken generates new tokens using refresh token
|
||||
func (s *CustomerService) RefreshToken(ctx context.Context, refreshToken string) (*aggregate.AuthorizationResponse, error) {
|
||||
func (s *CustomerService) RefreshToken(ctx context.Context, refreshToken string) (*AuthorizationResponse, error) {
|
||||
// Parse and validate refresh token
|
||||
token, err := jwt.Parse(refreshToken, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
@@ -234,8 +372,8 @@ func (s *CustomerService) RefreshToken(ctx context.Context, refreshToken string)
|
||||
return nil, errors.New("failed to generate refresh token")
|
||||
}
|
||||
|
||||
return &aggregate.AuthorizationResponse{
|
||||
Customer: aggregate.NewCustomer(*customer),
|
||||
return &AuthorizationResponse{
|
||||
Customer: NewCustomerAggregate(*customer),
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: newRefreshToken,
|
||||
ExpiresIn: int64(s.config.JWT.AccessTokenDuration.Seconds()),
|
||||
@@ -243,7 +381,7 @@ func (s *CustomerService) RefreshToken(ctx context.Context, refreshToken string)
|
||||
}
|
||||
|
||||
// ValidateToken validates a JWT token and returns the customer
|
||||
func (s *CustomerService) ValidateToken(ctx context.Context, tokenString string) (*entity.Customer, error) {
|
||||
func (s *CustomerService) ValidateToken(ctx context.Context, tokenString string) (*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")
|
||||
@@ -314,7 +452,7 @@ func (s *CustomerService) ChangePassword(ctx context.Context, customerID uuid.UU
|
||||
|
||||
// Update password
|
||||
customer.Password = hashedPassword
|
||||
customer.UpdatedAt = time.Now()
|
||||
customer.UpdatedAt = time.Now().Unix()
|
||||
|
||||
if err := s.customerRepo.Update(ctx, customer); err != nil {
|
||||
s.logger.Error("Failed to update customer password", map[string]interface{}{
|
||||
@@ -383,7 +521,7 @@ func (s *CustomerService) verifyPassword(password, hashedPassword string) bool {
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (s *CustomerService) generateAccessToken(customer *entity.Customer) (string, error) {
|
||||
func (s *CustomerService) generateAccessToken(customer *Customer) (string, error) {
|
||||
claims := jwt.MapClaims{
|
||||
"customer_id": customer.ID.String(),
|
||||
"email": customer.Email,
|
||||
@@ -398,7 +536,7 @@ func (s *CustomerService) generateAccessToken(customer *entity.Customer) (string
|
||||
return token.SignedString([]byte(s.config.JWT.Secret))
|
||||
}
|
||||
|
||||
func (s *CustomerService) generateRefreshToken(customer *entity.Customer) (string, error) {
|
||||
func (s *CustomerService) generateRefreshToken(customer *Customer) (string, error) {
|
||||
claims := jwt.MapClaims{
|
||||
"customer_id": customer.ID.String(),
|
||||
"user_type": "customer",
|
||||
@@ -411,7 +549,7 @@ func (s *CustomerService) generateRefreshToken(customer *entity.Customer) (strin
|
||||
return token.SignedString([]byte(s.config.JWT.Secret))
|
||||
}
|
||||
|
||||
func (s *CustomerService) sendVerificationEmail(ctx context.Context, customer *entity.Customer) {
|
||||
func (s *CustomerService) sendVerificationEmail(ctx context.Context, customer *Customer) {
|
||||
// TODO: Implement email sending logic
|
||||
_ = ctx
|
||||
s.logger.Info("Verification email would be sent", map[string]interface{}{
|
||||
@@ -1,151 +0,0 @@
|
||||
package customer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
infrastructure "tm/infra"
|
||||
domain "tm/internal/customer/domain"
|
||||
"tm/internal/customer/domain/entity"
|
||||
"tm/pkg/logger"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CustomerService implements the CustomerService interface
|
||||
type CustomerService struct {
|
||||
customerRepo domain.Repository
|
||||
config *infrastructure.AuthConfig
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
// NewCustomerService creates a new customer service
|
||||
func NewCustomerService(
|
||||
customerRepo domain.Repository,
|
||||
config *infrastructure.AuthConfig,
|
||||
logger logger.Logger,
|
||||
) CustomerService {
|
||||
return CustomerService{
|
||||
customerRepo: customerRepo,
|
||||
config: config,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCustomers retrieves customers with pagination (for panel users)
|
||||
func (s *CustomerService) GetCustomers(ctx context.Context, limit, offset int) ([]*entity.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 *CustomerService) GetCustomerByID(ctx context.Context, id uuid.UUID) (*entity.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 *CustomerService) GetCustomersByCompany(ctx context.Context, companyID uuid.UUID, limit, offset int) ([]*entity.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 *CustomerService) 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
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package customer
|
||||
|
||||
// Customer Authentication DTOs
|
||||
type RegisterForm struct {
|
||||
FirstName string `json:"first_name" validate:"required"`
|
||||
LastName string `json:"last_name" validate:"required"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Mobile string `json:"mobile,omitempty"`
|
||||
Password string `json:"password" validate:"required,min=8"`
|
||||
}
|
||||
|
||||
type LoginForm struct {
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Password string `json:"password" validate:"required"`
|
||||
}
|
||||
|
||||
type RefreshTokenForm struct {
|
||||
RefreshToken string `json:"refresh_token" validate:"required"`
|
||||
}
|
||||
|
||||
type ChangePasswordForm struct {
|
||||
OldPassword string `json:"old_password" validate:"required"`
|
||||
NewPassword string `json:"new_password" validate:"required,min=8"`
|
||||
}
|
||||
Reference in New Issue
Block a user