Skip to content

Manage responsible users for events, event series and organizations #40

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 5, 2024
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This application allows to manage events, their booking forms and bookings via a
- If a date of birth is submitted, the age of each participant and average age of each group is shown.
- Sub events have the bookings from parent event.
- Generate groups (randomized or age-based)
- Manage responsibilities (responsible users with their position) for events, event series and organizations
- Add, update, delete documents for events, event series, and organizations
- Login and logout, reset password, verify e-mail address, edit own account
- Manage users and flexible roles
Expand Down
7 changes: 7 additions & 0 deletions app/Http/Controllers/AccountController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@

class AccountController extends Controller
{
public function show(): View
{
$this->authorize('viewAccount', User::class);

return view('account.account_show');
}

public function edit(): View
{
$this->authorize('editAccount', User::class);
Expand Down
4 changes: 4 additions & 0 deletions app/Http/Controllers/EventController.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public function index(EventFilterRequest $request): View
'location',
'organizations',
'parentEvent',
'responsibleUsers',
])
->withCount([
'documents',
Expand Down Expand Up @@ -62,12 +63,15 @@ public function show(Event $event): View
]),
'parentEvent.subEvents.eventSeries',
'parentEvent.subEvents.location',
'parentEvent.subEvents.responsibleUsers',
'responsibleUsers',
'subEvents' => static fn (HasMany $query) => $query->withCount([
'documents',
'groups',
]),
'subEvents.eventSeries',
'subEvents.location',
'subEvents.responsibleUsers',
])
->loadCount([
'groups',
Expand Down
8 changes: 8 additions & 0 deletions app/Http/Controllers/EventSeriesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function index(EventSeriesFilterRequest $request): View
'eventSeries' => EventSeries::buildQueryFromRequest()
->with([
'parentEventSeries',
'responsibleUsers',
])
->withCount([
'documents',
Expand All @@ -45,9 +46,15 @@ public function show(EventSeries $eventSeries): View
'documents',
'groups',
]),
'events.bookingOptions' => fn (HasMany $bookingOptions) => $bookingOptions
->withCount([
'bookings',
]),
'events.location',
'events.parentEvent',
'events.responsibleUsers',
'parentEventSeries',
'responsibleUsers',
'subEventSeries' => fn (HasMany $subEventSeries) => $subEventSeries
->withCount([
'documents',
Expand All @@ -59,6 +66,7 @@ public function show(EventSeries $eventSeries): View
'events_min_started_at' => 'datetime',
'events_max_started_at' => 'datetime',
]),
'subEventSeries.responsibleUsers',
]),
]);
}
Expand Down
1 change: 1 addition & 0 deletions app/Http/Controllers/OrganizationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public function index(OrganizationFilterRequest $request): View
'organizations' => Organization::buildQueryFromRequest()
->with([
'location',
'responsibleUsers',
])
->withCount([
'documents',
Expand Down
12 changes: 12 additions & 0 deletions app/Http/Controllers/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ public function index(UserFilterRequest $request): View
->withCount([
'bookings',
'documents',
'responsibleForEvents',
'responsibleForEventSeries',
'responsibleForOrganizations',
])
->paginate(),
]));
Expand All @@ -51,6 +54,15 @@ public function store(UserRequest $request): RedirectResponse
return back();
}

public function show(User $user): View
{
$this->authorize('view', $user);

return view('users.user_show', [
'user' => $user->loadProfileData(),
]);
}

public function edit(User $user): View
{
$this->authorize('update', $user);
Expand Down
8 changes: 8 additions & 0 deletions app/Http/Requests/EventRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Http\Controllers\EventController;
use App\Http\Requests\Traits\AuthorizationViaController;
use App\Http\Requests\Traits\ValidatesResponsibleUsers;
use App\Models\Event;
use App\Options\Visibility;
use App\Policies\EventPolicy;
Expand All @@ -18,6 +19,7 @@ class EventRequest extends FormRequest
{
/** {@see EventPolicy} in {@see EventController} */
use AuthorizationViaController;
use ValidatesResponsibleUsers;

protected function prepareForValidation(): void
{
Expand Down Expand Up @@ -91,6 +93,12 @@ public function rules(): array
'nullable',
Rule::exists('event_series', 'id'),
],
...$this->rulesForResponsibleUsers(),
];
}

