feat(api): add media-source write API shared DTOs (#202 slice S0)
Response DTOs (ErsatzTV.Core/Api/MediaSources) and request DTOs (ErsatzTV/Controllers/Api/Requests) per the #202 design doc §B — the shared shapes that backend slices S1 (local libraries), S2 (Plex), and S3 (Jellyfin/Emby) will consume. No controllers or handler changes; DTOs are unused so far. Notable: RemoteConnectionResponseModel deliberately never carries the raw API key (secure connection contract); SaveRemoteConnectionRequest's To{Jellyfin,Emby}Command(existingApiKey) retains the existing key when the incoming ApiKey is blank/omitted. No conventions changed; nothing to update in docs/api-conventions.md.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
using ErsatzTV.Application.Libraries;
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
public record CreateLocalLibraryRequest(string Name, LibraryMediaKind MediaKind, List<string> Paths)
|
||||
{
|
||||
public CreateLocalLibrary ToCommand() => new(Name, MediaKind, Paths ?? []);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
// No command mapping — the controller checks filesystem existence directly (see design #202 §C2).
|
||||
public record LocalPathCheckRequest(string Path);
|
||||
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Application.Libraries;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
public record MoveLocalLibraryPathRequest(int TargetLibraryId)
|
||||
{
|
||||
public MoveLocalLibraryPath ToCommand(int pathId) => new(pathId, TargetLibraryId);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
// Id 0/absent = new; existing Id = update; existing row whose Id is absent from the request = delete
|
||||
// (design #202 §C4b) — plus a per-row ownership guard and nonblank validation enforced by the handler.
|
||||
public record PathReplacementItemRequest(int Id, string RemotePath, string LocalPath);
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
public record RemoteLibraryPreferenceRequest(int Id, bool ShouldSyncItems);
|
||||
@@ -0,0 +1,29 @@
|
||||
using ErsatzTV.Application.Emby;
|
||||
using ErsatzTV.Application.Jellyfin;
|
||||
using ErsatzTV.Application.Plex;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
public record ReplacePathReplacementsRequest(List<PathReplacementItemRequest> Items)
|
||||
{
|
||||
public UpdatePlexPathReplacements ToPlexCommand(int sourceId) =>
|
||||
new(
|
||||
sourceId,
|
||||
(Items ?? [])
|
||||
.Select(item => new PlexPathReplacementItem(item.Id, item.RemotePath, item.LocalPath))
|
||||
.ToList());
|
||||
|
||||
public UpdateJellyfinPathReplacements ToJellyfinCommand(int sourceId) =>
|
||||
new(
|
||||
sourceId,
|
||||
(Items ?? [])
|
||||
.Select(item => new JellyfinPathReplacementItem(item.Id, item.RemotePath, item.LocalPath))
|
||||
.ToList());
|
||||
|
||||
public UpdateEmbyPathReplacements ToEmbyCommand(int sourceId) =>
|
||||
new(
|
||||
sourceId,
|
||||
(Items ?? [])
|
||||
.Select(item => new EmbyPathReplacementItem(item.Id, item.RemotePath, item.LocalPath))
|
||||
.ToList());
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using ErsatzTV.Application.Emby;
|
||||
using ErsatzTV.Application.Jellyfin;
|
||||
using ErsatzTV.Application.Plex;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
// Shared by Plex/Jellyfin/Emby (design #202 §C4a) — the PUT body is the complete set of the
|
||||
// source's libraries; a row absent from the request is left untouched, not deleted. The controller
|
||||
// validates the id set against that source's known libraries before dispatch.
|
||||
public record ReplaceRemoteLibraryPreferencesRequest(List<RemoteLibraryPreferenceRequest> Libraries)
|
||||
{
|
||||
public UpdatePlexLibraryPreferences ToPlexCommand() =>
|
||||
new(
|
||||
(Libraries ?? [])
|
||||
.Select(item => new PlexLibraryPreference(item.Id, item.ShouldSyncItems))
|
||||
.ToList());
|
||||
|
||||
public UpdateJellyfinLibraryPreferences ToJellyfinCommand() =>
|
||||
new(
|
||||
(Libraries ?? [])
|
||||
.Select(item => new JellyfinLibraryPreference(item.Id, item.ShouldSyncItems))
|
||||
.ToList());
|
||||
|
||||
public UpdateEmbyLibraryPreferences ToEmbyCommand() =>
|
||||
new(
|
||||
(Libraries ?? [])
|
||||
.Select(item => new EmbyLibraryPreference(item.Id, item.ShouldSyncItems))
|
||||
.ToList());
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using ErsatzTV.Application.Emby;
|
||||
using ErsatzTV.Application.Jellyfin;
|
||||
using ErsatzTV.Core.Emby;
|
||||
using ErsatzTV.Core.Jellyfin;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
// ApiKey blank/omitted = retain the existing key (design #202 secure connection contract, finding 1)
|
||||
// — the caller passes the source's current key so ToCommand can substitute it when the incoming
|
||||
// value is blank. A non-blank value sets a new key. Required on first connect (no existing key),
|
||||
// which the controller/handler enforces before dispatch.
|
||||
public record SaveRemoteConnectionRequest(string Address, string ApiKey)
|
||||
{
|
||||
public SaveJellyfinSecrets ToJellyfinCommand(string existingApiKey) =>
|
||||
new(
|
||||
new JellyfinSecrets
|
||||
{
|
||||
Address = Address,
|
||||
ApiKey = string.IsNullOrWhiteSpace(ApiKey) ? existingApiKey : ApiKey
|
||||
});
|
||||
|
||||
public SaveEmbySecrets ToEmbyCommand(string existingApiKey) =>
|
||||
new(
|
||||
new EmbySecrets
|
||||
{
|
||||
Address = Address,
|
||||
ApiKey = string.IsNullOrWhiteSpace(ApiKey) ? existingApiKey : ApiKey
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using ErsatzTV.Application.Libraries;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
// Id 0/absent = new path (matches UpdateLocalLibraryHandler's normalized-path-string identity merge;
|
||||
// the Id is advisory only — see design #202 §C4c).
|
||||
public record UpdateLocalLibraryPathRequest(int Id, string Path)
|
||||
{
|
||||
public UpdateLocalLibraryPath ToCommand() => new(Id, Path);
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
using ErsatzTV.Application.Libraries;
|
||||
|
||||
namespace ErsatzTV.Controllers.Api.Requests;
|
||||
|
||||
// MediaKind is immutable after create, so it is deliberately absent here (structural immutability
|
||||
// instead of a 422 — see design #202 §C3).
|
||||
public record UpdateLocalLibraryRequest(string Name, List<UpdateLocalLibraryPathRequest> Paths)
|
||||
{
|
||||
public UpdateLocalLibrary ToCommand(int id) =>
|
||||
new(
|
||||
id,
|
||||
Name,
|
||||
(Paths ?? [])
|
||||
.Select(path => path.ToCommand())
|
||||
.ToList());
|
||||
}
|
||||
Reference in New Issue
Block a user