Add queue events for memberships
This commit is contained in:
parent
e60bc94b80
commit
20833426ca
|
@ -2,28 +2,27 @@
|
|||
|
||||
namespace App\Lib\Events;
|
||||
|
||||
use App\Lib\JobMiddleware\JobChannels;
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Ramsey\Uuid\Lazy\LazyUuidFromString;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
class JobEvent implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
public bool $reload = false;
|
||||
public string $message = '';
|
||||
|
||||
final private function __construct(public string $channel, public UuidInterface $jobId)
|
||||
final private function __construct(public UuidInterface $jobId)
|
||||
{
|
||||
}
|
||||
|
||||
public static function on(string $channel, UuidInterface $jobId): static
|
||||
public static function on(UuidInterface $jobId): static
|
||||
{
|
||||
return new static($channel, $jobId);
|
||||
return new static($jobId);
|
||||
}
|
||||
|
||||
public function withMessage(string $message): static
|
||||
|
@ -46,15 +45,7 @@ class JobEvent implements ShouldBroadcastNow
|
|||
public function broadcastOn()
|
||||
{
|
||||
return [
|
||||
new Channel($this->channel),
|
||||
new Channel('jobs'),
|
||||
];
|
||||
}
|
||||
|
||||
public function shouldReload(): static
|
||||
{
|
||||
$this->reload = true;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\Events;
|
||||
|
||||
use App\Lib\JobMiddleware\JobChannels;
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ReloadTriggered implements ShouldBroadcastNow
|
||||
{
|
||||
use Dispatchable, InteractsWithSockets, SerializesModels;
|
||||
|
||||
final private function __construct(public JobChannels $channels)
|
||||
{
|
||||
}
|
||||
|
||||
public static function on(JobChannels $channels): self
|
||||
{
|
||||
return new static($channels);
|
||||
}
|
||||
|
||||
public function dispatch(): void
|
||||
{
|
||||
event($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return array<int, Channel>
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
return $this->channels->toBroadcast();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<?php
|
||||
|
||||
namespace App\Lib\JobMiddleware;
|
||||
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
|
||||
/**
|
||||
* @implements Arrayable<int, string>
|
||||
*/
|
||||
class JobChannels implements Arrayable
|
||||
{
|
||||
|
||||
public static function make(): self
|
||||
{
|
||||
return new self();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $channels
|
||||
*/
|
||||
public function __construct(
|
||||
public array $channels = []
|
||||
) {
|
||||
}
|
||||
|
||||
public function add(string $channelName): self
|
||||
{
|
||||
$this->channels[] = $channelName;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->channels;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, Channel>
|
||||
*/
|
||||
public function toBroadcast(): array
|
||||
{
|
||||
return array_map(fn ($channel) => new Channel($channel), $this->channels);
|
||||
}
|
||||
}
|
|
@ -5,9 +5,9 @@ namespace App\Lib\JobMiddleware;
|
|||
use App\Lib\Events\JobFailed;
|
||||
use App\Lib\Events\JobFinished;
|
||||
use App\Lib\Events\JobStarted;
|
||||
use App\Lib\Events\ReloadTriggered;
|
||||
use Closure;
|
||||
use Lorisleiva\Actions\Decorators\JobDecorator;
|
||||
use Ramsey\Uuid\Lazy\LazyUuidFromString;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
use Throwable;
|
||||
|
||||
|
@ -17,51 +17,47 @@ class WithJobState
|
|||
public ?JobStarted $beforeMessage = null;
|
||||
public ?JobFinished $afterMessage = null;
|
||||
public ?JobFailed $failedMessage = null;
|
||||
public ?ReloadTriggered $reloadAfter = null;
|
||||
|
||||
private function __construct(public string $channel, public UuidInterface $jobId)
|
||||
private function __construct(public UuidInterface $jobId)
|
||||
{
|
||||
}
|
||||
|
||||
public static function make(string $channel, UuidInterface $jobId): self
|
||||
public static function make(UuidInterface $jobId): self
|
||||
{
|
||||
return new self($channel, $jobId);
|
||||
return new self($jobId);
|
||||
}
|
||||
|
||||
public function before(string $message): self
|
||||
{
|
||||
$this->beforeMessage = JobStarted::on($this->channel, $this->jobId)->withMessage($message);
|
||||
$this->beforeMessage = JobStarted::on($this->jobId)->withMessage($message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function after(string $message): self
|
||||
{
|
||||
$this->afterMessage = JobFinished::on($this->channel, $this->jobId)->withMessage($message);
|
||||
$this->afterMessage = JobFinished::on($this->jobId)->withMessage($message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function failed(string $message): self
|
||||
{
|
||||
$this->failedMessage = JobFailed::on($this->channel, $this->jobId)->withMessage($message);
|
||||
$this->failedMessage = JobFailed::on($this->jobId)->withMessage($message);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function shouldReload(): self
|
||||
public function shouldReload(JobChannels $channels): self
|
||||
{
|
||||
$this->afterMessage?->shouldReload();
|
||||
$this->failedMessage?->shouldReload();
|
||||
$this->reloadAfter = ReloadTriggered::on($channels);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function handle(JobDecorator $job, Closure $next): void
|
||||
{
|
||||
if ($this->beforeMessage) {
|
||||
event($this->beforeMessage);
|
||||
}
|
||||
|
||||
try {
|
||||
$next($job);
|
||||
} catch (Throwable $e) {
|
||||
|
@ -74,5 +70,9 @@ class WithJobState
|
|||
if ($this->afterMessage) {
|
||||
event($this->afterMessage);
|
||||
}
|
||||
|
||||
if ($this->reloadAfter) {
|
||||
event($this->reloadAfter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
namespace App\Lib\Queue;
|
||||
|
||||
use App\Lib\JobMiddleware\JobChannels;
|
||||
use Illuminate\Support\Str;
|
||||
use App\Lib\JobMiddleware\WithJobState;
|
||||
|
||||
trait TracksJob
|
||||
{
|
||||
abstract public function jobState(WithJobState $jobState, ...$parameters): WithJobState;
|
||||
abstract public function jobChannel(): string;
|
||||
|
||||
/**
|
||||
* @param mixed $parameters
|
||||
|
@ -16,7 +16,7 @@ trait TracksJob
|
|||
public function startJob(...$parameters): void
|
||||
{
|
||||
$jobId = Str::uuid();
|
||||
$jobState = WithJobState::make($this->jobChannel(), $jobId);
|
||||
$jobState = WithJobState::make($jobId);
|
||||
$this->jobState(...[$jobState, ...$parameters])->beforeMessage->dispatch();
|
||||
$parameters[] = $jobId;
|
||||
static::dispatch(...$parameters);
|
||||
|
@ -30,7 +30,7 @@ trait TracksJob
|
|||
public function getJobMiddleware(...$parameters): array
|
||||
{
|
||||
$jobId = array_pop($parameters);
|
||||
$jobState = WithJobState::make($this->jobChannel(), $jobId);
|
||||
$jobState = WithJobState::make($jobId);
|
||||
$jobState = $this->jobState(...[$jobState, ...$parameters]);
|
||||
$jobState->beforeMessage = null;
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
namespace App\Member\Actions;
|
||||
|
||||
use App\Lib\JobMiddleware\JobChannels;
|
||||
use App\Lib\JobMiddleware\WithJobState;
|
||||
use App\Lib\Queue\TracksJob;
|
||||
use App\Maildispatcher\Actions\ResyncAction;
|
||||
|
@ -45,11 +46,6 @@ class MemberDeleteAction
|
|||
->before('Mitglied ' . $member->fullname . ' wird gelöscht')
|
||||
->after('Mitglied ' . $member->fullname . ' gelöscht')
|
||||
->failed('Löschen von ' . $member->fullname . ' fehlgeschlagen.')
|
||||
->shouldReload();
|
||||
}
|
||||
|
||||
public function jobChannel(): string
|
||||
{
|
||||
return 'member';
|
||||
->shouldReload(JobChannels::make()->add('member'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,45 +2,58 @@
|
|||
|
||||
namespace App\Membership\Actions;
|
||||
|
||||
use App\Lib\JobMiddleware\JobChannels;
|
||||
use App\Lib\JobMiddleware\WithJobState;
|
||||
use App\Lib\Queue\TracksJob;
|
||||
use App\Maildispatcher\Actions\ResyncAction;
|
||||
use App\Member\Member;
|
||||
use App\Member\Membership;
|
||||
use App\Setting\NamiSettings;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Lorisleiva\Actions\ActionRequest;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class MembershipDestroyAction
|
||||
{
|
||||
use AsAction;
|
||||
use TracksJob;
|
||||
|
||||
public function handle(Member $member, Membership $membership, NamiSettings $settings): void
|
||||
public function handle(Membership $membership): void
|
||||
{
|
||||
$api = $settings->login();
|
||||
$api = app(NamiSettings::class)->login();
|
||||
|
||||
if ($membership->hasNami) {
|
||||
$settings->login()->deleteMembership(
|
||||
$member->nami_id,
|
||||
$api->membership($member->nami_id, $membership->nami_id)
|
||||
$api->deleteMembership(
|
||||
$membership->member->nami_id,
|
||||
$api->membership($membership->member->nami_id, $membership->nami_id)
|
||||
);
|
||||
}
|
||||
|
||||
$membership->delete();
|
||||
|
||||
if ($membership->hasNami) {
|
||||
$member->syncVersion();
|
||||
$membership->member->syncVersion();
|
||||
}
|
||||
}
|
||||
|
||||
public function asController(Membership $membership, NamiSettings $settings): JsonResponse
|
||||
{
|
||||
$this->handle(
|
||||
$membership->member,
|
||||
$membership,
|
||||
$settings,
|
||||
);
|
||||
|
||||
ResyncAction::dispatch();
|
||||
}
|
||||
|
||||
public function asController(Membership $membership): JsonResponse
|
||||
{
|
||||
$this->startJob($membership);
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $parameters
|
||||
*/
|
||||
public function jobState(WithJobState $jobState, ...$parameters): WithJobState
|
||||
{
|
||||
$member = $parameters[0]->member;
|
||||
|
||||
return $jobState
|
||||
->before('Mitgliedschaft für ' . $member->fullname . ' wird gelöscht')
|
||||
->after('Mitgliedschaft für ' . $member->fullname . ' gelöscht')
|
||||
->failed('Fehler beim Löschen der Mitgliedschaft für ' . $member->fullname)
|
||||
->shouldReload(JobChannels::make()->add('member')->add('membership'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ namespace App\Membership\Actions;
|
|||
|
||||
use App\Activity;
|
||||
use App\Group;
|
||||
use App\Lib\JobMiddleware\JobChannels;
|
||||
use App\Lib\JobMiddleware\WithJobState;
|
||||
use App\Lib\Queue\TracksJob;
|
||||
use App\Maildispatcher\Actions\ResyncAction;
|
||||
|
@ -12,6 +13,7 @@ use App\Member\Membership;
|
|||
use App\Setting\NamiSettings;
|
||||
use App\Subactivity;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\In;
|
||||
|
@ -90,7 +92,7 @@ class MembershipStoreAction
|
|||
];
|
||||
}
|
||||
|
||||
public function asController(Member $member, ActionRequest $request): Response
|
||||
public function asController(Member $member, ActionRequest $request): JsonResponse
|
||||
{
|
||||
$this->startJob(
|
||||
$member,
|
||||
|
@ -100,7 +102,7 @@ class MembershipStoreAction
|
|||
$request->promised_at ? Carbon::parse($request->promised_at) : null,
|
||||
);
|
||||
|
||||
return response('');
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -114,11 +116,6 @@ class MembershipStoreAction
|
|||
->before('Mitgliedschaft für ' . $member->fullname . ' wird gespeichert')
|
||||
->after('Mitgliedschaft für ' . $member->fullname . ' gespeichert')
|
||||
->failed('Fehler beim Erstellen der Mitgliedschaft für ' . $member->fullname)
|
||||
->shouldReload();
|
||||
}
|
||||
|
||||
public function jobChannel(): string
|
||||
{
|
||||
return 'member';
|
||||
->shouldReload(JobChannels::make()->add('member')->add('membership'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
namespace App\Membership\Actions;
|
||||
|
||||
use App\Activity;
|
||||
use App\Lib\JobMiddleware\JobChannels;
|
||||
use App\Lib\JobMiddleware\WithJobState;
|
||||
use App\Lib\Queue\TracksJob;
|
||||
use App\Maildispatcher\Actions\ResyncAction;
|
||||
use App\Member\Membership;
|
||||
use App\Subactivity;
|
||||
|
@ -16,6 +19,7 @@ use Lorisleiva\Actions\Concerns\AsAction;
|
|||
class MembershipUpdateAction
|
||||
{
|
||||
use AsAction;
|
||||
use TracksJob;
|
||||
|
||||
public function handle(Membership $membership, Activity $activity, ?Subactivity $subactivity, ?Carbon $promisedAt): Membership
|
||||
{
|
||||
|
@ -25,6 +29,8 @@ class MembershipUpdateAction
|
|||
'promised_at' => $promisedAt,
|
||||
]);
|
||||
|
||||
ResyncAction::dispatch();
|
||||
|
||||
return $membership;
|
||||
}
|
||||
|
||||
|
@ -55,15 +61,27 @@ class MembershipUpdateAction
|
|||
|
||||
public function asController(Membership $membership, ActionRequest $request): JsonResponse
|
||||
{
|
||||
$this->handle(
|
||||
$this->startJob(
|
||||
$membership,
|
||||
Activity::find($request->activity_id),
|
||||
$request->subactivity_id ? Subactivity::find($request->subactivity_id) : null,
|
||||
$request->promised_at ? Carbon::parse($request->promised_at) : null,
|
||||
);
|
||||
|
||||
ResyncAction::dispatch();
|
||||
|
||||
return response()->json([]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $parameters
|
||||
*/
|
||||
public function jobState(WithJobState $jobState, ...$parameters): WithJobState
|
||||
{
|
||||
$member = $parameters[0]->member;
|
||||
|
||||
return $jobState
|
||||
->before('Mitgliedschaft für ' . $member->fullname . ' wird aktualisiert')
|
||||
->after('Mitgliedschaft für ' . $member->fullname . ' aktualisiert')
|
||||
->failed('Fehler beim Aktualisieren der Mitgliedschaft für ' . $member->fullname)
|
||||
->shouldReload(JobChannels::make()->add('member')->add('membership'));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,9 +91,4 @@ class StoreForGroupAction
|
|||
->after('Gruppen aktualisiert')
|
||||
->failed('Aktualisieren von Gruppen fehlgeschlagen');
|
||||
}
|
||||
|
||||
public function jobChannel(): string
|
||||
{
|
||||
return 'group';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,10 @@
|
|||
import {useToast} from 'vue-toastification';
|
||||
const toast = useToast();
|
||||
|
||||
function handleJobEvent(event, type = 'success', reloadCallback) {
|
||||
if (event.message) {
|
||||
toast[type](event.message);
|
||||
}
|
||||
if (event.reload) {
|
||||
reloadCallback();
|
||||
}
|
||||
}
|
||||
|
||||
export default function (siteName, reloadCallback) {
|
||||
return {
|
||||
startListener: function () {
|
||||
window.Echo.channel('jobs').listen('\\App\\Lib\\Events\\ClientMessage', (e) => handleJobEvent(e, 'success', reloadCallback));
|
||||
window.Echo.channel(siteName)
|
||||
.listen('\\App\\Lib\\Events\\JobStarted', (e) => handleJobEvent(e, 'success', reloadCallback))
|
||||
.listen('\\App\\Lib\\Events\\JobFinished', (e) => handleJobEvent(e, 'success', reloadCallback))
|
||||
.listen('\\App\\Lib\\Events\\JobFailed', (e) => handleJobEvent(e, 'error', reloadCallback));
|
||||
window.Echo.channel(siteName).listen('\\App\\Lib\\Events\\ReloadTriggered', () => reloadCallback());
|
||||
},
|
||||
stopListener() {
|
||||
window.Echo.leave(siteName);
|
||||
window.Echo.leave('jobs');
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,8 +1,18 @@
|
|||
import Pusher from 'pusher-js';
|
||||
import Echo from 'laravel-echo';
|
||||
import {useToast} from 'vue-toastification';
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
window.Pusher = Pusher;
|
||||
export default new Echo({
|
||||
|
||||
function handleJobEvent(event, type = 'success') {
|
||||
if (event.message) {
|
||||
toast[type](event.message);
|
||||
}
|
||||
}
|
||||
|
||||
var echo = new Echo({
|
||||
broadcaster: 'pusher',
|
||||
key: 'adremakey',
|
||||
wsHost: window.location.hostname,
|
||||
|
@ -13,3 +23,10 @@ export default new Echo({
|
|||
cluster: 'adrema',
|
||||
enabledTransports: ['ws', 'wss'],
|
||||
});
|
||||
|
||||
echo.channel('jobs')
|
||||
.listen('\\App\\Lib\\Events\\JobStarted', (e) => handleJobEvent(e, 'success'))
|
||||
.listen('\\App\\Lib\\Events\\JobFinished', (e) => handleJobEvent(e, 'success'))
|
||||
.listen('\\App\\Lib\\Events\\JobFailed', (e) => handleJobEvent(e, 'error'));
|
||||
|
||||
export default echo;
|
||||
|
|
|
@ -4,10 +4,14 @@ namespace Tests\Feature\Membership;
|
|||
|
||||
use App\Activity;
|
||||
use App\Group;
|
||||
use App\Lib\Events\JobFinished;
|
||||
use App\Lib\Events\JobStarted;
|
||||
use App\Lib\Events\ReloadTriggered;
|
||||
use App\Member\Member;
|
||||
use App\Subactivity;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Facades\Event;
|
||||
use Tests\RequestFactories\MembershipRequestFactory;
|
||||
use Tests\TestCase;
|
||||
use Zoomyboy\LaravelNami\Fakes\MemberFake;
|
||||
|
@ -45,7 +49,7 @@ class StoreTest extends TestCase
|
|||
MembershipRequestFactory::new()->promise(now())->in($activity, $activity->subactivities->first())->group($member->group)->create()
|
||||
);
|
||||
|
||||
$response->assertRedirect('/member');
|
||||
$response->assertOk();
|
||||
$this->assertEquals(1506, $member->fresh()->version);
|
||||
$this->assertDatabaseHas('memberships', [
|
||||
'member_id' => $member->id,
|
||||
|
@ -64,6 +68,23 @@ class StoreTest extends TestCase
|
|||
]);
|
||||
}
|
||||
|
||||
public function testItFiresJobEvents(): void
|
||||
{
|
||||
Event::fake([JobStarted::class, JobFinished::class, ReloadTriggered::class]);
|
||||
$this->withoutExceptionHandling();
|
||||
$member = Member::factory()->defaults()->for(Group::factory())->createOne();
|
||||
$activity = Activity::factory()->hasAttached(Subactivity::factory())->createOne();
|
||||
|
||||
$this->from('/member')->post(
|
||||
"/member/{$member->id}/membership",
|
||||
MembershipRequestFactory::new()->in($activity, $activity->subactivities->first())->group($member->group)->create()
|
||||
);
|
||||
|
||||
Event::assertDispatched(JobStarted::class, fn ($event) => $event->broadcastOn()[0]->name === 'jobs' && $event->message !== null);
|
||||
Event::assertDispatched(JobFinished::class, fn ($event) => $event->broadcastOn()[0]->name === 'jobs' && $event->message !== null);
|
||||
Event::assertDispatched(ReloadTriggered::class, fn ($event) => ['member', 'membership'] === $event->channels->toArray());
|
||||
}
|
||||
|
||||
public function testItDoesntFireNamiWhenMembershipIsLocal(): void
|
||||
{
|
||||
$this->withoutExceptionHandling();
|
||||
|
|
Loading…
Reference in New Issue