When a user creates a school, make them the head

Work on #64
This commit is contained in:
Matt Young 2024-08-10 18:09:32 -05:00
parent 5862d05f35
commit df9b64a4e2
7 changed files with 136 additions and 10 deletions

View File

@ -0,0 +1,40 @@
<?php
namespace App\Actions\Schools;
use App\Exceptions\AuditionAdminException;
use App\Models\School;
use App\Models\User;
use function auditionLog;
use function is_null;
class SetHeadDirector
{
public function __construct()
{
}
public function __invoke(User $user, School $school): void
{
$this->setHeadDirector($user, $school);
}
/**
* @throws AuditionAdminException
*/
public function setHeadDirector(User $user): void
{
if (is_null($user->school_id)) {
throw new AuditionAdminException('User is not associated with a school');
}
foreach ($user->school->directors as $director) {
$director->removeFlag('head_director');
}
$user->addFlag('head_director');
$logMessage = 'Set '.$user->full_name().' as head director at '.$user->school->name;
$logAffected = ['users' => [$user->id], 'schools' => [$user->school_id]];
auditionLog($logMessage, $logAffected);
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\Exceptions;
use Exception;
class AuditionAdminException extends Exception
{
//
}

View File

@ -2,6 +2,8 @@
namespace App\Http\Controllers;
use App\Actions\Schools\SetHeadDirector;
use App\Exceptions\AuditionAdminException;
use App\Models\AuditLogEntry;
use App\Models\School;
use App\Models\SchoolEmailDomain;
@ -15,7 +17,7 @@ use function request;
class SchoolController extends Controller
{
public function store(Request $request): RedirectResponse
public function store(Request $request, SetHeadDirector $headSetter): RedirectResponse
{
if ($request->user()->cannot('create', School::class)) {
abort(403);
@ -70,14 +72,12 @@ class SchoolController extends Controller
'schools' => [$school->id],
],
]);
auth()->user()->addFlag('head_director'); // If user is creating a school, they are initially the head
AuditLogEntry::create([
'user' => auth()->user()->email,
'ip_address' => request()->ip(),
'message' => 'Marked '.auth()->user()->full_name().' as head director for '.$school->name,
'affected' => ['schools' => [$school->id], 'users' => [auth()->user()->id]],
]);
auth()->user()->refresh();
try {
$headSetter->setHeadDirector(auth()->user());
} catch (AuditionAdminException $e) {
redirect(route('schools.show', $school))->with('error', 'Could not set as head director');
}
}

View File

@ -187,6 +187,13 @@ class User extends Authenticatable implements MustVerifyEmail
$this->load('flags');
}
public function removeFlag($flag): void
{
// remove related userFlag where flag_name = $flag
$this->flags()->where('flag_name', $flag)->delete();
$this->load('flags');
}
public function scoresForEntry($entry)
{
return $this->scoreSheets->where('entry_id', '=', $entry)->first()?->subscores;

View File

@ -2,6 +2,9 @@
namespace App\Providers;
use App\Actions\Entries\CreateEntry;
use App\Actions\Entries\UpdateEntry;
use App\Actions\Schools\SetHeadDirector;
use App\Actions\Tabulation\AllowForOlympicScoring;
use App\Actions\Tabulation\CalculateEntryScore;
use App\Actions\Tabulation\CalculateScoreSheetTotal;
@ -32,7 +35,6 @@ use App\Services\DoublerService;
use App\Services\DrawService;
use App\Services\EntryService;
use App\Services\ScoreService;
use App\Services\StudentService;
use App\Services\UserService;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\ServiceProvider;
@ -52,6 +54,10 @@ class AppServiceProvider extends ServiceProvider
$this->app->singleton(ScoreService::class, ScoreService::class);
$this->app->singleton(UserService::class, UserService::class);
$this->app->singleton(DoublerService::class, DoublerService::class);
$this->app->singleton(CreateEntry::class, CreateEntry::class);
$this->app->singleton(UpdateEntry::class, UpdateEntry::class);
$this->app->singleton(SetHeadDirector::class, SetHeadDirector::class);
}
/**

View File

@ -2,6 +2,7 @@
use App\Actions\Tabulation\EnterScore;
use App\Exceptions\ScoreEntryException;
use App\Models\AuditLogEntry;
use App\Models\Entry;
use App\Models\User;
use App\Settings;
@ -35,11 +36,22 @@ function auditionSetting($key)
return Settings::get($key);
}
function auditionLog(string $message, array $affected)
{
AuditLogEntry::create([
'user' => auth()->user()->email ?? 'no user',
'ip_address' => request()->ip(),
'message' => $message,
'affected' => $affected,
]);
}
/**
* @throws ScoreEntryException
*/
function enterScore(User $user, Entry $entry, array $scores): \App\Models\ScoreSheet
{
$scoreEntry = App::make(EnterScore::class);
return $scoreEntry($user, $entry, $scores);
}

View File

@ -0,0 +1,51 @@
<?php
use App\Actions\Schools\SetHeadDirector;
use App\Exceptions\AuditionAdminException;
use App\Models\School;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->setter = app(SetHeadDirector::class);
});
it('sets a head director flag for a user with a school', function () {
// Arrange
$school = School::factory()->create();
$user = User::factory()->create(['school_id' => $school->id]);
$this->setter->setHeadDirector($user);
$this->assertDatabaseHas('user_flags', [
'user_id' => $user->id,
'flag_name' => 'head_director',
]);
});
it('throws an error if the user has no school', function () {
// Arrange
$user = User::factory()->create();
// Act & Assert
$this->setter->setHeadDirector($user);
})->throws(AuditionAdminException::class, 'User is not associated with a school');
it('removes the head director flag from any other users as the school', function () {
// Arrange
$school = School::factory()->create();
$oldHead = User::factory()->create(['school_id' => $school->id]);
$newHead = User::factory()->create(['school_id' => $school->id]);
$oldHead->addFlag('head_director');
$this->assertDatabaseHas('user_flags', [
'user_id' => $oldHead->id,
'flag_name' => 'head_director',
]);
// Act
$this->setter->setHeadDirector($newHead);
$this->assertDatabaseHas('user_flags', [
'user_id' => $newHead->id,
'flag_name' => 'head_director',
]);
$this->assertDatabaseMissing('user_flags', [
'user_id' => $oldHead->id,
'flag_name' => 'head_director',
]);
});