Add platform admin hosting API for Ladill account provisioning.
Deploy Ladill Hosting / deploy (push) Successful in 1m29s
Deploy Ladill Hosting / deploy (push) Successful in 1m29s
Expose authenticated service endpoints for assigning, listing, renewing, and managing hosting accounts from the platform, with tests and deploy docs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\HostingAccount;
|
||||
use App\Services\Hosting\AdminHostingAssignmentService;
|
||||
use App\Services\Hosting\HostingResourcePolicyService;
|
||||
use App\Services\Platform\PlatformUserResolver;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class PlatformHostingController extends Controller
|
||||
{
|
||||
public function products(AdminHostingAssignmentService $assignments): JsonResponse
|
||||
{
|
||||
return response()->json([
|
||||
'data' => $assignments->listProducts(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function index(string $publicId, PlatformUserResolver $users, AdminHostingAssignmentService $assignments): JsonResponse
|
||||
{
|
||||
$owner = $users->resolveOrCreate($publicId);
|
||||
if (! $owner) {
|
||||
return response()->json(['data' => []]);
|
||||
}
|
||||
|
||||
$accounts = HostingAccount::query()
|
||||
->where('user_id', $owner->id)
|
||||
->with('product')
|
||||
->latest()
|
||||
->get()
|
||||
->map(fn (HostingAccount $account) => $assignments->serializeAccount($account))
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return response()->json(['data' => $accounts]);
|
||||
}
|
||||
|
||||
public function assign(
|
||||
Request $request,
|
||||
string $publicId,
|
||||
PlatformUserResolver $users,
|
||||
AdminHostingAssignmentService $assignments,
|
||||
): JsonResponse {
|
||||
$validated = $request->validate([
|
||||
'hosting_product_id' => ['required', 'integer', 'exists:hosting_products,id'],
|
||||
'duration_months' => ['required', 'integer', 'min:1', 'max:120'],
|
||||
'primary_domain' => ['nullable', 'string', 'max:255'],
|
||||
'username' => ['nullable', 'string', 'max:32'],
|
||||
'notes' => ['nullable', 'string', 'max:1000'],
|
||||
'owner_email' => ['required', 'email', 'max:255'],
|
||||
'owner_name' => ['nullable', 'string', 'max:255'],
|
||||
'assigned_by_admin' => ['nullable', 'integer'],
|
||||
]);
|
||||
|
||||
$owner = $users->resolveOrCreate(
|
||||
$publicId,
|
||||
(string) $validated['owner_email'],
|
||||
(string) ($validated['owner_name'] ?? ''),
|
||||
);
|
||||
|
||||
if (! $owner) {
|
||||
return response()->json(['error' => 'User not found.'], 404);
|
||||
}
|
||||
|
||||
$result = $assignments->assign($owner, $validated);
|
||||
|
||||
return response()->json(['data' => $result], 201);
|
||||
}
|
||||
|
||||
public function updateDuration(
|
||||
Request $request,
|
||||
int $account,
|
||||
AdminHostingAssignmentService $assignments,
|
||||
): JsonResponse {
|
||||
$validated = $request->validate([
|
||||
'duration_months' => ['required', 'integer', 'min:1', 'max:120'],
|
||||
'admin_id' => ['nullable', 'integer'],
|
||||
]);
|
||||
|
||||
$hostingAccount = HostingAccount::query()->findOrFail($account);
|
||||
|
||||
$data = $assignments->updateDuration(
|
||||
$hostingAccount,
|
||||
(int) $validated['duration_months'],
|
||||
isset($validated['admin_id']) ? (int) $validated['admin_id'] : null,
|
||||
);
|
||||
|
||||
return response()->json(['data' => $data]);
|
||||
}
|
||||
|
||||
public function renew(
|
||||
Request $request,
|
||||
int $account,
|
||||
AdminHostingAssignmentService $assignments,
|
||||
): JsonResponse {
|
||||
$validated = $request->validate([
|
||||
'admin_id' => ['nullable', 'integer'],
|
||||
]);
|
||||
|
||||
$hostingAccount = HostingAccount::query()->findOrFail($account);
|
||||
|
||||
$data = $assignments->renew(
|
||||
$hostingAccount,
|
||||
isset($validated['admin_id']) ? (int) $validated['admin_id'] : null,
|
||||
);
|
||||
|
||||
return response()->json(['data' => $data]);
|
||||
}
|
||||
|
||||
public function unsuspend(
|
||||
int $account,
|
||||
AdminHostingAssignmentService $assignments,
|
||||
HostingResourcePolicyService $resourcePolicy,
|
||||
): JsonResponse {
|
||||
$hostingAccount = HostingAccount::query()->findOrFail($account);
|
||||
$assignments->unsuspend($hostingAccount, $resourcePolicy);
|
||||
|
||||
return response()->json(['data' => ['ok' => true]]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
/**
|
||||
* Generic service-to-service auth for internal APIs. Validates the bearer token
|
||||
* against per-consumer keys in config("{namespace}.service_api_keys").
|
||||
*/
|
||||
class AuthenticateService
|
||||
{
|
||||
public function handle(Request $request, Closure $next, string $namespace = 'platform'): Response
|
||||
{
|
||||
$token = (string) $request->bearerToken();
|
||||
$caller = null;
|
||||
|
||||
if ($token !== '') {
|
||||
foreach ((array) config("{$namespace}.service_api_keys", []) as $name => $key) {
|
||||
if (is_string($key) && $key !== '' && hash_equals($key, $token)) {
|
||||
$caller = $name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($caller === null) {
|
||||
return response()->json(['error' => 'Unauthorized.'], 401);
|
||||
}
|
||||
|
||||
$request->attributes->set('service_caller', $caller);
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Services\Domains;
|
||||
|
||||
use Illuminate\Http\Client\Response;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@@ -11,9 +12,11 @@ class LadillDomainsClient
|
||||
/** @return list<string> Active domain names owned by the account (lowercase). */
|
||||
public function ownedForUser(string $publicId): array
|
||||
{
|
||||
[$storefront, $platform] = $this->fetchOwnedDomainsInParallel($publicId);
|
||||
|
||||
return collect()
|
||||
->merge($this->fetchFromStorefront($publicId))
|
||||
->merge($this->fetchFromPlatform($publicId))
|
||||
->merge($storefront)
|
||||
->merge($platform)
|
||||
->map(fn ($d) => strtolower(trim((string) $d)))
|
||||
->filter()
|
||||
->unique()
|
||||
@@ -22,61 +25,60 @@ class LadillDomainsClient
|
||||
->all();
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
private function fetchFromStorefront(string $publicId): array
|
||||
/**
|
||||
* @return array{0: list<string>, 1: list<string>}
|
||||
*/
|
||||
private function fetchOwnedDomainsInParallel(string $publicId): array
|
||||
{
|
||||
$key = (string) (config('domains.api_key') ?? '');
|
||||
if ($key === '') {
|
||||
return [];
|
||||
}
|
||||
$storefront = [];
|
||||
$platform = [];
|
||||
|
||||
try {
|
||||
$res = Http::withToken($key)
|
||||
->acceptJson()
|
||||
->timeout(10)
|
||||
->get(rtrim((string) config('domains.api_url'), '/').'/owned-domains', [
|
||||
'user' => $publicId,
|
||||
]);
|
||||
$responses = Http::pool(function ($pool) use ($publicId) {
|
||||
$requests = [];
|
||||
$storefrontKey = (string) (config('domains.api_key') ?? '');
|
||||
if ($storefrontKey !== '') {
|
||||
$requests['storefront'] = $pool->as('storefront')
|
||||
->withToken($storefrontKey)
|
||||
->acceptJson()
|
||||
->timeout(10)
|
||||
->get(rtrim((string) config('domains.api_url'), '/').'/owned-domains', [
|
||||
'user' => $publicId,
|
||||
]);
|
||||
}
|
||||
$platformKey = (string) (config('domains.platform_api_key') ?? '');
|
||||
if ($platformKey !== '') {
|
||||
$requests['platform'] = $pool->as('platform')
|
||||
->withToken($platformKey)
|
||||
->acceptJson()
|
||||
->timeout(10)
|
||||
->get(rtrim((string) config('domains.platform_api_url'), '/').'/owned-domains', [
|
||||
'user' => $publicId,
|
||||
]);
|
||||
}
|
||||
|
||||
$res->throw();
|
||||
return $requests;
|
||||
});
|
||||
|
||||
return (array) $res->json('data', []);
|
||||
foreach ($responses as $name => $response) {
|
||||
if (! $response instanceof Response || ! $response->successful()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = (array) $response->json('data', []);
|
||||
if ($name === 'storefront') {
|
||||
$storefront = $data;
|
||||
} elseif ($name === 'platform') {
|
||||
$platform = $data;
|
||||
}
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('LadillDomainsClient: storefront owned domains unavailable', [
|
||||
Log::warning('LadillDomainsClient: owned domains lookup failed', [
|
||||
'user' => $publicId,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/** @return list<string> */
|
||||
private function fetchFromPlatform(string $publicId): array
|
||||
{
|
||||
$key = (string) (config('domains.platform_api_key') ?? '');
|
||||
if ($key === '') {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
$res = Http::withToken($key)
|
||||
->acceptJson()
|
||||
->timeout(10)
|
||||
->get(rtrim((string) config('domains.platform_api_url'), '/').'/owned-domains', [
|
||||
'user' => $publicId,
|
||||
]);
|
||||
|
||||
$res->throw();
|
||||
|
||||
return (array) $res->json('data', []);
|
||||
} catch (\Throwable $e) {
|
||||
Log::warning('LadillDomainsClient: platform owned domains unavailable', [
|
||||
'user' => $publicId,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return [];
|
||||
}
|
||||
return [$storefront, $platform];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Hosting;
|
||||
|
||||
use App\Jobs\ProvisionHostingAccountJob;
|
||||
use App\Models\HostingAccount;
|
||||
use App\Models\HostingProduct;
|
||||
use App\Models\User;
|
||||
use Carbon\CarbonInterface;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AdminHostingAssignmentService
|
||||
{
|
||||
public function __construct(
|
||||
private NodeCapacityService $nodeCapacity,
|
||||
private HostingResourcePolicyService $resourcePolicy,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $input
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function assign(User $owner, array $input): array
|
||||
{
|
||||
$product = HostingProduct::query()->findOrFail((int) $input['hosting_product_id']);
|
||||
|
||||
if (! $product->is_active) {
|
||||
throw ValidationException::withMessages([
|
||||
'hosting_product_id' => 'Selected hosting product is not active.',
|
||||
]);
|
||||
}
|
||||
|
||||
$username = isset($input['username']) && $input['username'] !== ''
|
||||
? (string) $input['username']
|
||||
: $this->generateUsername($owner);
|
||||
|
||||
if (HostingAccount::query()->where('username', $username)->exists()) {
|
||||
throw ValidationException::withMessages([
|
||||
'username' => 'This username is already taken.',
|
||||
]);
|
||||
}
|
||||
|
||||
$primaryDomain = isset($input['primary_domain']) && $input['primary_domain'] !== ''
|
||||
? (string) $input['primary_domain']
|
||||
: null;
|
||||
|
||||
$accountType = match ($product->type) {
|
||||
'single_domain', 'multi_domain', 'wordpress' => HostingAccount::TYPE_SHARED,
|
||||
'vps' => HostingAccount::TYPE_VPS,
|
||||
'dedicated' => HostingAccount::TYPE_DEDICATED,
|
||||
'email_standalone' => HostingAccount::TYPE_SHARED,
|
||||
default => HostingAccount::TYPE_SHARED,
|
||||
};
|
||||
|
||||
$node = null;
|
||||
$nodeId = null;
|
||||
if ($accountType === HostingAccount::TYPE_SHARED) {
|
||||
$node = $this->nodeCapacity->findAvailableNodeForProduct($product);
|
||||
$nodeId = $node?->id;
|
||||
}
|
||||
|
||||
$resourceLimits = $this->resourcePolicy->defaultLimitsForProduct($product);
|
||||
$durationMonths = (int) $input['duration_months'];
|
||||
|
||||
$hostingAccount = HostingAccount::create([
|
||||
'user_id' => $owner->id,
|
||||
'hosting_product_id' => $product->id,
|
||||
'hosting_node_id' => $nodeId,
|
||||
'username' => $username,
|
||||
'primary_domain' => $primaryDomain,
|
||||
'type' => $accountType,
|
||||
'status' => HostingAccount::STATUS_PENDING,
|
||||
'allocated_disk_gb' => (int) ($product->disk_gb ?? 0),
|
||||
'cpu_limit_percent' => $resourceLimits['cpu_limit_percent'],
|
||||
'memory_limit_mb' => $resourceLimits['memory_limit_mb'],
|
||||
'process_limit' => $resourceLimits['process_limit'],
|
||||
'io_limit_mb' => $resourceLimits['io_limit_mb'],
|
||||
'inode_limit' => $resourceLimits['inode_limit'],
|
||||
'resource_status' => HostingAccount::RESOURCE_STATUS_ACTIVE,
|
||||
'expires_at' => $this->expiresAtFromDuration($durationMonths),
|
||||
'resource_limits' => array_merge([
|
||||
'disk_gb' => (int) ($product->disk_gb ?? 0),
|
||||
'max_domains' => $product->max_domains,
|
||||
'max_databases' => $product->max_databases,
|
||||
], $resourceLimits),
|
||||
'metadata' => [
|
||||
'assigned_by_admin' => $input['assigned_by_admin'] ?? null,
|
||||
'assigned_at' => now()->toISOString(),
|
||||
'assigned_duration_months' => $durationMonths,
|
||||
'notes' => $input['notes'] ?? null,
|
||||
],
|
||||
]);
|
||||
|
||||
if ($node) {
|
||||
ProvisionHostingAccountJob::dispatch($hostingAccount->id);
|
||||
|
||||
return [
|
||||
'account' => $this->serializeAccount($hostingAccount->fresh(['product'])),
|
||||
'provisioning' => true,
|
||||
'message' => "Hosting product '{$product->name}' assigned. Provisioning is in progress.",
|
||||
];
|
||||
}
|
||||
|
||||
$hostingAccount->update([
|
||||
'status' => HostingAccount::STATUS_ACTIVE,
|
||||
'provisioned_at' => now(),
|
||||
]);
|
||||
|
||||
return [
|
||||
'account' => $this->serializeAccount($hostingAccount->fresh(['product'])),
|
||||
'provisioning' => false,
|
||||
'message' => "Hosting product '{$product->name}' assigned.",
|
||||
];
|
||||
}
|
||||
|
||||
public function updateDuration(HostingAccount $account, int $durationMonths, ?int $adminId = null): array
|
||||
{
|
||||
$metadata = (array) ($account->metadata ?? []);
|
||||
$metadata['assigned_duration_months'] = $durationMonths;
|
||||
$metadata['duration_updated_by_admin'] = $adminId;
|
||||
$metadata['duration_updated_at'] = now()->toISOString();
|
||||
|
||||
$account->update([
|
||||
'expires_at' => $this->expiresAtFromDuration($durationMonths),
|
||||
'metadata' => $metadata,
|
||||
]);
|
||||
|
||||
return $this->serializeAccount($account->fresh(['product']));
|
||||
}
|
||||
|
||||
public function renew(HostingAccount $account, ?int $adminId = null): array
|
||||
{
|
||||
if (! $account->canBeRenewed()) {
|
||||
throw ValidationException::withMessages([
|
||||
'account' => 'This hosting account does not have a configured renewal duration.',
|
||||
]);
|
||||
}
|
||||
|
||||
$months = $account->assignedDurationMonths();
|
||||
$account->renew($months, [
|
||||
'renewed_by_admin' => $adminId,
|
||||
'renewed_at' => now()->toISOString(),
|
||||
]);
|
||||
|
||||
return $this->serializeAccount($account->fresh(['product']));
|
||||
}
|
||||
|
||||
public function unsuspend(HostingAccount $account, HostingResourcePolicyService $resourcePolicy): void
|
||||
{
|
||||
if (
|
||||
$account->status !== HostingAccount::STATUS_SUSPENDED
|
||||
&& $account->resource_status !== HostingAccount::RESOURCE_STATUS_SUSPENDED
|
||||
) {
|
||||
throw ValidationException::withMessages([
|
||||
'account' => 'This hosting account is not suspended.',
|
||||
]);
|
||||
}
|
||||
|
||||
$resourcePolicy->unsuspendAccount($account, 'Manual unsuspension by admin.');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function serializeAccount(HostingAccount $account): array
|
||||
{
|
||||
$product = $account->product;
|
||||
|
||||
return [
|
||||
'id' => $account->id,
|
||||
'username' => $account->username,
|
||||
'primary_domain' => $account->primary_domain,
|
||||
'type' => $account->type,
|
||||
'status' => $account->status,
|
||||
'resource_status' => $account->resource_status,
|
||||
'expires_at' => $account->expires_at?->toIso8601String(),
|
||||
'provisioned_at' => $account->provisioned_at?->toIso8601String(),
|
||||
'metadata' => (array) ($account->metadata ?? []),
|
||||
'product' => $product ? [
|
||||
'id' => $product->id,
|
||||
'name' => $product->name,
|
||||
'type' => $product->type,
|
||||
'slug' => $product->slug,
|
||||
] : null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array<string, mixed>>
|
||||
*/
|
||||
public function listProducts(): array
|
||||
{
|
||||
return HostingProduct::query()
|
||||
->where('is_active', true)
|
||||
->orderBy('sort_order')
|
||||
->orderBy('name')
|
||||
->get()
|
||||
->map(fn (HostingProduct $product) => [
|
||||
'id' => $product->id,
|
||||
'name' => $product->name,
|
||||
'type' => $product->type,
|
||||
'slug' => $product->slug,
|
||||
'display_currency' => $product->display_currency,
|
||||
'display_price_monthly' => $product->display_price_monthly,
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
private function generateUsername(User $user): string
|
||||
{
|
||||
$baseUsername = strtolower(str_replace([' ', '.', '_'], '', $user->name));
|
||||
$baseUsername = preg_replace('/[^a-z0-9]/', '', $baseUsername);
|
||||
|
||||
if (strlen($baseUsername) < 3) {
|
||||
$baseUsername = strtolower(substr($user->email, 0, str_contains($user->email, '@') ? strpos($user->email, '@') : strlen($user->email)));
|
||||
$baseUsername = preg_replace('/[^a-z0-9]/', '', $baseUsername);
|
||||
}
|
||||
|
||||
if (strlen($baseUsername) < 3) {
|
||||
$baseUsername = 'user'.$user->id;
|
||||
}
|
||||
|
||||
$username = substr($baseUsername, 0, 16);
|
||||
$counter = 1;
|
||||
$originalUsername = $username;
|
||||
|
||||
while (HostingAccount::query()->where('username', $username)->exists()) {
|
||||
$suffix = (string) $counter;
|
||||
$maxBaseLength = 16 - strlen($suffix);
|
||||
$username = substr($originalUsername, 0, $maxBaseLength).$suffix;
|
||||
$counter++;
|
||||
}
|
||||
|
||||
return $username;
|
||||
}
|
||||
|
||||
private function expiresAtFromDuration(int $durationMonths): CarbonInterface
|
||||
{
|
||||
return now()->addMonthsNoOverflow($durationMonths);
|
||||
}
|
||||
}
|
||||
@@ -909,16 +909,7 @@ NGINX;
|
||||
$domain = $site->domain;
|
||||
$docRoot = $site->document_root ?: "/home/{$username}/public_html/{$domain}";
|
||||
|
||||
// Create document root
|
||||
$quotedDocRoot = escapeshellarg($docRoot);
|
||||
$this->ensureAdministrativeResultSucceeded(
|
||||
$this->runLocalAdminOperationOrRemote($ssh, 'mkdir', [$docRoot], "mkdir -p {$quotedDocRoot}"),
|
||||
"Failed to create document root for {$domain}"
|
||||
);
|
||||
$this->ensureAdministrativeResultSucceeded(
|
||||
$this->runLocalAdminOperationOrRemote($ssh, 'chown', ["{$username}:{$username}", $docRoot], "chown {$username}:{$username} {$quotedDocRoot}"),
|
||||
"Failed to assign document root ownership for {$domain}"
|
||||
);
|
||||
$this->ensureSiteDocumentRoot($ssh, $username, $docRoot);
|
||||
$this->hardenAccountFilesystem($account, [$docRoot]);
|
||||
|
||||
$this->applyManagedSiteConfig($ssh, $site, $docRoot, $username, [
|
||||
@@ -931,6 +922,24 @@ NGINX;
|
||||
];
|
||||
}
|
||||
|
||||
private function ensureSiteDocumentRoot(mixed $ssh, string $username, string $docRoot): void
|
||||
{
|
||||
$quotedDocRoot = escapeshellarg($docRoot);
|
||||
$this->ensureAdministrativeResultSucceeded(
|
||||
$this->runLocalAdminOperationOrRemote($ssh, 'mkdir', [$docRoot], "mkdir -p {$quotedDocRoot}"),
|
||||
"Failed to create document root at {$docRoot}"
|
||||
);
|
||||
$this->ensureAdministrativeResultSucceeded(
|
||||
$this->runLocalAdminOperationOrRemote(
|
||||
$ssh,
|
||||
'run-cmd',
|
||||
["chown {$username}:" . self::WEB_SERVER_GROUP . " {$quotedDocRoot} && chmod 2750 {$quotedDocRoot}"],
|
||||
"chown {$username}:" . self::WEB_SERVER_GROUP . " {$quotedDocRoot} && chmod 2750 {$quotedDocRoot}"
|
||||
),
|
||||
"Failed to set document root ownership at {$docRoot}"
|
||||
);
|
||||
}
|
||||
|
||||
public function hardenAccountFilesystem(HostingAccount $account, array $additionalDocumentRoots = []): void
|
||||
{
|
||||
$node = $account->node;
|
||||
@@ -1002,6 +1011,7 @@ NGINX;
|
||||
$domain = $site->domain;
|
||||
$email = $account->user?->email ?: 'admin@' . $domain;
|
||||
$docRoot = $site->document_root ?: "/home/{$account->username}/public_html/{$domain}";
|
||||
$this->ensureSiteDocumentRoot($ssh, $account->username, $docRoot);
|
||||
$hadCertificate = $this->siteHasCertificate($ssh, $domain);
|
||||
|
||||
Log::info("SSL: Starting certificate request", [
|
||||
@@ -1677,7 +1687,7 @@ LIMITS;
|
||||
'runtime_port' => $port,
|
||||
'runtime_type' => $appType,
|
||||
]),
|
||||
]);
|
||||
])->save();
|
||||
|
||||
try {
|
||||
$this->applyManagedSiteConfig($ssh, $site, $site->document_root, $username, [
|
||||
@@ -2451,14 +2461,7 @@ SERVICE;
|
||||
NGINX;
|
||||
|
||||
if ($proxyPort !== null) {
|
||||
$httpBody = <<<NGINX
|
||||
access_log /home/{$username}/logs/{$domain}-access.log;
|
||||
error_log /home/{$username}/logs/{$domain}-error.log;
|
||||
|
||||
{$acmeLocation}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:{$proxyPort};
|
||||
$proxyHeaders = <<<NGINX
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
@@ -2469,6 +2472,27 @@ NGINX;
|
||||
proxy_cache_bypass \$http_upgrade;
|
||||
proxy_read_timeout 300;
|
||||
proxy_connect_timeout 300;
|
||||
NGINX;
|
||||
$apiPort = data_get($site->app_config, 'api_runtime_port');
|
||||
$apiLocation = is_numeric($apiPort)
|
||||
? <<<NGINX
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:{$apiPort};
|
||||
{$proxyHeaders}
|
||||
}
|
||||
NGINX
|
||||
: '';
|
||||
$httpBody = <<<NGINX
|
||||
access_log /home/{$username}/logs/{$domain}-access.log;
|
||||
error_log /home/{$username}/logs/{$domain}-error.log;
|
||||
|
||||
{$acmeLocation}
|
||||
{$apiLocation}
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:{$proxyPort};
|
||||
{$proxyHeaders}
|
||||
}
|
||||
|
||||
client_max_body_size 64M;
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Platform;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class PlatformUserResolver
|
||||
{
|
||||
public function resolveOrCreate(string $publicId, string $email = '', string $name = ''): ?User
|
||||
{
|
||||
$publicId = trim($publicId);
|
||||
if ($publicId === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = User::query()->where('public_id', $publicId)->first();
|
||||
if ($user) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
if ($email === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return User::query()->create([
|
||||
'public_id' => $publicId,
|
||||
'email' => $email,
|
||||
'name' => $name !== '' ? $name : $email,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user