Move observer logic to actions

This commit is contained in:
brufdev
2025-02-02 18:06:49 +00:00
parent a1f6237691
commit 059e1cd54a
12 changed files with 347 additions and 165 deletions

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace App\Actions;
use App\Models\User;
use App\Models\Vault;
use Illuminate\Support\Facades\Storage;
final readonly class CreateVault
{
/**
* @param array{name: string} $attributes
*/
public function handle(User $user, array $attributes): Vault
{
// Generate a new vault name if the current one already exists
$vaultExists = $user->vaults()
->where('name', 'like', $attributes['name'])
->exists();
if ($vaultExists) {
/** @var list<string> $vaults */
$vaults = array_column(
$user->vaults()
->select('name')
->where('name', 'like', $attributes['name'] . '-%')
->get()
->toArray(),
'name',
);
natcasesort($vaults);
$attributes['name'] .= count($vaults) && preg_match('/-(\d+)$/', end($vaults), $matches) === 1 ?
'-' . ((int) $matches[1] + 1) :
'-1';
}
// Save vault to database
$vault = $user->vaults()->create($attributes);
// Save vault to disk
$vaultPath = new GetPathFromVault()->handle($vault);
Storage::disk('local')->makeDirectory($vaultPath);
return $vault;
}
}

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace App\Actions;
use App\Models\Vault;
use App\Models\VaultNode;
use Illuminate\Support\Facades\Storage;
final readonly class CreateVaultNode
{
/**
* @param array{
* parent_id?: int|null,
* is_file: bool,
* name: string,
* extension?: string|null,
* content?: string|null
* } $attributes
*/
public function handle(Vault $vault, array $attributes): VaultNode
{
$attributes['parent_id'] ??= null;
$attributes['extension'] ??= null;
$attributes['content'] ??= null;
// Generate a new filename if the current one already exists
$nodeExists = $vault->nodes()
->where('parent_id', $attributes['parent_id'])
->where('is_file', $attributes['is_file'])
->where('name', 'like', $attributes['name'])
->where('extension', $attributes['extension'])
->exists();
if ($nodeExists) {
/** @var list<string> $nodes */
$nodes = array_column(
$vault->nodes()
->select('name')
->where('parent_id', $attributes['parent_id'])
->where('is_file', $attributes['is_file'])
->where('name', 'like', $attributes['name'] . '-%')
->where('extension', $attributes['extension'])
->get()
->toArray(),
'name',
);
natcasesort($nodes);
$attributes['name'] .= count($nodes) && preg_match('/-(\d+)$/', end($nodes), $matches) === 1
? '-' . ((int) $matches[1] + 1)
: '-1';
}
// Save node to database
$databaseContent = $attributes['extension'] === 'md' ? $attributes['content'] : null;
$node = $vault->nodes()->create([
'parent_id' => $attributes['parent_id'],
'is_file' => $attributes['is_file'],
'name' => $attributes['name'],
'extension' => $attributes['extension'],
'content' => $databaseContent,
]);
// Save node to disk
$nodePath = new GetPathFromVaultNode()->handle($node);
if ($node->is_file) {
Storage::disk('local')->put($nodePath, $attributes['content'] ?? '');
} else {
Storage::disk('local')->makeDirectory($nodePath);
}
return $node;
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace App\Actions;
use App\Models\User;
use App\Models\Vault;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Throwable;
final readonly class DeleteVault
{
public function handle(Vault $vault): void
{
try {
DB::beginTransaction();
$this->deleteFromDatabase($vault);
DB::commit();
} catch (Throwable) {
DB::rollBack();
throw new Exception(__('Something went wrong'));
}
$this->deleteFromDisk($vault);
}
/**
* Deletes vault from the database.
*/
private function deleteFromDatabase(Vault $vault): void
{
$deleteVaultNode = new DeleteVaultNode();
$rootNodes = $vault->nodes()->whereNull('parent_id')->get();
foreach ($rootNodes as $node) {
$deleteVaultNode->handle($node, false);
}
$vault->delete();
}
/**
* Deletes vault from the disk.
*/
private function deleteFromDisk(Vault $vault): void
{
/** @var User $user */
$user = $vault->user()->first();
$vaultPath = new GetPathFromUser()->handle($user) . $vault->name;
if (!Storage::disk('local')->exists($vaultPath)) {
return;
}
Storage::disk('local')->deleteDirectory($vaultPath);
}
}

View File

@@ -0,0 +1,78 @@
<?php
declare(strict_types=1);
namespace App\Actions;
use App\Models\VaultNode;
use Exception;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Throwable;
final readonly class DeleteVaultNode
{
/**
* Handles the action.
*
* @return array<int, VaultNode>
*/
public function handle(VaultNode $node, bool $deleteFromDisk = true): array
{
try {
DB::beginTransaction();
$deletedNodes = $this->deleteFromDatabase($node);
DB::commit();
} catch (Throwable) {
DB::rollBack();
throw new Exception(__('Something went wrong'));
}
if ($deleteFromDisk) {
$this->deleteFromDisk($node);
}
return $deletedNodes;
}
/**
* Deletes node from the database.
*
* @return array<int, VaultNode>
*/
private function deleteFromDatabase(VaultNode $node): array
{
$deletedNodes = [$node];
if (!$node->is_file) {
foreach ($node->children()->get() as $child) {
$deletedNodes = array_merge(
$deletedNodes,
$this->deleteFromDatabase($child),
);
}
}
$node->delete();
return $deletedNodes;
}
/**
* Deletes node from the disk.
*/
private function deleteFromDisk(VaultNode $node): void
{
$nodePath = new GetPathFromVaultNode()->handle($node);
if (!Storage::disk('local')->exists($nodePath)) {
return;
}
if ($node->is_file) {
Storage::disk('local')->delete($nodePath);
} else {
Storage::disk('local')->deleteDirectory($nodePath);
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace App\Actions;
use App\Models\User;
use App\Models\Vault;
use Illuminate\Support\Facades\Storage;
final readonly class UpdateVault
{
/**
* @param array{name?: string, templates_node_id?: int|null} $attributes
*/
public function handle(Vault $vault, array $attributes): void
{
/** @var array{name: string} $original */
$original = $vault->toArray();
$vault->update($attributes);
if (!$vault->wasChanged('name')) {
return;
}
/** @var User $user */
$user = $vault->user()->first();
$relativePath = new GetPathFromUser()->handle($user);
Storage::disk('local')->move(
$relativePath . $original['name'],
$relativePath . $vault->name,
);
}
}

View File

@@ -0,0 +1,36 @@
<?php
declare(strict_types=1);
namespace App\Actions;
use App\Models\VaultNode;
use Illuminate\Support\Facades\Storage;
final readonly class UpdateVaultNode
{
/**
* @param array{
* parent_id?: int|null,
* is_file: bool,
* name: string,
* extension?: string|null,
* content?: string|null
* } $attributes
*/
public function handle(VaultNode $node, array $attributes): void
{
$relativeOriginalPath = new GetPathFromVaultNode()->handle($node);
$node->update($attributes);
if (!$node->wasChanged('name')) {
return;
}
$relativePath = new GetPathFromVaultNode()->handle($node);
Storage::disk('local')->move(
$relativeOriginalPath,
$relativePath,
);
}
}

View File

@@ -4,9 +4,10 @@ declare(strict_types=1);
namespace App\Livewire\Forms;
use App\Actions\CreateVault;
use App\Actions\UpdateVault;
use App\Models\User;
use App\Models\Vault;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Unique;
use Livewire\Attributes\Validate;
@@ -47,11 +48,11 @@ final class VaultForm extends Form
public function create(): void
{
$this->name = mb_trim($this->name);
$this->validate();
/** @var User $currentUser */
$currentUser = auth()->user();
$this->name = Str::trim($this->name);
$currentUser->vaults()->create([
new CreateVault()->handle($currentUser, [
'name' => $this->name,
]);
$this->reset(['name']);
@@ -59,14 +60,14 @@ final class VaultForm extends Form
public function update(): void
{
$this->validate();
if (is_null($this->vault)) {
return;
}
$this->name = Str::trim($this->name);
$this->vault->update([
$this->name = mb_trim($this->name);
$this->validate();
new UpdateVault()->handle($this->vault, [
'name' => $this->name,
]);
}

View File

@@ -4,9 +4,10 @@ declare(strict_types=1);
namespace App\Livewire\Forms;
use App\Actions\CreateVaultNode;
use App\Actions\UpdateVaultNode;
use App\Models\Vault;
use App\Models\VaultNode;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Unique;
use Livewire\Attributes\Validate;
@@ -65,9 +66,9 @@ final class VaultNodeForm extends Form
public function create(): VaultNode
{
$this->name = mb_trim($this->name);
$this->validate();
$this->name = Str::trim($this->name);
$node = $this->vault->nodes()->create([
$node = new CreateVaultNode()->handle($this->vault, [
'parent_id' => $this->parent_id,
'is_file' => $this->is_file,
'name' => $this->name,
@@ -81,16 +82,18 @@ final class VaultNodeForm extends Form
public function update(): void
{
$this->validate();
if (is_null($this->node)) {
return;
}
$this->name = Str::trim($this->name);
$this->node->update([
$this->name = mb_trim($this->name);
$this->validate();
new UpdateVaultNode()->handle($this->node, [
'parent_id' => $this->parent_id,
'is_file' => (bool) $this->node->is_file,
'name' => $this->name,
'extension' => $this->node->extension,
'content' => $this->content,
]);
}

View File

@@ -4,15 +4,12 @@ declare(strict_types=1);
namespace App\Models;
use App\Observers\VaultObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
#[ObservedBy([VaultObserver::class])]
final class Vault extends Model
{
/** @use HasFactory<\Database\Factories\VaultFactory> */

View File

@@ -4,15 +4,12 @@ declare(strict_types=1);
namespace App\Models;
use App\Observers\VaultNodeObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Staudenmeir\LaravelAdjacencyList\Eloquent\HasRecursiveRelationships;
#[ObservedBy([VaultNodeObserver::class])]
final class VaultNode extends Model
{
/** @use HasFactory<\Database\Factories\VaultNodeFactory> */

View File

@@ -1,77 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Observers;
use App\Actions\GetPathFromVaultNode;
use App\Models\VaultNode;
use Illuminate\Support\Facades\Storage;
final readonly class VaultNodeObserver
{
/**
* Handle the VaultNode "creating" event.
*/
public function creating(VaultNode $node): void
{
$relativePath = new GetPathFromVaultNode()->handle($node);
if (Storage::disk('local')->exists($relativePath)) {
abort(500);
}
if ($node->is_file) {
Storage::disk('local')->put($relativePath, '');
} else {
Storage::disk('local')->makeDirectory($relativePath);
}
}
/**
* Handle the VaultNode "updating" event.
*/
public function updating(VaultNode $node): void
{
$relativePath = new GetPathFromVaultNode()->handle($node, false);
if (Storage::disk('local')->exists($relativePath . $node->name)) {
abort(500);
}
if ($node->isDirty('name')) {
/** @var string $originalName */
$originalName = $node->getOriginal('name');
$paths = [
$relativePath . $originalName,
$relativePath . $node->name,
];
if ($node->is_file) {
$paths[0] .= '.' . $node->extension;
$paths[1] .= '.' . $node->extension;
}
Storage::disk('local')->move(...$paths);
}
if ($node->is_file) {
Storage::disk('local')->put(
$relativePath . $node->name . '.' . $node->extension,
$node->content ?? '',
);
}
}
/**
* Handle the VaultNode "deleting" event.
*/
public function deleting(VaultNode $node): void
{
$relativePath = new GetPathFromVaultNode()->handle($node);
if ($node->is_file) {
Storage::disk('local')->delete($relativePath);
} else {
Storage::disk('local')->deleteDirectory($relativePath);
}
}
}

View File

@@ -1,68 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Observers;
use App\Actions\GetPathFromUser;
use App\Models\User;
use App\Models\Vault;
use Illuminate\Support\Facades\Storage;
final readonly class VaultObserver
{
/**
* Handle the Vault "creating" event.
*/
public function creating(Vault $vault): void
{
/** @var User $user */
$user = $vault->user;
$relativePath = new GetPathFromUser()->handle($user);
if (Storage::disk('local')->exists($relativePath . $vault->name)) {
abort(500);
}
Storage::disk('local')->makeDirectory($relativePath . $vault->name);
}
/**
* Handle the Vault "updating" event.
*/
public function updating(Vault $vault): void
{
/** @var User $user */
$user = $vault->user;
if (!$vault->isDirty('name')) {
return;
}
$relativePath = new GetPathFromUser()->handle($user);
if (Storage::disk('local')->exists($relativePath . $vault->name)) {
abort(500);
}
/** @var string $originalName */
$originalName = $vault->getOriginal('name');
Storage::disk('local')->move(
$relativePath . $originalName,
$relativePath . $vault->name,
);
}
/**
* Handle the Vault "deleting" event.
*/
public function deleting(Vault $vault): void
{
/** @var User $user */
$user = $vault->user;
$relativePath = new GetPathFromUser()->handle($user);
Storage::disk('local')->deleteDirectory($relativePath . $vault->name);
}
}