Merge pull request #62 from okorpheus/auditionadmin-61

Implement basic logging
This commit is contained in:
Matt 2024-08-07 21:06:56 -05:00 committed by GitHub
commit 03beecf65e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 572 additions and 26 deletions

View File

@ -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;
}

View File

@ -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.'<br>';
$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.'<br>';
} 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 '.'<br>';
} 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 '.'<br>';
}
}
@ -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').'<br>';
} 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').'<br>';
}
}

View File

@ -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']
.'<br>Name: '.$input['first_name'].' '.$input['last_name']
.'<br>Judging Pref: '.$input['judging_preference']
.'<br>Cell Phone: '.$input['cell_phone'];
AuditLogEntry::create([
'user' => $input['email'],
'ip_address' => request()->ip(),
'message' => $message,
'affected' => ['users' => $user->id],
]);
return $user;
}
}

View File

@ -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]],
]);
}
}

View File

@ -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]],
]);
}
}

View File

@ -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
.'<br>Name: '.$user->full_name()
.'<br>Judging Pref: '.$user->judging_preference
.'<br>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
.'<br>Name: '.$user->full_name()
.'<br>Email: '.$user->email
.'<br>Judging Pref: '.$user->judging_preference
.'<br>Cell Phone: '.$user->cell_phone;
AuditLogEntry::create([
'user' => auth()->user()->email,
'ip_address' => request()->ip(),
'message' => $message,
'affected' => ['users' => [$user->id]],
]);
$user->sendEmailVerificationNotification();
}

View File

@ -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');

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AuditLogEntry;
use Illuminate\Http\Request;
class LogViewer extends Controller
{
/**
* Handle the incoming request.
*/
public function __invoke(Request $request)
{
$log_entries = AuditLogEntry::orderBy('created_at', 'desc')->paginate(20);
return view('admin.logview', compact('log_entries'));
}
}

View File

@ -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 <br>'.$school->address.'<br>'.$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 <br>'.$school->address.'<br>'.$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');

View File

@ -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().'<br>Grade: '.$student->grade.'<br>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.'<br>Name: '.$student->full_name().'<br>Grade: '.$student->grade.'<br>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.'<br>Name: '.$student->full_name().'<br>Grade: '.$student->grade.'<br>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.');

View File

@ -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
.'<br>Name: '.$user->full_name()
.'<br>Email: '.$user->email
.'<br>Cell Phone: '.$user->cell_phone
.'<br>Judging Pref: '.$user->judging_preference
.'<br>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().'<br>Cell Phone: '.$user->cell_phone.'<br>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');

View File

@ -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.');
}
}

View File

@ -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 <br>'.$school->address.'<br>'.$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 <br>'.$school->address.'<br>'.$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');
}

View File

@ -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().'<br>Grade: '.$student->grade.'<br>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.'<br>Name: '.$student->full_name().'<br>Grade: '.$student->grade.'<br>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.'<br>Name: '.$student->full_name().'<br>Grade: '.$student->grade.'<br>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'));

View File

@ -0,0 +1,33 @@
<?php
namespace App\Listeners;
use App\Models\AuditLogEntry;
use App\Models\User;
use Illuminate\Auth\Events\Login;
class LogLogin
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(Login $event): void
{
/** @var User $user */
$user = $event->user;
AuditLogEntry::create([
'user' => $user->email,
'ip_address' => request()->ip(),
'message' => 'Logged In',
'affected' => ['users' => [$user->id]],
]);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Listeners;
use App\Models\AuditLogEntry;
use App\Models\User;
use Illuminate\Auth\Events\Logout;
class LogLogout
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(Logout $event): void
{
/** @var User $user */
$user = $event->user;
AuditLogEntry::create([
'user' => $user->email,
'ip_address' => request()->ip(),
'message' => 'Logged Out',
'affected' => ['users' => [$user->id]],
]);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Listeners;
use App\Models\AuditLogEntry;
use Illuminate\Mail\Events\MessageSending;
use function request;
class LogSendingEmail
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(MessageSending $event): void
{
$email_to = ($event->message->getTo()[0]->getAddress());
$subject = $event->message->getSubject();
$message = 'Sent email to '.$email_to.'<br>Subject: '.$subject;
AuditLogEntry::create([
'user' => auth()->user()->email ?? 'none',
'ip_address' => request()->ip(),
'message' => $message,
]);
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class AuditLogEntry extends Model
{
use HasFactory;
protected $guarded = [];
protected $casts = ['affected' => 'json'];
public function getCreatedAtAttribute($value)
{
return \Carbon\Carbon::parse($value)
->setTimezone('America/Chicago')
->format('M j, Y H:i:s');
}
}

View File

@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('audit_log_entries', function (Blueprint $table) {
$table->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');
}
};

View File

@ -0,0 +1,50 @@
<x-layout.app>
<x-slot:page_title>AuditionAdmin Logs</x-slot:page_title>
<x-card.card>
<div class="px-4 sm:px-6 lg:px-8">
<div class="mt-4 flow-root">
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
<table class="min-w-full divide-y divide-gray-300">
<thead>
<tr class="divide-x divide-gray-200">
<th scope="col"
class="py-3.5 pl-4 pr-4 text-left text-sm font-semibold text-gray-900 sm:pl-0">
Timestamp
</th>
<th scope="col" class="px-4 py-3.5 text-left text-sm font-semibold text-gray-900">User
</th>
<th scope="col" class="px-4 py-3.5 text-left text-sm font-semibold text-gray-900">IP
</th>
<th scope="col"
class="py-3.5 pl-4 pr-4 text-left text-sm font-semibold text-gray-900 sm:pr-0">
Message
</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 bg-white">
@foreach($log_entries as $entry)
@php($message = strip_tags($entry->message, '<br>'))
<tr class="divide-x divide-gray-200">
<td class="whitespace-nowrap py-4 pl-4 pr-4 text-sm font-medium text-gray-900 sm:pl-0">{{ $entry->created_at }}</td>
<td class="whitespace-nowrap p-4 text-sm text-gray-500">{{ $entry->user }}</td>
<td class="whitespace-nowrap p-4 text-sm text-gray-500">{{ $entry->ip_address }}</td>
<td class="whitespace-nowrap py-4 pl-4 pr-4 text-sm text-gray-500 sm:pr-0">{!! $message !!}</td>
</tr>
@endforeach
<!-- More people... -->
</tbody>
</table>
<div class="py-6 border-t">
{{ $log_entries->links() }}
</div>
</div>
</div>
</div>
</div>
</x-card.card>
</x-layout.app>

View File

@ -25,6 +25,7 @@
<a href="/admin/schools" class="block p-2 hover:text-indigo-600">Schools</a>
<a href="/admin/students" class="block p-2 hover:text-indigo-600">Students</a>
<a href="/admin/entries" class="block p-2 hover:text-indigo-600">Entries</a>
<a href="{{route('admin.view_logs')}}" class="block p-2 hover:text-indigo-600">View Logs</a>
</div>
</div>
</div>

View File

@ -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');

View File

@ -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');
});

View File

@ -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'),
])