PUQ Mautic

PUQcloud Panel for developers

A step-by-step guide to creating, integrating and testing modules for PUQcloud Panel. Suitable for both beginners and experienced PHP developers who want to expand the panel, gain a deeper understanding of the architecture and interaction of subsystems.

PUQcloud Module Development Documentation

COMPREHENSIVE GUIDE: Complete documentation for developing professional modules in PUQcloud billing system.

📚 Documentation Structure

This documentation suite provides everything you need to build professional, scalable modules for PUQcloud:

🎯 Start Here

📖 Development Resources

🛠️ Support & Quality

🏗️ PUQcloud Module Architecture


Module System Overview

PUQcloud uses a sophisticated modular architecture with four distinct module types:

App\Modules\Module (Base Class)
├── Product      → Service lifecycle management (hosting, VPS, domains)
├── Plugin       → System extensions (monitoring, analytics, reports)
├── Payment      → Payment gateway integrations (Stripe, PayPal, crypto)
└── Notification → Communication channels (SMTP, SMS, Slack, webhooks)

Class Responsibilities and APIs

Every module is a PHP class inheriting from App\Modules\Module and shares a consistent contract:

Derived Module Types and Typical Methods

Product (Service Management)
Plugin (System Extensions)
Payment (Payment Processing)
Notification (Communication Channels)

Real Module Structure

Every module follows this standardized structure:

modules/{Type}/{ModuleName}/
├── {ModuleName}.php              # Main module class (required)
├── config.php                    # Module metadata (required)
├── hooks.php                     # Event hooks (optional)
├── Controllers/                  # HTTP request handlers
│   └── {ModuleName}Controller.php
├── Services/                     # External API clients
│   └── {ExternalService}Client.php
├── Models/                       # Database models
│   └── {ModuleName}Model.php
├── views/                        # Blade templates
│   ├── admin_area/              # Admin interface views
│   ├── client_area/             # Client interface views
│   └── assets/                  # CSS, JS, images
└── lang/                        # Internationalization
    ├── en.php
    └── pl.php

🚀 Quick Start Examples

Product Module (Service Management)

Real implementation example based on puqNextcloud:

<?php

use App\Models\Service;
use App\Models\Task;
use App\Modules\Product;

class MyNextcloudService extends Product
{
    public function __construct()
    {
        parent::__construct();
    }

    // Product configuration
    public function getProductData(array $data = []): array
    {
        $this->product_data = [
            'group_uuid' => $data['group_uuid'] ?? '',
            'username_prefix' => $data['username_prefix'] ?? 'nc_',
            'username_suffix' => $data['username_suffix'] ?? '',
            'quota' => $data['quota'] ?? '1GB',
        ];
        return $this->product_data;
    }

    // Service creation (asynchronous)
    public function create(): array
    {
        $data = [
            'module' => $this,              // Module instance reference
            'method' => 'createJob',        // Method to execute
            'tries' => 1,                   // Retry attempts
            'backoff' => 60,                // Delay between retries (seconds)
            'timeout' => 600,               // Max execution time
            'maxExceptions' => 1,           // Max exceptions before failure
        ];

        $tags = ['create'];
        
        $service = Service::find($this->service_uuid);
        $service->setProvisionStatus('processing');
        Task::add('ModuleJob', 'Module', $data, $tags);

        return ['status' => 'success'];
    }

    // Actual provisioning logic
    public function createJob(): array
    {
        try {
            $service = Service::find($this->service_uuid);
            
            // Generate service credentials
            $this->service_data['username'] = $this->product_data['username_prefix'] . 
                                             random_int(100000, 999999) . 
                                             $this->product_data['username_suffix'];
            $this->service_data['password'] = generateStrongPassword(10);
            
            // Create API client
            $apiClient = new NextcloudAPIClient($this->getServerConfig());
            
            // Provision account via API
            $response = $apiClient->createUser([
                'userid' => $this->service_data['username'],
                'password' => $this->service_data['password'],
            ]);
            
            if ($response['status'] === 'success') {
                $service->setProvisionStatus('completed');
                $this->logInfo('createJob', 'User created successfully');
                return ['status' => 'success'];
            } else {
                $service->setProvisionStatus('failed');
                $this->logError('createJob', 'User creation failed', $response);
                return ['status' => 'error', 'message' => $response['error']];
            }
            
        } catch (Exception $e) {
            $service->setProvisionStatus('failed');
            $this->logError('createJob', 'Exception occurred', $e->getMessage());
            return ['status' => 'error'];
        }
    }
}

Payment Module (Stripe Integration)

Real implementation example based on puqStripe:

<?php

use App\Modules\Payment;

class MyStripePayment extends Payment
{
    public function getModuleData(array $data = []): array
    {
        return [
            'publishable_key' => $data['publishable_key'] ?? '',
            'secret_key' => $data['secret_key'] ?? '',
            'webhook_secret' => $data['webhook_secret'] ?? '',
            'sandbox' => $data['sandbox'] ?? false,
        ];
    }

    public function getClientAreaHtml(array $data = []): string
    {
        $invoice = $data['invoice'];
        $amount = $invoice->getDueAmountAttribute();
        $currency = $invoice->client->currency->code;

        $stripe = new StripeClient($this->module_data);
        
        $session = $stripe->createSession(
            referenceId: $invoice->uuid,
            invoiceId: $invoice->number,
            description: 'Invoice #' . $invoice->number,
            amount: $amount,
            currency: $currency,
            return_url: $this->getReturnUrl(),
            cancel_url: $this->getCancelUrl(),
        );

        return $this->view('client_area', ['session' => $session]);
    }
}

Notification Module (SMTP Email)

Real implementation example based on puqSMTP:

<?php

use App\Modules\Notification;

class MySMTPNotification extends Notification
{
    public function getModuleData(array $data = []): array
    {
        return [
            'email' => $data['email'] ?? '',
            'server' => $data['server'] ?? '',
            'sender_name' => $data['sender_name'] ?? '',
            'port' => $data['port'] ?? 587,
            'encryption' => $data['encryption'] ?? 'tls',
            'username' => $data['username'] ?? '',
            'password' => $data['password'] ?? '',
        ];
    }

    public function send(array $data = []): array
    {
        try {
            $config = $this->buildMailConfig();
            
            Mail::mailer($config)->send(new NotificationMail(
                $data['to'],
                $data['subject'],
                $data['message']
            ));
            
            $this->logInfo('send', 'Email sent successfully', [
                'to' => $data['to'],
                'subject' => $data['subject']
            ]);
            
            return ['status' => 'success'];
            
        } catch (Exception $e) {
            $this->logError('send', 'Email sending failed', $e->getMessage());
            return ['status' => 'error', 'message' => $e->getMessage()];
        }
    }
}

🔧 Key Implementation Details

Task System (Asynchronous Processing)

Correct Task::add usage:

// Standard task configuration
$data = [
    'module' => $this,                    // Required: module instance
    'method' => 'methodName',             // Required: method to execute
    'tries' => 1,                         // Optional: retry attempts (default: 1)
    'backoff' => 60,                      // Optional: delay between retries
    'timeout' => 600,                     // Optional: max execution time
    'maxExceptions' => 1,                 // Optional: max exceptions
];

$tags = ['operation_type', 'service:' . $this->service_uuid];
Task::add('ModuleJob', 'Module', $data, $tags);

Database Integration

Table creation in activate():

public function activate(): string
{
    try {
        if (!Schema::hasTable('module_servers')) {
            Schema::create('module_servers', function (Blueprint $table) {
                $table->uuid()->primary();
                $table->string('name')->unique();
                $table->string('host');
                $table->string('username');
                $table->string('password');
                $table->boolean('active')->default(true);
                $table->integer('port')->default(443);
                $table->timestamps();
            });
        }
        
        $this->logInfo('activate', 'Module activated successfully');
        return 'success';
    } catch (Exception $e) {
        $this->logError('activate', 'Activation failed: ' . $e->getMessage());
        return 'Error: ' . $e->getMessage();
    }
}

Configuration File

Standard config.php structure:

<?php

return [
    'name' => 'My Module Name',
    'description' => 'Professional module description',
    'version' => '1.0.0',
    'author' => 'Developer Name',
    'email' => 'developer@domain.com',
    'website' => 'https://developer-website.com',
    'logo' => __DIR__ . '/views/assets/img/logo.png',
    'icon' => 'fas fa-server',  // FontAwesome icon
];

📋 Module Types Comparison

 
Feature Product Plugin Payment Notification
Purpose Service lifecycle System extension Payment processing Communication
Base Class App\Modules\Product App\Modules\Plugin App\Modules\Payment App\Modules\Notification
Key Methods create(), suspend(), terminate() activate(), custom methods getClientAreaHtml() send()
Client Area Full interface with tabs None Payment forms Configuration only
Examples Nextcloud, VPS hosting Monitoring, analytics Stripe, PayPal, MonoBank SMTP, SMS

🛠️ Development Environment

Prerequisites

# Required stack
PHP 8.1+
Laravel 9.0+
Composer
MySQL/PostgreSQL
Redis (for queues)
Node.js & NPM (for assets)

# Recommended tools
Docker & Docker Compose
Git
PHPStorm/VSCode

Quick Setup

# 1. Create module directory
mkdir -p modules/Product/MyModule

# 2. Create basic files
cat > modules/Product/MyModule/MyModule.php << 'EOF'
<?php

use App\Modules\Product;

class MyModule extends Product
{
    public function __construct()
    {
        parent::__construct();
    }
}
EOF

# 3. Create configuration
cat > modules/Product/MyModule/config.php << 'EOF'
<?php

return [
    'name' => 'My Module',
    'description' => 'Module description',
    'version' => '1.0.0',
    'author' => 'Your Name',
    'email' => 'your.email@domain.com',
    'website' => 'https://yourdomain.com',
    'icon' => 'fas fa-server',
];
EOF

📖 Next Steps

Learning Path

Development Support

Issue Type Resource Description
Getting Started Development Guide Step-by-step tutorial
Technical Issues FAQ & Troubleshooting Common problems & solutions
API Questions API Reference Method documentation
Best Practices Development Checklist Quality guidelines

⚡ Key Success Factors

🎯 Accuracy First

🔒 Security & Performance

🧪 Quality Assurance

Ready to build professional modules? Start with What is a Module? and create modules that integrate seamlessly with PUQcloud's architecture!

What is a Module?

Introduction

Modules are the foundation of PUQcloud's extensible billing system architecture. They are self-contained, professionally developed components that seamlessly extend the platform's functionality. Each module can add new service types, integrate with external APIs, implement payment gateways, or provide communication channels - all while maintaining consistent performance, security, and user experience standards.

Think of modules as professional plugins that transform PUQcloud from a basic billing system into a comprehensive service management platform capable of handling any business model.

Core Architecture Concept

A module in PUQcloud is a PHP class that inherits from one of four specialized base classes and implements specific business logic. The modular framework provides:

Module System Architecture

Hierarchical Inheritance Structure

PUQcloud's module system follows a clean inheritance hierarchy:

App\Modules\Module (Base Class)
├── Product      → Full service lifecycle management
├── Plugin       → System functionality extensions  
├── Payment      → Payment gateway integrations
└── Notification → Communication channel implementations

Class Responsibilities and APIs

A module is an object-oriented PHP class that inherits from App\Modules\Module and shares a common contract:

Derived Module Types and Typical Methods

Product (Service Management)
Plugin (System Extensions)
Payment (Payment Processing)
Notification (Communication Channels)

Standard Module Structure

Every module follows this professional directory structure:

modules/{Type}/{ModuleName}/
├── {ModuleName}.php              # Main module class (required)
├── config.php                    # Module metadata and settings (required)
├── hooks.php                     # Event hooks and listeners (optional)
├── Controllers/                  # HTTP request handlers
│   └── {ModuleName}Controller.php
├── Services/                     # External API clients and business logic
│   └── {ExternalService}Client.php
├── Models/                       # Database models and data validation
│   ├── {ModuleName}.php
│   └── {ModuleName}Server.php
├── views/                        # User interface templates
│   ├── admin_area/              # Admin panel interface
│   │   ├── product.blade.php
│   │   ├── service.blade.php
│   │   └── configuration.blade.php
│   ├── client_area/             # Client panel interface
│   │   ├── general.blade.php
│   │   ├── statistics.blade.php
│   │   └── management.blade.php
│   └── assets/                  # Static resources
│       ├── css/
│       ├── js/
│       └── img/
└── lang/                        # Internationalization files
    ├── en.php
    ├── pl.php
    └── {language}.php

Component Breakdown

