Skip to content

Contributing to Property Management System

Thank you for your interest in contributing to the Property Management System! This guide will help you understand our development process, coding standards, and how to effectively contribute to the project.

Table of Contents

Getting Started

Prerequisites

Before you start contributing, ensure you have the following installed:

  • .NET 8 SDK (latest version)
  • Docker Desktop 4.0+
  • Node.js 18+ and npm 9+
  • PostgreSQL 14+ (or use Docker)
  • Redis 6+ (or use Docker)
  • Git with proper configuration

Environment Setup

  1. Fork and clone the repository

    git clone https://github.com/your-username/property-management.git
    cd property-management
    

  2. Set up your development environment

    # Install .NET dependencies
    dotnet restore
    
    # Install frontend dependencies
    cd frontend/web
    npm ci
    cd ../..
    
    # Set up environment configuration
    cp src/AppHost/appsettings.Development.example.json src/AppHost/appsettings.Development.json
    # Edit the configuration file with your local settings
    

  3. Run the application

    # Start all services with Aspire
    dotnet run --project src/AppHost/PropertyManagement.AppHost
    
    # Access the Aspire dashboard at https://localhost:15888
    # Frontend will be available at the URL shown in the dashboard
    

  4. Verify your setup

    # Run tests to ensure everything is working
    dotnet test
    cd frontend/web && npm test && cd ../..
    

Development Workflow

Branch Strategy

We use GitFlow with the following branch structure:

  • main: Production-ready code
  • develop: Integration branch for features
  • feature/*: Individual features (branch from develop)
  • hotfix/*: Critical production fixes (branch from main)
  • release/*: Release preparation (branch from develop)

Creating a Feature Branch

# Switch to develop and get latest changes
git checkout develop
git pull origin develop

# Create and switch to your feature branch
git checkout -b feature/property-search-enhancement

# Make your changes and commit regularly
git add .
git commit -m "feat: add advanced search filters for properties"

# Push your branch
git push origin feature/property-search-enhancement

Commit Message Format

We follow Conventional Commits specification:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Types: - feat: New feature - fix: Bug fix - docs: Documentation changes - style: Code style changes (formatting, etc.) - refactor: Code refactoring - test: Adding or updating tests - chore: Maintenance tasks

Examples:

feat(property): add unit management functionality
fix(auth): resolve JWT token expiration issue
docs(api): update property endpoint documentation
test(property): add integration tests for property creation

Coding Standards

C# Backend Standards

Code Style

  • Use C# 12 features including primary constructors and collection expressions
  • Enable nullable reference types in all projects
  • Use record types for DTOs and value objects
  • Follow Clean Architecture principles with clear layer separation
  • Use Result pattern for business logic error handling

Example Patterns

// Domain Entity
public class Property : Entity<PropertyId>
{
    public string Name { get; private set; } = string.Empty;
    public Address Address { get; private set; } = null!;

    public Result<Unit> AddUnit(Unit unit)
    {
        if (Units.Count >= GetMaxUnitsForType())
            return Result.Failure<Unit>("Maximum units exceeded for property type");

        Units.Add(unit);
        RaiseDomainEvent(new UnitAddedEvent(Id, unit.Id));
        return Result.Success(unit);
    }
}

// Application Service
public class PropertyApplicationService
{
    public async Task<Result<PropertyResponse>> CreatePropertyAsync(
        CreatePropertyCommand command,
        CancellationToken cancellationToken = default)
    {
        var validation = await _validator.ValidateAsync(command, cancellationToken);
        if (!validation.IsValid)
            return Result.Failure<PropertyResponse>(validation.Errors.First().ErrorMessage);

        // Domain logic and persistence
        using var transaction = await _unitOfWork.BeginTransactionAsync(cancellationToken);

        var propertyResult = Property.Create(command.Name, command.Address, command.Type);
        if (propertyResult.IsFailure)
            return Result.Failure<PropertyResponse>(propertyResult.Error);

        await _propertyRepository.AddAsync(propertyResult.Value, cancellationToken);
        await _unitOfWork.SaveChangesAsync(cancellationToken);
        await transaction.CommitAsync(cancellationToken);

        return Result.Success(propertyResult.Value.ToResponse());
    }
}

Documentation Requirements

  • All public APIs must have XML documentation
  • Complex business logic should include explanatory comments
  • Domain concepts should be thoroughly documented
/// <summary>
/// Creates a new property with comprehensive validation and business rules.
/// </summary>
/// <param name="command">Property creation command with required details</param>
/// <param name="cancellationToken">Cancellation token for async operation</param>
/// <returns>Result containing the created property or validation errors</returns>
/// <exception cref="ValidationException">Thrown when business rules are violated</exception>
public async Task<Result<PropertyResponse>> CreatePropertyAsync(
    CreatePropertyCommand command,
    CancellationToken cancellationToken = default)

TypeScript/React Frontend Standards

Code Style

  • Use strict TypeScript configuration with no any types
  • Prefer Server Components for data fetching in Next.js 15
  • Use custom hooks for complex state logic
  • Implement proper error boundaries for resilient UX
  • Follow functional programming patterns where appropriate

Component Patterns

// Server Component
export default async function PropertyPage({ 
  params 
}: { 
  params: Promise<{ id: string }> 
}) {
  const { id } = await params;
  const property = await getPropertyDetails(id);

  if (!property) {
    notFound();
  }

  return (
    <div className="space-y-6">
      <PropertyHeader property={property} />
      <PropertyDetails property={property} />
    </div>
  );
}

// Client Component with proper typing
interface PropertyFormProps {
  initialData?: Partial<Property>;
  onSubmit: (data: CreatePropertyRequest) => Promise<void>;
}

export function PropertyForm({ initialData, onSubmit }: PropertyFormProps) {
  const { mutate: createProperty, isPending } = useMutation({
    mutationFn: onSubmit,
    onSuccess: () => toast.success('Property created successfully'),
    onError: (error) => toast.error(error.message),
  });

  return (
    <form onSubmit={(e) => { e.preventDefault(); createProperty(formData); }}>
      {/* Form implementation */}
    </form>
  );
}

