# 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