Skip to content

Contributing Guide

Last Updated: 2025-11-23 Difficulty: Beginner to Intermediate

Welcome to the ocmonica project! This guide will help you contribute effectively, whether you're fixing a bug, adding a feature, or improving documentation.


Table of Contents


Code of Conduct

Our Standards

  • Be respectful and considerate in all interactions
  • Be collaborative and help others learn
  • Be professional in code reviews and discussions
  • Be patient with newcomers and questions
  • Be constructive when providing feedback

Our Responsibilities

Project maintainers are responsible for:

  • Clarifying standards of acceptable behavior
  • Taking corrective action in response to inappropriate behavior
  • Removing, editing, or rejecting contributions that don't align with this Code of Conduct

Reporting Issues

If you experience or witness unacceptable behavior, please report it by opening an issue or contacting the maintainers directly.


Getting Started

Prerequisites

Before you begin, ensure you have:

Fork and Clone

  1. Fork the repository on GitHub
  2. Clone your fork locally:
    git clone git@github.com:YOUR_USERNAME/ocmonica.git
    cd ocmonica
    
  3. Add upstream remote:
    git remote add upstream git@github.com:j-pye/ocmonica.git
    
  4. Verify remotes:
    git remote -v
    # origin    git@github.com:YOUR_USERNAME/ocmonica.git (fetch)
    # origin    git@github.com:YOUR_USERNAME/ocmonica.git (push)
    # upstream  git@github.com:j-pye/ocmonica.git (fetch)
    # upstream  git@github.com:j-pye/ocmonica.git (push)
    

Initial Setup

# Install all dependencies and generate code
task setup

# Or step by step:
task install       # Install Go dependencies
task web:install   # Install Node.js dependencies
task proto:gen     # Generate protobuf code
task config:init   # Create config files

# Install git hooks
task hooks:install

Verify Setup

# Run all tests
task test

# Run linting
task lint

# Start development server
task dev

If all commands succeed, you're ready to contribute!


Development Setup

Editor Configuration

Install these extensions:

  • Go (golang.go)
  • golangci-lint
  • Prettier (esbenp.prettier-vscode)
  • ESLint (dbaeumer.vscode-eslint)
  • Buf (bufbuild.vscode-buf)

Settings (.vscode/settings.json):

{
  "go.useLanguageServer": true,
  "go.lintTool": "golangci-lint",
  "go.lintOnSave": "workspace",
  "editor.formatOnSave": true,
  "[go]": {
    "editor.defaultFormatter": "golang.go",
    "editor.codeActionsOnSave": {
      "source.organizeImports": "always"
    }
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  }
}

GoLand / IntelliJ IDEA

  1. Enable golangci-lint integration:
  2. Preferences → Tools → Go Linter
  3. Enable "Run golangci-lint"
  4. Enable goimports on save:
  5. Preferences → Tools → File Watchers
  6. Add "goimports" watcher
  7. Set Go version to 1.25.2+:
  8. Preferences → Go → GOROOT

Environment Variables

Create a .env file (gitignored):

# Server
OCMONICA_SERVER__PORT=8080
OCMONICA_SERVER__HOST=localhost

# Database
OCMONICA_DATABASE__PATH=./data/ocmonica.db

# Storage
OCMONICA_STORAGE__BASE_PATH=./data/files

# JWT (development only - use external keys in production)
OCMONICA_JWT__GENERATE_KEYS=true

# Logging
OCMONICA_LOGGING__LEVEL=debug
OCMONICA_LOGGING__FORMAT=text

# Observability
OCMONICA_OBSERVABILITY__JAEGER__ENABLED=true
OCMONICA_OBSERVABILITY__JAEGER__ENDPOINT=http://localhost:14268/api/traces

Code Style

Go Code Standards

Naming Conventions

Public (exported):

type FileService struct {}           // PascalCase
func (s *FileService) Upload() {}    // PascalCase
var ErrNotFound = errors.New("...")  // PascalCase

Private (unexported):

type fileRepository struct {}        // camelCase
func (r *fileRepository) create() {} // camelCase
var errInvalidInput = errors.New("...") // camelCase

Receivers:

func (s *FileService) Method() {}    // 1-2 letter abbreviation
func (fh *FileHandler) Handle() {}   // Acronyms acceptable

Avoid stuttering:

// ❌ Wrong
package file
type FileService struct {}  // file.FileService (stutters)

