Skip to content

Commit cfab1a2

Browse files
committed
chore: migrate create vault collaboration to Vue
1 parent 966c55f commit cfab1a2

18 files changed

Lines changed: 732 additions & 25 deletions

app/Actions/CreateCollaborationInvite.php

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Actions;
6+
7+
use App\Events\VaultCollaborationCreatedEvent;
8+
use App\Models\User;
9+
use App\Models\Vault;
10+
use App\Notifications\VaultCollaborationInvitationReceived;
11+
12+
final readonly class CreateVaultCollaboration
13+
{
14+
public function handle(Vault $vault, User $user): User
15+
{
16+
$vault->collaborators()->attach($user, ['accepted' => 0]);
17+
18+
$collaborator = $vault->collaborators()->wherePivot('user_id', $user->id)->firstOrFail();
19+
20+
$user->notify(new VaultCollaborationInvitationReceived($vault));
21+
22+
broadcast(new VaultCollaborationCreatedEvent($vault, $collaborator))->toOthers();
23+
24+
return $collaborator;
25+
}
26+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Events;
6+
7+
use App\Models\User;
8+
use App\Models\Vault;
9+
use App\ViewModels\VaultCollaboratorViewModel;
10+
use Illuminate\Broadcasting\Channel;
11+
use Illuminate\Broadcasting\InteractsWithSockets;
12+
use Illuminate\Broadcasting\PrivateChannel;
13+
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
14+
15+
final class VaultCollaborationCreatedEvent implements ShouldBroadcastNow
16+
{
17+
use InteractsWithSockets;
18+
19+
public function __construct(
20+
private Vault $vault,
21+
private User $user,
22+
) {
23+
//
24+
}
25+
26+
/** @return array<int, Channel> */
27+
public function broadcastOn(): array
28+
{
29+
return [
30+
new PrivateChannel('Vault.' . $this->vault->id),
31+
];
32+
}
33+
34+
/** @return array<string, array<string, mixed>> */
35+
public function broadcastWith(): array
36+
{
37+
return [
38+
'data' => VaultCollaboratorViewModel::fromModel($this->user)->toArray(),
39+
];
40+
}
41+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Controllers;
6+
7+
use App\Actions\CreateVaultCollaboration;
8+
use App\Http\Requests\StoreVaultCollaborationRequest;
9+
use App\Models\User;
10+
use App\Models\Vault;
11+
use App\ViewModels\VaultCollaboratorViewModel;
12+
use Illuminate\Container\Attributes\CurrentUser;
13+
use Illuminate\Database\Eloquent\ModelNotFoundException;
14+
use Illuminate\Http\JsonResponse;
15+
use Illuminate\Validation\ValidationException;
16+
17+
final readonly class VaultCollaborationController
18+
{
19+
public function store(
20+
StoreVaultCollaborationRequest $request,
21+
Vault $vault,
22+
#[CurrentUser] User $currentUser,
23+
CreateVaultCollaboration $createVaultCollaboration,
24+
): JsonResponse {
25+
abort_unless($currentUser->id === $vault->user->id, 403);
26+
27+
/** @var array{email: string} $validated */
28+
$validated = $request->validated();
29+
30+
/** @var User $invitedUser */
31+
$invitedUser = User::where('email', $validated['email'])->first();
32+
33+
if ($invitedUser->id === $currentUser->id) {
34+
throw ValidationException::withMessages([
35+
'email' => __('You are the owner of this vault'),
36+
]);
37+
}
38+
39+
try {
40+
$collaborator = $vault->collaborators()
41+
->wherePivot('user_id', $invitedUser->id)
42+
->firstOrFail();
43+
44+
$message = $collaborator->pivot->accepted
45+
? __('User is already a collaborator')
46+
: __('User is already invited');
47+
48+
throw ValidationException::withMessages([
49+
'email' => $message,
50+
]);
51+
} catch (ModelNotFoundException) {
52+
$collaborator = $createVaultCollaboration->handle($vault, $invitedUser);
53+
54+
return response()->json([
55+
'data' => VaultCollaboratorViewModel::fromModel($collaborator)->toArray(),
56+
]);
57+
}
58+
}
59+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Http\Requests;
6+
7+
use Illuminate\Foundation\Http\FormRequest;
8+
9+
final class StoreVaultCollaborationRequest extends FormRequest
10+
{
11+
/** @return array<string, array<int, string>> */
12+
public function rules(): array
13+
{
14+
return [
15+
'email' => [
16+
'required',
17+
'string',
18+
'max:255',
19+
'lowercase',
20+
'email',
21+
'exists:users,email',
22+
],
23+
];
24+
}
25+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\Notifications;
6+
7+
use App\Models\User;
8+
use App\Models\Vault;
9+
use App\ViewModels\VaultCollaborationInvitationReceivedViewModel;
10+
use Illuminate\Notifications\Messages\BroadcastMessage;
11+
use Illuminate\Notifications\Notification;
12+
13+
final class VaultCollaborationInvitationReceived extends Notification
14+
{
15+
public function __construct(
16+
private readonly Vault $vault,
17+
) {
18+
//
19+
}
20+
21+
/** @return array<int, string> */
22+
public function via(object $notifiable): array
23+
{
24+
return ['database', 'broadcast'];
25+
}
26+
27+
/** @return array<string, mixed> */
28+
public function toArray(object $notifiable): array
29+
{
30+
return [
31+
'vault_id' => $this->vault->id,
32+
];
33+
}
34+
35+
public function toBroadcast(User $notifiable): BroadcastMessage
36+
{
37+
$viewModel = new VaultCollaborationInvitationReceivedViewModel(
38+
$this->id,
39+
class_basename($this),
40+
$notifiable,
41+
$this->vault,
42+
);
43+
44+
return new BroadcastMessage([
45+
'data' => $viewModel->toArray(),
46+
]);
47+
}
48+
}

app/ViewModels/NotificationViewModelResolver.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace App\ViewModels;
66

7+
use App\Notifications\VaultCollaborationInvitationReceived;
78
use Illuminate\Notifications\DatabaseNotification;
89

910
final readonly class NotificationViewModelResolver
@@ -12,6 +13,9 @@
1213
public static function resolve(DatabaseNotification $notification): array
1314
{
1415
return match ($notification->type) {
16+
VaultCollaborationInvitationReceived::class => (
17+
VaultCollaborationInvitationReceivedViewModel::fromModel($notification)->toArray()
18+
),
1519
default => [],
1620
};
1721
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\ViewModels;
6+
7+
use App\Models\User;
8+
use App\Models\Vault;
9+
use Illuminate\Notifications\DatabaseNotification;
10+
11+
final readonly class VaultCollaborationInvitationReceivedViewModel
12+
{
13+
public function __construct(
14+
public string $id,
15+
public string $type,
16+
public User $user,
17+
public Vault $vault,
18+
) {
19+
//
20+
}
21+
22+
public static function fromModel(DatabaseNotification $notification): self
23+
{
24+
/** @var Vault $vault */
25+
$vault = Vault::find($notification->data['vault_id']);
26+
27+
return new self(
28+
$notification->id,
29+
class_basename($notification->type),
30+
$vault->user,
31+
$vault,
32+
);
33+
}
34+
35+
/** @return array<string, mixed> */
36+
public function toArray(): array
37+
{
38+
return [
39+
'id' => $this->id,
40+
'type' => $this->type,
41+
'data' => [
42+
'user_name' => $this->user->name,
43+
'vault_id' => $this->vault->id,
44+
'vault_name' => $this->vault->name,
45+
],
46+
];
47+
}
48+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace App\ViewModels;
6+
7+
use App\Models\User;
8+
9+
final readonly class VaultCollaboratorViewModel
10+
{
11+
public function __construct(
12+
public int $id,
13+
public string $name,
14+
public string $email,
15+
public bool $accepted,
16+
) {
17+
//
18+
}
19+
20+
public static function fromModel(User $user): self
21+
{
22+
return new self(
23+
$user->id,
24+
$user->name,
25+
$user->email,
26+
/** @phpstan-ignore-next-line */
27+
(bool) $user->pivot->accepted,
28+
);
29+
}
30+
31+
/** @return array<string, mixed> */
32+
public function toArray(): array
33+
{
34+
return [
35+
'id' => $this->id,
36+
'name' => $this->name,
37+
'email' => $this->email,
38+
'accepted' => $this->accepted,
39+
];
40+
}
41+
}

resources/js/components/menu/NotificationMenu.vue

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
<script setup lang="ts">
22
import Menu from '@/components/menu/Menu.vue';
3+
import VaultCollaborationInvitationReceived from '@/components/notification/VaultCollaborationInvitationReceived.vue';
34
import Bell from '@/icons/Bell.vue';
45
import { useNotificationStore } from '@/stores/notification';
56
import { type Component } from 'vue';
67
78
const notificationStore = useNotificationStore();
89
910
const notificationComponents: Record<string, Component> = {
11+
VaultCollaborationInvitationReceived: VaultCollaborationInvitationReceived,
1012
};
1113
</script>
1214

0 commit comments

Comments
 (0)