← Back to Table of Contents

Development Guide

Comprehensive development guide for the Open Source Site Tracking platform, covering setup, architecture, and best practices.

Setup Architecture Backend Development Frontend Development Testing Deployment Contributing

Setup

Development Environment

1Prerequisites

  • Python 3.8+: Backend development
  • Node.js 16+: Frontend development
  • Docker: Containerization
  • Git: Version control
  • PostgreSQL: Database (recommended)

2Clone Repository

git clone https://github.com/AutoBotSolutions/Opensource-Site-Tracking.git
cd opensource-site-tracking

3Backend Setup

# Create virtual environment
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate
# Install dependencies
cd backend
pip install -r requirements.txt
pip install -r requirements-dev.txt
# Initialize database
python init_db.py

4Frontend Setup

# Install dependencies
cd frontend
npm install
# Start development server
npm run dev

5Start Backend

# Development server
cd backend
python -m uvicorn main:app --reload --host 0.0.0.0 --port 8000

Development Tools

Backend Tools

Frontend Tools

Architecture

System Architecture

The platform follows a modern microservices-inspired architecture within a monolithic structure:

Backend Architecture

Frontend Architecture

Project Structure

opensource-site-tracking/
├── backend/
│   ├── app/
│   │   ├── __init__.py
│   │   ├── models/          # Database models
│   │   ├── routes/          # API endpoints
│   │   ├── services/        # Business logic
│   │   ├── utils/           # Helper functions
│   │   └── config/          # Configuration
│   ├── tests/               # Test suite
│   ├── migrations/          # Database migrations
│   ├── requirements.txt     # Dependencies
│   └── main.py             # Application entry
├── frontend/
│   ├── components/          # React components
│   ├── pages/               # Page components
│   ├── hooks/               # Custom hooks
│   ├── services/            # API services
│   ├── utils/               # Utility functions
│   ├── styles/              # Styling
│   ├── package.json         # Dependencies
│   └── next.config.js       # Next.js config
├── docs/                    # Documentation
├── docker-compose.yml       # Docker configuration
└── README.md               # Project README

Backend Development

API Development

Creating API Endpoints

# backend/app/routes/analytics.py
from fastapi import APIRouter, Depends
from app.services.analytics import AnalyticsService
router = APIRouter(prefix="/analytics", tags=["analytics"])
@router.get("/{project_id}/overview")
async def get_analytics_overview(
    project_id: str,
    analytics_service: AnalyticsService = Depends()
):
    """Get analytics overview for project"""
    return await analytics_service.get_overview(project_id)
@router.get("/{project_id}/pageviews")
async def get_page_views(
    project_id: str,
    start_date: str,
    end_date: str,
    analytics_service: AnalyticsService = Depends()
):
    """Get page views analytics"""
    return await analytics_service.get_page_views(
        project_id, start_date, end_date
    )

Service Layer

Business Logic

# backend/app/services/analytics.py
from typing import Dict, List, Optional
from datetime import datetime
from app.models.analytics import AnalyticsData
class AnalyticsService:
    def __init__(self, db_session):
        self.db = db_session
    
    async def get_overview(self, project_id: str) -> Dict:
        """Get analytics overview"""
        page_views = await self.get_page_views_count(project_id)
        unique_visitors = await self.get_unique_visitors_count(project_id)
        
        return {
            "page_views": page_views,
            "unique_visitors": unique_visitors,
            "average_session_duration": await self.get_avg_session_duration(project_id),
            "bounce_rate": await self.get_bounce_rate(project_id)
        }
    
    async def get_page_views_count(self, project_id: str) -> int:
        """Get total page views for project"""
        return self.db.query(AnalyticsData).filter(
            AnalyticsData.project_id == project_id,
            AnalyticsData.event_type == "page_view"
        ).count()

Database Models

SQLAlchemy Models

# backend/app/models/analytics.py
from sqlalchemy import Column, String, Integer, DateTime, Text, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from datetime import datetime
Base = declarative_base()
class Project(Base):
    __tablename__ = "projects"
    
    id = Column(String, primary_key=True)
    name = Column(String, nullable=False)
    description = Column(Text)
    repository_url = Column(String)
    created_at = Column(DateTime, default=datetime.utcnow)
    updated_at = Column(DateTime, default=datetime.utcnow)
    
    analytics = relationship("AnalyticsData", back_populates="project")
class AnalyticsData(Base):
    __tablename__ = "analytics_data"
    
    id = Column(Integer, primary_key=True)
    project_id = Column(String, ForeignKey("projects.id"))
    event_type = Column(String, nullable=False)
    page_url = Column(String)
    user_id = Column(String)
    session_id = Column(String)
    properties = Column(Text)  # JSON string
    timestamp = Column(DateTime, default=datetime.utcnow)
    
    project = relationship("Project", back_populates="analytics")

Configuration

Application Configuration

