Skip to main content

Contributing Guide

Thank you for your interest in contributing to the Posthoot backend! This guide will help you get started with the development process.

Table of Contents

Code of Conduct

This project adheres to the Contributor Covenant Code of Conduct. By participating, you are expected to uphold this code.

Getting Started

Prerequisites

Fork and Clone

  1. Fork the repository on GitHub
  2. Clone your fork locally:
git clone https://github.com/YOUR_USERNAME/posthoot-backend.git
cd posthoot-backend
  1. Add the upstream remote:
git remote add upstream https://github.com/posthoot/posthoot-backend.go.git

Development Setup

1. Environment Configuration

Create a development environment file:
cp .env.example .env.dev
Edit .env.dev with your development settings:
# Development Configuration
SERVER_HOST=localhost
SERVER_PORT=8080
PUBLIC_URL=http://localhost:8080

# Database Configuration
POSTGRES_HOST=localhost
POSTGRES_PORT=5432
POSTGRES_USER=posthoot_dev
POSTGRES_PASSWORD=dev_password
POSTGRES_DB=posthoot_dev
POSTGRES_SSLMODE=disable

# JWT Configuration
JWT_SECRET=dev_jwt_secret_key_for_development_only

# Redis Configuration
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
REDIS_DB=1

# Super Admin Configuration
SUPERADMIN_EMAIL=dev@example.com
SUPERADMIN_PASSWORD=dev_password
SUPERADMIN_NAME=Dev Admin

# Storage Configuration
STORAGE_PROVIDER=local
STORAGE_BASE_PATH=./storage/dev

# Worker Configuration
WORKER_CONCURRENCY=2
WORKER_QUEUE_SIZE=50

# Development Features
GIN_MODE=debug
LOG_LEVEL=debug

2. Database Setup

Create a development database:
# Connect to PostgreSQL
sudo -u postgres psql

# Create development database
CREATE DATABASE posthoot_dev;
CREATE USER posthoot_dev WITH PASSWORD 'dev_password';
GRANT ALL PRIVILEGES ON DATABASE posthoot_dev TO posthoot_dev;
\q

3. Install Dependencies

go mod download
go mod tidy

4. Run the Application

# Load development environment
export $(cat .env.dev | xargs)

# Run the server
go run cmd/server/main.go

5. Using Docker for Development

Create a docker-compose.dev.yml file:
version: '3.8'

services:
  posthoot-backend-dev:
    build: .
    ports:
      - "8080:8080"
    environment:
      - POSTGRES_HOST=postgres-dev
      - REDIS_HOST=redis-dev
    depends_on:
      - postgres-dev
      - redis-dev
    volumes:
      - .:/app
      - ./storage/dev:/app/storage
    command: go run cmd/server/main.go
    env_file:
      - .env.dev

  postgres-dev:
    image: postgres:14
    environment:
      - POSTGRES_DB=posthoot_dev
      - POSTGRES_USER=posthoot_dev
      - POSTGRES_PASSWORD=dev_password
    ports:
      - "5432:5432"
    volumes:
      - postgres_dev_data:/var/lib/postgresql/data

  redis-dev:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_dev_data:/data

volumes:
  postgres_dev_data:
  redis_dev_data:
Run the development environment:
docker-compose -f docker-compose.dev.yml up -d

Project Structure

