260 lines
6.6 KiB
Markdown
260 lines
6.6 KiB
Markdown
# Code Guidelines for WhatsHooked
|
|
|
|
## General Principles
|
|
|
|
- Write clean, idiomatic Go code following standard conventions
|
|
- Use meaningful variable and function names
|
|
- Keep functions small and focused on a single responsibility
|
|
- Document exported functions, types, and packages
|
|
- Handle errors explicitly, never ignore them
|
|
- Use context.Context for cancellation and timeout handling
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
whatshooked/
|
|
├── cmd/ # Command-line entry points
|
|
│ ├── server/ # HTTP server command
|
|
│ └── cli/ # CLI tools
|
|
├── pkg/ # Public packages
|
|
│ ├── auth/ # Authentication & authorization
|
|
│ ├── api/ # API endpoints
|
|
│ ├── cache/ # Caching layer
|
|
│ ├── config/ # Configuration management
|
|
│ ├── events/ # Event system
|
|
│ ├── eventlogger/ # Event logging
|
|
│ ├── handlers/ # HTTP handlers
|
|
│ ├── hooks/ # Webhook management
|
|
│ ├── logging/ # Structured logging
|
|
│ ├── storage/ # Database storage layer
|
|
│ ├── utils/ # Utility functions
|
|
│ ├── webserver/ # Web server with ResolveSpec
|
|
│ ├── whatsapp/ # WhatsApp integration
|
|
│ └── whatshooked/ # Core application logic
|
|
└── tooldoc/ # Tool & library documentation
|
|
```
|
|
|
|
## Naming Conventions
|
|
|
|
### Packages
|
|
- Use lowercase, single-word package names
|
|
- Use descriptive names that reflect the package's purpose
|
|
- Avoid generic names like `util` or `common` (use `utils` with specific subdirectories if needed)
|
|
|
|
### Files
|
|
- Use snake_case for file names: `user_service.go`, `auth_middleware.go`
|
|
- Test files: `user_service_test.go`
|
|
- Keep related functionality in the same file
|
|
|
|
### Variables & Functions
|
|
- Use camelCase for private: `userService`, `handleRequest`
|
|
- Use PascalCase for exported: `UserService`, `HandleRequest`
|
|
- Use descriptive names: prefer `userRepository` over `ur`
|
|
- Boolean variables should be prefixed: `isValid`, `hasPermission`, `canAccess`
|
|
|
|
### Constants
|
|
- Use PascalCase for exported: `DefaultTimeout`
|
|
- Use camelCase for private: `defaultTimeout`
|
|
- Group related constants together
|
|
|
|
### Interfaces
|
|
- Name interfaces with -er suffix when appropriate: `Reader`, `Writer`, `Handler`
|
|
- Use descriptive names: `UserRepository`, `AuthService`
|
|
|
|
## Error Handling
|
|
|
|
```go
|
|
// Wrap errors with context
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load user: %w", err)
|
|
}
|
|
|
|
// Check specific errors
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, ErrUserNotFound
|
|
}
|
|
|
|
// Custom error types
|
|
type ValidationError struct {
|
|
Field string
|
|
Message string
|
|
}
|
|
|
|
func (e *ValidationError) Error() string {
|
|
return fmt.Sprintf("%s: %s", e.Field, e.Message)
|
|
}
|
|
```
|
|
|
|
## Logging
|
|
|
|
Use zerolog for structured logging:
|
|
|
|
```go
|
|
import "github.com/rs/zerolog/log"
|
|
|
|
log.Info().
|
|
Str("user_id", userID).
|
|
Msg("User logged in")
|
|
|
|
log.Error().
|
|
Err(err).
|
|
Str("operation", "database_query").
|
|
Msg("Failed to query database")
|
|
```
|
|
|
|
## Context Usage
|
|
|
|
Always pass context as the first parameter:
|
|
|
|
```go
|
|
func ProcessRequest(ctx context.Context, userID string) error {
|
|
// Check for cancellation
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
default:
|
|
}
|
|
|
|
// Pass context to downstream calls
|
|
user, err := userRepo.GetByID(ctx, userID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
```
|
|
|
|
## Testing
|
|
|
|
- Write table-driven tests
|
|
- Use descriptive test names: `TestUserService_Create_WithValidData_Success`
|
|
- Mock external dependencies
|
|
- Aim for high coverage of business logic
|
|
- Use t.Run for subtests
|
|
|
|
```go
|
|
func TestUserService_Create(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input *User
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "valid user",
|
|
input: &User{Name: "John", Email: "john@example.com"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "missing email",
|
|
input: &User{Name: "John"},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
service := NewUserService()
|
|
err := service.Create(tt.input)
|
|
if (err != nil) != tt.wantErr {
|
|
t.Errorf("Create() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
```
|
|
|
|
## Database Operations
|
|
|
|
- Use transactions for multiple related operations
|
|
- Always use prepared statements
|
|
- Handle NULL values properly
|
|
- Use meaningful struct tags
|
|
|
|
```go
|
|
type User struct {
|
|
ID string `gorm:"primaryKey" json:"id"`
|
|
Name string `gorm:"not null" json:"name"`
|
|
Email string `gorm:"uniqueIndex;not null" json:"email"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
```
|
|
|
|
## ResolveSpec Integration
|
|
|
|
When using ResolveSpec:
|
|
- Register all models in the registry
|
|
- Use schema.table format: "public.users", "core.accounts"
|
|
- Implement hooks for auth and validation
|
|
- Use lifecycle hooks for audit logging
|
|
|
|
## API Design
|
|
|
|
- Use RESTful conventions
|
|
- Return appropriate HTTP status codes
|
|
- Include meaningful error messages
|
|
- Version your APIs: `/api/v1/users`
|
|
- Document all endpoints
|
|
|
|
## Security
|
|
|
|
- Never log sensitive data (passwords, tokens, etc.)
|
|
- Validate all input
|
|
- Use parameterized queries
|
|
- Implement rate limiting
|
|
- Use HTTPS in production
|
|
- Sanitize user input
|
|
- Implement proper CORS policies
|
|
|
|
## Dependencies
|
|
|
|
- Keep dependencies minimal
|
|
- Pin versions in go.mod
|
|
- Regularly update dependencies
|
|
- Document why each dependency is needed
|
|
|
|
## Documentation
|
|
|
|
- Document all exported functions, types, and packages
|
|
- Use godoc format
|
|
- Include examples in documentation
|
|
- Keep README.md up to date
|
|
- Document configuration options
|
|
|
|
## Comments
|
|
|
|
```go
|
|
// Package auth provides authentication and authorization functionality.
|
|
package auth
|
|
|
|
// User represents a system user.
|
|
type User struct {
|
|
ID string
|
|
}
|
|
|
|
// Authenticate verifies user credentials and returns a token.
|
|
// Returns ErrInvalidCredentials if authentication fails.
|
|
func Authenticate(ctx context.Context, username, password string) (string, error) {
|
|
// Implementation
|
|
}
|
|
```
|
|
|
|
## Performance
|
|
|
|
- Use connection pools for databases
|
|
- Implement caching where appropriate
|
|
- Avoid N+1 queries
|
|
- Use batch operations when possible
|
|
- Profile critical paths
|
|
- Set appropriate timeouts
|
|
|
|
## Git Workflow
|
|
|
|
- Write clear commit messages
|
|
- Keep commits atomic and focused
|
|
- Use conventional commits format
|
|
- Create feature branches
|
|
- Run tests before committing
|
|
- Review your own changes before pushing
|