This commit is contained in:
Saeed Vaziry
2023-07-02 12:47:50 +02:00
commit 5c72f12490
825 changed files with 41659 additions and 0 deletions

View File

@ -0,0 +1,85 @@
<?php
namespace App\Actions\Backup;
use App\Enums\DatabaseStatus;
use App\Models\Backup;
use App\Models\Database;
use App\Models\Server;
use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class CreateBackup
{
/**
* @throws AuthorizationException
* @throws ValidationException
*/
public function create($type, Server $server, User $user, array $input): Backup
{
$this->validate($type, $server, $user, $input);
if ($type == 'database') {
Gate::forUser($user)->authorize('viewAny', [Database::class, $server]);
}
$backup = new Backup([
'name' => $input['name'],
'type' => $type,
'server_id' => $server->id,
'database_id' => $input['database'] ?? null,
'storage_id' => $input['storage'],
'interval' => $input['interval'],
'keep_backups' => $input['keep_backups'],
'status' => 'running',
]);
$backup->save();
$backup->run();
return $backup;
}
/**
* @throws ValidationException
*/
private function validate($type, Server $server, User $user, array $input): void
{
$rules = [
'name' => 'required',
'storage' => [
'required',
Rule::exists('storage_providers', 'id')
->where('user_id', $user->id)
->where('connected', 1),
],
'keep_backups' => [
'required',
'numeric',
'min:1',
],
'interval' => [
'required',
Rule::in([
'0 * * * *',
'0 0 * * *',
'0 0 * * 0',
'0 0 1 * *',
]),
],
];
if ($type === 'database') {
$rules['database'] = [
'required',
Rule::exists('databases', 'id')
->where('server_id', $server->id)
->where('status', DatabaseStatus::READY),
];
}
Validator::make($input, $rules)->validateWithBag('createBackup');
}
}

View File