📦 posthoot-backend
 ┣ 📂 cmd                     # Application entry points
 ┃ ┗ 📂 server               # Main server binary
 ┃   ┗ 📜 main.go           # Server entry point
 ┣ 📂 internal               # Private application code
 ┃ ┣ 📂 api                 # API layer
 ┃ ┃ ┣ 📂 middleware        # Custom middlewares
 ┃ ┃ ┃ ┣ 📜 auth.go        # Authentication middleware
 ┃ ┃ ┃ ┣ 📜 permissions.go  # Permission middleware
 ┃ ┃ ┃ ┗ 📜 ratelimit.go   # Rate limiting middleware
 ┃ ┃ ┣ 📂 validator         # Request validators
 ┃ ┃ ┣ 📂 controllers       # API controllers
 ┃ ┃ ┣ 📜 routes.go        # Route definitions
 ┃ ┃ ┗ 📜 server.go        # Server setup
 ┃ ┣ 📂 config              # Configuration management
 ┃ ┃ ┣ 📜 config.go        # Configuration structs
 ┃ ┃ ┗ 📜 loader.go        # Environment loading
 ┃ ┣ 📂 events              # Event bus system
 ┃ ┃ ┣ 📜 bus.go           # Event bus implementation
 ┃ ┃ ┗ 📜 handlers.go      # Event handlers
 ┃ ┣ 📂 handlers            # HTTP request handlers
 ┃ ┃ ┣ 📜 auth.go          # Authentication handlers
 ┃ ┃ ┣ 📜 campaigns.go     # Campaign handlers
 ┃ ┃ ┣ 📜 users.go         # User handlers
 ┃ ┃ ┗ 📜 webhooks.go      # Webhook handlers
 ┃ ┣ 📂 models              # Database models
 ┃ ┃ ┣ 📜 user.go          # User model
 ┃ ┃ ┣ 📜 campaign.go      # Campaign model
 ┃ ┃ ┣ 📜 team.go          # Team model
 ┃ ┃ ┣ 📜 permissions.go   # Permission model
 ┃ ┃ ┗ 📜 seed.go          # Database seeding
 ┃ ┣ 📂 routes              # Route definitions
 ┃ ┃ ┣ 📜 auth.go          # Auth routes
 ┃ ┃ ┣ 📜 campaigns.go     # Campaign routes
 ┃ ┃ ┗ 📜 api.go           # API route groups
 ┃ ┣ 📂 services            # Business logic layer
 ┃ ┃ ┣ 📜 email.go         # Email service
 ┃ ┃ ┣ 📜 auth.go          # Authentication service
 ┃ ┃ ┗ 📜 campaign.go      # Campaign service
 ┃ ┣ 📂 tasks               # Background tasks
 ┃ ┃ ┣ 📜 email.go         # Email sending tasks
 ┃ ┃ ┗ 📜 cleanup.go       # Cleanup tasks
 ┃ ┗ 📂 utils               # Utility functions
 ┃   ┣ 📜 logger.go        # Logging utilities
 ┃   ┣ 📜 redis.go         # Redis utilities
 ┃   ┗ 📜 validator.go     # Validation utilities
 ┣ 📂 migrations            # Database migrations
 ┣ 📂 storage               # Local storage
 ┣ 📂 docs                  # Documentation
 ┣ 📂 scripts               # Build and deployment scripts
 ┣ 📜 .env.example         # Environment template
 ┣ 📜 .gitignore           # Git ignore rules
 ┣ 📜 Dockerfile           # Docker configuration
 ┣ 📜 docker-compose.yml   # Docker Compose config
 ┣ 📜 go.mod               # Go module definition
 ┣ 📜 go.sum               # Go module checksums
 ┣ 📜 Makefile             # Build automation
 ┗ 📜 README.md            # Project documentation

Development Workflow

1. Branch Strategy

We use a feature branch workflow:
# Create a new feature branch
git checkout -b feature/your-feature-name

# Or for bug fixes
git checkout -b fix/your-bug-description

# Or for documentation
git checkout -b docs/your-doc-update

2. Making Changes

  1. Create a feature branch from main
  2. Make your changes following the coding standards
  3. Write tests for new functionality
  4. Update documentation as needed
  5. Commit your changes with clear messages

3. Commit Message Format

Use conventional commit format:
type(scope): description

[optional body]

[optional footer]
Examples:
feat(auth): add Google OAuth support
fix(api): resolve rate limiting issue
docs(readme): update installation instructions
test(handlers): add unit tests for user creation
refactor(models): simplify permission structure

4. Keeping Your Branch Updated

# Fetch latest changes
git fetch upstream

# Rebase your branch on main
git rebase upstream/main

Coding Standards

1. Go Code Style

Follow the Go Code Review Comments:
  • Use gofmt for code formatting
  • Follow naming conventions
  • Use meaningful variable names
  • Keep functions small and focused
  • Add comments for exported functions

