first commit
This commit is contained in:
79
app/Data/PointOfInterest.php
Normal file
79
app/Data/PointOfInterest.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace App\Data;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
|
||||
readonly class PointOfInterest implements Arrayable
|
||||
{
|
||||
private readonly array $info;
|
||||
function __construct
|
||||
(
|
||||
public int $id,
|
||||
public ?int $poiNumber,
|
||||
public ?int $poiNumberWinter,
|
||||
public string $category,
|
||||
public array $tags,
|
||||
public string $area,
|
||||
public array $seasons,
|
||||
public string $title,
|
||||
public ?string $description,
|
||||
public array $entrance,
|
||||
public ?string $titleImage,
|
||||
)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function setInfo(array $info): void
|
||||
{
|
||||
$this->info = $info;
|
||||
}
|
||||
|
||||
public function queueTime(): int
|
||||
{
|
||||
return $this?->info['waitTime'] ?? 0;
|
||||
}
|
||||
|
||||
public function isOpen(): bool
|
||||
{
|
||||
$now = now()->setTimezone('Europe/Berlin');
|
||||
$inOpeningHours = $now->between($this->opensAt(), $this->closesAt());
|
||||
|
||||
$rideOpen = $this->info['open'];
|
||||
|
||||
if(!$rideOpen) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $inOpeningHours;
|
||||
}
|
||||
|
||||
public function closesAt(): Carbon
|
||||
{
|
||||
return Carbon::parse($this->info['closing'], 'Europe/Berlin');
|
||||
}
|
||||
|
||||
public function opensAt(): Carbon
|
||||
{
|
||||
return Carbon::parse($this->info['opening'], 'Europe/Berlin');
|
||||
}
|
||||
|
||||
public function toArray()
|
||||
{
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'poiNumber' => $this->poiNumber,
|
||||
'poiNumberWinter' => $this->poiNumberWinter,
|
||||
'category' => $this->category,
|
||||
'tags' => $this->tags,
|
||||
'area' => $this->area,
|
||||
'seasons' => $this->seasons,
|
||||
'title' => $this->title,
|
||||
'description' => $this->description,
|
||||
'entrance' => $this->entrance,
|
||||
'titleImage' => $this->titleImage,
|
||||
];
|
||||
}
|
||||
}
|
||||
8
app/Http/Controllers/Controller.php
Executable file
8
app/Http/Controllers/Controller.php
Executable file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
38
app/Http/Controllers/ThemeParkController.php
Normal file
38
app/Http/Controllers/ThemeParkController.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Services\PhantasialandApi;
|
||||
use App\Models\ThemeParkUser;
|
||||
use Illuminate\Contracts\View\Factory;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Foundation\Application;
|
||||
|
||||
class ThemeParkController extends Controller
|
||||
{
|
||||
|
||||
function __construct(readonly PhantasialandApi $api, readonly ThemeParkUser $user)
|
||||
{
|
||||
}
|
||||
|
||||
public function index(): Factory|View|Application
|
||||
{
|
||||
$pois = $this->api->getPointsOfInterest();
|
||||
$queueTimes = $this->api->getLiveQueueTimes();
|
||||
|
||||
$queueTimes = $queueTimes->mapWithKeys(function ($queueTime) {
|
||||
return [$queueTime["poiId"] => $queueTime];
|
||||
});
|
||||
|
||||
$pois = $pois->filter(function ($poi) use ($queueTimes) {
|
||||
return $queueTimes->has($poi->id);
|
||||
})->map(function ($poi) use ($queueTimes) {
|
||||
$poi->setInfo($queueTimes[$poi->id]);
|
||||
return $poi;
|
||||
});
|
||||
|
||||
return view('theme-park', [
|
||||
'pois' => $pois
|
||||
]);
|
||||
}
|
||||
}
|
||||
120
app/Http/Services/PhantasialandApi.php
Normal file
120
app/Http/Services/PhantasialandApi.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Services;
|
||||
|
||||
use App\Data\PointOfInterest;
|
||||
use App\Models\ThemeParkUser;
|
||||
use Illuminate\Http\Client\PendingRequest;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
class PhantasialandApi
|
||||
{
|
||||
private readonly string $uri;
|
||||
private readonly PendingRequest $client;
|
||||
|
||||
function __construct()
|
||||
{
|
||||
$this->uri = 'https://api.phlsys.de/api/';
|
||||
$this->client = Http::baseUrl($this->uri)->withRequestMiddleware(function (RequestInterface $request) {
|
||||
$user = app(ThemeParkUser::class);
|
||||
$uri = $request->getUri();
|
||||
$uri = $uri->withQuery($uri->getQuery().'&access_token='.$user->access_token);
|
||||
return $request->withUri($uri);
|
||||
});
|
||||
}
|
||||
|
||||
public function getPointsOfInterest(): Collection
|
||||
{
|
||||
$response = $this->client->get('pois');
|
||||
$pois = collect($response->json());
|
||||
|
||||
// NOTE: Either poiNumber or poiNumberWinter is always null
|
||||
// TODO: Use a generic field name and fill in the one that is not null
|
||||
// TODO: Add a type if the poi is winter only
|
||||
|
||||
// NOTE: we filter everything out that is not a ATTRACTIONS
|
||||
// NOTE: we filter everything out that is adminOnly = true
|
||||
$pois = $pois->filter(function ($poi) {
|
||||
return $poi['category'] === 'ATTRACTIONS' && $poi['adminOnly'] === false &&
|
||||
$poi['poiNumber'] !== null;
|
||||
});
|
||||
|
||||
// sort by parkMonitorReferenceName
|
||||
$pois = $pois->sortBy('parkMonitorReferenceName');
|
||||
|
||||
return $pois->transform(function ($poi) {
|
||||
return new PointOfInterest(
|
||||
id: $poi['id'],
|
||||
poiNumber: $poi['poiNumber'],
|
||||
poiNumberWinter: $poi['poiNumberWinter'],
|
||||
category: $poi['category'],
|
||||
tags: $poi['tags'],
|
||||
area: $poi['area'],
|
||||
seasons: $poi['seasons'],
|
||||
title: $poi['_title']['de'],
|
||||
description: $poi['_description']['de'],
|
||||
entrance: $poi['_entrance']['world'],
|
||||
titleImage: key_exists('titleImage', $poi) ? $poi['titleImage']['url'] : null,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public function getLiveQueueTimes(): Collection
|
||||
{
|
||||
[
|
||||
'longitude' => $longitude,
|
||||
'latitude' => $latitude
|
||||
] = $this->getRandomLocation();
|
||||
|
||||
$latitude = number_format($latitude, 14);
|
||||
$longitude = number_format($longitude, 14);
|
||||
|
||||
$response = $this->client->get('signage-snapshots', [
|
||||
'loc' => "$latitude,$longitude",
|
||||
]);
|
||||
|
||||
return collect($response->json());
|
||||
}
|
||||
|
||||
public function createUser(): array
|
||||
{
|
||||
$username = uuid_create().'@android.com';
|
||||
$password = uuid_create();
|
||||
|
||||
$response = $this->client->post('app-users', [
|
||||
'email' => $username,
|
||||
'password' => $password,
|
||||
'language' => 'en',
|
||||
'platform' => 'android',
|
||||
]);
|
||||
|
||||
return [
|
||||
...$response->json(),
|
||||
'password' => $password,
|
||||
];
|
||||
}
|
||||
|
||||
public function getAccessToken(ThemeParkUser $user): string
|
||||
{
|
||||
$response = $this->client->post('app-users/login', [
|
||||
'email' => $user->username,
|
||||
'password' => $user->password,
|
||||
'ttl' => now()->diffInSeconds(now()->addYear()),
|
||||
]);
|
||||
|
||||
return $response->json()['id'];
|
||||
}
|
||||
|
||||
|
||||
private function getRandomLocation(): array
|
||||
{
|
||||
// TODO: Its not random right now
|
||||
// $rnd = mt_rand() / mt_getrandmax();
|
||||
return [
|
||||
'longitude' => 6.878342628,
|
||||
'latitude' => 50.800659529,
|
||||
];
|
||||
}
|
||||
}
|
||||
41
app/Models/ThemeParkUser.php
Normal file
41
app/Models/ThemeParkUser.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Http\Services\PhantasialandApi;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class ThemeParkUser extends Model
|
||||
{
|
||||
use HasFactory;
|
||||
|
||||
protected $fillable = [
|
||||
'username',
|
||||
'password',
|
||||
];
|
||||
|
||||
|
||||
public function authenticate(): void
|
||||
{
|
||||
if(!$this->isTokenExpired()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->refreshToken();
|
||||
}
|
||||
|
||||
private function refreshToken(): void
|
||||
{
|
||||
$api = app(PhantasialandApi::class);
|
||||
$token = $api->getAccessToken($this);
|
||||
$this->access_token = $token;
|
||||
$this->access_token_expires_at = now()->addMonths(11);
|
||||
$this->save();
|
||||
}
|
||||
|
||||
private function isTokenExpired(): bool
|
||||
{
|
||||
return $this?->access_token_expires_at < now();
|
||||
}
|
||||
}
|
||||
47
app/Models/User.php
Executable file
47
app/Models/User.php
Executable file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use HasFactory, Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
];
|
||||
|
||||
/**
|
||||
* The attributes that should be hidden for serialization.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
];
|
||||
|
||||
/**
|
||||
* Get the attributes that should be cast.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'email_verified_at' => 'datetime',
|
||||
'password' => 'hashed',
|
||||
];
|
||||
}
|
||||
}
|
||||
36
app/Providers/AppServiceProvider.php
Executable file
36
app/Providers/AppServiceProvider.php
Executable file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use App\Http\Services\PhantasialandApi;
|
||||
use App\Models\ThemeParkUser;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* Bootstrap any application services.
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
$this->app->bind(ThemeParkUser::class, function () {
|
||||
$api = app(PhantasialandApi::class);
|
||||
if(ThemeParkUser::query()->count() === 0) {
|
||||
$userData = $api->createUser();
|
||||
return ThemeParkUser::query()->create([
|
||||
'username' => $userData['email'],
|
||||
'password' => $userData['password'],
|
||||
]);
|
||||
}
|
||||
return ThemeParkUser::query()->first();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user