diff --git a/app/Actions/Entries/CreateEntry.php b/app/Actions/Entries/CreateEntry.php
index 175d85a..2f1df8f 100644
--- a/app/Actions/Entries/CreateEntry.php
+++ b/app/Actions/Entries/CreateEntry.php
@@ -5,12 +5,9 @@ namespace App\Actions\Entries;
use App\Exceptions\AuditionAdminException;
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()
@@ -50,20 +47,6 @@ 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/Http/Controllers/EntryController.php b/app/Http/Controllers/EntryController.php
index ce5c5fc..534fc5b 100644
--- a/app/Http/Controllers/EntryController.php
+++ b/app/Http/Controllers/EntryController.php
@@ -3,11 +3,9 @@
namespace App\Http\Controllers;
use App\Actions\Entries\CreateEntry;
-use App\Exceptions\AuditionAdminException;
+use App\Http\Requests\EntryStoreRequest;
use App\Models\Audition;
-use App\Models\AuditLogEntry;
use App\Models\Entry;
-use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
@@ -17,11 +15,19 @@ class EntryController extends Controller
{
public function index()
{
+ if (! auth()->user()->school_id) {
+ abort(403);
+ }
- $entries = Auth::user()->entries()->with(['student', 'audition'])->get();
- $entries = $entries->sortBy(function ($entry) {
- return $entry->student->last_name.$entry->student->first_name.$entry->audition->score_order;
- });
+ $entries = Auth::user()->entries()
+ ->select('entries.*')
+ ->join('students as s', 's.id', '=', 'entries.student_id')
+ ->join('auditions as a', 'a.id', '=', 'entries.audition_id')
+ ->with(['student', 'audition'])
+ ->orderBy('s.last_name')
+ ->orderBy('s.first_name')
+ ->orderBy('a.score_order')
+ ->get();
$auditions = Audition::open()->get();
$students = Auth::user()->students;
$students->load('school');
@@ -29,37 +35,11 @@ class EntryController extends Controller
return view('entries.index', ['entries' => $entries, 'students' => $students, 'auditions' => $auditions]);
}
- public function store(Request $request, CreateEntry $creator)
+ public function store(EntryStoreRequest $request, CreateEntry $creator)
{
- if ($request->user()->cannot('create', Entry::class)) {
- abort(403);
- }
- $validData = $request->validate([
- 'student_id' => ['required', 'exists:students,id'],
- 'audition_id' => ['required', 'exists:auditions,id'],
- ]);
- $audition = Audition::find($validData['audition_id']);
- $currentDate = Carbon::now('America/Chicago');
- $currentDate = $currentDate->format('Y-m-d');
- if ($audition->entry_deadline < $currentDate) {
- return redirect()->route('entries.index')->with('error', 'The entry deadline for that audition has passed');
- }
+ $validData = $request->validatedWithEnterFor();
- $validData['for_seating'] = $request->get('for_seating') ? 1 : 0;
- $validData['for_advancement'] = $request->get('for_advancement') ? 1 : 0;
- $enter_for = [];
- if ($validData['for_seating']) {
- $enter_for[] = 'seating';
- }
- if ($validData['for_advancement']) {
- $enter_for[] = 'advancement';
- }
-
- try {
- $creator($validData['student_id'], $validData['audition_id'], $enter_for);
- } catch (AuditionAdminException $ex) {
- return redirect()->route('entries.index')->with('error', $ex->getMessage());
- }
+ $creator($validData['student_id'], $validData['audition_id'], $validData['enter_for'] ?? []);
return redirect()->route('entries.index')->with('success', 'The entry has been added.');
}
@@ -69,21 +49,7 @@ 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',
diff --git a/app/Http/Requests/EntryStoreRequest.php b/app/Http/Requests/EntryStoreRequest.php
new file mode 100644
index 0000000..886b518
--- /dev/null
+++ b/app/Http/Requests/EntryStoreRequest.php
@@ -0,0 +1,84 @@
+user()->is_admin) {
+ return true;
+ }
+ if (auth()->user()->school_id) {
+ return true;
+ }
+
+ return false;
+ }
+
+ public function rules()
+ {
+ return [
+ 'student_id' => ['required', 'exists:students,id'],
+ 'audition_id' => ['required', 'exists:auditions,id'],
+ 'for_seating' => ['sometimes', 'boolean'],
+ 'for_advancement' => ['sometimes', 'boolean'],
+ ];
+ }
+
+ public function withValidator($validator)
+ {
+ $validator->after(function ($validator) {
+ $auditionId = $this->input('audition_id');
+ $audition = Audition::find($auditionId);
+
+ if (! $audition) {
+ $validator->errors()->add('audition_id', 'The selected audition does not exist.');
+
+ return;
+ }
+
+ $currentDate = Carbon::now('America/Chicago')->format('Y-m-d');
+
+ if ($audition->entry_deadline < $currentDate) {
+ $validator->errors()->add('entry_deadline', 'The entry deadline for that audition has passed.');
+ }
+ });
+ }
+
+ /**
+ * Prepare the data for validation.
+ */
+ protected function prepareForValidation()
+ {
+ // Normalize the boolean inputs to 1 or 0
+ $this->merge([
+ 'for_seating' => $this->boolean('for_seating'),
+ 'for_advancement' => $this->boolean('for_advancement'),
+ ]);
+ }
+
+ /**
+ * Get the data after validation and add the "enter_for" array.
+ */
+ public function validatedWithEnterFor()
+ {
+ $validated = $this->validated();
+
+ $enter_for = [];
+ if (! empty($validated['for_seating'])) {
+ $enter_for[] = 'seating';
+ }
+ if (! empty($validated['for_advancement'])) {
+ $enter_for[] = 'advancement';
+ }
+
+ $validated['enter_for'] = $enter_for;
+
+ return $validated;
+ }
+}
diff --git a/app/Observers/EntryObserver.php b/app/Observers/EntryObserver.php
index aa020d8..d36ec45 100644
--- a/app/Observers/EntryObserver.php
+++ b/app/Observers/EntryObserver.php
@@ -18,13 +18,25 @@ class EntryObserver
$count = $entry->student->entriesForEvent($entry->audition->event_id)->count();
// If less than two entries, they're not a doubler
- if ($count < 2) {
- return;
+ if ($count > 1) {
+ // Update doublers for the event
+ $syncer = app(DoublerSync::class);
+ $syncer($entry->audition->event_id);
}
- // Update doublers for the event
- $syncer = app(DoublerSync::class);
- $syncer($entry->audition->event_id);
+ // Log Entry Creation
+ $message = 'Created Entry #'.$entry->id;
+ $message .= '
Audition: '.$entry->audition->name;
+ $message .= '
Student: '.$entry->student->full_name();
+ $message .= '
Grade: '.$entry->student->grade;
+ $message .= '
School: '.$entry->student->school->name;
+
+ $affected = [
+ 'students' => [$entry->student_id],
+ 'schools' => [$entry->student->school_id],
+ 'auditions' => [$entry->audition_id],
+ ];
+ auditionLog($message, $affected);
}
@@ -47,5 +59,18 @@ class EntryObserver
Doubler::where('student_id', $entry->student_id)->delete();
$audition = Audition::where('id', $entry->audition_id)->first();
$syncer($audition->event_id);
+
+ $message = 'Deleted Entry #'.$entry->id;
+ $message .= '
Audition: '.$entry->audition->name;
+ $message .= '
Student: '.$entry->student->full_name();
+ $message .= '
Grade: '.$entry->student->grade;
+ $message .= '
School: '.$entry->student->school->name;
+
+ $affected = [
+ 'students' => [$entry->student_id],
+ 'schools' => [$entry->student->school_id],
+ 'auditions' => [$entry->audition_id],
+ ];
+ auditionLog($message, $affected);
}
}
diff --git a/tests/Feature/app/Actions/Entries/CreateEntriesTest.php b/tests/Feature/app/Actions/Entries/CreateEntriesTest.php
index 47ce7b9..601c17f 100644
--- a/tests/Feature/app/Actions/Entries/CreateEntriesTest.php
+++ b/tests/Feature/app/Actions/Entries/CreateEntriesTest.php
@@ -4,7 +4,6 @@ use App\Actions\Entries\CreateEntry;
use App\Exceptions\AuditionAdminException;
use App\Models\Audition;
use App\Models\AuditionFlag;
-use App\Models\AuditLogEntry;
use App\Models\Entry;
use App\Models\Student;
use Illuminate\Foundation\Testing\RefreshDatabase;
@@ -139,18 +138,3 @@ it('throws and exception if the student is above the maximum grade for the audit
$audition = Audition::factory()->create(['minimum_grade' => 9, 'maximum_grade' => 10]);
$this->scribe->createEntry($student, $audition);
})->throws(AuditionAdminException::class, 'The grade of the student exceeds the maximum for that audition');
-
-it('logs the entry creation', function () {
- actAsAdmin();
- $student = Student::factory()->create(['grade' => 9]);
- $audition = Audition::factory()->create(['minimum_grade' => 9, 'maximum_grade' => 12]);
- $this->scribe->createEntry($student, $audition);
- $thisEntry = Entry::where('student_id', $student->id)->first();
- $logEntry = AuditLogEntry::orderBy('id', 'desc')->first();
- expect($logEntry->message)->toEqual('Entered '.$thisEntry->student->full_name().' from '.$thisEntry->student->school->name.' in '.$audition->name.'.')
- ->and($logEntry->affected['entries'])->toEqual([$thisEntry->id])
- ->and($logEntry->affected['students'])->toEqual([$thisEntry->student_id])
- ->and($logEntry->affected['auditions'])->toEqual([$thisEntry->audition_id])
- ->and($logEntry->affected['schools'])->toEqual([$thisEntry->student->school->id])
- ->and($logEntry->user)->toEqual(auth()->user()->email);
-});
diff --git a/tests/Feature/app/Http/Controllers/EntryContrllerTest.php b/tests/Feature/app/Http/Controllers/EntryContrllerTest.php
new file mode 100644
index 0000000..cee13f2
--- /dev/null
+++ b/tests/Feature/app/Http/Controllers/EntryContrllerTest.php
@@ -0,0 +1,126 @@
+create();
+ $response = $this->actingAs($user)->get(route('entries.index'));
+ $response->assertForbidden();
+ });
+
+ it('provides an index of the entries for the users school', function () {
+ $user = User::factory()->create();
+ $school = School::factory()->create();
+ $user->school_id = $school->id;
+ $user->save();
+ Student::factory()->count(3)
+ ->forSchool($school)
+ ->has(Entry::factory()->count(2))
+ ->create();
+ expect(Entry::count())->toEqual(6);
+ $response = $this->actingAs($user)->get(route('entries.index'));
+ $response->assertOk()
+ ->assertViewIs('entries.index')
+ ->assertViewHas('entries');
+ $this->assertCount(6, $response->viewData('entries'));
+ foreach (Entry::all() as $entry) {
+ $response->assertSee($entry->student->full_name());
+ }
+ });
+
+ it('provides a form for creating new entries', function () {
+ $user = User::factory()->create();
+ $school = School::factory()->create();
+ $user->school_id = $school->id;
+ $user->save();
+ Student::factory()->count(3)
+ ->forSchool($school)
+ ->has(Entry::factory()->count(2))
+ ->create();
+ Audition::each(function (Audition $audition) {
+ $audition->update(['entry_deadline' => now()->subDays(10)]);
+ });
+ $openAuditions = Audition::factory()->count(12)->create(['entry_deadline' => now()->addDays(10)]);
+ $response = $this->actingAs($user)->get(route('entries.index'));
+
+ $response->assertOk()
+ ->assertViewHas('students')
+ ->assertViewHas('auditions')
+ ->assertSee(route('entries.store'));
+ $this->assertCount(12, $response->viewData('auditions'));
+ $this->assertCount(3, $response->viewData('students'));
+ foreach ($openAuditions as $audition) {
+ $response->assertSee($audition->name);
+ }
+ });
+});
+
+describe('EntryController::store', function () {
+ it('denies access if the user does not have a school', function () {
+ $user = User::factory()->create();
+ $response = $this->actingAs($user)->post(route('entries.store'));
+ $response->assertForbidden();
+ });
+
+ it('creates a new entry for the users school', function () {
+ $user = User::factory()->create();
+ $school = School::factory()->create();
+ $user->school_id = $school->id;
+ $user->save();
+ $student = Student::factory()->forSchool($school)->create(['grade' => 10]);
+ $audition = Audition::factory()->create([
+ 'minimum_grade' => 9,
+ 'maximum_grade' => 12,
+ 'entry_deadline' => now()->addDays(10),
+ ]);
+ $response = actingAs($user)->post(route('entries.store'), [
+ 'student_id' => $student->id,
+ 'audition_id' => $audition->id,
+ 'for_advancement' => 'on',
+ 'for_seating' => 'on',
+ ]);
+ $response->assertRedirect(route('entries.index'))
+ ->assertSessionHas('success');
+ $this->assertCount(1, Entry::all());
+ $entry = Entry::first();
+ expect($entry->student_id)->toBe($student->id)
+ ->and($entry->audition_id)->toBe($audition->id)
+ ->and($entry->for_advancement)->toBeTruthy()
+ ->and($entry->for_seating)->toBeTruthy();
+
+ });
+});
+describe('EntryController::destroy', function () {
+ it('denies access if the user is not a director at the entries school', function () {
+ $user = User::factory()->create();
+ $school = School::factory()->create();
+ $user->update(['school_id' => $school->id]);
+ $user->refresh();
+ $entry = Entry::factory()->create();
+ $response = $this->actingAs($user)->delete(route('entries.destroy', $entry->id));
+ $response->assertForbidden();
+ });
+ it('deletes an entry', function () {
+ $user = User::factory()->create();
+ $school = School::factory()->create();
+ $student = Student::factory()->forSchool($school)->create();
+ $entry = Entry::factory()->forStudent($student)->create();
+ $user->school_id = $school->id;
+ $user->save();
+ expect(Entry::count())->toEqual(1);
+ $response = $this->actingAs($user)->delete(route('entries.destroy', $entry->id));
+ $response->assertRedirect(route('entries.index'))
+ ->assertSessionHas('success');
+ expect(Entry::count())->toEqual(0);
+ });
+});