First off, thank you for considering contributing to ContributionAPI! 🎉
It's people like you that make ContributionAPI such a great tool for developers worldwide. This document provides guidelines and information for contributors.
- Code of Conduct
- Getting Started
- Development Process
- Coding Standards
- Testing Guidelines
- Submitting Changes
- Community
This project and everyone participating in it is governed by our Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to the project maintainers.
There are many ways you can contribute to ContributionAPI:
- Report Bugs: Use our bug report template
- Suggest Features: Use our feature request template
- Improve Documentation: Help make our docs clearer and more comprehensive
- Submit Code: Fix bugs, implement features, or improve performance
- Write Tests: Help improve our test coverage
- Review Pull Requests: Help review code changes from other contributors
Before contributing, ensure you have:
- .NET 8.0 SDK or later
- Git for version control
- A Firebase project (for testing integrations)
- Google Cloud project with Secret Manager enabled
- Familiarity with C# and ASP.NET Core
-
Fork and Clone
git clone https://github.com/your-username/ContributionAPI.git cd ContributionAPI -
Set up Development Environment
# Restore dependencies dotnet restore # Build the solution dotnet build
-
Configure Local Settings
# Copy development configuration cp src/Contribution.Hub/appsettings.Development.json.example src/Contribution.Hub/appsettings.Development.json # Edit configuration with your test Firebase/GCP settings
-
Run Tests
dotnet test -
Start Development Services
# Option 1: Use VS Code tasks (recommended) # Open in VS Code and use "Run Contribution API" task # Option 2: Manual startup cd src/Contribution.Hub && dotnet run --urls "http://localhost:5000" cd src/Contribution.GitHub && dotnet run --urls "http://localhost:5001" cd src/Contribution.AzureDevOps && dotnet run --urls "http://localhost:5002"
We use a simplified Git flow:
main: Production-ready codedevelop: Integration branch for featuresfeature/feature-name: Feature development brancheshotfix/fix-name: Critical bug fixesrelease/version: Release preparation branches
- Create an Issue: Before starting work, create or find an existing issue
- Create a Branch: Branch from
developfor features,mainfor hotfixes - Develop: Make your changes following our coding standards
- Test: Ensure all tests pass and add new tests for your changes
- Document: Update documentation as needed
- Submit PR: Create a pull request using our template
- Review: Address feedback from code reviewers
- Merge: Once approved, your changes will be merged
Use descriptive branch names:
feature/github-rate-limitingbugfix/azure-token-refreshhotfix/critical-security-patchdocs/api-documentation-update
We follow Microsoft's C# coding conventions with some project-specific additions:
// Classes: PascalCase
public class ContributionManager { }
// Methods: PascalCase
public async Task<ContributionsResponse> GetContributionsAsync() { }
// Properties: PascalCase
public string UserId { get; set; }
// Fields: camelCase with underscore prefix for private
private readonly ILogger _logger;
// Constants: PascalCase
public const string DefaultProvider = "github";
// Interfaces: PascalCase with 'I' prefix
public interface IContributionProvider { }// File structure order:
using System; // System namespaces first
using Microsoft.AspNetCore.Mvc; // Microsoft namespaces
using Contribution.Common; // Project namespaces
namespace Contribution.Hub.Controllers; // Namespace
public class ContributionsController : ControllerBase // Class declaration
{
// Constants
private const string CachePrefix = "contributions";
// Fields
private readonly ILogger<ContributionsController> _logger;
// Constructor
public ContributionsController(ILogger<ContributionsController> logger)
{
_logger = logger;
}
// Properties
public string ApiVersion { get; } = "v1.0";
// Methods (public first, then private)
public async Task<IActionResult> GetAsync() { }
private bool ValidateRequest() { }
}Dependency Injection:
// Use constructor injection
public class ContributionManager(
IContributionRepository repository,
ILogger<ContributionManager> logger)
{
private readonly IContributionRepository _repository = repository;
private readonly ILogger<ContributionManager> _logger = logger;
}Async/Await:
// Always use ConfigureAwait(false) in libraries
public async Task<ContributionsResponse> GetContributionsAsync()
{
var result = await _httpClient.GetAsync(url).ConfigureAwait(false);
return await ProcessResponse(result).ConfigureAwait(false);
}Error Handling:
// Use specific exception types
public async Task<ContributionsResponse> GetContributionsAsync()
{
try
{
return await FetchContributions().ConfigureAwait(false);
}
catch (HttpRequestException ex)
{
_logger.LogError(ex, "Failed to fetch contributions for user {UserId}", userId);
throw new ContributionServiceException("Failed to fetch contributions", ex);
}
}// Register services in logical groups
// Core services
builder.Services.AddScoped<IContributionAggregatorManager, ContributionAggregatorManager>();
builder.Services.AddScoped<IUserDataRepository, UserDataRepository>();
// External service clients
builder.Services.AddHttpClient<IContributionServiceClient, ContributionServiceClient>();
// Configuration
builder.Services.Configure<HubOptions>(builder.Configuration.GetSection(HubOptions.SectionName));// Use strongly-typed configuration
public class HubOptions
{
public const string SectionName = "Hub";
public string FirebaseProjectId { get; set; } = string.Empty;
public string SecretManagerProject { get; set; } = string.Empty;
public TimeSpan CacheExpiration { get; set; } = TimeSpan.FromMinutes(30);
}/// <summary>
/// Aggregates contributions from multiple providers for a specific user and year.
/// </summary>
/// <param name="userId">The unique identifier for the user.</param>
/// <param name="year">The year for which to retrieve contributions.</param>
/// <param name="providers">Optional list of specific providers to query.</param>
/// <returns>A task representing the asynchronous operation with aggregated contributions.</returns>
/// <exception cref="ArgumentException">Thrown when userId is null or empty.</exception>
/// <exception cref="ContributionServiceException">Thrown when contribution aggregation fails.</exception>
public async Task<ContributionsResponse> AggregateContributionsAsync(
string userId,
int year,
string[]? providers = null)// Use comments for business logic, not obvious code
public async Task<ContributionsResponse> ProcessContributions()
{
// Apply rate limiting to prevent API quota exhaustion
await _rateLimiter.WaitAsync().ConfigureAwait(false);
// Fetch from cache first to reduce external API calls
var cached = await _cache.GetAsync(cacheKey).ConfigureAwait(false);
if (cached != null)
{
return cached;
}
// Cache miss - fetch from external provider
var fresh = await FetchFromProvider().ConfigureAwait(false);
// Cache the result with sliding expiration
await _cache.SetAsync(cacheKey, fresh, TimeSpan.FromMinutes(30)).ConfigureAwait(false);
return fresh;
}We use a three-tier testing approach:
- Test individual components in isolation
- Mock external dependencies
- Fast execution (< 100ms per test)
- High coverage (aim for 80%+ code coverage)
[Fact]
public async Task GetContributionsAsync_ValidUser_ReturnsContributions()
{
// Arrange
var mockRepo = new Mock<IContributionRepository>();
mockRepo.Setup(x => x.GetUserDataAsync("user123"))
.ReturnsAsync(new UserData { UserId = "user123" });
var manager = new ContributionManager(mockRepo.Object);
// Act
var result = await manager.GetContributionsAsync("user123", 2024);
// Assert
Assert.NotNull(result);
Assert.Equal("user123", result.UserId);
Assert.Equal(2024, result.Year);
}- Test component interactions
- Use test containers for external dependencies
- Verify end-to-end scenarios
[Collection("Integration")]
public class ContributionHubIntegrationTests : IClassFixture<WebApplicationFactory<Program>>
{
[Fact]
public async Task GetContributions_ValidRequest_ReturnsSuccessResponse()
{
// Arrange
var client = _factory.CreateClient();
// Act
var response = await client.GetAsync("/contributions?userId=test&year=2024");
// Assert
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<ContributionsResponse>(content);
Assert.NotNull(result);
}
}- Verify response times under load
- Test memory usage and resource cleanup
- Use BenchmarkDotNet for micro-benchmarks
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net80)]
public class ContributionManagerBenchmarks
{
[Benchmark]
public async Task GetContributions_Benchmark()
{
var manager = new ContributionManager(/* dependencies */);
await manager.GetContributionsAsync("user123", 2024);
}
}tests/
├── Contribution.Hub.Tests/ # Unit tests for Hub
│ ├── Controllers/
│ ├── Managers/
│ └── Services/
├── Contribution.GitHub.Tests/ # Unit tests for GitHub service
├── Contribution.AzureDevOps.Tests/ # Unit tests for Azure service
├── Contribution.Common.Tests/ # Unit tests for shared library
├── Integration.Tests/ # Integration tests
│ ├── Api/
│ └── Services/
└── Performance.Tests/ # Performance benchmarks
Use descriptive test names that explain the scenario:
// MethodName_Scenario_ExpectedResult
[Fact]
public void GetContributions_UserNotFound_ThrowsUserNotFoundException() { }
[Fact]
public void GetContributions_ValidUser_ReturnsContributionsWithCorrectTotal() { }
[Fact]
public void GetContributions_RateLimitExceeded_RetriesWithExponentialBackoff() { }- Update Documentation: Ensure README, API docs, and code comments are updated
- Add/Update Tests: Include tests for new functionality or bug fixes
- Update Changelog: Add entry to CHANGELOG.md (if applicable)
- Run Full Test Suite: Ensure all tests pass
- Check Code Style: Run linting and formatting tools
- Create PR: Use our pull request template
Title Format:
feat: add GitHub rate limiting supportfix: resolve Azure DevOps token refresh issuedocs: update API documentationtest: add integration tests for Hub servicerefactor: optimize contribution aggregation logic
Description Requirements:
- Clear description of changes
- Link to related issues
- Breaking changes clearly marked
- Testing steps included
- Screenshots (if UI changes)
- Automated Checks: CI/CD pipeline runs automatically
- Maintainer Review: Core team reviews for design and implementation
- Community Review: Other contributors may provide feedback
- Address Feedback: Make requested changes
- Final Approval: Maintainer approves and merges
Reviewers will check for:
- Functionality: Does it work as intended?
- Code Quality: Is it readable and maintainable?
- Performance: Are there any performance implications?
- Security: Are there any security concerns?
- Testing: Is there adequate test coverage?
- Documentation: Is documentation updated?
- GitHub Issues: Bug reports, feature requests
- GitHub Discussions: General questions, ideas
- Pull Requests: Code review and collaboration
If you need help:
- Check existing documentation
- Search existing issues
- Create a new issue with detailed information
- Join GitHub Discussions
Contributors are recognized through:
- GitHub contributor graphs
- CHANGELOG.md mentions
- Special recognition for significant contributions
Looking for something to work on? Check out issues labeled:
good first issue: Perfect for newcomershelp wanted: We need community helpdocumentation: Improve our docstesting: Help improve test coverage
Thank you for contributing to ContributionAPI!
Your contributions help make this project better for developers worldwide. If you have any questions about contributing, don't hesitate to ask in our GitHub Discussions.