Skip to content

Commit

Permalink
Merge branch 'frontend-2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Gawdl3y committed Jan 7, 2025
2 parents 5312d1e + 0271126 commit 3a5caef
Show file tree
Hide file tree
Showing 158 changed files with 9,855 additions and 4,726 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ auth.json
/public/storage
/storage/*.key
pgdump.sql
components.d.ts
2 changes: 1 addition & 1 deletion app/Console/Commands/NotifyEligibleRewardsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function handle(): void {
$notified = 0;
/** @var User $user */
foreach ($users as $user) {
$rewardInfo = $user->getRewardInfo($event, $user->timeEntries);
$rewardInfo = $user->getRewardInfo($event, timeEntries: $user->timeEntries);
foreach ($rewardInfo['eligible'] as $reward) {
// Make sure the user hasn't already been notified for the reward or claimed it
if ($user->hasBeenNotifiedForEligibleReward($reward, $user->notifications)) continue;
Expand Down
108 changes: 77 additions & 31 deletions app/Http/Controllers/AttendeeLogController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,79 +7,99 @@
use App\Http\Requests\AttendeeLogUpdateRequest;
use App\Http\Requests\AttendeeLogUserStoreRequest;
use App\Models\AttendeeLog;
use App\Models\AttendeeType;
use App\Models\Event;
use App\Models\Setting;
use App\Models\User;
use App\Reports\Report;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Illuminate\View\View;
use Inertia\Inertia;
use Inertia\Response as InertiaResponse;
use Throwable;

class AttendeeLogController extends Controller {
/**
* List all attendee logs
*/
public function index(Request $request, ?Event $event = null): JsonResponse|View {
public function index(Request $request, ?Event $event = null): JsonResponse|InertiaResponse {
if (!$event) $event = Setting::activeEvent();
$this->authorize('viewForEvent', [AttendeeLog::class, $event]);

$query = $request->user()->can('viewAny', AttendeeLog::class)
? AttendeeLog::forEvent($event)
: $request->user()->attendeeLogs()->forEvent($event)->wherePivot('type', 'gatekeeper');
$logs = $query->withCount(['users', 'attendees', 'gatekeepers'])->get();
$canViewAnyEvent = $request->user()->can('viewAny', Event::class);
$logs = $this->getVisibleLogs($event);

return $request->expectsJson()
? response()->json(['attendee_logs' => $logs])
: view('attendee-logs.list', [
'event' => $event,
'events' => Event::orderBy('name')->get(),
: Inertia::render('AttendeeLogIndex', [
'attendeeLogs' => $logs,
'event' => $event,
'events' => fn () => $canViewAnyEvent ? Event::orderBy('name')->get() : null,
]);
}

/**
* View an attendee log
*/
public function show(AttendeeLog $attendeeLog): JsonResponse|View {
public function show(Request $request, AttendeeLog $attendeeLog): JsonResponse|InertiaResponse {
$this->authorize('view', $attendeeLog);
return request()->expectsJson()

return $request->expectsJson()
? response()->json(['attendee_log' => $attendeeLog])
: view('attendee-logs.view', ['attendeeLog' => $attendeeLog, 'exportTypes' => Report::EXPORT_FILE_TYPES]);
: Inertia::render('AttendeeLogDetails', [
'attendeeLog' => $attendeeLog->load(['users' => function ($query) {
$query->select('id', 'badge_id', 'badge_name')->withPivot('type', 'created_at');
}]),
'event' => fn () => $attendeeLog->event,
'exportTypes' => fn () => Report::EXPORT_FILE_TYPES,
]);
}

/**
* Create an attendee log
*/
public function store(AttendeeLogStoreRequest $request, Event $event): JsonResponse {
public function store(AttendeeLogStoreRequest $request, Event $event): JsonResponse|RedirectResponse {
$attendeeLog = new AttendeeLog($request->validated());
$attendeeLog->event_id = $event->id;
$attendeeLog->save();
return response()->json(['attendee_log' => $attendeeLog]);

return $request->expectsJson()
? response()->json(['attendee_log' => $attendeeLog])
: redirect()->back()->withSuccess("Created attendee log {$attendeeLog->name}.");
}

/**
* Update an attendee log
*/
public function update(AttendeeLogUpdateRequest $request, AttendeeLog $attendeeLog): JsonResponse {
public function update(AttendeeLogUpdateRequest $request, AttendeeLog $attendeeLog): JsonResponse|RedirectResponse {
$attendeeLog->update($request->validated());
return response()->json(['attendee_log' => $attendeeLog]);

return $request->expectsJson()
? response()->json(['attendee_log' => $attendeeLog])
: redirect()->back()->withSuccess("Updated attendee log {$attendeeLog->name}.");
}

/**
* Delete an attendee log
*/
public function destroy(AttendeeLog $attendeeLog): JsonResponse {
public function destroy(Request $request, AttendeeLog $attendeeLog): JsonResponse|RedirectResponse {
$this->authorize('delete', $attendeeLog);

$attendeeLog->delete();
return response()->json(null, 205);

return $request->expectsJson()
? response()->json(null, 205)
: redirect()->back()->withSuccess("Deleted attendee log {$attendeeLog->name}.");
}

/**
* Add a user to an attendee log
*/
public function storeUser(AttendeeLogUserStoreRequest $request, AttendeeLog $attendeeLog): JsonResponse {
public function storeUser(AttendeeLogUserStoreRequest $request, AttendeeLog $attendeeLog): JsonResponse|RedirectResponse {
// Authorize the change
$type = $request->validated('type') ?? 'attendee';
$policyType = Str::plural(Str::title($type), 2);
Expand All @@ -100,38 +120,64 @@ public function storeUser(AttendeeLogUserStoreRequest $request, AttendeeLog $att
'badge_id' => $badgeId,
'error' => $err,
]);
return response()->json(['error' => 'Unable to find user with given badge ID.'], 404);
return $request->expectsJson()
? response()->json(['error' => "Unable to find user with badge #{$badgeId}."], 404)
: redirect()->back()->withErrors(['badge_id' => "Unable to find user with badge #{$badgeId}."]);
}

$user = User::createFromConCatRegistration($registration, 'Attendee');
}

// Make sure the user isn't already present in the log
if ($attendeeLog->users()->whereUserId($user->id)->exists()) {
return response()->json(['error' => 'User is already present in the log.'], 422);
if ($attendeeLog->users()->whereUserId($user->id)->wherePivot('type', $type)->exists()) {
$typeName = Str::title($type);
return $request->expectsJson()
? response()->json(['error' => "{$typeName} {$user->audit_name} is already present in the log."], 422)
: redirect()->back()->withErrors(['badge_id' => "{$typeName} {$user->audit_name} is already present in the log."]);
}

$attendeeLog->users()->attach($user, ['type' => $type]);
return response()->json([
'user' => $user->setVisible(['id', 'badge_id', 'badge_name']),
'type' => $type,
'logged_at' => now()->timezone(config('tracker.timezone'))->toDayDateTimeString(),
]);

return $request->expectsJson()
? response()->json([
'user' => $user->setVisible(['id', 'badge_id', 'badge_name']),
'type' => $type,
'logged_at' => now()->timezone(config('tracker.timezone'))->toDayDateTimeString(),
])
: redirect()->back()->withSuccess("Added {$type} {$user->audit_name} to the log.");
}

/**
* Remove a user from an attendee log
*/
public function destroyUser(Request $request, AttendeeLog $attendeeLog, User $user): JsonResponse {
public function destroyUser(Request $request, AttendeeLog $attendeeLog, AttendeeType $type, User $user): JsonResponse|RedirectResponse {
// Make sure the user is in the log
$logUser = $attendeeLog->users()->whereUserId($user->id)->first();
$logUser = $attendeeLog->users()->whereUserId($user->id)->wherePivot('type', $type)->first();
if (!$logUser) return response()->json(null, 404);

// Authorize this change
$policyType = Str::plural(Str::title($logUser->pivot->type), 2);
$this->authorize("manage{$policyType}", $attendeeLog);

$attendeeLog->users()->detach($user);
return response()->json(null, 205);
$attendeeLog->users()->wherePivot('type', $type)->detach($user);

return $request->expectsJson()
? response()->json(null, 205)
: redirect()->back()->withSuccess("Removed {$type->value} {$user->audit_name} from the log.");
}

/**
* Gets an event's attendee logs that are visible to the user
*
* @return Collection<string, AttendeeLog>
*/
protected function getVisibleLogs(?Event $event, ?User $user = null): Collection {
if (!$user) $user = request()->user();

$logsQuery = $user->can('viewAny', AttendeeLog::class)
? AttendeeLog::forEvent($event)
: $user->attendeeLogs()->forEvent($event)->wherePivot('type', 'gatekeeper');

return $logsQuery->withCount(['users', 'attendees', 'gatekeepers'])->get();
}
}
47 changes: 17 additions & 30 deletions app/Http/Controllers/AuthController.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,54 +13,41 @@
use Illuminate\Http\RedirectResponse;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\View\View;
use Inertia\Inertia;
use Inertia\Response as InertiaResponse;
use Laravel\Socialite\Facades\Socialite;
use Symfony\Component\HttpFoundation\Response;

class AuthController extends Controller {
/**
* Presents the login page
*/
public function getLogin(): View {
return view('auth.login');
public function getLogin(): InertiaResponse {
return Inertia::render('Login');
}

/**
* Logs the user out and redirects to the login page.
* Only for use from legacy (non-Inertia) pages.
* Logs the user out and redirects to the login page
*/
public function getLogout(): RedirectResponse {
public function getLogout(): Response {
if (!Auth::check()) return redirect()->route('auth.login');
Auth::logout();

$conCatLogout = $this->buildConCatLogoutUrl();
if ($conCatLogout) return redirect()->to($conCatLogout);
if ($conCatLogout) return Inertia::location(redirect()->to($conCatLogout));

return redirect()->route('auth.login');
}

/**
* Logs the user out and redirects to the login page.
* Only for use from Inertia pages.
*/
public function postLogout(): Response {
if (!Auth::check()) return Inertia::location(redirect()->route('auth.login'));
Auth::logout();

$conCatLogout = $this->buildConCatLogoutUrl();
if ($conCatLogout) return Inertia::location($conCatLogout);

return Inertia::location(redirect()->route('auth.login'));
}

/**
* Redirects to the beginning of the ConCat OAuth flow
*/
public function getRedirect(): RedirectResponse {
return Socialite::driver('concat')
->scopes(['pii:basic'])
->redirect();
public function getRedirect(): Response {
return Inertia::location(
Socialite::driver('concat')
->scopes(['pii:basic'])
->redirect()
);
}

/**
Expand Down Expand Up @@ -95,7 +82,7 @@ public function postQuickcode(QuickCodeRequest $request): JsonResponse|RedirectR
$error = 'Too many failed quick code login attempts have been made from this location. Try again in a minute.';
return $request->expectsJson()
? response()->json(['error' => $error], 429)
: redirect()->back()->withError($error)->withInput();
: redirect()->back()->withErrors(['code' => $error])->withInput();
}

// Retrieve the quick code
Expand All @@ -109,8 +96,8 @@ public function postQuickcode(QuickCodeRequest $request): JsonResponse|RedirectR
RateLimiter::hit($rateLimitKey);
$error = 'Quick code not recognized.';
return $request->expectsJson()
? response()->json(['error' => 'Quick code not recognized.'], 401)
: redirect()->back()->withError($error)->withInput();
? response()->json(['errors' => $error], 401)
: redirect()->back()->withErrors(['code' => $error])->withInput();
}

// Log the user in and delete the quick code to ensure it can't be used again
Expand All @@ -126,9 +113,9 @@ public function postQuickcode(QuickCodeRequest $request): JsonResponse|RedirectR
/**
* Display the banned notice
*/
public function getBanned(): View|RedirectResponse {
public function getBanned(): InertiaResponse|RedirectResponse {
if (!Auth::user()?->isBanned()) return redirect()->route('tracker.index');
return view('auth.banned');
return Inertia::render('Suspended');
}

/**
Expand Down
58 changes: 19 additions & 39 deletions app/Http/Controllers/ManagementController.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,38 +38,35 @@ class ManagementController extends Controller {
'attendee-log' => AttendeeLogReport::class,
];

/**
* Render the lead panel
*/
public function getLeadIndex(): View {
return view('management.lead');
}

/**
* Render the management panel
*/
public function getManageIndex(?Event $event = null): Response {
public function getManageIndex(?Event $event = null, ?User $user = null): Response {
if (!$event) $event = Setting::activeEvent();
if ($event) $this->authorize('view', $event);

return Inertia::render('ManagerDashboard', [
'event' => $event,
'events' => fn () => Event::orderBy('name')->get(),
'rewards' => fn () => Reward::forEvent($event)->orderBy('hours')->get(),
'departments' => fn () => Department::orderBy('hidden')->orderBy('name')->get(),
'kioskLifetime' => fn () => config('tracker.kiosk_lifetime'),
'longestOngoingEntries' => TimeEntry::with(['user', 'department'])
->forEvent($event)
->ongoing()
->orderBy('start')
->limit(10)
->get(),
'recentTimeActivities' => Activity::with(['subject', 'subject.user'])
->whereHasMorph('subject', TimeEntry::class, function (Builder $query) use ($event) {
$query->whereEventId($event?->id);
})
->orderByDesc('created_at')
->limit(10)
->get(),
'ongoingEntries' => Inertia::defer(
fn () => TimeEntry::with(['user', 'department'])
->forEvent($event)
->ongoing()
->orderBy('start')
->get()
),
'recentTimeActivities' => Inertia::defer(
fn () => Activity::with(['subject', 'subject.user'])
->whereHasMorph('subject', TimeEntry::class, function (Builder $query) use ($event) {
$query->whereEventId($event?->id);
})
->orderByDesc('created_at')
->limit(20)
->get()
),
'volunteer' => fn () => $user?->getVolunteerInfo($event),
]);
}

Expand Down Expand Up @@ -142,23 +139,6 @@ public function getAdminBonuses(?Event $event = null): View|RedirectResponse {
]);
}

/**
* Render the attendee logs admin page
*/
public function getAdminAttendeeLogs(?Event $event = null): View|RedirectResponse {
// Get the event and redirect to the page for the active event, if applicable
if (!$event) {
$event = Setting::activeEvent();
if ($event) return redirect()->route('admin.event.attendee-logs', $event);
}

return view('admin.attendee-logs', [
'event' => $event,
'events' => Event::all(),
'attendeeLogs' => $event?->attendeeLogs()?->with('gatekeepers')?->get(),
]);
}

/**
* Render the reports list admin page
*/
Expand Down
Loading

0 comments on commit 3a5caef

Please sign in to comment.