@ -0,0 +1,48 @@
<?php
namespace App\Actions\CronJob;
use App\Enums\CronjobStatus;
use App\Models\CronJob;
use App\Models\Server;
use App\ValidationRules\CronRule;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateCronJob
{
public function create(Server $server, array $input): void
{
$this->validate($input);
$cronJob = new CronJob([
'server_id' => $server->id,
'user' => $input['user'],
'command' => $input['command'],
'frequency' => $input['frequency'],
'status' => CronjobStatus::CREATING,
]);
$cronJob->save();
$cronJob->addToServer();
}
/**
* @throws ValidationException
*/
private function validate(array $input): void
{
Validator::make($input, [
'command' => [
'required',
],
'user' => [
'required',
'in:root,'.config('core.ssh_user'),
],
'frequency' => [
'required',
new CronRule(),
],
])->validateWithBag('createCronJob');
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Actions\Database;
use App\Models\Database;
use App\Models\Server;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class CreateDatabase
{
/**
* @throws ValidationException
*/
public function create(Server $server, array $input): Database
{
$this->validate($server, $input);
$database = new Database([
'server_id' => $server->id,
'name' => $input['name'],
]);
$database->save();
$database->createOnServer();
return $database;
}
/**
* @throws ValidationException
*/
private function validate(Server $server, array $input): void
{
$rules = [
'name' => [
'required',
'alpha_dash',
Rule::unique('databases', 'name')->where('server_id', $server->id),
],
];
if (isset($input['user']) && $input['user']) {
$rules['username'] = [
'required',
'alpha_dash',
Rule::unique('database_users', 'username')->where('server_id', $server->id),
];
$rules['password'] = [
'required',
'min:6',
];
}
if (isset($input['remote']) && $input['remote']) {
$rules['host'] = 'required';
}
Validator::make($input, $rules)->validate();
}
}

View File

@ -0,0 +1,53 @@
<?php
namespace App\Actions\Database;
use App\Models\DatabaseUser;
use App\Models\Server;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class CreateDatabaseUser
{
/**
* @throws ValidationException
*/
public function create(Server $server, array $input): DatabaseUser
{
$this->validate($server, $input);
$databaseUser = new DatabaseUser([
'server_id' => $server->id,
'username' => $input['username'],
'password' => $input['password'],
'host' => isset($input['remote']) && $input['remote'] ? $input['host'] : 'localhost',
]);
$databaseUser->save();
$databaseUser->createOnServer();
return $databaseUser;
}
/**
* @throws ValidationException
*/
private function validate(Server $server, array $input): void
{
$rules = [
'username' => [
'required',
'alpha_dash',
Rule::unique('database_users', 'username')->where('server_id', $server->id),
],
'password' => [
'required',
'min:6',
],
];
if (isset($input['remote']) && $input['remote']) {
$rules['host'] = 'required';
}
Validator::make($input, $rules)->validate();
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Actions\Database;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class InstallPHPMyAdmin
{
/**
* @throws ValidationException
*/
public function install(Server $server, array $input): Service
{
$this->validate($input);
$phpMyAdmin = $server->defaultService('phpmyadmin');
if ($phpMyAdmin) {
if ($phpMyAdmin->status === 'ready') {
throw ValidationException::withMessages([
'install' => __('Already installed'),
])->errorBag('installPHPMyAdmin');
}
$phpMyAdmin->delete();
}
$phpMyAdmin = new Service([
'server_id' => $server->id,
'type' => 'phpmyadmin',
'type_data' => [
'allowed_ip' => $input['allowed_ip'],
'php' => $server->defaultService('php')->version,
],
'name' => 'phpmyadmin',
'version' => '5.1.2',
'status' => 'installing',
'is_default' => 1,
]);
$phpMyAdmin->save();
$phpMyAdmin->install();
return $phpMyAdmin;
}
/**
* @throws ValidationException
*/
private function validate(array $input): void
{
Validator::make($input, [
'allowed_ip' => 'required',
])->validateWithBag('installPHPMyAdmin');
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Actions\Database;
use App\Models\Database;
use App\Models\DatabaseUser;
use Illuminate\Validation\ValidationException;
class LinkUser
{
/**
* @throws ValidationException
*/
public function link(DatabaseUser $databaseUser, array $databases): void
{
$dbs = Database::query()
->where('server_id', $databaseUser->server_id)
->whereIn('name', $databases)
->count();
if (count($databases) !== $dbs) {
throw ValidationException::withMessages(['databases' => __('Databases not found!')])
->errorBag('linkUser');
}
$databaseUser->databases = $databases;
$databaseUser->unlinkUser();
$databaseUser->linkUser();
$databaseUser->save();
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace App\Actions\FirewallRule;
use App\Enums\FirewallRuleStatus;
use App\Models\FirewallRule;
use App\Models\Server;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class CreateRule
{
public function create(Server $server, array $input): FirewallRule
{
$this->validate($server, $input);
$rule = new FirewallRule([
'server_id' => $server->id,
'type' => $input['type'],
'protocol' => $input['protocol'],
'port' => $input['port'],
'source' => $input['source'],
'mask' => $input['mask'],
'status' => FirewallRuleStatus::CREATING,
]);
$rule->save();
$rule->addToServer();
return $rule;
}
/**
* @throws ValidationException
*/
private function validate(Server $server, array $input): void
{
Validator::make($input, [
'type' => [
'required',
'in:allow,deny',
],
'protocol' => [
'required',
'in:'.implode(',', array_keys(config('core.firewall_protocols_port'))),
],
'port' => [
'required',
'numeric',
'min:1',
'max:65535',
Rule::unique('firewall_rules', 'port')->where('server_id', $server->id),
],
'source' => [
'required',
'ip',
],
'mask' => [
'required',
'numeric',
],
])->validateWithBag('createRule');
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Actions\NotificationChannels;
use App\Models\NotificationChannel;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class AddChannel
{
/**
* @throws ValidationException
*/
public function add(User $user, array $input): void
{
$this->validate($input);
$channel = new NotificationChannel([
'user_id' => $user->id,
'provider' => $input['provider'],
'label' => $input['label'],
]);
$this->validateType($channel, $input);
$channel->data = $channel->provider()->data($input);
$channel->save();
if (! $channel->provider()->connect()) {
$channel->delete();
throw ValidationException::withMessages([
'provider' => __('Could not connect'),
]);
}
}
/**
* @throws ValidationException
*/
protected function validate(array $input): void
{
Validator::make($input, [
'provider' => 'required|in:'.implode(',', config('core.notification_channels_providers')),
'label' => 'required',
])->validate();
}
/**
* @throws ValidationException
*/
protected function validateType(NotificationChannel $channel, array $input): void
{
Validator::make($input, $channel->provider()->validationRules())
->validate();
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Actions\PHP;
use App\Enums\ServiceStatus;
use App\Models\Server;
use App\Models\Service;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class InstallNewPHP
{
public function install(Server $server, array $input): void
{
$this->validate($server, $input);
$php = new Service([
'server_id' => $server->id,
'type' => 'php',
'type_data' => [
'extensions' => [],
'settings' => config('core.php_settings'),
],
'name' => 'php',
'version' => $input['version'],
'status' => ServiceStatus::INSTALLING,
'is_default' => false,
]);
$php->save();
$php->install();
}
/**
* @throws ValidationException
*/
private function validate(Server $server, array $input): void
{
Validator::make($input, [
'version' => [
'required',
Rule::in(config('core.php_versions')),
],
])->validateWithBag('installPHP');
if (in_array($input['version'], $server->installedPHPVersions())) {
throw ValidationException::withMessages(
['version' => __('This version is already installed')]
)->errorBag('installPHP');
}
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Actions\PHP;
use App\Models\Service;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class InstallPHPExtension
{
/**
* @throws ValidationException
*/
public function handle(Service $service, array $input): Service
{
$typeData = $service->type_data;
$typeData['extensions'] = $typeData['extensions'] ?? [];
$service->type_data = $typeData;
$service->save();
$this->validate($service, $input);
$service->handler()->installExtension($input['name']);
return $service;
}
/**
* @throws ValidationException
*/
private function validate(Service $service, array $input): void
{
Validator::make($input, [
'name' => [
'required',
'in:'.implode(',', config('core.php_extensions')),
],
])->validateWithBag('installPHPExtension');
if (in_array($input['name'], $service->type_data['extensions'])) {
throw ValidationException::withMessages(
['name' => __('This extension already installed')]
)->errorBag('installPHPExtension');
}
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Actions\PHP;
use App\Models\Server;
use Illuminate\Validation\ValidationException;
class UninstallPHP
{
public function uninstall(Server $server, string $version): void
{
$this->validate($server, $version);
$php = $server->services()->where('type', 'php')->where('version', $version)->first();
$php->uninstall();
}
/**
* @throws ValidationException
*/
private function validate(Server $server, string $version): void
{
$php = $server->services()->where('type', 'php')->where('version', $version)->first();
if (! $php) {
throw ValidationException::withMessages(
['version' => __('This version has not been installed yet!')]
);
}
$hasSite = $server->sites()->where('php_version', $version)->first();
if ($hasSite) {
throw ValidationException::withMessages(
['version' => __('Cannot uninstall this version because some sites are using it!')]
);
}
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Actions\PHP;
use App\Models\Service;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
use Throwable;
class UpdatePHPIni
{
/**
* @throws ValidationException
*/
public function update(Service $service, string $ini): void
{
$tmpName = Str::random(10).strtotime('now');
try {
Storage::disk('local')->put($tmpName, $ini);
$service->server->ssh('root')->upload(
Storage::disk('local')->path($tmpName),
"/etc/php/$service->version/cli/php.ini"
);
$this->deleteTempFile($tmpName);
} catch (Throwable) {
$this->deleteTempFile($tmpName);
throw ValidationException::withMessages([
'ini' => __("Couldn't update php.ini file!"),
]);
}
}
private function deleteTempFile(string $name): void
{
if (Storage::disk('local')->exists($name)) {
Storage::disk('local')->delete($name);
}
}
}

View File

@ -0,0 +1,65 @@
<?php
namespace App\Actions\Queue;
use App\Enums\QueueStatus;
use App\Models\Queue;
use App\Models\Server;
use App\Models\Site;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateQueue
{
/**
* @throws ValidationException
*/
public function create(mixed $queueable, array $input): void
{
$this->validate($input);
$queue = new Queue([
'server_id' => $queueable instanceof Server ? $queueable->id : $queueable->server_id,
'site_id' => $queueable instanceof Site ? $queueable->id : null,
'command' => $input['command'],
'user' => $input['user'],
'auto_start' => $input['auto_start'],
'auto_restart' => $input['auto_restart'],
'numprocs' => $input['numprocs'],
'status' => QueueStatus::CREATING,
]);
$queue->save();
$queue->deploy();
}
/**
* @throws ValidationException
*/
protected function validate(array $input): void
{
$rules = [
'command' => [
'required',
],
'user' => [
'required',
'in:root,'.config('core.ssh_user'),
],
'auto_start' => [
'required',
'in:0,1',
],
'auto_restart' => [
'required',
'in:0,1',
],
'numprocs' => [
'required',
'numeric',
'min:1',
],
];
Validator::make($input, $rules)->validateWithBag('createQueue');
}
}

View File

@ -0,0 +1,47 @@
<?php
namespace App\Actions\SSL;
use App\Enums\SslType;
use App\Models\Site;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class CreateSSL
{
/**
* @throws ValidationException
*/
public function create(Site $site, array $input): void
{
$this->validate($input);
if ($input['type'] == SslType::LETSENCRYPT) {
$site->createFreeSsl();
}
if ($input['type'] == SslType::CUSTOM) {
$site->createCustomSsl($input['certificate'], $input['private']);
}
}
/**
* @throws ValidationException
*/
protected function validate(array $input): void
{
$rules = [
'type' => [
'required',
Rule::in(SslType::getValues()),
],
];
if (isset($input['type']) && $input['type'] == SslType::CUSTOM) {
$rules['certificate'] = 'required';
$rules['private'] = 'required';
}
Validator::make($input, $rules)->validateWithBag('createSSL');
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Actions\Script;
use App\Models\Script;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateScript
{
/**
* @throws ValidationException
*/
public function handle(User $creator, array $input): Script
{
$this->validateInputs($input);
$script = new Script([
'user_id' => $creator->id,
'name' => $input['name'],
'content' => $input['content'],
]);
$script->save();
return $script;
}
/**
* @throws ValidationException
*/
private function validateInputs(array $input): void
{
$rules = [
'name' => 'required',
'content' => 'required',
];
Validator::make($input, $rules)->validateWithBag('createScript');
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Actions\Script;
use App\Models\User;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
class GetScripts
{
public function handle(User $user): LengthAwarePaginator
{
return $user->scripts()
->orderBy('id', 'desc')
->paginate(6)
->onEachSide(1);
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Actions\Script;
use App\Models\Script;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class UpdateScript
{
/**
* @throws ValidationException
*/
public function handle(Script $script, array $input): Script
{
$this->validateInputs($input);
$script->name = $input['name'];
$script->content = $input['content'];
$script->save();
return $script;
}
/**
* @throws ValidationException
*/
private function validateInputs(array $input): void
{
$rules = [
'name' => 'required',
'content' => 'required',
];
Validator::make($input, $rules)->validateWithBag('updateScript');
}
}

View File

@ -0,0 +1,183 @@
<?php
namespace App\Actions\Server;
use App\Enums\FirewallRuleStatus;
use App\Exceptions\ServerProviderError;
use App\Jobs\Installation\ContinueInstallation;
use App\Models\Server;
use App\Models\User;
use App\ValidationRules\RestrictedIPAddressesRule;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Throwable;
class CreateServer
{
/**
* @throws Throwable
*/
public function create(User $creator, array $input): Server
{
$this->validateInputs($input);
$server = new Server([
'user_id' => $creator->id,
'name' => $input['name'],
'ssh_user' => config('core.server_providers_default_user')[$input['provider']][$input['os']],
'ip' => $input['ip'],
'port' => $input['port'] ?? 22,
'os' => $input['os'],
'type' => $input['type'],
'provider' => $input['provider'],
'authentication' => [
'user' => config('core.ssh_user'),
'pass' => Str::random(10),
'root_pass' => Str::random(10),
],
'progress' => 0,
'progress_step' => 'Initializing',
]);
try {
DB::beginTransaction();
if ($server->provider != 'custom') {
$server->provider_id = $input['server_provider'];
}
// validate type
$this->validateType($server, $input);
$server->type_data = $server->type()->data($input);
// validate provider
$this->validateProvider($server, $input);
$server->provider_data = $server->provider()->data($input);
// save
$server->save();
// create firewall rules
$this->createFirewallRules($server);
// create instance
$server->provider()->create();
// add services
$server->type()->createServices($input);
// install server
if ($server->provider == 'custom') {
$server->install();
} else {
$server->progress_step = __('Installation will begin in 3 minutes!');
$server->save();
dispatch(new ContinueInstallation($server))
->delay(now()->addMinutes(3))
->onQueue('default');
}
DB::commit();
return $server;
} catch (Exception $e) {
$server->provider()->delete();
DB::rollBack();
if ($e instanceof ServerProviderError) {
throw ValidationException::withMessages([
'provider' => __('Provider Error: ').$e->getMessage(),
])->errorBag('createServer');
}
throw $e;
}
}
/**
* @throws ValidationException
*/
private function validateInputs(array $input): void
{
$rules = [
'provider' => 'required|in:'.implode(',', config('core.server_providers')),
'name' => 'required',
'os' => 'required|in:'.implode(',', config('core.operating_systems')),
'type' => [
'required',
Rule::in(config('core.server_types')),
],
];
Validator::make($input, $rules)->validate();
if ($input['provider'] != 'custom') {
$rules['server_provider'] = 'required|exists:server_providers,id,user_id,'.auth()->user()->id;
}
if ($input['provider'] == 'custom') {
$rules['ip'] = [
'required',
'ip',
new RestrictedIPAddressesRule(),
];
$rules['port'] = [
'required',
'numeric',
'min:1',
'max:65535',
];
}
Validator::make($input, $rules)->validate();
}
/**
* @throws ValidationException
*/
private function validateType(Server $server, array $input): void
{
Validator::make($input, $server->type()->createValidationRules($input))
->validate();
}
/**
* @throws ValidationException
*/
private function validateProvider(Server $server, array $input): void
{
Validator::make($input, $server->provider()->createValidationRules($input))
->validate();
}
private function createFirewallRules(Server $server): void
{
$server->firewallRules()->createMany([
[
'type' => 'allow',
'protocol' => 'ssh',
'port' => 22,
'source' => '0.0.0.0',
'mask' => 0,
'status' => FirewallRuleStatus::READY,
],
[
'type' => 'allow',
'protocol' => 'http',
'port' => 80,
'source' => '0.0.0.0',
'mask' => 0,
'status' => FirewallRuleStatus::READY,
],
[
'type' => 'allow',
'protocol' => 'https',
'port' => 443,
'source' => '0.0.0.0',
'mask' => 0,
'status' => FirewallRuleStatus::READY,
],
]);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class DeleteServer
{
/**
* @throws ValidationException
*/
public function delete(Server $server, array $input): void
{
$this->validateDelete($input);
DB::transaction(function () use ($server) {
$server->cleanDelete();
});
}
/**
* @throws ValidationException
*/
protected function validateDelete(array $input): void
{
Validator::make($input, [
'confirm' => 'required|in:delete',
])->validateWithBag('deleteServer');
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use App\ValidationRules\RestrictedIPAddressesRule;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class EditServer
{
/**
* @throws ValidationException
*/
public function edit(Server $server, array $input): Server
{
$this->validate($input);
$checkConnection = false;
if (isset($input['name'])) {
$server->name = $input['name'];
}
if (isset($input['ip'])) {
if ($server->ip !== $input['ip']) {
$checkConnection = true;
}
$server->ip = $input['ip'];
}
if (isset($input['port'])) {
if ($server->port !== $input['port']) {
$checkConnection = true;
}
$server->port = $input['port'];
}
$server->save();
if ($checkConnection) {
$server->checkConnection();
}
return $server;
}
/**
* @throws ValidationException
*/
protected function validate(array $input): void
{
Validator::make($input, [
'ip' => [
new RestrictedIPAddressesRule(),
],
])->validateWithBag('editServer');
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace App\Actions\Server;
use App\Models\Server;
use Illuminate\Database\Eloquent\Collection;
class GetServers
{
public function handle(): Collection
{
return Server::query()->latest()->get();
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace App\Actions\ServerProvider;
use App\Contracts\ServerProvider as ServerProviderContract;
use App\Models\ServerProvider;
use App\Models\User;
use Exception;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class CreateServerProvider
{
/**
* @throws ValidationException
*/
public function create(User $user, array $input): ServerProvider
{
$this->validateInput($input);
$provider = $this->getProvider($input['provider']);
$this->validateProvider($provider, $input);
try {
$provider->connect($input);
} catch (Exception) {
throw ValidationException::withMessages([
'provider' => [
__("Couldn't connect to provider. Please check your credentials and try again later."),
],
]);
}
$serverProvider = new ServerProvider();
$serverProvider->user_id = $user->id;
$serverProvider->profile = $input['name'];
$serverProvider->provider = $input['provider'];
$serverProvider->credentials = $provider->credentialData($input);
$serverProvider->save();
return $serverProvider;
}
private function getProvider($name): ServerProviderContract
{
$providerClass = config('core.server_providers_class.'.$name);
return new $providerClass();
}
/**
* @throws ValidationException
*/
private function validateInput(array $input): void
{
Validator::make($input, [
'name' => [
'required',
],
'provider' => [
'required',
Rule::in(config('core.server_providers')),
Rule::notIn('custom'),
],
])->validate();
}
/**
* @throws ValidationException
*/
private function validateProvider(ServerProviderContract $provider, array $input): void
{
Validator::make($input, $provider->credentialValidationRules($input))->validate();
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Actions\Site;
use App\Models\Site;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class ChangePHPVersion
{
/**
* @throws ValidationException
*/
public function handle(Site $site, array $input): void
{
$this->validate($site, $input);
$site->changePHPVersion($input['php_version']);
}
/**
* @throws ValidationException
*/
protected function validate(Site $site, array $input): void
{
Validator::make($input, [
'php_version' => 'required|in:'.implode(',', $site->server->installedPHPVersions()),
])->validateWithBag('changePHPVersion');
}
}

View File

@ -0,0 +1,51 @@
<?php
namespace App\Actions\Site;
use App\Models\Redirect;
use App\Models\Site;
use Exception;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateRedirect
{
/**
* @throws Exception
*/
public function handle(Site $site, array $input): void
{
$this->validate($input);
$redirect = new Redirect([
'site_id' => $site->id,
'mode' => $input['mode'],
'from' => $input['from'],
'to' => $input['to'],
]);
$redirect->save();
$redirect->addToServer();
}
/**
* @throws ValidationException
*/
private function validate(array $input): void
{
$rules = [
'mode' => [
'required',
'in:301,302',
],
'from' => [
'required',
],
'to' => [
'required',
'url',
],
];
Validator::make($input, $rules)->validateWithBag('createRedirect');
}
}

118
app/Actions/Site/CreateSite.php Executable file
View File

@ -0,0 +1,118 @@
<?php
namespace App\Actions\Site;
use App\Enums\SiteStatus;
use App\Exceptions\SourceControlIsNotConnected;
use App\Models\Server;
use App\Models\Site;
use App\ValidationRules\DomainRule;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
class CreateSite
{
/**
* @throws SourceControlIsNotConnected
* @throws ValidationException
*/
public function create(Server $server, array $input): Site
{
$this->validateInputs($server, $input);
try {
DB::beginTransaction();
$site = new Site([
'server_id' => $server->id,
'type' => $input['type'],
'domain' => $input['domain'],
'aliases' => isset($input['alias']) ? [$input['alias']] : [],
'path' => '/home/'.$server->ssh_user.'/'.$input['domain'],
'status' => SiteStatus::INSTALLING,
]);
// fields based on type
$site->fill($site->type()->createFields($input));
// check has access to repository
try {
if ($site->sourceControl()) {
$site->sourceControl()->getRepo($site->repository);
}
} catch (SourceControlIsNotConnected) {
throw ValidationException::withMessages([
'source_control' => __('Source control is not connected'),
]);
}
// detect php version
if ($site->type()->language() === 'php') {
$site->php_version = $input['php_version'];
}
// validate type
$this->validateType($site, $input);
// set type data
$site->type_data = $site->type()->data($input);
// save
$site->save();
// create default deployment script
$site->deploymentScript()->create([
'name' => 'default',
'content' => '',
]);
// install server
$site->install();
DB::commit();
return $site;
} catch (Exception $e) {
DB::rollBack();
throw $e;
}
}
/**
* @throws ValidationException
*/
private function validateInputs(Server $server, array $input): void
{
$rules = [
'type' => [
'required',
Rule::in(config('core.site_types')),
],
'domain' => [
'required',
new DomainRule(),
Rule::unique('sites', 'domain')->where(function ($query) use ($server) {
return $query->where('server_id', $server->id);
}),
],
'alias' => [
new DomainRule(),
],
];
Validator::make($input, $rules)->validate();
}
/**
* @throws ValidationException
*/
private function validateType(Site $site, array $input): void
{
$rules = $site->type()->createValidationRules($input);
Validator::make($input, $rules)->validate();
}
}

32
app/Actions/Site/DeleteSite.php Executable file
View File

@ -0,0 +1,32 @@
<?php
namespace App\Actions\Site;
use App\Models\Site;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class DeleteSite
{
/**
* @throws ValidationException
*/
public function handle(Site $site, array $input): void
{
$this->validateDelete($input);
$site->update(['status' => 'deleting']);
$site->remove();
}
/**
* @throws ValidationException
*/
protected function validateDelete(array $input): void
{
Validator::make($input, [
'confirm' => 'required|in:delete',
])->validateWithBag('deleteSite');
}
}

41
app/Actions/Site/EditSite.php Executable file
View File

@ -0,0 +1,41 @@
<?php
namespace App\Actions\Site;
use App\Models\Site;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class EditSite
{
/**
* @throws ValidationException
*/
public function handle(Site $site, array $input): Site
{
// validate type
$this->validateType($site, $input);
// set type data
$site->type_data = $site->type()->data($input);
// save
$site->port = $input['port'] ?? null;
$site->save();
// edit
$site->type()->edit();
return $site;
}
/**
* @throws ValidationException
*/
private function validateType(Site $site, array $input): void
{
$rules = $site->type()->editValidationRules($input);
Validator::make($input, $rules)->validateWithBag('editSite');
}
}

14
app/Actions/Site/GetSites.php Executable file
View File

@ -0,0 +1,14 @@
<?php
namespace App\Actions\Site;
use App\Models\Server;
use Illuminate\Database\Eloquent\Collection;
class GetSites
{
public function handle(Server $server): Collection
{
return $server->sites()->orderBy('id', 'desc')->get();
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Actions\Site;
use App\Models\Site;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class UpdateBranch
{
/**
* @throws ValidationException
*/
public function update(Site $site, array $input): void
{
$this->validate($input);
$site->updateBranch($input['branch']);
}
/**
* @throws ValidationException
*/
protected function validate(array $input): void
{
Validator::make($input, [
'branch' => 'required',
])->validateWithBag('updateBranch');
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Actions\Site;
use App\Models\Site;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class UpdateDeploymentScript
{
/**
* @throws ValidationException
*/
public function update(Site $site, array $input): void
{
$this->validate($input);
$site->deploymentScript()->update([
'content' => $input['script'],
]);
}
/**
* @throws ValidationException
*/
protected function validate(array $input): void
{
Validator::make($input, [
'script' => 'required',
])->validateWithBag('updateDeploymentScript');
}
}

18
app/Actions/Site/UpdateEnv.php Executable file
View File

@ -0,0 +1,18 @@
<?php
namespace App\Actions\Site;
use App\Models\Site;
class UpdateEnv
{
public function handle(Site $site, array $input): void
{
$typeData = $site->type_data;
$typeData['env'] = $input['env'];
$site->type_data = $typeData;
$site->save();
$site->deployEnv();
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Actions\SourceControl;
use App\Models\SourceControl;
use Illuminate\Validation\ValidationException;
class ConnectSourceControl
{
public function connect(string $provider, array $input): void
{
$sourceControl = SourceControl::query()
->where('provider', $provider)
->first();
if (! $sourceControl) {
$sourceControl = new SourceControl([
'provider' => $provider,
]);
}
if (! $input['token']) {
$sourceControl->delete();
return;
}
$sourceControl->access_token = $input['token'];
if (! $sourceControl->provider()->connect()) {
throw ValidationException::withMessages([
'token' => __('Cannot connect to :provider or invalid token!', ['provider' => $provider]),
]);
}
$sourceControl->save();
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Actions\SshKey;
use App\Models\SshKey;
use App\Models\User;
use App\ValidationRules\SshKeyRule;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
class CreateSshKey
{
/**
* @throws ValidationException
*/
public function create(User $user, array $input): SshKey
{
$this->validate($input);
$key = new SshKey([
'user_id' => $user->id,
'name' => $input['name'],
'public_key' => $input['public_key'],
]);
$key->save();
return $key;
}
/**
* @throws ValidationException
*/
private function validate(array $input): void
{
Validator::make($input, [
'name' => 'required',
'public_key' => [
'required',
new SshKeyRule(),
],
])->validate();
}
}

View File

@ -0,0 +1,30 @@
<?php
namespace App\Actions\StorageProvider;
use App\Models\StorageProvider;
use App\Models\User;
use Illuminate\Validation\ValidationException;
class AddStorageProvider
{
use ValidateProvider;
/**
* @throws ValidationException
*/
public function add(User $user, array $input): mixed
{
$this->validate($user, $input);
$storageProvider = new StorageProvider([
'user_id' => $user->id,
'provider' => $input['provider'],
'label' => $input['label'],
'connected' => false,
]);
$storageProvider->save();
return $storageProvider->provider()->connect();
}
}

View File

@ -0,0 +1,34 @@
<?php
namespace App\Actions\StorageProvider;
use App\Models\StorageProvider;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Laravel\Socialite\Facades\Socialite;
use Laravel\Socialite\Two\User;
use Throwable;
class HandleProviderCallback
{
public function callback(Request $request, string $provider): string|RedirectResponse
{
try {
$providerId = $request->session()->get('storage_provider_id');
/** @var StorageProvider $storageProvider */
$storageProvider = StorageProvider::query()->findOrFail($providerId);
/** @var User $oauthUser */
$oauthUser = Socialite::driver($provider)->user();
$storageProvider->token = $oauthUser->token;
$storageProvider->refresh_token = $oauthUser->refreshToken;
$storageProvider->token_expires_at = now()->addSeconds($oauthUser->expiresIn);
$storageProvider->connected = true;
$storageProvider->save();
/** @TODO toast success message */
} catch (Throwable) {
/** @TODO toast failed message */
}
return redirect()->route('storage-providers');
}
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Actions\StorageProvider;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
trait ValidateProvider
{
/**
* @throws ValidationException
*/
private function validate(User $user, array $input): void
{
Validator::make($input, [
'label' => [
'required',
Rule::unique('storage_providers', 'label')->where('user_id', $user->id),
],
'provider' => [
'required',
Rule::in(config('core.storage_providers')),
],
])->validateWithBag('addStorageProvider');
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace App\Actions\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class UpdateUserPassword
{
/**
* Validate and update the user's password.
*/
public function update($user, array $input): void
{
Validator::make($input, [
'current_password' => ['required', 'string', 'current-password'],
'password' => ['required', 'string'],
'password_confirmation' => ['required', 'same:password'],
])->validate();
$user->forceFill([
'password' => Hash::make($input['password']),
])->save();
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Actions\User;
use App\Models\User;
use Exception;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class UpdateUserProfileInformation
{
/**
* Validate and update the given user's profile information.
*
* @throws Exception
*/
public function update(User $user, array $input): void
{
Validator::make($input, [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'email', 'max:255', Rule::unique('users')->ignore($user->id)],
])->validateWithBag('updateProfileInformation');
if ($input['email'] !== $user->email) {
$this->updateVerifiedUser($user, $input);
} else {
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
])->save();
}
}
/**
* Update the given verified user's profile information.
*/
protected function updateVerifiedUser(User $user, array $input): void
{
$user->forceFill([
'name' => $input['name'],
'email' => $input['email'],
])->save();
}
}