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

24
app/Models/AbstractModel.php Executable file
View File

@ -0,0 +1,24 @@
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $id
* @property Carbon $created_at
* @property Carbon $updated_at
*/
abstract class AbstractModel extends Model
{
public function jsonUpdate(string $field, string $key, mixed $value, bool $save = true): void
{
$current = $this->{$field};
$current[$key] = $value;
$this->{$field} = $current;
if ($save) {
$this->save();
}
}
}

87
app/Models/Backup.php Normal file
View File

@ -0,0 +1,87 @@
<?php
namespace App\Models;
use App\Jobs\Backup\RunBackup;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Str;
/**
* @property string $type
* @property string $name
* @property int $server_id
* @property int $storage_id
* @property int $database_id
* @property string $interval
* @property int $keep_backups
* @property string $status
* @property Server $server
* @property StorageProvider $storage
* @property Database $database
* @property BackupFile[] $files
*/
class Backup extends AbstractModel
{
use HasFactory;
protected $fillable = [
'type',
'name',
'server_id',
'storage_id',
'database_id',
'interval',
'keep_backups',
'status',
];
protected $casts = [
'server_id' => 'integer',
'storage_id' => 'integer',
'database_id' => 'integer',
'keep_backups' => 'integer',
];
public static function boot(): void
{
parent::boot();
static::deleting(function (Backup $backup) {
$backup->files()->delete();
});
}
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
public function storage(): BelongsTo
{
return $this->belongsTo(StorageProvider::class, 'storage_id');
}
public function database(): BelongsTo
{
return $this->belongsTo(Database::class);
}
public function files(): HasMany
{
return $this->hasMany(BackupFile::class, 'backup_id');
}
public function run(): void
{
$file = new BackupFile([
'backup_id' => $this->id,
'name' => Str::of($this->name)->slug().'-'.now()->format('YmdHis'),
'status' => 'creating',
]);
$file->save();
dispatch(new RunBackup($file))->onConnection('ssh');
}
}

88
app/Models/BackupFile.php Normal file
View File

@ -0,0 +1,88 @@
<?php
namespace App\Models;
use App\Jobs\Backup\RestoreDatabase;
use App\Jobs\StorageProvider\DeleteFile;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $backup_id
* @property string $name
* @property int $size
* @property string $status
* @property string $restored_to
* @property Carbon $restored_at
* @property Backup $backup
* @property string $path
* @property string $storage_path
*/
class BackupFile extends AbstractModel
{
use HasFactory;
protected $fillable = [
'backup_id',
'name',
'size',
'status',
'restored_to',
'restored_at',
];
protected $casts = [
'backup_id' => 'integer',
'restored_at' => 'datetime',
];
protected static function booted(): void
{
static::created(function (BackupFile $backupFile) {
$keep = $backupFile->backup->keep_backups;
if ($backupFile->backup->files()->count() > $keep) {
/* @var BackupFile $lastFileToKeep */
$lastFileToKeep = $backupFile->backup->files()->orderByDesc('id')->skip($keep)->first();
if ($lastFileToKeep) {
$files = $backupFile->backup->files()
->where('id', '<=', $lastFileToKeep->id)
->get();
foreach ($files as $file) {
$file->delete();
}
}
}
});
static::deleted(function (BackupFile $backupFile) {
dispatch(new DeleteFile(
$backupFile->backup->storage,
[$backupFile->storage_path]
));
});
}
public function backup(): BelongsTo
{
return $this->belongsTo(Backup::class);
}
public function getPathAttribute(): string
{
return '/home/'.$this->backup->server->ssh_user.'/'.$this->name.'.zip';
}
public function getStoragePathAttribute(): string
{
return '/'.$this->backup->name.'/'.$this->name.'.zip';
}
public function restore(Database $database): void
{
$this->status = 'restoring';
$this->restored_to = $database->name;
$this->save();
dispatch(new RestoreDatabase($this, $database))->onConnection('ssh');
}
}

90
app/Models/CronJob.php Executable file
View File

@ -0,0 +1,90 @@
<?php
namespace App\Models;
use App\Enums\CronjobStatus;
use App\Jobs\CronJob\AddToServer;
use App\Jobs\CronJob\RemoveFromServer;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $server_id
* @property string $command
* @property string $user
* @property string $frequency
* @property string $frequency_label
* @property bool $hidden
* @property string $status
* @property string $crontab
* @property Server $server
*/
class CronJob extends AbstractModel
{
use HasFactory;
protected $fillable = [
'server_id',
'command',
'user',
'frequency',
'hidden',
'status',
];
protected $casts = [
'server_id' => 'integer',
'hidden' => 'boolean',
];
protected $appends = [
'frequency_label',
];
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
public function getCrontabAttribute(): string
{
$data = '';
$cronJobs = $this->server->cronJobs()->where('user', $this->user)->get();
foreach ($cronJobs as $key => $cronJob) {
$data .= $cronJob->frequency.' '.$cronJob->command;
if ($key != count($cronJobs) - 1) {
$data .= "\n";
}
}
return $data;
}
public function addToServer(): void
{
dispatch(new AddToServer($this))->onConnection('ssh');
}
public function removeFromServer(): void
{
$this->status = CronjobStatus::DELETING;
$this->save();
dispatch(new RemoveFromServer($this))->onConnection('ssh');
}
public function getFrequencyLabelAttribute(): string
{
$labels = [
'* * * * *' => 'Every minute',
'0 * * * *' => 'Hourly',
'0 0 * * *' => 'Daily',
'0 0 * * 0' => 'Weekly',
'0 0 1 * *' => 'Monthly',
];
if (isset($labels[$this->frequency])) {
return $labels[$this->frequency];
}
return $this->frequency;
}
}

79
app/Models/Database.php Executable file
View File

