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 Class: Main business logic and lifecycle management

Controllers: Handle HTTP requests and API endpoints

Models: Database interactions and data validation

Views: Blade templates for UI rendering

Configuration: Module metadata and settings

Task System: Asynchronous job processing

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

PHP 8.1+

Laravel 9.0+

Composer

Redis/Database for queues

Basic understanding of Laravel concepts

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

Define Requirements - Service features and capabilities - External API integrations - Data models and relationships - User interface requirements

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

Create Module Structure

Implement Core Methods

Add Controllers and Views

Configure Routes and Permissions

Add Language Files

Phase 3: Testing

Unit Tests

Integration Tests

Manual Testing

Performance Testing

Phase 4: Deployment

Install Module

Configure Settings

Activate Module

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

Check file permissions (755 for directories, 644 for files)

Verify class name matches filename

Ensure proper namespace usage

Check for PHP syntax errors

Routes Not Working

Verify module is activated

Check permission keys match

Ensure controller exists and methods are public

Clear route cache: php artisan route:clear

Jobs Not Processing

Check queue configuration

Verify Redis/database connection

Run queue worker: php artisan queue:work

Check failed jobs: php artisan queue:failed

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.