Styling Guidelines

  • Use Tailwind CSS for utility-first styling
  • Implement responsive design with mobile-first approach
  • Follow design system tokens for consistency
  • Use semantic HTML for accessibility

Database Standards

Entity Framework Configuration

public class PropertyConfiguration : IEntityTypeConfiguration<Property>
{
    public void Configure(EntityTypeBuilder<Property> builder)
    {
        builder.ToTable("Properties");

        // Value object mapping
        builder.OwnsOne(p => p.Address, address =>
        {
            address.Property(a => a.Street).HasColumnName("Address_Street").HasMaxLength(200);
            address.Property(a => a.City).HasColumnName("Address_City").HasMaxLength(100);
        });

        // Performance indexes
        builder.HasIndex(p => p.Name);
        builder.HasIndex("Address_City", "Address_State");
    }
}

Migration Guidelines

  • Always create reversible migrations
  • Include data migration scripts when needed
  • Test migrations against production-like data
  • Document breaking changes in migration comments

Testing Guidelines

Testing Strategy

We follow the Test Pyramid approach: - Many unit tests (70-80% of total tests) - Fewer integration tests (15-25% of total tests) - Minimal end-to-end tests (5-10% of total tests)

Unit Testing Standards

C# Unit Tests

[TestFixture]
public class PropertyTests
{
    private readonly Address _validAddress = new("123 Main St", "Springfield", "IL", "62701");

    [Test]
    public void AddUnit_WhenPropertyAtCapacity_ShouldReturnFailureWithMessage()
    {
        // Arrange
        var property = PropertyTestBuilder
            .Create()
            .WithType(PropertyType.SingleFamily)
            .WithMaxUnits(1)
            .Build();

        var existingUnit = Unit.Create("Unit-1").Value;
        property.AddUnit(existingUnit);

        var newUnit = Unit.Create("Unit-2").Value;

        // Act
        var result = property.AddUnit(newUnit);

        // Assert
        result.IsFailure.Should().BeTrue();
        result.Error.Should().Contain("Maximum units exceeded");
        property.Units.Should().HaveCount(1);
    }
}

React Component Tests

describe('PropertyForm', () => {
  it('should display validation errors when form is submitted with invalid data', async () => {
    render(<PropertyForm onSubmit={jest.fn()} />);

    fireEvent.click(screen.getByRole('button', { name: /create property/i }));

    await waitFor(() => {
      expect(screen.getByText(/property name is required/i)).toBeInTheDocument();
      expect(screen.getByText(/address is required/i)).toBeInTheDocument();
    });
  });

  it('should call onSubmit with correct data when form is valid', async () => {
    const mockOnSubmit = jest.fn();
    render(<PropertyForm onSubmit={mockOnSubmit} />);

    fireEvent.change(screen.getByLabelText(/property name/i), {
      target: { value: 'Test Property' },
    });
    fireEvent.change(screen.getByLabelText(/address/i), {
      target: { value: '123 Test Street' },
    });

    fireEvent.click(screen.getByRole('button', { name: /create/i }));

    await waitFor(() => {
      expect(mockOnSubmit).toHaveBeenCalledWith(
        expect.objectContaining({
          name: 'Test Property',
          address: '123 Test Street',
        })
      );
    });
  });
});

Integration Testing

Use TestContainers for integration tests to ensure consistent test environments:

