Implement basic logging #62

Merged
okorpheus merged 8 commits from auditionadmin-61 into master 2024-08-08 02:06:56 +00:00
9 changed files with 159 additions and 7 deletions
Showing only changes of commit 78cc52c86e - Show all commits

View File

@ -4,9 +4,12 @@ namespace App\Actions\Entries;
use App\Exceptions\ManageEntryException; use App\Exceptions\ManageEntryException;
use App\Models\Audition; use App\Models\Audition;
use App\Models\AuditLogEntry;
use App\Models\Entry; use App\Models\Entry;
use App\Models\Student; use App\Models\Student;
use function auth;
class CreateEntry class CreateEntry
{ {
public function __construct() public function __construct()
@ -46,6 +49,20 @@ class CreateEntry
'for_advancement' => $entry_for->contains('advancement'), 'for_advancement' => $entry_for->contains('advancement'),
]); ]);
$entry->save(); $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; return $entry;
} }

View File

@ -4,14 +4,22 @@ namespace App\Actions\Entries;
use App\Exceptions\ManageEntryException; use App\Exceptions\ManageEntryException;
use App\Models\Audition; use App\Models\Audition;
use App\Models\AuditLogEntry;
use App\Models\Entry; use App\Models\Entry;
use function array_key_exists; use function array_key_exists;
use function auditionSetting;
use function auth;
use function request;
class UpdateEntry class UpdateEntry
{ {
protected Entry $entry; protected Entry $entry;
protected string $log_message = '';
protected array $log_affected = [];
public function __construct() public function __construct()
{ {
} }
@ -48,8 +56,19 @@ class UpdateEntry
if (array_key_exists('audition', $updateData)) { if (array_key_exists('audition', $updateData)) {
$this->updateAudition($updateData['audition']); $this->updateAudition($updateData['audition']);
} }
$this->entry->save(); $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'); throw new ManageEntryException('Cannot change the audition for an entry with scores');
} }
if ($audition->id !== $this->entry->audition_id && if ($audition->id !== $this->entry->audition_id &&
Entry::where('student_id', $this->entry->student_id) Entry::where('student_id', $this->entry->student_id)
->where('audition_id', $audition->id)->exists()) { ->where('audition_id', $audition->id)->exists()) {
throw new ManageEntryException('That student is already entered in that audition'); 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 // OK we're allowed to change the audition
$oldAudition = $this->entry->audition;
$this->entry->audition_id = $audition->id; $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 // Deal with our draw number
if ($audition->hasFlag('drawn')) { if ($audition->hasFlag('drawn')) {
$draw_number = $audition->entries()->max('draw_number'); $draw_number = $audition->entries()->max('draw_number');
$this->entry->draw_number = $draw_number + 1; $this->entry->draw_number = $draw_number + 1;
$this->log_message .= 'Entry '.$this->entry->id.' draw number set to '.$this->entry->draw_number.'<br>';
} else { } else {
$this->entry->draw_number = null; $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'); throw new ManageEntryException('Cannot add seating to an entry in an audition where seats are published');
} }
$this->entry->for_seating = 1; $this->entry->for_seating = 1;
$this->log_message .= 'Entry '.$this->entry->id.' is entered for seating '.'<br>';
} else { } else {
if ($this->entry->audition->hasFlag('seats_published')) { if ($this->entry->audition->hasFlag('seats_published')) {
throw new ManageEntryException('Cannot remove seating from an entry in an audition where seats are published'); throw new ManageEntryException('Cannot remove seating from an entry in an audition where seats are published');
} }
$this->entry->for_seating = 0; $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'); throw new ManageEntryException('Cannot add advancement to an entry in an audition where advancement is published');
} }
$this->entry->for_advancement = 1; $this->entry->for_advancement = 1;
$this->log_message .= 'Entry '.$this->entry->id.' is entered for '.auditionSetting('advanceTo').'<br>';
} else { } else {
if ($this->entry->audition->hasFlag('advancement_published')) { if ($this->entry->audition->hasFlag('advancement_published')) {
throw new ManageEntryException('Cannot remove advancement from an entry in an audition where advancement is published'); throw new ManageEntryException('Cannot remove advancement from an entry in an audition where advancement is published');
} }
$this->entry->for_advancement = 0; $this->entry->for_advancement = 0;
$this->log_message .= 'Entry '.$this->entry->id.' is NOT entered for '.auditionSetting('advanceTo').'<br>';
} }
} }

View File

@ -8,6 +8,7 @@ use App\Actions\Tabulation\CalculateScoreSheetTotal;
use App\Exceptions\ManageEntryException; use App\Exceptions\ManageEntryException;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\Audition; use App\Models\Audition;
use App\Models\AuditLogEntry;
use App\Models\Entry; use App\Models\Entry;
use App\Models\School; use App\Models\School;
use App\Models\Seat; use App\Models\Seat;
@ -202,9 +203,24 @@ class EntryController extends Controller
} }
if ($entry->scoreSheets()->count() > 0) { 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(); $entry->delete();
return redirect()->route('admin.entries.index')->with('success', 'Entry Deleted'); return redirect()->route('admin.entries.index')->with('success', 'Entry Deleted');

View File

@ -5,6 +5,7 @@ namespace App\Http\Controllers;
use App\Actions\Entries\CreateEntry; use App\Actions\Entries\CreateEntry;
use App\Exceptions\ManageEntryException; use App\Exceptions\ManageEntryException;
use App\Models\Audition; use App\Models\Audition;
use App\Models\AuditLogEntry;
use App\Models\Entry; use App\Models\Entry;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@ -65,9 +66,25 @@ class EntryController extends Controller
if ($request->user()->cannot('delete', $entry)) { if ($request->user()->cannot('delete', $entry)) {
abort(403); 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(); $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

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

@ -8,4 +8,8 @@ use Illuminate\Database\Eloquent\Model;
class AuditLogEntry extends Model class AuditLogEntry extends Model
{ {
use HasFactory; use HasFactory;
protected $guarded = [];
protected $casts = ['affected' => 'json'];
} }

View File

@ -14,6 +14,7 @@ return new class extends Migration
Schema::create('audit_log_entries', function (Blueprint $table) { Schema::create('audit_log_entries', function (Blueprint $table) {
$table->id(); $table->id();
$table->string('user'); $table->string('user');
$table->ipAddress('ip_address')->nullable();
$table->string('message'); $table->string('message');
$table->json('affected')->nullable(); $table->json('affected')->nullable();
$table->timestamps(); $table->timestamps();

View File

@ -73,7 +73,7 @@ it('has a field with forms for each audition setting', function () {
'name' => 'school_fee', 'name' => 'school_fee',
'value' => number_format(auditionSetting('school_fee') / 100, 2), 'value' => number_format(auditionSetting('school_fee') / 100, 2),
]) ])
->containsInput([ ->containsTextarea([
'name' => 'payment_address', 'name' => 'payment_address',
'value' => auditionSetting('payment_address'), 'value' => auditionSetting('payment_address'),
]) ])