veza/veza-backend-api/CONTRIBUTING.md
2025-12-25 11:06:54 +01:00

858 lines
19 KiB
Markdown

# Contributing to Veza Backend API
Thank you for your interest in contributing to Veza Backend API! This guide will help you get started and ensure your contributions align with our standards.
## Table of Contents
1. [Code of Conduct](#code-of-conduct)
2. [Getting Started](#getting-started)
3. [Development Workflow](#development-workflow)
4. [Code Standards](#code-standards)
5. [Testing](#testing)
6. [Documentation](#documentation)
7. [Pull Requests](#pull-requests)
8. [Code Review Process](#code-review-process)
9. [Commit Guidelines](#commit-guidelines)
10. [Project Structure](#project-structure)
11. [Common Tasks](#common-tasks)
12. [Getting Help](#getting-help)
## Code of Conduct
### Our Principles
1. **Stability over Velocity**: Every change improves robustness (tests, types, lint, docs)
2. **Unidirectional Flow**: Need → Design → Implementation → Test → Doc → PR → Review → Merge
3. **Full Traceability**: All decisions = ADR (Architecture Decision Record), atomic commits with Conventional Commits
4. **Integrated Security**: No secret leaks, mandatory secret scanners, verified dependencies
5. **Controlled Autonomy**: AI agents work in background, always via PR + checklists, mandatory human review
### Expected Behavior
- Be respectful and inclusive
- Welcome newcomers and help them learn
- Focus on constructive feedback
- Respect different viewpoints and experiences
### Unacceptable Behavior
- Harassment or discriminatory language
- Personal attacks
- Trolling or inflammatory comments
- Publishing others' private information
## Getting Started
### Prerequisites
- **Go** 1.23 or higher
- **PostgreSQL** 12+ (for database)
- **Redis** (optional, for caching and rate limiting)
- **Docker** (optional, for containerized development)
- **Git** 2.30+
### Initial Setup
1. **Fork and Clone**
```bash
# Fork the repository on GitHub
git clone https://github.com/your-username/veza-backend-api.git
cd veza-backend-api
```
2. **Add Upstream Remote**
```bash
git remote add upstream https://github.com/veza/veza-backend-api.git
```
3. **Install Dependencies**
```bash
make deps
# or manually
go mod download
go mod tidy
```
4. **Set Up Environment**
```bash
# Copy example environment file
cp .env.example .env
# Edit .env with your configuration
# Minimum required:
# - DATABASE_URL
# - JWT_SECRET (min 32 characters)
# - APP_ENV
```
5. **Verify Installation**
```bash
# Build the application
make build
# Run tests
make test
# Check code quality
make lint
make vet
```
## Development Workflow
### Branching Model
We follow a feature branch workflow:
- `main`: Always stable, always deployable
- `develop`: Integration branch (optional)
- Feature branches: `feat/<feature-name>`
- Bug fix branches: `fix/<bug-description>`
- Documentation branches: `docs/<description>`
- Refactoring branches: `refactor/<description>`
### Creating a Branch
```bash
# Update main branch
git checkout main
git pull upstream main
# Create feature branch
git checkout -b feat/your-feature-name
# Or use the shorthand
git checkout -b feat/your-feature-name upstream/main
```
### Branch Naming Conventions
- `feat/`: New features
- `fix/`: Bug fixes
- `docs/`: Documentation changes
- `refactor/`: Code refactoring
- `test/`: Test additions or changes
- `chore/`: Maintenance tasks
- `perf/`: Performance improvements
Examples:
- `feat/auth-refresh-tokens`
- `fix/jwt-uuid-mismatch`
- `refactor/user-service-cleanup`
- `docs/api-documentation-update`
## Code Standards
### Go Code Style
#### Formatting
- Use `gofmt` (automatically applied)
- Use `goimports` for import organization
- Run `make format` before committing
```bash
# Format code
make format
# Or manually
go fmt ./...
goimports -w .
```
#### Naming Conventions
- **Packages**: lowercase, single word, no underscores
- **Exported functions/types**: PascalCase
- **Unexported functions/types**: camelCase
- **Constants**: PascalCase for exported, camelCase for unexported
- **Interfaces**: PascalCase, often end with `-er` (e.g., `Reader`, `Writer`)
```go
// Good
package user
type UserService struct {
repo UserRepository
}
func (s *UserService) GetByID(id uuid.UUID) (*User, error) {
// ...
}
// Bad
package user_service
type user_service struct {
repo user_repository
}
func (s *user_service) get_by_id(id uuid.UUID) (*user, error) {
// ...
}
```
#### Code Organization
- Keep functions small and focused (single responsibility)
- Prefer composition over inheritance
- Use interfaces for abstraction
- Avoid deep nesting (max 3-4 levels)
- Return errors explicitly, don't ignore them
```go
// Good
func (s *UserService) GetByID(ctx context.Context, id uuid.UUID) (*User, error) {
if id == uuid.Nil {
return nil, errors.New("user ID cannot be nil")
}
user, err := s.repo.FindByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("failed to get user: %w", err)
}
return user, nil
}
// Bad
func (s *UserService) GetByID(ctx context.Context, id uuid.UUID) (*User, error) {
user, _ := s.repo.FindByID(ctx, id) // Error ignored!
return user, nil
}
```
#### Error Handling
- Always handle errors explicitly
- Wrap errors with context using `fmt.Errorf` with `%w`
- Use sentinel errors for expected error conditions
- Return errors, don't log them (let the caller decide)
```go
// Good
var ErrUserNotFound = errors.New("user not found")
func (s *UserService) GetByID(ctx context.Context, id uuid.UUID) (*User, error) {
user, err := s.repo.FindByID(ctx, id)
if err != nil {
if errors.Is(err, ErrUserNotFound) {
return nil, ErrUserNotFound
}
return nil, fmt.Errorf("failed to get user %s: %w", id, err)
}
return user, nil
}
// Bad
func (s *UserService) GetByID(ctx context.Context, id uuid.UUID) (*User, error) {
user, err := s.repo.FindByID(ctx, id)
if err != nil {
log.Printf("Error: %v", err) // Don't log here!
return nil, err
}
return user, nil
}
```
#### Context Usage
- Always accept `context.Context` as the first parameter
- Use context for cancellation, timeouts, and request-scoped values
- Don't store contexts in structs
```go
// Good
func (s *UserService) GetByID(ctx context.Context, id uuid.UUID) (*User, error) {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
user, err := s.repo.FindByID(ctx, id)
// ...
}
// Bad
type UserService struct {
ctx context.Context // Don't store context!
}
```
#### Comments and Documentation
- Document all exported functions, types, and constants
- Use complete sentences
- Start with the name of the thing being described
- Use `//` for comments, `/* */` for block comments (rarely)
```go
// UserService provides operations for managing users.
type UserService struct {
repo UserRepository
}
// GetByID retrieves a user by their unique identifier.
// It returns ErrUserNotFound if the user does not exist.
func (s *UserService) GetByID(ctx context.Context, id uuid.UUID) (*User, error) {
// ...
}
```
### Linting and Code Quality
#### Required Tools
- **golangci-lint**: Comprehensive Go linter
- **go vet**: Built-in Go static analysis
- **goimports**: Import organization
```bash
# Install golangci-lint
go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
# Run linters
make lint
make vet
```
#### Common Issues to Avoid
1. **Unused imports**: Use `goimports` to clean up
2. **Unused variables**: Remove or use `_` to explicitly ignore
3. **Shadowed variables**: Use different names
4. **Error handling**: Always check and handle errors
5. **Race conditions**: Use proper synchronization
### Project Structure
```
veza-backend-api/
├── cmd/ # Application entry points
│ ├── modern-server/ # Main server
│ └── tools/ # Utility tools
├── internal/ # Private application code
│ ├── handlers/ # HTTP handlers
│ ├── services/ # Business logic
│ ├── repositories/ # Data access layer
│ ├── models/ # Data models
│ ├── dto/ # Data transfer objects
│ ├── middleware/ # HTTP middleware
│ ├── validators/ # Input validation
│ ├── errors/ # Error definitions
│ └── config/ # Configuration
├── pkg/ # Public library code
├── migrations/ # Database migrations
├── tests/ # Integration tests
├── docs/ # Documentation
├── scripts/ # Utility scripts
└── Makefile # Build automation
```
### Package Organization
- **`internal/`**: Private code, not importable by other projects
- **`pkg/`**: Public library code, importable by other projects
- **`cmd/`**: Application entry points
- Keep packages focused and cohesive
- Avoid circular dependencies
## Testing
### Test Requirements
- **All new features** must include tests
- **Bug fixes** must include regression tests
- **Test coverage** should be ≥ 80% for new code
- **Tests must pass** before submitting PR
### Test Structure
```go
package user_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestUserService_GetByID(t *testing.T) {
// Arrange
ctx := context.Background()
service := setupUserService(t)
user := createTestUser(t, ctx)
// Act
result, err := service.GetByID(ctx, user.ID)
// Assert
require.NoError(t, err)
assert.Equal(t, user.ID, result.ID)
assert.Equal(t, user.Email, result.Email)
}
```
### Test Types
1. **Unit Tests**: Test individual functions/methods
```bash
go test ./internal/services/...
```
2. **Integration Tests**: Test with real dependencies
```bash
go test -tags=integration ./tests/integration/...
```
3. **Race Detection**: Detect race conditions
```bash
go test -race ./...
```
### Test Best Practices
- Use table-driven tests for multiple scenarios
- Use `testify/assert` and `testify/require`
- Clean up test data (use `t.Cleanup()`)
- Use test helpers for common setup
- Mock external dependencies
```go
func TestUserService_GetByID_TableDriven(t *testing.T) {
tests := []struct {
name string
userID uuid.UUID
wantErr bool
errType error
}{
{
name: "valid user",
userID: validUserID,
wantErr: false,
},
{
name: "user not found",
userID: uuid.New(),
wantErr: true,
errType: ErrUserNotFound,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test implementation
})
}
}
```
### Running Tests
```bash
# Run all tests
make test
# Run tests with coverage
make test-coverage
# Run tests with race detection
make test-race
# Run specific test
go test -v ./internal/services/user_service_test.go
# Run tests matching pattern
go test -v -run TestUserService
```
## Documentation
### Code Documentation
- Document all exported functions, types, and constants
- Use Go doc comments (start with the name)
- Include examples for complex functions
- Document error conditions
```go
// User represents a user in the system.
//
// Example:
// user := &User{
// ID: uuid.New(),
// Email: "user@example.com",
// }
type User struct {
ID uuid.UUID
Email string
}
// GetByID retrieves a user by ID.
//
// Returns ErrUserNotFound if the user does not exist.
func (s *UserService) GetByID(ctx context.Context, id uuid.UUID) (*User, error) {
// ...
}
```
### API Documentation
- Update Swagger/OpenAPI documentation for API changes
- Include request/response examples
- Document error responses
- Document authentication requirements
### README Updates
- Update README for significant changes
- Document new environment variables
- Update installation instructions if needed
- Add migration notes for breaking changes
## Pull Requests
### Before Creating a PR
1. **Update your branch**
```bash
git checkout main
git pull upstream main
git checkout feat/your-feature
git rebase main
```
2. **Run quality checks**
```bash
make format
make lint
make vet
make test
```
3. **Verify your changes**
- All tests pass
- Code compiles without errors
- No linting errors
- Documentation updated
### Creating a PR
1. **Push your branch**
```bash
git push origin feat/your-feature
```
2. **Create PR on GitHub**
- Use the PR template
- Fill in all sections
- Link related issues
- Add reviewers
3. **PR Title Format**
```
[CATEGORY] Brief description
Examples:
[FEAT] Add user profile endpoint
[FIX] Correct JWT token validation
[DOCS] Update API documentation
```
### PR Description Template
```markdown
## Description
Brief description of changes
## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update
## Related Issues
Closes #123
## Changes Made
- Change 1
- Change 2
## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing performed
## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Comments added for complex code
- [ ] Documentation updated
- [ ] No new warnings generated
- [ ] Tests added and passing
- [ ] Breaking changes documented
```
### PR Best Practices
- Keep PRs focused and small (< 500 lines ideally)
- One logical change per PR
- Include tests for new features
- Update documentation
- Respond to review comments promptly
- Rebase on main if conflicts arise
## Code Review Process
### Review Criteria
1. **Functionality**: Does it work as intended?
2. **Code Quality**: Follows style guidelines?
3. **Tests**: Adequate test coverage?
4. **Documentation**: Properly documented?
5. **Performance**: No obvious performance issues?
6. **Security**: No security vulnerabilities?
### Review Guidelines
- Be constructive and respectful
- Focus on code, not the person
- Explain the "why" behind suggestions
- Approve when satisfied
- Request changes when needed
### Responding to Reviews
- Address all comments
- Ask for clarification if needed
- Make requested changes
- Re-request review when ready
- Thank reviewers for their time
## Commit Guidelines
### Conventional Commits
We follow [Conventional Commits](https://www.conventionalcommits.org/) specification:
```
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]
```
### Commit Types
- `feat`: New feature
- `fix`: Bug fix
- `docs`: Documentation changes
- `style`: Code style changes (formatting, etc.)
- `refactor`: Code refactoring
- `test`: Test additions/changes
- `chore`: Maintenance tasks
- `perf`: Performance improvements
- `ci`: CI/CD changes
### Commit Examples
```bash
feat(auth): add refresh token endpoint
fix(user): correct email validation regex
docs(api): update authentication documentation
refactor(service): simplify user service logic
test(user): add tests for user creation
```
### Commit Best Practices
- Write clear, descriptive commit messages
- Use present tense ("add" not "added")
- Keep commits atomic (one logical change)
- Reference issues in commit message
- Don't commit generated files
```bash
# Good
feat(auth): add refresh token endpoint
Implements refresh token generation and validation.
Adds /api/v1/auth/refresh endpoint.
Closes #123
# Bad
fix stuff
```
## Common Tasks
### Adding a New Endpoint
1. **Define DTOs** (`internal/dto/`)
```go
type CreateUserRequest struct {
Email string `json:"email" binding:"required,email"`
Username string `json:"username" binding:"required,min=3"`
}
```
2. **Create Handler** (`internal/handlers/`)
```go
func (h *UserHandler) CreateUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
RespondWithError(c, http.StatusBadRequest, err)
return
}
// ...
}
```
3. **Add Service Logic** (`internal/services/`)
```go
func (s *UserService) CreateUser(ctx context.Context, req *CreateUserRequest) (*User, error) {
// Business logic
}
```
4. **Register Route** (`cmd/modern-server/main.go` or router)
```go
api.POST("/users", userHandler.CreateUser)
```
5. **Add Tests**
```go
func TestUserHandler_CreateUser(t *testing.T) {
// Test implementation
}
```
6. **Update Documentation**
- Update Swagger annotations
- Update API documentation
### Adding a New Service
1. **Define Interface** (`internal/services/`)
```go
type ProductService interface {
CreateProduct(ctx context.Context, req *CreateProductRequest) (*Product, error)
GetProduct(ctx context.Context, id uuid.UUID) (*Product, error)
}
```
2. **Implement Service**
```go
type productService struct {
repo ProductRepository
}
func NewProductService(repo ProductRepository) ProductService {
return &productService{repo: repo}
}
```
3. **Add Tests**
```go
func TestProductService_CreateProduct(t *testing.T) {
// Test implementation
}
```
### Adding Database Migrations
1. **Create Migration File**
```bash
migrate create -ext sql -dir migrations -seq add_products_table
```
2. **Write Migration** (`migrations/XXXXXX_add_products_table.up.sql`)
```sql
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
3. **Write Rollback** (`migrations/XXXXXX_add_products_table.down.sql`)
```sql
DROP TABLE products;
```
4. **Test Migration**
```bash
migrate -path ./migrations -database "$DATABASE_URL" up
migrate -path ./migrations -database "$DATABASE_URL" down
```
## Getting Help
### Resources
- **Documentation**: See `docs/` directory
- **API Documentation**: `/swagger/index.html` when server is running
- **Architecture Guide**: `docs/ARCHITECTURE.md`
- **Development Guide**: `docs/DEVELOPMENT_SETUP_GUIDE.md`
- **Troubleshooting**: `docs/TROUBLESHOOTING_GUIDE.md`
### Communication Channels
- **GitHub Issues**: For bug reports and feature requests
- **GitHub Discussions**: For questions and discussions
- **Pull Requests**: For code-related questions
### Asking Questions
When asking for help, include:
1. **What you're trying to do**
2. **What you've tried**
3. **Error messages** (if any)
4. **Relevant code snippets**
5. **Environment details** (OS, Go version, etc.)
### Reporting Bugs
Use the bug report template:
```markdown
## Description
Brief description of the bug
## Steps to Reproduce
1. Step 1
2. Step 2
3. Step 3
## Expected Behavior
What should happen
## Actual Behavior
What actually happens
## Environment
- OS: [e.g., Linux, macOS, Windows]
- Go Version: [e.g., 1.23.0]
- Application Version: [e.g., 1.2.0]
## Additional Context
Any other relevant information
```
## Recognition
Contributors are recognized in:
- **README**: Contributors section
- **CHANGELOG**: Release notes
- **GitHub**: Contributors page
Thank you for contributing to Veza Backend API! 🚀