# backend/app/config.py
import os
from typing import Optional
class Config:
    # Database
    DATABASE_URL: str = os.environ.get("DATABASE_URL", "sqlite:///site_tracking.db")
    
    # Security
    SECRET_KEY: str = os.environ.get("SECRET_KEY", "dev-secret-key")
    JWT_SECRET_KEY: str = os.environ.get("JWT_SECRET_KEY", "jwt-secret-key")
    JWT_EXPIRATION_HOURS: int = int(os.environ.get("JWT_EXPIRATION_HOURS", "24"))
    
    # Application
    DEBUG: bool = os.environ.get("DEBUG", "False").lower() == "true"
    ENVIRONMENT: str = os.environ.get("ENVIRONMENT", "development")
    
    # Analytics
    ANALYTICS_ENABLED: bool = os.environ.get("ANALYTICS_ENABLED", "True").lower() == "true"
    DATA_RETENTION_DAYS: int = int(os.environ.get("DATA_RETENTION_DAYS", "365"))
class DevelopmentConfig(Config):
    DEBUG = True
    ENVIRONMENT = "development"
class ProductionConfig(Config):
    DEBUG = False
    ENVIRONMENT = "production"
config = {
    "development": DevelopmentConfig,
    "production": ProductionConfig
}

Frontend Development

Component Development

React Components

// frontend/components/AnalyticsDashboard.tsx
import React, { useState, useEffect } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
interface AnalyticsData {
  date: string;
  pageViews: number;
  uniqueVisitors: number;
}
const AnalyticsDashboard: React.FC = () => {
  const [data, setData] = useState([]);
  const [loading, setLoading] = useState(true);
  useEffect(() => {
    fetchAnalyticsData();
  }, []);
  const fetchAnalyticsData = async () => {
    try {
      const response = await fetch('/api/analytics/overview');
      const analyticsData = await response.json();
      setData(analyticsData);
    } catch (error) {
      console.error('Error fetching analytics:', error);
    } finally {
      setLoading(false);
    }
  };
  if (loading) {
    return 
Loading...
; } return (

Analytics Overview

); }; export default AnalyticsDashboard;

Custom Hooks

React Hooks

// frontend/hooks/useAnalytics.ts
import { useState, useEffect } from 'react';
interface AnalyticsData {
  pageViews: number;
  uniqueVisitors: number;
  averageSessionDuration: number;
  bounceRate: number;
}
export const useAnalytics = (projectId: string) => {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);
  useEffect(() => {
    const fetchData = async () => {
      try {
        setLoading(true);
        const response = await fetch(`/api/analytics/${projectId}/overview`);
        if (!response.ok) {
          throw new Error('Failed to fetch analytics');
        }
        const analyticsData = await response.json();
        setData(analyticsData);
      } catch (err) {
        setError(err instanceof Error ? err.message : 'Unknown error');
      } finally {
        setLoading(false);
      }
    };
    fetchData();
  }, [projectId]);
  return { data, loading, error };
};

API Services

API Integration

// frontend/services/analytics.ts
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000';
export class AnalyticsService {
  private static async request(endpoint: string, options: RequestInit = {}) {
    const url = `${API_BASE_URL}/api/v1${endpoint}`;
    const response = await fetch(url, {
      headers: {
        'Content-Type': 'application/json',
        ...options.headers,
      },
      ...options,
    });
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  }
  static async getOverview(projectId: string) {
    return this.request(`/analytics/${projectId}/overview`);
  }
  static async getPageViews(projectId: string, startDate: string, endDate: string) {
    return this.request(`/analytics/${projectId}/pageviews?start_date=${startDate}&end_date=${endDate}`);
  }
  static async getVisitors(projectId: string, startDate: string, endDate: string) {
    return this.request(`/analytics/${projectId}/visitors?start_date=${startDate}&end_date=${endDate}`);
  }
}

Testing

Backend Testing

Unit Tests

# backend/tests/test_analytics.py
import pytest
from app.services.analytics import AnalyticsService
from app.models.analytics import Project, AnalyticsData
class TestAnalyticsService:
    def setup_method(self):
        self.analytics_service = AnalyticsService(db_session)
        self.project = Project(
            id="test-project",
            name="Test Project"
        )
        db_session.add(self.project)
        db_session.commit()
    def test_get_overview(self):
        # Create test data
        analytics_data = AnalyticsData(
            project_id=self.project.id,
            event_type="page_view",
            page_url="/test"
        )
        db_session.add(analytics_data)
        db_session.commit()
        # Test service method
        result = self.analytics_service.get_overview(self.project.id)
        
        assert "page_views" in result
        assert "unique_visitors" in result
        assert result["page_views"] >= 1
    def test_get_page_views_count(self):
        # Create test data
        for i in range(5):
            analytics_data = AnalyticsData(
                project_id=self.project.id,
                event_type="page_view",
                page_url=f"/test{i}"
            )
            db_session.add(analytics_data)
        
        db_session.commit()
        # Test service method
        count = self.analytics_service.get_page_views_count(self.project.id)
        assert count == 5

Frontend Testing

Component Tests