Component Purpose Required Description
Main Class Core business logic and lifecycle management ✅ Required Implements module functionality
Configuration Module metadata, version, and settings ✅ Required Defines module properties
Controllers Handle HTTP requests and API endpoints ⚠️ Conditional Required for admin/client interfaces
Models Database interactions and data validation ⚠️ Conditional Required for data persistence
Views User interface templates and forms ⚠️ Conditional Required for user interaction
Services External API clients and business logic ❌ Optional For third-party integrations
Language Files Multi-language support and localization ❌ Optional For internationalization

Module Types Deep Dive

1. Product Modules 🛡️ (Service Management)

Purpose: Complete service lifecycle management with customer billing integration

Real-World Examples:

Key Capabilities:

Architecture Example (based on real puqNextcloud implementation):

<?php

use App\Models\Service;
use App\Models\Task;
use App\Modules\Product;

class NextcloudService extends Product
{
    // Product configuration for admin
    public function getProductData(array $data = []): array
    {
        $this->product_data = [
            'group_uuid' => $data['group_uuid'] ?? '',
            'username_prefix' => $data['username_prefix'] ?? 'nc_',
            'username_suffix' => $data['username_suffix'] ?? '',
            'quota' => $data['quota'] ?? '1GB',
        ];
        return $this->product_data;
    }

    // Service instance creation
    public function create(): array
    {
        $data = [
            'module' => $this,
            'method' => 'createJob',
            'tries' => 1,
            'backoff' => 60,
            'timeout' => 600,
            'maxExceptions' => 1,
        ];

        $service = Service::find($this->service_uuid);
        $service->setProvisionStatus('processing');
        Task::add('ModuleJob', 'Module', $data, ['create']);

        return ['status' => 'success'];
    }

    // Asynchronous provisioning logic
    public function createJob(): array
    {
        try {
            $service = Service::find($this->service_uuid);
            
            // Generate unique credentials
            $this->service_data['username'] = $this->product_data['username_prefix'] . 
                                             random_int(100000, 999999) . 
                                             $this->product_data['username_suffix'];
            $this->service_data['password'] = generateStrongPassword(10);
            
            // Provision through external API
            $apiClient = new NextcloudAPIClient($this->getServerConfig());
            $result = $apiClient->createUser($this->service_data);
            
            if ($result['status'] === 'success') {
                $service->setProvisionStatus('completed');
                return ['status' => 'success'];
            } else {
                $service->setProvisionStatus('failed');
                return ['status' => 'error', 'message' => $result['error']];
            }
            
        } catch (Exception $e) {
            $service->setProvisionStatus('failed');
            $this->logError('createJob', 'Provisioning failed', $e->getMessage());
            return ['status' => 'error'];
        }
    }

    // Client area interface configuration
    public function getClientAreaMenuConfig(): array
    {
        return [
            'general' => [
                'name' => 'Overview',
                'template' => 'client_area.general',
            ],
            'files' => [
                'name' => 'File Manager',
                'template' => 'client_area.files',
            ],
            'statistics' => [
                'name' => 'Usage Statistics',
                'template' => 'client_area.statistics',
            ],
        ];
    }
}

2. Plugin Modules 🔧 (System Extensions)

Purpose: Extend system functionality without managing individual services

Real-World Examples:

Key Capabilities:

Architecture Example:

<?php

use App\Modules\Plugin;
use Illuminate\Support\Facades\Schema;

class ServerMonitoringPlugin extends Plugin
{
    public function activate(): string
    {
        try {
            // Create monitoring infrastructure
            Schema::create('server_monitoring_checks', function (Blueprint $table) {
                $table->id();
                $table->string('name');
                $table->string('type'); // ping, http, port, ssl
                $table->string('target'); // IP/URL to monitor
                $table->json('config'); // Check-specific configuration
                $table->integer('interval')->default(300); // Check interval in seconds
                $table->boolean('active')->default(true);
                $table->timestamps();
            });

            Schema::create('server_monitoring_results', function (Blueprint $table) {
                $table->id();
                $table->foreignId('check_id')->constrained('server_monitoring_checks');
                $table->boolean('status'); // up/down
                $table->integer('response_time')->nullable(); // milliseconds
                $table->string('error_message')->nullable();
                $table->timestamp('checked_at');
                $table->timestamps();
            });

            $this->logInfo('activate', 'Monitoring plugin activated successfully');
            return 'success';
        } catch (Exception $e) {
            $this->logError('activate', 'Activation failed: ' . $e->getMessage());
            return 'Error: ' . $e->getMessage();
        }
    }

    public function adminWebRoutes(): array
    {
        return [
            [
                'method' => 'get',
                'uri' => 'dashboard',
                'permission' => 'monitoring-dashboard',
                'name' => 'dashboard',
                'controller' => 'ServerMonitoringController@dashboard',
            ],
            [
                'method' => 'get',
                'uri' => 'checks',
                'permission' => 'monitoring-checks',
                'name' => 'checks',
                'controller' => 'ServerMonitoringController@checks',
            ],
        ];
    }
}

3. Payment Modules 💳 (Payment Processing)

Purpose: Secure payment processing through various payment gateways

Real-World Examples:

Key Capabilities:

Architecture Example (based on real puqStripe implementation):

<?php

use App\Modules\Payment;

class StripePaymentGateway extends Payment
{
    public function getModuleData(array $data = []): array
    {
        return [
            'publishable_key' => $data['publishable_key'] ?? '',
            'secret_key' => $data['secret_key'] ?? '',
            'webhook_secret' => $data['webhook_secret'] ?? '',
            'sandbox' => $data['sandbox'] ?? false,
        ];
    }

    public function getClientAreaHtml(array $data = []): string
    {
        $invoice = $data['invoice'];
        $amount = $invoice->getDueAmountAttribute();
        $currency = $invoice->client->currency->code;

        $stripe = new StripeClient($this->module_data);
        
        $session = $stripe->createSession(
            referenceId: $invoice->uuid,
            invoiceId: $invoice->number,
            description: 'Invoice #' . $invoice->number,
            amount: $amount,
            currency: $currency,
            return_url: $this->getReturnUrl(),
            cancel_url: $this->getCancelUrl(),
        );

        return $this->view('client_area', ['session' => $session]);
    }

    public function getSettingsPage(array $data = []): string
    {
        $data['webhook_url'] = route('static.module.post', [
            'type' => 'Payment',
            'name' => 'puqStripe',
            'method' => 'apiWebhookPost',
            'uuid' => $this->payment_gateway_uuid
        ]);

        return $this->view('configuration', $data);
    }
}

4. Notification Modules 📧 (Communication Channels)

Purpose: Send notifications through various communication channels

Real-World Examples:

Key Capabilities:

Architecture Example (based on real puqSMTP implementation):

<?php

use App\Modules\Notification;
use Illuminate\Support\Facades\Mail;

class SMTPNotificationService extends Notification
{
    public function getModuleData(array $data = []): array
    {
        return [
            'email' => $data['email'] ?? '',
            'server' => $data['server'] ?? '',
            'sender_name' => $data['sender_name'] ?? '',
            'port' => $data['port'] ?? 587,
            'encryption' => $data['encryption'] ?? 'tls',
            'username' => $data['username'] ?? '',
            'password' => $data['password'] ?? '',
        ];
    }

    public function send(array $data = []): array
    {
        try {
            $validator = Validator::make($data, [
                'to' => 'required|email',
                'subject' => 'required|string|max:255',
                'message' => 'required|string',
            ]);

            if ($validator->fails()) {
                return [
                    'status' => 'error',
                    'message' => $validator->errors(),
                    'code' => 422,
                ];
            }

            $mailConfig = $this->buildMailerConfiguration();
            
            Mail::mailer($mailConfig)->send(new NotificationMail(
                $data['to'],
                $data['subject'],
                $data['message'],
                $data['attachments'] ?? []
            ));
            
            $this->logInfo('send', 'Email sent successfully', [
                'to' => $data['to'],
                'subject' => $data['subject']
            ]);
            
            return ['status' => 'success'];
            
        } catch (Exception $e) {
            $this->logError('send', 'Email sending failed', $e->getMessage());
            return ['status' => 'error', 'message' => 'Failed to send email'];
        }
    }
}

How Modules Integrate with PUQcloud

Lifecycle Management

Every module follows a standardized lifecycle:

  1. 📦 Installation: Module files are uploaded to the correct directory structure
  2. 🔍 Discovery: System automatically detects and registers the module
  3. ⚙️ Activation: activate() method creates databases, configures settings
  4. 🚀 Operation: Module handles requests and performs designated functions
  5. 🔄 Updates: update() method handles version migrations and schema changes
  6. ⏹️ Deactivation: deactivate() method cleans up resources and data

Integration Points

Admin Interface Integration
// Navigation menu configuration
public function adminSidebar(): array
{
    return [
        [
            'title' => 'Server Management',
            'link' => 'servers',
            'active_links' => ['servers', 'server-groups'],
            'permission' => 'manage-servers'
        ]
    ];
}

// Route definitions with permissions
public function adminWebRoutes(): array
{
    return [
        [
            'method' => 'get',
            'uri' => 'servers',
            'permission' => 'view-servers',
            'name' => 'servers',
            'controller' => 'ServerController@index'
        ]
    ];
}
Client Area Integration
// Multi-tab client interface
public function getClientAreaMenuConfig(): array
{
    return [
        'overview' => [
            'name' => 'Service Overview',
            'template' => 'client_area.overview'
        ],
        'management' => [
            'name' => 'Account Management',
            'template' => 'client_area.management'
        ],
        'statistics' => [
            'name' => 'Usage Statistics',
            'template' => 'client_area.statistics'
        ]
    ];
}

 

 

Development of modules

This section will describe in detail how modules for PUQcloud Panel are created — from the structure to integration with the panel, logic, interface and events. The section is designed for developers who want to expand the functionality of the panel to suit their own needs.

Development of modules

Module Development Guide

Architecture Overview

Module Framework Architecture

PUQcloud uses a hierarchical module system based on inheritance and dynamic loading:

Base Module Class
├── Product Module
├── Plugin Module  
├── Payment Module
└── Notification Module

Core Components

Module Types

Type Purpose Examples
Product Service provisioning and management VPS, Cloud Services
Plugin System extensions and integrations Monitoring, Analytics
Payment Payment gateway integrations Stripe, PayPal, Bank transfers
Notification Communication channels Email, SMS, Slack

Getting Started

Prerequisites

Creating Your First Module

Step 1: Choose Module Type and Name
# Module naming convention: StudlyCaseModuleName (exact filename match required)
# Directory structure: modules/{Type}/{ModuleName}/
mkdir -p modules/Product/MyCloudService
Step 2: Create Professional Module Structure

Based on real PUQcloud modules like puqNextcloud:

modules/Product/MyCloudService/
├── MyCloudService.php            # Main module class (required)
├── config.php                    # Module metadata (required)
├── hooks.php                     # Event hooks (optional)
├── Controllers/                  # HTTP request handlers
│   └── MyCloudServiceController.php
├── Services/                     # External API clients
│   └── CloudAPIClient.php
├── Models/                       # Database models
│   ├── CloudServer.php
│   └── CloudServerGroup.php
├── views/                        # User interface templates
│   ├── admin_area/              # Admin panel views
│   │   ├── product.blade.php
│   │   ├── service.blade.php
│   │   ├── servers.blade.php
│   │   └── configuration.blade.php
│   ├── client_area/             # Client panel views
│   │   ├── general.blade.php
│   │   ├── files.blade.php
│   │   └── statistics.blade.php
│   └── assets/                  # Static resources
│       ├── css/
│       ├── js/
│       └── img/
└── lang/                        # Internationalization
    ├── en.php
    └── pl.php

Module Structure

Main Module Class

<?php

use App\Models\Service;
use App\Models\Task;
use App\Modules\Product;
use Illuminate\Support\Facades\Validator;

class MyCloudService extends Product
{
    public $product_data;
    public $product_uuid;
    public $service_data;
    public $service_uuid;

    public function __construct()
    {
        parent::__construct();
    }

    // Lifecycle Methods
    public function activate(): string
    {
        try {
            // Create necessary database tables
            $this->createTables();
            $this->logInfo('activate', 'Module activated successfully');
            return 'success';
        } catch (Exception $e) {
            $this->logError('activate', 'Activation failed: ' . $e->getMessage());
            return 'error: ' . $e->getMessage();
        }
    }

    public function deactivate(): string
    {
        try {
            // Cleanup resources
            $this->dropTables();
            $this->logInfo('deactivate', 'Module deactivated successfully');
            return 'success';
        } catch (Exception $e) {
            $this->logError('deactivate', 'Deactivation failed: ' . $e->getMessage());
            return 'error: ' . $e->getMessage();
        }
    }