public function attributes(): array
{
return $this->attributesForResponsibleUsers();
}
}
8 changes: 8 additions & 0 deletions app/Http/Requests/EventSeriesRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Http\Controllers\EventSeriesController;
use App\Http\Requests\Traits\AuthorizationViaController;
use App\Http\Requests\Traits\ValidatesResponsibleUsers;
use App\Models\EventSeries;
use App\Options\Visibility;
use App\Policies\EventSeriesPolicy;
Expand All @@ -17,6 +18,7 @@ class EventSeriesRequest extends FormRequest
{
/** {@see EventSeriesPolicy} in {@see EventSeriesController} */
use AuthorizationViaController;
use ValidatesResponsibleUsers;

/**
* Get the validation rules that apply to the request.
Expand Down Expand Up @@ -50,6 +52,12 @@ public function rules(): array
->whereNull('parent_event_series_id')
->whereNot('id', $this->event_series->id ?? null),
],
...$this->rulesForResponsibleUsers(),
];
}

public function attributes(): array
{
return $this->attributesForResponsibleUsers();
}
}
8 changes: 8 additions & 0 deletions app/Http/Requests/OrganizationRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use App\Http\Controllers\OrganizationController;
use App\Http\Requests\Traits\AuthorizationViaController;
use App\Http\Requests\Traits\ValidatesResponsibleUsers;
use App\Models\Organization;
use App\Options\ActiveStatus;
use App\Policies\OrganizationPolicy;
Expand All @@ -17,6 +18,7 @@ class OrganizationRequest extends FormRequest
{
/** {@see OrganizationPolicy} in {@see OrganizationController} */
use AuthorizationViaController;
use ValidatesResponsibleUsers;

/**
* Get the validation rules that apply to the request.
Expand Down Expand Up @@ -54,6 +56,12 @@ public function rules(): array
'required',
Rule::exists('locations', 'id'),
],
...$this->rulesForResponsibleUsers(),
];
}

public function attributes(): array
{
return $this->attributesForResponsibleUsers();
}
}
46 changes: 46 additions & 0 deletions app/Http/Requests/Traits/ValidatesResponsibleUsers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

namespace App\Http\Requests\Traits;

use Illuminate\Validation\Rule;

trait ValidatesResponsibleUsers
{
protected function rulesForResponsibleUsers(): array
{
return [
'responsible_user_id' => [
'nullable',
'array',
'distinct:strict',
],
'responsible_user_id.*' => [
Rule::exists('users', 'id'),
],
'responsible_user_data.*.publicly_visible' => [
'nullable',
'boolean',
],
'responsible_user_data.*.position' => [
'nullable',
'string',
'max:255',
],
'responsible_user_data.*.sort' => [
'nullable',
'integer',
'min:1',
'max:999999',
],
];
}

protected function attributesForResponsibleUsers(): array
{
return [
'responsible_user_data.*.publicly_visible' => __('publicly visible'),
'responsible_user_data.*.position' => __('Position'),
'responsible_user_data.*.sort' => __('Sort'),
];
}
}
72 changes: 72 additions & 0 deletions app/Livewire/Users/SearchUsers.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<?php

namespace App\Livewire\Users;

use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\View\View;
use Livewire\Attributes\Locked;
use Livewire\Component;

class SearchUsers extends Component
{
public $searchTerm = '';

#[Locked]
public $fieldName = 'user_id';

/**
* @var Collection<User>
*/
#[Locked]
public $selectedUsers;

public function mount($selectedUsers): void
{
// Key selected users by their ID.
$this->selectedUsers = $selectedUsers->keyBy('id');
}

public function render(): View
{
$searchTerms = array_filter(array_map('trim', explode(',', $this->searchTerm)));

[$users, $usersCount] = count($searchTerms) === 0
? [Collection::empty(), 0]
: [$this->userQuery($searchTerms)->limit(100)->get(), $this->userQuery($searchTerms)->count()];

return view('livewire.users.search-users', [
'users' => $users,
'usersCount' => $usersCount,
]);
}

private function userQuery(array $searchTerms): Builder
{
return User::query()
->where(
fn (Builder $query) => $query
/** @see User::scopeName() */
->name(...$searchTerms)
/** @see User::scopeEmail() */
->orWhere(fn (Builder $query2) => $query2->email(...$searchTerms))
)
->whereNotIn('id', $this->selectedUsers->keys())
->orderBy('last_name')
->orderBy('first_name');
}

public function addUser($userId): void
{
$user = User::find($userId);
if (isset($user)) {
$this->selectedUsers[$userId] = $user;
}
}

public function removeUser($userId): void
{
unset($this->selectedUsers[$userId]);
}
}
11 changes: 10 additions & 1 deletion app/Models/Event.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
use App\Models\Traits\HasDocuments;
use App\Models\Traits\HasLocation;
use App\Models\Traits\HasNameAndDescription;
use App\Models\Traits\HasResponsibleUsers;
use App\Models\Traits\HasSlugForRouting;
use App\Models\Traits\HasWebsite;
use App\Options\Ability;
use App\Options\EventType;
use App\Options\Visibility;
use Carbon\Carbon;
Expand Down Expand Up @@ -48,6 +50,7 @@ class Event extends Model
use HasFactory;
use HasLocation;
use HasNameAndDescription;
use HasResponsibleUsers;
use HasSlugForRouting;
use HasWebsite;

Expand Down Expand Up @@ -165,7 +168,13 @@ public function fillAndSave(array $validatedData): bool
$this->parentEvent()->associate($validatedData['parent_event_id'] ?? null);

return $this->save()
&& $this->organizations()->sync($validatedData['organization_id'] ?? []);
&& $this->organizations()->sync($validatedData['organization_id'] ?? [])
&& $this->saveResponsibleUsers($validatedData);
}

public function getAbilityToViewResponsibilities(): Ability
{
return Ability::ViewResponsibilitiesOfEvents;
}

public function getBookingOptions(): Collection
Expand Down
11 changes: 10 additions & 1 deletion app/Models/EventSeries.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
use App\Models\QueryBuilder\BuildsQueryFromRequest;
use App\Models\QueryBuilder\SortOptions;
use App\Models\Traits\HasDocuments;
use App\Models\Traits\HasResponsibleUsers;
use App\Models\Traits\HasSlugForRouting;
use App\Options\Ability;
use App\Options\EventSeriesType;
use App\Options\Visibility;
use Illuminate\Database\Eloquent\Builder;
Expand All @@ -31,6 +33,7 @@ class EventSeries extends Model
use BuildsQueryFromRequest;
use HasDocuments;
use HasFactory;
use HasResponsibleUsers;
use HasSlugForRouting;

/**
Expand Down Expand Up @@ -95,7 +98,13 @@ public function fillAndSave(array $validatedData): bool
$this->fill($validatedData);
$this->parentEventSeries()->associate($validatedData['parent_event_series_id'] ?? null);

return $this->save();
return $this->save()
&& $this->saveResponsibleUsers($validatedData);
}

public function getAbilityToViewResponsibilities(): Ability
{
return Ability::ViewResponsibilitiesOfEventSeries;
}

public function getRoute(): string
Expand Down
Loading