Skip to content

Commit

Permalink
Adding basic tagging system
Browse files Browse the repository at this point in the history
  • Loading branch information
lewislarsen authored Jun 18, 2024
2 parents b12ab37 + 4c7a281 commit bdb87cf
Show file tree
Hide file tree
Showing 42 changed files with 1,136 additions and 6 deletions.
1 change: 1 addition & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ e7bc3b942e1391b53c97663eea56d8eb57119290
9ea7e0a73de9cc356dbdcc01977492106548a9ae
80ae05cec1520a13efa53b8d8b007f42de61b5c8
52445316a5fdb2dfe46a9a147eba5cbd63a914a4
8260edeec8da22d26ed1f9a7ef120ed468e50184
18 changes: 18 additions & 0 deletions app/Http/Controllers/Tags/EditController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace App\Http\Controllers\Tags;

use App\Http\Controllers\Controller;
use App\Models\Tag;
use Illuminate\Http\Request;
use Illuminate\View\View;

class EditController extends Controller
{
public function __invoke(Request $request, Tag $tag): View
{
return view('tags.edit', [
'tag' => $tag,
]);
}
}
12 changes: 11 additions & 1 deletion app/Livewire/BackupTasks/CreateBackupTaskForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use Livewire\Component;
use Livewire\Features\SupportRedirects\Redirector;
Expand Down Expand Up @@ -58,6 +59,9 @@ class CreateBackupTaskForm extends Component

public ?string $excludedDatabaseTables = null;

public ?Collection $availableTags;
public ?array $selectedTags;