// ✅ Correct
package file
type Service struct {}      // file.Service (clean)

Documentation

Required for all public types and functions:

// FileService handles file management operations.
// It coordinates between the repository layer and handlers,
// enforcing permissions and validating business rules.
type FileService struct {
    repo repository.FileRepository
}

// Upload stores a new file in the system.
// It validates the filename, checks permissions, and stores the file
// content along with metadata.
//
// Returns ErrInvalidInput if the filename is invalid.
// Returns ErrPermissionDenied if the user lacks write permissions.
func (s *FileService) Upload(ctx context.Context, filename string, content io.Reader) (*models.File, error) {
    // Implementation
}

Documentation style: - Start with the item name - Use complete sentences with periods - Explain what, not how (implementation details go in comments) - Document error conditions - Include usage examples for complex APIs

Import Organization

import (
    // 1. Standard library (alphabetical)
    "context"
    "errors"
    "fmt"
    "time"

    // 2. Third-party packages (alphabetical)
    "connectrpc.com/connect"
    "github.com/labstack/echo/v4"
    "github.com/google/uuid"

    // 3. Project packages (alphabetical)
    "github.com/j-pye/ocmonica/internal/models"
    "github.com/j-pye/ocmonica/internal/repository"
    "github.com/j-pye/ocmonica/internal/service"
)

Error Handling

// Define sentinel errors
var (
    ErrNotFound      = errors.New("not found")
    ErrInvalidInput  = errors.New("invalid input")
)

// Wrap errors with context (use %w, not %v)
if err != nil {
    return nil, fmt.Errorf("failed to upload file %s: %w", filename, err)
}

// Check errors with errors.Is
if errors.Is(err, repository.ErrNotFound) {
    return connect.NewError(connect.CodeNotFound, err)
}

// Never ignore errors
result, err := doSomething()
if err != nil {
    return fmt.Errorf("operation failed: %w", err)
}

TypeScript/React Code Standards

Naming Conventions

// Components: PascalCase
export function FileUpload() {}

// Functions: camelCase
function handleUpload() {}

// Constants: UPPER_SNAKE_CASE
const MAX_FILE_SIZE = 10 * 1024 * 1024;

// Interfaces/Types: PascalCase
interface FileMetadata {
    name: string;
    size: number;
}

Component Structure

// 1. Imports
import React, { useState } from 'react';
import { FileService } from '@/lib/api';

// 2. Types/Interfaces
interface FileUploadProps {
    onComplete: (file: File) => void;
}

// 3. Component
export function FileUpload({ onComplete }: FileUploadProps) {
    // Hooks first
    const [file, setFile] = useState<File | null>(null);

    // Event handlers
    const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        // ...
    };

    // Render
    return (
        // JSX
    );
}

Formatting

Automated by git hooks: - Go: goimports + go fmt - TypeScript: prettier - Protobuf: buf format

Manual formatting:

# Format Go code
task fmt

# Format TypeScript code
task web:fmt

# Format protobuf
task proto:fmt


Git Workflow

Branching Strategy

We use GitHub Flow:

main (protected)
  feature/add-file-preview
  bugfix/fix-auth-token
  docs/update-contributing
  chore/update-dependencies

Branch naming: - feature/ - New features - bugfix/ - Bug fixes - docs/ - Documentation updates - chore/ - Maintenance tasks - refactor/ - Code refactoring - test/ - Test additions/improvements

Branch naming rules: - Use lowercase - Use hyphens, not underscores - Be descriptive but concise - Reference issue number when applicable: feature/123-add-dark-mode

Commit Message Format

We follow Conventional Commits:

<type>(<scope>): <subject>

<body>

<footer>

Types: - feat: New feature - fix: Bug fix - docs: Documentation changes - style: Code style changes (formatting, semicolons, etc.) - refactor: Code refactoring - test: Adding or updating tests - chore: Maintenance tasks (dependencies, build, etc.) - perf: Performance improvements - ci: CI/CD changes

Examples:

# Feature
feat(files): add video playback with position tracking

Implements HTTP Range Request support for video streaming.
Users can now seek to any position in uploaded videos.

Closes #123

# Bug fix
fix(auth): prevent token refresh race condition

Multiple simultaneous refresh requests were creating duplicate tokens.
Now uses mutex to ensure single refresh at a time.

Fixes #456

# Documentation
docs(contributing): add git workflow section

