Der Orden des Phönix stand vor dem Stein der Weisen
Alphabetisch stimmt das, nur sieht man sie andersherum. Es gibt jetzt einen Sortiertitel: „Harry Potter 1", „Harry Potter 2" — und die Reihe steht in der Ordnung, in der man sie schaut. Leer gelassen bleibt alles wie bisher, beim Titel ohne Artikel davor. Er hat einen eigenen Merker, nicht den des Titels. Wer die Reihenfolge festlegt, soll den Titel trotzdem weiter automatisch bekommen; beides an einem Schalter hiesse, für das eine auf das andere zu verzichten. Eine Korrektur der Zuordnung lässt ihn ebenfalls stehen. Und WALL·E bleibt nicht mehr unter E. Die Kachel wurde nach einer Korrektur zwar neu beschriftet, blieb aber an ihrem Platz — jetzt rutscht sie dorthin, wo sie hingehört. Liegt der Platz im noch nicht geladenen Teil der Liste, geht sie weg und sagt wohin, statt falsch stehenzubleiben. Dazu ein Dialog, in dem alles zu einem Titel beieinander steht: Titel, Sortiertitel, Bilder, Zuordnung. Die vier Menüpunkte gab es schon, jeder für sich richtig — zusammen aber kein Ort. Wer einen Film geraderückt, macht selten nur eines davon. Die Abkürzungen bleiben. Beim Bauen gefunden: das Zurücksetzen des Sortiertitels endete in einer 500. SortTitle.For ist eine C#-Methode, und EF Core übersetzt sie nicht nach SQL. Es geht um eine Zeile — die wird jetzt geladen und zurückgeschrieben, statt die Regel ein zweites Mal als SQL zu schreiben. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
e70c33f7e2
commit
fcf40e4394
@@ -283,7 +283,22 @@ public sealed record MovieResponse(
|
||||
/// etwas sie unterscheidet — und wer einen davon sucht, findet ihn nur
|
||||
/// über die Prüfliste.
|
||||
/// </remarks>
|
||||
DateTimeOffset? PublishAt = null);
|
||||
DateTimeOffset? PublishAt = null,
|
||||
|
||||
/// <summary>Ob der Titel von Hand gesetzt wurde.</summary>
|
||||
bool TitlePinned = false,
|
||||
|
||||
/// <summary>Wonach einsortiert wird, wenn nicht nach dem Titel.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <b>Damit der Client weiss, wohin ein Titel gehört.</b> Nach einer
|
||||
/// Korrektur stand „WALL·E" weiter unter E, bis man die Seite neu lud —
|
||||
/// die Kachel wusste schlicht nicht, dass sie woandershin gehört.
|
||||
/// </remarks>
|
||||
string? SortTitle = null,
|
||||
|
||||
/// <summary>Ob der Sortiertitel von Hand gesetzt wurde.</summary>
|
||||
bool SortTitlePinned = false);
|
||||
|
||||
public sealed record CreditResponse(
|
||||
Guid Id, string Name, string Role, string? Character, string? Image);
|
||||
@@ -330,7 +345,22 @@ public sealed record SeriesResponse(
|
||||
/// Kachel nicht wissen, ob „Als gesehen markieren" oder „Als ungesehen
|
||||
/// markieren" darauf gehört — sie bot immer das erste an.
|
||||
/// </remarks>
|
||||
ItemStateResponse? State = null);
|
||||
ItemStateResponse? State = null,
|
||||
|
||||
/// <summary>Ob der Titel von Hand gesetzt wurde.</summary>
|
||||
bool TitlePinned = false,
|
||||
|
||||
/// <summary>Wonach einsortiert wird, wenn nicht nach dem Titel.</summary>
|
||||
///
|
||||
/// <remarks>
|
||||
/// <b>Damit der Client weiss, wohin ein Titel gehört.</b> Nach einer
|
||||
/// Korrektur stand „WALL·E" weiter unter E, bis man die Seite neu lud —
|
||||
/// die Kachel wusste schlicht nicht, dass sie woandershin gehört.
|
||||
/// </remarks>
|
||||
string? SortTitle = null,
|
||||
|
||||
/// <summary>Ob der Sortiertitel von Hand gesetzt wurde.</summary>
|
||||
bool SortTitlePinned = false);
|
||||
|
||||
public sealed record EpisodeResponse(
|
||||
Guid Id, int Season, int Episode, string? Title, string? Overview, string? StillImage,
|
||||
|
||||
@@ -364,6 +364,10 @@ public static class LibraryContentEndpoints
|
||||
m.PosterImage, m.BackdropImage,
|
||||
m.LogoImage, m.RuntimeMinutes, m.Rating, m.ContentRating,
|
||||
m.PublishAt,
|
||||
// **Damit die Kachel weiss, wohin sie gehört.** Nach einer
|
||||
// Korrektur stand „WALL·E" weiter unter E, bis man die
|
||||
// Seite neu lud.
|
||||
m.SortTitleValue, m.TitlePinned, m.SortTitlePinned,
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
@@ -385,7 +389,10 @@ public static class LibraryContentEndpoints
|
||||
stände.GetValueOrDefault(m.Id),
|
||||
m.LogoImage, m.RuntimeMinutes, m.Rating, null, m.ContentRating,
|
||||
OriginalTitle: m.OriginalTitle,
|
||||
PublishAt: m.PublishAt)).ToList();
|
||||
PublishAt: m.PublishAt,
|
||||
TitlePinned: m.TitlePinned,
|
||||
SortTitle: m.SortTitleValue,
|
||||
SortTitlePinned: m.SortTitlePinned)).ToList();
|
||||
|
||||
return Results.Ok(new Page<MovieResponse>(items, gesamt));
|
||||
}).RequireAuthorization();
|
||||
@@ -440,6 +447,7 @@ public static class LibraryContentEndpoints
|
||||
// ist; steht dort nichts, wartet alles.
|
||||
PublishAt = db.Episodes.Where(f => f.SeriesId == s.Id)
|
||||
.Min(f => f.PublishAt),
|
||||
s.SortTitleValue, s.TitlePinned, s.SortTitlePinned,
|
||||
})
|
||||
.ToListAsync();
|
||||
|
||||
@@ -465,7 +473,10 @@ public static class LibraryContentEndpoints
|
||||
s.LogoImage, s.RuntimeMinutes, s.Rating, null, s.ContentRating,
|
||||
OriginalTitle: s.OriginalTitle,
|
||||
PublishAt: s.PublishAt,
|
||||
State: stände.GetValueOrDefault(s.Id));
|
||||
State: stände.GetValueOrDefault(s.Id),
|
||||
TitlePinned: s.TitlePinned,
|
||||
SortTitle: s.SortTitleValue,
|
||||
SortTitlePinned: s.SortTitlePinned);
|
||||
}).ToList();
|
||||
|
||||
return Results.Ok(new Page<SeriesResponse>(items, gesamt));
|
||||
@@ -562,7 +573,10 @@ public static class LibraryContentEndpoints
|
||||
uebersetzt?.Tagline ?? film.Tagline, film.ContentRating,
|
||||
await GenresZu(db, film: id, serie: null),
|
||||
await MitwirkendeZu(db, film: id, serie: null),
|
||||
OriginalTitle: film.OriginalTitle));
|
||||
OriginalTitle: film.OriginalTitle,
|
||||
TitlePinned: film.TitlePinned,
|
||||
SortTitle: film.SortTitleValue,
|
||||
SortTitlePinned: film.SortTitlePinned));
|
||||
}).RequireAuthorization();
|
||||
|
||||
app.MapGet("/api/v1/series/{id:guid}", async (
|
||||
@@ -593,7 +607,10 @@ public static class LibraryContentEndpoints
|
||||
await GenresZu(db, film: null, serie: id),
|
||||
await MitwirkendeZu(db, film: null, serie: id),
|
||||
OriginalTitle: serie.OriginalTitle,
|
||||
State: (await ZuständeZu(db, wer, serien: [id])).GetValueOrDefault(id)));
|
||||
State: (await ZuständeZu(db, wer, serien: [id])).GetValueOrDefault(id),
|
||||
TitlePinned: serie.TitlePinned,
|
||||
SortTitle: serie.SortTitleValue,
|
||||
SortTitlePinned: serie.SortTitlePinned));
|
||||
}).RequireAuthorization();
|
||||
|
||||
// **Die nächste Folge nach dieser.** Reihenfolge ist Staffel, dann
|
||||
|
||||
@@ -51,19 +51,89 @@ public static class MatchEndpoints
|
||||
["title"] = ["Höchstens 512 Zeichen."],
|
||||
});
|
||||
|
||||
// **Der Sortiertitel bleibt, wenn er von Hand gesetzt wurde.**
|
||||
// Sonst wäre „Harry Potter 3" nach jeder Titeländerung wieder weg.
|
||||
var betroffen = art == "movies"
|
||||
? await db.Movies.Where(m => m.Id == id).ExecuteUpdateAsync(s => s
|
||||
.SetProperty(m => m.Title, titel)
|
||||
.SetProperty(m => m.SortTitleValue, SortTitle.For(titel))
|
||||
.SetProperty(m => m.SortTitleValue,
|
||||
m => m.SortTitlePinned ? m.SortTitleValue : SortTitle.For(titel))
|
||||
.SetProperty(m => m.TitlePinned, true))
|
||||
: await db.Series.Where(x => x.Id == id).ExecuteUpdateAsync(s => s
|
||||
.SetProperty(x => x.Title, titel)
|
||||
.SetProperty(x => x.SortTitleValue, SortTitle.For(titel))
|
||||
.SetProperty(x => x.SortTitleValue,
|
||||
x => x.SortTitlePinned ? x.SortTitleValue : SortTitle.For(titel))
|
||||
.SetProperty(x => x.TitlePinned, true));
|
||||
|
||||
return betroffen > 0 ? Results.NoContent() : Results.NotFound();
|
||||
}).RequireAuthorization(FlaroPolicies.Administrator);
|
||||
|
||||
// **Der Sortiertitel — wonach einsortiert wird, wenn nicht nach dem Titel.**
|
||||
//
|
||||
// Eine Reihe steht alphabetisch durcheinander: „…Orden des Phönix" vor
|
||||
// „…Stein der Weisen", weil O vor S liegt. Mit „Harry Potter 1",
|
||||
// „Harry Potter 2" stehen sie so, wie man sie sieht.
|
||||
app.MapPut("/api/v1/{art:regex(^movies|series$)}/{id:guid}/sorttitle", async (
|
||||
string art, Guid id, TitelRequest anfrage, FlaroContext db) =>
|
||||
{
|
||||
var sortiert = anfrage.Title?.Trim();
|
||||
if (string.IsNullOrEmpty(sortiert))
|
||||
{
|
||||
return Results.Problem(
|
||||
"Ein Sortiertitel darf nicht leer sein. Zum Zurücksetzen gibt es „Wieder automatisch“.",
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
}
|
||||
if (sortiert.Length > 512)
|
||||
return Results.Problem("Höchstens 512 Zeichen.",
|
||||
statusCode: StatusCodes.Status400BadRequest);
|
||||
|
||||
var betroffen = art == "movies"
|
||||
? await db.Movies.Where(m => m.Id == id).ExecuteUpdateAsync(s => s
|
||||
.SetProperty(m => m.SortTitleValue, sortiert)
|
||||
.SetProperty(m => m.SortTitlePinned, true))
|
||||
: await db.Series.Where(x => x.Id == id).ExecuteUpdateAsync(s => s
|
||||
.SetProperty(x => x.SortTitleValue, sortiert)
|
||||
.SetProperty(x => x.SortTitlePinned, true));
|
||||
|
||||
return betroffen > 0 ? Results.NoContent() : Results.NotFound();
|
||||
}).RequireAuthorization(FlaroPolicies.Administrator);
|
||||
|
||||
// **Zurück zum errechneten Sortiertitel.**
|
||||
//
|
||||
// Anders als beim Titel wird er sofort neu gebildet: er steht nirgends
|
||||
// sichtbar, und ihn bis zum nächsten Auffrischen falsch stehen zu
|
||||
// lassen hiesse, dass die Liste solange in der falschen Reihenfolge
|
||||
// steht.
|
||||
//
|
||||
// **Geladen und zurückgeschrieben, nicht in einem Rutsch.** Die Regel
|
||||
// steht in `SortTitle.For` — einer C#-Methode, die EF Core nicht nach
|
||||
// SQL übersetzen kann. Der Versuch endete in einer 500; dieselbe Regel
|
||||
// ein zweites Mal als SQL zu schreiben wäre die Variante, bei der
|
||||
// beide irgendwann auseinanderlaufen. Es geht um eine Zeile.
|
||||
app.MapDelete("/api/v1/{art:regex(^movies|series$)}/{id:guid}/sorttitle", async (
|
||||
string art, Guid id, FlaroContext db) =>
|
||||
{
|
||||
if (art == "movies")
|
||||
{
|
||||
var film = await db.Movies.FirstOrDefaultAsync(m => m.Id == id);
|
||||
if (film is null) return Results.NotFound();
|
||||
|
||||
film.SortTitleValue = SortTitle.For(film.Title);
|
||||
film.SortTitlePinned = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
var serie = await db.Series.FirstOrDefaultAsync(x => x.Id == id);
|
||||
if (serie is null) return Results.NotFound();
|
||||
|
||||
serie.SortTitleValue = SortTitle.For(serie.Title);
|
||||
serie.SortTitlePinned = false;
|
||||
}
|
||||
|
||||
await db.SaveChangesAsync();
|
||||
return Results.NoContent();
|
||||
}).RequireAuthorization(FlaroPolicies.Administrator);
|
||||
|
||||
// **Zurück zum automatischen Titel.**
|
||||
//
|
||||
// Ohne diesen Weg gäbe es keinen zurück, ausser den alten Namen zu
|
||||
|
||||
Reference in New Issue
Block a user