Extend LegacyUiRedirects from an exact-match dictionary to a two-tier matcher:
Tier 1 keeps the exact Map (now 52 entries incl. the ?kind= browse roots),
Tier 2 adds 36 ordered segment-template PatternRules for id-carrying routes.
{id} is a strict positive integer (non-int/0/neg/overflow falls through), which
also makes the rule set collision-free by construction. New AppendQueryString
helper merges the incoming query into ?kind= targets with '&' (kills the
double-'?' bug); one-line Startup change keeps the redirect GET/HEAD-only 302
before UseRouting.
Completes phase-(a) Step 1 for every PARITY-OK route (#91 phase b); the
catch-all fallback replacing MapFallbackToPage stays with the removal PR.
/media/sources/* (#202) and /system/health remain deliberately un-redirected.
fixes#204
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The OpenAPI document is generated from System.Text.Json metadata, whose
camelCase drifted from the runtime MVC serializer (Newtonsoft via
CustomContractResolver/CustomNamingStrategy): the spec said "fFmpegProfileId"
and "fFmpegProfile" while the wire emits "ffmpegProfileId" (naming-strategy
special case) and "ffmpegProfile" (ChannelResponseModel's [JsonProperty]
override). That fed the SPA the wrong keys.
Add NewtonsoftSchemaNamingTransformer, an OpenAPI schema transformer registered
on all three documents that renames each object schema's Properties (and
Required) keys through the SAME Newtonsoft contract resolver the runtime uses,
so the spec matches the wire format by construction. Regenerate v1.json.
Guard with OpenApiSerializerContractTests: serializes fully-populated DTOs
through the runtime Newtonsoft settings and pins the v1.json schema property
sets to the emitted keys, failing if generation drifts again.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a singleton ITroubleshootingPlaybackStatusStore (Core, alongside
TroubleshootingNotifier) that records the exit code + speed of the most
recent troubleshooting playback session. A new MediatR notification
handler writes to it on PlaybackTroubleshootingCompletedNotification, and
PrepareTroubleshootingPlaybackHandler resets it when a new session starts
(both the channel and media-item lock paths).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backend plumbing for the SPA transcoding editors (#143 blocker):
- FFmpeg profile response DTO round-trip: add NormalizeAudio, NormalizeVideo,
PadMode, TargetLoudness, NormalizeColors, ResolutionId (keep Resolution name
for compat); DeinterlaceVideo now bool (was bool?). #nullable enable on the DTO.
- New GET /api/ffmpeg/hardware-acceleration-kinds wrapping
GetSupportedHardwareAccelerationKinds (returns enum names as strings).
- Filler preset CRUD: GET-by-id/POST/PUT/DELETE on FillerPresetController with
full request/response DTOs; new FillerPresetFullResponseModel +
GetFillerPresetByIdForApi. CreateFillerPreset now returns the new id
(CreateFillerPresetResult) to match sibling Create commands and enable a
201+Location; updated the one Blazor call site.
- Watermark CRUD: GET-by-id/POST/PUT/DELETE on WatermarkController with full
DTOs (path+contentType, all 15 fields); new WatermarkFullResponseModel +
GetWatermarkByIdForApi.
- Startup: register WatermarkLocation/WatermarkSize as OpenAPI string enums
(they live in ErsatzTV.FFmpeg.State and were documented as ints, mismatching
the Newtonsoft StringEnumConverter runtime serialization).
- Tests: full CRUD controller tests for filler + watermark; contract-test
entries (404/401/422) for the new mutating endpoints; regenerated v1.json.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Make the ChicoryTV SPA the default UI: GET/HEAD requests to / and to
legacy Blazor routes with SPA equivalents (channels, schedules,
playouts, libraries, all settings pages, channel add) now 302 to their
/app counterparts, preserving query strings and the ETV_BASE_URL path
base. 302 not 301: the map will grow as parity lands and permanent-
redirect caching would make rollback painful.
Blazor-only functionality (collections, media browse/search, trakt,
filler presets, watermarks, ffmpeg profiles, blocks/decos/templates,
playout detail editors, logs, troubleshooting, channel edit) keeps
serving Blazor; the Blazor home stays reachable at /system/health.
Parity gaps are tracked in #140-#146; Blazor removal is phase (b).
Also adds an /app smoke assertion to the docker build workflow.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First slice of the REST API (ersatztv#2). Idiomatic REST over existing MediatR handlers; design in docs/rest-api.md.
Foundation: NotFoundError : BaseError (Core) to split 404 from 422; ApiResults mapping helpers (Either/Option -> 201+Location / 200 / 204 / 404 / 422), additive (existing *ToActionResult untouched); ApiKeyAuthorizationFilter (optional X-Api-Key on mutating actions, gated by Api:WriteKey, no-op when unset) — independent of the IPTV JWT toggle so Jellyfin/Dispatcharr reads are unaffected, applied at controller-class level so every mutating action incl. ResetPlayout is covered fail-safe.
Channels: POST /api/channels (201+Location), PUT/{id} (200/404), DELETE/{id} (204/404), GET/{id} (200/404). Request DTOs -> existing commands; responses reuse ChannelViewModel. Ported page-only validations into handlers (ShowInEpg-when-disabled, external-logo-URL, Group NotEmpty) + FFmpegProfile/Watermark/Filler existence on update for create-parity. Fixed a latent 500: the .Filter(c>0) existence pattern threw TaskCanceledException on not-found; rewritten to AnyAsync.
Tests (NUnit, TZ=UTC): handler success/404/422, ApiResults mapping, API-key filter, controller status/Location/mapping, a controller-security regression net, and a create->read->delete EF integration test on a new in-memory SQLite harness. ErsatzTV.Tests 46/46, Architecture 5/5.
Refs #34
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Add Polish localization for MainLayout
* Add Polish language option to UI settings
* Add Polish to supported UI cultures
---------
Co-authored-by: Jason Dove <1695733+jasongdove@users.noreply.github.com>
* move plex artwork removal to its own repository
* clean up old local movie artwork
* clien up old music video/artist artwork
* clean up old remote stream artwork
* clean up old song artwork
* clean up old show artwork; properly update season artwork
* Add PathBase to variant playlists
* add commented code to help with testing
* update changelog
---------
Co-authored-by: Jason Dove <1695733+jasongdove@users.noreply.github.com>
* handle artwork timeouts so they aren't reported
* catch some more cancellation errors
* add free space validation on startup
* add downgrade health check
* update dependencies
* generate openapi definitions as separate build task
* first defns
* install etv-client module in docker
* include python entrypoint in docker
* update changelog