Move scoring functions to their own controller. ScoringGuide cache service and listener to update

This commit is contained in:
Matt Young 2024-06-12 22:15:28 -05:00
parent e8732293ba
commit d8e20d5f64
12 changed files with 210 additions and 81 deletions

View File

@ -0,0 +1,36 @@
<?php
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class ScoringGuideChange
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*/
public function __construct()
{
//
}
/**
* Get the channels the event should broadcast on.
*
* @return array<int, \Illuminate\Broadcasting\Channel>
*/
public function broadcastOn(): array
{
return [
new PrivateChannel('channel-name'),
];
}
}

View File

@ -2,6 +2,7 @@
namespace App\Http\Controllers\Admin; namespace App\Http\Controllers\Admin;
use App\Events\ScoringGuideChange;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\ScoringGuide; use App\Models\ScoringGuide;
use App\Models\SubscoreDefinition; use App\Models\SubscoreDefinition;
@ -37,6 +38,8 @@ class ScoringGuideController extends Controller
'name' => request('name') 'name' => request('name')
]); ]);
ScoringGuideChange::dispatch();
return redirect('/admin/scoring'); return redirect('/admin/scoring');
} }
@ -62,6 +65,7 @@ class ScoringGuideController extends Controller
$guide->update([ $guide->update([
'name' => request('name') 'name' => request('name')
]); ]);
ScoringGuideChange::dispatch();
return redirect('/admin/scoring/guides/' . $guide->id . '/edit' )->with('success','Scoring guide updated'); return redirect('/admin/scoring/guides/' . $guide->id . '/edit' )->with('success','Scoring guide updated');
} }
@ -93,7 +97,7 @@ class ScoringGuideController extends Controller
'for_seating' => $for_seating, 'for_seating' => $for_seating,
'for_advance' => $for_advance, 'for_advance' => $for_advance,
]); ]);
ScoringGuideChange::dispatch();
return redirect('/admin/scoring/guides/' . $guide->id . '/edit' )->with('success','Subscore added'); return redirect('/admin/scoring/guides/' . $guide->id . '/edit' )->with('success','Subscore added');
} }
@ -105,6 +109,7 @@ class ScoringGuideController extends Controller
$subscore = SubscoreDefinition::find($id); $subscore = SubscoreDefinition::find($id);
$subscore->update(['display_order' => $index]); $subscore->update(['display_order' => $index]);
} }
ScoringGuideChange::dispatch();
return response()->json(['status'=>'success']); return response()->json(['status'=>'success']);
} }
@ -117,6 +122,7 @@ class ScoringGuideController extends Controller
$subscore = SubscoreDefinition::find($id); $subscore = SubscoreDefinition::find($id);
$subscore->update(['tiebreak_order' => $index]); $subscore->update(['tiebreak_order' => $index]);
} }
ScoringGuideChange::dispatch();
return response()->json(['status'=>'success']); return response()->json(['status'=>'success']);
} }

View File

