Add cursor rules and initial configuration for Tender Management Go Backend
This commit is contained in:
+838
@@ -0,0 +1,838 @@
|
||||
# Cursor Rules for Tender Management Go Backend
|
||||
|
||||
## Project Context
|
||||
This is a Go backend API for a tender management system following Clean Architecture principles with Domain-Driven Design (DDD) patterns, designed to be AI-friendly and easy to maintain.
|
||||
|
||||
## 🏗️ Architecture Guidelines
|
||||
|
||||
### Clean Architecture Layers
|
||||
- **Domain Layer** (`internal/{domain}/domain/`): Business entities, aggregates, interfaces, and core business rules
|
||||
- **Service Layer** (`internal/{domain}/service/`): Business logic and use cases
|
||||
- **Handler Layer** (`internal/{domain}/handler/`): HTTP controllers and request/response handling
|
||||
- **Repository Layer** (`internal/{domain}/domain/`): Data access implementations (embedded in domain)
|
||||
- **Infrastructure Layer** (`infra/`): External dependencies (DB, cache, queues, config)
|
||||
|
||||
### Domain Structure Pattern
|
||||
Each domain follows this structure:
|
||||
```
|
||||
internal/{domain}/
|
||||
├── domain/
|
||||
│ ├── entity/
|
||||
│ │ └── entity.go # Core domain entities
|
||||
│ ├── aggregate/
|
||||
│ │ └── aggregate.go # Aggregate roots and DTOs
|
||||
│ ├── {domain}.go # Repository implementation
|
||||
│ └── repository.go # Repository interface
|
||||
├── service/
|
||||
│ ├── {domain}.go # Main service implementation
|
||||
│ ├── profile.go # Profile-related operations
|
||||
│ └── form.go # Request/response forms
|
||||
└── handler/
|
||||
├── v1/
|
||||
│ └── handler.go # HTTP handlers
|
||||
└── middleware/ # Domain-specific middleware
|
||||
```
|
||||
|
||||
### Dependency Rules
|
||||
- Always depend on interfaces, not concrete implementations
|
||||
- Use dependency injection through constructors
|
||||
- Inner layers should never depend on outer layers
|
||||
- All dependencies flow inward toward the domain
|
||||
- Repository implementations are embedded in domain package
|
||||
|
||||
## 💻 Go Best Practices
|
||||
|
||||
### Code Structure
|
||||
- Use standard Go project layout with `internal/` for private code
|
||||
- Group related functionality in domain packages
|
||||
- Keep packages focused on single responsibility
|
||||
- Use meaningful package names (not generic like `utils` or `helpers`)
|
||||
|
||||
### Naming Conventions
|
||||
- Use Go naming conventions: camelCase for private, PascalCase for public
|
||||
- Interface names should describe what they do (e.g., `CustomerRepository`, `AuthService`)
|
||||
- Use descriptive variable names, avoid abbreviations
|
||||
- Constants should be in ALL_CAPS with underscores
|
||||
- Domain packages use singular form (e.g., `customer`, not `customers`)
|
||||
|
||||
### Error Handling
|
||||
- Always handle errors explicitly, never ignore them
|
||||
- Use structured error messages with context
|
||||
- Wrap errors with additional context using `fmt.Errorf("context: %w", err)`
|
||||
- Log errors at the point they occur with relevant fields
|
||||
- Return domain-specific error types when appropriate
|
||||
|
||||
### Function Design
|
||||
- Keep functions small and focused (max 20-30 lines)
|
||||
- Use early returns to reduce nesting
|
||||
- Validate inputs at function entry points
|
||||
- Use descriptive parameter names
|
||||
- Limit function parameters (max 3-4, use structs for more)
|
||||
|
||||
## 🔌 Interface Design
|
||||
|
||||
### Repository Interfaces
|
||||
- Always create interfaces in the domain layer (`repository.go`)
|
||||
- Use context.Context as first parameter for all methods
|
||||
- Return domain entities, not database models
|
||||
- Include common operations: Create, GetByID, Update, Delete, List
|
||||
- Use specific query methods (e.g., `GetByEmail`, `GetByCompanyID`)
|
||||
|
||||
### Service Interfaces
|
||||
- Define business operations clearly in service package
|
||||
- Use request/response forms for complex operations
|
||||
- Include validation in service methods
|
||||
- Handle business logic, not infrastructure concerns
|
||||
|
||||
## 📝 Logging Standards
|
||||
|
||||
### Using Custom Logger Interface
|
||||
- Always use structured logging with fields
|
||||
- Include relevant context (user_id, customer_id, request_id, etc.)
|
||||
- Use appropriate log levels:
|
||||
- `Debug`: Detailed debugging information
|
||||
- `Info`: General operational messages
|
||||
- `Warn`: Warning conditions that should be addressed
|
||||
- `Error`: Error conditions that need attention
|
||||
- `Fatal`: Critical errors that cause program termination
|
||||
|
||||
### Logging Examples
|
||||
```go
|
||||
// Good
|
||||
log.Info("Customer authenticated successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"email": customer.Email,
|
||||
"ip": clientIP,
|
||||
})
|
||||
|
||||
// Bad
|
||||
log.Info("Customer login")
|
||||
```
|
||||
|
||||
### Error Logging
|
||||
- Log errors with full context
|
||||
- Include error details and relevant fields
|
||||
- Don't log the same error multiple times in the call stack
|
||||
|
||||
## 🗄️ Database & Repository Patterns
|
||||
|
||||
### Repository Implementation
|
||||
- Implement repository interfaces in domain package (`{domain}.go`)
|
||||
- Use proper database transactions for related operations
|
||||
- Handle database errors gracefully
|
||||
- Use prepared statements or ORM query builders
|
||||
- Implement proper pagination with limit/offset
|
||||
- **Time Handling**: Always use Unix timestamps (int64) for database operations
|
||||
- **Timestamp Updates**: Use `time.Now().Unix()` for created_at and updated_at fields
|
||||
|
||||
### Data Models
|
||||
- Use domain entities directly with BSON tags for MongoDB
|
||||
- Include audit fields (created_at, updated_at)
|
||||
- Use proper database field tags (bson, json)
|
||||
- Never serialize sensitive fields (passwords)
|
||||
|
||||
### Time Handling Patterns
|
||||
- **Input**: Accept Unix timestamps (int64) for all time fields
|
||||
- **Database Storage**: Store as Unix timestamps (int64) in MongoDB
|
||||
- **Output**: Return Unix timestamps (int64) for all time fields in API responses
|
||||
- **Internal Processing**: Use Unix timestamps (int64) for all business logic
|
||||
- **Audit Fields**: Always use Unix timestamps for created_at, updated_at
|
||||
- **Consistency**: All time operations use Unix timestamps throughout the application
|
||||
|
||||
### MongoDB Patterns
|
||||
- Create indexes in repository constructor
|
||||
- Use proper BSON field names
|
||||
- Handle duplicate key errors appropriately
|
||||
- Use MongoDB-specific features (aggregations, etc.)
|
||||
- **Time Fields**: Store all time fields as Unix timestamps (int64) for consistency
|
||||
- **Queries**: Use Unix timestamps for time-based queries and filtering
|
||||
|
||||
## 🌐 HTTP Handler Guidelines
|
||||
|
||||
### Request Handling
|
||||
- Validate all incoming requests using govalidator
|
||||
- Use proper HTTP status codes
|
||||
- Return consistent API response format using `pkg/response`
|
||||
- Handle CORS appropriately
|
||||
- Include request timeout handling
|
||||
- **Time Input**: Accept Unix timestamps (int64) for all time fields in requests
|
||||
- **Time Validation**: Validate Unix timestamps are within reasonable ranges
|
||||
- **Time Consistency**: All time inputs must be Unix timestamps (int64)
|
||||
|
||||
### Govalidator Usage
|
||||
- Use govalidator for all request validation
|
||||
- Define validation tags in request structs
|
||||
- Use custom validation functions for complex rules
|
||||
- Validate at handler level before calling services
|
||||
- Return validation errors using `response.ValidationError()`
|
||||
- Use meaningful error messages for validation failures
|
||||
|
||||
### Response Format
|
||||
- Always use standardized response structure from `pkg/response`
|
||||
- Include appropriate metadata for paginated responses
|
||||
- Provide meaningful error messages
|
||||
- Never expose internal errors to clients
|
||||
- **Time Output**: Always return Unix timestamps (int64) for all time fields in API responses
|
||||
- **Time Consistency**: Ensure all time fields in responses use Unix timestamps consistently
|
||||
|
||||
### Security
|
||||
- Validate and sanitize all inputs
|
||||
- Use proper authentication middleware
|
||||
- Implement rate limiting
|
||||
- Never log sensitive information (passwords, tokens)
|
||||
- Use HTTPS in production
|
||||
|
||||
## 🧪 Testing Guidelines
|
||||
|
||||
### Test Structure
|
||||
- Write unit tests for all business logic
|
||||
- Use table-driven tests for multiple scenarios
|
||||
- Mock external dependencies using interfaces
|
||||
- Test error cases, not just happy paths
|
||||
- Use meaningful test names that describe the scenario
|
||||
|
||||
### Test Organization
|
||||
- Place tests in same package as code being tested
|
||||
- Use `_test.go` suffix for test files
|
||||
- Create test utilities in `testutil` package if needed
|
||||
- Use setup/teardown functions for common test data
|
||||
|
||||
## 📋 Configuration Management
|
||||
|
||||
### Configuration Structure
|
||||
- Use YAML for configuration files
|
||||
- Support environment variable overrides
|
||||
- Validate configuration on startup
|
||||
- Use default values for optional settings
|
||||
- Document all configuration options
|
||||
|
||||
### Configuration Types
|
||||
- Server configuration (host, port, timeouts)
|
||||
- Database configuration (MongoDB URI, pool settings)
|
||||
- Cache configuration (Redis settings)
|
||||
- Queue configuration (RabbitMQ settings)
|
||||
- Auth configuration (JWT settings)
|
||||
- Logging configuration (level, format, output)
|
||||
|
||||
### Environment Variables
|
||||
- Use consistent naming convention (e.g., `TM_DATABASE_URI`)
|
||||
- Never commit secrets in configuration files
|
||||
- Use different configs for different environments
|
||||
|
||||
## 🚀 Performance Guidelines
|
||||
|
||||
### Optimization
|
||||
- Use database indexes appropriately
|
||||
- Implement caching for frequently accessed data
|
||||
- Use connection pooling for databases
|
||||
- Avoid N+1 queries
|
||||
- Profile performance bottlenecks
|
||||
|
||||
### Memory Management
|
||||
- Close resources properly (database connections, files)
|
||||
- Use context for cancellation and timeouts
|
||||
- Avoid memory leaks in goroutines
|
||||
- Use sync.Pool for frequently allocated objects
|
||||
|
||||
## 📚 Documentation Standards
|
||||
|
||||
### Code Documentation
|
||||
- Write clear comments for complex business logic
|
||||
- Document all public interfaces and types
|
||||
- Use Go doc comments format
|
||||
- Include examples for complex functions
|
||||
- Keep comments up to date with code changes
|
||||
|
||||
### API Documentation
|
||||
- Use OpenAPI/Swagger annotations
|
||||
- Document all endpoints with examples
|
||||
- Include error response examples
|
||||
- Document authentication requirements
|
||||
|
||||
## 🔐 Security Best Practices
|
||||
|
||||
### Authentication & Authorization
|
||||
- Use JWT tokens with proper expiration
|
||||
- Implement refresh token rotation
|
||||
- Use bcrypt for password hashing
|
||||
- Validate token claims properly
|
||||
- Implement proper RBAC
|
||||
|
||||
### Input Validation
|
||||
- Validate all inputs at API boundaries
|
||||
- Use whitelist validation, not blacklist
|
||||
- Sanitize inputs to prevent injection attacks
|
||||
- Validate file uploads properly
|
||||
|
||||
## 📦 Dependency Management
|
||||
|
||||
### Go Modules
|
||||
- Keep go.mod clean and organized
|
||||
- Pin major versions for stability
|
||||
- Regular dependency updates with testing
|
||||
- Remove unused dependencies
|
||||
- Use vendor directory for critical dependencies
|
||||
|
||||
### External Libraries
|
||||
- Prefer standard library when possible
|
||||
- Choose well-maintained libraries
|
||||
- Avoid dependencies with security issues
|
||||
- Keep dependencies minimal
|
||||
|
||||
## 🔄 Code Review Guidelines
|
||||
|
||||
### Before Committing
|
||||
- Run `go fmt`, `go vet`, and linting tools
|
||||
- Ensure all tests pass
|
||||
- Check for proper error handling
|
||||
- Verify logging is appropriate
|
||||
- Confirm no sensitive data is exposed
|
||||
|
||||
### Code Quality
|
||||
- Follow single responsibility principle
|
||||
- Avoid code duplication
|
||||
- Use meaningful variable names
|
||||
- Keep functions and files reasonably sized
|
||||
- Maintain consistent code style
|
||||
|
||||
## 🚨 Error Patterns to Avoid
|
||||
|
||||
### Anti-patterns
|
||||
- Don't use panic for regular error handling
|
||||
- Avoid global variables for application state
|
||||
- Don't ignore context cancellation
|
||||
- Avoid deeply nested if statements
|
||||
- Don't use reflection unless absolutely necessary
|
||||
|
||||
### Common Mistakes
|
||||
- Not handling database connection errors
|
||||
- Forgetting to close resources
|
||||
- Not validating user inputs
|
||||
- Exposing internal errors to clients
|
||||
- Not using proper HTTP status codes
|
||||
|
||||
## 📋 Checklist for New Features
|
||||
|
||||
### Before Implementation
|
||||
- [ ] Define interfaces in domain layer
|
||||
- [ ] Create proper request/response forms with govalidator tags
|
||||
- [ ] Plan error handling strategy
|
||||
- [ ] Design database schema if needed
|
||||
- [ ] Consider caching requirements
|
||||
- [ ] Define custom validators if needed
|
||||
|
||||
### During Implementation
|
||||
- [ ] Follow clean architecture layers
|
||||
- [ ] Use dependency injection
|
||||
- [ ] Implement proper logging
|
||||
- [ ] Add govalidator validation tags to forms
|
||||
- [ ] Register custom validators in init() functions
|
||||
- [ ] Handle errors gracefully
|
||||
|
||||
### After Implementation
|
||||
- [ ] Write unit tests
|
||||
- [ ] Update API documentation
|
||||
- [ ] Test error scenarios
|
||||
- [ ] Verify security implications
|
||||
- [ ] Check performance impact
|
||||
- [ ] Test validation scenarios
|
||||
|
||||
## 🎯 Code Examples
|
||||
|
||||
### Time Handling Utilities
|
||||
```go
|
||||
// pkg/timeutil/timeutil.go
|
||||
package timeutil
|
||||
|
||||
import "time"
|
||||
|
||||
// NowUnix returns current Unix timestamp
|
||||
func NowUnix() int64 {
|
||||
return time.Now().Unix()
|
||||
}
|
||||
|
||||
// ValidateUnixTimestamp validates if Unix timestamp is reasonable
|
||||
func ValidateUnixTimestamp(unix int64) bool {
|
||||
// Check if timestamp is not too old (before 2020) or too future (after 2030)
|
||||
minTime := time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
|
||||
maxTime := time.Date(2030, 12, 31, 23, 59, 59, 0, time.UTC).Unix()
|
||||
return unix >= minTime && unix <= maxTime
|
||||
}
|
||||
|
||||
// ParseUnixTimestamp parses string to Unix timestamp
|
||||
func ParseUnixTimestamp(timestamp string) (int64, error) {
|
||||
// Handle various timestamp formats and convert to Unix
|
||||
// This can be extended based on your input requirements
|
||||
return 0, nil // Implementation depends on input format
|
||||
}
|
||||
|
||||
// FormatUnixTimestamp formats Unix timestamp for display (if needed)
|
||||
func FormatUnixTimestamp(unix int64, format string) string {
|
||||
return time.Unix(unix, 0).Format(format)
|
||||
}
|
||||
```
|
||||
|
||||
### Govalidator Request Forms
|
||||
```go
|
||||
// internal/customer/service/form.go
|
||||
package service
|
||||
|
||||
import (
|
||||
"github.com/asaskevich/govalidator"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// RegisterForm represents customer registration request
|
||||
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"`
|
||||
Password string `json:"password" valid:"required,length(8|128)"`
|
||||
Mobile string `json:"mobile" valid:"required,length(10|15)"`
|
||||
CompanyID string `json:"company_id" valid:"required,uuid"`
|
||||
}
|
||||
|
||||
// LoginForm represents customer login request
|
||||
type LoginForm struct {
|
||||
Email string `json:"email" valid:"required,email"`
|
||||
Password string `json:"password" valid:"required"`
|
||||
}
|
||||
|
||||
// UpdateProfileForm represents profile update request
|
||||
type UpdateProfileForm struct {
|
||||
FirstName string `json:"first_name" valid:"required,alpha,length(2|50)"`
|
||||
LastName string `json:"last_name" valid:"required,alpha,length(2|50)"`
|
||||
Mobile string `json:"mobile" valid:"required,length(10|15)"`
|
||||
LastLoginAt *int64 `json:"last_login_at,omitempty" valid:"optional,unix_timestamp"`
|
||||
}
|
||||
|
||||
// CreateCustomerForm represents customer creation request
|
||||
type CreateCustomerForm 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"`
|
||||
Password string `json:"password" valid:"required,length(8|128)"`
|
||||
Mobile string `json:"mobile" valid:"required,length(10|15)"`
|
||||
CompanyID string `json:"company_id" valid:"required,uuid"`
|
||||
CreatedAt int64 `json:"created_at,omitempty" valid:"optional,unix_timestamp"`
|
||||
UpdatedAt int64 `json:"updated_at,omitempty" valid:"optional,unix_timestamp"`
|
||||
LastLoginAt *int64 `json:"last_login_at,omitempty" valid:"optional,unix_timestamp"`
|
||||
}
|
||||
|
||||
// Custom validation functions
|
||||
func init() {
|
||||
// Register custom validators
|
||||
govalidator.CustomTypeTagMap.Set("unix_timestamp", govalidator.CustomTypeValidator(func(i interface{}, context interface{}) bool {
|
||||
if unix, ok := i.(int64); ok {
|
||||
return timeutil.ValidateUnixTimestamp(unix)
|
||||
}
|
||||
return false
|
||||
}))
|
||||
}
|
||||
```
|
||||
|
||||
### Domain Entity Pattern
|
||||
```go
|
||||
// internal/customer/domain/entity/entity.go
|
||||
type Customer struct {
|
||||
ID uuid.UUID `bson:"_id"`
|
||||
Email string `bson:"email"`
|
||||
Password string `bson:"password"` // Never serialize
|
||||
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"`
|
||||
LastLoginAt *int64 `bson:"last_login_at,omitempty"` // Unix timestamp
|
||||
CreatedAt int64 `bson:"created_at"` // Unix timestamp
|
||||
UpdatedAt int64 `bson:"updated_at"` // Unix timestamp
|
||||
}
|
||||
```
|
||||
|
||||
### Aggregate Pattern
|
||||
```go
|
||||
// internal/customer/domain/aggregate/aggregate.go
|
||||
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"`
|
||||
LastLoginAt *int64 `json:"last_login_at,omitempty"` // Unix timestamp
|
||||
CreatedAt int64 `json:"created_at"` // Unix timestamp
|
||||
UpdatedAt int64 `json:"updated_at"` // Unix timestamp
|
||||
}
|
||||
|
||||
func NewCustomer(c entity.Customer) Customer {
|
||||
customer := Customer{
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
### Repository Implementation with Time Handling
|
||||
```go
|
||||
// internal/customer/domain/customer.go
|
||||
func (r *customerRepository) Create(ctx context.Context, customer *entity.Customer) error {
|
||||
// Set created/updated timestamps using Unix timestamps
|
||||
now := time.Now().Unix()
|
||||
customer.CreatedAt = now
|
||||
customer.UpdatedAt = now
|
||||
|
||||
// Insert customer
|
||||
_, err := r.collection.InsertOne(ctx, customer)
|
||||
if err != nil {
|
||||
if mongo.IsDuplicateKeyError(err) {
|
||||
return errors.New("customer already exists")
|
||||
}
|
||||
r.logger.Error("Failed to create customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"email": customer.Email,
|
||||
"customer_id": customer.ID.String(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
r.logger.Info("Customer created successfully", map[string]interface{}{
|
||||
"customer_id": customer.ID.String(),
|
||||
"email": customer.Email,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *customerRepository) Update(ctx context.Context, customer *entity.Customer) error {
|
||||
// Set updated timestamp using Unix timestamp
|
||||
customer.UpdatedAt = time.Now().Unix()
|
||||
|
||||
filter := bson.M{"_id": customer.ID}
|
||||
update := bson.M{"$set": customer}
|
||||
|
||||
result, err := r.collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update customer", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": customer.ID.String(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return errors.New("customer not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *customerRepository) UpdateLastLogin(ctx context.Context, id uuid.UUID) error {
|
||||
now := time.Now().Unix()
|
||||
|
||||
filter := bson.M{"_id": id}
|
||||
update := bson.M{
|
||||
"$set": bson.M{
|
||||
"last_login_at": now,
|
||||
"updated_at": now,
|
||||
},
|
||||
}
|
||||
|
||||
result, err := r.collection.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
r.logger.Error("Failed to update last login", map[string]interface{}{
|
||||
"error": err.Error(),
|
||||
"customer_id": id.String(),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
if result.MatchedCount == 0 {
|
||||
return errors.New("customer not found")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Repository Interface
|
||||
```go
|
||||
// internal/customer/domain/repository.go
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
### Service Implementation Template
|
||||
```go
|
||||
type CustomerService struct {
|
||||
customerRepo domain.Repository
|
||||
config *infra.AuthConfig
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewCustomerService(
|
||||
customerRepo domain.Repository,
|
||||
config *infra.AuthConfig,
|
||||
logger logger.Logger,
|
||||
) CustomerService {
|
||||
return CustomerService{
|
||||
customerRepo: customerRepo,
|
||||
config: config,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CustomerService) Register(ctx context.Context, req RegisterForm) (*aggregate.AuthorizationResponse, error) {
|
||||
s.logger.Info("Processing customer registration", map[string]interface{}{
|
||||
"email": req.Email,
|
||||
})
|
||||
|
||||
// Convert form to entity
|
||||
customer := &entity.Customer{
|
||||
ID: uuid.New(),
|
||||
FirstName: req.FirstName,
|
||||
LastName: req.LastName,
|
||||
Email: req.Email,
|
||||
Password: req.Password, // Will be hashed in service
|
||||
Mobile: req.Mobile,
|
||||
CompanyID: uuid.MustParse(req.CompanyID),
|
||||
IsActive: true,
|
||||
IsVerified: false,
|
||||
}
|
||||
|
||||
// Set timestamps
|
||||
s.setTimestamps(customer)
|
||||
|
||||
// Create customer
|
||||
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, err
|
||||
}
|
||||
|
||||
// Generate tokens and return response
|
||||
// ... token generation logic
|
||||
|
||||
return &aggregate.AuthorizationResponse{
|
||||
Customer: aggregate.NewCustomer(*customer),
|
||||
// ... token fields
|
||||
}, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Time handling helper functions - all using Unix timestamps
|
||||
func (s *CustomerService) setTimestamps(customer *entity.Customer) {
|
||||
now := timeutil.NowUnix()
|
||||
if customer.CreatedAt == 0 {
|
||||
customer.CreatedAt = now
|
||||
}
|
||||
customer.UpdatedAt = now
|
||||
}
|
||||
|
||||
func (s *CustomerService) setLastLogin(customer *entity.Customer) {
|
||||
now := timeutil.NowUnix()
|
||||
customer.LastLoginAt = &now
|
||||
customer.UpdatedAt = now
|
||||
}
|
||||
|
||||
func (s *CustomerService) validateTimestamps(customer *entity.Customer) error {
|
||||
if customer.CreatedAt != 0 && !timeutil.ValidateUnixTimestamp(customer.CreatedAt) {
|
||||
return errors.New("invalid created_at timestamp")
|
||||
}
|
||||
if customer.UpdatedAt != 0 && !timeutil.ValidateUnixTimestamp(customer.UpdatedAt) {
|
||||
return errors.New("invalid updated_at timestamp")
|
||||
}
|
||||
if customer.LastLoginAt != nil && !timeutil.ValidateUnixTimestamp(*customer.LastLoginAt) {
|
||||
return errors.New("invalid last_login_at timestamp")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Handler Implementation Template
|
||||
```go
|
||||
type CustomerHandler struct {
|
||||
service service.CustomerService
|
||||
logger logger.Logger
|
||||
}
|
||||
|
||||
func NewHandler(
|
||||
customerService service.CustomerService,
|
||||
logger logger.Logger,
|
||||
) *CustomerHandler {
|
||||
return &CustomerHandler{
|
||||
service: customerService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CustomerHandler) Register(c echo.Context) error {
|
||||
var req service.RegisterForm
|
||||
|
||||
// Bind request body
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return response.BadRequest(c, "Invalid request format", err.Error())
|
||||
}
|
||||
|
||||
// Validate request using govalidator
|
||||
if _, err := govalidator.ValidateStruct(&req); err != nil {
|
||||
return response.ValidationError(c, "Validation failed", err.Error())
|
||||
}
|
||||
|
||||
// Call service
|
||||
authResponse, err := h.service.Register(c.Request().Context(), req)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Registration failed")
|
||||
}
|
||||
|
||||
return response.Created(c, authResponse, "Customer registered successfully")
|
||||
}
|
||||
|
||||
func (h *CustomerHandler) UpdateProfile(c echo.Context) error {
|
||||
var req service.UpdateProfileForm
|
||||
|
||||
// Bind request body
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return response.BadRequest(c, "Invalid request format", err.Error())
|
||||
}
|
||||
|
||||
// Validate request using govalidator
|
||||
if _, err := govalidator.ValidateStruct(&req); err != nil {
|
||||
return response.ValidationError(c, "Validation failed", err.Error())
|
||||
}
|
||||
|
||||
// Call service
|
||||
customer, err := h.service.UpdateProfile(c.Request().Context(), req)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Profile update failed")
|
||||
}
|
||||
|
||||
return response.Success(c, customer, "Profile updated successfully")
|
||||
}
|
||||
|
||||
func (h *CustomerHandler) CreateWithTimestamp(c echo.Context) error {
|
||||
var req service.CreateCustomerForm
|
||||
|
||||
// Bind request body
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return response.BadRequest(c, "Invalid request format", err.Error())
|
||||
}
|
||||
|
||||
// Validate request using govalidator
|
||||
if _, err := govalidator.ValidateStruct(&req); err != nil {
|
||||
return response.ValidationError(c, "Validation failed", err.Error())
|
||||
}
|
||||
|
||||
// Call service
|
||||
customer, err := h.service.CreateCustomer(c.Request().Context(), req)
|
||||
if err != nil {
|
||||
return response.InternalServerError(c, "Customer creation failed")
|
||||
}
|
||||
|
||||
return response.Created(c, customer, "Customer created successfully")
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration Structure
|
||||
```go
|
||||
// infra/config.go
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
Database DatabaseConfig `mapstructure:"database"`
|
||||
Cache CacheConfig `mapstructure:"cache"`
|
||||
Queue QueueConfig `mapstructure:"queue"`
|
||||
Search SearchConfig `mapstructure:"search"`
|
||||
Auth AuthConfig `mapstructure:"auth"`
|
||||
AI AIConfig `mapstructure:"ai"`
|
||||
Scraping ScrapingConfig `mapstructure:"scraping"`
|
||||
Logging LoggingConfig `mapstructure:"logging"`
|
||||
RateLimit RateLimitConfig `mapstructure:"rate_limiting"`
|
||||
}
|
||||
```
|
||||
|
||||
### Application Bootstrap
|
||||
```go
|
||||
// cmd/web/main.go
|
||||
func main() {
|
||||
conf := intiConfig()
|
||||
|
||||
logger := initLogger(conf.Logging)
|
||||
defer logger.Sync()
|
||||
|
||||
logger.Info(
|
||||
"Starting Tender Management API Server",
|
||||
map[string]interface{}{
|
||||
"version": "1.0.0",
|
||||
"host": conf.Server.Host,
|
||||
"port": conf.Server.Port,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
## 🚀 Remember
|
||||
- Prioritize simplicity and readability over cleverness
|
||||
- Make code self-documenting through clear naming
|
||||
- Always consider the person who will maintain this code
|
||||
- Test your code thoroughly
|
||||
- Security and performance are not optional
|
||||
- Follow the established domain structure pattern
|
||||
- Use the custom logger interface consistently
|
||||
- Implement proper error handling and logging
|
||||
- Follow the repository pattern with embedded implementations
|
||||
- **Time Consistency**: Always use Unix timestamps (int64) for input, storage, and output
|
||||
- **Time Validation**: Validate all Unix timestamp inputs are within reasonable ranges
|
||||
- **Time Utilities**: Use centralized time utility functions for consistency across the application
|
||||
- **No Format Conversion**: Never convert between Unix timestamps and other time formats in the application
|
||||
- **Validation**: Always use govalidator for request validation with proper tags
|
||||
- **Form Design**: Define validation rules in request forms, not in handlers
|
||||
- **Custom Validators**: Register custom validators for complex validation rules
|
||||
Reference in New Issue
Block a user