Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,11 @@ public function definition(): array
'application_schema' => null,
];
}

public function rsvp(): static
{
return $this->state(fn (): array => [
'enrollment_method' => EnrollmentMethod::Rsvp,
]);
}
}
27 changes: 27 additions & 0 deletions app-modules/events/database/factories/EventFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,31 @@ public function definition(): array
'status' => EventStatus::Draft,
];
}

public function published(): static
{
return $this->state(fn (): array => [
'status' => EventStatus::Published,
]);
}

public function upcoming(): static
{
$startsAt = Date::now()->addDays(7);

return $this->state(fn (): array => [
'starts_at' => $startsAt,
'ends_at' => $startsAt->clone()->addHours(3),
]);
}

public function past(): static
{
$startsAt = Date::now()->subDays(7);

return $this->state(fn (): array => [
'starts_at' => $startsAt,
'ends_at' => $startsAt->clone()->addHours(3),
]);
}
}
10 changes: 10 additions & 0 deletions app-modules/events/lang/en/exceptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

return [
'already_enrolled' => 'You are already enrolled in this event.',
'event_past' => 'This event has already started and is no longer accepting enrollments.',
'event_not_active' => 'This event is not available for enrollment.',
'invalid_enrollment_method' => 'This event does not accept RSVP enrollments.',
];
14 changes: 14 additions & 0 deletions app-modules/events/lang/en/pages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

return [
'back_to_events' => 'Back to Events',
'confirm_presence' => 'Confirm Presence',
'confirm_presence_hint' => 'Confirm your attendance to this event.',
'confirm_presence_success' => 'Your presence has been confirmed!',
'enrollment_status_label' => 'Your enrollment status',
'enrolled_at' => 'Enrolled on :date',
'no_enrollments' => 'You are not enrolled in any events yet.',
'no_upcoming_events' => 'No upcoming events available.',
];
10 changes: 10 additions & 0 deletions app-modules/events/lang/pt_BR/exceptions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

return [
'already_enrolled' => 'Você já está inscrito neste evento.',
'event_past' => 'Este evento já começou e não aceita mais inscrições.',
'event_not_active' => 'Este evento não está disponível para inscrição.',
'invalid_enrollment_method' => 'Este evento não aceita inscrições via RSVP.',
];
14 changes: 14 additions & 0 deletions app-modules/events/lang/pt_BR/pages.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

declare(strict_types=1);

return [
'back_to_events' => 'Voltar para Eventos',
'confirm_presence' => 'Confirmar Presença',
'confirm_presence_hint' => 'Confirme sua presença neste evento.',
'confirm_presence_success' => 'Sua presença foi confirmada!',
'enrollment_status_label' => 'Status da sua inscrição',
'enrolled_at' => 'Inscrito em :date',
'no_enrollments' => 'Você ainda não está inscrito em nenhum evento.',
'no_upcoming_events' => 'Nenhum evento disponível no momento.',
];
92 changes: 92 additions & 0 deletions app-modules/events/src/Enrollment/Actions/EnrollUserAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace He4rt\Events\Enrollment\Actions;

use He4rt\Events\Enrollment\DTOs\EnrollUserDTO;
use He4rt\Events\Enrollment\Enums\EnrollmentMethod;
use He4rt\Events\Enrollment\Enums\EnrollmentStatus;
use He4rt\Events\Enrollment\Enums\TriggeredBy;
use He4rt\Events\Enrollment\Events\EnrollmentConfirmed;
use He4rt\Events\Enrollment\Exceptions\EnrollmentException;
use He4rt\Events\Enrollment\Models\Enrollment;
use He4rt\Events\Enrollment\Models\EnrollmentTransition;
use He4rt\Events\Event\Enums\EventStatus;
use Illuminate\Support\Facades\DB;