@ -0,0 +1,79 @@
<?php
namespace App\Models;
use App\Enums\DatabaseStatus;
use App\Jobs\Database\CreateOnServer;
use App\Jobs\Database\DeleteFromServer;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property int $server_id
* @property string $name
* @property string $status
* @property Server $server
* @property Backup[] $backups
*/
class Database extends AbstractModel
{
use HasFactory;
protected $fillable = [
'server_id',
'name',
'status',
];
protected $casts = [
'server_id' => 'integer',
];
public static function boot(): void
{
parent::boot();
static::deleting(function (Database $database) {
$database->server->databaseUsers()->each(function (DatabaseUser $user) use ($database) {
$databases = $user->databases;
if ($databases && in_array($database->name, $databases)) {
unset($databases[array_search($database->name, $databases)]);
$user->databases = $databases;
$user->save();
}
});
$database->backups()->each(function (Backup $backup) {
$backup->delete();
});
});
}
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
/**
* create database on server
*/
public function createOnServer(): void
{
dispatch(new CreateOnServer($this))->onConnection('ssh');
}
/**
* delete database from server
*/
public function deleteFromServer(): void
{
$this->status = DatabaseStatus::DELETING;
$this->save();
dispatch(new DeleteFromServer($this))->onConnection('ssh');
}
public function backups(): HasMany
{
return $this->hasMany(Backup::class)->where('type', 'database');
}
}

96
app/Models/DatabaseUser.php Executable file
View File

@ -0,0 +1,96 @@
<?php
namespace App\Models;
use App\Enums\DatabaseStatus;
use App\Jobs\DatabaseUser\CreateOnServer;
use App\Jobs\DatabaseUser\DeleteFromServer;
use App\Jobs\DatabaseUser\LinkUser;
use App\Jobs\DatabaseUser\UnlinkUser;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $server_id
* @property string $username
* @property string $password
* @property array $databases
* @property string $host
* @property string $status
* @property Server $server
* @property string $full_user
*/
class DatabaseUser extends AbstractModel
{
use HasFactory;
protected $fillable = [
'server_id',
'username',
'password',
'databases',
'host',
'status',
];
protected $casts = [
'server_id' => 'integer',
'password' => 'encrypted',
'databases' => 'array',
];
protected $hidden = [
'password',
];
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
public function scopeHasDatabase(Builder $query, string $databaseName): Builder
{
return $query->where('databases', 'like', "%\"$databaseName\"%");
}
public function createOnServer(): void
{
dispatch(new CreateOnServer($this))->onConnection('ssh');
}
public function deleteFromServer(): void
{
$this->status = DatabaseStatus::DELETING;
$this->save();
dispatch(new DeleteFromServer($this))->onConnection('ssh');
}
public function linkNewDatabase(string $name): void
{
$linkedDatabases = $this->databases ?? [];
if (! in_array($name, $linkedDatabases)) {
$linkedDatabases[] = $name;
$this->databases = $linkedDatabases;
$this->unlinkUser();
$this->linkUser();
$this->save();
}
}
public function linkUser(): void
{
dispatch(new LinkUser($this))->onConnection('ssh');
}
public function unlinkUser(): void
{
dispatch(new UnlinkUser($this))->onConnection('ssh');
}
public function getFullUserAttribute(): string
{
return $this->username.'@'.$this->host;
}
}

67
app/Models/Deployment.php Executable file
View File