[TestFixture]
public class PropertyIntegrationTests : IntegrationTestBase
{
    [Test]
    public async Task CreateProperty_EndToEnd_ShouldPersistCorrectly()
    {
        var request = new CreatePropertyRequest
        {
            Name = "Integration Test Property",
            Address = "123 Integration St",
            Type = PropertyType.Apartment,
            Units = 50
        };

        var response = await TestClient.PostAsJsonAsync("/api/properties", request);

        response.StatusCode.Should().Be(HttpStatusCode.Created);

        var property = await response.Content.ReadFromJsonAsync<PropertyResponse>();
        property.Should().NotBeNull();

        // Verify database persistence
        var dbProperty = await DbContext.Properties.FindAsync(Guid.Parse(property!.Id));
        dbProperty.Should().NotBeNull();
    }
}

Test Coverage Requirements

  • Minimum 80% coverage for new code
  • 90%+ coverage for business logic in Domain layer
  • Critical paths must have 100% coverage
  • All public APIs must have corresponding tests

Pull Request Process

Before Creating a PR

  1. Ensure all tests pass

    dotnet test
    cd frontend/web && npm test
    

  2. Run code formatting

    dotnet format
    cd frontend/web && npm run lint:fix
    

  3. Update documentation if needed

  4. Add/update tests for new functionality
  5. Verify performance impact for critical paths

PR Template

When creating a pull request, use this template:

## Description
Brief description of the changes made.

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

## Testing
- [ ] Unit tests added/updated
- [ ] Integration tests added/updated
- [ ] Manual testing completed
- [ ] Performance testing (if applicable)

## Checklist
- [ ] Code follows the style guidelines
- [ ] Self-review of code completed
- [ ] Code is commented, particularly in hard-to-understand areas
- [ ] Corresponding changes to documentation made
- [ ] No new warnings introduced
- [ ] Tests added that prove the fix is effective or feature works
- [ ] New and existing unit tests pass locally

Review Process

  1. Automated checks must pass (CI/CD pipeline)
  2. At least 2 reviewers must approve
  3. Code owner approval for architectural changes
  4. Security review for authentication/authorization changes
  5. Performance review for database or API changes

Issue Guidelines

Reporting Bugs

Use the bug report template:

**Bug Description**
Clear and concise description of the bug.

**Steps to Reproduce**
1. Go to '...'
2. Click on '...'
3. Scroll down to '...'
4. See error

**Expected Behavior**
What you expected to happen.

**Actual Behavior**
What actually happened.

**Environment**
- OS: [e.g., Windows 10, macOS 12.0]
- Browser: [e.g., Chrome 96, Safari 15]
- .NET Version: [e.g., 8.0]
- Node.js Version: [e.g., 18.17.0]

**Additional Context**
Any other context about the problem.

Feature Requests

Include these details: - Business justification for the feature - User stories describing the need - Acceptance criteria for completion - Technical considerations if known

Architecture Guidelines

Clean Architecture Principles

  1. Dependency Rule: Dependencies point inward toward the Domain layer
  2. Single Responsibility: Each class has one reason to change
  3. Open/Closed: Open for extension, closed for modification
  4. Interface Segregation: Clients depend only on methods they use
  5. Dependency Inversion: Depend on abstractions, not concretions

Service Communication

  • Synchronous: Use HTTP for immediate consistency requirements
  • Asynchronous: Use domain events for eventual consistency
  • Error Handling: Implement circuit breakers and retries
  • Monitoring: Include comprehensive logging and metrics

Database Design

  • One database per service in microservices architecture
  • Use appropriate indexes for query patterns
  • Implement soft deletes for audit requirements
  • Version schemas for backward compatibility

Performance Considerations

Backend Performance

  • Use caching strategically with proper invalidation
  • Implement pagination for large datasets
  • Optimize database queries with appropriate indexes
  • Use async/await consistently for I/O operations
  • Monitor performance with Application Insights

Frontend Performance

  • Implement code splitting for large bundles
  • Use React.memo for expensive component renders
  • Optimize images with Next.js Image component
  • Implement proper loading states for better UX
  • Measure Core Web Vitals regularly

Database Performance

  • Monitor query performance with query plans
  • Use connection pooling effectively
  • Implement read replicas for read-heavy workloads
  • Consider partitioning for large tables
  • Regular maintenance of indexes and statistics

Getting Help

  • Documentation: Check the comprehensive docs at /documentations
  • Discussions: Use GitHub Discussions for questions
  • Issues: Create GitHub Issues for bugs or feature requests
  • Code Review: Tag specific team members for specialized review
  • Architecture: Consult with the architecture team for design decisions

Thank you for contributing to the Property Management System! Your efforts help make this project better for everyone.