    // Product Configuration
    public function getProductData(array $data = []): array
    {
        $this->product_data = [
            'server_location' => $data['server_location'] ?? '',
            'plan_type' => $data['plan_type'] ?? '',
            'disk_space' => $data['disk_space'] ?? '',
            'bandwidth' => $data['bandwidth'] ?? '',
        ];
        return $this->product_data;
    }

    public function saveProductData(array $data = []): array
    {
        $validator = Validator::make($data, [
            'server_location' => 'required|string',
            'plan_type' => 'required|in:basic,premium,enterprise',
            'disk_space' => 'required|integer|min:1',
            'bandwidth' => 'required|integer|min:1',
        ]);

        if ($validator->fails()) {
            return [
                'status' => 'error',
                'message' => $validator->errors(),
                'code' => 422,
            ];
        }

        return [
            'status' => 'success',
            'data' => $data,
            'code' => 200,
        ];
    }

    // Service Management
    public function getServiceData(array $data = []): array
    {
        $this->service_data = [
            'domain' => $data['domain'] ?? '',
            'username' => $data['username'] ?? '',
            'password' => $data['password'] ?? '',
            'ip_address' => $data['ip_address'] ?? '',
        ];
        return $this->service_data;
    }

    public function saveServiceData(array $data = []): array
    {
        $validator = Validator::make($data, [
            'domain' => 'required|string|max:255',
            'username' => 'required|string|max:50',
            'password' => 'required|string|min:8',
            'ip_address' => 'nullable|ip',
        ]);

        if ($validator->fails()) {
            return [
                'status' => 'error',
                'message' => $validator->errors(),
                'code' => 422,
            ];
        }

        return [
            'status' => 'success',
            'data' => $data,
            'code' => 200,
        ];
    }

    // Asynchronous Operations
    public function create(): array
    {
        $data = [
            'module' => $this,
            'method' => 'createJob',
            'tries' => 1,
            'backoff' => 60,
            'timeout' => 600,
            'maxExceptions' => 1,
        ];

        $service = Service::find($this->service_uuid);
        $service->setProvisionStatus('processing');
        Task::add('ModuleJob', 'Module', $data, ['create']);

        return ['status' => 'success'];
    }

    public function createJob(): array
    {
        try {
            $service = Service::find($this->service_uuid);
            $service->setProvisionStatus('processing');

            // Generate service credentials (real implementation pattern)
            $this->service_data['username'] = $this->product_data['username_prefix'] . 
                                             random_int(100000, 999999) . 
                                             $this->product_data['username_suffix'];
            $this->service_data['password'] = generateStrongPassword(10);
            
            // Create API client and provision account
            $apiClient = new HostingAPIClient($this->config('api_url'), $this->config('api_key'));
            $result = $apiClient->createAccount([
                'domain' => $this->service_data['domain'],
                'username' => $this->service_data['username'],
                'password' => $this->service_data['password'],
            ]);
            
            // Store service data
            $service->setProvisionData($this->service_data);
            
            if ($result['success']) {
                $service->setProvisionStatus('completed');
                $this->logInfo('createJob', 'Service created successfully', $result);
            } else {
                $service->setProvisionStatus('failed');
                $this->logError('createJob', 'Service creation failed', $result);
            }

            return ['status' => $result['success'] ? 'success' : 'error', 'data' => $result];
            
        } catch (Exception $e) {
            $this->logError('createJob', 'Exception in createJob', $e->getMessage());
            return ['status' => 'error', 'message' => $e->getMessage()];
        }
    }

    // Helper method for task queuing (based on real PUQcloud implementation)
    private function queueModuleTask(string $method, array $tags = [], int $tries = 1): array
    {
        $data = [
            'module' => $this,                // Required: module instance
            'method' => $method,              // Required: method to execute
            'tries' => $tries,                // Optional: retry attempts
            'backoff' => 60,                  // Optional: delay between retries
            'timeout' => 600,                 // Optional: max execution time
            'maxExceptions' => 1,             // Optional: max exceptions
        ];

        Task::add('ModuleJob', 'Module', $data, $tags);
        return ['status' => 'success'];
    }

    // Admin Interface Configuration
    public function adminPermissions(): array
    {
        return [
            [
                'name' => 'View Servers',
                'key' => 'view-servers',
                'description' => 'View cloud servers',
            ],
            [
                'name' => 'Manage Servers', 
                'key' => 'manage-servers',
                'description' => 'Create and manage cloud servers',
            ],
        ];
    }

    public function adminSidebar(): array
    {
        return [
            [
                'title' => 'Servers',
                'link' => 'servers',
                'active_links' => ['servers'],
                'permission' => 'view-servers',
            ],
        ];
    }

    public function adminWebRoutes(): array
    {
        return [
            [
                'method' => 'get',
                'uri' => 'servers',
                'permission' => 'view-servers',
                'name' => 'servers',
                'controller' => 'MyCloudServiceController@servers',
            ],
        ];
    }

    // Client Area Configuration
    public function getClientAreaMenuConfig(): array
    {
        return [
            'general' => [
                'name' => 'General',
                'template' => 'client_area.general',
            ],
            'files' => [
                'name' => 'File Manager',
                'template' => 'client_area.files',
            ],
        ];
    }

    public function variables_general(): array
    {
        return [
            'service_data' => $this->service_data,
            'config' => $this->config,
            'module_name' => $this->module_name,
            'service_uuid' => $this->service_uuid,
        ];
    }
}

Configuration File (config.php)

Standard structure based on real PUQcloud modules:

<?php

return [
    'name' => 'My Cloud Service',
    'description' => 'Professional cloud service module with ownPanel integration',
    'version' => '1.0.0',
    'author' => 'Your Name',
    'email' => 'your.email@domain.com',
    'website' => 'https://yourdomain.com',
    'logo' => __DIR__ . '/views/assets/img/logo.png',
    'icon' => 'fas fa-server',
];

Note: Store sensitive configuration in database models with encryption, not in config.php.

Database Model for Server Configuration:

// Models/CloudServer.php
class CloudServer extends Model
{
    protected $fillable = [
        'name', 'host', 'username', 'password', 'api_key', 'active'
    ];

    // Password automatically encrypted when saving
    public function setPasswordAttribute($value)
    {
        $this->attributes['password'] = Crypt::encryptString($value);
    }

    public function getPasswordAttribute($value)
    {
        return Crypt::decryptString($value);
    }
}

Access in module:

// Get server configuration from database
$server = CloudServer::where('active', true)->first();
$apiClient = new CloudAPIClient($server->host, $server->api_key);

Controller Example

<?php

namespace Modules\Product\MyCloudService\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Contracts\View\View;

class MyCloudServiceController extends Controller
{
    public function servers(Request $request): View
    {
        $title = 'Cloud Servers';
        return view_admin_module('Product', 'MyCloudService', 'admin_area.servers', compact('title'));
    }

    public function createServer(Request $request): JsonResponse
    {
        $validator = Validator::make($request->all(), [
            'name' => 'required|string|max:255',
            'ip' => 'required|ip',
            'location' => 'required|string',
        ]);

        if ($validator->fails()) {
            return response()->json([
                'success' => false,
                'errors' => $validator->errors()
            ], 422);
        }

        try {
            // Create server logic
            return response()->json([
                'success' => true,
                'message' => 'Server created successfully'
            ]);
        } catch (Exception $e) {
            return response()->json([
                'success' => false,
                'message' => $e->getMessage()
            ], 500);
        }
    }
}

Development Workflow

Phase 1: Planning and Design

  1. Define Requirements
    - Service features and capabilities
     - External API integrations
     - Data models and relationships
     - User interface requirements
  2. Design Database Schema
// In activate() method - Real PUQcloud pattern
Schema::create('cloud_server_groups', function (Blueprint $table) {
    $table->uuid()->primary();
    $table->string('name')->unique();
    $table->string('description')->nullable();
    $table->timestamps();
});

Schema::create('cloud_servers', function (Blueprint $table) {
    $table->uuid()->primary();
    $table->uuid('group_uuid');
    $table->string('name')->unique();
    $table->string('host');
    $table->string('username');
    $table->text('password'); // Encrypted using Crypt::encryptString()
    $table->text('api_key')->nullable(); // Encrypted API key
    $table->boolean('active')->default(true);
    $table->boolean('ssl')->default(true);
    $table->integer('port')->default(443);
    $table->integer('max_accounts')->default(0);
    $table->timestamps();

    $table->foreign('group_uuid')->references('uuid')->on('cloud_server_groups');
    $table->index(['active', 'group_uuid']);
});

Phase 2: Implementation

  1. Create Module Structure
  2. Implement Core Methods
  3. Add Controllers and Views
  4. Configure Routes and Permissions
  5. Add Language Files

Phase 3: Testing

  1. Unit Tests
  2. Integration Tests
  3. Manual Testing
  4. Performance Testing

Phase 4: Deployment

  1. Install Module
  2. Configure Settings
  3. Activate Module
  4. Verify Functionality

Best Practices


Error Handling

public function provisionAccount(): array
{
    try {
        $this->validateConfiguration();
        $result = $this->callExternalAPI();
        $this->validateResponse($result);
        
        return ['success' => true, 'data' => $result];
        
    } catch (InvalidArgumentException $e) {
        $this->logError('provision', 'Configuration error', $e->getMessage());
        return ['success' => false, 'error' => 'Invalid configuration'];
        
    } catch (GuzzleException $e) {
        $this->logError('provision', 'API error', $e->getMessage());
        return ['success' => false, 'error' => 'External service unavailable'];
        
    } catch (Exception $e) {
        $this->logError('provision', 'Unexpected error', $e->getMessage());
        return ['success' => false, 'error' => 'Internal error occurred'];
    }
}

Security

public function saveServiceData(array $data = []): array
{
    // Sanitize input
    $data = array_map('trim', $data);
    
    // Validate with strict rules
    $validator = Validator::make($data, [
        'username' => 'required|alpha_dash|min:3|max:20',
        'password' => 'required|min:8|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/',
        'domain' => 'required|regex:/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.[a-zA-Z]{2,}$/',
    ]);

    // Encrypt sensitive data
    $data['password'] = encrypt($data['password']);
    
    return ['status' => 'success', 'data' => $data];
}

Performance

use Illuminate\Support\Facades\Cache;

public function getServerList(): array
{
    return Cache::remember('cloud_servers', 300, function () {
        return $this->apiClient->getServers();
    });
}

Logging

private function logOperation(string $operation, array $context = []): void
{
    $this->logInfo($operation, array_merge([
        'module' => $this->module_name,
        'service_uuid' => $this->service_uuid,
        'timestamp' => now()->toISOString(),
        'user_id' => auth()->id(),
    ], $context));
}

Advanced Features


Professional API Client Implementation

Based on real puqNextcloud API client pattern:

<?php

namespace Modules\Product\MyCloudService\Services;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Support\Facades\Crypt;

class CloudAPIClient
{
    private Client $client;
    private string $apiKey;
    private string $baseUrl;
    private string $username;
    private string $password;

    public function __construct(array $serverConfig)
    {
        $this->baseUrl = "https://{$serverConfig['host']}:{$serverConfig['port']}";
        $this->apiKey = $serverConfig['api_key'] ?? '';
        $this->username = $serverConfig['username'];
        $this->password = Crypt::decryptString($serverConfig['password']);
        
        $this->client = new Client([
            'base_uri' => $this->baseUrl,
            'timeout' => 30,
            'connect_timeout' => 10,
            'headers' => [
                'Authorization' => 'Bearer ' . $this->apiKey,
                'Content-Type' => 'application/json',
                'User-Agent' => 'PUQcloud-Module/1.0',
                'Accept' => 'application/json',
            ],
        ]);
    }

    public function createAccount(array $data): array
    {
        return $this->makeRequest('POST', '/accounts', $data);
    }

    public function suspendAccount(string $username): array
    {
        return $this->makeRequest('PUT', "/accounts/{$username}/suspend");
    }

    public function deleteAccount(string $username): array
    {
        return $this->makeRequest('DELETE', "/accounts/{$username}");
    }

    private function makeRequest(string $method, string $endpoint, array $data = []): array
    {
        try {
            $options = [];
            if (!empty($data)) {
                $options['json'] = $data;
            }

            $response = $this->client->request($method, $endpoint, $options);
            
            $body = $response->getBody()->getContents();
            $result = json_decode($body, true);

            return [
                'status' => 'success',
                'data' => $result,
                'http_code' => $response->getStatusCode(),
            ];

        } catch (GuzzleException $e) {
            return [
                'status' => 'error',
                'error' => $e->getMessage(),
                'http_code' => $e->getCode(),
            ];
        }
    }
}