@ -0,0 +1,67 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $site_id
* @property int $deployment_script_id
* @property int $log_id
* @property string $commit_id
* @property string $commit_id_short
* @property array $commit_data
* @property string $status
* @property Site $site
* @property DeploymentScript $deploymentScript
* @property ServerLog $log
*/
class Deployment extends AbstractModel
{
use HasFactory;
protected $fillable = [
'site_id',
'deployment_script_id',
'log_id',
'commit_id',
'commit_data',
'status',
];
protected $casts = [
'site_id' => 'integer',
'deployment_script_id' => 'integer',
'log_id' => 'integer',
'commit_data' => 'json',
];
protected $appends = [
'commit_id_short',
];
public function site(): BelongsTo
{
return $this->belongsTo(Site::class);
}
public function deploymentScript(): BelongsTo
{
return $this->belongsTo(DeploymentScript::class);
}
public function log(): BelongsTo
{
return $this->belongsTo(ServerLog::class, 'log_id');
}
public function getCommitIdShortAttribute(): string
{
if ($this->commit_id) {
return substr($this->commit_id, 0, 7);
}
return '';
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $site_id
* @property string $name
* @property string $content
* @property Site $site
*/
class DeploymentScript extends AbstractModel
{
use HasFactory;
protected $fillable = [
'site_id',
'name',
'content',
];
protected $casts = [
'site_id' => 'integer',
];
public function site(): BelongsTo
{
return $this->belongsTo(Site::class);
}
}

68
app/Models/FirewallRule.php Executable file
View File

@ -0,0 +1,68 @@
<?php
namespace App\Models;
use App\Enums\FirewallRuleStatus;
use App\Jobs\Firewall\AddToServer;
use App\Jobs\Firewall\RemoveFromServer;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $server_id
* @property string $type
* @property string $protocol
* @property string $real_protocol
* @property int $port
* @property string $source
* @property string $mask
* @property string $note
* @property string $status
* @property Server $server
*/
class FirewallRule extends AbstractModel
{
use HasFactory;
protected $fillable = [
'server_id',
'type',
'protocol',
'port',
'source',
'mask',
'note',
'status',
];
protected $casts = [
'server_id' => 'integer',
'port' => 'integer',
];
protected $appends = [
'real_protocol',
];
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
public function addToServer(): void
{
dispatch(new AddToServer($this))->onConnection('ssh');
}
public function removeFromServer(): void
{
$this->status = FirewallRuleStatus::DELETING;
$this->save();
dispatch(new RemoveFromServer($this))->onConnection('ssh');
}
public function getRealProtocolAttribute(): string
{
return $this->protocol === 'udp' ? 'udp' : 'tcp';
}
}

83
app/Models/GitHook.php Executable file
View File

@ -0,0 +1,83 @@
<?php
namespace App\Models;
use App\Exceptions\FailedToDeployGitHook;
use Exception;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\DB;
use Throwable;
/**
* @property int $site_id
* @property int $source_control_id
* @property string $secret
* @property array $events
* @property array $actions
* @property string $hook_id
* @property array $hook_response
* @property Site $site
* @property SourceControl $sourceControl
*/
class GitHook extends AbstractModel
{
protected $fillable = [
'site_id',
'source_control_id',
'secret',
'events',
'actions',
'hook_id',
'hook_response',
];
protected $casts = [
'site_id' => 'integer',
'source_control_id' => 'integer',
'events' => 'array',
'actions' => 'array',
'hook_response' => 'json',
];
public function site(): BelongsTo
{
return $this->belongsTo(Site::class);
}
public function sourceControl(): BelongsTo
{
return $this->belongsTo(SourceControl::class);
}
public function scopeHasEvent(Builder $query, string $event): Builder
{
return $query->where('events', 'like', "%\"{$event}\"%");
}
/**
* @throws FailedToDeployGitHook
*/
public function deployHook(): void
{
$this->update(
$this->sourceControl->provider()->deployHook($this->site->repository, $this->events, $this->secret)
);
}
/**
* @throws Throwable
*/
public function destroyHook(): void
{
try {
DB::beginTransaction();
$this->sourceControl->provider()->destroyHook($this->site->repository, $this->hook_id);
$this->delete();
DB::commit();
} catch (Exception $e) {
DB::rollBack();
throw $e;
}
}
}

View File

@ -0,0 +1,39 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* @property string $provider
* @property string $label
* @property array $data
* @property bool $connected
* @property bool $is_default
* @property User $user
*/
class NotificationChannel extends AbstractModel
{
use HasFactory;
protected $fillable = [
'provider',
'label',
'data',
'connected',
'is_default',
];
protected $casts = [
'data' => 'json',
'connected' => 'boolean',
'is_default' => 'boolean',
];
public function provider(): \App\Contracts\NotificationChannel
{
$provider = config('core.notification_channels_providers_class')[$this->provider];
return new $provider($this);
}
}

143
app/Models/Queue.php Normal file
View File

@ -0,0 +1,143 @@
<?php
namespace App\Models;
use App\Enums\QueueStatus;
use App\Jobs\Queue\Deploy;
use App\Jobs\Queue\GetLogs;
use App\Jobs\Queue\Manage;
use App\Jobs\Queue\Remove;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $server_id
* @property int $site_id
* @property string $command
* @property string $user
* @property bool $auto_start
* @property bool $auto_restart
* @property int $numprocs
* @property int $redirect_stderr
* @property string $stdout_logfile
* @property string $status
* @property string $log_directory
* @property string $log_file
* @property Server $server
* @property Site $site
*/
class Queue extends AbstractModel
{
use HasFactory;
protected $fillable = [
'server_id',
'site_id',
'command',
'user',
'auto_start',
'auto_restart',
'numprocs',
'redirect_stderr',
'stdout_logfile',
'status',
];
protected $casts = [
'server_id' => 'integer',
'site_id' => 'integer',
'auto_start' => 'boolean',
'auto_restart' => 'boolean',
'numprocs' => 'integer',
'redirect_stderr' => 'boolean',
];
public function getServerIdAttribute(int $value): int
{
if (! $value) {
$value = $this->site->server_id;
$this->fill(['server_id' => $this->site->server_id]);
$this->save();
}
return $value;
}
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
public function site(): BelongsTo
{
return $this->belongsTo(Site::class);
}
public function getLogDirectoryAttribute(): string
{
return '/home/'.$this->user.'/.logs/workers';
}
public function getLogFileAttribute(): string
{
return $this->log_directory.'/'.$this->id.'.log';
}
public function deploy(): void
{
dispatch(new Deploy($this))->onConnection('ssh');
}
private function action(string $type, string $status, string $successStatus, string $failStatus, string $failMessage): void
{
$this->status = $status;
$this->save();
dispatch(new Manage($this, $type, $successStatus, $failStatus, $failMessage))
->onConnection('ssh');
}
public function start(): void
{
$this->action(
'start',
QueueStatus::STARTING,
QueueStatus::RUNNING,
QueueStatus::FAILED,
__('Failed to start')
);
}
public function stop(): void
{
$this->action(
'stop',
QueueStatus::STOPPING,
QueueStatus::STOPPED,
QueueStatus::FAILED,
__('Failed to stop')
);
}
public function restart(): void
{
$this->action(
'restart',
QueueStatus::RESTARTING,
QueueStatus::RUNNING,
QueueStatus::FAILED,
__('Failed to restart')
);
}
public function remove(): void
{
$this->status = QueueStatus::DELETING;
$this->save();
dispatch(new Remove($this))->onConnection('ssh');
}
public function getLogs(): void
{
dispatch(new GetLogs($this))->onConnection('ssh');
}
}

50
app/Models/Redirect.php Normal file
View File

@ -0,0 +1,50 @@
<?php
namespace App\Models;
use App\Jobs\Redirect\AddToServer;
use App\Jobs\Redirect\DeleteFromServer;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $site_id
* @property int $mode
* @property string $from
* @property string $to
* @property string $status
*/
class Redirect extends AbstractModel
{
use HasFactory;
protected $fillable = [
'site_id',
'mode',
'from',
'to',
'status',
];
protected $casts = [
'site_id' => 'integer',
'mode' => 'integer',
];
public function site(): BelongsTo
{
return $this->belongsTo(Site::class);
}
public function addToServer(): void
{
dispatch(new AddToServer($this))->onConnection('ssh');
}
public function deleteFromServer(): void
{
$this->status = 'deleting';
$this->save();
dispatch(new DeleteFromServer($this))->onConnection('ssh');
}
}

44
app/Models/Script.php Normal file
View File

@ -0,0 +1,44 @@
<?php
namespace App\Models;
use App\Jobs\Script\ExecuteOn;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property int $user_id
* @property string $name
* @property string $content
* @property User $creator
*/
class Script extends AbstractModel
{
use HasFactory;
protected $fillable = [
'user_id',
'name',
'content',
];
protected $casts = [
'user_id' => 'integer',
];
public function creator(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function executions(): HasMany
{
return $this->hasMany(ScriptExecution::class, 'script_id');
}
public function executeOn(Server $server, string $user): void
{
dispatch(new ExecuteOn($this, $server, $user))->onConnection('ssh');
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $script_id
* @property int $server_id
* @property string $user
* @property Carbon $finished_at
* @property ?Server $server
* @property Script $script
*/
class ScriptExecution extends AbstractModel
{
use HasFactory;
protected $fillable = [
'script_id',
'server_id',
'user',
'finished_at',
];
protected $casts = [
'script_id' => 'integer',
'server_id' => 'integer',
];
public function script(): BelongsTo
{
return $this->belongsTo(Script::class);
}
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
}

388
app/Models/Server.php Executable file
View File

@ -0,0 +1,388 @@
<?php
namespace App\Models;
use App\Contracts\ServerType;
use App\Facades\SSH;
use App\Jobs\Installation\Upgrade;
use App\Jobs\Server\CheckConnection;
use App\Jobs\Server\RebootServer;
use App\Support\Testing\SSHFake;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
/**
* @property int $user_id
* @property string $name
* @property string $ssh_user
* @property string $ip
* @property string $local_ip
* @property int $port
* @property string $os
* @property string $type
* @property array $type_data
* @property string $provider
* @property int $provider_id
* @property array $provider_data
* @property array $authentication
* @property string $public_key
* @property string $status
* @property bool $auto_update
* @property int $available_updates
* @property int $security_updates
* @property int $progress
* @property string $progress_step
* @property User $creator
* @property ServerProvider $serverProvider
* @property ServerLog[] $logs
* @property Site[] $sites
* @property Service[] $services
* @property Database[] $databases
* @property DatabaseUser[] $databaseUsers
* @property FirewallRule[] $firewallRules
* @property CronJob[] $cronJobs
* @property Queue[] $queues
* @property ScriptExecution[] $scriptExecutions
* @property Backup[] $backups
* @property Queue[] $daemons
* @property SshKey[] $sshKeys
* @property string $hostname
*/
class Server extends AbstractModel
{
use HasFactory;
protected $fillable = [
'user_id',
'name',
'ssh_user',
'ip',
'local_ip',
'port',
'os',
'type',
'type_data',
'provider',
'provider_id',
'provider_data',
'authentication',
'public_key',
'status',
'auto_update',
'available_updates',
'security_updates',
'progress',
'progress_step',
];
protected $casts = [
'user_id' => 'integer',
'type_data' => 'json',
'port' => 'integer',
'provider_data' => 'json',
'authentication' => 'encrypted:json',
'auto_update' => 'boolean',
'available_updates' => 'integer',
'security_updates' => 'integer',
'progress' => 'integer',
];
protected $hidden = [
'authentication',
];
public static function boot(): void
{
parent::boot();
static::deleting(function (Server $server) {
$server->sites()->each(function (Site $site) {
$site->delete();
});
$server->provider()->delete();
$server->logs()->delete();
$server->services()->delete();
$server->databases()->delete();
$server->databaseUsers()->delete();
$server->firewallRules()->delete();
$server->cronJobs()->delete();
$server->queues()->delete();
$server->daemons()->delete();
$server->scriptExecutions()->delete();
$server->sshKeys()->detach();
if (File::exists($server->sshKey()['public_key_path'])) {
File::delete($server->sshKey()['public_key_path']);
}
if (File::exists($server->sshKey()['private_key_path'])) {
File::delete($server->sshKey()['private_key_path']);
}
});
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
public function serverProvider(): BelongsTo
{
return $this->belongsTo(ServerProvider::class, 'provider_id');
}
public function logs(): HasMany
{
return $this->hasMany(ServerLog::class);
}
public function sites(): HasMany
{
return $this->hasMany(Site::class);
}
public function services(): HasMany
{
return $this->hasMany(Service::class);
}
public function databases(): HasMany
{
return $this->hasMany(Database::class);
}
public function databaseUsers(): HasMany
{
return $this->hasMany(DatabaseUser::class);
}
public function firewallRules(): HasMany
{
return $this->hasMany(FirewallRule::class);
}
public function cronJobs(): HasMany
{
return $this->hasMany(CronJob::class);
}
public function queues(): HasMany
{
return $this->hasMany(Queue::class);
}
public function scriptExecutions(): HasMany
{
return $this->hasMany(ScriptExecution::class);
}
public function backups(): HasMany
{
return $this->hasMany(Backup::class);
}
public function daemons(): HasMany
{
return $this->queues()->whereNull('site_id');
}
public function service($type, $version = null): ?Service
{
/* @var Service $service */
$service = $this->services()
->where(function ($query) use ($type, $version) {
$query->where('type', $type);
if ($version) {
$query->where('version', $version);
}
})
->first();
return $service;
}
public function defaultService($type): ?Service
{
/* @var Service $service */
$service = $this->services()
->where('type', $type)
->where('is_default', 1)
->first();
return $service;
}
public function getServiceByUnit($unit): ?Service
{
/* @var Service $service */
$service = $this->services()
->where('unit', $unit)
->where('is_default', 1)
->first();
return $service;
}
public function install(): void
{
$this->type()->install();
// $this->team->notify(new ServerInstallationStarted($this));
}
public function ssh(string $user = null, bool $defaultKeys = false): \App\Helpers\SSH|SSHFake
{
return SSH::init($this, $user, $defaultKeys);
}
public function installedPHPVersions(): array
{
$versions = [];
$phps = $this->services()->where('type', 'php')->get(['version']);
foreach ($phps as $php) {
$versions[] = $php->version;
}
return $versions;
}
public function type(): ServerType
{
$typeClass = config('core.server_types_class')[$this->type];
return new $typeClass($this);
}
public function provider(): \App\Contracts\ServerProvider
{
$providerClass = config('core.server_providers_class')[$this->provider];
return new $providerClass($this);
}
public function webserver(string $version = null): ?Service
{
if (! $version) {
return $this->defaultService('webserver');
}
return $this->service('webserver', $version);
}
public function database(string $version = null): ?Service
{
if (! $version) {
return $this->defaultService('database');
}
return $this->service('database', $version);
}
public function firewall(string $version = null): ?Service
{
if (! $version) {
return $this->defaultService('firewall');
}
return $this->service('firewall', $version);
}
public function processManager(string $version = null): ?Service
{
if (! $version) {
return $this->defaultService('process_manager');
}
return $this->service('process_manager', $version);
}
public function php(string $version = null): ?Service
{
if (! $version) {
return $this->defaultService('php');
}
return $this->service('php', $version);
}
public function sshKeys(): BelongsToMany
{
return $this->belongsToMany(SshKey::class, 'server_ssh_keys')
->withPivot('status')
->withTimestamps();
}
public function getSshUserAttribute(string $value): string
{
if ($value) {
return $value;
}
return config('core.ssh_user');
}
public function sshKey(bool $default = false): array
{
if (app()->environment() == 'testing') {
return [
'public_key' => 'public',
'public_key_path' => '/path',
'private_key_path' => '/path',
];
}
if ($default) {
return [
'public_key' => Str::replace("\n", '', File::get(storage_path(config('core.ssh_public_key_name')))),
'public_key_path' => storage_path(config('core.ssh_public_key_name')),
'private_key_path' => storage_path(config('core.ssh_private_key_name')),
];
}
return [
'public_key' => Str::replace("\n", '', Storage::disk(config('core.key_pairs_disk'))->get($this->id.'.pub')),
'public_key_path' => Storage::disk(config('core.key_pairs_disk'))->path($this->id.'.pub'),
'private_key_path' => Storage::disk(config('core.key_pairs_disk'))->path((string) $this->id),
];
}
public function getServiceUnits(): array
{
$units = [];
$services = $this->services;
foreach ($services as $service) {
if ($service->unit) {
$units[] = $service->unit;
}
}
return $units;
}
public function checkConnection(): void
{
dispatch(new CheckConnection($this))->onConnection('ssh');
}
public function installUpdates(): void
{
$this->available_updates = 0;
$this->security_updates = 0;
$this->save();
dispatch(new Upgrade($this))->onConnection('ssh');
}
public function reboot(): void
{
$this->status = 'disconnected';
$this->save();
dispatch(new RebootServer($this))->onConnection('ssh');
}
public function getHostnameAttribute(): string
{
return Str::of($this->name)->slug();
}
}

72
app/Models/ServerLog.php Executable file
View File

@ -0,0 +1,72 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
/**
* @property int $server_id
* @property ?int $site_id
* @property string $type
* @property string $name
* @property string $disk
* @property Server $server
* @property ?Site $site
* @property string $content
*/
class ServerLog extends AbstractModel
{
use HasFactory;
protected $fillable = [
'server_id',
'site_id',
'type',
'name',
'disk',
];
protected $casts = [
'server_id' => 'integer',
'site_id' => 'integer',
];
public function getRouteKey(): string
{
return 'log';
}
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
public function site(): BelongsTo
{
return $this->belongsTo(Site::class);
}
public function write($buf): void
{
if (Str::contains($buf, 'VITO_SSH_ERROR')) {
$buf = str_replace('VITO_SSH_ERROR', '', $buf);
}
if (Storage::disk($this->disk)->exists($this->name)) {
Storage::disk($this->disk)->append($this->name, $buf);
} else {
Storage::disk($this->disk)->put($this->name, $buf);
}
}
public function getContentAttribute(): ?string
{
if (Storage::disk($this->disk)->exists($this->name)) {
return Storage::disk($this->disk)->get($this->name);
}
return '';
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $user_id
* @property string $profile
* @property string $provider
* @property array $credentials
* @property bool $connected
* @property User $user
*/
class ServerProvider extends AbstractModel
{
use HasFactory;
protected $fillable = [
'user_id',
'profile',
'provider',
'credentials',
'connected',
];
protected $casts = [
'user_id' => 'integer',
'credentials' => 'encrypted:array',
'connected' => 'boolean',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function getCredentials(): array
{
return $this->credentials;
}
}

217
app/Models/Service.php Executable file
View File

@ -0,0 +1,217 @@
<?php
namespace App\Models;
use App\Contracts\Database;
use App\Contracts\Firewall;
use App\Contracts\ProcessManager;
use App\Contracts\Webserver;
use App\Enums\ServiceStatus;
use App\Events\Broadcast;
use App\Exceptions\InstallationFailed;
use App\Jobs\Service\Manage;
use App\ServiceHandlers\PHP;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Str;
/**
* @property int $server_id
* @property string $type
* @property array $type_data
* @property string $name
* @property string $version
* @property string $unit
* @property string $logs
* @property string $status
* @property bool $is_default
* @property Server $server
*/
class Service extends AbstractModel
{
use HasFactory;
protected $fillable = [
'server_id',
'type',
'type_data',
'name',
'version',
'unit',
'logs',
'status',
'is_default',
];
protected $casts = [
'server_id' => 'integer',
'type_data' => 'json',
'is_default' => 'boolean',
];
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
public function handler(): Database|Firewall|Webserver|PHP|ProcessManager
{
$handler = config('core.service_handlers')[$this->name];
return new $handler($this);
}
public function installer(): mixed
{
$installer = config('core.service_installers')[$this->name];
return new $installer($this);
}
public function uninstaller(): mixed
{
$uninstaller = config('core.service_uninstallers')[$this->name];
return new $uninstaller($this);
}
public function getUnitAttribute($value): string
{
if ($value) {
return $value;
}
if (isset(config('core.service_units')[$this->name])) {
$value = config('core.service_units')[$this->name][$this->server->os][$this->version];
if ($value) {
$this->fill(['unit' => $value]);
$this->save();
}
}
return $value;
}
public function install(): void
{
Bus::chain([
$this->installer(),
function () {
event(
new Broadcast('install-service-finished', [
'service' => $this,
])
);
},
])->catch(function () {
event(
new Broadcast('install-service-failed', [
'service' => $this,
])
);
})->onConnection('ssh-long')->dispatch();
}
/**
* @throws InstallationFailed
*/
public function validateInstall($result): void
{
if (Str::contains($result, 'Active: active')) {
event(
new Broadcast('install-service-finished', [
'service' => $this,
])
);
} else {
event(
new Broadcast('install-service-failed', [
'service' => $this,
])
);
throw new InstallationFailed();
}
}
public function uninstall(): void
{
$this->status = ServiceStatus::UNINSTALLING;
$this->save();
Bus::chain([
$this->uninstaller(),
function () {
event(
new Broadcast('uninstall-service-finished', [
'service' => $this,
])
);
$this->delete();
},
])->catch(function () {
$this->status = ServiceStatus::FAILED;
$this->save();
event(
new Broadcast('uninstall-service-failed', [
'service' => $this,
])
);
})->onConnection('ssh')->dispatch();
}
public function start(): void
{
$this->action(
'start',
ServiceStatus::STARTING,
ServiceStatus::READY,
ServiceStatus::STOPPED,
__('Failed to start')
);
}
public function stop(): void
{
$this->action(
'stop',
ServiceStatus::STOPPING,
ServiceStatus::STOPPED,
ServiceStatus::FAILED,
__('Failed to stop')
);
}
public function restart(): void
{
$this->action(
'restart',
ServiceStatus::RESTARTING,
ServiceStatus::READY,
ServiceStatus::FAILED,
__('Failed to restart')
);
}
public function action(
string $type,
string $status,
string $successStatus,
string $failStatus,
string $failMessage
): void {
$this->status = $status;
$this->save();
dispatch(new Manage($this, $type, $successStatus, $failStatus, $failMessage))
->onConnection('ssh');
}
public function installedVersions(): array
{
$versions = [];
$services = $this->server->services()->where('type', $this->type)->get(['version']);
foreach ($services as $service) {
$versions[] = $service->version;
}
return $versions;
}
}

397
app/Models/Site.php Executable file
View File

@ -0,0 +1,397 @@
<?php
namespace App\Models;
use App\Contracts\SiteType;
use App\Enums\DeploymentStatus;
use App\Enums\SiteStatus;
use App\Enums\SslStatus;
use App\Exceptions\FailedToDeployGitHook;
use App\Exceptions\SourceControlIsNotConnected;
use App\Jobs\Site\ChangePHPVersion;
use App\Jobs\Site\Deploy;
use App\Jobs\Site\DeployEnv;
use App\Jobs\Site\UpdateBranch;
use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
use Throwable;
/**
* @property int $server_id
* @property string $type
* @property array $type_data
* @property string $domain
* @property array $aliases
* @property string $web_directory
* @property string $web_directory_path
* @property string $path
* @property string $php_version
* @property string $source_control
* @property string $repository
* @property string $branch
* @property string $status
* @property int $port
* @property int $progress
* @property bool $auto_deployment
* @property string $url
* @property Server $server
* @property ServerLog[] $logs
* @property Deployment[] $deployments
* @property ?GitHook $gitHook
* @property DeploymentScript $deploymentScript
* @property Redirect[] $redirects
* @property Queue[] $queues
* @property Ssl[] $ssls
* @property ?Ssl $activeSsl
* @property string $full_repository_url
* @property string $aliases_string
* @property string $deployment_script_text
* @property string $env
*/
class Site extends AbstractModel
{
use HasFactory;
protected $fillable = [
'server_id',
'type',
'type_data',
'domain',
'aliases',
'web_directory',
'path',
'php_version',
'source_control',
'repository',
'branch',
'status',
'port',
'progress',
];
protected $casts = [
'server_id' => 'integer',
'type_data' => 'json',
'port' => 'integer',
'progress' => 'integer',
'auto_deployment' => 'boolean',
'aliases' => 'array',
];
protected $appends = [
'url',
'auto_deployment',
];
public static function boot(): void
{
parent::boot();
static::deleting(function (Site $site) {
$site->redirects()->delete();
$site->queues()->delete();
$site->ssls()->delete();
$site->deployments()->delete();
$site->deploymentScript()->delete();
});
static::created(function (Site $site) {
$site->deploymentScript()->create([
'name' => 'default',
'content' => ''
]);
});
}
public function server(): BelongsTo
{
return $this->belongsTo(Server::class);
}
public function logs(): HasMany
{
return $this->hasMany(ServerLog::class);
}
public function deployments(): HasMany
{
return $this->hasMany(Deployment::class);
}
public function gitHook(): HasOne
{
return $this->hasOne(GitHook::class);
}
public function deploymentScript(): HasOne
{
return $this->hasOne(DeploymentScript::class);
}
public function redirects(): HasMany
{
return $this->hasMany(Redirect::class);
}
public function queues(): HasMany
{
return $this->hasMany(Queue::class);
}
public function ssls(): HasMany
{
return $this->hasMany(Ssl::class);
}
/**
* @throws SourceControlIsNotConnected
*/
public function sourceControl(): SourceControl|HasOne|null
{
if (! $this->source_control) {
return null;
}
if ($this->source_control == 'custom') {
return new SourceControl([
'user_id' => $this->id,
'provider' => 'custom',
'token' => '',
'connected' => true,
]);
}
$sourceControl = SourceControl::query()->where('provider', $this->source_control)->first();
if (! $sourceControl) {
throw new SourceControlIsNotConnected($this->source_control);
}
return $sourceControl;
}
/**
* @throws SourceControlIsNotConnected
*/
public function getFullRepositoryUrlAttribute()
{
return $this->sourceControl()->provider()->fullRepoUrl($this->repository);
}
public function getAliasesStringAttribute(): string
{
if (count($this->aliases) > 0) {
return implode(' ', $this->aliases);
}
return '';
}
public function type(): SiteType
{
$typeClass = config('core.site_types_class.'.$this->type);
return new $typeClass($this);
}
public function install(): void
{
$this->type()->install();
}
public function remove(): void
{
$this->update([
'status' => SiteStatus::DELETING,
]);
$this->type()->delete();
}
public function php(): ?Service
{
if ($this->php_version) {
return $this->server->php($this->php_version);
}
return null;
}
public function changePHPVersion($version): void
{
dispatch(new ChangePHPVersion($this, $version))->onConnection('ssh');
}
public function getDeploymentScriptTextAttribute(): string
{
/* @var DeploymentScript $script */
$script = $this->deploymentScript()->firstOrCreate([
'site_id' => $this->id,
], [
'site_id' => $this->id,
'name' => 'default',
]);
return $script->content;
}
/**
* @throws SourceControlIsNotConnected
*/
public function deploy(): Deployment
{
if ($this->sourceControl()) {
$this->sourceControl()->getRepo($this->repository);
}
$deployment = new Deployment([
'site_id' => $this->id,
'deployment_script_id' => $this->deploymentScript->id,
'status' => DeploymentStatus::DEPLOYING,
]);
$lastCommit = $this->sourceControl()->provider()->getLastCommit($this->repository, $this->branch);
if ($lastCommit) {
$deployment->commit_id = $lastCommit['commit_id'];
$deployment->commit_data = $lastCommit['commit_data'];
}
$deployment->save();
dispatch(
new Deploy(
$deployment,
$this->path,
$this->deployment_script_text
)
)->onConnection('ssh');
return $deployment;
}
public function getEnvAttribute(): string
{
$typeData = $this->type_data;
if (! isset($typeData['env'])) {
$typeData['env'] = '';
$this->type_data = $typeData;
$this->save();
}
return $typeData['env'];
}
public function deployEnv(): void
{
dispatch(new DeployEnv($this))->onConnection('ssh');
}
public function activeSsl(): HasOne
{
return $this->hasOne(Ssl::class)
->where('expires_at', '>=', now())
->orderByDesc('id');
}
public function createFreeSsl(): void
{
$ssl = new Ssl([
'site_id' => $this->id,
'type' => 'letsencrypt',
'expires_at' => now()->addMonths(3),
'status' => SslStatus::CREATING,
]);
$ssl->save();
$ssl->deploy();
}
public function createCustomSsl(string $certificate, string $pk): void
{
$ssl = new Ssl([
'site_id' => $this->id,
'type' => 'custom',
'certificate' => $certificate,
'pk' => $pk,
'expires_at' => '',
'status' => SslStatus::CREATING,
]);
$ssl->save();
$ssl->deploy();
}
public function getUrlAttribute(): string
{
if ($this->activeSsl) {
return 'https://'.$this->domain;
}
return 'http://'.$this->domain;
}
public function getWebDirectoryPathAttribute(): string
{
if ($this->web_directory) {
return $this->path.'/'.$this->web_directory;
}
return $this->path;
}
/**
* @throws SourceControlIsNotConnected
* @throws ValidationException
* @throws FailedToDeployGitHook
* @throws Throwable
*/
public function enableAutoDeployment(): void
{
if ($this->gitHook) {
throw ValidationException::withMessages([
'auto_deployment' => __('Auto deployment already enabled'),
])->errorBag('auto_deployment');
}
if (! $this->sourceControl()) {
throw ValidationException::withMessages([
'auto_deployment' => __('Your application does not use any source controls'),
])->errorBag('auto_deployment');
}
try {
DB::beginTransaction();
$gitHook = new GitHook([
'site_id' => $this->id,
'source_control_id' => $this->sourceControl()->id,
'secret' => generate_uid(),
'actions' => ['deploy'],
'events' => ['push'],
]);
$gitHook->save();
$gitHook->deployHook();
DB::commit();
} catch (Exception $e) {
DB::rollBack();
throw $e;
}
}
/**
* @throws Throwable
*/
public function disableAutoDeployment(): void
{
$this->gitHook?->destroyHook();
}
public function getAutoDeploymentAttribute(): bool
{
return (bool) $this->gitHook;
}
public function updateBranch(string $branch): void
{
dispatch(new UpdateBranch($this, $branch))->onConnection('ssh');
}
}

36
app/Models/SourceControl.php Executable file
View File

@ -0,0 +1,36 @@
<?php
namespace App\Models;
use App\Contracts\SourceControlProvider;
use Illuminate\Database\Eloquent\Factories\HasFactory;
/**
* @property string $provider
* @property string $access_token
*/
class SourceControl extends AbstractModel
{
use HasFactory;
protected $fillable = [
'provider',
'access_token',
];
protected $casts = [
'access_token' => 'encrypted',
];
public function provider(): SourceControlProvider
{
$providerClass = config('core.source_control_providers_class')[$this->provider];
return new $providerClass($this);
}
public function getRepo(string $repo = null): ?array
{
return $this->provider()->getRepo($repo);
}
}

66
app/Models/SshKey.php Normal file
View File

@ -0,0 +1,66 @@
<?php
namespace App\Models;
use App\Enums\SshKeyStatus;
use App\Jobs\SshKey\DeleteSshKeyFromServer;
use App\Jobs\SshKey\DeploySshKeyToServer;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
/**
* @property int $user_id
* @property string $name
* @property string $public_key
* @property User $user
* @property Server[] $servers
*/
class SshKey extends AbstractModel
{
use HasFactory;
protected $fillable = [
'user_id',
'name',
'public_key',
];
protected $casts = [
'user_id' => 'integer',
'public_key' => 'encrypted',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function servers(): BelongsToMany
{
return $this->belongsToMany(Server::class, 'server_ssh_keys')
->withPivot('status')
->withTimestamps();
}
public function existsOnServer(Server $server): bool
{
return (bool) $this->servers()->where('id', $server->id)->first();
}
public function deployTo(Server $server): void
{
$server->sshKeys()->attach($this, [
'status' => SshKeyStatus::ADDING,
]);
dispatch(new DeploySshKeyToServer($server, $this))->onConnection('ssh');
}
public function deleteFrom(Server $server): void
{
$this->servers()->updateExistingPivot($server->id, [
'status' => SshKeyStatus::DELETING,
]);
dispatch(new DeleteSshKeyFromServer($server, $this))->onConnection('ssh');
}
}

132
app/Models/Ssl.php Normal file
View File

@ -0,0 +1,132 @@
<?php
namespace App\Models;
use App\Enums\SslStatus;
use App\Jobs\Ssl\Deploy;
use App\Jobs\Ssl\Remove;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Str;
/**
* @property int $site_id
* @property string $type
* @property string $certificate
* @property string $pk
* @property string $ca
* @property Carbon $expires_at
* @property string $status
* @property Site $site
* @property string $certs_directory_path
* @property string $certificate_path
* @property string $pk_path
* @property string $ca_path
*/
class Ssl extends AbstractModel
{
use HasFactory;
protected $fillable = [
'site_id',
'type',
'certificate',
'pk',
'ca',
'expires_at',
'status',
];
protected $casts = [
'site_id' => 'integer',
'certificate' => 'encrypted',
'pk' => 'encrypted',
'ca' => 'encrypted',
'expires_at' => 'datetime',
];
public function site(): BelongsTo
{
return $this->belongsTo(Site::class);
}
public function getCertsDirectoryPathAttribute(): ?string
{
if ($this->type == 'letsencrypt') {
return '/etc/letsencrypt/live/'.$this->site->domain;
}
if ($this->type == 'custom') {
return '/etc/ssl/'.$this->site->domain;
}
return '';
}
public function getCertificatePathAttribute(): ?string
{
if ($this->type == 'letsencrypt') {
return $this->certificate;
}
if ($this->type == 'custom') {
return $this->certs_directory_path.'/cert.pem';
}
return '';
}
public function getPkPathAttribute(): ?string
{
if ($this->type == 'letsencrypt') {
return $this->pk;
}
if ($this->type == 'custom') {
return $this->certs_directory_path.'/privkey.pem';
}
return '';
}
public function getCaPathAttribute(): ?string
{
if ($this->type == 'letsencrypt') {
return $this->ca;
}
if ($this->type == 'custom') {
return $this->certs_directory_path.'/fullchain.pem';
}
return '';
}
public function deploy(): void
{
dispatch(new Deploy($this))->onConnection('ssh');
}
public function remove(): void
{
$this->status = SslStatus::DELETING;
$this->save();
dispatch(new Remove($this))->onConnection('ssh');
}
public function validateSetup(string $result): bool
{
if (! Str::contains($result, 'Successfully received certificate')) {
return false;
}
if ($this->type == 'letsencrypt') {
$this->certificate = $this->certs_directory_path.'/fullchain.pem';
$this->pk = $this->certs_directory_path.'/privkey.pem';
$this->save();
}
return true;
}
}

View File

@ -0,0 +1,50 @@
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
/**
* @property int $user_id
* @property string $provider
* @property string $label
* @property string $token
* @property string $refresh_token
* @property bool $connected
* @property Carbon $token_expires_at
* @property User $user
*/
class StorageProvider extends AbstractModel
{
use HasFactory;
protected $fillable = [
'user_id',
'provider',
'label',
'token',
'refresh_token',
'connected',
'token_expires_at',
];
protected $casts = [
'user_id' => 'integer',
'connected' => 'boolean',
'token_expires_at' => 'datetime',
];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function provider(): \App\Contracts\StorageProvider
{
$providerClass = config('core.storage_providers_class')[$this->provider];
return new $providerClass($this);
}
}

106
app/Models/User.php Executable file
View File

@ -0,0 +1,106 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection;
use Laravel\Sanctum\HasApiTokens;
/**
* @property int $id
* @property string $name
* @property string $email
* @property string $password
* @property string $profile_photo_path
* @property string $two_factor_recovery_codes
* @property string $two_factor_secret
* @property SshKey[] $sshKeys
* @property SourceControl[] $sourceControls
* @property ServerProvider[] $serverProviders
* @property Script[] $scripts
* @property StorageProvider[] $storageProviders
* @property StorageProvider[] $connectedStorageProviders
* @property Collection $tokens
* @property string $profile_photo_url
* @property string $timezone
*/
class User extends Authenticatable
{
use HasApiTokens;
use HasFactory;
use Notifiable;
protected $fillable = [
'name',
'email',
'password',
'timezone',
];
protected $hidden = [
'password',
'remember_token',
'two_factor_recovery_codes',
'two_factor_secret',
];
protected $appends = [
];
public function sshKeys(): HasMany
{
return $this->hasMany(SshKey::class);
}
public function sourceControls(): HasMany
{
return $this->hasMany(SourceControl::class);
}
public function serverProviders(): HasMany
{
return $this->hasMany(ServerProvider::class);
}
public function scripts(): HasMany
{
return $this->hasMany(Script::class, 'user_id');
}
public function sourceControl(string $provider): HasOne
{
return $this->hasOne(SourceControl::class)->where('provider', $provider);
}
public function storageProviders(): HasMany
{
return $this->hasMany(StorageProvider::class);
}
public function storageProvider(string $provider): HasOne
{
return $this->hasOne(StorageProvider::class)->where('provider', $provider);
}
public function connectedStorageProviders(): HasMany
{
return $this->storageProviders()->where('connected', true);
}
public function connectedSourceControls(): array
{
$connectedSourceControls = [];
$sourceControls = $this->sourceControls()
->where('connected', 1)
->get(['provider']);
foreach ($sourceControls as $sourceControl) {
$connectedSourceControls[] = $sourceControl->provider;
}
return $connectedSourceControls;
}
}