@ -0,0 +1,78 @@
<?php
namespace App\Http\Controllers\Tabulation;
use App\Http\Controllers\Controller;
use App\Models\Entry;
use App\Models\ScoreSheet;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Session;
class ScoreController extends Controller
{
public function chooseEntry(Request $request)
{
return view('tabulation.choose_entry');
}
public function destroyScore(ScoreSheet $score) {
$score->delete();
return redirect()->back()->with('success','Score Deleted');
}
public function entryScoreSheet(Request $request)
{
$existing_sheets = [];
$entry = Entry::with(['student','audition.room.judges'])->find($request->input('entry_id'));
$judges = $entry->audition->room->judges;
foreach ($judges as $judge) {
$scoreSheet = ScoreSheet::where('entry_id',$entry->id)->where('user_id',$judge->id)->first();
if ($scoreSheet) {
Session::flash('caution','Scores exist for this entry. Now editing existing scores');
$existing_sheets[$judge->id] = $scoreSheet;
}
}
$scoring_guide = $entry->audition->scoringGuide;
$subscores = $entry->audition->scoringGuide->subscores->sortBy('display_order');
if (!$entry) {
return redirect()->route('tabulation.chooseEntry')->with('error','Entry not found');
}
return view('tabulation.entry_score_sheet', compact('entry','judges','scoring_guide','subscores','existing_sheets'));
}
public function saveEntryScoreSheet(Request $request, Entry $entry)
{
$judges = $entry->audition->room->judges;
$subscores = $entry->audition->scoringGuide->subscores->sortBy('tiebreak_order');
$scoringGuide = $entry->audition->scoringGuide;
$preparedScoreSheets = [];
foreach ($judges as $judge) {
$preparedScoreSheets[$judge->id]['user_id'] = $judge->id;
$preparedScoreSheets[$judge->id]['entry_id'] = $entry->id;
$scoreValidation = $scoringGuide->validateScores($request->input('judge'.$judge->id));
if ($scoreValidation != 'success') {
return redirect(url()->previous())->with('error', $judge->full_name() . ': ' . $scoreValidation)->with('oldScores',$request->all());
}
$scoreSubmission = $request->input('judge'.$judge->id);
$scoresToSave = [];
foreach ($subscores as $subscore) {
$scoresToSave[$subscore->id] = [
'subscore_id'=>$subscore->id,
'subscore_name' => $subscore->name,
'score' => intval($scoreSubmission[$subscore->id])
];
}
$preparedScoreSheets[$judge->id]['scores'] = $scoresToSave;
}
foreach ($preparedScoreSheets as $sheet) {
ScoreSheet::updateOrCreate(
['entry_id' => $sheet['entry_id'], 'user_id' => $sheet['user_id']],
['subscores' => $sheet['scores']]
);
}
return redirect()->route('scores.chooseEntry')->with('success',count($preparedScoreSheets) . " Scores saved");
}
}

View File

