Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

- [#313](https://github.com/os2display/display-api-service/pull/313)
- Add BRND booking feed type

## [2.5.2] - 2025-09-25

- [#260](https://github.com/os2display/display-api-service/pull/260)
Expand Down
183 changes: 183 additions & 0 deletions src/Feed/BrndFeedType.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
<?php

declare(strict_types=1);

namespace App\Feed;

use App\Entity\Tenant\Feed;
use App\Entity\Tenant\FeedSource;
use App\Feed\SourceType\Brnd\ApiClient;
use App\Feed\SourceType\Brnd\SecretsDTO;
use App\Service\FeedService;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\HttpFoundation\Request;

/**
* Brnd Bookingsystem Feed.
*
* @see https://brndapi.brnd.com/swagger/index.html
*/
class BrndFeedType implements FeedTypeInterface
{
public const int CACHE_TTL = 3600;

final public const string SUPPORTED_FEED_TYPE = FeedOutputModels::BRND_BOOKING_OUTPUT;

public function __construct(
private readonly FeedService $feedService,
private readonly ApiClient $apiClient,
private readonly CacheItemPoolInterface $feedsCache,
) {}

public function getAdminFormOptions(FeedSource $feedSource): array
{
$feedEntryRecipients = $this->feedService->getFeedSourceConfigUrl($feedSource, 'sport-center');

return [
[
'key' => 'brnd-sport-center-id',
'input' => 'input',
'type' => 'text',
'name' => 'sport_center_id',
'label' => 'Sportcenter ID',
'formGroupClasses' => 'mb-3',
],
];
}

public function getData(Feed $feed): array
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure this function does not throw an exception.
It should always return $result, so the client does not have to handle the exceptions.

{
$result = [
'title' => 'BRND Booking',
'bookings' => [],
];

$configuration = $feed->getConfiguration();
$feedSource = $feed->getFeedSource();

if (null == $feedSource) {
return $result;
}

$secrets = new SecretsDTO($feedSource);

$baseUri = $secrets->apiBaseUri;
$sportCenterId = $configuration['sport_center_id'] ?? null;

if ('' === $baseUri || null === $sportCenterId || '' === $sportCenterId) {
return $result;
}

$feedSource = $feed->getFeedSource();

if (null === $feedSource) {
return $result;
}

$bookings = $this->apiClient->getInfomonitorBookingsDetails($feedSource, $sportCenterId);

$result['bookings'] = array_map([$this, 'parseBrndBooking'], $bookings);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you need to filter out entries that do not meet some minimum requirements (startTime, endTime, activity, ?)
Otherwise, you can end up with slides that display broken data.

I would replace array_map with array_reduce and add some validation checks and ignore broken entries.


return $result;
}

private function parseBrndBooking(array $booking): array
{
// Parse start time
$startDateTime = null;
if (!empty($booking['dato']) && isset($booking['starttid']) && is_string($booking['starttid'])) {
// Trim starttid to 6 digits after dot for microseconds
$starttid = preg_replace('/\.(\d{6})\d+$/', '.$1', $booking['starttid']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace danish variable name with english.
Maybe: $startTimeString

$dateOnly = substr($booking['dato'], 0, 10);
$dateTimeString = $dateOnly.' '.$starttid;
$startDateTime = \DateTimeImmutable::createFromFormat('m/d/Y H:i:s.u', $dateTimeString);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if this throws an exception or returns false?
https://www.php.net/manual/en/datetime.createfromformat.php

}

// Parse end time
$endDateTime = null;
if (!empty($booking['dato']) && isset($booking['sluttid']) && is_string($booking['sluttid'])) {
$sluttid = preg_replace('/\.(\d{6})\d+$/', '.$1', $booking['sluttid']);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace danish variable name with english.
Maybe: $endTimeString

$dateOnly = substr($booking['dato'], 0, 10);
$dateTimeString = $dateOnly.' '.$sluttid;
$endDateTime = \DateTimeImmutable::createFromFormat('m/d/Y H:i:s.u', $dateTimeString);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if this throws an exception or returns false?
https://www.php.net/manual/en/datetime.createfromformat.php

}

return [
'bookingcode' => $booking['ansøgning'] ?? '',
'remarks' => $booking['bemærkninger'] ?? '',
'startTime' => $startDateTime ? $startDateTime->getTimestamp() : null,
'endTime' => $endDateTime ? $endDateTime->getTimestamp() : null,
'complex' => $booking['anlæg'] ?? '',
'area' => $booking['område'] ?? '',
'facility' => $booking['facilitet'] ?? '',
'activity' => $booking['aktivitet'] ?? '',
'team' => $booking['hold'] ?? '',
'status' => $booking['status'] ?? '',
'checkIn' => $booking['checK_IN'] ?? '',
'bookingBy' => $booking['ansøgt_af'] ?? '',
'changingRooms' => $booking['omklædningsrum'] ?? '',
];
}

public function getConfigOptions(Request $request, FeedSource $feedSource, string $name): ?array
{
return null;
}

public function getRequiredSecrets(): array
{
return [
'api_base_uri' => [
'type' => 'string',
'exposeValue' => true,
],
'company_id' => [
'type' => 'string',
'exposeValue' => true,
],
'api_auth_key' => [
'type' => 'string',
'exposeValue' => true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should hide this value, otherwise everybody in the admin can see the value.

Suggested change
'exposeValue' => true,
'exposeValue' => false,

It will still be possible to set the value, it will just not be visible in the admin after being set.

],
];
}

public function getRequiredConfiguration(): array
{
return ['sport_center_id'];
}

public function getSupportedFeedOutputType(): string
{
return self::SUPPORTED_FEED_TYPE;
}

public function getSchema(): array
{
return [
'$schema' => 'http://json-schema.org/draft-04/schema#',
'type' => 'object',
'properties' => [
'api_base_uri' => [
'type' => 'string',
'format' => 'uri',
],
'company_id' => [
'type' => 'string',
],
'api_auth_key' => [
'type' => 'string',
],
],
'required' => ['api_base_uri', 'company_id', 'api_auth_key'],
];
}

public static function getIdKey(FeedSource $feedSource): string
{
$ulid = $feedSource->getId();
assert(null !== $ulid);

return $ulid->toBase32();
}
}
39 changes: 39 additions & 0 deletions src/Feed/FeedOutputModels.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,43 @@ class FeedOutputModels
* ]
*/
final public const string RSS_OUTPUT = 'rss';

/**
* Data example:
* [
* {
* activity: "Svømning",
* area: "Svømmehal",
* bookingBy: "Offentlig svømning",
* bookingcode: "BKN-363973",
* changingRooms: "",
* checkIn: "0",
* complex: "Humlehøj Hallen",
* endTime: 1751615100,
* facility: "Svømmehal",
* remarks: "",
* startTime: 1751608800,
* status: "Tildelt tid",
* team: ""
* },
* {
* activity: "Undervisning",
* area: "Mødelokaler",
* bookingBy: "Svømmeklubben Sønderborg",
* bookingcode: "BKN-388946",
* changingRooms: "",
* checkIn: "0",
* complex: "Humlehøj Hallen",
* endTime: 1751641200,
* facility: "Mødelokale 1+2",
* remarks: "",
* startTime: 1751630400,
* status: "Tildelt tid",
* team: ""
* }
* ]
*
* Start/end time are unix timestamps.
*/
final public const string BRND_BOOKING_OUTPUT = 'brnd-booking';
}
Loading