Skip to content

Commit b881e7f

Browse files
committed
feat: create instructor availability management
Implement backend logic for getting and storing instructor availability. Each new submission replaces old availability to align with frontend design, where users set availability for the current and next week in bulk. Separate POST and UPDATE endpoints seems unnecessary.
1 parent 6175632 commit b881e7f

9 files changed

+219
-0
lines changed
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Http\Requests\StoreInstructorAvailabilityRequest;
6+
use App\Http\Resources\InstructorAvailabilityCollection;
7+
use App\Http\Resources\InstructorStoreAvailabilityResource;
8+
use App\Models\InstructorAvailability;
9+
use App\Services\InstructorAvailabilityService;
10+
use Exception;
11+
use Illuminate\Http\Request;
12+
13+
class InstructorAvailabilityController extends Controller
14+
{
15+
public function __construct(protected InstructorAvailabilityService $availabilityService) {}
16+
17+
public function index(Request $request): InstructorAvailabilityCollection
18+
{
19+
$instructorId = $request->user()->id;
20+
$availabilities = InstructorAvailability::where('instructor_id', $instructorId)->get();
21+
22+
return new InstructorAvailabilityCollection($availabilities);
23+
}
24+
25+
/**
26+
* @throws Exception
27+
*/
28+
public function store(StoreInstructorAvailabilityRequest $request): InstructorStoreAvailabilityResource
29+
{
30+
$validated = $request->validated();
31+
$instructor = $request->user();
32+
33+
return new InstructorStoreAvailabilityResource($this->availabilityService->storeAvailability(
34+
$instructor,
35+
$validated['availability']
36+
));
37+
}
38+
39+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
namespace App\Http\Requests;
4+
5+
use Illuminate\Foundation\Http\FormRequest;
6+
7+
class StoreInstructorAvailabilityRequest extends FormRequest
8+
{
9+
/**
10+
* Determine if the user is authorized to make this request.
11+
*/
12+
public function authorize(): bool
13+
{
14+
return true;
15+
}
16+
17+
public function rules(): array
18+
{
19+
return [
20+
'availability' => 'required|array|min:1', //min 1 time slot
21+
'availability.*.start_time' => 'required|date|after:now',
22+
'availability.*.end_time' => 'required|date|after:availability.*.start_time',
23+
];
24+
}
25+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
namespace App\Http\Resources;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Http\Resources\Json\ResourceCollection;
7+
8+
class InstructorAvailabilityCollection extends ResourceCollection
9+
{
10+
public function toArray(Request $request): array
11+
{
12+
return [
13+
'currentWeek' => InstructorAvailabilityResource::collection(
14+
$this->collection->filter(fn ($item) => $item->start_time->isCurrentWeek())
15+
),
16+
'nextWeek' => InstructorAvailabilityResource::collection(
17+
$this->collection->filter(fn ($item) => $item->start_time->isNextWeek())
18+
),
19+
];
20+
}
21+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace App\Http\Resources;
4+
5+
use Illuminate\Http\Request;
6+
use Illuminate\Http\Resources\Json\JsonResource;
7+
8+
class InstructorAvailabilityResource extends JsonResource
9+
{
10+
public function toArray(Request $request): array
11+
{
12+
return [
13+
'start_time' => $this->start_time->toIso8601String(),
14+
'end_time' => $this->end_time->toIso8601String(),
15+
];
16+
}
17+
18+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
namespace App\Http\Resources;
4+
5+
use Illuminate\Http\Resources\Json\JsonResource;
6+
7+
class InstructorStoreAvailabilityResource extends JsonResource
8+
{
9+
public function toArray($request): array
10+
{
11+
return [
12+
'message' => 'Availability processed.',
13+
'saved' => $this->resource['saved'],
14+
];
15+
}
16+
}

app/Models/InstructorAvailability.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace App\Models;
4+
5+
use Illuminate\Database\Eloquent\Model;
6+
use Illuminate\Database\Eloquent\Relations\BelongsTo;
7+
8+
class InstructorAvailability extends Model
9+
{
10+
protected $fillable = ['start_time', 'end_time', 'instructor_id'];
11+
12+
protected $casts = [
13+
'start_time' => 'datetime',
14+
'end_time' => 'datetime',
15+
];
16+
17+
18+
public function instructor(): BelongsTo
19+
{
20+
return $this->belongsTo(User::class, 'instructor_id');
21+
}
22+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
use App\Models\InstructorAvailability;
6+
use App\Models\User;
7+
use Exception;
8+
use Illuminate\Support\Facades\DB;
9+
10+
class InstructorAvailabilityService
11+
{
12+
/**
13+
* @throws Exception
14+
*/
15+
public function storeAvailability(User $instructor, array $availabilities): array
16+
{
17+
$savedSlots = [];
18+
19+
DB::beginTransaction();
20+
21+
try {
22+
InstructorAvailability::where('instructor_id', $instructor->id)->delete(); //delete old availability
23+
foreach ($availabilities as $slot) {
24+
$start = $slot['start_time'];
25+
$end = $slot['end_time'];
26+
27+
$savedSlots[] = InstructorAvailability::create([
28+
'instructor_id' => $instructor->id,
29+
'start_time' => $start,
30+
'end_time' => $end,
31+
]);
32+
}
33+
34+
DB::commit();
35+
36+
return [
37+
'message' => 'Availability processed.',
38+
'saved' => $savedSlots,
39+
];
40+
41+
} catch (Exception $e) {
42+
DB::rollBack();
43+
throw $e;
44+
}
45+
}
46+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::create('instructor_availabilities', function (Blueprint $table) {
15+
$table->id();
16+
$table->foreignId('instructor_id')->constrained('users')->onDelete('cascade');
17+
$table->dateTime('start_time');
18+
$table->dateTime('end_time');
19+
$table->timestamps();
20+
});
21+
}
22+
23+
/**
24+
* Reverse the migrations.
25+
*/
26+
public function down(): void
27+
{
28+
Schema::dropIfExists('instructor_availabilities');
29+
}
30+
};

routes/api/instructor.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php
22

33
use App\Enums\RolesEnum;
4+
use App\Http\Controllers\InstructorAvailabilityController;
45
use App\Http\Controllers\InstructorDriverController;
56
use App\Http\Controllers\InstructorEventController;
67
use Illuminate\Support\Facades\Route;
@@ -10,5 +11,6 @@
1011
//define instructor routes
1112
Route::apiResource('events', InstructorEventController::class);
1213
Route::apiResource('drivers', InstructorDriverController::class)->only(['index']);
14+
Route::apiResource('availability', InstructorAvailabilityController::class)->only(['index', 'store']);
1315

1416
});

0 commit comments

Comments
 (0)