@ -14,71 +14,7 @@ use function redirect;
class TabulationController extends Controller class TabulationController extends Controller
{ {
public function chooseEntry(Request $request)
{
return view('tabulation.choose_entry');
}
public function destroyScore(ScoreSheet $score) {
$score->delete();
return redirect()->back()->with('success','Score Deleted');
}
public function entryScoreSheet(Request $request)
{
$existing_sheets = [];
$entry = Entry::with(['student','audition.room.judges'])->find($request->input('entry_id'));
$judges = $entry->audition->room->judges;
foreach ($judges as $judge) {
$scoreSheet = ScoreSheet::where('entry_id',$entry->id)->where('user_id',$judge->id)->first();
if ($scoreSheet) {
Session::flash('caution','Scores exist for this entry. Now editing existing scores');
$existing_sheets[$judge->id] = $scoreSheet;
}
}
$scoring_guide = $entry->audition->scoringGuide;
$subscores = $entry->audition->scoringGuide->subscores->sortBy('display_order');
if (!$entry) {
return redirect()->route('tabulation.chooseEntry')->with('error','Entry not found');
}
return view('tabulation.entry_score_sheet', compact('entry','judges','scoring_guide','subscores','existing_sheets'));
}
public function saveEntryScoreSheet(Request $request, Entry $entry)
{
$judges = $entry->audition->room->judges;
$subscores = $entry->audition->scoringGuide->subscores->sortBy('tiebreak_order');
$scoringGuide = $entry->audition->scoringGuide;
$preparedScoreSheets = [];
foreach ($judges as $judge) {
$preparedScoreSheets[$judge->id]['user_id'] = $judge->id;
$preparedScoreSheets[$judge->id]['entry_id'] = $entry->id;
$scoreValidation = $scoringGuide->validateScores($request->input('judge'.$judge->id));
if ($scoreValidation != 'success') {
return redirect(url()->previous())->with('error', $judge->full_name() . ': ' . $scoreValidation)->with('oldScores',$request->all());
}
$scoreSubmission = $request->input('judge'.$judge->id);
$scoresToSave = [];
foreach ($subscores as $subscore) {
$scoresToSave[$subscore->id] = [
'subscore_id'=>$subscore->id,
'subscore_name' => $subscore->name,
'score' => intval($scoreSubmission[$subscore->id])
];
}
$preparedScoreSheets[$judge->id]['scores'] = $scoresToSave;
}
foreach ($preparedScoreSheets as $sheet) {
ScoreSheet::updateOrCreate(
['entry_id' => $sheet['entry_id'], 'user_id' => $sheet['user_id']],
['subscores' => $sheet['scores']]
);
}
return redirect()->route('tabulation.chooseEntry')->with('success',count($preparedScoreSheets) . " Scores created");
}
public function status() public function status()
{ {

View File

@ -0,0 +1,30 @@
<?php
namespace App\Listeners;
use App\Events\ScoringGuideChange;
use App\Services\ScoringGuideCacheService;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class RefreshScoringGuideCache
{
/**
* Create the event listener.
*/
public function __construct()
{
//
}
/**
* Handle the event.
*/
public function handle(ScoringGuideChange $event): void
{
ScoringGuideCacheService::refreshCache();
}
}
//TODO To give your application a speed boost, you should cache a manifest of all of your application's listeners using the optimize or event:cache Artisan commands. Typically, this command should be run as part of your application's deployment process. This manifest will be used by the framework to speed up the event registration process. The event:clear command may be used to destroy the event cache.

View File

@ -2,6 +2,9 @@
namespace App\Providers; namespace App\Providers;
use App\Events\ScoringGuideChange;
use App\Listeners\RefreshScoringGuideCache;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider class AppServiceProvider extends ServiceProvider
@ -19,6 +22,9 @@ class AppServiceProvider extends ServiceProvider
*/ */
public function boot(): void public function boot(): void
{ {
// Event::listen(
ScoringGuideChange::class,
RefreshScoringGuideCache::class
);
} }
} }

View File

@ -0,0 +1,31 @@
<?php
namespace App\Services;
use App\Models\ScoringGuide;
use Illuminate\Support\Facades\Cache;
class ScoringGuideCacheService
{
protected $cacheKey = 'scoring_guides';
/**
* Create a new class instance.
*/
public function __construct()
{
//
}
public function getScoringGuides()
{
return Cache::rememberForever($this->cacheKey, function () {
return ScoringGuide::with('subscores')->get();
});
}
public function refreshCache()
{
Cache::forget($this->cacheKey);
$this->getScoringGuides();
}
}

View File

@ -46,7 +46,7 @@
</div> </div>
@endforeach @endforeach
@if(! $score->isValid()) @if(! $score->isValid())
<form method="POST" action="/admin/scores/{{ $score->id }}"> <form method="POST" action="{{ route('scores.destroy',['score'=>$score->id]) }}">
@csrf @csrf
@method('DELETE') @method('DELETE')
<x-slot:right_link_button class="bg-red-500 text-white">INVALID SCORE - DELETE</x-slot:right_link_button> <x-slot:right_link_button class="bg-red-500 text-white">INVALID SCORE - DELETE</x-slot:right_link_button>

View File

@ -20,7 +20,7 @@
--> -->
<div class="absolute left-1/2 z-10 mt-5 flex w-screen max-w-min -translate-x-1/2 px-4" x-show="open" x-cloak> <div class="absolute left-1/2 z-10 mt-5 flex w-screen max-w-min -translate-x-1/2 px-4" x-show="open" x-cloak>
<div class="w-56 shrink rounded-xl bg-white p-4 text-sm font-semibold leading-6 text-gray-900 shadow-lg ring-1 ring-gray-900/5"> <div class="w-56 shrink rounded-xl bg-white p-4 text-sm font-semibold leading-6 text-gray-900 shadow-lg ring-1 ring-gray-900/5">
<a href="/tabulation/enter_scores" class="block p-2 hover:text-indigo-600">Enter Scores</a> <a href="{{ route('scores.chooseEntry') }}" class="block p-2 hover:text-indigo-600">Enter Scores</a>
<a href="/tabulation/status" class="block p-2 hover:text-indigo-600">Audition Status</a> <a href="/tabulation/status" class="block p-2 hover:text-indigo-600">Audition Status</a>
</div> </div>

View File

@ -3,12 +3,12 @@
<x-card.card class="mx-auto max-w-sm"> <x-card.card class="mx-auto max-w-sm">
<x-card.heading>Choose Entry</x-card.heading> <x-card.heading>Choose Entry</x-card.heading>
<div class=""> <div class="">
<x-form.form method="GET" action="/tabulation/entries/" class="mb-4 mt-3"> <x-form.form method="GET" action="{{ route('scores.entryScoreSheet') }}" class="mb-4 mt-3">
<x-form.field name="entry_id" label_text="Entry ID"></x-form.field> <x-form.field name="entry_id" label_text="Entry ID"></x-form.field>
</x-form.form> <x-form.footer >
<x-form.footer class="px-4 sm:px-8 pb-4">
<x-form.button>Select</x-form.button> <x-form.button>Select</x-form.button>
</x-form.footer> </x-form.footer>
</x-form.form>
</div> </div>
</x-card.card> </x-card.card>

View File

@ -11,7 +11,7 @@
</x-slot:right_side> </x-slot:right_side>
</x-card.heading> </x-card.heading>
<x-form.form method="POST" action="/tabulation/entries/{{ $entry->id }}"> <x-form.form method="POST" action="{{ route('scores.saveEntryScoreSheet',[ 'entry' => $entry->id]) }}">
<x-table.table> <x-table.table>
<thead> <thead>
<tr> <tr>

View File

@ -27,14 +27,19 @@ Route::middleware(['auth','verified',CheckIfCanJudge::class])->prefix('judging')
Route::post('/entry/{entry}','saveScoreSheet'); Route::post('/entry/{entry}','saveScoreSheet');
}); });
// Score Tabulation Routes // Tabulation Routes
Route::middleware(['auth','verified',CheckIfCanTab::class])->prefix('tabulation/')->group(function() { Route::middleware(['auth','verified',CheckIfCanTab::class])->group(function() {
// Score Management
Route::prefix('scores/')->controller(\App\Http\Controllers\Tabulation\ScoreController::class)->group(function() {
Route::get('/choose_entry','chooseEntry')->name('scores.chooseEntry');
Route::get('/entry','entryScoreSheet')->name('scores.entryScoreSheet');
Route::post('/entry/{entry}','saveEntryScoreSheet')->name('scores.saveEntryScoreSheet');
Route::delete('/{score}',[\App\Http\Controllers\Tabulation\ScoreController::class,'destroyScore'])->name('scores.destroy');
});
// Generic Tabulation Routes // Generic Tabulation Routes
Route::controller(\App\Http\Controllers\Tabulation\TabulationController::class)->group(function() { Route::prefix('tabulation/')->controller(\App\Http\Controllers\Tabulation\TabulationController::class)->group(function() {
Route::get('/enter_scores','chooseEntry')->name('tabulation.chooseEntry');
Route::get('/record_noshow','chooseEntry');
Route::get('/entries','entryScoreSheet');
Route::post('/entries/{entry}','saveEntryScoreSheet');
Route::get('/status','status'); Route::get('/status','status');
Route::get('/auditions/{audition}','auditionSeating'); Route::get('/auditions/{audition}','auditionSeating');
}); });
@ -45,7 +50,8 @@ Route::middleware(['auth','verified',CheckIfCanTab::class])->prefix('tabulation/
// Admin Routes // Admin Routes
Route::middleware(['auth','verified',CheckIfAdmin::class])->prefix('admin/')->group(function() { Route::middleware(['auth','verified',CheckIfAdmin::class])->prefix('admin/')->group(function() {
Route::view('/','admin.dashboard'); Route::view('/','admin.dashboard');
Route::delete('/scores/{score}',[TabulationController::class,'destroyScore']);
Route::post('/auditions/roomUpdate',[\App\Http\Controllers\Admin\AuditionController::class,'roomUpdate']); // Endpoint for JS assigning auditions to rooms Route::post('/auditions/roomUpdate',[\App\Http\Controllers\Admin\AuditionController::class,'roomUpdate']); // Endpoint for JS assigning auditions to rooms
Route::post('/scoring/assign_guide_to_audition',[\App\Http\Controllers\Admin\AuditionController::class,'scoringGuideUpdate']); // Endpoint for JS assigning scoring guides to auditions Route::post('/scoring/assign_guide_to_audition',[\App\Http\Controllers\Admin\AuditionController::class,'scoringGuideUpdate']); // Endpoint for JS assigning scoring guides to auditions