Event Handling

use Illuminate\Support\Facades\Event;

public function createJob(): array
{
    Event::dispatch('cloud.account.creating', [
        'module' => $this->module_name,
        'service_uuid' => $this->service_uuid,
    ]);

    $result = $this->provisionAccount();

    Event::dispatch('hcloud.account.created', [
        'module' => $this->module_name,
        'service_uuid' => $this->service_uuid,
        'success' => $result['success'],
    ]);

    return $result;
}

Testing


Unit Tests

<?php

namespace Tests\Modules\Product\MyHostingService;

use Tests\TestCase;
use MyHostingService;

class MyHostingServiceTest extends TestCase
{
    private MyHostingService $module;

    protected function setUp(): void
    {
        parent::setUp();
        $this->module = new MyHostingService();
    }

    public function test_product_data_validation()
    {
        $data = [
            'server_location' => 'US-East',
            'plan_type' => 'premium',
            'disk_space' => 100,
            'bandwidth' => 1000,
        ];

        $result = $this->module->saveProductData($data);

        $this->assertEquals('success', $result['status']);
        $this->assertEquals(200, $result['code']);
    }

    public function test_invalid_product_data_rejected()
    {
        $data = [
            'server_location' => '',
            'plan_type' => 'invalid',
            'disk_space' => -1,
        ];

        $result = $this->module->saveProductData($data);

        $this->assertEquals('error', $result['status']);
        $this->assertEquals(422, $result['code']);
    }
}

Integration Tests

public function test_complete_service_lifecycle()
{
    // Create service
    $this->module->setServiceUuid('test-uuid');
    $result = $this->module->create();
    $this->assertEquals('success', $result['status']);

    // Suspend service
    $result = $this->module->suspend();
    $this->assertEquals('success', $result['status']);

    // Unsuspend service
    $result = $this->module->unsuspend();
    $this->assertEquals('success', $result['status']);

    // Terminate service
    $result = $this->module->termination();
    $this->assertEquals('success', $result['status']);
}

Troubleshooting


Common Issues

Module Not Loading
Routes Not Working
Jobs Not Processing

Debugging Tools

// Add to any method for debugging
$this->logDebug('method_name', [
    'input' => $data,
    'service_uuid' => $this->service_uuid,
    'stack_trace' => debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5),
]);

Performance Monitoring

private function measurePerformance(callable $operation, string $operationName): mixed
{
    $startTime = microtime(true);
    $startMemory = memory_get_usage();

    $result = $operation();

    $endTime = microtime(true);
    $endMemory = memory_get_usage();

    $this->logInfo('performance', [
        'operation' => $operationName,
        'execution_time' => round(($endTime - $startTime) * 1000, 2) . 'ms',
        'memory_usage' => round(($endMemory - $startMemory) / 1024, 2) . 'KB',
        'peak_memory' => round(memory_get_peak_usage() / 1024 / 1024, 2) . 'MB',
    ]);

    return $result;
}

This comprehensive guide provides everything needed to develop professional modules for PUQcloud. Follow these patterns and best practices to ensure your modules are secure, performant, and maintainable.

Development of modules

API Reference

Base Module Class Methods

Core Methods

__construct()

Initializes the module instance. Always call parent::__construct() first.

public function __construct()
{
    parent::__construct();
    // Custom initialization logic here
}
Lifecycle Methods
activate(): string

Called when module is activated. Should set up database tables, initial configuration.

Returns: 'success' or error message

public function activate(): string
{
    try {
        Schema::create('module_table', function (Blueprint $table) {
            $table->id();
            $table->uuid('service_uuid');
            $table->string('external_id')->nullable();
            $table->enum('status', ['active', 'suspended', 'terminated']);
            $table->timestamps();
            
            $table->foreign('service_uuid')->references('uuid')->on('services');
            $table->index(['service_uuid', 'status']);
        });
        
        $this->logInfo('activate', 'Module activated successfully');
        return 'success';
    } catch (Exception $e) {
        $this->logError('activate', 'Activation failed: ' . $e->getMessage());
        return 'Error: ' . $e->getMessage();
    }
}
deactivate(): string

Called when module is deactivated. Should clean up resources.

Returns: 'success' or error message

public function deactivate(): string
{
    try {
        Schema::dropIfExists('module_table');
        $this->logInfo('deactivate', 'Module deactivated successfully');
        return 'success';
    } catch (Exception $e) {
        $this->logError('deactivate', 'Deactivation failed: ' . $e->getMessage());
        return 'Error: ' . $e->getMessage();
    }
}
update(): string

Called when module version changes. Handle data migrations here.

Returns: 'success' or error message

public function update(): string
{
    try {
        $currentVersion = $this->getCurrentVersion();
        
        if (version_compare($currentVersion, '1.1.0', '<')) {
            Schema::table('module_table', function (Blueprint $table) {
                $table->string('new_field')->nullable();
            });
        }
        
        return 'success';
    } catch (Exception $e) {
        return 'Error: ' . $e->getMessage();
    }
}

Product Module Methods

Configuration Methods

getProductData(array $data = []): array

Processes and stores product configuration data.

Parameters:

Returns: Processed configuration array

public function getProductData(array $data = []): array
{
    $this->product_data = [
        'server_location' => $data['server_location'] ?? '',
        'plan_type' => $data['plan_type'] ?? '',
        'disk_space' => $data['disk_space'] ?? '',
        'bandwidth' => $data['bandwidth'] ?? '',
    ];
    return $this->product_data;
}
saveProductData(array $data = []): array

Validates and saves product configuration.

Parameters:

Returns:

[
    'status' => 'success|error',
    'message' => 'Error messages if validation fails',
    'code' => 200|422,
    'data' => $validatedData
]

Example:

public function saveProductData(array $data = []): array
{
    $validator = Validator::make($data, [
        'server_location' => 'required|string',
        'plan_type' => 'required|in:basic,premium,enterprise',
        'disk_space' => 'required|integer|min:1',
        'bandwidth' => 'required|integer|min:1',
    ]);

    if ($validator->fails()) {
        return [
            'status' => 'error',
            'message' => $validator->errors(),
            'code' => 422,
        ];
    }

    return [
        'status' => 'success',
        'data' => $data,
        'code' => 200,
    ];
}
getProductPage(): string

Returns HTML for product configuration page in admin area.

Returns: Rendered HTML string

Notes: Should use view() to render Blade templates and pass validated configuration.

public function getProductPage(): string
{
    return $this->view('admin_area.product', [
        'config' => $this->product_data,
        'validation' => [/* rules, hints */],
    ]);
}
Service Methods
getServiceData(array $data = []): array

Processes service instance data.

Parameters:

Returns: Processed service data

Example:

public function getServiceData(array $data = []): array
{
    $this->service_data = [
        'domain' => strtolower(trim($data['domain'] ?? '')),
        'username' => trim($data['username'] ?? ''),
        'notes' => $data['notes'] ?? null,
    ];
    return $this->service_data;
}
saveServiceData(array $data = []): array

Validates and saves service configuration.

Returns: Same format as saveProductData()

Example:

public function saveServiceData(array $data = []): array
{
    $validator = Validator::make($data, [
        'domain' => 'required|regex:/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.[a-zA-Z]{2,}$/',
        'username' => 'required|alpha_dash|min:3|max:20',
    ]);

    if ($validator->fails()) {
        return [
            'status' => 'error',
            'message' => $validator->errors(),
            'code' => 422,
        ];
    }

    return [
        'status' => 'success',
        'data' => $data,
        'code' => 200,
    ];
}
getServicePage(): string

Returns HTML for service configuration page.

Returns: Rendered HTML string

Service Lifecycle Methods

create(): array

Initiates service creation process (usually queues a job).

Returns:

['status' => 'success|error', 'message' => 'Optional message']

Example:

public function create(): array
{
    $data = [
        'module' => $this,              // Module instance reference
        'method' => 'createJob',        // Method to execute
        'tries' => 1,                   // Retry attempts
        'backoff' => 60,                // Delay between retries (seconds)
        'timeout' => 600,               // Max execution time
        'maxExceptions' => 1,           // Max exceptions before failure
    ];
    
    $service = Service::find($this->service_uuid);
    $service->setProvisionStatus('processing');
    Task::add('ModuleJob', 'Module', $data, ['create']);
    return ['status' => 'success'];
}
createJob(): array

Actual service creation logic executed asynchronously.

Returns: Status array

Example:

public function createJob(): array
{
    try {
        $service = Service::find($this->service_uuid);
        $service->setProvisionStatus('processing');
        
        // Your service creation logic here
        // ...
        
        $service->setProvisionStatus('completed');
        $this->logInfo('createJob', 'Service created successfully');
        return ['status' => 'success'];
        
    } catch (Exception $e) {
        $service->setProvisionStatus('failed');
        $this->logError('createJob', 'Service creation failed: ' . $e->getMessage());
        return ['status' => 'error', 'message' => 'Service creation failed'];
    }
}
suspend(): array

Initiates service suspension.

public function suspend(): array
{
    $service = Service::find($this->service_uuid);
    $service->setProvisionStatus('processing');

    Task::add('ModuleJob', 'Module', [
        'module' => $this,
        'method' => 'suspendJob',
        'tries' => 1,
        'backoff' => 60,
        'timeout' => 600,
        'maxExceptions' => 1,
    ], ['suspend', 'service:' . $this->service_uuid]);

    return ['status' => 'success'];
}
suspendJob(): array

Actual suspension logic.

public function suspendJob(): array
{
    try {
        $api = new ExternalAPI($this->getServerConfig());
        $result = $api->suspend($this->service_data);

        if ($result['status'] === 'success') {
            Service::find($this->service_uuid)->setProvisionStatus('suspended');
            $this->logInfo('suspendJob', 'Service suspended');
            return ['status' => 'success'];
        }

        Service::find($this->service_uuid)->setProvisionStatus('failed');
        return ['status' => 'error', 'message' => $result['error'] ?? 'Unknown error'];
    } catch (Exception $e) {
        Service::find($this->service_uuid)->setProvisionStatus('failed');
        $this->logError('suspendJob', 'Suspension failed', $e->getMessage());
        return ['status' => 'error', 'message' => 'Suspension failed'];
    }
}
unsuspend(): array

Initiates service reactivation.

public function unsuspend(): array
{
    Task::add('ModuleJob', 'Module', [
        'module' => $this,
        'method' => 'unsuspendJob',
        'tries' => 1,
        'backoff' => 60,
        'timeout' => 600,
        'maxExceptions' => 1,
    ], ['unsuspend', 'service:' . $this->service_uuid]);

    return ['status' => 'success'];
}
unsuspendJob(): array

Actual reactivation logic.

public function unsuspendJob(): array
{
    try {
        $api = new ExternalAPI($this->getServerConfig());
        $result = $api->unsuspend($this->service_data);
        if ($result['status'] === 'success') {
            Service::find($this->service_uuid)->setProvisionStatus('completed');
            return ['status' => 'success'];
        }
        return ['status' => 'error', 'message' => $result['error'] ?? 'Unknown error'];
    } catch (Exception $e) {
        $this->logError('unsuspendJob', 'Reactivation failed', $e->getMessage());
        return ['status' => 'error'];
    }
}
termination(): array

Initiates service termination.

public function termination(): array
{
    Task::add('ModuleJob', 'Module', [
        'module' => $this,
        'method' => 'terminationJob',
        'tries' => 1,
        'backoff' => 60,
        'timeout' => 600,
        'maxExceptions' => 1,
    ], ['terminate', 'service:' . $this->service_uuid]);
    return ['status' => 'success'];
}
terminationJob(): array

Actual termination logic.

public function terminationJob(): array
{
    try {
        $api = new ExternalAPI($this->getServerConfig());
        $result = $api->terminate($this->service_data);
        if ($result['status'] === 'success') {
            Service::find($this->service_uuid)->setProvisionStatus('terminated');
            return ['status' => 'success'];
        }
        return ['status' => 'error', 'message' => $result['error'] ?? 'Unknown error'];
    } catch (Exception $e) {
        $this->logError('terminationJob', 'Termination failed', $e->getMessage());
        return ['status' => 'error'];
    }
}
change_package(): array

Initiates package/plan change.

