feat: integrated OAuth2 server

This commit is contained in:
Björn Fromme
2025-04-24 20:28:27 +02:00
parent c418ae6399
commit adbfb49c11
84 changed files with 2542 additions and 312 deletions
+5 -4
View File
@@ -2,7 +2,6 @@
namespace App\BusProNet;
use App\BusProNet\XmlParser\ApiResponseParser;
use App\BusProNet\DataProcessor\BookingDataProcessor;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\Exception\ResponseParserException;
@@ -12,6 +11,8 @@ use App\BusProNet\Model\BookingUpdate;
use App\BusProNet\Model\CrmAttributes;
use App\BusProNet\Model\Notification;
use App\BusProNet\Model\PersonalData;
use App\BusProNet\Traits\ApiClientTrait;
use App\BusProNet\XmlParser\ApiResponseParser;
use App\Form\Model\BookingData;
use App\Form\Model\RegistrationData;
use League\Flysystem\FilesystemException;
@@ -39,7 +40,7 @@ class ApiClient
private readonly ApiResponseParser $responseParser,
private readonly FilesystemOperator $xmlDump,
private readonly LoggerInterface $logger,
array $options
array $options,
) {
$this->config = $this->resolveOptions($options);
}
@@ -107,7 +108,7 @@ class ApiClient
string $email,
string $password,
PersonalData $personalData,
bool $debug = false
bool $debug = false,
): Notification|PersonalData {
$data = [
'user' => $this->config['bpn_username'],
@@ -333,7 +334,7 @@ class ApiClient
private function dumpXmlToFile(string $type, string $requestId, string $body): void
{
try {
$this->xmlDump->write($requestId . '_' . $type . '.xml', $body);
$this->xmlDump->write($requestId.'_'.$type.'.xml', $body);
} catch (FilesystemException $e) {
}
}
@@ -3,7 +3,6 @@
namespace App\BusProNet\DataProvider;
use App\BusProNet\ApiClient;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\Model\Country;
use Psr\Cache\InvalidArgumentException;
use Symfony\Contracts\Cache\CacheInterface;
@@ -12,7 +11,8 @@ use Symfony\Contracts\Cache\ItemInterface;
class CountryDataProvider
{
public function __construct(private readonly ApiClient $apiClient, private readonly CacheInterface $cache)
{}
{
}
public function getAll(): array
{
@@ -39,4 +39,4 @@ class CountryDataProvider
return $this->getAll()[$token] ?? null;
}
}
}
@@ -4,4 +4,4 @@ namespace App\BusProNet\Exception;
class ResponseParserException extends \Exception
{
}
}
@@ -11,9 +11,10 @@ use Symfony\Component\Form\ChoiceList\Loader\ChoiceLoaderInterface;
class CountryChoiceLoader implements ChoiceLoaderInterface
{
public function __construct(private readonly CountryDataProvider $countries, private readonly string $property)
{}
{
}
public function loadChoiceList(callable $value = null): ChoiceListInterface
public function loadChoiceList(?callable $value = null): ChoiceListInterface
{
$choices = [];
/** @var Country[] $countries */
@@ -25,16 +26,15 @@ class CountryChoiceLoader implements ChoiceLoaderInterface
}
return new ArrayChoiceList($choices);
}
public function loadChoicesForValues(array $values, callable $value = null): array
public function loadChoicesForValues(array $values, ?callable $value = null): array
{
return $values;
}
public function loadValuesForChoices(array $choices, callable $value = null): array
public function loadValuesForChoices(array $choices, ?callable $value = null): array
{
return $choices;
}
}
}
+3 -2
View File
@@ -13,7 +13,8 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
class CountryType extends AbstractType
{
public function __construct(private readonly CountryDataProvider $countries)
{}
{
}
public function getParent(): string
{
@@ -35,4 +36,4 @@ class CountryType extends AbstractType
},
]);
}
}
}
+1 -1
View File
@@ -16,4 +16,4 @@ class Availability implements \JsonSerializable
'available' => $this->available,
];
}
}
}
+3 -2
View File
@@ -5,7 +5,8 @@ namespace App\BusProNet\Model;
class BaseData
{
public function __construct(private readonly array $items)
{}
{
}
public function getItems(): array
{
@@ -16,4 +17,4 @@ class BaseData
{
return $this->items[$key] ?? null;
}
}
}
+1 -1
View File
@@ -8,4 +8,4 @@ class Country
public ?string $name = null;
public ?string $token = null;
public ?string $nationality = null;
}
}
+1 -1
View File
@@ -12,4 +12,4 @@ class CrmAttributes
public bool $teamer = false;
public ?string $hotelCode = null;
public array $roles = [];
}
}
+1 -1
View File
@@ -17,4 +17,4 @@ class CrmSelection
#[Groups(['api:single'])]
public bool $selected = false;
}
}
+2 -1
View File
@@ -8,7 +8,8 @@ class CrmSelectionGroup
{
/**
* Maps BusPro IDs of CRM selection groups representing
* included services
* included services.
*
* @var array|string[]
*/
public static array $includedServicesMapping = [
+1 -1
View File
@@ -100,4 +100,4 @@ class Error
985 => 'Zustieg mit ID #1# nicht fürs Internet buchbar',
999 => 'Systemfehler: #1#',
];
}
}
+1 -1
View File
@@ -14,4 +14,4 @@ class File
public ?string $filename = null;
public ?string $content = null;
public ?string $mimeType = null;
}
}
+1 -1
View File
@@ -6,4 +6,4 @@ class Guide
{
public ?string $name = null;
public ?string $phone = null;
}
}
+1 -1
View File
@@ -26,4 +26,4 @@ class Hotel
#[Groups(['api:list', 'api:single'])]
public ?string $country = null;
}
}
+1 -1
View File
@@ -21,4 +21,4 @@ class MutableData
public ?string $category = null;
public bool $mutable = false;
public ?\DateTimeImmutable $mutableBefore = null;
}
}
+32
View File
@@ -64,4 +64,36 @@ class PersonalData
'bemerkung' => $this->remarks,
];
}
public function getClaims(): array
{
return [
'email' => $this->communication->email,
'profile' => [
'first_name' => $this->firstName,
'last_name' => $this->name,
'title' => $this->title,
'salutation' => $this->salutation,
'gender' => $this->gender,
'nationality' => $this->nationality,
'address' => [
'street' => $this->address->street,
'postcode' => $this->address->postCode,
'city' => $this->address->city,
'disctrict' => $this->address->district,
'country' => $this->address->country,
],
'communication' => [
'email' => $this->communication->email,
'mobile' => $this->communication->mobile,
'phone' => $this->communication->phone,
'newsletter' => $this->communication->newsletter,
],
'height' => $this->height,
'weight' => $this->weight,
'shoe_size' => $this->shoeSize,
'remarks' => $this->remarks,
],
];
}
}
+1 -1
View File
@@ -32,4 +32,4 @@ class Pickup
#[Groups(['api:booking'])]
public array $mapping = [];
}
}
+1 -1
View File
@@ -63,4 +63,4 @@ class Room
#[Groups(['booking'])]
public array $individualPrice = [];
}
}
+1 -1
View File
@@ -31,7 +31,7 @@ class Service
public ?string $category = null;
#[Groups(['api:single', 'api:list'])]
public ?string $status = null;
public ?string $status = null;
#[Groups(['api:single', 'api:list'])]
public bool $mandatory = false;
+1 -1
View File
@@ -8,4 +8,4 @@ class Surcharge
public array $mapping = [];
public ?float $totalPrice = null;
public array $individualPrice = [];
}
}
@@ -1,6 +1,6 @@
<?php
namespace App\BusProNet;
namespace App\BusProNet\Traits;
use App\BusProNet\Exception\ApiClientException;
@@ -56,7 +56,7 @@ trait ApiClientTrait
private function send($socket, string $data): void
{
// message length is prepended to actual message
$send = sprintf('%010s', strlen($data)) . $data;
$send = sprintf('%010s', strlen($data)).$data;
fwrite($socket, $send);
}
@@ -75,4 +75,4 @@ trait ApiClientTrait
{
@fclose($socket);
}
}
}
@@ -1,6 +1,6 @@
<?php
namespace App\BusProNet;
namespace App\BusProNet\Traits;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
@@ -52,6 +52,7 @@ trait TypeConversionTrait
try {
$date = Carbon::createFromFormat('d.m.Y', $string)->startOfDay();
return $date->toDateTimeImmutable();
} catch (InvalidFormatException $e) {
return null;
@@ -66,6 +67,7 @@ trait TypeConversionTrait
try {
$dateTime = Carbon::createFromFormat('d.m.Y H:i', substr($string, 0, 16))->startOfDay();
return $dateTime->toDateTimeImmutable();
} catch (InvalidFormatException $e) {
return null;
@@ -1,6 +1,6 @@
<?php
namespace App\BusProNet;
namespace App\BusProNet\Traits;
use Symfony\Component\DomCrawler\Crawler;
@@ -15,4 +15,4 @@ trait XmlParserTrait
{
return 0 < $node->count() ? (int) $node->text() : null;
}
}
}
+2 -2
View File
@@ -2,8 +2,8 @@
namespace App\BusProNet\XmlLoader;
use App\BusProNet\TypeConversionTrait;
use App\BusProNet\XmlParserTrait;
use App\BusProNet\Traits\TypeConversionTrait;
use App\BusProNet\Traits\XmlParserTrait;
use League\Flysystem\FilesystemOperator;
use Symfony\Contracts\Cache\CacheInterface;
+1 -1
View File
@@ -24,7 +24,7 @@ class TravelLoader extends AbstractLoader
private readonly HotelLoader $hotelDataLoader,
CacheInterface $cache,
private readonly string $travelInfoBaseUrl,
FileSystemOperator $xmlExport,
FilesystemOperator $xmlExport,
) {
parent::__construct($cache, $xmlExport);
}
+2 -2
View File
@@ -2,7 +2,7 @@
namespace App\BusProNet\XmlParser;
use App\BusProNet\TypeConversionTrait;
use App\BusProNet\Traits\TypeConversionTrait;
use Symfony\Component\DomCrawler\Crawler;
abstract class AbstractParser
@@ -47,4 +47,4 @@ abstract class AbstractParser
return $this->stringToArray($node->text(), $separator);
}
}
}
@@ -61,4 +61,4 @@ class ApiResponseParser extends AbstractParser
throw new ResponseParserException('Unable to parse XML response');
}
}
}
@@ -27,4 +27,4 @@ class AvailabilitiesParser extends AbstractParser
return new BaseData($availabilities);
}
}
}
+1 -1
View File
@@ -38,4 +38,4 @@ class BookingsParser extends AbstractParser
return new BaseData($bookings);
}
}
}
+1 -1
View File
@@ -27,4 +27,4 @@ class CountriesParser
return new BaseData($countries);
}
}
}
@@ -6,7 +6,7 @@ use App\BusProNet\Model\CrmAction;
use App\BusProNet\Model\CrmAttributes;
use App\BusProNet\Model\CrmSelection;
use App\BusProNet\Model\CrmSelectionGroup;
use App\BusProNet\TypeConversionTrait;
use App\BusProNet\Traits\TypeConversionTrait;
use Symfony\Component\DomCrawler\Crawler;
class CrmAttributesResponseParser
+2 -3
View File
@@ -37,8 +37,7 @@ class DocumentsParser
});
return new File('reisedokumente.zip', $zip->outputAsString(), 'application/zip');
}
catch (ZipException $e) {
} catch (ZipException $e) {
return null;
}
}
@@ -57,4 +56,4 @@ class DocumentsParser
return [$filename, $pdfData];
}
}
}
@@ -39,4 +39,4 @@ class MutableDataParser extends AbstractParser
return new BaseData($mutableFields);
}
}
}
@@ -14,4 +14,4 @@ class NotificationParser extends AbstractParser
return new Notification($number, $text);
}
}
}
@@ -36,8 +36,8 @@ class PersonalDataParser extends AbstractParser
$address = new Address();
$address->street = $this->getStringOrNullValue($addressNode->filterXPath('//strasse'));
$address->postCode = $this->getStringOrNullValue($addressNode->filterXPath('//plz'));
$address->city = $this->getStringOrNullValue($addressNode->filterXPath('//ort')) ;
$address->country = $this->getStringOrNullValue($addressNode->filterXPath('//land')) ;
$address->city = $this->getStringOrNullValue($addressNode->filterXPath('//ort'));
$address->country = $this->getStringOrNullValue($addressNode->filterXPath('//land'));
$personalData->address = $address;
}
@@ -63,4 +63,4 @@ class PersonalDataParser extends AbstractParser
return $personalData;
}
}
}
+1 -1
View File
@@ -33,4 +33,4 @@ class PickupsParser extends AbstractParser
return $pickups;
}
}
}
+2 -2
View File
@@ -13,7 +13,7 @@ class RoomsParser extends AbstractParser
$result->each(function (Crawler $node) use (&$rooms) {
$room = new Room();
$room->id = (int) $node->attr('idzimmer');;
$room->id = (int) $node->attr('idzimmer');
$room->label = $node->attr('zimmer');
$room->category = $node->attr('kategorie');
$room->boardId = (int) $node->attr('idverpflegung');
@@ -38,4 +38,4 @@ class RoomsParser extends AbstractParser
return $rooms;
}
}
}
+1 -1
View File
@@ -39,4 +39,4 @@ class ServicesParser extends AbstractParser
return $services;
}
}
}
+1 -1
View File
@@ -30,4 +30,4 @@ class SurchargesParser extends AbstractParser
return $surcharges;
}
}
}