public function updatedUseCustomCron(): void
{
$this->useCustomCron = (bool) $this->useCustomCron;
Expand All @@ -81,6 +85,7 @@ public function updatedBackupType(): void

public function mount(): void
{
$this->availableTags = Auth::user()->tags;
$this->userTimezone = Auth::user()->timezone ?? 'UTC';

$this->remoteServers = Auth::user()->remoteServers->where('database_password', null);
Expand All @@ -107,6 +112,7 @@ public function mount(): void
public function submit(): RedirectResponse|Redirector
{
$messages = [
'selectedTags.*.exists' => __('One or more of the selected tags do not exist.'),
'storePath.regex' => __('The path must be a valid Unix path.'),
'notifyEmail.email' => __('Please enter a valid email address.'),
'notifySlackWebhook.url' => __('Please enter a valid URL.'),
Expand Down Expand Up @@ -137,6 +143,7 @@ public function submit(): RedirectResponse|Redirector

if ($this->backupType === 'files') {
$this->validate([
'selectedTags' => ['nullable', 'array', Rule::exists('tags', 'id')->where('user_id', Auth::id())],
'excludedDatabaseTables' => ['nullable', 'string', 'regex:/^([a-zA-Z0-9_]+(,[a-zA-Z0-9_]+)*)$/'],
'storePath' => ['nullable', 'string', 'regex:/^(\/[^\/\0]+)+\/?$/'], // Unix path regex
'notifyEmail' => ['nullable', 'email'],
Expand All @@ -158,6 +165,7 @@ public function submit(): RedirectResponse|Redirector
}

$this->validate([
'selectedTags' => ['nullable', 'array', Rule::exists('tags', 'id')->where('user_id', Auth::id())],
'excludedDatabaseTables' => ['nullable', 'string', 'regex:/^([a-zA-Z0-9_]+(,[a-zA-Z0-9_]+)*)$/'],
'storePath' => ['nullable', 'string', 'regex:/^(\/[^\/\0]+)+\/?$/'], // Unix path regex
'notifyEmail' => ['nullable', 'email'],
Expand Down Expand Up @@ -189,7 +197,7 @@ public function submit(): RedirectResponse|Redirector
$this->timeToRun = Carbon::createFromFormat('H:i', $this->timeToRun, $this->userTimezone)?->setTimezone('UTC')->format('H:i');
}

BackupTask::create([
$backupTask = BackupTask::create([
'user_id' => Auth::id(),
'remote_server_id' => $this->remoteServerId,
'backup_destination_id' => $this->backupDestinationId,
Expand All @@ -211,6 +219,8 @@ public function submit(): RedirectResponse|Redirector
'excluded_database_tables' => $this->excludedDatabaseTables,
]);

$backupTask->tags()->sync($this->selectedTags);

Toaster::success(__('Backup task has been added.'));

return Redirect::route('backup-tasks.index');
Expand Down
12 changes: 12 additions & 0 deletions app/Livewire/BackupTasks/UpdateBackupTaskForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Validation\Rule;
use Illuminate\View\View;
use Livewire\Component;
use Livewire\Features\SupportRedirects\Redirector;
Expand Down Expand Up @@ -60,6 +61,9 @@ class UpdateBackupTaskForm extends Component

public ?string $excludedDatabaseTables;

public ?Collection $availableTags;
public ?array $selectedTags;

public function updatedUseCustomCron(): void
{
$this->useCustomCron = (bool) $this->useCustomCron;
Expand All @@ -83,6 +87,9 @@ public function updatedBackupType(): void

public function mount(): void
{
$this->availableTags = Auth::user()->tags;
$this->selectedTags = $this->backupTask->tags->pluck('id')->toArray();

$this->backupTimes = collect(range(0, 47))->map(function ($halfHour) {
$hour = intdiv($halfHour, 2);
$minute = ($halfHour % 2) * 30;
Expand Down Expand Up @@ -128,6 +135,7 @@ public function submit(): RedirectResponse|Redirector
$this->authorize('update', $this->backupTask);

$messages = [
'selectedTags.*.exists' => __('One or more of the selected tags do not exist.'),
'excludedDatabaseTables.regex' => __('Please enter a valid list of table names separated by commas.'),
'storePath.regex' => __('The path must be a valid Unix path.'),
'notifyEmail.email' => __('Please enter a valid email address.'),
Expand Down Expand Up @@ -159,6 +167,7 @@ public function submit(): RedirectResponse|Redirector

if ($this->backupType === 'files') {
$this->validate([
'selectedTags' => ['nullable', 'array', Rule::exists('tags', 'id')->where('user_id', Auth::id())],
'excludedDatabaseTables' => ['nullable', 'string', 'regex:/^([a-zA-Z0-9_]+(,[a-zA-Z0-9_]+)*)$/'],
'storePath' => ['nullable', 'string', 'regex:/^(\/[^\/\0]+)+\/?$/'], // Unix path regex
'notifyEmail' => ['nullable', 'email'],
Expand All @@ -180,6 +189,7 @@ public function submit(): RedirectResponse|Redirector
}

$this->validate([
'selectedTags' => ['nullable', 'array', Rule::exists('tags', 'id')->where('user_id', Auth::id())],
'excludedDatabaseTables' => ['nullable', 'string', 'regex:/^([a-zA-Z0-9_]+(,[a-zA-Z0-9_]+)*)$/'],
'storePath' => ['nullable', 'string', 'regex:/^(\/[^\/\0]+)+\/?$/'], // Unix path regex
'notifyEmail' => ['nullable', 'email'],
Expand Down Expand Up @@ -232,6 +242,8 @@ public function submit(): RedirectResponse|Redirector
'store_path' => $this->storePath ?? null,
]);

$this->backupTask->tags()->sync($this->selectedTags);

Toaster::success(__('Backup task details saved.'));

return Redirect::route('backup-tasks.index');
Expand Down
43 changes: 43 additions & 0 deletions app/Livewire/Tags/CreateForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace App\Livewire\Tags;

use App\Models\Tag;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
use Livewire\Component;
use Livewire\Features\SupportRedirects\Redirector;
use Masmerise\Toaster\Toaster;

class CreateForm extends Component
{
public string $label;
public ?string $description;

public function submit(): RedirectResponse|Redirector
{
$this->validate([
'label' => ['required', 'string'],
'description' => ['nullable', 'string'],
], [
'label.required' => __('Please enter a label.'),
]);

$tag = Tag::create([
'user_id' => Auth::id(),
'label' => $this->label,
'description' => $this->description ?? null,
]);

Toaster::success(__('The tag :label has been added.', ['label' => $tag->label]));

return Redirect::route('tags.index');
}

public function render(): View
{
return view('livewire.tags.create-form');
}
}
37 changes: 37 additions & 0 deletions app/Livewire/Tags/DeleteTagButton.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

namespace App\Livewire\Tags;

use App\Models\Tag;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
use Livewire\Component;
use Livewire\Features\SupportRedirects\Redirector;
use Masmerise\Toaster\Toaster;

class DeleteTagButton extends Component
{
public Tag $tag;

public function mount(Tag $tag): void
{
$this->tag = $tag;
}

public function delete(): RedirectResponse|Redirector
{
$this->authorize('forceDelete', $this->tag);

Toaster::success("The tag {$this->tag->label} has been removed.");

$this->tag->forceDelete();

return Redirect::route('tags.index');
}

public function render(): View
{
return view('livewire.tags.delete-tag-button');
}
}
22 changes: 22 additions & 0 deletions app/Livewire/Tags/IndexItem.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

namespace App\Livewire\Tags;

use App\Models\Tag;
use Illuminate\View\View;
use Livewire\Component;

class IndexItem extends Component
{
public Tag $tag;

public function mount(Tag $tag): void
{
$this->tag = $tag;
}

public function render(): View
{
return view('livewire.tags.index-item');
}
}
23 changes: 23 additions & 0 deletions app/Livewire/Tags/IndexTable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

namespace App\Livewire\Tags;

use App\Models\Tag;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
use Livewire\Component;
use Livewire\WithPagination;

class IndexTable extends Component
{
use withPagination;

public function render(): View
{
$tags = Tag::where('user_id', Auth::id())
->orderBy('created_at', 'desc')
->paginate(30, pageName: 'tags');

return view('livewire.tags.index-table', ['tags' => $tags]);
}
}
54 changes: 54 additions & 0 deletions app/Livewire/Tags/UpdateForm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace App\Livewire\Tags;

use App\Models\Tag;
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Redirect;
use Illuminate\View\View;
use Livewire\Component;
use Livewire\Features\SupportRedirects\Redirector;
use Masmerise\Toaster\Toaster;

class UpdateForm extends Component
{
public string $label;
public ?string $description;

public Tag $tag;

public function mount(Tag $tag): void
{
$this->tag = $tag;
$this->label = $tag->label;
$this->description = $tag->description ?? null;
}

public function submit(): RedirectResponse|Redirector
{
$this->authorize('update', $this->tag);

$this->validate([
'label' => ['required', 'string'],
'description' => ['nullable', 'string'],
], [
'label.required' => __('Please enter a label.'),
]);

$this->tag->update([
'label' => $this->label,
'description' => $this->description ?? null,
]);

$this->tag->save();

Toaster::success(__('The tag :label has been updated.', ['label' => $this->tag->label]));

return Redirect::route('tags.index');
}

public function render(): View
{
return view('livewire.tags.update-form');
}
}
12 changes: 11 additions & 1 deletion app/Models/BackupTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use App\Jobs\RunDatabaseBackupTaskJob;
use App\Jobs\RunFileBackupTaskJob;
use App\Mail\BackupTasks\OutputMail;
use App\Traits\HasTags;
use Cron\CronExpression;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
Expand All @@ -20,7 +21,7 @@

class BackupTask extends Model
{
use HasFactory;
use HasFactory, HasTags;

const string STATUS_READY = 'ready';

Expand Down Expand Up @@ -465,6 +466,15 @@ public function isAnotherTaskRunningOnSameRemoteServer(): bool
->exists();
}

public function listOfAttachedTagLabels(): ?string
{
if ($this->tags->isEmpty()) {
return null;
}

return $this->tags->pluck('label')->implode(', ');
}

private function cronExpression(): CronExpression
{
return new CronExpression($this->custom_cron_expression);
Expand Down
19 changes: 19 additions & 0 deletions app/Models/Tag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class Tag extends Model
{
use HasFactory;

protected $guarded = [];

public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
}
Loading

0 comments on commit bdb87cf

Please sign in to comment.