Development Environment

This guide provides information for developers who want to contribute to the OnlyAutomator project or customize their own instance.

System Requirements

  • Node.js: v18.x or later
  • npm: v9.x or later
  • PostgreSQL: v14.x or later
  • Git: v2.x or later

Local Development Setup

  1. Clone the repository:
git clone https://github.com/yourorganization/onlyautomator.git
cd onlyautomator
  1. Install dependencies:
npm install
  1. Set up environment variables:
cp .env.example .env.local
Edit .env.local to include your development keys and settings.
  1. Run the development server:
npm run dev
The application will be available at http://localhost:3000.

Project Architecture

OnlyAutomator follows a modular architecture pattern to ensure clean separation of concerns and maintainability.

Key Directories

  • /app - Next.js app router pages and layouts
  • /components - Reusable UI components
  • /lib - Utility functions and shared code
  • /api - API route handlers
  • /services - Business logic services
  • /types - TypeScript type definitions
  • /prisma - Database schema and migrations
  • /public - Static assets

Component Structure

Components follow a consistent structure to ensure maintainability:
// components/ui/Button.tsx
import React from 'react';
import { cn } from '@/lib/utils';

interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
  variant?: 'default' | 'primary' | 'secondary' | 'danger';
  size?: 'sm' | 'md' | 'lg';
  isLoading?: boolean;
}

export const Button: React.FC<ButtonProps> = ({
  children,
  className,
  variant = 'default',
  size = 'md',
  isLoading = false,
  disabled,
  ...props
}) => {
  return (
    <button
      className={cn(
        'rounded-md font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
        {
          'bg-blue-500 text-white hover:bg-blue-600': variant === 'primary',
          'bg-gray-200 text-gray-800 hover:bg-gray-300': variant === 'default',
          'bg-red-500 text-white hover:bg-red-600': variant === 'danger',
          'px-2 py-1 text-sm': size === 'sm',
          'px-4 py-2': size === 'md',
          'px-6 py-3 text-lg': size === 'lg',
          'opacity-75 cursor-not-allowed': isLoading || disabled,
        },
        className
      )}
      disabled={isLoading || disabled}
      {...props}
    >
      {isLoading ? (
        <span className="flex items-center justify-center">
          <svg
            className="animate-spin -ml-1 mr-2 h-4 w-4 text-current"
            xmlns="http://www.w3.org/2000/svg"
            fill="none"
            viewBox="0 0 24 24"
          >
            <circle
              className="opacity-25"
              cx="12"
              cy="12"
              r="10"
              stroke="currentColor"
              strokeWidth="4"
            ></circle>
            <path
              className="opacity-75"
              fill="currentColor"
              d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
            ></path>
          </svg>
          Loading...
        </span>
      ) : (
        children
      )}
    </button>
  );
};

Development Workflow

Git Workflow

We follow a feature branch workflow:
  1. Create a new branch for each feature or fix:
git checkout -b feature/your-feature-name
  1. Make changes and commit them:
git add .
git commit -m "feat: add your feature description"
We use Conventional Commits for commit messages.
  1. Push your branch:
git push origin feature/your-feature-name
  1. Create a Pull Request on GitHub for review.

Continuous Integration

Our CI pipeline runs on each PR and includes:
  • Linting
  • Type checking
  • Unit tests
  • Integration tests
  • Build verification

Data Models

Core Data Models

OnlyAutomator relies on several core data models that drive the application functionality:

API Structure

The API follows RESTful conventions and is organized by resource:

Authentication APIs

  • POST /api/auth/login - Log in a user
  • POST /api/auth/logout - Log out a user
  • POST /api/auth/refresh - Refresh authentication token
  • POST /api/auth/register - Register a new user

User APIs

  • GET /api/users/me - Get current user
  • PATCH /api/users/me - Update current user
  • GET /api/users/me/accounts - Get user accounts

Account APIs

  • GET /api/accounts - List accounts
  • POST /api/accounts - Create account
  • GET /api/accounts/:id - Get account
  • PATCH /api/accounts/:id - Update account
  • DELETE /api/accounts/:id - Delete account
  • GET /api/accounts/:id/fans - Get account fans

Fans APIs

  • GET /api/fans - List fans
  • GET /api/fans/:id - Get fan
  • PATCH /api/fans/:id - Update fan
  • GET /api/fans/:id/stats - Get fan statistics

Subscription APIs

  • GET /api/subscriptions - List subscriptions
  • POST /api/subscriptions - Create subscription
  • GET /api/subscriptions/:id - Get subscription
  • PATCH /api/subscriptions/:id - Update subscription
  • DELETE /api/subscriptions/:id - Cancel subscription

Authentication

We use JWT-based authentication with Supabase:

