Skip to content
Merged
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
13 changes: 12 additions & 1 deletion .claude/skills/add-feed-type/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ class MyFeedType implements FeedTypeInterface
Authoritative signatures live in `src/Feed/FeedTypeInterface.php`. Canonical behaviour for each:

### 3.1 `getSupportedFeedOutputType(): string`

Return one of `FeedOutputModels::*`. **Frozen for the source's lifetime** — changing it on an existing `FeedSource` orphans all linked slides.

### 3.2 `getRequiredSecrets(): array`

Declare keys for `FeedSource.secrets`. `'exposeValue' => true` makes a key readable by the frontend.

```php
Expand All @@ -100,9 +102,11 @@ return [
```

### 3.3 `getRequiredConfiguration(): array`

Keys that must exist in `Feed.configuration` before `getData()` runs. Documentation only — enforce in `getData()`.

### 3.4 `getSchema(): array`

JSON Schema (draft-04) validated by `FeedSourceProcessor` on POST/PUT. Start permissive.

```php
Expand All @@ -113,9 +117,11 @@ return [
'required' => ['token'],
];
```

Gotcha: PUT with empty `secrets` payload skips validation (intentional — allows editing title without re-sending secrets).

### 3.5 `getAdminFormOptions(FeedSource $feedSource): array`

Admin form descriptor. Common inputs:

| `input` | Use for |
Expand All @@ -141,6 +147,7 @@ return [[
`name` becomes the key under `Feed.configuration`. The second arg to `getFeedSourceConfigUrl()` is the `$name` your `getConfigOptions()` dispatches on.

### 3.6 `getConfigOptions(Request $request, FeedSource $feedSource, string $name): ?array`

Called by `FeedSourceConfigGetController` for dynamic dropdowns. Return `[{id, title, value}, …]` or `null` for unknown `$name`.

**Swallow exceptions here** — the admin form should degrade gracefully while an editor is still typing credentials. Log + return `null`/`[]`.
Expand Down Expand Up @@ -168,6 +175,7 @@ Rules (these are the most common bugs):
1. **Validate required secrets/config first** — throw `\RuntimeException` with class-prefixed message if missing.
2. **Let exceptions bubble.** `FeedService::getData()` wraps the call and caches an empty result for **30 s** on failure (`ERROR_CACHE_TTL_SECONDS`). Catching-and-returning-`[]` yourself caches empty with the **full feed TTL** (often hours) — silent breakage.
3. **Log at the catch site, then rethrow:**

```php
try { /* … */ }
catch (\Throwable $t) {
Expand All @@ -179,6 +187,7 @@ Rules (these are the most common bugs):
throw $t;
}
```

4. **Don't add per-call caching around the whole `getData()`** — `FeedService` already caches per-feed and honours `configuration['cache_expire']` as a per-feed TTL override. Cache lower-level shared lookups separately if needed.

## 4. Output data contract
Expand Down Expand Up @@ -226,7 +235,8 @@ Declare in `config/packages/cache.yaml` and inject by name. **Don't** reuse `$fe
| External system with its own SDK/client | `src/Feed/BrndFeedType.php` + `src/Feed/SourceType/Brnd/` |
| Hydra/REST poster-event source | `src/Feed/EventDatabaseApiV2FeedType.php` + `src/Feed/EventDatabaseApiV2Helper.php` |

**Deprecated — do not use as templates:** `SparkleIOFeedType`, `EventDatabaseApiFeedType`, `KobaFeedType`.
**Removed in 3.0.0:** `SparkleIOFeedType`, `EventDatabaseApiFeedType` (use `EventDatabaseApiV2FeedType`),
and `KobaFeedType` were deprecated in 2.x and removed in 3.0.0 — do not reintroduce them.

## 7. Testing

Expand All @@ -237,6 +247,7 @@ Add `tests/Feed/MyFeedTypeTest.php`. Pure unit tests (no kernel) are fine for tr
`testGetDataErrorIsNotCachedWithNormalTtl` in the same file documents the error-caching contract — read it before tweaking exception handling.

Run:

```shell
task test:api -- tests/Feed tests/Service/FeedServiceTest.php
task code-analysis
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

- Removed the deprecated feed types `SparkleIOFeedType`, `EventDatabaseApiFeedType` and `KobaFeedType`.
Made the unknown-feed-type handling consistent: **reads degrade, writes are rejected.** Feed sources
(and feeds) that reference a removed type keep loading — item and collection reads return them with no
exposed secrets instead of HTTP 500, and the feed data endpoint still returns an empty result — while
creating or updating a feed source with an unknown feed type now returns HTTP 422 (was an opaque 500).
Run the new `app:feed:remove-deprecated-feed-sources` command to review and `--force`-remove the
inert feed sources together with their feeds and slides; `app:update` prints a notice when any exist.
Migrate event database feeds to `EventDatabaseApiV2FeedType`. See `UPGRADE.md`.
- Decoupled the dev compose stack from `itkdev-docker`: dropped the wrapper overlays in
favour of a self-contained stack with bundled traefik (opt-in via `COMPOSE_PROFILES=traefik`)
and a `docker-compose.shared-frontend.yml` overlay for devs keeping a host-level traefik.
Expand Down
21 changes: 21 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,24 @@ docker compose exec phpfpm bin/console app:screen-layouts:install
- Use `--all` option for installing all available templates.
- Use `--update` option for updating existing templates.
- Use `--cleanupRegions` option for cleaning up regions that are no longer connected to a layout.

#### 8 - Clean up feed sources using removed feed types

The feed types `SparkleIOFeedType`, `EventDatabaseApiFeedType` and `KobaFeedType` were deprecated in
2.x and **removed in 3.0**. Unknown feed types are now handled consistently: **reads degrade, writes are
rejected.** Feed sources that still reference a removed type keep loading (item and collection reads
return them with no exposed secrets, and feed data endpoints return empty) but can no longer fetch data;
creating or updating a feed source with such a type returns HTTP 422. `app:update` prints a notice when
any such feed sources exist.

Review them, then remove them together with their feeds and slides:

```shell
# Report feed sources referencing a removed feed type (no changes made).
docker compose exec phpfpm bin/console app:feed:remove-deprecated-feed-sources

# Remove them, their feeds and the slides bound to those feeds.
docker compose exec phpfpm bin/console app:feed:remove-deprecated-feed-sources --force
```

Event database feeds should be recreated using `EventDatabaseApiV2FeedType`.
9 changes: 0 additions & 9 deletions assets/admin/components/feed-sources/feed-source-form.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import ContentBody from "../util/content-body/content-body";
import FormInput from "../util/forms/form-input";
import CalendarApiFeedType from "./templates/calendar-api-feed-type";
import NotifiedFeedType from "./templates/notified-feed-type";
import EventDatabaseApiFeedType from "./templates/event-database-feed-type";
import ColiboFeedType from "./templates/colibo-feed-type";
import StickyFooter from "../util/sticky-footer";
import EventDatabaseApiV2FeedType from "./templates/event-database-v2-feed-type";
Expand Down Expand Up @@ -133,14 +132,6 @@ function FeedSourceForm({
feedSourceId={feedSource["@id"]}
/>
)}
{feedSource?.feedType ===
"App\\Feed\\EventDatabaseApiFeedType" && (
<EventDatabaseApiFeedType
handleInput={handleSecretInput}
formStateObject={feedSource.secrets}
mode={mode}
/>
)}
{feedSource?.feedType ===
"App\\Feed\\EventDatabaseApiV2FeedType" && (
<EventDatabaseApiV2FeedType
Expand Down
8 changes: 0 additions & 8 deletions assets/admin/components/feed-sources/feed-source-manager.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,6 @@ function FeedSourceManager({
recipients: [],
},
},
{
value: "App\\Feed\\EventDatabaseApiFeedType",
title: t("dynamic-fields.event-database-api-feed-type.title"),
key: "EventDatabaseApiFeedType",
secretsDefault: {
host: "",
},
},
{
value: "App\\Feed\\EventDatabaseApiV2FeedType",
title: t("event-database-api-v2-feed-type.title"),
Expand Down

This file was deleted.

5 changes: 0 additions & 5 deletions assets/admin/translations/da/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -287,11 +287,6 @@
"title": "BRND"
},
"dynamic-fields": {
"event-database-api-feed-type": {
"title": "Eventdatabase API",
"host": "Eventdatabase API host",
"redacted-value-input-placeholder": "Skjult værdi"
},
"notified-feed-type": {
"title": "Notified",
"token": "Notified API token",
Expand Down
5 changes: 5 additions & 0 deletions config/packages/api_platform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ api_platform:
# Validation exception
ApiPlatform\Validator\Exception\ValidationException: !php/const Symfony\Component\HttpFoundation\Response::HTTP_UNPROCESSABLE_ENTITY

# Writing a feed source with a feed type that cannot be resolved (e.g. a
# type removed in 3.0.0) is a validation failure on feedType, not a 500.
# Reads degrade instead of throwing (see FeedSourceProvider::toOutput).
App\Exceptions\UnknownFeedTypeException: !php/const Symfony\Component\HttpFoundation\Response::HTTP_UNPROCESSABLE_ENTITY

# App exception mappings
App\Exceptions\BadRequestException: 400
App\Exceptions\ForbiddenException: 403
Expand Down
114 changes: 0 additions & 114 deletions phpstan-baseline.neon
Original file line number Diff line number Diff line change
Expand Up @@ -1608,42 +1608,6 @@ parameters:
count: 1
path: src/Feed/ColiboFeedType.php

-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getAdminFormOptions\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/EventDatabaseApiFeedType.php

-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getConfigOptions\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/EventDatabaseApiFeedType.php

-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getData\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/EventDatabaseApiFeedType.php

-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getRequiredConfiguration\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/EventDatabaseApiFeedType.php

-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getRequiredSecrets\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/EventDatabaseApiFeedType.php

-
message: '#^Method App\\Feed\\EventDatabaseApiFeedType\:\:getSchema\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/EventDatabaseApiFeedType.php

-
message: '#^Method App\\Feed\\EventDatabaseApiV2FeedType\:\:getAdminFormOptions\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
Expand Down Expand Up @@ -1764,48 +1728,6 @@ parameters:
count: 1
path: src/Feed/FeedTypeInterface.php

-
message: '#^Method App\\Feed\\KobaFeedType\:\:getAdminFormOptions\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/KobaFeedType.php

-
message: '#^Method App\\Feed\\KobaFeedType\:\:getBookingsFromResource\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/KobaFeedType.php

-
message: '#^Method App\\Feed\\KobaFeedType\:\:getConfigOptions\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/KobaFeedType.php

-
message: '#^Method App\\Feed\\KobaFeedType\:\:getData\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/KobaFeedType.php

-
message: '#^Method App\\Feed\\KobaFeedType\:\:getRequiredConfiguration\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/KobaFeedType.php

-
message: '#^Method App\\Feed\\KobaFeedType\:\:getRequiredSecrets\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/KobaFeedType.php

-
message: '#^Method App\\Feed\\KobaFeedType\:\:getSchema\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/KobaFeedType.php

-
message: '#^Method App\\Feed\\NotifiedFeedType\:\:getAdminFormOptions\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
Expand Down Expand Up @@ -1932,42 +1854,6 @@ parameters:
count: 1
path: src/Feed/SourceType/Colibo/ApiClient.php

-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getAdminFormOptions\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/SparkleIOFeedType.php

-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getConfigOptions\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/SparkleIOFeedType.php

-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getData\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/SparkleIOFeedType.php

-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getRequiredConfiguration\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/SparkleIOFeedType.php

-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getRequiredSecrets\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/SparkleIOFeedType.php

-
message: '#^Method App\\Feed\\SparkleIOFeedType\:\:getSchema\(\) return type has no value type specified in iterable type array\.$#'
identifier: missingType.iterableValue
count: 1
path: src/Feed/SparkleIOFeedType.php

-
message: '#^Method App\\Filter\\CampaignFilter\:\:filterProperty\(\) has parameter \$value with no type specified\.$#'
identifier: missingType.parameter
Expand Down
Loading
Loading