final readonly class EnrollUserAction
{
public function handle(EnrollUserDTO $dto): Enrollment
{
$this->validate($dto);

return DB::transaction(function () use ($dto): Enrollment {
throw_if(
Enrollment::query()
->where('event_id', $dto->eventId)
->where('user_id', $dto->userId)
->exists(),
EnrollmentException::alreadyEnrolled(),
Comment thread
BrunaDomingues marked this conversation as resolved.
);

$now = now();

$enrollment = new Enrollment([
'event_id' => $dto->eventId,
'user_id' => $dto->userId,
'enrolled_at' => $now,
'confirmed_at' => $now,
]);
$enrollment->status = EnrollmentStatus::Confirmed;
$enrollment->save();

EnrollmentTransition::query()->create([
'enrollment_id' => $enrollment->id,
'from_status' => null,
'to_status' => EnrollmentStatus::Confirmed,
'actor_id' => $dto->userId,
'triggered_by' => TriggeredBy::User,
]);

event(new EnrollmentConfirmed(
enrollmentId: $enrollment->id,
eventId: $dto->eventId,
userId: $dto->userId,
xpRewardRsvp: $dto->xpRewardOnConfirmed,
));

return $enrollment->fresh(['event.enrollmentPolicy']);
});
}

private function validate(EnrollUserDTO $dto): void
{
throw_unless(
$dto->eventStatus === EventStatus::Published,
EnrollmentException::eventNotActive(),
);

throw_if(
$dto->eventStartsAt->lte(now()),
EnrollmentException::eventPast(),
);

throw_unless(
in_array(
$dto->enrollmentMethod,
[EnrollmentMethod::Rsvp, EnrollmentMethod::RsvpCheckin],
strict: true,
),
EnrollmentException::invalidEnrollmentMethod(),
);

throw_if(
Enrollment::query()
->where('event_id', $dto->eventId)
->where('user_id', $dto->userId)
->exists(),
EnrollmentException::alreadyEnrolled(),
);
}
}
37 changes: 37 additions & 0 deletions app-modules/events/src/Enrollment/DTOs/EnrollUserDTO.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace He4rt\Events\Enrollment\DTOs;

use Carbon\CarbonInterface;
use He4rt\Events\Enrollment\Enums\EnrollmentMethod;
use He4rt\Events\Event\Enums\EventStatus;
use He4rt\Events\Event\Models\Event;
use He4rt\Identity\User\Models\User;

final readonly class EnrollUserDTO
{
public function __construct(
public string $eventId,
public string $userId,
public EventStatus $eventStatus,
public CarbonInterface $eventStartsAt,
public ?EnrollmentMethod $enrollmentMethod,
public int $xpRewardOnConfirmed,
) {}

public static function fromModels(Event $event, User $user): self
{
$event->loadMissing('enrollmentPolicy');

return new self(
eventId: $event->id,
userId: $user->id,
eventStatus: $event->status,
eventStartsAt: $event->starts_at,
enrollmentMethod: $event->enrollmentPolicy?->enrollment_method,
xpRewardOnConfirmed: $event->enrollmentPolicy?->xp_on_confirmed ?? 0,
);
}
}
20 changes: 20 additions & 0 deletions app-modules/events/src/Enrollment/Events/EnrollmentConfirmed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php

declare(strict_types=1);

namespace He4rt\Events\Enrollment\Events;

use Illuminate\Contracts\Events\ShouldDispatchAfterCommit;
use Illuminate\Foundation\Events\Dispatchable;

final readonly class EnrollmentConfirmed implements ShouldDispatchAfterCommit
{
use Dispatchable;

public function __construct(
public string $enrollmentId,
public string $eventId,
public string $userId,
public int $xpRewardRsvp,
) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

declare(strict_types=1);

namespace He4rt\Events\Enrollment\Exceptions;

use Exception;
use Symfony\Component\HttpFoundation\Response;

final class EnrollmentException extends Exception
{
public static function alreadyEnrolled(): self
{
return new self(
__('events::exceptions.already_enrolled'),
Response::HTTP_CONFLICT,
);
}

public static function eventPast(): self
{
return new self(
__('events::exceptions.event_past'),
Response::HTTP_UNPROCESSABLE_ENTITY,
);
}

public static function eventNotActive(): self
{
return new self(
__('events::exceptions.event_not_active'),
Response::HTTP_UNPROCESSABLE_ENTITY,
);
}

public static function invalidEnrollmentMethod(): self
{
return new self(
__('events::exceptions.invalid_enrollment_method'),
Response::HTTP_UNPROCESSABLE_ENTITY,
);
}
}
Loading