2. Code Organization

  • Package structure: Follow standard Go package layout
  • File naming: Use descriptive names (e.g., user_handler.go)
  • Function organization: Group related functions together
  • Import organization: Use standard library, third-party, then local imports

3. Error Handling

// Good: Handle errors explicitly
if err != nil {
    return fmt.Errorf("failed to create user: %w", err)
}

// Good: Use custom error types for specific errors
var ErrUserNotFound = errors.New("user not found")

// Good: Log errors with context
log.Error("failed to process email", "error", err, "user_id", userID)

4. Database Operations

// Good: Use transactions for related operations
func (s *Service) CreateUserWithTeam(ctx context.Context, user *User, team *Team) error {
    return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
        if err := tx.Create(team).Error; err != nil {
            return err
        }
        user.TeamID = team.ID
        return tx.Create(user).Error
    })
}

// Good: Use proper indexing
// Add indexes in migrations for frequently queried columns

5. API Design

// Good: Consistent response structure
type APIResponse struct {
    Success bool        `json:"success"`
    Data    interface{} `json:"data,omitempty"`
    Error   string      `json:"error,omitempty"`
    Meta    *Meta       `json:"meta,omitempty"`
}

// Good: Use proper HTTP status codes
func (h *Handler) CreateUser(c echo.Context) error {
    user := &models.User{}
    if err := c.Bind(user); err != nil {
        return echo.NewHTTPError(http.StatusBadRequest, "invalid request body")
    }
    
    if err := h.service.CreateUser(c.Request().Context(), user); err != nil {
        return echo.NewHTTPError(http.StatusInternalServerError, "failed to create user")
    }
    
    return c.JSON(http.StatusCreated, APIResponse{
        Success: true,
        Data:    user,
    })
}

Testing

1. Unit Tests

Create tests for all new functionality:
// handlers/user_test.go
package handlers

import (
    "testing"
    "net/http"
    "net/http/httptest"
    "strings"
    
    "github.com/labstack/echo/v4"
    "github.com/stretchr/testify/assert"
)

func TestCreateUser(t *testing.T) {
    // Setup
    e := echo.New()
    req := httptest.NewRequest(http.MethodPost, "/users", strings.NewReader(`{
        "email": "test@example.com",
        "password": "password123",
        "first_name": "Test",
        "last_name": "User"
    }`))
    req.Header.Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
    rec := httptest.NewRecorder()
    c := e.NewContext(req, rec)
    
    // Test
    handler := &UserHandler{}
    err := handler.CreateUser(c)
    
    // Assertions
    assert.NoError(t, err)
    assert.Equal(t, http.StatusCreated, rec.Code)
}

2. Integration Tests

Test API endpoints with a real database:
// integration/user_test.go
package integration

import (
    "testing"
    "net/http"
    
    "github.com/stretchr/testify/suite"
)

type UserTestSuite struct {
    suite.Suite
    app *TestApp
}

func (suite *UserTestSuite) SetupSuite() {
    suite.app = NewTestApp()
}

func (suite *UserTestSuite) TearDownSuite() {
    suite.app.Cleanup()
}

func (suite *UserTestSuite) TestUserRegistration() {
    // Test user registration flow
    resp, err := suite.app.Post("/api/v1/auth/register", map[string]interface{}{
        "email": "test@example.com",
        "password": "password123",
        "first_name": "Test",
        "last_name": "User",
    })
    
    suite.NoError(err)
    suite.Equal(http.StatusCreated, resp.Code)
}

func TestUserTestSuite(t *testing.T) {
    suite.Run(t, new(UserTestSuite))
}

3. Running Tests

# Run all tests
go test ./...

# Run tests with coverage
go test -cover ./...

# Run tests with verbose output
go test -v ./...

# Run specific test
go test -v ./internal/handlers -run TestCreateUser

# Run integration tests
go test -v ./integration/...

4. Test Database

Use a separate test database:
// testutils/db.go
package testutils

import (
    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

func NewTestDB() *gorm.DB {
    dsn := "host=localhost user=posthoot_test password=test_password dbname=posthoot_test port=5432 sslmode=disable"
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{})
    if err != nil {
        panic(err)
    }
    
    // Run migrations
    // AutoMigrate(&models.User{}, &models.Team{}, ...)
    
    return db
}