public function change_package(): array
{
    Task::add('ModuleJob', 'Module', [
        'module' => $this,
        'method' => 'change_packageJob',
        'tries' => 1,
        'backoff' => 60,
        'timeout' => 600,
        'maxExceptions' => 1,
    ], ['change_package', 'service:' . $this->service_uuid]);
    return ['status' => 'success'];
}
change_packageJob(): array

Actual package change logic.

public function change_packageJob(): array
{
    try {
        $api = new ExternalAPI($this->getServerConfig());
        $result = $api->changePackage([
            'plan' => $this->service_data['plan'] ?? 'basic',
            'bandwidth' => $this->product_data['bandwidth'] ?? null,
        ]);
        if ($result['status'] === 'success') {
            $this->logInfo('change_packageJob', 'Plan changed');
            return ['status' => 'success'];
        }
        return ['status' => 'error', 'message' => $result['error'] ?? 'Unknown error'];
    } catch (Exception $e) {
        $this->logError('change_packageJob', 'Change failed', $e->getMessage());
        return ['status' => 'error'];
    }
}

Plugin Module Methods

Lifecycle

activate(): string — create required tables, seed defaults. Must be idempotent.

deactivate(): string — drop or archive resources safely.

update(): string — migrate schema/config between versions.

Admin Interface

adminSidebar(): array — sidebar entries. See format in Admin Interface Methods.

adminWebRoutes(): array — web routes with permissions.

adminApiRoutes(): array — API routes for AJAX.

public function adminWebRoutes(): array
{
    return [
        [
            'method' => 'get',
            'uri' => 'dashboard',
            'permission' => 'monitoring-dashboard',
            'name' => 'dashboard',
            'controller' => 'MonitoringController@dashboard',
        ],
    ];
}

Background Work

Schedule jobs via Task::add() and listen to events in hooks.php.

Task::add('ModuleJob', 'Module', [
    'module' => $this,
    'method' => 'collectMetrics',
], ['metrics']);

Payment Module Methods

Configuration

getModuleData(array $data = []): array — normalize gateway settings.

Parameters: gateway keys, secrets, webhook secrets, sandbox flag.

Returns: sanitized config array.

Client UI

getClientAreaHtml(array $data = []): string — render payment form/session.

Parameters: $data['invoice'], optional customer/context.

Returns: rendered HTML.

public function getClientAreaHtml(array $data = []): string
{
    $invoice = $data['invoice'];
    $session = (new StripeClient($this->module_data))->createSession(
        referenceId: $invoice->uuid,
        invoiceId: $invoice->number,
        description: 'Invoice #' . $invoice->number,
        amount: $invoice->getDueAmountAttribute(),
        currency: $invoice->client->currency->code,
        return_url: $this->getReturnUrl(),
        cancel_url: $this->getCancelUrl(),
    );
    return $this->view('client_area', ['session' => $session]);
}

Settings Page

getSettingsPage(array $data = []): string — render admin configuration; should provide generated webhook_url.

Webhooks

apiWebhookPost(Request $request): Response — process gateway callbacks (POST).

apiWebhookGet(Request $request): Response — optional GET verification.

public function apiWebhookPost(Request $request): Response
{
    $payload = $request->all();
    // Verify signature and handle events
    switch ($payload['type'] ?? '') {
        case 'payment_intent.succeeded':
            return $this->onPaymentSuccess($payload);
        case 'payment_intent.payment_failed':
            return $this->onPaymentFailed($payload);
        default:
            return response('ok', 200);
    }
}

Payment Actions

onPaymentSuccess(array $payload): Response|array — mark invoice paid, log transaction.

onPaymentFailed(array $payload): Response|array — record failure.

refund(string $transactionId, int|float $amount): array — optional refund handler.

Notification Module Methods

Configuration

getModuleData(array $data = []): array — server, port, encryption, credentials, sender.

Delivery

send(array $data = []): array — send message through the channel.

Parameters:

Returns: ['status' => 'success'] or error with message/code.

public function send(array $data = []): array
{
    $validator = Validator::make($data, [
        'to' => 'required|email',
        'subject' => 'required|string|max:255',
        'message' => 'required|string',
    ]);
    if ($validator->fails()) {
        return ['status' => 'error', 'message' => $validator->errors(), 'code' => 422];
    }
    $mailer = $this->buildMailerConfiguration();
    Mail::mailer($mailer)->send(new NotificationMail(
        $data['to'], $data['subject'], $data['message'], $data['attachments'] ?? []
    ));
    return ['status' => 'success'];
}

Common Module Properties

The following properties are available to all module instances:

Webhook Endpoints

Modules can expose webhook endpoints via static routes. Typical signature:

// POST endpoint
public function apiWebhookPost(Request $request): Response
{
    // Validate, authenticate, process, respond
}

// GET endpoint
public function apiWebhookGet(Request $request): Response
{
    return response('ok', 200);
}

Admin Interface Methods

adminPermissions(): array

Defines module permissions for admin users.

Returns:

[
    [
        'name' => 'Permission Display Name',
        'key' => 'permission-key',
        'description' => 'Permission description'
    ]
]

Example:

public function adminPermissions(): array
{
    return [
        [
            'name' => 'View Servers',
            'key' => 'view-servers',
            'description' => 'View hosting servers',
        ],
        [
            'name' => 'Manage Servers', 
            'key' => 'manage-servers',
            'description' => 'Create and manage hosting servers',
        ],
    ];
}
adminSidebar(): array

Defines sidebar menu items in admin area.

Returns:

[
    [
        'title' => 'Menu Title',
        'link' => 'route-name',
        'active_links' => ['route1', 'route2'],
        'permission' => 'required-permission-key'
    ]
]

Active state handling:

Working example:

public function adminWebRoutes(): array
{
    return [
        [
            'method' => 'get',
            'uri' => 'servers',
            'permission' => 'view-servers',
            'name' => 'servers',
            'controller' => 'ServerController@index',
        ],
        [
            'method' => 'get',
            'uri' => 'server-groups',
            'permission' => 'view-server-groups',
            'name' => 'server-groups',
            'controller' => 'ServerGroupController@index',
        ],
    ];
}

public function adminSidebar(): array
{
    return [
        [
            'title' => 'Server Management',
            'link' => 'servers', // resolves by route name
            'active_links' => ['servers', 'server-groups'],
            'permission' => 'manage-servers',
        ],
    ];
}
public function adminSidebar(): array
{
    return [
        [
            'title' => 'Diagnostics',
            'link' => 'diag', // NOT present in adminWebRoutes()
            'active_links' => ['diag'],
            'permission' => 'view-diagnostics',
        ],
    ];
}
// Result: menu renders an href like /admin/modules/Product/YourModule/diag
// Since no route exists, navigation will fail (404). Keep link and routes consistent.
adminWebRoutes(): array

Defines web routes for admin area.

Returns:

[
    [
        'method' => 'get|post|put|delete',
        'uri' => 'route/path',
        'permission' => 'required-permission',
        'name' => 'route.name',
        'controller' => 'ControllerName@methodName'
    ]
]
adminApiRoutes(): array

Defines API routes for admin area.

Returns: Same format as adminWebRoutes()

Client Area Methods

getClientAreaMenuConfig(): array

Defines client area menu tabs.

Returns:

[
    'tab_key' => [
        'name' => 'Tab Display Name',
        'template' => 'client_area.template_name'
    ]
]

Example:

public function getClientAreaMenuConfig(): array
{
    return [
        'general' => [
            'name' => 'General',
            'template' => 'client_area.general',
        ],
        'files' => [
            'name' => 'File Manager',
            'template' => 'client_area.files',
        ],
    ];
}

Note: Each tab requires a corresponding variables_{tab_name}() method to provide data to the template.

variables_{tab_name}(): array

Provides variables for specific client area tab.

Returns: Array of variables for the template

Example:

public function variables_general(): array
{
    return [
        'service_data' => $this->service_data,
        'config' => $this->config,
        'status' => $this->getServiceStatus(),
    ];
}
controllerClient_{tab_name}{Method}(Request $request): JsonResponse

Handles AJAX requests from client area.

Parameters:

Returns: JsonResponse

Example:

public function controllerClient_generalGet(Request $request): JsonResponse
{
    try {
        $data = $this->getServiceDetails();
        return response()->json([
            'success' => true,
            'data' => $data,
        ]);
    } catch (Exception $e) {
        return response()->json([
            'success' => false,
            'message' => $e->getMessage(),
        ], 500);
    }
}

Utility Methods

view(string $template, array $data = []): string

Renders module template.

Parameters:

Returns: Rendered HTML

Example:

public function getServicePage(): string
{
    return $this->view('admin_area.service', [
        'service_data' => $this->service_data,
        'config' => $this->config,
    ]);
}
config(string $key): mixed

Gets configuration value from config.php.

Parameters:

Returns: Configuration value

Example:

$apiUrl = $this->config('api_url');
$apiKey = $this->config('api_key');

Logging Methods

logInfo(string $action, array|string $request = [], array|string $response = []): void

Logs informational message.

Example:

$this->logInfo('service_created', [
    'service_uuid' => $this->service_uuid,
    'product_data' => $this->product_data,
], ['status' => 'success']);
logError(string $action, array|string $request = [], array|string $response = []): void

Logs error message.

Example:

$this->logError('api_call_failed', [
    'endpoint' => '/api/create',
    'data' => $requestData,
], ['error' => $e->getMessage()]);
logDebug(string $action, mixed $request = [], mixed $response = []): void

Logs debug message.

Example:

$this->logDebug('processing_step', [
    'step' => 'validation',
    'data' => $inputData,
]);

 

Task System


Task::add()

Queues a background job.

Task::add($jobName, $queue, $inputData, $tags);

Parameters:

Job Data Structure for ModuleJob:

$data = [
    'module' => $this,                   // Module instance (required)
    'method' => 'methodToCall',          // Method name to execute (required)
    'tries' => 1,                        // Number of retry attempts (default: 1)
    'backoff' => 60,                     // Delay between retries in seconds (default: 10)
    'timeout' => 600,                    // Maximum execution time in seconds (default: 600)
    'maxExceptions' => 1,                // Max exceptions before failure (default: 2, recommended: 1)
];

// Tags for job identification and filtering
$tags = ['create', 'service:' . $this->service_uuid];

Task::add('ModuleJob', 'Module', $data, $tags);

Complete Example:

public function create(): array
{
    $data = [
        'module' => $this,
        'method' => 'createJob',
        'tries' => 1,
        'backoff' => 60,
        'timeout' => 600,
        'maxExceptions' => 1,
    ];
    
    $tags = [
        'create',
        'hosting',
        'service:' . $this->service_uuid,
    ];
    
    $service = Service::find($this->service_uuid);
    $service->setProvisionStatus('processing');
    Task::add('ModuleJob', 'Module', $data, $tags);
    return ['status' => 'success'];
}

Helper Functions


view_admin_module()

Renders admin module view.

view_admin_module($type, $name, $view, $data = [], $mergeData = [])

Parameters:

Example:

public function dashboard(Request $request): View
{
    $title = 'Server Dashboard';
    return view_admin_module('Product', 'MyHostingService', 'admin_area.dashboard', compact('title'));
}
logModule()

Direct logging function.

logModule($type, $name, $action, $level, $request = [], $response = [])

Parameters:

Service Model Methods


setProvisionStatus()

Updates service provisioning status.

$service->setProvisionStatus($status);

Status Values:

Example:

public function createJob(): array
{
    try {
        $service = Service::find($this->service_uuid);
        $service->setProvisionStatus('processing');
        
        // Generate service credentials
        $this->service_data['username'] = $this->product_data['username_prefix'] . 
                                         random_int(100000, 999999) . 
                                         $this->product_data['username_suffix'];
        $this->service_data['password'] = generateStrongPassword(10);
        
        // Create API client and provision account
        $apiClient = new HostingAPIClient($this->config('api_url'), $this->config('api_key'));
        $result = $apiClient->createAccount([
            'domain' => $this->service_data['domain'],
            'username' => $this->service_data['username'],
            'password' => $this->service_data['password'],
        ]);
        
        if ($result['status'] === 'success') {
            $service->setProvisionData($this->service_data);
            $service->setProvisionStatus('completed');
            $this->logInfo('createJob', 'Service created successfully');
            return ['status' => 'success'];
        } else {
            $service->setProvisionStatus('failed');
            $this->logError('createJob', 'Service creation failed', $result);
            return ['status' => 'error', 'message' => $result['error']];
        }
        
    } catch (Exception $e) {
        $service->setProvisionStatus('failed');
        $this->logError('createJob', 'Exception occurred', $e->getMessage());
        return ['status' => 'error', 'message' => 'Service creation failed'];
    }
}

