diff --git a/app/Actions/Entries/CreateEntry.php b/app/Actions/Entries/CreateEntry.php index abc95c0..89a3cb4 100644 --- a/app/Actions/Entries/CreateEntry.php +++ b/app/Actions/Entries/CreateEntry.php @@ -4,9 +4,12 @@ namespace App\Actions\Entries; use App\Exceptions\ManageEntryException; use App\Models\Audition; +use App\Models\AuditLogEntry; use App\Models\Entry; use App\Models\Student; +use function auth; + class CreateEntry { public function __construct() @@ -46,6 +49,20 @@ class CreateEntry 'for_advancement' => $entry_for->contains('advancement'), ]); $entry->save(); + if (auth()->user()) { + $message = 'Entered '.$entry->student->full_name().' from '.$entry->student->school->name.' in '.$entry->audition->name.'.'; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => [ + 'entries' => [$entry->id], + 'students' => [$entry->student_id], + 'auditions' => [$entry->audition_id], + 'schools' => [$entry->student->school_id], + ], + ]); + } return $entry; } diff --git a/app/Actions/Entries/UpdateEntry.php b/app/Actions/Entries/UpdateEntry.php index 9fe0bba..4926dc8 100644 --- a/app/Actions/Entries/UpdateEntry.php +++ b/app/Actions/Entries/UpdateEntry.php @@ -4,14 +4,22 @@ namespace App\Actions\Entries; use App\Exceptions\ManageEntryException; use App\Models\Audition; +use App\Models\AuditLogEntry; use App\Models\Entry; use function array_key_exists; +use function auditionSetting; +use function auth; +use function request; class UpdateEntry { protected Entry $entry; + protected string $log_message = ''; + + protected array $log_affected = []; + public function __construct() { } @@ -48,8 +56,19 @@ class UpdateEntry if (array_key_exists('audition', $updateData)) { $this->updateAudition($updateData['audition']); } - $this->entry->save(); + if (auth()->user()) { + $this->log_affected['auditions'][] = $this->entry->audition_id; + $this->log_affected['entries'][] = $this->entry->id; + $this->log_affected['students'][] = $this->entry->student_id; + $this->log_affected['schools'][] = $this->entry->student->school_id; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $this->log_message, + 'affected' => $this->log_affected, + ]); + } } /** @@ -87,16 +106,24 @@ class UpdateEntry throw new ManageEntryException('Cannot change the audition for an entry with scores'); } if ($audition->id !== $this->entry->audition_id && - Entry::where('student_id', $this->entry->student_id) - ->where('audition_id', $audition->id)->exists()) { + Entry::where('student_id', $this->entry->student_id) + ->where('audition_id', $audition->id)->exists()) { throw new ManageEntryException('That student is already entered in that audition'); } + // Escape if we're not actually making a change + if ($this->entry->audition_id == $audition->id) { + return; + } // OK we're allowed to change the audition + $oldAudition = $this->entry->audition; $this->entry->audition_id = $audition->id; + $this->log_message .= 'Changed entry '.$this->entry->id.' from '.$oldAudition->name.' to '.$audition->name.'
'; + $this->log_affected['auditions'][] = $oldAudition->id; // Deal with our draw number if ($audition->hasFlag('drawn')) { $draw_number = $audition->entries()->max('draw_number'); $this->entry->draw_number = $draw_number + 1; + $this->log_message .= 'Entry '.$this->entry->id.' draw number set to '.$this->entry->draw_number.'
'; } else { $this->entry->draw_number = null; } @@ -115,11 +142,13 @@ class UpdateEntry throw new ManageEntryException('Cannot add seating to an entry in an audition where seats are published'); } $this->entry->for_seating = 1; + $this->log_message .= 'Entry '.$this->entry->id.' is entered for seating '.'
'; } else { if ($this->entry->audition->hasFlag('seats_published')) { throw new ManageEntryException('Cannot remove seating from an entry in an audition where seats are published'); } $this->entry->for_seating = 0; + $this->log_message .= 'Entry '.$this->entry->id.' is NOT entered for seating '.'
'; } } @@ -136,11 +165,13 @@ class UpdateEntry throw new ManageEntryException('Cannot add advancement to an entry in an audition where advancement is published'); } $this->entry->for_advancement = 1; + $this->log_message .= 'Entry '.$this->entry->id.' is entered for '.auditionSetting('advanceTo').'
'; } else { if ($this->entry->audition->hasFlag('advancement_published')) { throw new ManageEntryException('Cannot remove advancement from an entry in an audition where advancement is published'); } $this->entry->for_advancement = 0; + $this->log_message .= 'Entry '.$this->entry->id.' is NOT entered for '.auditionSetting('advanceTo').'
'; } } diff --git a/app/Actions/Fortify/CreateNewUser.php b/app/Actions/Fortify/CreateNewUser.php index 36fa9a9..6adb624 100644 --- a/app/Actions/Fortify/CreateNewUser.php +++ b/app/Actions/Fortify/CreateNewUser.php @@ -2,15 +2,14 @@ namespace App\Actions\Fortify; -use A6digital\Image\DefaultProfileImage; +use App\Models\AuditLogEntry; use App\Models\User; use App\Rules\ValidRegistrationCode; -use App\Settings; use Illuminate\Support\Facades\Hash; -use Illuminate\Support\Facades\Storage; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Laravel\Fortify\Contracts\CreatesNewUsers; + use function mb_substr; class CreateNewUser implements CreatesNewUsers @@ -41,9 +40,10 @@ class CreateNewUser implements CreatesNewUsers 'password' => $this->passwordRules(), ])->validate(); - $profileImageURL = 'https://ui-avatars.com/api/?name=' . mb_substr($input['first_name'],0,1) . '+' . mb_substr($input['last_name'],0,1); + $profileImageURL = 'https://ui-avatars.com/api/?name='.mb_substr($input['first_name'], 0, + 1).'+'.mb_substr($input['last_name'], 0, 1); - return User::create([ + $user = User::create([ 'first_name' => $input['first_name'], 'last_name' => $input['last_name'], 'judging_preference' => $input['judging_preference'], @@ -52,5 +52,18 @@ class CreateNewUser implements CreatesNewUsers 'profile_image_url' => $profileImageURL, 'password' => Hash::make($input['password']), ]); + + $message = 'New User Registered - '.$input['email'] + .'
Name: '.$input['first_name'].' '.$input['last_name'] + .'
Judging Pref: '.$input['judging_preference'] + .'
Cell Phone: '.$input['cell_phone']; + AuditLogEntry::create([ + 'user' => $input['email'], + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['users' => $user->id], + ]); + + return $user; } } diff --git a/app/Actions/Fortify/ResetUserPassword.php b/app/Actions/Fortify/ResetUserPassword.php index 7a57c50..939cf6e 100644 --- a/app/Actions/Fortify/ResetUserPassword.php +++ b/app/Actions/Fortify/ResetUserPassword.php @@ -2,6 +2,7 @@ namespace App\Actions\Fortify; +use App\Models\AuditLogEntry; use App\Models\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; @@ -25,5 +26,11 @@ class ResetUserPassword implements ResetsUserPasswords $user->forceFill([ 'password' => Hash::make($input['password']), ])->save(); + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => 'Reset Password', + 'affected' => ['users' => [$user->id]], + ]); } } diff --git a/app/Actions/Fortify/UpdateUserPassword.php b/app/Actions/Fortify/UpdateUserPassword.php index 8ce1488..12dd295 100644 --- a/app/Actions/Fortify/UpdateUserPassword.php +++ b/app/Actions/Fortify/UpdateUserPassword.php @@ -2,11 +2,14 @@ namespace App\Actions\Fortify; +use App\Models\AuditLogEntry; use App\Models\User; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Validator; use Laravel\Fortify\Contracts\UpdatesUserPasswords; +use function auth; + class UpdateUserPassword implements UpdatesUserPasswords { use PasswordValidationRules; @@ -28,5 +31,11 @@ class UpdateUserPassword implements UpdatesUserPasswords $user->forceFill([ 'password' => Hash::make($input['password']), ])->save(); + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => 'Changed Password', + 'affected' => ['users' => [$user->id]], + ]); } } diff --git a/app/Actions/Fortify/UpdateUserProfileInformation.php b/app/Actions/Fortify/UpdateUserProfileInformation.php index 5a5e1c9..2a56ab6 100644 --- a/app/Actions/Fortify/UpdateUserProfileInformation.php +++ b/app/Actions/Fortify/UpdateUserProfileInformation.php @@ -2,6 +2,7 @@ namespace App\Actions\Fortify; +use App\Models\AuditLogEntry; use App\Models\User; use Illuminate\Contracts\Auth\MustVerifyEmail; use Illuminate\Support\Facades\Validator; @@ -44,6 +45,16 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation 'email' => $input['email'], ])->save(); } + $message = 'Updated user #'.$user->id.' - '.$user->email + .'
Name: '.$user->full_name() + .'
Judging Pref: '.$user->judging_preference + .'
Cell Phone: '.$user->cell_phone; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['users' => [$user->id]], + ]); } /** @@ -53,6 +64,7 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation */ protected function updateVerifiedUser(User $user, array $input): void { + $oldEmail = $user->email; $user->forceFill([ 'first_name' => $input['first_name'], 'last_name' => $input['last_name'], @@ -61,6 +73,18 @@ class UpdateUserProfileInformation implements UpdatesUserProfileInformation 'email' => $input['email'], 'email_verified_at' => null, ])->save(); + $user->refresh(); + $message = 'Updated user #'.$user->id.' - '.$oldEmail + .'
Name: '.$user->full_name() + .'
Email: '.$user->email + .'
Judging Pref: '.$user->judging_preference + .'
Cell Phone: '.$user->cell_phone; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['users' => [$user->id]], + ]); $user->sendEmailVerificationNotification(); } diff --git a/app/Http/Controllers/Admin/EntryController.php b/app/Http/Controllers/Admin/EntryController.php index 4aa87a2..fbf66fb 100644 --- a/app/Http/Controllers/Admin/EntryController.php +++ b/app/Http/Controllers/Admin/EntryController.php @@ -8,6 +8,7 @@ use App\Actions\Tabulation\CalculateScoreSheetTotal; use App\Exceptions\ManageEntryException; use App\Http\Controllers\Controller; use App\Models\Audition; +use App\Models\AuditLogEntry; use App\Models\Entry; use App\Models\School; use App\Models\Seat; @@ -202,9 +203,24 @@ class EntryController extends Controller } if ($entry->scoreSheets()->count() > 0) { - return redirect()->route('admin.entries.index')->with('error', 'Cannot delete an entry that has been scored'); + return redirect()->route('admin.entries.index')->with('error', + 'Cannot delete an entry that has been scored'); + } + if (auth()->user()) { + $message = 'Deleted entry '.$entry->id; + $affected = [ + 'entries' => [$entry->id], + 'auditions' => [$entry->audition_id], + 'schools' => [$entry->student->school_id], + 'students' => [$entry->student_id], + ]; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => $affected, + ]); } - $entry->delete(); return redirect()->route('admin.entries.index')->with('success', 'Entry Deleted'); diff --git a/app/Http/Controllers/Admin/LogViewer.php b/app/Http/Controllers/Admin/LogViewer.php new file mode 100644 index 0000000..45b4543 --- /dev/null +++ b/app/Http/Controllers/Admin/LogViewer.php @@ -0,0 +1,20 @@ +paginate(20); + + return view('admin.logview', compact('log_entries')); + } +} diff --git a/app/Http/Controllers/Admin/SchoolController.php b/app/Http/Controllers/Admin/SchoolController.php index 3d335a6..702b602 100644 --- a/app/Http/Controllers/Admin/SchoolController.php +++ b/app/Http/Controllers/Admin/SchoolController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; +use App\Models\AuditLogEntry; use App\Models\School; use App\Models\SchoolEmailDomain; use App\Services\Invoice\InvoiceDataService; @@ -69,6 +70,13 @@ class SchoolController extends Controller 'state' => request('state'), 'zip' => request('zip'), ]); + $message = 'Modified school #'.$school->id.' - '.$school->name.' with address
'.$school->address.'
'.$school->city.', '.$school->state.' '.$school->zip; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['schools' => [$school->id]], + ]); return redirect()->route('admin.schools.show', ['school' => $school->id])->with('success', 'School '.$school->name.' updated'); @@ -100,6 +108,13 @@ class SchoolController extends Controller 'state' => request('state'), 'zip' => request('zip'), ]); + $message = 'Created school #'.$school->id.' - '.$school->name.' with address
'.$school->address.'
'.$school->city.', '.$school->state.' '.$school->zip; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['schools' => [$school->id]], + ]); return redirect('/admin/schools')->with('success', 'School '.$school->name.' created'); } @@ -110,6 +125,13 @@ class SchoolController extends Controller return to_route('admin.schools.index')->with('error', 'You cannot delete a school with students.'); } $name = $school->name; + $message = 'Delete school #'.$school->id.' - '.$school->name; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['schools' => [$school->id]], + ]); $school->delete(); return to_route('admin.schools.index')->with('success', 'School '.$school->name.' deleted'); @@ -128,6 +150,12 @@ class SchoolController extends Controller 'school_id' => $school->id, 'domain' => request('domain'), ]); + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => 'Added '.request('domain').' as an email domain for school #'.$school->id.' - '.$school->name, + 'affected' => ['schools' => [$school->id]], + ]); return redirect()->route('admin.schools.show', $school)->with('success', 'Domain Added'); diff --git a/app/Http/Controllers/Admin/StudentController.php b/app/Http/Controllers/Admin/StudentController.php index c0a4a3c..f536f88 100644 --- a/app/Http/Controllers/Admin/StudentController.php +++ b/app/Http/Controllers/Admin/StudentController.php @@ -4,11 +4,14 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Models\Audition; +use App\Models\AuditLogEntry; use App\Models\School; use App\Models\Student; use Illuminate\Support\Facades\Auth; use function abort; +use function auth; +use function request; use function to_route; use function view; @@ -55,14 +58,24 @@ class StudentController extends Controller return redirect('/admin/students/create')->with('error', 'This student already exists.'); } - Student::create([ + $student = Student::create([ 'first_name' => request('first_name'), 'last_name' => request('last_name'), 'grade' => request('grade'), 'school_id' => request('school_id'), ]); + $message = 'Created student #'.$student->id.' - '.$student->full_name().'
Grade: '.$student->grade.'
School: '.$student->school->name; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => [ + 'students' => [$student->id], + 'schools' => [$student->school_id], + ], + ]); - return redirect('/admin/students'); + return redirect('/admin/students')->with('success', 'Created student successfully'); } public function edit(Student $student) @@ -103,7 +116,8 @@ class StudentController extends Controller ->where('school_id', request('school_id')) ->where('id', '!=', $student->id) ->exists()) { - return redirect('/admin/students/'.$student->id.'/edit')->with('error', 'A student with that name already exists at that school'); + return redirect('/admin/students/'.$student->id.'/edit')->with('error', + 'A student with that name already exists at that school'); } $student->update([ @@ -113,7 +127,18 @@ class StudentController extends Controller 'school_id' => request('school_id'), ]); - return redirect('/admin/students'); + $message = 'Updated student #'.$student->id.'
Name: '.$student->full_name().'
Grade: '.$student->grade.'
School: '.$student->school->name; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => [ + 'students' => [$student->id], + 'schools' => [$student->school_id], + ], + ]); + + return redirect('/admin/students')->with('success', 'Student updated'); } @@ -123,6 +148,16 @@ class StudentController extends Controller return to_route('admin.students.index')->with('error', 'You cannot delete a student with entries.'); } $name = $student->full_name(); + $message = 'Deleted student #'.$student->id.'
Name: '.$student->full_name().'
Grade: '.$student->grade.'
School: '.$student->school->name; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => [ + 'students' => [$student->id], + 'schools' => [$student->school_id], + ], + ]); $student->delete(); return to_route('admin.students.index')->with('success', 'Student '.$name.' deleted successfully.'); diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index dec1523..82a804e 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -4,6 +4,7 @@ namespace App\Http\Controllers\Admin; use App\Http\Controllers\Controller; use App\Mail\NewUserPassword; +use App\Models\AuditLogEntry; use App\Models\School; use App\Models\User; use Illuminate\Http\Request; @@ -49,7 +50,9 @@ class UserController extends Controller if (! Auth::user()->is_admin) { abort(403); } - + $oldEmail = $user->email; + $wasAdmin = $user->is_admin; + $wasTab = $user->is_tab; $validData = $request->validate([ 'first_name' => ['required'], 'last_name' => ['required'], @@ -70,6 +73,39 @@ class UserController extends Controller 'is_admin' => $validData['is_admin'], 'is_tab' => $validData['is_tab'], ]); + $user->refresh(); + $logged_school = $user->school_id ? $user->school->name : 'No School'; + $message = 'Updated user #'.$user->id.' - '.$oldEmail + .'
Name: '.$user->full_name() + .'
Email: '.$user->email + .'
Cell Phone: '.$user->cell_phone + .'
Judging Pref: '.$user->judging_preference + .'
School: '.$logged_school; + + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['users' => [$user->id]], + ]); + if ($user->is_admin != $wasAdmin) { + $messageStart = $user->is_admin ? 'Granted admin privileges to ' : 'Revoked admin privileges from '; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $messageStart.$user->full_name().' - '.$user->email, + 'affected' => ['users' => [$user->id]], + ]); + } + if ($user->is_tab != $wasTab) { + $messageStart = $user->is_tab ? 'Granted tabulation privileges to ' : 'Revoked tabulation privileges from '; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $messageStart.$user->full_name().' - '.$user->email, + 'affected' => ['users' => [$user->id]], + ]); + } return redirect('/admin/users'); } @@ -101,7 +137,25 @@ class UserController extends Controller } $user->school_id = request('school_id'); $user->save(); - + $message = 'Created user '.$user->email.' - '.$user->full_name().'
Cell Phone: '.$user->cell_phone.'
Judging Pref: '.$user->judging_preference; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['users' => [$user->id]], + ]); + if ($user->school_id) { + $message = 'Set user '.$user->full_name().' ('.$user->email.') as a director at '.$user->school->name.'(#'.$user->school->id.')'; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => [ + 'users' => [$user->id], + 'schools' => [$user->id], + ], + ]); + } Mail::to($user->email)->send(new NewUserPassword($user, $randomPassword)); return redirect('/admin/users'); @@ -112,6 +166,13 @@ class UserController extends Controller if (! Auth::user()->is_admin) { abort(403); } + $message = 'Deleted user '.$user->email; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['users' => [$user->id]], + ]); $user->delete(); return redirect()->route('admin.users.index')->with('success', 'User deleted successfully'); diff --git a/app/Http/Controllers/EntryController.php b/app/Http/Controllers/EntryController.php index cdb992e..5c4dc8c 100644 --- a/app/Http/Controllers/EntryController.php +++ b/app/Http/Controllers/EntryController.php @@ -5,6 +5,7 @@ namespace App\Http\Controllers; use App\Actions\Entries\CreateEntry; use App\Exceptions\ManageEntryException; use App\Models\Audition; +use App\Models\AuditLogEntry; use App\Models\Entry; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; @@ -65,9 +66,25 @@ class EntryController extends Controller if ($request->user()->cannot('delete', $entry)) { abort(403); } + if (auth()->user()) { + $message = 'Deleted entry '.$entry->id; + $affected = [ + 'entries' => [$entry->id], + 'auditions' => [$entry->audition_id], + 'schools' => [$entry->student->school_id], + 'students' => [$entry->student_id], + ]; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => $affected, + ]); + } $entry->delete(); - return redirect()->route('entries.index')->with('success', 'The '.$entry->audition->name.'entry for '.$entry->student->full_name().'has been deleted.'); + return redirect()->route('entries.index')->with('success', + 'The '.$entry->audition->name.'entry for '.$entry->student->full_name().'has been deleted.'); } } diff --git a/app/Http/Controllers/SchoolController.php b/app/Http/Controllers/SchoolController.php index a084107..fa4bf75 100644 --- a/app/Http/Controllers/SchoolController.php +++ b/app/Http/Controllers/SchoolController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers; +use App\Models\AuditLogEntry; use App\Models\School; use App\Models\SchoolEmailDomain; use Illuminate\Http\RedirectResponse; @@ -34,16 +35,41 @@ class SchoolController extends Controller 'state' => request('state'), 'zip' => request('zip'), ]); + $message = 'Created school #'.$school->id.' - '.$school->name.' with address
'.$school->address.'
'.$school->city.', '.$school->state.' '.$school->zip; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['schools' => [$school->id]], + ]); if (! Auth::user()->school) { Auth::user()->update([ 'school_id' => $school->id, ]); - + $message = 'Set user '.auth()->user()->full_name().' ('.auth()->user()->email.') as a director at '.$school->name.'(#'.$school->id.')'; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => [ + 'users' => [auth()->user()->id], + 'schools' => [$school->id], + ], + ]); SchoolEmailDomain::create([ 'school_id' => $school->id, 'domain' => Auth::user()->emailDomain(), ]); + $message = 'Added '.auth()->user()->emailDomain().' as an email domain for '.$school->name.' (#'.$school->id.')'; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => [ + 'schools' => [$school->id], + ], + ]); } return redirect('/schools/'.$school->id); @@ -96,6 +122,13 @@ class SchoolController extends Controller 'state' => request('state'), 'zip' => request('zip'), ]); + $message = 'Modified school #'.$school->id.' - '.$school->name.' with address
'.$school->address.'
'.$school->city.', '.$school->state.' '.$school->zip; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => ['schools' => [$school->id]], + ]); return redirect()->route('schools.show', $school->id)->with('success', 'School details updated'); } diff --git a/app/Http/Controllers/StudentController.php b/app/Http/Controllers/StudentController.php index 82baca5..1b24434 100644 --- a/app/Http/Controllers/StudentController.php +++ b/app/Http/Controllers/StudentController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers; use App\Models\Audition; +use App\Models\AuditLogEntry; use App\Models\Student; use App\Rules\UniqueFullNameAtSchool; use Illuminate\Http\Request; @@ -58,10 +59,18 @@ class StudentController extends Controller 'grade' => request('grade'), 'school_id' => Auth::user()->school_id, ]); + $message = 'Created student #'.$student->id.' - '.$student->full_name().'
Grade: '.$student->grade.'
School: '.$student->school->name; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => [ + 'students' => [$student->id], + 'schools' => [$student->school_id], + ], + ]); - $request->session()->put('auditionMessages', ['success', 'I did it again ma']); - - return redirect('/students'); + return redirect('/students')->with('success', 'Student Created'); } /** @@ -104,7 +113,8 @@ class StudentController extends Controller ->where('school_id', Auth::user()->school_id) ->where('id', '!=', $student->id) ->exists()) { - return redirect()->route('students.edit', $student)->with('error', 'A student with that name already exists at your school.'); + return redirect()->route('students.edit', $student)->with('error', + 'A student with that name already exists at your school.'); } $student->update([ @@ -112,6 +122,16 @@ class StudentController extends Controller 'last_name' => request('last_name'), 'grade' => request('grade'), ]); + $message = 'Updated student #'.$student->id.'
Name: '.$student->full_name().'
Grade: '.$student->grade.'
School: '.$student->school->name; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => [ + 'students' => [$student->id], + 'schools' => [$student->school_id], + ], + ]); return redirect('/students')->with('success', 'Student updated successfully.'); } @@ -124,6 +144,16 @@ class StudentController extends Controller if ($request->user()->cannot('delete', $student)) { abort(403); } + $message = 'Deleted student #'.$student->id.'
Name: '.$student->full_name().'
Grade: '.$student->grade.'
School: '.$student->school->name; + AuditLogEntry::create([ + 'user' => auth()->user()->email, + 'ip_address' => request()->ip(), + 'message' => $message, + 'affected' => [ + 'students' => [$student->id], + 'schools' => [$student->school_id], + ], + ]); $student->delete(); return redirect(route('students.index')); diff --git a/app/Listeners/LogLogin.php b/app/Listeners/LogLogin.php new file mode 100644 index 0000000..38df128 --- /dev/null +++ b/app/Listeners/LogLogin.php @@ -0,0 +1,33 @@ +user; + AuditLogEntry::create([ + 'user' => $user->email, + 'ip_address' => request()->ip(), + 'message' => 'Logged In', + 'affected' => ['users' => [$user->id]], + ]); + } +} diff --git a/app/Listeners/LogLogout.php b/app/Listeners/LogLogout.php new file mode 100644 index 0000000..08cb8cd --- /dev/null +++ b/app/Listeners/LogLogout.php @@ -0,0 +1,33 @@ +user; + AuditLogEntry::create([ + 'user' => $user->email, + 'ip_address' => request()->ip(), + 'message' => 'Logged Out', + 'affected' => ['users' => [$user->id]], + ]); + } +} diff --git a/app/Listeners/LogSendingEmail.php b/app/Listeners/LogSendingEmail.php new file mode 100644 index 0000000..d858f4e --- /dev/null +++ b/app/Listeners/LogSendingEmail.php @@ -0,0 +1,35 @@ +message->getTo()[0]->getAddress()); + $subject = $event->message->getSubject(); + $message = 'Sent email to '.$email_to.'
Subject: '.$subject; + AuditLogEntry::create([ + 'user' => auth()->user()->email ?? 'none', + 'ip_address' => request()->ip(), + 'message' => $message, + ]); + + } +} diff --git a/app/Models/AuditLogEntry.php b/app/Models/AuditLogEntry.php new file mode 100644 index 0000000..205fd5b --- /dev/null +++ b/app/Models/AuditLogEntry.php @@ -0,0 +1,22 @@ + 'json']; + + public function getCreatedAtAttribute($value) + { + return \Carbon\Carbon::parse($value) + ->setTimezone('America/Chicago') + ->format('M j, Y H:i:s'); + } +} diff --git a/database/migrations/2024_08_05_205055_create_audit_log_entries_table.php b/database/migrations/2024_08_05_205055_create_audit_log_entries_table.php new file mode 100644 index 0000000..75959c5 --- /dev/null +++ b/database/migrations/2024_08_05_205055_create_audit_log_entries_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('user'); + $table->ipAddress('ip_address')->nullable(); + $table->string('message'); + $table->json('affected')->nullable(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('audit_log_entries'); + } +}; diff --git a/resources/views/admin/logview.blade.php b/resources/views/admin/logview.blade.php new file mode 100644 index 0000000..c13d62e --- /dev/null +++ b/resources/views/admin/logview.blade.php @@ -0,0 +1,50 @@ + + AuditionAdmin Logs + +
+
+
+
+ + + + + + + + + + + @foreach($log_entries as $entry) + @php($message = strip_tags($entry->message, '
')) + + + + + + + @endforeach + + + + +
+ Timestamp + User + IP + + Message +
{{ $entry->created_at }}{{ $entry->user }}{{ $entry->ip_address }}{!! $message !!}
+
+ {{ $log_entries->links() }} +
+ +
+
+
+
+
+ +
diff --git a/resources/views/components/layout/navbar/menus/admin.blade.php b/resources/views/components/layout/navbar/menus/admin.blade.php index c0430d2..2931e0b 100644 --- a/resources/views/components/layout/navbar/menus/admin.blade.php +++ b/resources/views/components/layout/navbar/menus/admin.blade.php @@ -25,6 +25,7 @@ Schools Students Entries + View Logs diff --git a/routes/admin.php b/routes/admin.php index f126e75..11529cb 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -18,6 +18,7 @@ use Illuminate\Support\Facades\Route; Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')->group(function () { Route::view('/', 'admin.dashboard')->name('admin.dashboard'); + Route::get('/logs', App\Http\Controllers\Admin\LogViewer::class)->name('admin.view_logs'); Route::post('/auditions/roomUpdate', [ AuditionController::class, 'roomUpdate', @@ -31,8 +32,7 @@ Route::middleware(['auth', 'verified', CheckIfAdmin::class])->prefix('admin/')-> [AuditionSettings::class, 'save'])->name('audition-settings-save'); // Admin Bonus Scores Routes - Route::prefix('bonus-scores')->controller(BonusScoreDefinitionController::class)->group(function ( - ) { + Route::prefix('bonus-scores')->controller(BonusScoreDefinitionController::class)->group(function () { Route::get('/', 'index')->name('admin.bonus-scores.index'); Route::post('/', 'store')->name('admin.bonus-scores.store'); Route::post('/assign_auditions', 'assignAuditions')->name('admin.bonus-scores.addAuditions'); diff --git a/routes/user.php b/routes/user.php index 95bd5b1..2d84a45 100644 --- a/routes/user.php +++ b/routes/user.php @@ -45,6 +45,6 @@ Route::middleware(['auth', 'verified'])->controller(SchoolController::class)->gr Route::get('/schools/create', 'create')->name('schools.create'); Route::post('/schools', 'store')->name('schools.store'); Route::get('/schools/{school}/edit', 'edit')->name('schools.edit'); - Route::get('/schools/{school}', 'show')->name('schools.show')->name('schools.show'); + Route::get('/schools/{school}', 'show')->name('schools.show'); Route::patch('/schools/{school}', 'update')->name('schools.update'); }); diff --git a/tests/Feature/Pages/Setup/SettingsTest.php b/tests/Feature/Pages/Setup/SettingsTest.php index 1a1dc2c..b80e204 100644 --- a/tests/Feature/Pages/Setup/SettingsTest.php +++ b/tests/Feature/Pages/Setup/SettingsTest.php @@ -73,7 +73,7 @@ it('has a field with forms for each audition setting', function () { 'name' => 'school_fee', 'value' => number_format(auditionSetting('school_fee') / 100, 2), ]) - ->containsInput([ + ->containsTextarea([ 'name' => 'payment_address', 'value' => auditionSetting('payment_address'), ])