Documentation

1. Code Documentation

Document all exported functions and types:
// User represents a user in the system
type User struct {
    ID        uint      `json:"id" gorm:"primaryKey"`
    Email     string    `json:"email" gorm:"uniqueIndex;not null"`
    Password  string    `json:"-" gorm:"not null"`
    FirstName string    `json:"first_name" gorm:"not null"`
    LastName  string    `json:"last_name" gorm:"not null"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
}

// CreateUser creates a new user in the database
// It validates the user data and hashes the password before saving
func (s *UserService) CreateUser(ctx context.Context, user *User) error {
    // Implementation...
}

2. API Documentation

Update Swagger documentation for new endpoints:
// @Summary Create a new user
// @Description Create a new user account with the provided information
// @Tags users
// @Accept json
// @Produce json
// @Param user body CreateUserRequest true "User information"
// @Success 201 {object} APIResponse{data=User}
// @Failure 400 {object} APIResponse{error=string}
// @Failure 500 {object} APIResponse{error=string}
// @Router /api/v1/users [post]
func (h *UserHandler) CreateUser(c echo.Context) error {
    // Implementation...
}

3. README Updates

Update relevant documentation when adding new features:
  • README.md: Update feature list and installation instructions
  • API Documentation: Add new endpoint documentation
  • Self-hosting guide: Update configuration options

Submitting Changes

1. Pre-submission Checklist

Before submitting a pull request, ensure:
  • Code compiles without errors
  • Tests pass (unit and integration)
  • Code is formatted (gofmt -w .)
  • Linting passes (golangci-lint run)
  • Documentation is updated
  • Commit messages follow conventional format
  • Branch is up to date with main

2. Creating a Pull Request

  1. Push your branch to your fork:
git push origin feature/your-feature-name
  1. Create a pull request on GitHub with:
    • Clear title describing the change
    • Detailed description of what was changed
    • Reference to any related issues
    • Screenshots (if UI changes)
  2. Template for PR description:
## Description
Brief description of the changes made.

## Type of Change
- [ ] Bug fix
- [ ] New feature
- [ ] Breaking change
- [ ] Documentation update

## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing completed

## Checklist
- [ ] Code follows style guidelines
- [ ] Self-review completed
- [ ] Documentation updated
- [ ] No breaking changes (or documented)

## Related Issues
Closes #123

3. Code Review Process

  1. Automated checks will run on your PR
  2. Reviewers will be assigned automatically
  3. Address feedback by pushing additional commits
  4. Maintainers will merge when approved

Release Process

1. Version Management

We use semantic versioning (SemVer):
  • MAJOR.MINOR.PATCH (e.g., 1.2.3)
  • MAJOR: Breaking changes
  • MINOR: New features (backward compatible)
  • PATCH: Bug fixes (backward compatible)

2. Release Checklist

Before a release:
  • All tests pass
  • Documentation is updated
  • Changelog is updated
  • Version is bumped
  • Release notes are prepared

3. Creating a Release

  1. Create a release branch:
git checkout -b release/v1.2.0
  1. Update version in relevant files
  2. Update changelog with new features/fixes
  3. Create a PR for the release
  4. Tag the release after merge:
git tag v1.2.0
git push origin v1.2.0

Getting Help

1. Questions and Discussions

  • GitHub Discussions: For general questions and feature discussions
  • GitHub Issues: For bug reports and feature requests
  • Discord: For real-time discussions (if available)

2. Issue Templates

Use the appropriate issue template:
  • Bug Report: For reporting bugs
  • Feature Request: For suggesting new features
  • Documentation: For documentation improvements

3. Community Guidelines

  • Be respectful and inclusive
  • Provide context when asking questions
  • Help others when you can
  • Follow the code of conduct

Recognition

Contributors are recognized in several ways:
  • Contributors list on GitHub
  • Release notes for significant contributions
  • Special thanks in documentation
  • Contributor badges for regular contributors
Thank you for contributing to the Posthoot backend project! Your contributions help make this project better for everyone.