diff --git a/app/Http/Controllers/BikeController.php b/app/Http/Controllers/BikeController.php new file mode 100644 index 0000000000000000000000000000000000000000..1baf9862bfffe2c5685a21302b16083fdec473cf --- /dev/null +++ b/app/Http/Controllers/BikeController.php @@ -0,0 +1,17 @@ +<?php + +namespace App\Http\Controllers; + +use App\Models\Bike; +use Illuminate\Http\Request; + +class BikeController extends Controller +{ + public function unlockBike(Bike $bike){ + return true; + } + + public function reserveBike(Bike $bike){ + return true; + } +} diff --git a/app/Http/Controllers/ElectricBikeController.php b/app/Http/Controllers/ElectricBikeController.php new file mode 100644 index 0000000000000000000000000000000000000000..cc434340db9af7627ecbc476cce0273dfb2ae7c0 --- /dev/null +++ b/app/Http/Controllers/ElectricBikeController.php @@ -0,0 +1,10 @@ +<?php + +namespace App\Http\Controllers; + +use Illuminate\Http\Request; + +class ElectricBikeController extends Controller +{ + // +} diff --git a/app/Http/Controllers/MapboxApiClient.php b/app/Http/Controllers/MapboxApiClient.php new file mode 100644 index 0000000000000000000000000000000000000000..a519e37bfb03ade25b0d4cd3aea2dc964f44c73c --- /dev/null +++ b/app/Http/Controllers/MapboxApiClient.php @@ -0,0 +1,24 @@ +<?php + +namespace App\Http\Controllers; + +use App\Models\Stop; +use Illuminate\Support\Facades\Http; + +class MapboxApiClient extends Http +{ + private String $url; + public function __construct(Stop $init, Stop $end) + { + $lng1 = $init->lng; + $lng2 = $end->lng; + $lat1 = $init->lat; + $lat2 = $end->lat; + $coordinates = $lng1.",".$lat1.";".$lng2.",".$lat2; + $token = env('MAPBOX_TOKEN', null); + $this->url = "https://api.mapbox.com/directions/v5/mapbox/cycling/${coordinates}?alternatives=false&geometries=geojson&overview=full&steps=false&access_token=".$token; + } + public function buildRequest(){ + return $this->url; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/ReservationController.php b/app/Http/Controllers/ReservationController.php new file mode 100644 index 0000000000000000000000000000000000000000..f7b61325184be61949f2f4648b0a9f98b37dca86 --- /dev/null +++ b/app/Http/Controllers/ReservationController.php @@ -0,0 +1,10 @@ +<?php + +namespace App\Http\Controllers; + +use Illuminate\Http\Request; + +class ReservationController extends Controller +{ + // +} diff --git a/app/Http/Controllers/RewardController.php b/app/Http/Controllers/RewardController.php new file mode 100644 index 0000000000000000000000000000000000000000..2385698eb53522d7c3c3921b434671023af9d632 --- /dev/null +++ b/app/Http/Controllers/RewardController.php @@ -0,0 +1,53 @@ +<?php + +namespace App\Http\Controllers; + +use App\Http\Resources\RewardResource; +use App\Http\Resources\RewardResourceObtained; +use App\Models\Reward; +use App\Models\User; +use Illuminate\Http\Request; + +class RewardController extends Controller +{ + public function getAllRewards(){ + return RewardResource::collection(Reward::all()); + } + + public function getNotRedeemedRewards(Request $request){ + $user = UserController::getUserByToken($request); + $userR = $user->rewards()->get(['user_reward.reward_id AS result'])->pluck('result'); + return RewardResource::collection(Reward::all()->whereNotIn('id',$userR)); + } + + public function getUserRewards(Request $request){ + $user = UserController::getUserByToken($request); + return RewardResourceObtained::collection($user->rewards()->orderByPivot('created_at', 'desc')->get()); + } + + public function obtainReward(Request $request, Reward $reward){ + $user = UserController::getUserByToken($request); + if ($this->checkReward($user,$reward)){ + $reward->redeemed += 1; + $reward->users()->attach($user); + $user->points -= $reward->points; + $reward->save(); + $user->save(); + return new RewardResourceObtained($reward); + } + return response()->json("Unable to redeem the reward", 500); + } + + private function checkReward(User $user, Reward $reward){ + if ($reward->total_available <= $reward->redeemed){ + return false; + } + if ($user->rewards()->find($reward)){ + return false; + } + if ($user->points < $reward->points){ + return false; + } + return true; + } +} diff --git a/app/Http/Controllers/RouteController.php b/app/Http/Controllers/RouteController.php new file mode 100644 index 0000000000000000000000000000000000000000..7ac0e9ad6c44053ac631f317647649a411f3272a --- /dev/null +++ b/app/Http/Controllers/RouteController.php @@ -0,0 +1,71 @@ +<?php + +namespace App\Http\Controllers; + +use App\Http\Resources\RouteResource; +use App\Models\Bike; +use App\Models\ElectricBike; +use App\Models\Route; +use App\Models\Stop; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Http; +use PhpParser\Node\Expr\Cast\Int_; + +class RouteController extends Controller +{ + public function createRoute(Request $request, Bike $bike){ + $user = UserController::getUserByToken($request); + $route = new Route; + $route->user_id = $user->id; + $route->initial_stop_id = $bike->stop()->first()->id; + $route->bike_id = $bike->id; + $route->save(); + //Remove any user reservation + $reservation = $user->reservations()->first(); + $reservation?->delete(); + //Free the bike from the Stop + $bike->stop_id = null; + $bike->save(); + + return new RouteResource($route); + } + + public function finishRoute(Request $request){ + $route = Route::find($request->id); + if($request->missing(['duration', 'final_stop'])){ + //TODO manage Errors + return false; + } + + $initial_stop = Stop::find($route->initial_stop_id); + $final_stop = Stop::find($request->final_stop); + $mapboxClient = new MapboxApiClient($initial_stop,$final_stop); + $url = $mapboxClient->buildRequest(); + $response = Http::get($url); + $mapbox_response = $response->json(); + $distance = $response->json('routes')[0]['distance']; + $estimated_duration = $response->json('routes')[0]['duration']; + + $route->final_stop_id = intval($request->final_stop); + $route->duration = intval($request->duration); + $route->mapbox_response = $mapbox_response; + $route->distance = intval($distance); + $route->estimated_duration = intval($estimated_duration); + $route->points = $this->calculatePoints($route); + $route->save(); + + $user = UserController::getUserByToken($request); + $user->points += $route->points; + $user->save(); + + $bike = $route->bike()->first(); + $bike->stop_id = $route->final_stop_id; + $bike->save(); + + return new RouteResource($route); + } + + private function calculatePoints($route): Int{ + return 50; + } +} diff --git a/app/Http/Controllers/StopController.php b/app/Http/Controllers/StopController.php new file mode 100644 index 0000000000000000000000000000000000000000..1d8f149a401a7e40cbd5f31977e192a6599b20a5 --- /dev/null +++ b/app/Http/Controllers/StopController.php @@ -0,0 +1,61 @@ +<?php + +namespace App\Http\Controllers; + +use App\Http\Resources\BikeResource; +use App\Http\Resources\StopResource; +use App\Models\Bike; +use App\Models\ElectricBike; +use App\Models\Reservation; +use App\Models\Stop; +use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; + +class StopController extends Controller +{ + public function getAllStops(){ + return StopResource::collection(Stop::all()); + } + + public function unlockBike(Request $request, Bike $bike){ + $user = UserController::getUserByToken($request); + $stop = $bike->stop()->first(); + $type = $bike->bikeable_type; + $bikes = $stop->bikes()->get()->where('bikeable_type', '=', $type); + $reservation = $user->reservations()->first(); + if ($type=='App\Models\ElectricBike'){ + $typable = 'electric'; + }else{ + $typable = 'pedal'; + } + if (($reservation!=null && $reservation->bike_type==$typable) || $stop->check_availability_bikes($type)){ + return new BikeResource($bikes->first()); + }else{ + return response()->json("No bikes available", 500); + } + } + + public function lockBike(Stop $stop){ + if ($stop->total_spaces>0){ + return new StopResource($stop); + } + else{ + return "No space available"; + } + } + + public function reserveBike(Request $request, Stop $stop, String $type){ + if ($stop->check_availability_bikes($type)){ + $user = UserController::getUserByToken($request); + $reservation = new Reservation(); + $reservation->user_id = $user->id; + $reservation->stop_id = $stop->id; + $reservation->bike_type = $type; + $reservation->save(); + return new StopResource($stop); + } + else{ + return response()->json("No bikes available for reservation", 500); + } + } +} diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php new file mode 100644 index 0000000000000000000000000000000000000000..90d8a6f49dfa494a14bea477d25564758077830e --- /dev/null +++ b/app/Http/Controllers/UserController.php @@ -0,0 +1,16 @@ +<?php + +namespace App\Http\Controllers; + +use App\Models\User; +use Illuminate\Http\Request; +use Laravel\Sanctum\PersonalAccessToken; + +class UserController extends Controller +{ + public static function getUserByToken(Request $request):User|null + { + $token = PersonalAccessToken::findToken($request->bearerToken()); + return ($token != null) ? $token->tokenable : null; + } +} diff --git a/app/Http/Resources/BikeResource.php b/app/Http/Resources/BikeResource.php new file mode 100644 index 0000000000000000000000000000000000000000..ac1068f3bbace1a8409f796fa5a773b0f55d516a --- /dev/null +++ b/app/Http/Resources/BikeResource.php @@ -0,0 +1,34 @@ +<?php + +namespace App\Http\Resources; + +use Illuminate\Http\Resources\Json\JsonResource; + +class BikeResource extends JsonResource +{ + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable + */ + public function toArray($request) + { + if ($this->bikeable_type == "App\\Models\\ElectricBike"){ + $battery = $this->bikeable->battery; + $resource = [ + 'bike_id' => $this->id, + 'unlocked' => $this->unlocked, + 'battery' => $battery + ]; + } + else{ + $resource = [ + 'bike_id' => $this->id, + 'unlocked' => $this->unlocked, + ]; + } + + return $resource; + } +} diff --git a/app/Http/Resources/ElectricBikeResource.php b/app/Http/Resources/ElectricBikeResource.php new file mode 100644 index 0000000000000000000000000000000000000000..56a6caf63e8c71f5fac0e083e0988c71dca22dbb --- /dev/null +++ b/app/Http/Resources/ElectricBikeResource.php @@ -0,0 +1,19 @@ +<?php + +namespace App\Http\Resources; + +use Illuminate\Http\Resources\Json\JsonResource; + +class ElectricBikeResource extends JsonResource +{ + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable + */ + public function toArray($request) + { + return parent::toArray($request); + } +} diff --git a/app/Http/Resources/RewardResource.php b/app/Http/Resources/RewardResource.php new file mode 100644 index 0000000000000000000000000000000000000000..0b5c2eca7b3a447b8826a814dddd498940b919b1 --- /dev/null +++ b/app/Http/Resources/RewardResource.php @@ -0,0 +1,25 @@ +<?php + +namespace App\Http\Resources; + +use Illuminate\Http\Resources\Json\JsonResource; + +class RewardResource extends JsonResource +{ + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable + */ + public function toArray($request) + { + return [ + "id"=> $this->id, + "title"=> $this->name, + "description"=> $this->description, + "points"=> $this->points, + "image" => $this->image, + ]; + } +} diff --git a/app/Http/Resources/RewardResourceObtained.php b/app/Http/Resources/RewardResourceObtained.php new file mode 100644 index 0000000000000000000000000000000000000000..4beaa7fd82495d758bc013f4b4ad2a0cc6be6306 --- /dev/null +++ b/app/Http/Resources/RewardResourceObtained.php @@ -0,0 +1,26 @@ +<?php + +namespace App\Http\Resources; + +use Illuminate\Http\Resources\Json\JsonResource; + +class RewardResourceObtained extends JsonResource +{ + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable + */ + public function toArray($request) + { + return [ + "id"=> $this->id, + "title"=> $this->name, + "description"=> $this->description, + "points"=> $this->points, + "image" => $this->image, + "obtained" => true, + ]; + } +} diff --git a/app/Http/Resources/RouteResource.php b/app/Http/Resources/RouteResource.php new file mode 100644 index 0000000000000000000000000000000000000000..ab74d0556540e4b18db48d3137df387da810063d --- /dev/null +++ b/app/Http/Resources/RouteResource.php @@ -0,0 +1,28 @@ +<?php + +namespace App\Http\Resources; + +use Illuminate\Http\Resources\Json\JsonResource; + +class RouteResource extends JsonResource +{ + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable + */ + public function toArray($request) + { + return [ + 'id' => $this->id, + 'user_id' => $this->user_id, + 'bike' => new BikeResource($this->bike()->first()), + 'initial_stop_id' => $this->initial_stop_id, + 'final_stop_id' => $this->final_stop_id, + 'distance' => $this->distance, + 'duration' => $this->duration, + 'points' => $this->points, + ]; + } +} diff --git a/app/Http/Resources/StopResource.php b/app/Http/Resources/StopResource.php new file mode 100644 index 0000000000000000000000000000000000000000..22311eafd43c4684032bd9e02db6526f5bbb82aa --- /dev/null +++ b/app/Http/Resources/StopResource.php @@ -0,0 +1,28 @@ +<?php + +namespace App\Http\Resources; + +use Illuminate\Http\Resources\Json\JsonResource; + +class StopResource extends JsonResource +{ + /** + * Transform the resource into an array. + * + * @param \Illuminate\Http\Request $request + * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable + */ + public function toArray($request) + { + return [ + 'id' => $this->id, + 'lng' => $this->lng, + 'lat' => $this->lat, + 'address' => $this->address, + 'totalSpaces' => $this->total_spaces, + 'reserved_pedal_bikes' => $this->reserved_pedal_bikes(), + 'reserved_electric_bikes' => $this->reserved_electric_bikes(), + 'bikes' => BikeResource::collection($this->bikes()->get()), + ]; + } +} diff --git a/app/Models/Reservation.php b/app/Models/Reservation.php new file mode 100644 index 0000000000000000000000000000000000000000..f74a1b508f74766b25bb2dc216feffa59551232c --- /dev/null +++ b/app/Models/Reservation.php @@ -0,0 +1,25 @@ +<?php + +namespace App\Models; + +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; + +/** + * @property mixed $user_id + * @property mixed $stop_id + * @property mixed|String $bike_type + */ +class Reservation extends Model +{ + use HasFactory; + + public function user(): BelongsTo{ + return $this->belongsTo(Route::class); + } + + public function stop(): BelongsTo{ + return $this->belongsTo(Stop::class); + } +} diff --git a/database/migrations/2022_06_20_140941_add_points_field_on_users_table.php b/database/migrations/2022_06_20_140941_add_points_field_on_users_table.php new file mode 100644 index 0000000000000000000000000000000000000000..49618aff91441215939f905f83595377f39e1734 --- /dev/null +++ b/database/migrations/2022_06_20_140941_add_points_field_on_users_table.php @@ -0,0 +1,32 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::table('users', function (Blueprint $table){ + $table->integer('points')->after('password')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('users', function (Blueprint $table){ + $table->dropColumn('points'); + }); + } +}; diff --git a/database/migrations/2022_06_26_185339_create_reservations_table.php b/database/migrations/2022_06_26_185339_create_reservations_table.php new file mode 100644 index 0000000000000000000000000000000000000000..e34ab1bce27cdc032d69804ee0ee8f278960ee5e --- /dev/null +++ b/database/migrations/2022_06_26_185339_create_reservations_table.php @@ -0,0 +1,34 @@ +<?php + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; +use Illuminate\Support\Facades\Schema; + +return new class extends Migration +{ + /** + * Run the migrations. + * + * @return void + */ + public function up() + { + Schema::create('reservations', function (Blueprint $table) { + $table->id(); + $table->foreignId('user_id'); + $table->foreignId('stop_id'); + $table->string('bike_type'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::dropIfExists('reservations'); + } +}; diff --git a/routes/api.php b/routes/api.php index 0276c009f2c0703dbd916195c7847262a4aad3d1..cbac4b3daef2729496acd31cd742db9465b4f5dd 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,13 +1,9 @@ <?php use App\Http\Controllers\AuthController; -use App\Http\Controllers\BikeController; use App\Http\Controllers\RewardController; use App\Http\Controllers\RouteController; use App\Http\Controllers\StopController; -use App\Http\Resources\BikeResource; -use App\Http\Resources\RewardColletion; -use App\Models\Bike; use Illuminate\Http\Request; use Illuminate\Support\Facades\Route;