Skip to content

Commit 02dfe13

Browse files
authored
Merge branch 'develop' into feature/screen-status
2 parents 90273a1 + a9338b9 commit 02dfe13

24 files changed

+1047
-80
lines changed

.env

+2
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,5 @@ CALENDAR_API_FEED_SOURCE_DATE_FORMAT=
105105
CALENDAR_API_FEED_SOURCE_DATE_TIMEZONE=
106106
CALENDAR_API_FEED_SOURCE_CACHE_EXPIRE_SECONDS=300
107107
###< Calendar Api Feed Source ###
108+
109+
EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS=300

CHANGELOG.md

+23-1
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,25 @@ All notable changes to this project will be documented in this file.
44

55
## [Unreleased]
66

7-
- [#229](https://github.com/os2display/display-api-service/pull/229)
7+
- [#227](https://github.com/os2display/display-api-service/pull/227)
88
- Added screen status to cache and endpoint for exposing screen status.
9+
10+
## [2.3.0] - 2025-03-24
11+
12+
- [#235](https://github.com/os2display/display-api-service/pull/234)
13+
- Fixed Eventdatabasen v2 subscription data order by using occurrences endpoint.
14+
- [#236](https://github.com/os2display/display-api-service/pull/236)
15+
- Fixed bug where no media url made Notified feed crash.
16+
- [#231](https://github.com/os2display/display-api-service/pull/231)
17+
- Adds new feed source: Eventdatabasen v2.
18+
- [#233](https://github.com/os2display/display-api-service/pull/233)
19+
- Added calendar api feed source tests for modifiers.
20+
- Changed to use PCRE pattern instead of custom pattern building and fixed modifier bugs for calendar api feed source.
21+
22+
## [2.2.0] - 2025-03-17
23+
24+
- [#229](https://github.com/os2display/display-api-service/pull/229)
25+
- Adds options to set paths to component and admin files from path to the json config file.
926
- [#225](https://github.com/os2display/display-api-service/pull/225)
1027
- Added ADRs.
1128
- [#215](https://github.com/os2display/display-api-service/pull/215)
@@ -18,6 +35,11 @@ All notable changes to this project will be documented in this file.
1835
- Adds create, update, delete operations to feed-source endpoint.
1936
- Adds data validation for feed source.
2037

38+
## [2.1.4] - 2025-01-14
39+
40+
- [#230](https://github.com/os2display/display-api-service/pull/230)
41+
- Adds options to set paths to component and admin files from path to the json config file.
42+
2143
## [2.1.3] - 2024-10-25
2244

2345
- [#220](https://github.com/os2display/display-api-service/pull/220)

composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@
130130
"bin/console doctrine:schema:validate",
131131
"@coding-standards-apply",
132132
"vendor/bin/rector",
133-
"vendor/bin/psalm",
133+
"vendor/bin/psalm --no-cache",
134134
"@test-setup",
135135
"@test"
136136
],

config/packages/cache.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ framework:
1717
# Default expire set to 5 minutes
1818
default_lifetime: 300
1919

20+
feed.without.expire.cache:
21+
adapter: cache.adapter.redis
22+
2023
# Creates a "calendar.api.cache" service
2124
calendar.api.cache:
2225
adapter: cache.adapter.redis

config/services.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ services:
5353
Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface: '@Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationFailureHandler'
5454
Symfony\Component\Security\Http\Authentication\AuthenticationSuccessHandlerInterface: '@Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Authentication\AuthenticationSuccessHandler'
5555

56+
App\Feed\EventDatabaseApiV2FeedType:
57+
arguments:
58+
$cacheExpire: '%env(int:EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS)%'
59+
5660
App\Feed\CalendarApiFeedType:
5761
arguments:
5862
$locationEndpoint: '%env(string:CALENDAR_API_FEED_SOURCE_LOCATION_ENDPOINT)%'

docs/calender-api-feed.md

+13-17
Original file line numberDiff line numberDiff line change
@@ -136,20 +136,19 @@ Modifiers can be set up to modify the output of the feed.
136136

137137
Two types of modifiers are available:
138138

139-
* EXCLUDE_IF_TITLE_NOT_CONTAINS: Removes entries from the feed if the title not contain the trigger word.
140-
* REPLACE_TITLE_IF_CONTAINS: Changes the title if it contains the trigger word.
139+
* EXCLUDE_IF_TITLE_NOT_CONTAINS: Removes entries from the feed if the title does not contain the pattern.
140+
* REPLACE_TITLE_IF_CONTAINS: Changes the title if it contains the pattern.
141141

142142
Parameters:
143143

144144
* type: EXCLUDE_IF_TITLE_NOT_CONTAINS or REPLACE_TITLE_IF_CONTAINS
145145
* id: Unique identifier for the modifier.
146146
* title: Display name when showing the modifier in the admin.
147-
* description: Help text for the modifier.
147+
* description: Description of the modifier.
148148
* activateInFeed: Should this filter be optional? If false the rule will always apply.
149-
* trigger: The string that should trigger the modifier.
150-
* replacement: The string to replace the title with.
151149
* removeTrigger: Should the trigger word be filtered from the title?
152-
* caseSensitive: Should the trigger word be case-sensitive?
150+
* pattern: The PCRE regular expression. See <https://www.php.net/manual/en/reference.pcre.pattern.syntax.php>.
151+
* replacement: The string to replace the title with. See <https://www.php.net/manual/en/function.preg-replace.php>.
153152

154153
Examples of modifiers:
155154

@@ -161,29 +160,26 @@ Examples of modifiers:
161160
"title": "Vis kun begivenheder med (liste) i titlen.",
162161
"description": "Denne mulighed fjerner begivenheder, der IKKE har (liste) i titlen. Den fjerner også (liste) fra titlen.",
163162
"activateInFeed": true,
164-
"trigger": "(liste)",
165-
"removeTrigger": true,
166-
"caseSensitive": false
163+
"pattern": "\/\\(liste\\)\/i",
164+
"removeTrigger": true
167165
},
168166
{
169167
"type": "REPLACE_TITLE_IF_CONTAINS",
170-
"id": "replaceIfContainsOptaget",
171168
"activateInFeed": false,
172-
"trigger": "(optaget)",
169+
"id": "replaceIfContainsOptaget",
170+
"pattern": "\/\\(optaget\\)\/i",
173171
"replacement": "Optaget",
174-
"removeTrigger": true,
175-
"caseSensitive": false
172+
"removeTrigger": true
176173
},
177174
{
178175
"type": "REPLACE_TITLE_IF_CONTAINS",
179-
"id": "onlyShowAsOptaget",
180176
"activateInFeed": true,
177+
"id": "onlyShowAsOptaget",
181178
"title": "Overskriv alle titler med Optaget",
182179
"description": "Denne mulighed viser alle titler som Optaget.",
183-
"trigger": "",
180+
"pattern": "\/\/",
184181
"replacement": "Optaget",
185-
"removeTrigger": false,
186-
"caseSensitive": false
182+
"removeTrigger": false
187183
}
188184
]
189185
```

infrastructure/itkdev/display-api-service/etc/confd/templates/env.local.tmpl

+3-1
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,7 @@ CALENDAR_API_FEED_SOURCE_CUSTOM_MAPPINGS={{ getenv "APP_CALENDAR_API_FEED_SOURCE
6060
CALENDAR_API_FEED_SOURCE_EVENT_MODIFIERS={{ getenv "APP_CALENDAR_API_FEED_SOURCE_EVENT_MODIFIERS" "'{}'" }}
6161
CALENDAR_API_FEED_SOURCE_DATE_FORMAT={{ getenv "APP_CALENDAR_API_FEED_SOURCE_DATE_FORMAT" "" }}
6262
CALENDAR_API_FEED_SOURCE_DATE_TIMEZONE={{ getenv "APP_CALENDAR_API_FEED_SOURCE_DATE_TIMEZONE" "" }}
63-
CALENDAR_API_FEED_SOURCE_CACHE_EXPIRE_SECONDS={{ getenv "CALENDAR_API_FEED_SOURCE_CACHE_EXPIRE_SECONDS" "300" }}
63+
CALENDAR_API_FEED_SOURCE_CACHE_EXPIRE_SECONDS={{ getenv "APP_CALENDAR_API_FEED_SOURCE_CACHE_EXPIRE_SECONDS" "300" }}
6464
###< Calendar Api Feed Source ###
65+
66+
EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS={{ getenv "APP_EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS" "300" }}

infrastructure/os2display/display-api-service/etc/confd/templates/env.local.tmpl

+2
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,5 @@ CALENDAR_API_FEED_SOURCE_DATE_FORMAT={{ getenv "APP_CALENDAR_API_FEED_SOURCE_DAT
6262
CALENDAR_API_FEED_SOURCE_DATE_TIMEZONE={{ getenv "APP_CALENDAR_API_FEED_SOURCE_DATE_TIMEZONE" "" }}
6363
CALENDAR_API_FEED_SOURCE_CACHE_EXPIRE_SECONDS={{ getenv "CALENDAR_API_FEED_SOURCE_CACHE_EXPIRE_SECONDS" "300" }}
6464
###< Calendar Api Feed Source ###
65+
66+
EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS={{ getenv "APP_EVENTDATABASE_API_V2_CACHE_EXPIRE_SECONDS" "300" }}

src/Command/LoadTemplateCommand.php

+20-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
use Symfony\Component\Console\Command\Command;
1515
use Symfony\Component\Console\Input\InputArgument;
1616
use Symfony\Component\Console\Input\InputInterface;
17+
use Symfony\Component\Console\Input\InputOption;
1718
use Symfony\Component\Console\Output\OutputInterface;
1819
use Symfony\Component\Console\Style\SymfonyStyle;
1920
use Symfony\Component\Uid\Ulid;
@@ -33,6 +34,8 @@ public function __construct(
3334
protected function configure(): void
3435
{
3536
$this->addArgument('filename', InputArgument::REQUIRED, 'json file to load. Can be a local file or a URL');
37+
$this->addOption('path-from-filename', 'p', InputOption::VALUE_NONE, 'Set path to component and admin from filename. Assumes that the config file loaded has the naming format: [templateName]-config[.*].json.', null);
38+
$this->addOption('timestamp', 't', InputOption::VALUE_NONE, 'Add a timestamp to the component and admin urls: ?ts=. Only applies if path-from-filename option is active.', null);
3639
}
3740

3841
final protected function execute(InputInterface $input, OutputInterface $output): int
@@ -41,7 +44,9 @@ final protected function execute(InputInterface $input, OutputInterface $output)
4144
$successMessage = 'Template updated';
4245

4346
try {
47+
/** @var string $filename */
4448
$filename = $input->getArgument('filename');
49+
4550
$content = json_decode(file_get_contents($filename), false, 512, JSON_THROW_ON_ERROR);
4651

4752
// Validate template json.
@@ -87,8 +92,21 @@ final protected function execute(InputInterface $input, OutputInterface $output)
8792
}
8893

8994
$template->setIcon($content->icon);
90-
// @TODO: Resource should be an object.
91-
$template->setResources(get_object_vars($content->resources));
95+
96+
$resources = get_object_vars($content->resources);
97+
98+
if ($input->getOption('path-from-filename')) {
99+
// Set paths to component and admin from filename.
100+
$resources['component'] = preg_replace("/-config.*\.json$/", '.js', $filename);
101+
$resources['admin'] = preg_replace("/-config.*\.json$/", '-admin.json', $filename);
102+
103+
if ($input->getOption('timestamp')) {
104+
$resources['component'] = $resources['component'].'?ts='.time();
105+
$resources['admin'] = $resources['admin'].'?ts='.time();
106+
}
107+
}
108+
109+
$template->setResources($resources);
92110
$template->setTitle($content->title);
93111
$template->setDescription($content->description);
94112

src/Feed/CalendarApiFeedType.php

+64-52
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,6 @@ public function __construct(
6363
public function getData(Feed $feed): array
6464
{
6565
try {
66-
$results = [];
67-
6866
$configuration = $feed->getConfiguration();
6967

7068
$enabledModifiers = $configuration['enabledModifiers'] ?? [];
@@ -76,62 +74,28 @@ public function getData(Feed $feed): array
7674
}
7775

7876
$resources = $configuration['resources'];
79-
foreach ($resources as $resource) {
80-
$events = $this->getResourceEvents($resource);
81-
82-
/** @var CalendarEvent $event */
83-
foreach ($events as $event) {
84-
$title = $event->title;
85-
86-
// Modify title according to event modifiers.
87-
foreach ($this->eventModifiers as $modifier) {
88-
// Make it configurable in the Feed if the modifiers should be enabled.
89-
if ($modifier['activateInFeed'] && !in_array($modifier['id'], $enabledModifiers)) {
90-
continue;
91-
}
92-
93-
if (self::EXCLUDE_IF_TITLE_NOT_CONTAINS == $modifier['type']) {
94-
$match = preg_match('/'.$modifier['trigger'].'/'.(!$modifier['caseSensitive'] ? 'i' : ''), $title);
95-
96-
if ($modifier['removeTrigger']) {
97-
$title = str_replace($modifier['trigger'], '', $title);
98-
}
99-
100-
if (!$match) {
101-
continue;
102-
}
103-
}
104-
105-
if (self::REPLACE_TITLE_IF_CONTAINS == $modifier['type']) {
106-
$match = preg_match('/'.$modifier['trigger'].'/'.(!$modifier['caseSensitive'] ? 'i' : ''), $title);
107-
108-
if ($modifier['removeTrigger']) {
109-
$title = str_replace($modifier['trigger'], '', $title);
110-
}
111-
112-
if ($match) {
113-
$title = $modifier['replacement'];
114-
}
115-
}
116-
}
11777

118-
$title = trim($title);
78+
$events = [];
11979

120-
$results[] = [
121-
'id' => Ulid::generate(),
122-
'title' => $title,
123-
'startTime' => $event->startTimeTimestamp,
124-
'endTime' => $event->endTimeTimestamp,
125-
'resourceTitle' => $event->resourceDisplayName,
126-
'resourceId' => $event->resourceId,
127-
];
128-
}
80+
foreach ($resources as $resource) {
81+
$events += $this->getResourceEvents($resource);
12982
}
13083

84+
$modifiedResults = static::applyModifiersToEvents($events, $this->eventModifiers, $enabledModifiers);
85+
86+
$resultsAsArray = array_map(fn (CalendarEvent $event) => [
87+
'id' => Ulid::generate(),
88+
'title' => $event->title,
89+
'startTime' => $event->startTimeTimestamp,
90+
'endTime' => $event->endTimeTimestamp,
91+
'resourceTitle' => $event->resourceDisplayName,
92+
'resourceId' => $event->resourceId,
93+
], $modifiedResults);
94+
13195
// Sort bookings by start time.
132-
usort($results, fn (array $a, array $b) => $a['startTime'] > $b['startTime'] ? 1 : -1);
96+
usort($resultsAsArray, fn (array $a, array $b) => $a['startTime'] > $b['startTime'] ? 1 : -1);
13397

134-
return $results;
98+
return $resultsAsArray;
13599
} catch (\Throwable $throwable) {
136100
$this->logger->error('{code}: {message}', [
137101
'code' => $throwable->getCode(),
@@ -142,6 +106,54 @@ public function getData(Feed $feed): array
142106
return [];
143107
}
144108

109+
public static function applyModifiersToEvents(array $events, array $eventModifiers, array $enabledModifiers): array
110+
{
111+
$results = [];
112+
113+
/** @var CalendarEvent $event */
114+
foreach ($events as $event) {
115+
$title = $event->title;
116+
117+
// Modify title according to event modifiers.
118+
foreach ($eventModifiers as $modifier) {
119+
// Make it configurable in the Feed if the modifiers should be enabled.
120+
if ($modifier['activateInFeed'] && !in_array($modifier['id'], $enabledModifiers)) {
121+
continue;
122+
}
123+
124+
$pattern = $modifier['pattern'];
125+
126+
if (self::EXCLUDE_IF_TITLE_NOT_CONTAINS == $modifier['type']) {
127+
$match = preg_match($pattern, $title);
128+
129+
if (!$match) {
130+
continue 2;
131+
}
132+
133+
if ($modifier['removeTrigger']) {
134+
$title = preg_replace($pattern, '', $title);
135+
}
136+
}
137+
138+
if (self::REPLACE_TITLE_IF_CONTAINS == $modifier['type']) {
139+
$match = preg_match($pattern, $title);
140+
141+
if ($match) {
142+
$title = $modifier['replacement'];
143+
}
144+
}
145+
}
146+
147+
$title = trim($title);
148+
149+
$event->title = $title;
150+
151+
$results[] = $event;
152+
}
153+
154+
return $results;
155+
}
156+
145157
/**
146158
* {@inheritDoc}
147159
*/

src/Feed/EventDatabaseApiFeedType.php

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Symfony\Contracts\HttpClient\HttpClientInterface;
1616

1717
/**
18+
* @deprecated
19+
*
1820
* See https://github.com/itk-event-database/event-database-api.
1921
*/
2022
class EventDatabaseApiFeedType implements FeedTypeInterface

0 commit comments

Comments
 (0)