State Management

We use a combination of approaches for state management:
  1. Server Components - For server-rendered data
  2. React Context - For shared state across components
  3. SWR/React Query - For data fetching and caching
  4. React Hook Form - For form state management
  5. Local State - For component-specific state

Example SWR Usage

import useSWR from 'swr';
import { fetcher } from '@/lib/api';

export const useAccount = (accountId: string) => {
  const { data, error, mutate } = useSWR(
    accountId ? `/api/accounts/${accountId}` : null,
    fetcher
  );

  return {
    account: data,
    isLoading: !error && !data,
    isError: error,
    mutate,
  };
};

Testing Strategy

We use a comprehensive testing strategy to ensure code quality:

Unit Tests

We use Jest for unit testing components and functions:
// components/ui/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

describe('Button component', () => {
  test('renders correctly', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });

  test('handles click events', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click me</Button>);
    fireEvent.click(screen.getByText('Click me'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  test('shows loading state', () => {
    render(<Button isLoading>Click me</Button>);
    expect(screen.getByText('Loading...')).toBeInTheDocument();
  });
});

Integration Tests

We use Cypress for integration tests:
// cypress/e2e/login.cy.js
describe('Login Page', () => {
  beforeEach(() => {
    cy.visit('/login');
  });

  it('should login successfully', () => {
    cy.get('input[name="email"]').type('test@example.com');
    cy.get('input[name="password"]').type('password123');
    cy.get('button[type="submit"]').click();
    cy.url().should('include', '/dashboard');
    cy.contains('Welcome back').should('be.visible');
  });

  it('should show error on invalid credentials', () => {
    cy.get('input[name="email"]').type('test@example.com');
    cy.get('input[name="password"]').type('wrongpassword');
    cy.get('button[type="submit"]').click();
    cy.contains('Invalid credentials').should('be.visible');
  });
});

Performance Considerations

Performance Optimizations

We focus on several key areas for performance:
  1. Code Splitting - Using dynamic imports for route-based code splitting
  2. Image Optimization - Using Next.js Image component for optimized images
  3. Server Components - Leveraging React Server Components for reduced client JS
  4. Lazy Loading - Implementing lazy loading for below-the-fold content
  5. Memoization - Using React.memo and useMemo to prevent unnecessary re-renders

Example Performance Optimization

// Lazy loaded component
import { lazy, Suspense } from 'react';

const DataVisualization = lazy(() => import('@/components/DataVisualization'));

const Dashboard = () => {
  return (
    <div>
      <h1>Dashboard</h1>
      <div className="dashboard-metrics">
        {/* Critical UI rendered immediately */}
        <MetricCard title="Total Users" value={userCount} />
        <MetricCard title="Active Accounts" value={activeAccountCount} />
      </div>
      
      {/* Heavy component lazy loaded */}
      <Suspense fallback={<div>Loading visualization...</div>}>
        <DataVisualization data={visualizationData} />
      </Suspense>
    </div>
  );
};

Deployment

Deployment Process

We use Vercel for deployments with the following pipeline:

Contabo Microservice Deployment

For the specialized microservice backend, we use a Contabo VPS with GitHub Actions for CI/CD:

GitHub Actions Workflow

The deployment is automated through a GitHub Actions workflow defined in .github/workflows/deploy.yml:
name: Deploy Node.js App to Contabo

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Set up Node.js
        uses: actions/setup-node@v3
        with:
          node-version: '18'
          cache: 'yarn'
      
      - name: Install dependencies
        run: yarn install --frozen-lockfile
      
      - name: Build TypeScript
        run: yarn build
      
      - name: Verify build directory
        run: |
          if [ ! -d "build" ]; then
            echo "Build directory does not exist!"
            exit 1
          fi

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to production server
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SSH_HOST }}
          username: ${{ secrets.SSH_USERNAME }}
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          port: 21
          script: |
            # Install necessary tools
            if ! command -v git &> /dev/null; then
              echo "Installing Git..."
              apt-get update && apt-get install -y git
            fi
            
            # Additional deployment script steps...

Required GitHub Secrets

The following secrets need to be configured in your GitHub repository:
  • SSH_HOST - Contabo VPS hostname or IP address
  • SSH_USERNAME - SSH username for the Contabo server
  • SSH_PRIVATE_KEY - Private SSH key for authentication
  • LOGIN_TOKEN - GitHub personal access token for repository access
  • SUPABASE_URL - Supabase project URL
  • SUPABASE_ANON_KEY - Supabase anonymous key
  • SUPABASE_SERVICE_ROLE_KEY - Supabase service role key
  • NEXT_PUBLIC_SITE_URL - Public URL of the web application

Process Manager Configuration

