adding Projects feature (#85)

This commit is contained in:
Saeed Vaziry
2024-01-02 19:50:49 +01:00
committed by GitHub
parent fd2244d382
commit 10a6bb57a8
32 changed files with 847 additions and 84 deletions

View File

@ -0,0 +1,36 @@
<?php
namespace App\Actions\Projects;
use App\Models\Project;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
class CreateProject
{
public function create(User $user, array $input): Project
{
$this->validate($user, $input);
$project = new Project([
'user_id' => $user->id,
'name' => $input['name'],
]);
$project->save();
return $project;
}
private function validate(User $user, array $input): void
{
Validator::make($input, [
'name' => [
'required',
'string',
'max:255',
'unique:projects,name,NULL,id,user_id,'.$user->id,
],
])->validate();
}
}

View File

@ -0,0 +1,31 @@
<?php
namespace App\Actions\Projects;
use App\Models\Project;
use App\Models\User;
use Illuminate\Validation\ValidationException;
class DeleteProject
{
public function delete(User $user, int $projectId): void
{
/** @var Project $project */
$project = $user->projects()->findOrFail($projectId);
if ($user->projects()->count() === 1) {
throw ValidationException::withMessages([
'project' => __('Cannot delete the last project.'),
]);
}
if ($user->current_project_id == $project->id) {
/** @var Project $randomProject */
$randomProject = $user->projects()->where('id', '!=', $project->id)->first();
$user->current_project_id = $randomProject->id;
$user->save();
}
$project->delete();
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Actions\Projects;
use App\Models\Project;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
class UpdateProject
{
public function update(Project $project, array $input): Project
{
$this->validate($project, $input);
$project->name = $input['name'];
$project->save();
return $project;
}
private function validate(Project $project, array $input): void
{
Validator::make($input, [
'name' => [
'required',
'string',
'max:255',
Rule::unique('projects')->ignore($project->id),
],
])->validate();
}
}

View File

@ -26,6 +26,7 @@ public function create(User $creator, array $input): Server
$this->validateInputs($input);
$server = new Server([
'project_id' => $creator->currentProject->id,
'user_id' => $creator->id,
'name' => $input['name'],
'ssh_user' => config('core.server_providers_default_user')[$input['provider']][$input['os']],

View File

@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers;
use App\Models\Project;
use App\Models\User;
use Illuminate\Contracts\View\View;
class ProjectController extends Controller
{
public function index(): View
{
return view('projects.index');
}
public function switch($projectId)
{
/** @var User $user */
$user = auth()->user();
/** @var Project $project */
$project = $user->projects()->findOrFail($projectId);
$user->current_project_id = $project->id;
$user->save();
return redirect()->route('servers');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Http\Livewire\Projects;
use App\Traits\HasToast;
use App\Traits\RefreshComponentOnBroadcast;
use Illuminate\Contracts\View\View;
use Livewire\Component;
class CreateProject extends Component
{
use HasToast;
use RefreshComponentOnBroadcast;
public bool $open = false;
public array $inputs = [];
public function create(): void
{
app(\App\Actions\Projects\CreateProject::class)
->create(auth()->user(), $this->inputs);
$this->emitTo(ProjectsList::class, '$refresh');
$this->dispatchBrowserEvent('created', true);
}
public function render(): View
{
if (request()->query('create')) {
$this->open = true;
}
return view('livewire.projects.create-project');
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Http\Livewire\Projects;
use App\Actions\Projects\UpdateProject;
use App\Models\Project;
use App\Traits\RefreshComponentOnBroadcast;
use Illuminate\Contracts\View\View;
use Livewire\Component;
class EditProject extends Component
{
use RefreshComponentOnBroadcast;
public Project $project;
public array $inputs = [];
public function save(): void
{
app(UpdateProject::class)->update($this->project, $this->inputs);
$this->redirect(route('projects'));
}
public function mount(): void
{
$this->inputs = [
'name' => $this->project->name,
];
}
public function render(): View
{
return view('livewire.projects.edit-project');
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Http\Livewire\Projects;
use App\Actions\Projects\DeleteProject;
use App\Traits\HasToast;
use App\Traits\RefreshComponentOnBroadcast;
use Illuminate\Contracts\View\View;
use Illuminate\Validation\ValidationException;
use Livewire\Component;
class ProjectsList extends Component
{
use HasToast;
use RefreshComponentOnBroadcast;
protected $listeners = [
'$refresh',
];
public int $deleteId;
public function delete(): void
{
try {
app(DeleteProject::class)->delete(auth()->user(), $this->deleteId);
$this->redirect(route('projects'));
return;
} catch (ValidationException $e) {
$this->toast()->error($e->getMessage());
}
}
public function render(): View
{
return view('livewire.projects.projects-list', [
'projects' => auth()->user()->projects()->orderByDesc('id')->get(),
]);
}
}

View File

@ -2,7 +2,7 @@
namespace App\Http\Livewire\Servers;
use App\Models\Server;
use App\Models\User;
use App\Traits\RefreshComponentOnBroadcast;
use Illuminate\Contracts\View\View;
use Livewire\Component;
@ -13,8 +13,12 @@ class ServersList extends Component
public function render(): View
{
/** @var User $user */
$user = auth()->user();
$servers = $user->currentProject->servers()->orderByDesc('created_at')->get();
return view('livewire.servers.servers-list', [
'servers' => Server::all(),
'servers' => $servers,
]);
}
}

56
app/Models/Project.php Normal file
View File

@ -0,0 +1,56 @@
<?php
namespace App\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
/**
* @property int $id
* @property int $user_id
* @property string $name
* @property Carbon $created_at
* @property Carbon $updated_at
* @property User $user
* @property Collection<Server> $servers
* @property Collection<NotificationChannel> $notificationChannels
*/
class Project extends Model
{
use HasFactory;
protected $fillable = [
'user_id',
'name',
];
public static function boot(): void
{
parent::boot();
static::deleting(function (Project $project) {
$project->servers()->each(function (Server $server) {
$server->delete();
});
});
}
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function servers(): HasMany
{
return $this->hasMany(Server::class);
}
public function notificationChannels(): HasMany
{
return $this->hasMany(NotificationChannel::class);
}
}

View File

@ -18,6 +18,7 @@
use Illuminate\Support\Str;
/**
* @property int $project_id
* @property int $user_id
* @property string $name
* @property string $ssh_user
@ -38,6 +39,7 @@
* @property int $security_updates
* @property int $progress
* @property string $progress_step
* @property Project $project
* @property User $creator
* @property ServerProvider $serverProvider
* @property ServerLog[] $logs
@ -59,6 +61,7 @@ class Server extends AbstractModel
use HasFactory;
protected $fillable = [
'project_id',
'user_id',
'name',
'ssh_user',
@ -82,6 +85,7 @@ class Server extends AbstractModel
];
protected $casts = [
'project_id' => 'integer',
'user_id' => 'integer',
'type_data' => 'json',
'port' => 'integer',
@ -125,6 +129,11 @@ public static function boot(): void
});
}
public function project(): BelongsTo
{
return $this->belongsTo(Project::class, 'project_id');
}
public function creator(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');

View File

@ -28,6 +28,9 @@
* @property Collection $tokens
* @property string $profile_photo_url
* @property string $timezone
* @property int $current_project_id
* @property Project $currentProject
* @property Collection<Project> $projects
*/
class User extends Authenticatable
{
@ -41,6 +44,7 @@ class User extends Authenticatable
'email',
'password',
'timezone',
'current_project_id',
];
protected $hidden = [
@ -53,6 +57,20 @@ class User extends Authenticatable
protected $appends = [
];
public static function boot(): void
{
parent::boot();
static::created(function (User $user) {
$user->createDefaultProject();
});
}
public function servers(): HasMany
{
return $this->hasMany(Server::class);
}
public function sshKeys(): HasMany
{
return $this->hasMany(SshKey::class);
@ -105,4 +123,36 @@ public function connectedSourceControls(): array
return $connectedSourceControls;
}
public function projects(): HasMany
{
return $this->hasMany(Project::class);
}
public function currentProject(): HasOne
{
return $this->HasOne(Project::class, 'id', 'current_project_id');
}
public function isMemberOfProject(Project $project): bool
{
return $project->user_id === $this->id;
}
public function createDefaultProject(): Project
{
$project = $this->projects()->first();
if (! $project) {
$project = new Project();
$project->user_id = $this->id;
$project->name = 'Default';
$project->save();
}
$this->current_project_id = $project->id;
$this->save();
return $project;
}
}