Skip to content

Commit

Permalink
Tags (#277)
Browse files Browse the repository at this point in the history
  • Loading branch information
saeedvaziry authored Aug 20, 2024
1 parent 431da1b commit 7f5e68e
Show file tree
Hide file tree
Showing 47 changed files with 1,380 additions and 99 deletions.
58 changes: 58 additions & 0 deletions app/Actions/Tag/AttachTag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
<?php

namespace App\Actions\Tag;

use App\Models\Server;
use App\Models\Site;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

class AttachTag
{
public function attach(User $user, array $input): Tag
{
$this->validate($input);

/** @var Server|Site $taggable */
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);

$tag = Tag::query()->where('name', $input['name'])->first();
if ($tag) {
if (! $taggable->tags->contains($tag->id)) {
$taggable->tags()->attach($tag->id);
}

return $tag;
}

$tag = new Tag([
'project_id' => $user->currentProject->id,
'name' => $input['name'],
'color' => config('core.tag_colors')[array_rand(config('core.tag_colors'))],
]);
$tag->save();

$taggable->tags()->attach($tag->id);

return $tag;
}

private function validate(array $input): void
{
Validator::make($input, [
'name' => [
'required',
],
'taggable_id' => [
'required',
'integer',
],
'taggable_type' => [
'required',
Rule::in(config('core.taggable_types')),
],
])->validate();
}
}
49 changes: 49 additions & 0 deletions app/Actions/Tag/CreateTag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace App\Actions\Tag;

use App\Models\Tag;
use App\Models\User;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class CreateTag
{
public function create(User $user, array $input): Tag
{
$this->validate($input);

$tag = Tag::query()
->where('project_id', $user->current_project_id)
->where('name', $input['name'])
->first();
if ($tag) {
throw ValidationException::withMessages([
'name' => ['Tag with this name already exists.'],
]);
}

$tag = new Tag([
'project_id' => $user->currentProject->id,
'name' => $input['name'],
'color' => $input['color'],
]);
$tag->save();

return $tag;
}

private function validate(array $input): void
{
Validator::make($input, [
'name' => [
'required',
],
'color' => [
'required',
Rule::in(config('core.tag_colors')),
],
])->validate();
}
}
15 changes: 15 additions & 0 deletions app/Actions/Tag/DeleteTag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

namespace App\Actions\Tag;

use App\Models\Tag;
use Illuminate\Support\Facades\DB;

class DeleteTag
{
public function delete(Tag $tag): void
{
DB::table('taggables')->where('tag_id', $tag->id)->delete();
$tag->delete();
}
}
36 changes: 36 additions & 0 deletions app/Actions/Tag/DetachTag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace App\Actions\Tag;

use App\Models\Server;
use App\Models\Site;
use App\Models\Tag;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;

class DetachTag
{
public function detach(Tag $tag, array $input): void
{
$this->validate($input);

/** @var Server|Site $taggable */
$taggable = $input['taggable_type']::findOrFail($input['taggable_id']);

$taggable->tags()->detach($tag->id);
}

private function validate(array $input): void
{
Validator::make($input, [
'taggable_id' => [
'required',
'integer',
],
'taggable_type' => [
'required',
Rule::in(config('core.taggable_types')),
],
])->validate();
}
}
38 changes: 38 additions & 0 deletions app/Actions/Tag/EditTag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

namespace App\Actions\Tag;

use App\Models\Tag;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;

class EditTag
{
public function edit(Tag $tag, array $input): void
{
$this->validate($input);

$tag->name = $input['name'];
$tag->color = $input['color'];

$tag->save();
}

/**
* @throws ValidationException
*/
private function validate(array $input): void
{
$rules = [
'name' => [
'required',
],
'color' => [
'required',
Rule::in(config('core.tag_colors')),
],
];
Validator::make($input, $rules)->validate();
}
}
90 changes: 90 additions & 0 deletions app/Http/Controllers/Settings/TagController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace App\Http\Controllers\Settings;

use App\Actions\Tag\AttachTag;
use App\Actions\Tag\CreateTag;
use App\Actions\Tag\DeleteTag;
use App\Actions\Tag\DetachTag;
use App\Actions\Tag\EditTag;
use App\Facades\Toast;
use App\Helpers\HtmxResponse;
use App\Http\Controllers\Controller;
use App\Models\Tag;
use App\Models\User;
use Illuminate\Contracts\View\View;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class TagController extends Controller
{
public function index(Request $request): View
{
$data = [
'tags' => Tag::getByProjectId(auth()->user()->current_project_id)->get(),
];

if ($request->has('edit')) {
$data['editTag'] = Tag::find($request->input('edit'));
}

return view('settings.tags.index', $data);
}

public function create(Request $request): HtmxResponse
{
/** @var User $user */
$user = $request->user();

app(CreateTag::class)->create(
$user,
$request->input(),
);

Toast::success('Tag created.');

return htmx()->redirect(route('settings.tags'));
}

public function update(Tag $tag, Request $request): HtmxResponse
{
app(EditTag::class)->edit(
$tag,
$request->input(),
);

Toast::success('Tag updated.');

return htmx()->redirect(route('settings.tags'));
}

public function attach(Request $request): RedirectResponse
{
/** @var User $user */
$user = $request->user();

app(AttachTag::class)->attach($user, $request->input());

return back()->with([
'status' => 'tag-created',
]);
}

public function detach(Request $request, Tag $tag): RedirectResponse
{
app(DetachTag::class)->detach($tag, $request->input());

return back()->with([
'status' => 'tag-detached',
]);
}

public function delete(Tag $tag): RedirectResponse
{
app(DeleteTag::class)->delete($tag);

Toast::success('Tag deleted.');

return back();
}
}
5 changes: 5 additions & 0 deletions app/Models/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,9 @@ public function sourceControls(): HasMany
{
return $this->hasMany(SourceControl::class);
}

public function tags(): HasMany
{
return $this->hasMany(Tag::class);
}
}
6 changes: 6 additions & 0 deletions app/Models/Server.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Filesystem\FilesystemAdapter;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
Expand Down Expand Up @@ -214,6 +215,11 @@ public function sshKeys(): BelongsToMany
->withTimestamps();
}

public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}

public function getSshUser(): string
{
if ($this->ssh_user) {
Expand Down
6 changes: 6 additions & 0 deletions app/Models/Site.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Database\Eloquent\Relations\MorphToMany;
use Illuminate\Support\Str;

/**
Expand Down Expand Up @@ -126,6 +127,11 @@ public function ssls(): HasMany
return $this->hasMany(Ssl::class);
}

public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, 'taggable');
}

/**
* @throws SourceControlIsNotConnected
*/
Expand Down
55 changes: 55 additions & 0 deletions app/Models/Tag.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

namespace App\Models;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

/**
* @property int $id
* @property int $project_id
* @property string $name
* @property string $color
* @property Carbon $created_at
* @property Carbon $updated_at
*/
class Tag extends Model
{
use HasFactory;

protected $fillable = [
'project_id',
'name',
'color',
];

protected $casts = [
'project_id' => 'int',
];

public function project(): BelongsTo
{
return $this->belongsTo(Project::class);
}

public function servers(): MorphToMany
{
return $this->morphedByMany(Server::class, 'taggable');
}

public function sites(): MorphToMany
{
return $this->morphedByMany(Site::class, 'taggable');
}

public static function getByProjectId(int $projectId): Builder
{
return self::query()
->where('project_id', $projectId)
->orWhereNull('project_id');
}
}
Loading

0 comments on commit 7f5e68e

Please sign in to comment.