# Chore
chore(deps): update dependencies to latest versions

Subject line: - Use imperative mood ("add" not "added") - Don't capitalize first letter - No period at the end - 50 characters or less

Body (optional): - Wrap at 72 characters - Explain what and why, not how - Reference issues and PRs

Commit Message Validation

Git hooks validate commit messages automatically. If validation fails:

# Example error
 Commit message validation failed:
   - Subject must be in lowercase
   - Subject must be 50 characters or less
   - Type must be one of: feat, fix, docs, style, refactor, test, chore, perf, ci

To skip validation (not recommended):

git commit --no-verify -m "WIP: temporary commit"


Making Changes

Creating a Feature Branch

# Sync with upstream
git checkout main
git pull upstream main

# Create feature branch
git checkout -b feature/my-awesome-feature

# Push to your fork
git push -u origin feature/my-awesome-feature

Development Workflow

  1. Write tests first (TDD approach):

    # Create test file
    touch internal/service/my_feature_test.go
    
    # Write failing test
    # Run test to see it fail
    go test -v ./internal/service/ -run TestMyFeature
    

  2. Implement feature:

    # Implement until tests pass
    go test -v ./internal/service/ -run TestMyFeature
    

  3. Run full test suite:

    task test
    

  4. Check coverage:

    task test:coverage
    

  5. Lint code:

    task lint
    

  6. Security scan:

    task security:scan
    

Committing Changes

# Stage specific files
git add internal/service/my_feature.go
git add internal/service/my_feature_test.go

# Commit with conventional message
git commit -m "feat(service): add awesome new feature

Implements XYZ functionality with ABC approach.
Includes comprehensive test coverage.

Closes #123"

Git hooks will automatically: - Format Go code with goimports and go fmt - Lint Go code with golangci-lint - Format TypeScript with prettier - Lint TypeScript with eslint - Format protobuf with buf format - Lint protobuf with buf lint - Validate commit message format - Regenerate STATUS.md

Pushing Changes

# Push to your fork
git push origin feature/my-awesome-feature

Pull Request Process

Before Creating a PR

Checklist: - [ ] All tests pass: task test - [ ] Linting passes: task lint - [ ] Security scan clean: task security:scan - [ ] Code formatted: task fmt - [ ] Coverage maintained or improved - [ ] Documentation updated - [ ] Commit messages follow convention - [ ] Branch is up to date with main

Update branch with main:

git checkout main
git pull upstream main
git checkout feature/my-awesome-feature
git rebase main
git push origin feature/my-awesome-feature --force-with-lease

Creating the PR

  1. Go to your fork on GitHub
  2. Click "Compare & pull request"
  3. Fill out the PR template:
## Description
Brief description of what this PR does and why.

## Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Documentation update

## How Has This Been Tested?
Describe the tests you added/ran to verify your changes:
- [ ] Unit tests
- [ ] Integration tests
- [ ] Manual testing

## Checklist
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published

## Screenshots (if applicable)
Add screenshots to help explain your changes.

## Related Issues
Closes #123
Related to #456
  1. Request reviewers (maintainers will be notified automatically)
  2. Add labels (if you have permissions)

During Review

Responding to feedback:

  1. Make requested changes:

    # Make changes
    git add .
    git commit -m "fix: address review feedback"
    git push origin feature/my-awesome-feature
    

  2. Reply to comments:

  3. Be professional and constructive
  4. Ask for clarification if needed
  5. Explain your reasoning when disagreeing
  6. Mark conversations as resolved when addressed

  7. Update the PR:

  8. Keep the PR description updated
  9. Add notes about significant changes
  10. Re-request review after addressing all feedback

If review takes time: - Be patient - reviewers are volunteers - Ping politely if no response after 3-5 days - Keep your branch up to date with main

After Approval

  1. Squash commits (if requested):

    git rebase -i HEAD~n  # n = number of commits
    # Follow interactive rebase prompts
    git push origin feature/my-awesome-feature --force-with-lease
    

  2. Wait for CI to pass

  3. Maintainer will merge your PR

Code Review Guidelines

For Contributors

Before requesting review: - Self-review your changes - Run all checks locally - Ensure PR description is complete - Add screenshots/GIFs for UI changes

During review: - Respond to all comments - Be open to feedback - Ask questions if unclear - Update the PR promptly

For Reviewers

