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/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/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/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/Models/AuditLogEntry.php b/app/Models/AuditLogEntry.php index c4bd3ee..7823ff5 100644 --- a/app/Models/AuditLogEntry.php +++ b/app/Models/AuditLogEntry.php @@ -8,4 +8,8 @@ use Illuminate\Database\Eloquent\Model; class AuditLogEntry extends Model { use HasFactory; + + protected $guarded = []; + + protected $casts = ['affected' => 'json']; } 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 index 3c5d76e..75959c5 100644 --- 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 @@ -14,6 +14,7 @@ return new class extends Migration 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(); 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'), ])