Validation Rules


Common Validation Patterns

// Domain validation
'domain' => 'required|regex:/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.[a-zA-Z]{2,}$/',

// Username validation
'username' => 'required|alpha_dash|min:3|max:20',

// Strong password
'password' => 'required|min:8|regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/',

// IP address
'ip' => 'nullable|ip',

// Plan/package validation
'plan' => 'required|in:basic,premium,enterprise',

// Disk space (in GB)
'disk_space' => 'required|integer|min:1|max:1000',

Error Handling Patterns


Standard Error Response

return [
    'status' => 'error',
    'message' => 'User-friendly error message',
    'errors' => $validator->errors(), // For validation errors
    'code' => 422, // HTTP status code
];

Exception Handling in Jobs

public function createJob(): array
{
    try {
        // Job logic here
        return ['status' => 'success'];
        
    } catch (ExternalServiceException $e) {
        $this->logError('createJob', 'External service error', $e->getMessage());
        return ['status' => 'error', 'message' => 'Service temporarily unavailable'];
        
    } catch (ValidationException $e) {
        $this->logError('createJob', 'Validation error', $e->errors());
        return ['status' => 'error', 'message' => 'Invalid data provided'];
        
    } catch (Exception $e) {
        $this->logError('createJob', 'Unexpected error', $e->getMessage());
        return ['status' => 'error', 'message' => 'An unexpected error occurred'];
    }
}

Performance Best Practices


Caching

use Illuminate\Support\Facades\Cache;

// Cache expensive operations
$servers = Cache::remember("module_{$this->module_name}_servers", 300, function() {
    return $this->fetchServersFromAPI();
});

// Cache with tags for easier invalidation
Cache::tags(['module', $this->module_name])->put('key', $value, 300);

// Invalidate cache
Cache::tags(['module', $this->module_name])->flush();

Database Optimization

// Use database transactions for multiple operations
DB::transaction(function() use ($data) {
    $this->createUser($data);
    $this->assignPermissions($data);
    $this->sendWelcomeEmail($data);
});

// Eager loading relationships
$services = Service::with(['product', 'customer'])->get();

Memory Management

// Process large datasets in chunks
Service::chunk(100, function($services) {
    foreach ($services as $service) {
        $this->processService($service);
    }
});

Security Guidelines


Input Sanitization

// Always sanitize input
$data = array_map('trim', $data);
$data = array_map('strip_tags', $data);

// For HTML content, use proper escaping
$safeHtml = htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');

Sensitive Data Handling

// Encrypt sensitive data before storage
$encryptedPassword = encrypt($password);

// Don't log sensitive information
$this->logInfo('user_created', [
    'username' => $username,
    'email' => $email,
    // Don't log password or API keys
]);

Permission Checking

// Always check permissions in controllers
if (!$admin->hasPermission('Product-ModuleName-action')) {
    abort(403, 'Insufficient permissions');
}
Development of modules

Practical Examples

Product Module Examples

1. VPS Hosting Module

Complete implementation of a VPS hosting service module with cloud provider integration.

Main Module Class
<?php

use App\Models\Service;
use App\Models\Task;
use App\Modules\Product;
use Illuminate\Support\Facades\Validator;
use Modules\Product\VPSHosting\Services\CloudAPIClient;

class VPSHosting extends Product
{
    private CloudAPIClient $apiClient;

    public function __construct()
    {
        parent::__construct();
        $this->apiClient = new CloudAPIClient(
            $this->config('api_url'),
            $this->config('api_key')
        );
    }

    public function activate(): string
    {
        try {
            Schema::create('vps_servers', function (Blueprint $table) {
                $table->id();
                $table->uuid('service_uuid');
                $table->string('server_id')->nullable();
                $table->ipAddress('ip_address')->nullable();
                $table->string('hostname');
                $table->string('root_password');
                $table->enum('status', ['creating', 'active', 'suspended', 'terminated']);
                $table->json('server_specs');
                $table->timestamps();
                
                $table->foreign('service_uuid')->references('uuid')->on('services');
                $table->index(['service_uuid', 'status']);
            });

            Schema::create('vps_images', function (Blueprint $table) {
                $table->id();
                $table->string('image_id');
                $table->string('name');
                $table->string('os_family');
                $table->string('version');
                $table->boolean('active')->default(true);
                $table->timestamps();
            });

            // Seed default images
            $this->seedDefaultImages();

            return 'success';
        } catch (Exception $e) {
            $this->logError('activate', $e->getMessage());
            return 'Error: ' . $e->getMessage();
        }
    }

    public function getProductData(array $data = []): array
    {
        $this->product_data = [
            'cpu_cores' => $data['cpu_cores'] ?? 1,
            'ram_mb' => $data['ram_mb'] ?? 1024,
            'disk_gb' => $data['disk_gb'] ?? 25,
            'bandwidth_gb' => $data['bandwidth_gb'] ?? 1000,
            'location' => $data['location'] ?? 'us-east-1',
            'backup_enabled' => $data['backup_enabled'] ?? false,
        ];
        return $this->product_data;
    }

    public function saveProductData(array $data = []): array
    {
        $validator = Validator::make($data, [
            'cpu_cores' => 'required|integer|min:1|max:32',
            'ram_mb' => 'required|integer|min:512|max:65536',
            'disk_gb' => 'required|integer|min:10|max:1000',
            'bandwidth_gb' => 'required|integer|min:100',
            'location' => 'required|in:us-east-1,us-west-1,eu-west-1',
            'backup_enabled' => 'boolean',
        ]);

        if ($validator->fails()) {
            return [
                'status' => 'error',
                'message' => $validator->errors(),
                'code' => 422,
            ];
        }

        return ['status' => 'success', 'data' => $data, 'code' => 200];
    }

    public function getServiceData(array $data = []): array
    {
        $this->service_data = [
            'hostname' => $data['hostname'] ?? '',
            'image_id' => $data['image_id'] ?? 'ubuntu-20.04',
            'ssh_keys' => $data['ssh_keys'] ?? [],
            'root_password' => $data['root_password'] ?? $this->generateSecurePassword(),
        ];
        return $this->service_data;
    }

    public function createJob(): array
    {
        try {
            $service = Service::find($this->service_uuid);
            $service->setProvisionStatus('processing');

            // Create VPS through cloud API
            $vpsData = [
                'name' => $this->service_data['hostname'],
                'image' => $this->service_data['image_id'],
                'size' => $this->getServerSize(),
                'region' => $this->product_data['location'],
                'ssh_keys' => $this->service_data['ssh_keys'],
            ];

            $result = $this->apiClient->createServer($vpsData);

            if ($result['success']) {
                // Store server details
                DB::table('vps_servers')->insert([
                    'service_uuid' => $this->service_uuid,
                    'server_id' => $result['data']['id'],
                    'hostname' => $this->service_data['hostname'],
                    'root_password' => encrypt($this->service_data['root_password']),
                    'status' => 'creating',
                    'server_specs' => json_encode($this->product_data),
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);

                                            // Queue status check job
                $this->scheduleStatusCheck($result['data']['id']);

                $this->logInfo('createJob', 'VPS creation initiated', $result);
                return ['status' => 'success'];
            } else {
                $service->setProvisionStatus('failed');
                $this->logError('createJob', 'VPS creation failed', $result);
                return ['status' => 'error', 'message' => $result['error']];
            }
        } catch (Exception $e) {
            $service->setProvisionStatus('failed');
            $this->logError('createJob', 'Exception in VPS creation', $e->getMessage());
            return ['status' => 'error', 'message' => 'VPS creation failed'];
        }
    }

    private function getServerSize(): string
    {
        // Map product specs to cloud provider size
        $cpu = $this->product_data['cpu_cores'];
        $ram = $this->product_data['ram_mb'];
        
        if ($cpu == 1 && $ram <= 1024) return 's-1vcpu-1gb';
        if ($cpu == 2 && $ram <= 2048) return 's-2vcpu-2gb';
        if ($cpu == 4 && $ram <= 8192) return 's-4vcpu-8gb';
        
        return 'custom';
    }

    private function scheduleStatusCheck(string $serverId): void
    {
        // Queue a job to check server status in 30 seconds
        $data = [
            'module' => $this,
            'method' => 'statusCheckJob',
            'server_id' => $serverId,  // Store as additional data
            'tries' => 2,
            'backoff' => 30,
            'timeout' => 120,
            'maxExceptions' => 1,
        ];
        
        Task::add('ModuleJob', 'Module', $data, ['status_check', 'vps']);
    }
    
    public function statusCheckJob(): array
    {
        try {
            // Get server ID from VPS servers table
            $vpsServer = DB::table('vps_servers')
                ->where('service_uuid', $this->service_uuid)
                ->first();
                
            if (!$vpsServer) {
                $this->logError('statusCheckJob', 'VPS server record not found');
                return ['status' => 'error'];
            }
            
            $result = $this->apiClient->getServerStatus($vpsServer->server_id);
            
            if ($result['success'] && $result['status'] === 'active') {
                $service = Service::find($this->service_uuid);
                $service->setProvisionStatus('completed');
                
                // Update server IP if available
                if (!empty($result['ip_address'])) {
                    DB::table('vps_servers')
                        ->where('service_uuid', $this->service_uuid)
                        ->update([
                            'ip_address' => $result['ip_address'],
                            'status' => 'active',
                            'updated_at' => now(),
                        ]);
                }
                
                $this->logInfo('statusCheckJob', 'VPS is now active', $result);
            } else {
                $this->logInfo('statusCheckJob', 'VPS still provisioning', $result);
                
                // Re-queue status check if still creating
                if ($result['status'] === 'new' || $result['status'] === 'creating') {
                    $this->scheduleStatusCheck($vpsServer->server_id);
                }
            }
            
            return ['status' => 'success'];
        } catch (Exception $e) {
            $this->logError('statusCheckJob', 'Status check failed', $e->getMessage());
            return ['status' => 'error'];
        }
    }

    public function getClientAreaMenuConfig(): array
    {
        return [
            'general' => [
                'name' => 'Overview',
                'template' => 'client_area.overview',
            ],
            'console' => [
                'name' => 'Console',
                'template' => 'client_area.console',
            ],
            'snapshots' => [
                'name' => 'Snapshots',
                'template' => 'client_area.snapshots',
            ],
            'monitoring' => [
                'name' => 'Monitoring',
                'template' => 'client_area.monitoring',
            ],
        ];
    }

    public function controllerClient_consoleGet(Request $request): JsonResponse
    {
        try {
            $vpsServer = DB::table('vps_servers')
                ->where('service_uuid', $this->service_uuid)
                ->first();

            if (!$vpsServer) {
                return response()->json(['error' => 'Server not found'], 404);
            }

            $consoleUrl = $this->apiClient->getConsoleUrl($vpsServer->server_id);

            return response()->json([
                'success' => true,
                'console_url' => $consoleUrl,
            ]);
        } catch (Exception $e) {
            return response()->json(['error' => 'Console unavailable'], 500);
        }
    }
}
Cloud API Client Service
<?php

namespace Modules\Product\VPSHosting\Services;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

class CloudAPIClient
{
    private Client $client;
    private string $apiKey;

    public function __construct(string $baseUrl, string $apiKey)
    {
        $this->apiKey = $apiKey;
        $this->client = new Client([
            'base_uri' => $baseUrl,
            'timeout' => 30,
            'headers' => [
                'Authorization' => 'Bearer ' . $apiKey,
                'Content-Type' => 'application/json',
            ],
        ]);
    }

    public function createServer(array $data): array
    {
        try {
            $response = $this->client->post('/v2/droplets', [
                'json' => $data,
            ]);

            $result = json_decode($response->getBody(), true);

            return [
                'success' => true,
                'data' => $result['droplet'],
            ];
        } catch (GuzzleException $e) {
            return [
                'success' => false,
                'error' => 'API Error: ' . $e->getMessage(),
            ];
        }
    }

