Skip to content

Commit ebc7190

Browse files
committed
feat: create driver availability management
- Implement backend logic for getting instructor availability as driver and create pending events based on this availability. - Add status enum - migration with status column
1 parent 3a8f006 commit ebc7190

11 files changed

+206
-19
lines changed

app/Enums/StatusEnum.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?php
2+
3+
namespace App\Enums;
4+
5+
enum StatusEnum: string
6+
{
7+
case AVAILABLE = 'available';
8+
case BOOKED = 'booked';
9+
10+
public static function values(): array
11+
{
12+
return array_column(self::cases(), 'value');
13+
}
14+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace App\Http\Controllers;
4+
5+
use App\Http\Resources\InstructorAvailabilityCollection;
6+
use App\Services\InstructorAvailabilityService;
7+
use Illuminate\Http\Request;
8+
9+
class DriverAvailabilityController extends Controller
10+
{
11+
public function __construct(protected InstructorAvailabilityService $availabilityService)
12+
{
13+
}
14+
15+
public function index(Request $request)
16+
{
17+
$driver = $request->user();
18+
$instructorId = $driver->instructor_id;
19+
20+
return new InstructorAvailabilityCollection($this->availabilityService->getInstructorAvailabilities($instructorId));
21+
}
22+
23+
}

app/Http/Controllers/DriverEventController.php

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,35 @@
22

33
namespace App\Http\Controllers;
44

5+
use App\Http\Requests\StoreDriverEventRequest;
56
use App\Http\Resources\DriverEventResource;
7+
use App\Services\DriverEventService;
68
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
79
use Illuminate\Http\Request;
810

911
class DriverEventController extends Controller
1012
{
1113
use AuthorizesRequests;
1214

15+
public function __construct(protected DriverEventService $driverEventService)
16+
{
17+
}
18+
19+
1320
public function index(Request $request)
1421
{
1522
$driver = $request->user();
1623
$events = $driver->events;
1724

1825
return DriverEventResource::collection($events);
1926
}
27+
28+
public function store(StoreDriverEventRequest $request)
29+
{
30+
$driver = $request->user();
31+
$eventData = $request->validated();
32+
33+
return new DriverEventResource($this->driverEventService->createEvent($driver, $eventData));
34+
}
35+
2036
}

app/Http/Controllers/InstructorAvailabilityController.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
class InstructorAvailabilityController extends Controller
1414
{
15-
public function __construct(protected InstructorAvailabilityService $availabilityService) {}
15+
public function __construct(protected InstructorAvailabilityService $availabilityService)
16+
{
17+
}
1618

1719
public function index(Request $request): InstructorAvailabilityCollection
1820
{
@@ -28,10 +30,8 @@ public function index(Request $request): InstructorAvailabilityCollection
2830
public function store(StoreInstructorAvailabilityRequest $request): InstructorStoreAvailabilityResource
2931
{
3032
$validated = $request->validated();
31-
$instructor = $request->user();
3233

3334
return new InstructorStoreAvailabilityResource($this->availabilityService->storeAvailability(
34-
$instructor,
3535
$validated['availability']
3636
));
3737
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
namespace App\Http\Requests;
4+
5+
use App\Rules\AvailableTimeSlot;
6+
use Illuminate\Foundation\Http\FormRequest;
7+
8+
class StoreDriverEventRequest extends FormRequest
9+
{
10+
public function authorize(): bool
11+
{
12+
return true;
13+
}
14+
15+
public function rules(): array
16+
{
17+
$instructorId = $this->user()->instructor_id;
18+
19+
return [
20+
'start' => ['required', 'date', 'after_or_equal:now', new AvailableTimeSlot($instructorId)],
21+
'end' => 'required|date|after:start',
22+
'title' => 'required|string|max:255'
23+
];
24+
}
25+
}
26+

app/Models/InstructorAvailability.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
class InstructorAvailability extends Model
99
{
10-
protected $fillable = ['start_time', 'end_time', 'instructor_id'];
10+
protected $fillable = ['instructor_id', 'start_time', 'end_time', 'status'];
1111

1212
protected $casts = [
1313
'start_time' => 'datetime',

app/Rules/AvailableTimeSlot.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
namespace App\Rules;
4+
5+
use App\Models\InstructorAvailability;
6+
use Closure;
7+
use Illuminate\Contracts\Validation\ValidationRule;
8+
9+
class AvailableTimeSlot implements ValidationRule
10+
{
11+
protected $instructorId;
12+
13+
// Inject instructorId through the constructor
14+
public function __construct($instructorId)
15+
{
16+
$this->instructorId = $instructorId;
17+
}
18+
19+
/**
20+
* Run the validation rule.
21+
*
22+
* @param string $attribute
23+
* @param mixed $value
24+
* @param Closure(string): void $fail
25+
*/
26+
public function validate(string $attribute, mixed $value, Closure $fail): void
27+
{
28+
$endTime = request('end');
29+
30+
$exists = InstructorAvailability::where('instructor_id', $this->instructorId)
31+
->where('start_time', '<=', $value)
32+
->where('end_time', '>=', $endTime)
33+
->exists();
34+
35+
if (!$exists) {
36+
$fail('The selected time slot is not available.');
37+
}
38+
}
39+
}

app/Services/DriverEventService.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
<?php
2+
3+
namespace App\Services;
4+
5+
use App\Enums\EventsEnum;
6+
use App\Enums\StatusEnum;
7+
use App\Models\Event;
8+
use App\Models\InstructorAvailability;
9+
use App\Models\User;
10+
use Illuminate\Support\Facades\DB;
11+
12+
class DriverEventService
13+
{
14+
public function createEvent(User $driver, array $data): Event
15+
{
16+
return DB::transaction(function () use ($driver, $data) {
17+
InstructorAvailability::where('status', StatusEnum::AVAILABLE->value)
18+
->where('instructor_id', $driver->instructor_id)
19+
->where('start_time', '<=', $data['start'])
20+
->where('end_time', '>=', $data['end'])
21+
->lockForUpdate()
22+
->firstOrFail()
23+
->update(['status' => StatusEnum::BOOKED->value]);
24+
25+
return Event::create([
26+
...$data,
27+
'instructor_id' => $driver->instructor_id,
28+
'driver_id' => $driver->id,
29+
'status' => EventsEnum::PENDING->value,
30+
]);
31+
});
32+
}
33+
34+
}

app/Services/InstructorAvailabilityService.php

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
namespace App\Services;
44

5+
use App\Enums\StatusEnum;
56
use App\Models\InstructorAvailability;
6-
use App\Models\User;
77
use Exception;
88
use Illuminate\Support\Facades\DB;
99

@@ -12,35 +12,37 @@ class InstructorAvailabilityService
1212
/**
1313
* @throws Exception
1414
*/
15-
public function storeAvailability(User $instructor, array $availabilities): array
15+
public function storeAvailability(array $availabilities): array
1616
{
17-
$savedSlots = [];
1817

19-
DB::beginTransaction();
20-
21-
try {
22-
InstructorAvailability::where('instructor_id', $instructor->id)->delete(); //delete old availability
18+
return DB::transaction(function () use ($availabilities) {
19+
$savedSlots = [];
20+
InstructorAvailability::where('instructor_id', auth()->id())->where(StatusEnum::AVAILABLE->value)->delete(); //delete old availability
2321
foreach ($availabilities as $slot) {
2422
$start = $slot['start_time'];
2523
$end = $slot['end_time'];
2624

2725
$savedSlots[] = InstructorAvailability::create([
28-
'instructor_id' => $instructor->id,
26+
'instructor_id' => auth()->id(),
2927
'start_time' => $start,
3028
'end_time' => $end,
29+
'status' => StatusEnum::AVAILABLE->value
3130
]);
3231
}
3332

34-
DB::commit();
35-
3633
return [
3734
'message' => 'Availability processed.',
3835
'saved' => $savedSlots,
3936
];
37+
});
38+
}
4039

41-
} catch (Exception $e) {
42-
DB::rollBack();
43-
throw $e;
44-
}
40+
public function getInstructorAvailabilities($instructorId)
41+
{
42+
return InstructorAvailability::where('instructor_id', $instructorId)
43+
->where('status', StatusEnum::AVAILABLE->value)
44+
->where('end_time', '>', now())
45+
->orderBy('start_time')
46+
->get();
4547
}
4648
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
use App\Enums\StatusEnum;
4+
use Illuminate\Database\Migrations\Migration;
5+
use Illuminate\Database\Schema\Blueprint;
6+
use Illuminate\Support\Facades\Schema;
7+
8+
return new class extends Migration {
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::table('instructor_availabilities', function (Blueprint $table) {
15+
$table->enum('status', [StatusEnum::AVAILABLE->value, StatusEnum::BOOKED->value])
16+
->default(StatusEnum::AVAILABLE->value)
17+
->after('instructor_id')
18+
->index();
19+
});
20+
}
21+
22+
/**
23+
* Reverse the migrations.
24+
*/
25+
public function down(): void
26+
{
27+
Schema::table('instructor_availabilities', function (Blueprint $table) {
28+
$table->dropColumn('status');
29+
});
30+
}
31+
};

0 commit comments

Comments
 (0)