What to look for:

  1. Correctness:
  2. Does the code do what it claims?
  3. Are edge cases handled?
  4. Is error handling appropriate?

  5. Tests:

  6. Are there tests for new functionality?
  7. Do tests cover edge cases?
  8. Are tests clear and maintainable?

  9. Style:

  10. Does it follow project conventions?
  11. Is the code readable?
  12. Are names descriptive?

  13. Documentation:

  14. Are public APIs documented?
  15. Is the PR description clear?
  16. Are comments helpful (not obvious)?

  17. Architecture:

  18. Does it fit the project structure?
  19. Are dependencies appropriate?
  20. Is it maintainable?

  21. Security:

  22. No hardcoded secrets
  23. Input validation present
  24. SQL injection prevented
  25. RBAC enforced

How to review: - Be constructive and specific - Explain the "why" behind suggestions - Ask questions instead of making demands - Praise good solutions - Use GitHub's "Start a review" feature - Request changes or approve when done


Git Hooks

What Are Git Hooks?

Git hooks are scripts that run automatically before/after git commands. We use Lefthook to manage hooks.

Installed Hooks

Pre-commit (runs before commit): - Format Go code (goimports, go fmt) - Lint Go code (golangci-lint --fast) - Format TypeScript (prettier) - Lint TypeScript (eslint) - Format protobuf (buf format) - Lint protobuf (buf lint) - Validate commit message format - Regenerate STATUS.md

Typical run time: < 5 seconds

Managing Hooks

# Install hooks
task hooks:install

# Run hooks manually (test before commit)
task hooks:run

# Run all hooks (including optional ones)
task hooks:run-all

# Skip hooks for one commit (not recommended)
git commit --no-verify -m "emergency fix"

# Or
LEFTHOOK=0 git commit -m "skip hooks"

# Uninstall hooks
task hooks:uninstall

Troubleshooting Hooks

Hooks are slow: - First run is slower (installs tools) - Subsequent runs should be < 5 seconds - Reduce staged files if possible

Hooks failing:

# Run manually to see full output
lefthook run pre-commit -v

# Check individual tool
golangci-lint run ./...
go fmt ./...

Can't commit: - Fix the errors shown by hooks - Or skip hooks temporarily (not recommended) - Ask for help in GitHub Discussions


Release Process

Versioning

We use Semantic Versioning:

  • MAJOR: Breaking changes (v1.0.0 → v2.0.0)
  • MINOR: New features, backward-compatible (v1.0.0 → v1.1.0)
  • PATCH: Bug fixes, backward-compatible (v1.0.0 → v1.0.1)

Release Workflow

For maintainers only:

  1. Update version:

    # Update version in main.go
    version = "1.2.0"
    
    # Update CHANGELOG.md
    

  2. Create release commit:

    git commit -m "chore: release v1.2.0"
    

  3. Create tag:

    git tag -a v1.2.0 -m "Release v1.2.0"
    git push origin v1.2.0
    

  4. GitHub Actions will:

  5. Run tests
  6. Build binaries
  7. Create GitHub release
  8. Publish Docker images

Communication

GitHub Issues

Use issues for: - Bug reports - Feature requests - Questions about usage - Documentation improvements

Issue templates: - Bug Report: Describe the bug with steps to reproduce - Feature Request: Propose new functionality - Question: Ask about usage or implementation

GitHub Discussions

Use discussions for: - General questions - Design proposals - Show and tell - Community conversations

Pull Request Discussions

Use PR comments for: - Code review feedback - Implementation questions - Design alternatives - Clarifications

Getting Help

Stuck? Here's how to get help:

  1. Check documentation (see project root):
  2. README.md
  3. DEVELOPMENT.md
  4. TESTING.md
  5. Guides

  6. Search existing issues and discussions

  7. Ask in GitHub Discussions

  8. Open an issue if you found a bug

When asking for help: - Provide context (what you're trying to do) - Include error messages (full stack traces) - Show what you've tried - Share relevant code snippets - Be patient and polite


Summary

Quick contribution checklist:

  1. Fork and clone repository
  2. Create feature branch from main
  3. Make changes with tests
  4. Run task ci to verify
  5. Commit with conventional message
  6. Push to your fork
  7. Create pull request
  8. Address review feedback
  9. Wait for approval and merge

Remember: - Quality over speed - Tests are required - Documentation matters - Be respectful and professional - Ask questions when stuck

Thank you for contributing to ocmonica! Your contributions make this project better for everyone.