    public function getServerStatus(string $serverId): array
    {
        try {
            $response = $this->client->get("/v2/droplets/{$serverId}");
            $result = json_decode($response->getBody(), true);

            return [
                'success' => true,
                'status' => $result['droplet']['status'],
                'ip_address' => $result['droplet']['networks']['v4'][0]['ip_address'] ?? null,
            ];
        } catch (GuzzleException $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }

    public function powerAction(string $serverId, string $action): array
    {
        try {
            $response = $this->client->post("/v2/droplets/{$serverId}/actions", [
                'json' => ['action' => $action],
            ]);

            return ['success' => true];
        } catch (GuzzleException $e) {
            return ['success' => false, 'error' => $e->getMessage()];
        }
    }
}

Plugin Module Examples


1. Monitoring Plugin

<?php

use App\Modules\Plugin;

class ServerMonitoring extends Plugin
{
    public function activate(): string
    {
        try {
            Schema::create('monitoring_checks', function (Blueprint $table) {
                $table->id();
                $table->string('name');
                $table->string('type'); // ping, http, port
                $table->string('target'); // IP/URL to monitor
                $table->json('config'); // Check-specific config
                $table->integer('interval')->default(60); // seconds
                $table->boolean('active')->default(true);
                $table->timestamps();
            });

            Schema::create('monitoring_results', function (Blueprint $table) {
                $table->id();
                $table->foreignId('check_id')->constrained('monitoring_checks');
                $table->boolean('status'); // up/down
                $table->integer('response_time')->nullable(); // milliseconds
                $table->string('error_message')->nullable();
                $table->timestamp('checked_at');
                $table->timestamps();

                $table->index(['check_id', 'checked_at']);
            });

                    // Schedule monitoring job
        $this->scheduleMonitoringJob();
        
        $this->logInfo('activate', 'Monitoring plugin activated successfully');

            return 'success';
        } catch (Exception $e) {
            return 'Error: ' . $e->getMessage();
        }
    }

    public function adminWebRoutes(): array
    {
        return [
            [
                'method' => 'get',
                'uri' => 'dashboard',
                'permission' => 'monitoring-dashboard',
                'name' => 'dashboard',
                'controller' => 'ServerMonitoringController@dashboard',
            ],
            [
                'method' => 'get',
                'uri' => 'checks',
                'permission' => 'monitoring-checks',
                'name' => 'checks',
                'controller' => 'ServerMonitoringController@checks',
            ],
        ];
    }

    private function scheduleMonitoringJob(): void
    {
        // Add recurring monitoring task
        $data = [
            'module' => $this,
            'method' => 'runChecks',
            'tries' => 2,
            'backoff' => 60,
            'timeout' => 300,
            'maxExceptions' => 1,
        ];
        
        Task::add('ModuleJob', 'Module', $data, ['monitoring', 'recurring']);
    }

    public function runChecks(): array
    {
        $checks = DB::table('monitoring_checks')
            ->where('active', true)
            ->get();

        foreach ($checks as $check) {
            $this->executeCheck($check);
        }

        return ['status' => 'success'];
    }

    private function executeCheck($check): void
    {
        $startTime = microtime(true);
        $success = false;
        $errorMessage = null;

        try {
            switch ($check->type) {
                case 'ping':
                    $success = $this->pingCheck($check->target);
                    break;
                case 'http':
                    $success = $this->httpCheck($check->target, json_decode($check->config, true));
                    break;
                case 'port':
                    $success = $this->portCheck($check->target, json_decode($check->config, true));
                    break;
            }
        } catch (Exception $e) {
            $errorMessage = $e->getMessage();
        }

        $responseTime = round((microtime(true) - $startTime) * 1000);

        DB::table('monitoring_results')->insert([
            'check_id' => $check->id,
            'status' => $success,
            'response_time' => $responseTime,
            'error_message' => $errorMessage,
            'checked_at' => now(),
            'created_at' => now(),
            'updated_at' => now(),
        ]);

        // Send alerts if needed
        if (!$success) {
            $this->sendAlert($check, $errorMessage);
        }
    }
}

Payment Module Examples


Bank Transfer Module

<?php

use App\Modules\Payment;

class BankTransfer extends Payment
{
    public function getClientAreaHtml(array $data = []): string
    {
        $invoice = $data['invoice'];
        $amount = $invoice->getDueAmountAttribute();
        $currency = $invoice->client->currency->code;

        // Generate unique reference number
        $reference = 'BT' . strtoupper(substr(md5($invoice->uuid . time()), 0, 8));

        // Store payment instruction
        DB::table('bank_transfer_payments')->insert([
            'invoice_uuid' => $invoice->uuid,
            'amount' => $amount,
            'currency' => $currency,
            'reference' => $reference,
            'status' => 'awaiting_transfer',
            'expires_at' => now()->addDays(7),
            'created_at' => now(),
        ]);

        return $this->view('client_area', [
            'reference' => $reference,
            'bank_details' => $this->getBankDetails($currency),
            'amount' => $amount,
            'currency' => $currency,
            'instructions' => $this->getTransferInstructions($reference),
        ]);
    }

    private function getBankDetails(string $currency): array
    {
        $bankDetails = [
            'USD' => [
                'bank_name' => 'Example Bank USA',
                'account_name' => 'YourCompany LLC',
                'account_number' => '1234567890',
                'routing_number' => '021000021',
                'swift_code' => 'EXBKUS33',
            ],
            'EUR' => [
                'bank_name' => 'Example Bank Europe',
                'account_name' => 'YourCompany EU',
                'iban' => 'DE89370400440532013000',
                'bic' => 'EXBKDEFF',
            ],
        ];

        return $bankDetails[$currency] ?? $bankDetails['USD'];
    }
}

Notification Module Examples


Slack Notification Module

<?php

use App\Modules\Notification;
use GuzzleHttp\Client;

class SlackNotification extends Notification
{
    private Client $client;

    public function __construct()
    {
        parent::__construct();
        $this->client = new Client(['timeout' => 10]);
    }

    public function send(array $data): array
    {
        try {
            $webhookUrl = $this->module_data['webhook_url'] ?? '';
            
            if (empty($webhookUrl)) {
                return ['status' => 'error', 'message' => 'Webhook URL not configured'];
            }

            $message = $this->buildSlackMessage($data);

            $response = $this->client->post($webhookUrl, [
                'json' => $message,
            ]);

            if ($response->getStatusCode() === 200) {
                $this->logInfo('send', 'Slack notification sent', $data);
                return ['status' => 'success'];
            } else {
                $this->logError('send', 'Slack API error', [
                    'status_code' => $response->getStatusCode(),
                    'body' => $response->getBody()->getContents()
                ]);
                return ['status' => 'error', 'message' => 'Slack API error'];
            }

        } catch (Exception $e) {
            $this->logError('send', 'Slack notification failed', $e->getMessage());
            return ['status' => 'error', 'message' => $e->getMessage()];
        }
    }

    private function buildSlackMessage(array $data): array
    {
        $color = $this->getColorForType($data['type']);
        
        return [
            'attachments' => [
                [
                    'color' => $color,
                    'title' => $data['title'],
                    'text' => $data['message'],
                    'fields' => [
                        [
                            'title' => 'Time',
                            'value' => now()->toDateTimeString(),
                            'short' => true,
                        ],
                        [
                            'title' => 'Module',
                            'value' => $data['module'] ?? 'System',
                            'short' => true,
                        ],
                    ],
                    'footer' => 'PUQcloud',
                    'ts' => time(),
                ],
            ],
        ];
    }

    private function getColorForType(string $type): string
    {
        return match($type) {
            'error' => 'danger',
            'warning' => 'warning',
            'success' => 'good',
            default => '#36a64f',
        };
    }
}

These examples demonstrate real-world implementations of different module types, showing how to integrate with external APIs, handle complex business logic, and implement robust error handling. Each example follows the established patterns while showcasing specific use cases and best practices.

Development Checklist


Pre-Development Planning


✅ Requirements Analysis

✅ Architecture Design

Development Phase


✅ Module Structure Setup

modules/{Type}/{ModuleName}/
├── [ ] {ModuleName}.php              # Main module class
├── [ ] config.php                    # Configuration file
├── [ ] hooks.php                     # Event hooks (optional)
├── Controllers/
│   └── [ ] {ModuleName}Controller.php
├── Models/
│   └── [ ] {ModuleName}.php
├── Services/                         # API clients, business logic
│   └── [ ] ExternalAPIClient.php
├── views/
│   ├── admin_area/
│   │   ├── [ ] product.blade.php
│   │   └── [ ] service.blade.php
│   └── client_area/
│       └── [ ] general.blade.php
└── lang/
    ├── [ ] en.php
    └── [ ] pl.php

✅ Core Module Implementation

Main Module Class
Lifecycle Methods
Product Module Specific
Service Lifecycle (Product Modules)

✅ Admin Interface

Permissions
Navigation
Routes
Controllers

✅ Client Area (Product Modules)

Configuration
Variables
AJAX Controllers

✅ Database Design

Tables
Migrations

✅ API Integration

External API Client
Security

✅ Validation & Security

Input Validation
Security Measures

✅ Error Handling & Logging

Error Handling
Logging

✅ Performance Optimization

Caching
Database
Memory Management

Testing Phase

✅ Unit Testing
✅ Integration Testing

✅ Manual Testing

✅ Performance Testing

Pre-Deployment Checklist


✅ Configuration

✅ Documentation

✅ Security Review

✅ Deployment Preparation

Post-Deployment


✅ Monitoring

✅ Maintenance

Code Quality Standards


✅ PHP Standards

✅ Laravel Best Practices

✅ Module-Specific Standards

Version Control


✅ Git Practices

✅ Release Management

Common Pitfalls to Avoid


❌ Don't Do This

✅ Best Practices


Remember: A well-designed module is secure, performant, maintainable, and provides excellent user experience. Take time to plan, implement best practices, and thoroughly test before deployment.

FAQ & Troubleshooting


Frequently Asked Questions


General Module Development

Q: What's the difference between Product, Plugin, Payment and Notification modules?

A: Each module type has its own purpose:

Q: How to properly name modules?

A: Follow the conventions used in this codebase:

Example:

// File: modules/Product/puqNextcloud/puqNextcloud.php
class puqNextcloud extends Product
{
    // ...
}
Q: What capabilities differ by module type in this codebase?
Q: Can I use external libraries in modules?

A: Yes, but follow the recommendations:

// In your project's composer.json add dependencies
{
    "require": {
        "guzzlehttp/guzzle": "^7.0",
        "league/oauth2-client": "^2.0"
    }
}

// In module use through autoloader
use GuzzleHttp\Client;
use League\OAuth2\Client\Provider\GenericProvider;
Q: How to handle multilingualism in modules?

A: Use language files:

// lang/en.php
return [
    'service_created' => 'Service created successfully',
    'invalid_credentials' => 'Invalid API credentials',
];

// In module code
__('Product.ModuleName.service_created')

Database & Migrations

Q: How to properly create tables in modules?

A: In the activate() method:

public function activate(): string
{
    try {
        Schema::create('module_custom_table', function (Blueprint $table) {
            $table->id();
            $table->uuid('service_uuid')->nullable();
            $table->string('external_id')->index();
            $table->json('metadata')->nullable();
            $table->enum('status', ['active', 'suspended', 'terminated']);
            $table->timestamps();
            
            // Always add foreign keys to link with main tables
            $table->foreign('service_uuid')->references('uuid')->on('services');
            
            // Add indexes for frequently used fields
            $table->index(['service_uuid', 'status']);
        });
        
        return 'success';
    } catch (Exception $e) {
        $this->logError('activate', $e->getMessage());
        return 'Error: ' . $e->getMessage();
    }
}
Q: How to update table structure when updating module?

A: Use the update() method:

public function update(): string
{
    try {
        $currentVersion = $this->getCurrentVersion();
        
        if (version_compare($currentVersion, '1.1.0', '<')) {
            Schema::table('module_custom_table', function (Blueprint $table) {
                $table->string('new_field')->nullable();
            });
        }
        
        if (version_compare($currentVersion, '1.2.0', '<')) {
            Schema::table('module_custom_table', function (Blueprint $table) {
                $table->index('new_field');
            });
        }
        
        return 'success';
    } catch (Exception $e) {
        return 'Error: ' . $e->getMessage();
    }
}

Jobs & Queue System

Q: When to use synchronous vs asynchronous operations?

A: Guidelines:

Synchronous operations (direct method calls):

Asynchronous operations (via Task::add) — primarily applicable to Product modules:

Q: How to ensure job execution reliability?

A: Use proper configuration:

$data = [
    'module' => $this,               // Module instance (required)
    'method' => 'createJob',         // Method to execute (required)
    'tries' => 1,                    // Number of attempts (default: 3, recommended: 1)
    'backoff' => 60,                 // Delay between attempts in seconds (default: 10)
    'timeout' => 600,                // Execution timeout (default: 600)
    'maxExceptions' => 1,            // Maximum exceptions (default: 2, recommended: 1)
];

// Add descriptive tags
$tags = ['create', 'hosting', 'service:' . $this->service_uuid];

$service = Service::find($this->service_uuid);
$service->setProvisionStatus('processing');
Task::add('ModuleJob', 'Module', $data, $tags); // used in Product modules
Q: How to monitor job execution?

A: Use logging and monitoring:

public function createJob(): array
{
    $startTime = microtime(true);
    
    try {
        $this->logInfo('createJob.started', ['service_uuid' => $this->service_uuid]);
        
        // Main logic - example of real provisioning
        $apiClient = new HostingAPIClient($this->config('api_url'), $this->config('api_key'));
        $result = $apiClient->createAccount([
            'domain' => $this->service_data['domain'],
            'username' => $this->service_data['username'],
            'password' => $this->service_data['password'],
        ]);
        
        $executionTime = round((microtime(true) - $startTime) * 1000, 2);
        $this->logInfo('createJob.completed', [
            'service_uuid' => $this->service_uuid,
            'execution_time_ms' => $executionTime,
            'result' => $result
        ]);
        
        return ['status' => 'success'];
        
    } catch (Exception $e) {
        $this->logError('createJob.failed', [
            'service_uuid' => $this->service_uuid,
            'error' => $e->getMessage(),
            'trace' => $e->getTraceAsString()
        ]);
        
        return ['status' => 'error', 'message' => 'Service creation failed'];
    }
}

API Integration

Q: How to properly integrate with external APIs?

A: Follow best practices:

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;

class ExternalAPIClient
{
    private Client $client;
    private string $apiKey;
    