// frontend/components/__tests__/AnalyticsDashboard.test.tsx
import React from 'react';
import { render, screen, waitFor } from '@testing-library/react';
import AnalyticsDashboard from '../AnalyticsDashboard';
// Mock fetch
global.fetch = jest.fn();
describe('AnalyticsDashboard', () => {
  beforeEach(() => {
    (fetch as jest.Mock).mockClear();
  });
  it('renders loading state', () => {
    (fetch as jest.Mock).mockImplementationOnce(() => 
      new Promise(() => {})
    );
    render();
    expect(screen.getByText('Loading...')).toBeInTheDocument();
  });
  it('renders analytics data', async () => {
    const mockData = [
      { date: '2024-01-01', pageViews: 100, uniqueVisitors: 50 },
      { date: '2024-01-02', pageViews: 120, uniqueVisitors: 60 }
    ];
    (fetch as jest.Mock).mockResolvedValueOnce({
      ok: true,
      json: async () => mockData,
    });
    render();
    await waitFor(() => {
      expect(screen.getByText('Analytics Overview')).toBeInTheDocument();
    });
  });
  it('handles fetch error', async () => {
    (fetch as jest.Mock).mockRejectedValueOnce(new Error('Network error'));
    render();
    await waitFor(() => {
      expect(screen.getByText('Error: Network error')).toBeInTheDocument();
    });
  });
});

Integration Tests

API Integration Tests

# backend/tests/test_api.py
import pytest
from fastapi.testclient import TestClient
from app.main import app
client = TestClient(app)
class TestAnalyticsAPI:
    def test_get_analytics_overview(self):
        response = client.get("/api/v1/analytics/test-project/overview")
        
        assert response.status_code == 200
        data = response.json()
        assert "page_views" in data
        assert "unique_visitors" in data
    def test_get_analytics_overview_invalid_project(self):
        response = client.get("/api/v1/analytics/invalid-project/overview")
        
        assert response.status_code == 404
    def test_track_event(self):
        event_data = {
            "event_type": "page_view",
            "page_url": "/test",
            "properties": {"test": "value"}
        }
        
        response = client.post("/api/v1/events/test-project", json=event_data)
        
        assert response.status_code == 200
        data = response.json()
        assert data["success"] is True

Deployment

Docker Deployment

Dockerfile

# backend/Dockerfile
FROM python:3.9-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
    gcc \
    postgresql-client \
    && rm -rf /var/lib/apt/lists/*
# Install Python dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy application code
COPY . .
# Expose port
EXPOSE 8000
# Run application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Docker Compose

version: '3.8'
services:
  backend:
    build: ./backend
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://postgres:password@db:5432/site_tracking
      - SECRET_KEY=your-secret-key
    depends_on:
      - db
    volumes:
      - ./backend:/app
  frontend:
    build: ./frontend
    ports:
      - "3000:3000"
    environment:
      - NEXT_PUBLIC_API_URL=http://localhost:8000
    depends_on:
      - backend
    volumes:
      - ./frontend:/app
  db:
    image: postgres:13
    environment:
      - POSTGRES_DB=site_tracking
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
volumes:
  postgres_data:

Production Deployment

Environment Configuration

# Production environment variables
DATABASE_URL=postgresql://user:password@localhost:5432/site_tracking
SECRET_KEY=your-production-secret-key
JWT_SECRET_KEY=your-production-jwt-secret
DEBUG=False
ENVIRONMENT=production
ANALYTICS_ENABLED=True
DATA_RETENTION_DAYS=365

Process Management

# Gunicorn configuration
gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app --bind 0.0.0.0:8000
# Systemd service
[Unit]
Description=Open Source Site Tracking Backend
After=network.target
[Service]
Type=exec
User=www-data
Group=www-data
WorkingDirectory=/opt/site-tracking/backend
ExecStart=/opt/site-tracking/venv/bin/gunicorn -w 4 -k uvicorn.workers.UvicornWorker main:app --bind 0.0.0.0:8000
Restart=always
[Install]
WantedBy=multi-user.target

Contributing

Development Workflow

  1. Fork Repository: Fork the project on GitHub
  2. Create Branch: Create feature branch from develop
  3. Develop: Implement your changes
  4. Test: Write and run tests
  5. Document: Update documentation
  6. Submit PR: Create pull request
  7. Review: Address feedback
  8. Merge: Merge into develop branch

Code Style

Backend Standards

  • PEP 8: Follow Python style guide
  • Black: Use Black for formatting
  • Type Hints: Add type annotations
  • Docstrings: Document all functions and classes

Frontend Standards

  • ESLint: Use ESLint for linting
  • Prettier: Use Prettier for formatting
  • TypeScript: Use TypeScript for type safety
  • Components: Follow React best practices

Development Tips

  • Test Locally: Test changes locally before committing
  • Small Commits: Keep commits small and focused
  • Clear Messages: Write clear commit messages
  • Documentation: Update documentation for new features
  • Code Review: Participate in code reviews