Deploy Ladill Hosting / deploy (push) Failing after 17s
Shared web hosting extracted from the platform monolith, with CI deploy to /var/www/ladill-hosting matching Bird/Domains/Email. Co-authored-by: Cursor <cursoragent@cursor.com>
279 lines
11 KiB
PHP
279 lines
11 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Hosting;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\HostingOrder;
|
|
use App\Services\Hosting\ResellerClubHostingService;
|
|
use App\Services\ResellerClub\ResellerClubCustomerService;
|
|
use App\Services\ResellerClub\ResellerClubProductService;
|
|
use App\Support\ResellerClubLegacy;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\RedirectResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\View\View;
|
|
|
|
class HostingController extends Controller
|
|
{
|
|
public function index(Request $request, ResellerClubHostingService $hosting): RedirectResponse
|
|
{
|
|
return redirect()->route('hosting.single-domain');
|
|
}
|
|
|
|
public function show(
|
|
Request $request,
|
|
HostingOrder $hostingOrder,
|
|
ResellerClubHostingService $hosting,
|
|
ResellerClubProductService $products
|
|
): View
|
|
{
|
|
if ((int) $hostingOrder->user_id !== (int) $request->user()->id) {
|
|
abort(403);
|
|
}
|
|
|
|
$remoteDetails = null;
|
|
if ($hostingOrder->rc_order_id && $hosting->isConfigured()) {
|
|
$result = $hosting->getOrderDetails($hostingOrder->rc_order_id);
|
|
if ($result['success'] ?? false) {
|
|
$remoteDetails = $result['order'];
|
|
// Sync latest data
|
|
$hosting->syncOrderToLocal($result['order']['raw'] ?? $result['order'], $request->user()->id, $hostingOrder->domain_id);
|
|
$hostingOrder->refresh();
|
|
}
|
|
}
|
|
|
|
return view('hosting.show', [
|
|
'order' => $hostingOrder,
|
|
'remoteDetails' => $remoteDetails,
|
|
'renewalOptions' => $this->renewalOptionsForOrder($hostingOrder, $products),
|
|
]);
|
|
}
|
|
|
|
public function sync(
|
|
Request $request,
|
|
ResellerClubHostingService $hosting,
|
|
ResellerClubCustomerService $customers
|
|
): RedirectResponse {
|
|
$user = $request->user();
|
|
|
|
if (! $hosting->isConfigured()) {
|
|
return redirect()->route('hosting.index')->with('error', 'Hosting service is not configured.');
|
|
}
|
|
|
|
$resolved = $customers->resolveCustomerForUser($user);
|
|
if (! ($resolved['success'] ?? false)) {
|
|
return redirect()->route('hosting.index')
|
|
->with('error', $resolved['message'] ?? 'Could not prepare your ResellerClub customer.');
|
|
}
|
|
|
|
$synced = $hosting->syncCustomerOrders((string) $resolved['customer_id'], $user->id);
|
|
|
|
return redirect()->route('hosting.index')
|
|
->with('success', "Synced {$synced} hosting order(s) from ResellerClub.");
|
|
}
|
|
|
|
public function order(
|
|
Request $request,
|
|
ResellerClubHostingService $hosting,
|
|
ResellerClubCustomerService $customers
|
|
): JsonResponse {
|
|
$request->validate([
|
|
'domain_name' => 'required|string|max:253',
|
|
'plan_id' => 'required|string',
|
|
'months' => 'sometimes|integer|min:1|max:120',
|
|
]);
|
|
|
|
if (! $hosting->isConfigured()) {
|
|
return response()->json(['message' => 'Hosting service is not available at this time.'], 503);
|
|
}
|
|
|
|
$domainName = strtolower(trim($request->input('domain_name')));
|
|
$planId = $request->input('plan_id');
|
|
$months = $request->integer('months', 1);
|
|
|
|
$resolved = $customers->resolveCustomerForUser($request->user());
|
|
if (! ($resolved['success'] ?? false)) {
|
|
return response()->json([
|
|
'message' => $resolved['message'] ?? 'Could not prepare your ResellerClub customer.',
|
|
], 422);
|
|
}
|
|
|
|
$result = $hosting->orderHosting($domainName, $planId, (string) $resolved['customer_id'], $months);
|
|
|
|
if (! $result['success']) {
|
|
return response()->json(['message' => $result['message'] ?? 'Hosting order failed.'], 422);
|
|
}
|
|
|
|
// Fetch details and save locally
|
|
$order = null;
|
|
if (! empty($result['order_id'])) {
|
|
$details = $hosting->getOrderDetails($result['order_id']);
|
|
if ($details['success'] ?? false) {
|
|
$order = $hosting->syncOrderToLocal(
|
|
$details['order']['raw'] ?? $details['order'],
|
|
$request->user()->id
|
|
);
|
|
} else {
|
|
$order = HostingOrder::create([
|
|
'user_id' => $request->user()->id,
|
|
'domain_name' => $domainName,
|
|
'rc_order_id' => $result['order_id'],
|
|
'plan_name' => $planId,
|
|
'status' => HostingOrder::STATUS_PENDING,
|
|
]);
|
|
}
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'Hosting order placed successfully.',
|
|
'order' => $order?->only('id', 'domain_name', 'status', 'rc_order_id'),
|
|
]);
|
|
}
|
|
|
|
public function resetPanelPassword(Request $request, HostingOrder $hostingOrder, ResellerClubHostingService $hosting): JsonResponse
|
|
{
|
|
if ((int) $hostingOrder->user_id !== (int) $request->user()->id) {
|
|
abort(403);
|
|
}
|
|
|
|
$validated = $request->validate([
|
|
'password' => ['required', 'string', 'min:9', 'max:16', 'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[~*!@$#%_+.\?:,{}]).+$/'],
|
|
]);
|
|
|
|
if (! $hosting->isConfigured() || ! $hostingOrder->rc_order_id) {
|
|
return response()->json(['message' => 'Hosting service is not available.'], 503);
|
|
}
|
|
|
|
$result = $hosting->changePanelPassword($hostingOrder->rc_order_id, $validated['password']);
|
|
|
|
if (! $result['success']) {
|
|
return response()->json(['message' => $result['message'] ?? 'Could not update the cPanel password.'], 422);
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'message' => 'cPanel password updated successfully.',
|
|
]);
|
|
}
|
|
|
|
public function renew(Request $request, HostingOrder $hostingOrder, ResellerClubHostingService $hosting): JsonResponse
|
|
{
|
|
if ((int) $hostingOrder->user_id !== (int) $request->user()->id) {
|
|
abort(403);
|
|
}
|
|
|
|
$request->validate([
|
|
'months' => 'sometimes|integer|min:1|max:120',
|
|
]);
|
|
|
|
if (! ResellerClubLegacy::renewalsEnabled()) {
|
|
return response()->json(['message' => ResellerClubLegacy::renewalsDisabledMessage()], 503);
|
|
}
|
|
|
|
if (! $hosting->isConfigured() || ! $hostingOrder->rc_order_id) {
|
|
return response()->json(['message' => 'Hosting service is not available.'], 503);
|
|
}
|
|
|
|
$result = $hosting->renewOrder($hostingOrder->rc_order_id, $request->integer('months', 1));
|
|
|
|
if (! $result['success']) {
|
|
return response()->json(['message' => $result['message'] ?? 'Renewal failed.'], 422);
|
|
}
|
|
|
|
// Re-sync details
|
|
$details = $hosting->getOrderDetails($hostingOrder->rc_order_id);
|
|
if ($details['success'] ?? false) {
|
|
$hosting->syncOrderToLocal($details['order']['raw'] ?? $details['order'], $request->user()->id, $hostingOrder->domain_id);
|
|
}
|
|
|
|
return response()->json(['success' => true, 'message' => 'Hosting renewed successfully.']);
|
|
}
|
|
|
|
public function destroy(Request $request, HostingOrder $hostingOrder, ResellerClubHostingService $hosting): RedirectResponse
|
|
{
|
|
if ((int) $hostingOrder->user_id !== (int) $request->user()->id) {
|
|
abort(403);
|
|
}
|
|
|
|
if ($hostingOrder->status === HostingOrder::STATUS_CANCELLED) {
|
|
$redirectRoute = $this->productRouteForOrder($hostingOrder);
|
|
$hostingOrder->delete();
|
|
|
|
return redirect()->route($redirectRoute)
|
|
->with('success', 'Cancelled hosting order deleted.');
|
|
}
|
|
|
|
if ($hostingOrder->rc_order_id && $hosting->isConfigured()) {
|
|
$result = $hosting->deleteOrder($hostingOrder->rc_order_id);
|
|
if (! $result['success']) {
|
|
return redirect()->route('hosting.show', $hostingOrder)
|
|
->with('error', $result['message'] ?? 'Could not cancel hosting at this time.');
|
|
}
|
|
}
|
|
|
|
$hostingOrder->update(['status' => HostingOrder::STATUS_CANCELLED]);
|
|
|
|
return redirect()->route($this->productRouteForOrder($hostingOrder))
|
|
->with('success', 'Hosting order cancelled.');
|
|
}
|
|
|
|
private function productRouteForOrder(HostingOrder $hostingOrder): string
|
|
{
|
|
return match ((string) $hostingOrder->hosting_type) {
|
|
HostingOrder::TYPE_MULTI_DOMAIN => 'hosting.multi-domain',
|
|
HostingOrder::TYPE_RESELLER => 'hosting.multi-domain',
|
|
default => 'hosting.single-domain',
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array{months: string, label: string, total: ?string, monthly: ?string}>
|
|
*/
|
|
private function renewalOptionsForOrder(HostingOrder $hostingOrder, ResellerClubProductService $products): array
|
|
{
|
|
$category = match ((string) $hostingOrder->hosting_type) {
|
|
HostingOrder::TYPE_MULTI_DOMAIN => 'multi-domain-hosting',
|
|
HostingOrder::TYPE_RESELLER => 'reseller-hosting',
|
|
default => 'single-domain-hosting',
|
|
};
|
|
|
|
$packages = collect((array) ($products->formDefinition($category)['packages'] ?? []));
|
|
$planId = (string) ($hostingOrder->plan_id ?? '');
|
|
$planName = strtolower(trim((string) $hostingOrder->plan_name));
|
|
|
|
$package = $packages->first(function (array $item) use ($planId, $planName): bool {
|
|
$value = (string) ($item['value'] ?? '');
|
|
$label = strtolower(trim((string) ($item['label'] ?? '')));
|
|
|
|
return ($planId !== '' && $value === $planId)
|
|
|| ($planName !== '' && $label !== '' && $label === $planName);
|
|
});
|
|
|
|
if (! is_array($package)) {
|
|
return collect(['1', '3', '6', '12', '24', '36'])
|
|
->map(fn (string $months) => [
|
|
'months' => $months,
|
|
'label' => $months.' '.((int) $months === 1 ? 'Month' : 'Months'),
|
|
'total' => null,
|
|
'monthly' => null,
|
|
])
|
|
->all();
|
|
}
|
|
|
|
$priceField = ! empty((array) ($package['renew_prices'] ?? [])) ? 'renew_prices' : 'prices';
|
|
$totalField = ! empty((array) ($package['renew_totals'] ?? [])) ? 'renew_totals' : 'totals';
|
|
|
|
return collect(array_keys((array) ($package[$priceField] ?? [])))
|
|
->sortBy(fn (string $months) => (int) $months)
|
|
->values()
|
|
->map(fn (string $months) => [
|
|
'months' => $months,
|
|
'label' => $months.' '.((int) $months === 1 ? 'Month' : 'Months'),
|
|
'total' => (string) (($package[$totalField][$months] ?? null) ?: ''),
|
|
'monthly' => (string) (($package[$priceField][$months] ?? null) ?: ''),
|
|
])
|
|
->all();
|
|
}
|
|
}
|