The microservice uses PM2 for process management, with its configuration in ecosystem.config.js:
module.exports = {
    apps: [{
            name: 'express-server',
            script: 'build/app.js',
            instances: 1,
            autorestart: true,
            watch: false,
            env: {
                // Environment variables
            },
        },
        {
            name: 'cron-job',
            script: 'build/cron_job.js',
            cron_restart: '0 */30 * * *', // every 30 minutes
            env: {
                // Environment variables
            },
        },
    ],
};

Manual Deployment (if needed)

In case you need to deploy manually:
  1. SSH into the Contabo server:
ssh username@hostname -p 21
  1. Navigate to the application directory:
cd /var/www/only-automator
  1. Pull the latest changes:
git pull
  1. Install dependencies and build:
yarn install
yarn build
  1. Restart the PM2 processes:
pm2 restart all

Environment Configuration

Each environment has its own configuration:
# Development (.env.development)
NEXT_PUBLIC_API_URL=http://localhost:3000/api
NEXT_PUBLIC_SUPABASE_URL=https://your-dev-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-dev-anon-key

# Staging (.env.staging)
NEXT_PUBLIC_API_URL=https://staging.onlyautomator.com/api
NEXT_PUBLIC_SUPABASE_URL=https://your-staging-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-staging-anon-key

# Production (.env.production)
NEXT_PUBLIC_API_URL=https://app.onlyautomator.com/api
NEXT_PUBLIC_SUPABASE_URL=https://your-prod-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-prod-anon-key

Customization

White Labeling

OnlyAutomator supports white labeling through configuration:
// lib/config.ts
export const siteConfig = {
  name: process.env.NEXT_PUBLIC_SITE_NAME || 'OnlyAutomator',
  description: process.env.NEXT_PUBLIC_SITE_DESCRIPTION || 'OnlyFans automation platform',
  logo: {
    light: process.env.NEXT_PUBLIC_LOGO_LIGHT || '/logo-light.svg',
    dark: process.env.NEXT_PUBLIC_LOGO_DARK || '/logo-dark.svg',
  },
  colors: {
    primary: process.env.NEXT_PUBLIC_PRIMARY_COLOR || '#0070f3',
    secondary: process.env.NEXT_PUBLIC_SECONDARY_COLOR || '#ff4081',
  },
};

Theme Customization

The UI theme can be customized through Tailwind:
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#f0f9ff',
          100: '#e0f2fe',
          // ...other shades
          600: '#0284c7',
          700: '#0369a1',
          800: '#075985',
          900: '#0c4a6e',
        },
        // ...other color definitions
      },
      fontFamily: {
        sans: ['var(--font-inter)', 'sans-serif'],
        mono: ['var(--font-roboto-mono)', 'monospace'],
      },
      // ...other customizations
    },
  },
};

API Extensions

Creating Custom API Routes

To extend the API with custom functionality:
  1. Create a new API route file:
// app/api/custom/route.ts
import { NextResponse } from 'next/server';
import { auth } from '@/lib/auth';

export async function GET(request: Request) {
  // Verify authentication
  const session = await auth.getSession();
  if (!session) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
  }
  
  // Implement custom logic
  const data = {
    customField: 'Custom value',
    timestamp: new Date().toISOString(),
  };
  
  return NextResponse.json(data);
}
  1. Access the API from your components:
// components/CustomData.tsx
'use client';

import useSWR from 'swr';
import { fetcher } from '@/lib/api';

export const CustomData = () => {
  const { data, error } = useSWR('/api/custom', fetcher);
  
  if (error) return <div>Failed to load</div>;
  if (!data) return <div>Loading...</div>;
  
  return (
    <div>
      <h2>Custom Data</h2>
      <pre>{JSON.stringify(data, null, 2)}</pre>
    </div>
  );
};

Contributing Guide

Contribution Process

  1. Fork the Repository - Create your own fork of the code
  2. Create Feature Branch - Make your changes in a new branch
  3. Follow Style Guide - Ensure code matches our style guidelines
  4. Write Tests - Add tests for your changes
  5. Run CI Checks Locally - Ensure all checks pass before submitting
  6. Create Pull Request - Submit a PR with a clear description
  7. Code Review - Address feedback from maintainers
  8. Merge - Once approved, your PR will be merged

Code Style

We use ESLint and Prettier for code formatting:
// .eslintrc.json
{
  "extends": [
    "next/core-web-vitals",
    "plugin:@typescript-eslint/recommended",
    "prettier"
  ],
  "rules": {
    "react/react-in-jsx-scope": "off",
    "react/prop-types": "off",
    "@typescript-eslint/explicit-module-boundary-types": "off",
    "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }],
    "no-console": ["warn", { "allow": ["warn", "error"] }]
  }
}

Further Resources