    public function __construct(string $baseUrl, string $apiKey)
    {
        $this->apiKey = $apiKey;
        $this->client = new Client([
            'base_uri' => $baseUrl,
            'timeout' => 30,
            'headers' => [
                'Authorization' => 'Bearer ' . $apiKey,
                'Content-Type' => 'application/json',
                'User-Agent' => 'PUQcloud-Module/1.0',
            ],
        ]);
    }
    
    public function makeRequest(string $method, string $endpoint, array $data = []): array
    {
        $attempts = 0;
        $maxAttempts = 3;
        
        while ($attempts < $maxAttempts) {
            try {
                $response = $this->client->request($method, $endpoint, [
                    'json' => $data,
                ]);
                
                return [
                    'success' => true,
                    'data' => json_decode($response->getBody(), true),
                    'status_code' => $response->getStatusCode()
                ];
                
            } catch (GuzzleException $e) {
                $attempts++;
                
                if ($attempts >= $maxAttempts) {
                    return [
                        'success' => false,
                        'error' => $e->getMessage(),
                        'status_code' => $e->getCode()
                    ];
                }
                
                // Exponential backoff
                sleep(pow(2, $attempts));
            }
        }
    }
}

Security

Q: How to securely store API keys and passwords?

A: Use encryption and environment variables:

// In .env file
EXTERNAL_API_KEY=your_secret_key_here

// In module config.php
return [
    'api_key' => env('EXTERNAL_API_KEY'),
    // DON'T store secrets directly in config!
];

// For passwords use Laravel encryption
$encryptedPassword = encrypt($password);
$decryptedPassword = decrypt($encryptedData);
Q: How to validate input data?

A: Use strict validation:

public function saveServiceData(array $data = []): array
{
    // Clean input data
    $data = array_map('trim', $data);
    
    $validator = Validator::make($data, [
        'domain' => [
            'required',
            'string',
            'max:255',
            'regex:/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\.[a-zA-Z]{2,}$/'
        ],
        'username' => [
            'required',
            'alpha_dash',
            'min:3',
            'max:20',
            'not_in:admin,root,administrator' // Forbidden names
        ],
        'password' => [
            'required',
            'min:8',
            'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/'
        ],
    ], [
        'domain.regex' => 'Please enter a valid domain name',
        'password.regex' => 'Password must contain uppercase, lowercase, number and special character',
    ]);

    if ($validator->fails()) {
        return [
            'status' => 'error',
            'message' => $validator->errors(),
            'code' => 422,
        ];
    }

    // Additional business logic validation
    if ($this->isDomainBlacklisted($data['domain'])) {
        return [
            'status' => 'error',
            'message' => 'Domain is not allowed',
            'code' => 403,
        ];
    }

    return ['status' => 'success', 'data' => $data, 'code' => 200];
}

private function isDomainBlacklisted(string $domain): bool
{
    // Example implementation - configure according to your requirements
    $blacklistedDomains = ['spam.com', 'test.invalid'];
    return in_array($domain, $blacklistedDomains);
}

Troubleshooting Guide


Module Not Loading

Symptoms:
Solutions:

1. Check file permissions:

chmod 755 modules/Product/YourModule/
chmod 644 modules/Product/YourModule/*.php
chmod 644 modules/Product/YourModule/config.php

2. Check PHP syntax:

php -l modules/Product/YourModule/YourModule.php

3. Check class name correctness:

// File: modules/Product/MyModule/MyModule.php
<?php

class MyModule extends Product  // Name must exactly match file name
{
    // ...
}

4. Check configuration file:

// config.php must return an array
return [
    'name' => 'Module Name',
    'version' => '1.0.0',
    // ... other required fields
];

Routes Not Working

Symptoms:
Solutions:

1. Clear route cache:

php artisan route:clear
php artisan config:clear

2. Check module status:

# Module should be in 'active' status

3. Check route definition correctness:

public function adminWebRoutes(): array
{
    return [
        [
            'method' => 'get',                           // Correct HTTP method
            'uri' => 'dashboard',                        // URI without leading slashes
            'permission' => 'view-dashboard',            // Existing permission (from adminPermissions)
            'name' => 'dashboard',                       // Unique route name
            'controller' => 'ModuleController@dashboard', // Existing controller and method
        ]
    ];
}

4. You can also define admin API routes (used for AJAX in admin area):

public function adminApiRoutes(): array
{
    return [
        [
            'method' => 'get',
            'uri' => 'server/{uuid}',                   // Route parameters are supported
            'permission' => 'configuration',
            'name' => 'server.get',
            'controller' => 'puqNextcloudController@getServer',
        ],
    ];
}

5. Check controller existence:

// File must exist: modules/Product/YourModule/Controllers/YourController.php
namespace Modules\Product\YourModule\Controllers;

use App\Http\Controllers\Controller;

class YourController extends Controller
{
    public function dashboard() {
        // Method must exist and be public
    }
}

Jobs Not Processing

Symptoms:
Solutions:

1. Check queue worker operation:

# Start worker
php artisan queue:work

# Check queue configuration
php artisan queue:monitor

2. Check failed jobs:

# View failed jobs
php artisan queue:failed

# Retry specific job by ID (replace 1 with actual ID)
php artisan queue:retry 1

# Retry all failed jobs
php artisan queue:retry all

# Clear all failed jobs
php artisan queue:flush

3. Check module logs:

// View module logs in admin panel or in log file
tail -f storage/logs/laravel.log

4. Ensure job data correctness:

// Check that all required data is passed
$data = [
    'module' => $this,                // Required: module instance
    'method' => 'createJob',          // Required: method must exist in module class
    'tries' => 1,                     // Optional: number of attempts
    'backoff' => 60,                  // Optional: delay between attempts
    'timeout' => 600,                 // Optional: maximum execution time
    'maxExceptions' => 1,             // Optional: maximum exceptions
];

$tags = ['create', 'service:' . $this->service_uuid];
Task::add('ModuleJob', 'Module', $data, $tags);

API Integration Issues

Symptoms:
Solutions:

1. Check API configuration:

// Ensure all settings are correct
$apiKey = $this->config('api_key');
$apiUrl = $this->config('api_url');

if (empty($apiKey) || empty($apiUrl)) {
    $this->logError('api_config', 'API credentials not configured');
    return ['status' => 'error', 'message' => 'API not configured'];
}

2. Add debug information:

public function callAPI($endpoint, $data = [])
{
    $this->logDebug('api_call', [
        'endpoint' => $endpoint,
        'data' => $data,
        'headers' => $this->getHeaders()
    ]);
    
    try {
        $response = $this->client->post($endpoint, ['json' => $data]);
        
        $this->logDebug('api_response', [
            'status_code' => $response->getStatusCode(),
            'body' => $response->getBody()->getContents()
        ]);
        
        return $response;
        
    } catch (Exception $e) {
        $this->logError('api_error', [
            'endpoint' => $endpoint,
            'error' => $e->getMessage()
        ]);
        throw $e;
    }
}

3. Use retry mechanism:

private function apiCallWithRetry($endpoint, $data, $maxAttempts = 3)
{
    $attempt = 0;
    
    while ($attempt < $maxAttempts) {
        try {
            return $this->callAPI($endpoint, $data);
        } catch (Exception $e) {
            $attempt++;
            
            if ($attempt >= $maxAttempts) {
                throw $e;
            }
            
            $delay = pow(2, $attempt); // Exponential backoff
            sleep($delay);
        }
    }
}

Performance Issues

Symptoms:
Solutions:

1. Optimize database queries:

// Bad: N+1 queries
$services = Service::all();
foreach ($services as $service) {
    echo $service->product->name; // Additional query for each service
}

// Good: Eager loading
$services = Service::with('product')->get();
foreach ($services as $service) {
    echo $service->product->name; // Data already loaded
}

2. Use caching:

use Illuminate\Support\Facades\Cache;

public function getExpensiveData($serviceId)
{
    return Cache::remember("service_data_{$serviceId}", 300, function() use ($serviceId) {
        return $this->fetchDataFromAPI($serviceId);
    });
}

// Invalidate cache when data changes
public function updateServiceData($serviceId, $data)
{
    $result = $this->updateData($serviceId, $data);
    Cache::forget("service_data_{$serviceId}");
    return $result;
}

3. Process large datasets in chunks:

// Instead of loading all records at once
Service::chunk(100, function ($services) {
    foreach ($services as $service) {
        $this->processService($service);
    }
});

4. Monitor performance:

private function measureOperation($operationName, callable $operation)
{
    $startTime = microtime(true);
    $startMemory = memory_get_usage();
    
    $result = $operation();
    
    $executionTime = (microtime(true) - $startTime) * 1000;
    $memoryUsed = memory_get_usage() - $startMemory;
    
    $this->logInfo('performance', [
        'operation' => $operationName,
        'execution_time_ms' => round($executionTime, 2),
        'memory_used_kb' => round($memoryUsed / 1024, 2),
        'peak_memory_mb' => round(memory_get_peak_usage() / 1024 / 1024, 2)
    ]);
    
    return $result;
}

Client Area Issues

Symptoms:
Solutions:

1. Check client area configuration:

public function getClientAreaMenuConfig(): array
{
    return [
        'general' => [
            'name' => 'General',
            'template' => 'client_area.general', // File must exist
        ],
    ];
}

2. Ensure template existence:

# File must exist
modules/Product/YourModule/views/client_area/general.blade.php

3. Check variable methods:

// Method must exist for each tab
public function variables_general(): array
{
    return [
        'service_data' => $this->service_data,
        'config' => $this->config,
    ];
}

4. Check AJAX controllers:

// Pattern A: method per tab and HTTP verb suffix
public function controllerClient_generalGet(Request $request): JsonResponse
{
    return response()->json([
        'status' => 'success',
        'data' => $this->getGeneralData()
    ]);
}

// Pattern B: custom action name without HTTP verb suffix
public function controllerClient_user_quota(Request $request): JsonResponse
{
    // ...
}

// Static client controllers (no bound service, e.g., Payment module forms)
public function controllerClientStatic_fetch_public_key(array $data = []): JsonResponse
{
    $request = $data['request'] ?? request();
    // ...
}

5. Optionally, modules may expose dedicated client API routes:

public function clientApiRoutes(): array
{
    return [
        // Define client API endpoints if needed
    ];
}

Debugging Tools


Enable Detailed Logging

// In module methods for debugging
public function createJob(): array
{
    $this->logDebug('createJob.start', [
        'service_uuid' => $this->service_uuid,
        'product_data' => $this->product_data,
        'service_data' => $this->service_data
    ]);
    
    // ... main logic
    
    $this->logDebug('createJob.end', ['result' => $result]);
    return $result;
}

Using Laravel Telescope

# Install Telescope for debugging
composer require laravel/telescope --dev
php artisan telescope:install
php artisan migrate

Performance Profiling

// Add to module for performance monitoring
use Illuminate\Support\Facades\DB;

public function enableQueryLogging()
{
    DB::enableQueryLog();
}

public function logQueries($operation)
{
    $queries = DB::getQueryLog();
    $this->logDebug('database_queries', [
        'operation' => $operation,
        'query_count' => count($queries),
        'queries' => $queries
    ]);
}

This FAQ and troubleshooting guide will help developers quickly resolve the most common issues when developing modules for PUQcloud.