From b876ea93aaecc0db013068b03fc3a4965435c03a Mon Sep 17 00:00:00 2001 From: Rodrigo Pizarro <11396307+BaaltRodrigo@users.noreply.github.com> Date: Mon, 23 Oct 2023 02:16:40 -0300 Subject: [PATCH] feat: academic charge files can be uploaded (#15) --- app/Actions/HandleAcademicChargeFileLoad.php | 21 ++++++++++++++----- .../v1/AcademicChargeController.php | 11 +++++++--- .../Requests/StoreAcademicChargeRequest.php | 5 +---- .../Identifiers/AcademicChargeIdentifier.php | 1 + tests/Feature/AcademicChargeTest.php | 18 ++++++++++++---- 5 files changed, 40 insertions(+), 16 deletions(-) diff --git a/app/Actions/HandleAcademicChargeFileLoad.php b/app/Actions/HandleAcademicChargeFileLoad.php index 8083a70..4eea4d7 100644 --- a/app/Actions/HandleAcademicChargeFileLoad.php +++ b/app/Actions/HandleAcademicChargeFileLoad.php @@ -13,7 +13,7 @@ class HandleAcademicChargeFileLoad { - public function execute(AcademicCharge $charge, array $fileLines) + public static function execute($validated): AcademicCharge { // Only for log purposes $counters = [ @@ -25,12 +25,23 @@ public function execute(AcademicCharge $charge, array $fileLines) 'duplicate schedules' => 0, ]; - // map the headers to line numbers - $headers = array_shift($fileLines); + // Read the file uploaded + $csvData = array_map('str_getcsv', file($validated['file'])); + $headers = array_shift($csvData); + // Replace the first element special character \u{FEFF} (zero width no-break space) + $headers[0] = ltrim($headers[0], "\u{FEFF}"); + //Map the headers to line numbers $headers = array_flip($headers); + // Create the academic charge + $charge = AcademicCharge::create([ + 'year' => $csvData[1][$headers['Año']], + 'semester' => $csvData[1][$headers['Período']], + 'name' => $csvData[1][$headers['Sede']], + ]); + // The following lines will create every necessary model for the academic charge - foreach ($fileLines as $line) { + foreach ($csvData as $line) { // Create or get the Career $career = Career::firstOrCreate([ 'name' => Str::slug($line[$headers['Carrera']]) @@ -86,7 +97,7 @@ public function execute(AcademicCharge $charge, array $fileLines) } } - info('Lines inside file: ' . count($fileLines)); + info('Lines inside file: ' . count($csvData)); info('Careers created: ' . $counters['careers']); info('Schools created: ' . $counters['schools']); info('Subjects created: ' . $counters['subjects']); diff --git a/app/Http/Controllers/v1/AcademicChargeController.php b/app/Http/Controllers/v1/AcademicChargeController.php index 47bde1a..573bb4b 100644 --- a/app/Http/Controllers/v1/AcademicChargeController.php +++ b/app/Http/Controllers/v1/AcademicChargeController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\v1; +use App\Actions\HandleAcademicChargeFileLoad; use App\Http\Controllers\Controller; use App\Http\Requests\ListSectionRequest; use App\Http\Requests\StoreAcademicChargeRequest as StoreRequest; @@ -11,6 +12,7 @@ use App\Http\Resources\Collections\AcademicChargeCollection; use App\Http\Resources\Collections\CareerCollection; use App\Http\Resources\Collections\SchoolCollection; +use App\Http\Resources\Identifiers\AcademicChargeIdentifier; use App\Models\AcademicCharge; use Illuminate\Http\Request; @@ -40,12 +42,15 @@ public function show(AcademicCharge $charge): AcademicChargeResource return new AcademicChargeResource($charge); } - public function store(StoreRequest $request): AcademicChargeResource + public function store(StoreRequest $request): AcademicChargeIdentifier { $validated = $request->validated(); - $academicCharge = AcademicCharge::create($validated); - return new AcademicChargeResource($academicCharge); + // TODO: Optimize this to avoid the creation of the same academic charge + // TODO: Move this to an job or something to avoid frontend waiting for the response + $charge = HandleAcademicChargeFileLoad::execute($validated); + + return new AcademicChargeIdentifier($charge); } public function update(UpdateRequest $request, AcademicCharge $charge): AcademicChargeResource diff --git a/app/Http/Requests/StoreAcademicChargeRequest.php b/app/Http/Requests/StoreAcademicChargeRequest.php index 956e59f..b792abc 100644 --- a/app/Http/Requests/StoreAcademicChargeRequest.php +++ b/app/Http/Requests/StoreAcademicChargeRequest.php @@ -25,10 +25,7 @@ public function authorize(): Response public function rules(): array { return [ - 'name' => ['required', 'string'], - 'year' => ['required', 'integer'], - 'semester' => ['required', 'string'], - 'is_hidden' => ['sometimes', 'boolean'], + 'file' => ['required', 'file', 'mimes:csv,txt'] ]; } } diff --git a/app/Http/Resources/Identifiers/AcademicChargeIdentifier.php b/app/Http/Resources/Identifiers/AcademicChargeIdentifier.php index 6b8e974..4548d33 100644 --- a/app/Http/Resources/Identifiers/AcademicChargeIdentifier.php +++ b/app/Http/Resources/Identifiers/AcademicChargeIdentifier.php @@ -18,6 +18,7 @@ public function toArray(Request $request): array 'id' => $this->id, 'name' => str_replace('-', ' ', $this->name), 'season' => "{$this->year}-{$this->semester}", + 'is_hidden' => $this->is_hidden, 'url' => route('academic-charges.show', $this->id), ]; } diff --git a/tests/Feature/AcademicChargeTest.php b/tests/Feature/AcademicChargeTest.php index b5d0c3a..19b8b42 100644 --- a/tests/Feature/AcademicChargeTest.php +++ b/tests/Feature/AcademicChargeTest.php @@ -5,12 +5,15 @@ use App\Models\AcademicCharge; use Illuminate\Foundation\Testing\RefreshDatabase; use Illuminate\Foundation\Testing\WithFaker; +use Illuminate\Http\UploadedFile; use Tests\TestCase; use Tests\Traits\UseRolesTable; use Tests\Traits\UseFirebaseUser; class AcademicChargeTest extends TestCase { + private string $CSV_PATH = 'database/data/SAN-JOAQUIN 2023-1.csv'; + use RefreshDatabase, UseRolesTable, UseFirebaseUser; public function test_index_route_is_public(): void @@ -42,16 +45,21 @@ public function test_it_shows_available_charges_by_default(): void $response->assertJsonFragment(['id' => $availableCharge->id]); } - public function test_it_allows_admin_to_create_academic_charges(): void + public function test_it_allows_admin_to_upload_academic_charge_file(): void { - $academicCharge = AcademicCharge::factory()->make(); $user = $this->createUserWithRoles(['duoc']); $this->actingAsFirebaseUser(); - $response = $this->postJson(route('academic-charges.store'), $academicCharge->toArray()); + // get a file from database/data folder + $file = new UploadedFile($this->CSV_PATH, 'SAN-JOAQUIN 2023-1.csv', 'text/csv', null, true); + + $response = $this->postJson( + route('academic-charges.store'), + ['file' => $file], + // ['Content-Type' => 'multipart/form-data'] + ); $response->assertStatus(201); - $this->assertDatabaseHas('academic_charges', $academicCharge->toArray()); } public function test_it_allows_admin_to_update_academic_charges(): void @@ -123,4 +131,6 @@ public function test_it_denies_common_users_to_delete_academic_charges(): void $response->assertStatus(403); } + + }