Halo heisst ab hier Flaro — die Marke stand im Weg

Der Upload zu App Store Connect scheiterte am Namen. „Halo" ist eine
eingetragene Marke von Microsoft; im App Store heisst zwar keine App exakt
so, aber reservierte und markenrechtlich belegte Namen tauchen in keiner
Suche auf. Der einzige verlässliche Test war die Eingabe dort, und die ging
daneben.

**Der Name wurde diesmal vorher geprüft.** Zwanzig Kandidaten gegen den
deutschen und amerikanischen App Store, auf exakte Treffer und auf ähnliche
Namen. „Kavo" wäre dieselbe Wand gewesen — exakt belegt in beiden Stores, in
Deutschland von KaVo Dental GmbH. „Flare" ist mit 42 ähnlichen Apps je Store
der am dichtesten besetzte Name der ganzen Runde. **Flaro** ist in beiden
frei und liest sich als eigenes Wort, nicht als Flare mit Tippfehler.

Das bleibt eine Store-Suche und keine Markenrecherche — reservierte Namen
ohne veröffentlichte App sieht sie nicht. Aber es ist mehr Prüfung, als beim
letzten Mal stattfand.

---

2160 Textvorkommen in 273 Dateien, 49 Pfade. Betroffen ist diesmal auch der
Server: `Flaro.Api`, `Flaro.Core`, `Flaro.Data`, `Flaro.Media`,
`Flaro.Metadata`, `FlaroContext`, `FlaroPaths`, `flaro.db`, die Variablen
`FLARO_DATA`, `FLARO_CACHE`, `FLARO_TRANSCODE`, `FLARO_WRITE_SIDECARS`, und
das Image.

**Vor dem Ersetzen geprüft**, ob „halo" irgendwo als Teil eines anderen
Wortes steckt — und ob es im TMDB-Token vorkommt. Tat es nicht; ein blindes
Ersetzen hätte den sonst zerlegt.

`docs/superpowers/specs/` bleibt wie beim letzten Mal unangetastet: datierte
Entwurfsdokumente sind ein Protokoll getroffener Entscheidungen. Das
Server-Dokument hat stattdessen einen Nachtrag bekommen, damit es niemanden
in die Irre führt.

**Die Bundle-ID wandert mit**, zum zweiten Mal an einem Tag:
`com.scarriffleservices.flaro`. Wer die vorige Fassung installiert hat, hat
jetzt zwei Karteileichen auf dem Gerät — beide löschen, dann ist Ruhe.

308 Server-Tests grün, 368 im Swift-Paket, null Warnungen. iOS und tvOS
bauen, das Image baut, und ein frischer Container läuft durch Einrichtung
und Anmeldung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-03 19:38:27 +02:00
co-authored by Claude Opus 5
parent f98b995050
commit a7cf6c5def
390 changed files with 2078 additions and 2069 deletions
+7 -7
View File
@@ -14,17 +14,17 @@ WORKDIR /src
# weil eine Liste bei jedem neuen Projekt vergessen wird — und dann scheitert
# erst der Bau im Container, nicht der auf dem Arbeitsrechner.
COPY --parents src/*/*.csproj ./
RUN dotnet restore src/Halo.Api/Halo.Api.csproj -a $TARGETARCH
RUN dotnet restore src/Flaro.Api/Flaro.Api.csproj -a $TARGETARCH
COPY . .
RUN dotnet publish src/Halo.Api/Halo.Api.csproj -a $TARGETARCH -c Release -o /app --no-restore
RUN dotnet publish src/Flaro.Api/Flaro.Api.csproj -a $TARGETARCH -c Release -o /app --no-restore
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime
# ffmpeg und die Treiber für Hardware-Beschleunigung.
#
# **Als eigenes Programm, nicht als Bibliothek.** Halo ruft es als Prozess
# **Als eigenes Programm, nicht als Bibliothek.** Flaro ruft es als Prozess
# auf; damit bleiben die Lizenzen getrennt (ffmpeg steht unter GPL) und ein
# Absturz dort nimmt den Server nicht mit.
#
@@ -57,9 +57,9 @@ RUN mkdir -p /config /cache /transcode && chown -R 1000:1000 /config /cache /tra
# braucht weder ein Startskript noch root im Container.
USER 1000:1000
ENV HALO_DATA=/config \
HALO_CACHE=/cache \
HALO_TRANSCODE=/transcode \
ENV FLARO_DATA=/config \
FLARO_CACHE=/cache \
FLARO_TRANSCODE=/transcode \
ASPNETCORE_URLS=http://+:8080 \
DOTNET_gcServer=0
@@ -69,4 +69,4 @@ COPY --from=build /app .
EXPOSE 8080
VOLUME ["/config", "/cache", "/transcode"]
ENTRYPOINT ["dotnet", "Halo.Api.dll"]
ENTRYPOINT ["dotnet", "Flaro.Api.dll"]
+15
View File
@@ -0,0 +1,15 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/Flaro.Api/Flaro.Api.csproj" />
<Project Path="src/Flaro.Core/Flaro.Core.csproj" />
<Project Path="src/Flaro.Data/Flaro.Data.csproj" />
<Project Path="src/Flaro.Media/Flaro.Media.csproj" />
<Project Path="src/Flaro.Metadata/Flaro.Metadata.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/Flaro.Api.Tests/Flaro.Api.Tests.csproj" />
<Project Path="tests/Flaro.Core.Tests/Flaro.Core.Tests.csproj" />
<Project Path="tests/Flaro.Media.Tests/Flaro.Media.Tests.csproj" />
<Project Path="tests/Flaro.Metadata.Tests/Flaro.Metadata.Tests.csproj" />
</Folder>
</Solution>
-15
View File
@@ -1,15 +0,0 @@
<Solution>
<Folder Name="/src/">
<Project Path="src/Halo.Api/Halo.Api.csproj" />
<Project Path="src/Halo.Core/Halo.Core.csproj" />
<Project Path="src/Halo.Data/Halo.Data.csproj" />
<Project Path="src/Halo.Media/Halo.Media.csproj" />
<Project Path="src/Halo.Metadata/Halo.Metadata.csproj" />
</Folder>
<Folder Name="/tests/">
<Project Path="tests/Halo.Api.Tests/Halo.Api.Tests.csproj" />
<Project Path="tests/Halo.Core.Tests/Halo.Core.Tests.csproj" />
<Project Path="tests/Halo.Media.Tests/Halo.Media.Tests.csproj" />
<Project Path="tests/Halo.Metadata.Tests/Halo.Metadata.Tests.csproj" />
</Folder>
</Solution>
+29 -29
View File
@@ -1,4 +1,4 @@
# Halo-Server
# Flaro-Server
Ein Medienserver, der dieselben Dinge kann wie Jellyfin — und zwei, die dort
nicht gehen.
@@ -7,7 +7,7 @@ nicht gehen.
Metadaten — und **Download mit allen Ton- und Textspuren**, der Grund für
dieses Projekt. Was noch fehlt: Streamen mit Umrechnen (HLS), Fortschritt und
Gesehen-Status, Live TV. Der Entwurf samt Begründungen steht
in [`docs/superpowers/specs/2026-10-03-halo-server-design.md`](../docs/superpowers/specs/2026-10-03-halo-server-design.md).
in [`docs/superpowers/specs/2026-10-03-flaro-server-design.md`](../docs/superpowers/specs/2026-10-03-flaro-server-design.md).
## Warum
@@ -35,13 +35,13 @@ Administratorkonto an. Danach verschliesst sich dieser Weg von selbst.
| Variable | Bedeutung | Voreinstellung im Image |
|---|---|---|
| `HALO_DATA` | Datenbank und Einstellungen. **Das Einzige, was gesichert werden muss.** | `/config` |
| `HALO_CACHE` | Bilder und Zwischenstände. Darf verlorengehen. | `/cache` |
| `HALO_TRANSCODE` | Kratzplatz fürs Umrechnen. Am besten auf eine SSD. | `/transcode` |
| `FLARO_DATA` | Datenbank und Einstellungen. **Das Einzige, was gesichert werden muss.** | `/config` |
| `FLARO_CACHE` | Bilder und Zwischenstände. Darf verlorengehen. | `/cache` |
| `FLARO_TRANSCODE` | Kratzplatz fürs Umrechnen. Am besten auf eine SSD. | `/transcode` |
| `ASPNETCORE_URLS` | Worauf gelauscht wird | `http://+:8080` |
| `TZ` | Zeitzone | `UTC` |
| `HALO_RATELIMIT_SIGNIN` | Anmeldeversuche je Minute und Adresse | `10` |
| `HALO_RATELIMIT_POLLING` | Nachfragen je Minute und Adresse | `120` |
| `FLARO_RATELIMIT_SIGNIN` | Anmeldeversuche je Minute und Adresse | `10` |
| `FLARO_RATELIMIT_POLLING` | Nachfragen je Minute und Adresse | `120` |
**Medienpfade stehen hier nicht.** Sie werden in der Oberfläche als
Bibliotheken angelegt und liegen in der Datenbank — sonst müsste der Container
@@ -60,7 +60,7 @@ diese Meldung stünde dort nur „unable to open database file".
### Hardware fürs Umrechnen
`/dev/dri` durchreichen — VAAPI deckt Intel wie AMD ab. Fehlt es, rechnet Halo
`/dev/dri` durchreichen — VAAPI deckt Intel wie AMD ab. Fehlt es, rechnet Flaro
in Software; das ist langsamer, aber es läuft. **Zum blossen Umpacken wird gar
keine Hardware gebraucht**, und das ist der Weg, der den meisten Beständen
hilft.
@@ -69,7 +69,7 @@ hilft.
```bash
dotnet test # 293 Tests
dotnet run --project src/Halo.Api # auf http://localhost:5000
dotnet run --project src/Flaro.Api # auf http://localhost:5000
```
Ohne gesetzte Variablen legt der Server `data/`, `cache/` und `transcode/`
@@ -77,10 +77,10 @@ neben dem Programm an.
```bash
# Image bauen, beide Architekturen
docker buildx build --platform linux/amd64,linux/arm64 -t halo:dev .
docker buildx build --platform linux/amd64,linux/arm64 -t flaro:dev .
# Eine Migration nach einer Modelländerung
dotnet ef migrations add <Name> --project src/Halo.Data --startup-project src/Halo.Api --output-dir Migrations
dotnet ef migrations add <Name> --project src/Flaro.Data --startup-project src/Flaro.Api --output-dir Migrations
```
Migrationen wandern **beim Start** des Servers, nicht über ein eigenes
@@ -90,13 +90,13 @@ Kommando.
| Projekt | Aufgabe |
|---|---|
| `Halo.Core` | Domäne: Benutzer, Anmeldung, Bibliotheken. Kennt kein HTTP und keine Datenbank |
| `Halo.Data` | EF Core, SQLite, Migrationen |
| `Halo.Media` | ffprobe und ffmpeg. Als eigene Prozesse, nicht eingebunden |
| `Halo.Metadata` | TMDB und die Bildablage. Hinter einer Schnittstelle, damit alles andere ohne Netz prüfbar bleibt |
| `Halo.Api` | Endpunkte, Authentifizierung, Auslieferung der Oberfläche |
| `Flaro.Core` | Domäne: Benutzer, Anmeldung, Bibliotheken. Kennt kein HTTP und keine Datenbank |
| `Flaro.Data` | EF Core, SQLite, Migrationen |
| `Flaro.Media` | ffprobe und ffmpeg. Als eigene Prozesse, nicht eingebunden |
| `Flaro.Metadata` | TMDB und die Bildablage. Hinter einer Schnittstelle, damit alles andere ohne Netz prüfbar bleibt |
| `Flaro.Api` | Endpunkte, Authentifizierung, Auslieferung der Oberfläche |
Was sicherheitsrelevant ist, liegt in `Halo.Core` — dort ist es ohne Server
Was sicherheitsrelevant ist, liegt in `Flaro.Core` — dort ist es ohne Server
prüfbar, und die Tests dazu sind die, die am meisten wert sind.
### Zwei Entscheidungen, die beim Lesen Fragen aufwerfen
@@ -133,7 +133,7 @@ und ändert sich dabei.
### Metadaten
**Halo bringt einen TMDB-Zugang mit** — es läuft ohne Einrichtung. Wer lieber
**Flaro bringt einen TMDB-Zugang mit** — es läuft ohne Einrichtung. Wer lieber
seinen eigenen benutzt, hinterlegt ihn unter `PUT /api/v1/settings/metadata`;
der sticht den mitgelieferten. Abgefragt werden kann nur, *ob* einer da ist und
ob es der mitgelieferte ist — der Zugang selbst kommt nie zurück.
@@ -144,8 +144,8 @@ in der Adresse, und kann nur lesen) oder der ältere **API Key**.
Die Sprache steht auf `en-US` und wird in denselben Einstellungen umgestellt.
Bilder werden einmal heruntergeladen und liegen unter `HALO_CACHE`. Die App
holt sie bei Halo, nicht bei TMDB: sonst bräuchte sie dafür Internet, und TMDB
Bilder werden einmal heruntergeladen und liegen unter `FLARO_CACHE`. Die App
holt sie bei Flaro, nicht bei TMDB: sonst bräuchte sie dafür Internet, und TMDB
erführe, wer wann was ansieht.
**Der Hinweis auf TMDB ist Pflicht, keine Höflichkeit.** Deren Bedingungen
@@ -182,12 +182,12 @@ geschehen.
**Umgepackt wird auf die Platte, nicht in die Antwort.** Der bequeme Weg wäre,
ffmpeg unmittelbar durchzureichen — dann kennt aber niemand die Länge im
Voraus: kein Fortschrittsbalken, kein Fortsetzen, kein Springen. Dafür gibt es
`HALO_TRANSCODE`.
`FLARO_TRANSCODE`.
| Variable | Bedeutung | Voreinstellung |
|---|---|---|
| `HALO_FFMPEG` | Pfad zu ffmpeg | `ffmpeg` |
| `HALO_FFPROBE` | Pfad zu ffprobe | `ffprobe` |
| `FLARO_FFMPEG` | Pfad zu ffmpeg | `ffmpeg` |
| `FLARO_FFPROBE` | Pfad zu ffprobe | `ffprobe` |
## Zuordnung korrigieren
@@ -213,7 +213,7 @@ Nur Administratoren, denn es ändert die Mediathek für alle.
## Beiwerk im Medienordner
Mit `HALO_WRITE_SIDECARS=true` legt Halo Plakate, Hintergründe und
Mit `FLARO_WRITE_SIDECARS=true` legt Flaro Plakate, Hintergründe und
NFO-Dateien neben den Film:
```
@@ -230,20 +230,20 @@ Serien bekommen `tvshow.nfo` eine Ebene über dem Staffelordner, Folgen je eine
NFO und ein Standbild neben der Datei.
**Standardmässig aus**, und die Medieneinhängung muss dafür ohne `:ro`
auskommen. Was Halo schreiben darf, steht als Erlaubnisliste im Code
auskommen. Was Flaro schreiben darf, steht als Erlaubnisliste im Code
(`SidecarNames`): alles andere wird abgelehnt, und Mediendateien werden nie
geändert, umbenannt oder gelöscht. Transcode-Zwischenstände kommen nie in
einen Medienordner — die liegen unter `HALO_TRANSCODE`.
einen Medienordner — die liegen unter `FLARO_TRANSCODE`.
**Gelesen wird auch ohne Schreiberlaubnis.** Eine vorhandene NFO ist eine
Zuordnung, die jemand getroffen hat; Halo nimmt sie und sucht gar nicht erst.
Zuordnung, die jemand getroffen hat; Flaro nimmt sie und sucht gar nicht erst.
Dasselbe gilt für Ordnernamen der Form `Der Untergang (2004) [tmdbid-613]` —
Halo liest sie, benennt aber **nichts** um. Umbenennen ist der einzige
Flaro liest sie, benennt aber **nichts** um. Umbenennen ist der einzige
Eingriff, der eine Bibliothek wirklich zerlegen kann.
| Variable | Bedeutung | Voreinstellung |
|---|---|---|
| `HALO_WRITE_SIDECARS` | Beiwerk in den Medienordner schreiben | `false` |
| `FLARO_WRITE_SIDECARS` | Beiwerk in den Medienordner schreiben | `false` |
## Anmeldung am Fernseher
+9 -9
View File
@@ -4,9 +4,9 @@
# Form ist bewährt, die Werte sind es nur dort.
services:
halo:
image: ghcr.io/scarriffle/halo:latest
container_name: halo
flaro:
image: ghcr.io/scarriffle/flaro:latest
container_name: flaro
restart: unless-stopped
ports:
@@ -27,7 +27,7 @@ services:
#
# Der Gewinn: dein Bestand wird tragbar. Ein Umzug, ein Neuaufbau der
# Datenbank oder ein zweiter Server finden Zuordnungen und Bilder wieder.
- HALO_WRITE_SIDECARS=true
- FLARO_WRITE_SIDECARS=true
# Wem die Dateien auf dem Wirt gehören. 1000:1000 ist die Voreinstellung
# im Image; wer andere Kennungen hat, trägt sie hier ein — `id -u` und
@@ -36,10 +36,10 @@ services:
volumes:
# Datenbank und Einstellungen. **Das Einzige, was gesichert werden muss.**
- /volume4/CRITICAL/Docker/Halo/config:/config
- /volume4/CRITICAL/Docker/Flaro/config:/config
# Bilder und Zwischenstände. Darf verlorengehen, baut sich neu auf.
- /volume4/CRITICAL/Docker/Halo/cache:/cache
- /volume4/CRITICAL/Docker/Flaro/cache:/cache
# Kratzplatz fürs Umrechnen. Am besten auf eine SSD und nicht dorthin,
# wo die Medien liegen — hier wird viel und kurzlebig geschrieben.
@@ -47,9 +47,9 @@ services:
# Die Medien.
#
# **Ohne `:ro`, weil HALO_WRITE_SIDECARS oben an ist.** Wer das nicht
# **Ohne `:ro`, weil FLARO_WRITE_SIDECARS oben an ist.** Wer das nicht
# will, setzt die Variable auf `false` und hängt wieder mit `:ro` ein —
# dann bleiben Bilder und Metadaten nur in Halos Cache, und der Bestand
# dann bleiben Bilder und Metadaten nur in Flaros Cache, und der Bestand
# ist gegen jeden Zugriff des Servers geschützt.
#
# Transcode-Zwischenstände kommen hier **nie** her; die liegen auf dem
@@ -60,7 +60,7 @@ services:
- /volume4/MYFLIX:/media
# Hardware fürs Umrechnen. Intel wie AMD laufen über VAAPI an derselben
# Stelle. Fehlt die Zeile, rechnet Halo in Software — langsamer, aber es
# Stelle. Fehlt die Zeile, rechnet Flaro in Software — langsamer, aber es
# läuft. Zum blossen Umpacken wird sie ohnehin nicht gebraucht.
devices:
- /dev/dri:/dev/dri
@@ -1,12 +1,12 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using Halo.Core.Auth;
using Halo.Data;
using Flaro.Core.Auth;
using Flaro.Data;
using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace Halo.Api.Auth;
namespace Flaro.Api.Auth;
/// <summary>Weist Anfragen über ein Geräte-Token aus.</summary>
///
@@ -26,11 +26,11 @@ public sealed class TokenAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
HaloContext db,
FlaroContext db,
TimeProvider clock) : AuthenticationHandler<AuthenticationSchemeOptions>(options, logger, encoder)
{
public const string SchemeName = "HaloToken";
public const string DeviceTokenIdClaim = "halo:device";
public const string SchemeName = "FlaroToken";
public const string DeviceTokenIdClaim = "flaro:device";
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
@@ -66,14 +66,14 @@ public sealed class TokenAuthenticationHandler(
new Claim(ClaimTypes.NameIdentifier, benutzer.Id.ToString()),
new Claim(ClaimTypes.Name, benutzer.UserName),
new Claim(DeviceTokenIdClaim, eintrag.Id.ToString()),
.. benutzer.IsAdministrator ? new[] { new Claim(ClaimTypes.Role, HaloRoles.Administrator) } : [],
.. benutzer.IsAdministrator ? new[] { new Claim(ClaimTypes.Role, FlaroRoles.Administrator) } : [],
], SchemeName);
return AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(ausweis), SchemeName));
}
}
public static class HaloRoles
public static class FlaroRoles
{
public const string Administrator = "administrator";
}
@@ -1,11 +1,11 @@
using System.Security.Claims;
using Halo.Api.Auth;
using Halo.Core.Auth;
using Halo.Core.Users;
using Halo.Data;
using Flaro.Api.Auth;
using Flaro.Core.Auth;
using Flaro.Core.Users;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
namespace Halo.Api.Endpoints;
namespace Flaro.Api.Endpoints;
public static class AuthEndpoints
{
@@ -23,7 +23,7 @@ public static class AuthEndpoints
public static void MapAuth(this IEndpointRouteBuilder app)
{
app.MapPost("/api/v1/auth/login", async (
LoginRequest anfrage, HaloContext db, TimeProvider clock) =>
LoginRequest anfrage, FlaroContext db, TimeProvider clock) =>
{
var name = anfrage.UserName.Trim();
var benutzer = await db.Users.FirstOrDefaultAsync(u => u.UserName == name);
@@ -46,9 +46,9 @@ public static class AuthEndpoints
var antwort = await TokenAusstellen(db, clock, benutzer, anfrage.DeviceName, anfrage.DeviceId);
await db.SaveChangesAsync();
return Results.Ok(antwort);
}).AllowAnonymous().RequireRateLimiting(HaloRateLimits.SignIn);
}).AllowAnonymous().RequireRateLimiting(FlaroRateLimits.SignIn);
app.MapPost("/api/v1/auth/logout", async (ClaimsPrincipal wer, HaloContext db, TimeProvider clock) =>
app.MapPost("/api/v1/auth/logout", async (ClaimsPrincipal wer, FlaroContext db, TimeProvider clock) =>
{
if (!Guid.TryParse(wer.FindFirstValue(TokenAuthenticationHandler.DeviceTokenIdClaim), out var id))
return Results.Unauthorized();
@@ -67,7 +67,7 @@ public static class AuthEndpoints
? Results.Ok(new AuthenticatedUser(
id,
wer.FindFirstValue(ClaimTypes.Name) ?? "",
wer.IsInRole(HaloRoles.Administrator)))
wer.IsInRole(FlaroRoles.Administrator)))
: Results.Unauthorized())
.RequireAuthorization();
@@ -77,7 +77,7 @@ public static class AuthEndpoints
/// <summary>Anmelden mit einem Code auf dem Bildschirm.</summary>
private static void MapCodeFlow(IEndpointRouteBuilder app)
{
app.MapPost("/api/v1/auth/code", async (CodeRequest anfrage, HaloContext db, TimeProvider clock) =>
app.MapPost("/api/v1/auth/code", async (CodeRequest anfrage, FlaroContext db, TimeProvider clock) =>
{
var jetzt = clock.GetUtcNow();
@@ -99,10 +99,10 @@ public static class AuthEndpoints
await db.SaveChangesAsync();
return Results.Ok(new CodeResponse(code.Code, abholschein, code.ExpiresAt));
}).AllowAnonymous().RequireRateLimiting(HaloRateLimits.SignIn);
}).AllowAnonymous().RequireRateLimiting(FlaroRateLimits.SignIn);
app.MapGet("/api/v1/auth/code/{handle}", async (
string handle, HaloContext db, TimeProvider clock) =>
string handle, FlaroContext db, TimeProvider clock) =>
{
var jetzt = clock.GetUtcNow();
var fingerabdruck = Secrets.Fingerprint(handle);
@@ -124,10 +124,10 @@ public static class AuthEndpoints
await db.SaveChangesAsync();
return Results.Ok(new CodeState("approved", antwort));
}).AllowAnonymous().RequireRateLimiting(HaloRateLimits.Polling);
}).AllowAnonymous().RequireRateLimiting(FlaroRateLimits.Polling);
app.MapPost("/api/v1/auth/code/{code}", async (
string code, ClaimsPrincipal wer, HaloContext db, TimeProvider clock) =>
string code, ClaimsPrincipal wer, FlaroContext db, TimeProvider clock) =>
{
if (!Guid.TryParse(wer.FindFirstValue(ClaimTypes.NameIdentifier), out var benutzerId))
return Results.Unauthorized();
@@ -144,7 +144,7 @@ public static class AuthEndpoints
// Damit am Telefon steht, was man da gerade hereinlässt.
return Results.Ok(new { device = offen.DeviceName });
}).RequireAuthorization().RequireRateLimiting(HaloRateLimits.SignIn);
}).RequireAuthorization().RequireRateLimiting(FlaroRateLimits.SignIn);
}
/// <summary>Stellt ein Geräte-Token aus und löst ein älteres desselben Geräts ab.</summary>
@@ -155,7 +155,7 @@ public static class AuthEndpoints
/// und nach einem Jahr weiss niemand mehr, welches Telefon welches ist.
/// </remarks>
private static async Task<LoginResponse> TokenAusstellen(
HaloContext db, TimeProvider clock, User benutzer, string geräteName, string geräteKennung)
FlaroContext db, TimeProvider clock, User benutzer, string geräteName, string geräteKennung)
{
var jetzt = clock.GetUtcNow();
var kennung = Kürzen(geräteKennung, 128);
@@ -1,4 +1,4 @@
namespace Halo.Api.Endpoints;
namespace Flaro.Api.Endpoints;
// Was über die Leitung geht. Bewusst eigene Typen und nicht die der Domäne:
// sonst wandert jede Umbenennung im Datenmodell ungefragt in die Schnittstelle
@@ -1,16 +1,16 @@
using Halo.Core.Delivery;
using Halo.Core.Library;
using Halo.Data;
using Flaro.Core.Delivery;
using Flaro.Core.Library;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
namespace Halo.Api.Endpoints;
namespace Flaro.Api.Endpoints;
public static class DeliveryEndpoints
{
public static void MapDelivery(this IEndpointRouteBuilder app)
{
app.MapGet("/api/v1/files/{id:guid}/delivery", async (
Guid id, HaloContext db, PackageCoordinator packer) =>
Guid id, FlaroContext db, PackageCoordinator packer) =>
{
var gefunden = await Laden(db, id);
if (gefunden is not var (datei, plan)) return Results.NotFound();
@@ -19,7 +19,7 @@ public static class DeliveryEndpoints
}).RequireAuthorization();
app.MapGet("/api/v1/files/{id:guid}/download", async (
Guid id, HaloContext db, PackageCoordinator packer) =>
Guid id, FlaroContext db, PackageCoordinator packer) =>
{
var gefunden = await Laden(db, id);
if (gefunden is not var (datei, plan)) return Results.NotFound();
@@ -64,7 +64,7 @@ public static class DeliveryEndpoints
}).RequireAuthorization();
}
private static async Task<(MediaFile Datei, DeliveryPlan Plan)?> Laden(HaloContext db, Guid id)
private static async Task<(MediaFile Datei, DeliveryPlan Plan)?> Laden(FlaroContext db, Guid id)
{
var datei = await db.MediaFiles.AsNoTracking().FirstOrDefaultAsync(f => f.Id == id);
if (datei is null) return null;
@@ -1,7 +1,7 @@
using Halo.Metadata;
using Flaro.Metadata;
using Microsoft.AspNetCore.Mvc;
namespace Halo.Api.Endpoints;
namespace Flaro.Api.Endpoints;
public static class ImageEndpoints
{
@@ -1,7 +1,7 @@
using Halo.Data;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
namespace Halo.Api.Endpoints;
namespace Flaro.Api.Endpoints;
public static class LibraryContentEndpoints
{
@@ -26,7 +26,7 @@ public static class LibraryContentEndpoints
/// Je Datei einzeln zu fragen hiesse: fünfzig Titel, fünfzig Abfragen —
/// und SQLite führt die nacheinander aus.
/// </remarks>
private static async Task<ILookup<Guid, MediaStreamResponse>> SpurenZu(HaloContext db, List<Guid> dateien)
private static async Task<ILookup<Guid, MediaStreamResponse>> SpurenZu(FlaroContext db, List<Guid> dateien)
{
if (dateien.Count == 0) return Array.Empty<(Guid, MediaStreamResponse)>().ToLookup(x => x.Item1, x => x.Item2);
@@ -48,7 +48,7 @@ public static class LibraryContentEndpoints
private static void MapScan(IEndpointRouteBuilder app)
{
app.MapPost("/api/v1/libraries/{id:guid}/scan", async (
Guid id, HaloContext db, ScanCoordinator koordinator) =>
Guid id, FlaroContext db, ScanCoordinator koordinator) =>
{
if (!await db.Libraries.AnyAsync(l => l.Id == id)) return Results.NotFound();
@@ -57,7 +57,7 @@ public static class LibraryContentEndpoints
: Results.Problem(
"Für diese Bibliothek läuft bereits ein Scan.",
statusCode: StatusCodes.Status409Conflict);
}).RequireAuthorization(HaloPolicies.Administrator);
}).RequireAuthorization(FlaroPolicies.Administrator);
app.MapGet("/api/v1/libraries/{id:guid}/scan", (Guid id, ScanCoordinator koordinator) =>
{
@@ -82,7 +82,7 @@ public static class LibraryContentEndpoints
private static void MapBrowse(IEndpointRouteBuilder app)
{
app.MapGet("/api/v1/movies", async (
HaloContext db, Guid? library, string? search, int skip = 0, int take = 50) =>
FlaroContext db, Guid? library, string? search, int skip = 0, int take = 50) =>
{
var abfrage = db.Movies.AsNoTracking();
if (library is { } kennung) abfrage = abfrage.Where(m => m.LibraryId == kennung);
@@ -115,7 +115,7 @@ public static class LibraryContentEndpoints
}).RequireAuthorization();
app.MapGet("/api/v1/series", async (
HaloContext db, Guid? library, string? search, int skip = 0, int take = 50) =>
FlaroContext db, Guid? library, string? search, int skip = 0, int take = 50) =>
{
var abfrage = db.Series.AsNoTracking();
if (library is { } kennung) abfrage = abfrage.Where(s => s.LibraryId == kennung);
@@ -146,7 +146,7 @@ public static class LibraryContentEndpoints
return Results.Ok(new Page<SeriesResponse>(items, gesamt));
}).RequireAuthorization();
app.MapGet("/api/v1/series/{id:guid}/episodes", async (Guid id, HaloContext db, int? season) =>
app.MapGet("/api/v1/series/{id:guid}/episodes", async (Guid id, FlaroContext db, int? season) =>
{
if (!await db.Series.AnyAsync(s => s.Id == id)) return Results.NotFound();
@@ -1,24 +1,24 @@
using Halo.Api.Auth;
using Halo.Core.Libraries;
using Halo.Core.Users;
using Halo.Data;
using Flaro.Api.Auth;
using Flaro.Core.Libraries;
using Flaro.Core.Users;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
namespace Halo.Api.Endpoints;
namespace Flaro.Api.Endpoints;
public static class ManagementEndpoints
{
public static void MapManagement(this IEndpointRouteBuilder app)
{
var benutzer = app.MapGroup("/api/v1/users").RequireAuthorization(HaloPolicies.Administrator);
var benutzer = app.MapGroup("/api/v1/users").RequireAuthorization(FlaroPolicies.Administrator);
benutzer.MapGet("/", async (HaloContext db) =>
benutzer.MapGet("/", async (FlaroContext db) =>
Results.Ok(await db.Users
.OrderBy(u => u.UserName)
.Select(u => new AuthenticatedUser(u.Id, u.UserName, u.IsAdministrator))
.ToListAsync()));
benutzer.MapPost("/", async (CreateUserRequest anfrage, HaloContext db) =>
benutzer.MapPost("/", async (CreateUserRequest anfrage, FlaroContext db) =>
{
var name = anfrage.UserName.Trim();
if (name.Length == 0 || anfrage.Password.Length < 8)
@@ -45,14 +45,14 @@ public static class ManagementEndpoints
var bibliotheken = app.MapGroup("/api/v1/libraries");
// Lesen darf jeder Angemeldete — ohne die Liste zeigt die App nichts an.
bibliotheken.MapGet("/", async (HaloContext db) =>
bibliotheken.MapGet("/", async (FlaroContext db) =>
Results.Ok(await db.Libraries
.OrderBy(l => l.Name)
.Select(l => new LibraryResponse(l.Id, l.Name, l.Kind.ToString(), l.Path))
.ToListAsync()))
.RequireAuthorization();
bibliotheken.MapPost("/", async (CreateLibraryRequest anfrage, HaloContext db) =>
bibliotheken.MapPost("/", async (CreateLibraryRequest anfrage, FlaroContext db) =>
{
if (!Enum.TryParse<LibraryKind>(anfrage.Kind, ignoreCase: true, out var art))
return Results.ValidationProblem(new Dictionary<string, string[]>
@@ -75,16 +75,16 @@ public static class ManagementEndpoints
await db.SaveChangesAsync();
return Results.Created($"/api/v1/libraries/{neu.Id}",
new LibraryResponse(neu.Id, neu.Name, neu.Kind.ToString(), neu.Path));
}).RequireAuthorization(HaloPolicies.Administrator);
}).RequireAuthorization(FlaroPolicies.Administrator);
}
}
public static class HaloPolicies
public static class FlaroPolicies
{
public const string Administrator = "administrator-only";
}
public static class HaloRateLimits
public static class FlaroRateLimits
{
/// <summary>Für alles, was ein Geheimnis prüft.</summary>
public const string SignIn = "sign-in";
@@ -1,9 +1,9 @@
using Halo.Core.Library;
using Halo.Core.Metadata;
using Halo.Data;
using Flaro.Core.Library;
using Flaro.Core.Metadata;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
namespace Halo.Api.Endpoints;
namespace Flaro.Api.Endpoints;
/// <summary>Eine falsche Zuordnung von Hand richtigstellen.</summary>
///
@@ -26,7 +26,7 @@ public static class MatchEndpoints
public static void MapMatching(this IEndpointRouteBuilder app)
{
app.MapGet("/api/v1/movies/{id:guid}/matches", async (
Guid id, HaloContext db, IMetadataProvider anbieter, string? query, int? year) =>
Guid id, FlaroContext db, IMetadataProvider anbieter, string? query, int? year) =>
{
var film = await db.Movies.AsNoTracking().FirstOrDefaultAsync(m => m.Id == id);
if (film is null) return Results.NotFound();
@@ -38,10 +38,10 @@ public static class MatchEndpoints
return Results.Ok(vorschläge.Select(v =>
new MatchCandidate(v.ProviderId, v.Title, v.Year, v.Overview, v.Poster?.Url)).ToList());
}).RequireAuthorization(HaloPolicies.Administrator);
}).RequireAuthorization(FlaroPolicies.Administrator);
app.MapPut("/api/v1/movies/{id:guid}/match", async (
Guid id, ApplyMatchRequest anfrage, HaloContext db,
Guid id, ApplyMatchRequest anfrage, FlaroContext db,
IMetadataProvider anbieter, MatchApplier anwender) =>
{
var film = await db.Movies.FirstOrDefaultAsync(m => m.Id == id);
@@ -54,10 +54,10 @@ public static class MatchEndpoints
await anwender.ApplyAsync(film, gefunden);
return Results.Ok(new MatchState(film.TmdbId, film.ImdbId, film.MatchLocked, film.Title, film.Year));
}).RequireAuthorization(HaloPolicies.Administrator);
}).RequireAuthorization(FlaroPolicies.Administrator);
app.MapGet("/api/v1/series/{id:guid}/matches", async (
Guid id, HaloContext db, IMetadataProvider anbieter, string? query, int? year) =>
Guid id, FlaroContext db, IMetadataProvider anbieter, string? query, int? year) =>
{
var serie = await db.Series.AsNoTracking().FirstOrDefaultAsync(s => s.Id == id);
if (serie is null) return Results.NotFound();
@@ -67,10 +67,10 @@ public static class MatchEndpoints
return Results.Ok(vorschläge.Select(v =>
new MatchCandidate(v.ProviderId, v.Title, v.Year, v.Overview, v.Poster?.Url)).ToList());
}).RequireAuthorization(HaloPolicies.Administrator);
}).RequireAuthorization(FlaroPolicies.Administrator);
app.MapPut("/api/v1/series/{id:guid}/match", async (
Guid id, ApplyMatchRequest anfrage, HaloContext db,
Guid id, ApplyMatchRequest anfrage, FlaroContext db,
IMetadataProvider anbieter, MatchApplier anwender) =>
{
var serie = await db.Series.FirstOrDefaultAsync(s => s.Id == id);
@@ -83,6 +83,6 @@ public static class MatchEndpoints
await anwender.ApplyAsync(serie, gefunden);
return Results.Ok(new MatchState(serie.TmdbId, serie.ImdbId, serie.MatchLocked, serie.Title, serie.Year));
}).RequireAuthorization(HaloPolicies.Administrator);
}).RequireAuthorization(FlaroPolicies.Administrator);
}
}
@@ -1,15 +1,15 @@
using Halo.Core;
using Halo.Data;
using Halo.Metadata;
using Flaro.Core;
using Flaro.Data;
using Flaro.Metadata;
using Microsoft.EntityFrameworkCore;
namespace Halo.Api.Endpoints;
namespace Flaro.Api.Endpoints;
public static class SettingsEndpoints
{
public static void MapSettings(this IEndpointRouteBuilder app)
{
app.MapGet("/api/v1/settings/metadata", async (HaloContext db) =>
app.MapGet("/api/v1/settings/metadata", async (FlaroContext db) =>
{
var zeilen = await db.Settings.AsNoTracking()
.Where(s => s.Key == DatabaseTmdbSettings.ApiKeyName || s.Key == DatabaseTmdbSettings.LanguageName)
@@ -28,10 +28,10 @@ public static class SettingsEndpoints
// Damit in der Oberfläche steht, ob der mitgelieferte Schlüssel
// benutzt wird — und dass ein eigener ihn ablösen würde.
UsingProjectKey: aufgelöst.UsingProjectKey));
}).RequireAuthorization(HaloPolicies.Administrator);
}).RequireAuthorization(FlaroPolicies.Administrator);
app.MapPut("/api/v1/settings/metadata", async (
MetadataSettingsRequest anfrage, HaloContext db) =>
MetadataSettingsRequest anfrage, FlaroContext db) =>
{
if (anfrage.ApiKey is not null) await Setzen(db, DatabaseTmdbSettings.ApiKeyName, anfrage.ApiKey.Trim());
if (anfrage.Language is not null)
@@ -49,10 +49,10 @@ public static class SettingsEndpoints
await db.SaveChangesAsync();
return Results.NoContent();
}).RequireAuthorization(HaloPolicies.Administrator);
}).RequireAuthorization(FlaroPolicies.Administrator);
}
private static async Task Setzen(HaloContext db, string schlüssel, string wert)
private static async Task Setzen(FlaroContext db, string schlüssel, string wert)
{
var vorhanden = await db.Settings.FindAsync(schlüssel);
if (vorhanden is null) db.Settings.Add(new ServerSetting { Key = schlüssel, Value = wert });
@@ -1,9 +1,9 @@
using Halo.Core.Users;
using Halo.Metadata;
using Halo.Data;
using Flaro.Core.Users;
using Flaro.Metadata;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
namespace Halo.Api.Endpoints;
namespace Flaro.Api.Endpoints;
public static class SystemEndpoints
{
@@ -18,7 +18,7 @@ public static class SystemEndpoints
public static void MapSystem(this IEndpointRouteBuilder app)
{
app.MapGet("/api/v1/system/info", async (HaloContext db) =>
app.MapGet("/api/v1/system/info", async (FlaroContext db) =>
Results.Ok(new SystemInfo(
Version: typeof(SystemEndpoints).Assembly.GetName().Version?.ToString(3) ?? "0.0.0",
NeedsSetup: !await db.Users.AnyAsync(),
@@ -30,7 +30,7 @@ public static class SystemEndpoints
])))
.AllowAnonymous();
app.MapPost("/api/v1/system/setup", async (SetupRequest anfrage, HaloContext db) =>
app.MapPost("/api/v1/system/setup", async (SetupRequest anfrage, FlaroContext db) =>
{
if (string.IsNullOrWhiteSpace(anfrage.UserName) || anfrage.Password.Length < 8)
return Results.ValidationProblem(new Dictionary<string, string[]>
@@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<ItemGroup>
<ProjectReference Include="..\Halo.Core\Halo.Core.csproj" />
<ProjectReference Include="..\Halo.Data\Halo.Data.csproj" />
<ProjectReference Include="..\Halo.Metadata\Halo.Metadata.csproj" />
<ProjectReference Include="..\Halo.Media\Halo.Media.csproj" />
<ProjectReference Include="..\Flaro.Core\Flaro.Core.csproj" />
<ProjectReference Include="..\Flaro.Data\Flaro.Data.csproj" />
<ProjectReference Include="..\Flaro.Metadata\Flaro.Metadata.csproj" />
<ProjectReference Include="..\Flaro.Media\Flaro.Media.csproj" />
</ItemGroup>
<ItemGroup>
@@ -1,4 +1,4 @@
namespace Halo.Api;
namespace Flaro.Api;
/// <summary>Wo der Server seine Sachen ablegt.</summary>
///
@@ -21,26 +21,26 @@ namespace Halo.Api;
/// Container neu starten, um eine Bibliothek hinzuzufügen.
/// </para>
/// </remarks>
public sealed class HaloPaths
public sealed class FlaroPaths
{
public required string Data { get; init; }
public required string Cache { get; init; }
public required string Transcode { get; init; }
public string DatabaseFile => Path.Combine(Data, "halo.db");
public string DatabaseFile => Path.Combine(Data, "flaro.db");
public static HaloPaths FromEnvironment(string contentRoot)
public static FlaroPaths FromEnvironment(string contentRoot)
{
string lies(string name, string voreinstellung) =>
Environment.GetEnvironmentVariable(name) is { Length: > 0 } wert
? wert
: Path.Combine(contentRoot, voreinstellung);
return new HaloPaths
return new FlaroPaths
{
Data = lies("HALO_DATA", "data"),
Cache = lies("HALO_CACHE", "cache"),
Transcode = lies("HALO_TRANSCODE", "transcode"),
Data = lies("FLARO_DATA", "data"),
Cache = lies("FLARO_CACHE", "cache"),
Transcode = lies("FLARO_TRANSCODE", "transcode"),
};
}
@@ -62,13 +62,13 @@ public sealed class HaloPaths
/// </remarks>
public void EnsureExists()
{
foreach (var (name, pfad) in new[] { ("HALO_DATA", Data), ("HALO_CACHE", Cache), ("HALO_TRANSCODE", Transcode) })
foreach (var (name, pfad) in new[] { ("FLARO_DATA", Data), ("FLARO_CACHE", Cache), ("FLARO_TRANSCODE", Transcode) })
{
try
{
Directory.CreateDirectory(pfad);
var probe = Path.Combine(pfad, $".halo-schreibprobe-{Environment.ProcessId}");
var probe = Path.Combine(pfad, $".flaro-schreibprobe-{Environment.ProcessId}");
File.WriteAllBytes(probe, []);
File.Delete(probe);
}
@@ -78,7 +78,7 @@ public sealed class HaloPaths
$"""
{name} zeigt auf „{pfad}“, und dort lässt sich nicht schreiben.
Halo läuft als Benutzer {Kennung()}. Dieses Verzeichnis muss
Flaro läuft als Benutzer {Kennung()}. Dieses Verzeichnis muss
ihm gehören oder für ihn beschreibbar sein.
Im Container ist das fast immer die Einhängung: entweder gehört
@@ -1,9 +1,9 @@
using Halo.Core.Library;
using Halo.Core.Metadata;
using Halo.Data;
using Flaro.Core.Library;
using Flaro.Core.Metadata;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
namespace Halo.Api;
namespace Flaro.Api;
/// <summary>Schreibt eine korrigierte Zuordnung überallhin, wo sie hingehört.</summary>
///
@@ -14,7 +14,7 @@ namespace Halo.Api;
/// Entscheidung wieder überstimmt.
/// </remarks>
public sealed class MatchApplier(
HaloContext db,
FlaroContext db,
IMetadataProvider anbieter,
IImageStore bilder,
IImageReader bildLeser,
@@ -1,20 +1,20 @@
using Halo.Data;
using Halo.Metadata;
using Flaro.Data;
using Flaro.Metadata;
using Microsoft.EntityFrameworkCore;
namespace Halo.Api;
namespace Flaro.Api;
/// <summary>Holt die Metadaten-Einstellungen aus der Datenbank.</summary>
///
/// <remarks>
/// <b>Hier und nicht in <c>Halo.Data</c>:</b> sonst müsste die Datenschicht
/// <b>Hier und nicht in <c>Flaro.Data</c>:</b> sonst müsste die Datenschicht
/// den Metadaten-Anbieter kennen. So zeigen beide Richtungen auf den Kern und
/// keine aufeinander.
///
/// Je Anfrage einmal gelesen und dann gemerkt. Ohne das fragte ein Scan über
/// tausend Titel tausendmal dieselbe Zeile ab.
/// </remarks>
public sealed class DatabaseTmdbSettings(HaloContext db) : ITmdbSettingsSource
public sealed class DatabaseTmdbSettings(FlaroContext db) : ITmdbSettingsSource
{
public const string ApiKeyName = "metadata.tmdb.apiKey";
public const string LanguageName = "metadata.language";
@@ -1,11 +1,11 @@
using System.Collections.Concurrent;
using Halo.Core.Delivery;
using Halo.Core.Library;
using Halo.Data;
using Halo.Media;
using Flaro.Core.Delivery;
using Flaro.Core.Library;
using Flaro.Data;
using Flaro.Media;
using Microsoft.EntityFrameworkCore;
namespace Halo.Api;
namespace Flaro.Api;
public sealed record PackageState(
string Status,
@@ -27,12 +27,12 @@ public sealed record PackageState(
/// </para>
/// <para>
/// Der Preis ist Plattenplatz und Wartezeit vor dem ersten Byte. Dafür gibt
/// es <c>HALO_TRANSCODE</c>, und dafür meldet dieser Weg einen Fortschritt.
/// es <c>FLARO_TRANSCODE</c>, und dafür meldet dieser Weg einen Fortschritt.
/// </para>
/// </remarks>
public sealed class PackageCoordinator(
IServiceScopeFactory scopes,
HaloPaths pfade,
FlaroPaths pfade,
TimeProvider clock,
ILogger<PackageCoordinator> log)
{
@@ -77,7 +77,7 @@ public sealed class PackageCoordinator(
try
{
using var bereich = scopes.CreateScope();
var db = bereich.ServiceProvider.GetRequiredService<HaloContext>();
var db = bereich.ServiceProvider.GetRequiredService<FlaroContext>();
var datei = await db.MediaFiles.AsNoTracking().FirstOrDefaultAsync(f => f.Id == fileId)
?? throw new InvalidOperationException("Diese Datei steht nicht mehr in der Mediathek.");
@@ -1,24 +1,24 @@
using System.Threading.RateLimiting;
using Halo.Api;
using Halo.Api.Auth;
using Halo.Api.Endpoints;
using Halo.Core.Metadata;
using Halo.Data;
using Halo.Core.Library;
using Halo.Media;
using Halo.Metadata;
using Flaro.Api;
using Flaro.Api.Auth;
using Flaro.Api.Endpoints;
using Flaro.Core.Metadata;
using Flaro.Data;
using Flaro.Core.Library;
using Flaro.Media;
using Flaro.Metadata;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
var pfade = HaloPaths.FromEnvironment(builder.Environment.ContentRootPath);
var pfade = FlaroPaths.FromEnvironment(builder.Environment.ContentRootPath);
pfade.EnsureExists();
builder.Services.AddSingleton(pfade);
builder.Services.AddSingleton(TimeProvider.System);
builder.Services.AddDbContext<HaloContext>(o => o.UseSqlite($"Data Source={pfade.DatabaseFile}"));
builder.Services.AddDbContext<FlaroContext>(o => o.UseSqlite($"Data Source={pfade.DatabaseFile}"));
builder.Services
.AddAuthentication(TokenAuthenticationHandler.SchemeName)
@@ -31,9 +31,9 @@ builder.Services.AddAuthorizationBuilder()
// dem irgendwann einer vergessen wird.
.SetFallbackPolicy(new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder()
.RequireAuthenticatedUser().Build())
.AddPolicy(HaloPolicies.Administrator, p => p.RequireRole(HaloRoles.Administrator));
.AddPolicy(FlaroPolicies.Administrator, p => p.RequireRole(FlaroRoles.Administrator));
// Einstellbar, weil die richtige Zahl vom Aufbau abhängt: steht Halo hinter
// Einstellbar, weil die richtige Zahl vom Aufbau abhängt: steht Flaro hinter
// einem Reverse Proxy, erscheinen **alle** Benutzer unter derselben Adresse
// und teilen sich einen Eimer. Wer das nicht weiss, hält die eigene App für
// kaputt, während in Wahrheit der Nachbar im selben Haus gerade angemeldet hat.
@@ -47,16 +47,16 @@ int grenze(string name, int voreinstellung) =>
builder.Services.AddRateLimiter(o =>
{
o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
var beimAnmelden = grenze("HALO_RATELIMIT_SIGNIN", 10);
var beimNachfragen = grenze("HALO_RATELIMIT_POLLING", 120);
var beimAnmelden = grenze("FLARO_RATELIMIT_SIGNIN", 10);
var beimNachfragen = grenze("FLARO_RATELIMIT_POLLING", 120);
// Je Absender, nicht global: sonst sperrt ein einzelner Angreifer den
// ganzen Haushalt aus, und die Abwehr wäre selbst der Ausfall.
o.AddPolicy(HaloRateLimits.SignIn, anfrage => RateLimitPartition.GetFixedWindowLimiter(
o.AddPolicy(FlaroRateLimits.SignIn, anfrage => RateLimitPartition.GetFixedWindowLimiter(
anfrage.Connection.RemoteIpAddress?.ToString() ?? "unbekannt",
_ => new FixedWindowRateLimiterOptions { PermitLimit = beimAnmelden, Window = TimeSpan.FromMinutes(1) }));
o.AddPolicy(HaloRateLimits.Polling, anfrage => RateLimitPartition.GetFixedWindowLimiter(
o.AddPolicy(FlaroRateLimits.Polling, anfrage => RateLimitPartition.GetFixedWindowLimiter(
anfrage.Connection.RemoteIpAddress?.ToString() ?? "unbekannt",
_ => new FixedWindowRateLimiterOptions { PermitLimit = beimNachfragen, Window = TimeSpan.FromMinutes(1) }));
});
@@ -100,7 +100,7 @@ var app = builder.Build();
// hochfährt, soll nicht erst eine Migration von Hand anstossen müssen.
using (var bereich = app.Services.CreateScope())
{
await bereich.ServiceProvider.GetRequiredService<HaloContext>().Database.MigrateAsync();
await bereich.ServiceProvider.GetRequiredService<FlaroContext>().Database.MigrateAsync();
}
app.UseExceptionHandler();
@@ -1,7 +1,7 @@
using System.Collections.Concurrent;
using Halo.Data;
using Flaro.Data;
namespace Halo.Api;
namespace Flaro.Api;
public sealed record ScanState(
string Status,
@@ -1,6 +1,6 @@
using System.Security.Cryptography;
namespace Halo.Core.Auth;
namespace Flaro.Core.Auth;
/// <summary>Eine laufende Anmeldung per Code — für den Fernseher.</summary>
///
@@ -1,4 +1,4 @@
namespace Halo.Core.Auth;
namespace Flaro.Core.Auth;
/// <summary>Ein angemeldetes Gerät.</summary>
///
@@ -1,7 +1,7 @@
using System.Security.Cryptography;
using System.Text;
namespace Halo.Core.Auth;
namespace Flaro.Core.Auth;
/// <summary>Erzeugt und verfingert Geheimnisse, die Clients vorzeigen.</summary>
///
@@ -1,6 +1,6 @@
using Halo.Core.Library;
using Flaro.Core.Library;
namespace Halo.Core.Delivery;
namespace Flaro.Core.Delivery;
public enum DeliveryMethod
{
@@ -1,4 +1,4 @@
namespace Halo.Core.Delivery;
namespace Flaro.Core.Delivery;
/// <summary>Was ein Gerät von sich aus öffnen kann.</summary>
///
@@ -1,4 +1,4 @@
namespace Halo.Core.Libraries;
namespace Flaro.Core.Libraries;
public enum LibraryKind { Movies, Shows }
@@ -7,7 +7,7 @@ public enum LibraryKind { Movies, Shows }
/// <remarks>
/// <b>In der Datenbank und nicht in der Umgebung.</b> Stünden Medienpfade in
/// der Compose-Datei, müsste der Container neu starten, um eine Bibliothek
/// hinzuzufügen — und ein Fremder, der Halo zum ersten Mal startet, hätte
/// hinzuzufügen — und ein Fremder, der Flaro zum ersten Mal startet, hätte
/// keine Datei, in der schon das Richtige steht.
/// </remarks>
public sealed class Library
@@ -1,7 +1,7 @@
using System.Globalization;
using System.Text.RegularExpressions;
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
public sealed record MovieName(string Title, int? Year);
@@ -1,4 +1,4 @@
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
/// <summary>Was in einer Datei steckt.</summary>
public sealed record ProbeResult(
@@ -1,4 +1,4 @@
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
/// <summary>Legt Beiwerk neben eine Mediendatei.</summary>
///
@@ -1,4 +1,4 @@
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
/// <summary>Eine gefundene Datei, so wie das Dateisystem sie beschreibt.</summary>
public sealed record ScannedFile(string Path, long SizeBytes, DateTimeOffset ModifiedAt);
@@ -1,4 +1,4 @@
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
/// <summary>Eine Datei auf der Platte, so wie der Scan sie vorgefunden hat.</summary>
///
@@ -1,4 +1,4 @@
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
/// <summary>Welche Dateien überhaupt als Medien gelten.</summary>
///
@@ -1,4 +1,4 @@
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
public enum MediaStreamKind { Video, Audio, Subtitle }
@@ -1,7 +1,7 @@
using System.Xml;
using System.Xml.Linq;
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
public sealed record MovieNfo(
string Title, int? Year, string? Plot, ProviderIds Ids, bool Locked);
@@ -132,7 +132,7 @@ public static class Nfo
///
/// <remarks>
/// <b>DTDs sind abgeschaltet und es wird nichts nachgeladen.</b> Diese
/// Dateien liegen im Medienordner, und was dort liegt, hat Halo nicht
/// Dateien liegen im Medienordner, und was dort liegt, hat Flaro nicht
/// geschrieben — eine NFO mit einer externen Entität könnte sonst den
/// Server dazu bringen, fremde Dateien zu lesen und auszuliefern.
/// </remarks>
@@ -1,6 +1,6 @@
using System.Text.RegularExpressions;
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
/// <summary>Kennungen bei Metadatenquellen, wie sie in Namen stehen können.</summary>
public sealed record ProviderIds(int? Tmdb, string? Imdb)
@@ -16,10 +16,10 @@ public sealed record ProviderIds(int? Tmdb, string? Imdb)
/// <b>Die Form stammt von Kodi und wird von Jellyfin gelesen:</b>
/// <c>Der Untergang (2004) [tmdbid-613]</c>. Wer seine Ordner so benennt —
/// von Hand oder über Radarr —, hat seine Zuordnung damit festgeschrieben,
/// und Halo soll sie nicht erneut erraten.
/// und Flaro soll sie nicht erneut erraten.
/// </para>
/// <para>
/// <b>Halo benennt nichts um.</b> Es liest nur. Festgeschrieben wird eine
/// <b>Flaro benennt nichts um.</b> Es liest nur. Festgeschrieben wird eine
/// Korrektur in der NFO-Datei — dieselbe Wirkung, ohne den einen Eingriff,
/// der eine Bibliothek wirklich zerlegen kann.
/// </para>
@@ -1,6 +1,6 @@
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
/// <summary>Welche Dateien Halo neben eine Mediendatei legen darf — und sonst keine.</summary>
/// <summary>Welche Dateien Flaro neben eine Mediendatei legen darf — und sonst keine.</summary>
///
/// <remarks>
/// <para>
@@ -17,7 +17,7 @@ namespace Halo.Core.Library;
/// </para>
/// <para>
/// <b>Transcode-Zwischenstände gehören ausdrücklich nicht hierher.</b> Die
/// liegen unter <c>HALO_TRANSCODE</c>, auf einem eigenen Volume. Ein
/// liegen unter <c>FLARO_TRANSCODE</c>, auf einem eigenen Volume. Ein
/// Medienordner ist kein Kratzplatz.
/// </para>
/// </remarks>
@@ -1,4 +1,4 @@
namespace Halo.Core.Library;
namespace Flaro.Core.Library;
/// <summary>Der Titel, nach dem sortiert wird — ohne den Artikel davor.</summary>
///
@@ -1,4 +1,4 @@
namespace Halo.Core.Metadata;
namespace Flaro.Core.Metadata;
/// <summary>Ein Bild bei der Quelle — noch nicht heruntergeladen.</summary>
public sealed record RemoteImage(string Url);
@@ -1,4 +1,4 @@
namespace Halo.Core;
namespace Flaro.Core;
/// <summary>Eine Einstellung des Servers, als Zeile in der Datenbank.</summary>
///
@@ -2,7 +2,7 @@ using System.Security.Cryptography;
using System.Text;
using Konscious.Security.Cryptography;
namespace Halo.Core.Users;
namespace Flaro.Core.Users;
/// <summary>Passwörter als Argon2id, im PHC-Format abgelegt.</summary>
///
@@ -1,4 +1,4 @@
namespace Halo.Core.Users;
namespace Flaro.Core.Users;
/// <summary>Ein Benutzerkonto.</summary>
///
@@ -1,6 +1,6 @@
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Halo.Data;
namespace Flaro.Data;
/// <summary>Zeitpunkte als Unix-Millisekunden statt als Text.</summary>
///
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Halo.Core\Halo.Core.csproj" />
<ProjectReference Include="..\Flaro.Core\Flaro.Core.csproj" />
</ItemGroup>
<ItemGroup>
@@ -1,11 +1,11 @@
using Halo.Core;
using Halo.Core.Auth;
using Halo.Core.Libraries;
using Halo.Core.Library;
using Halo.Core.Users;
using Flaro.Core;
using Flaro.Core.Auth;
using Flaro.Core.Libraries;
using Flaro.Core.Library;
using Flaro.Core.Users;
using Microsoft.EntityFrameworkCore;
namespace Halo.Data;
namespace Flaro.Data;
/// <summary>Die Datenbank des Servers.</summary>
///
@@ -19,7 +19,7 @@ namespace Halo.Data;
/// nicht — dann ist der Umstieg auf PostgreSQL fällig, und deshalb steht hier
/// nirgends SQLite-eigenes SQL.
/// </remarks>
public sealed class HaloContext(DbContextOptions<HaloContext> options) : DbContext(options)
public sealed class FlaroContext(DbContextOptions<FlaroContext> options) : DbContext(options)
{
public DbSet<User> Users => Set<User>();
public DbSet<DeviceToken> DeviceTokens => Set<DeviceToken>();
@@ -1,9 +1,9 @@
using Halo.Core.Libraries;
using Halo.Core.Library;
using Flaro.Core.Libraries;
using Flaro.Core.Library;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Halo.Data;
namespace Flaro.Data;
public sealed record ScanReport(
int FilesFound,
@@ -31,7 +31,7 @@ public sealed record ScanReport(
/// </para>
/// </remarks>
public sealed class LibraryScanner(
HaloContext db, IMediaProbe sonde, TimeProvider clock, ILogger<LibraryScanner> log)
FlaroContext db, IMediaProbe sonde, TimeProvider clock, ILogger<LibraryScanner> log)
{
public async Task<ScanReport> ScanAsync(Guid libraryId, CancellationToken ct = default)
{
@@ -1,9 +1,9 @@
using Halo.Core.Library;
using Halo.Core.Metadata;
using Flaro.Core.Library;
using Flaro.Core.Metadata;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace Halo.Data;
namespace Flaro.Data;
public sealed record EnrichReport(
int MoviesMatched, int SeriesMatched, int EpisodesMatched, bool Skipped, string? Error);
@@ -25,7 +25,7 @@ public sealed record EnrichReport(
/// </para>
/// </remarks>
public sealed class MetadataEnricher(
HaloContext db,
FlaroContext db,
IMetadataProvider anbieter,
IImageStore bilder,
IImageReader bildLeser,
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using Halo.Data;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -8,9 +8,9 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
[DbContext(typeof(HaloContext))]
[DbContext(typeof(FlaroContext))]
[Migration("20261003095500_Initial")]
partial class Initial
{
@@ -20,7 +20,7 @@ namespace Halo.Data.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
modelBuilder.Entity("Halo.Core.Auth.AuthCode", b =>
modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -68,7 +68,7 @@ namespace Halo.Data.Migrations
b.ToTable("AuthCodes");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -111,7 +111,7 @@ namespace Halo.Data.Migrations
b.ToTable("DeviceTokens");
});
modelBuilder.Entity("Halo.Core.Libraries.Library", b =>
modelBuilder.Entity("Flaro.Core.Libraries.Library", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -140,7 +140,7 @@ namespace Halo.Data.Migrations
b.ToTable("Libraries");
});
modelBuilder.Entity("Halo.Core.ServerSetting", b =>
modelBuilder.Entity("Flaro.Core.ServerSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -156,7 +156,7 @@ namespace Halo.Data.Migrations
b.ToTable("Settings");
});
modelBuilder.Entity("Halo.Core.Users.User", b =>
modelBuilder.Entity("Flaro.Core.Users.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -187,9 +187,9 @@ namespace Halo.Data.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.HasOne("Halo.Core.Users.User", null)
b.HasOne("Flaro.Core.Users.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
/// <inheritdoc />
public partial class Initial : Migration
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using Halo.Data;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -8,9 +8,9 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
[DbContext(typeof(HaloContext))]
[DbContext(typeof(FlaroContext))]
[Migration("20261003115256_Mediathek")]
partial class Mediathek
{
@@ -20,7 +20,7 @@ namespace Halo.Data.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
modelBuilder.Entity("Halo.Core.Auth.AuthCode", b =>
modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -68,7 +68,7 @@ namespace Halo.Data.Migrations
b.ToTable("AuthCodes");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -111,7 +111,7 @@ namespace Halo.Data.Migrations
b.ToTable("DeviceTokens");
});
modelBuilder.Entity("Halo.Core.Libraries.Library", b =>
modelBuilder.Entity("Flaro.Core.Libraries.Library", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -140,7 +140,7 @@ namespace Halo.Data.Migrations
b.ToTable("Libraries");
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -173,7 +173,7 @@ namespace Halo.Data.Migrations
b.ToTable("Episodes");
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -214,7 +214,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaFiles");
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -252,7 +252,7 @@ namespace Halo.Data.Migrations
b.ToTable("Movies");
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -290,7 +290,7 @@ namespace Halo.Data.Migrations
b.ToTable("Series");
});
modelBuilder.Entity("Halo.Core.ServerSetting", b =>
modelBuilder.Entity("Flaro.Core.ServerSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -306,7 +306,7 @@ namespace Halo.Data.Migrations
b.ToTable("Settings");
});
modelBuilder.Entity("Halo.Core.Users.User", b =>
modelBuilder.Entity("Flaro.Core.Users.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -337,45 +337,45 @@ namespace Halo.Data.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.HasOne("Halo.Core.Users.User", null)
b.HasOne("Flaro.Core.Users.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.HasOne("Halo.Core.Library.Series", null)
b.HasOne("Flaro.Core.Library.Series", null)
.WithMany()
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
/// <inheritdoc />
public partial class Mediathek : Migration
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using Halo.Data;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -8,9 +8,9 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
[DbContext(typeof(HaloContext))]
[DbContext(typeof(FlaroContext))]
[Migration("20261003120100_Metadaten")]
partial class Metadaten
{
@@ -20,7 +20,7 @@ namespace Halo.Data.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
modelBuilder.Entity("Halo.Core.Auth.AuthCode", b =>
modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -68,7 +68,7 @@ namespace Halo.Data.Migrations
b.ToTable("AuthCodes");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -111,7 +111,7 @@ namespace Halo.Data.Migrations
b.ToTable("DeviceTokens");
});
modelBuilder.Entity("Halo.Core.Libraries.Library", b =>
modelBuilder.Entity("Flaro.Core.Libraries.Library", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -140,7 +140,7 @@ namespace Halo.Data.Migrations
b.ToTable("Libraries");
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -176,7 +176,7 @@ namespace Halo.Data.Migrations
b.ToTable("Episodes");
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -217,7 +217,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaFiles");
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -261,7 +261,7 @@ namespace Halo.Data.Migrations
b.ToTable("Movies");
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -305,7 +305,7 @@ namespace Halo.Data.Migrations
b.ToTable("Series");
});
modelBuilder.Entity("Halo.Core.ServerSetting", b =>
modelBuilder.Entity("Flaro.Core.ServerSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -321,7 +321,7 @@ namespace Halo.Data.Migrations
b.ToTable("Settings");
});
modelBuilder.Entity("Halo.Core.Users.User", b =>
modelBuilder.Entity("Flaro.Core.Users.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -352,45 +352,45 @@ namespace Halo.Data.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.HasOne("Halo.Core.Users.User", null)
b.HasOne("Flaro.Core.Users.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.HasOne("Halo.Core.Library.Series", null)
b.HasOne("Flaro.Core.Library.Series", null)
.WithMany()
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
@@ -2,7 +2,7 @@
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
/// <inheritdoc />
public partial class Metadaten : Migration
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using Halo.Data;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -8,9 +8,9 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
[DbContext(typeof(HaloContext))]
[DbContext(typeof(FlaroContext))]
[Migration("20261003123234_GeparsterTitel")]
partial class GeparsterTitel
{
@@ -20,7 +20,7 @@ namespace Halo.Data.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
modelBuilder.Entity("Halo.Core.Auth.AuthCode", b =>
modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -68,7 +68,7 @@ namespace Halo.Data.Migrations
b.ToTable("AuthCodes");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -111,7 +111,7 @@ namespace Halo.Data.Migrations
b.ToTable("DeviceTokens");
});
modelBuilder.Entity("Halo.Core.Libraries.Library", b =>
modelBuilder.Entity("Flaro.Core.Libraries.Library", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -140,7 +140,7 @@ namespace Halo.Data.Migrations
b.ToTable("Libraries");
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -176,7 +176,7 @@ namespace Halo.Data.Migrations
b.ToTable("Episodes");
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -217,7 +217,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaFiles");
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -265,7 +265,7 @@ namespace Halo.Data.Migrations
b.ToTable("Movies");
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -313,7 +313,7 @@ namespace Halo.Data.Migrations
b.ToTable("Series");
});
modelBuilder.Entity("Halo.Core.ServerSetting", b =>
modelBuilder.Entity("Flaro.Core.ServerSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -329,7 +329,7 @@ namespace Halo.Data.Migrations
b.ToTable("Settings");
});
modelBuilder.Entity("Halo.Core.Users.User", b =>
modelBuilder.Entity("Flaro.Core.Users.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -360,45 +360,45 @@ namespace Halo.Data.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.HasOne("Halo.Core.Users.User", null)
b.HasOne("Flaro.Core.Users.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.HasOne("Halo.Core.Library.Series", null)
b.HasOne("Flaro.Core.Library.Series", null)
.WithMany()
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
@@ -2,7 +2,7 @@
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
/// <inheritdoc />
public partial class GeparsterTitel : Migration
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using Halo.Data;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -8,9 +8,9 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
[DbContext(typeof(HaloContext))]
[DbContext(typeof(FlaroContext))]
[Migration("20261003150124_Spuren")]
partial class Spuren
{
@@ -20,7 +20,7 @@ namespace Halo.Data.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
modelBuilder.Entity("Halo.Core.Auth.AuthCode", b =>
modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -68,7 +68,7 @@ namespace Halo.Data.Migrations
b.ToTable("AuthCodes");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -111,7 +111,7 @@ namespace Halo.Data.Migrations
b.ToTable("DeviceTokens");
});
modelBuilder.Entity("Halo.Core.Libraries.Library", b =>
modelBuilder.Entity("Flaro.Core.Libraries.Library", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -140,7 +140,7 @@ namespace Halo.Data.Migrations
b.ToTable("Libraries");
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -176,7 +176,7 @@ namespace Halo.Data.Migrations
b.ToTable("Episodes");
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -230,7 +230,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaFiles");
});
modelBuilder.Entity("Halo.Core.Library.MediaStream", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -282,7 +282,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaStreams");
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -330,7 +330,7 @@ namespace Halo.Data.Migrations
b.ToTable("Movies");
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -378,7 +378,7 @@ namespace Halo.Data.Migrations
b.ToTable("Series");
});
modelBuilder.Entity("Halo.Core.ServerSetting", b =>
modelBuilder.Entity("Flaro.Core.ServerSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -394,7 +394,7 @@ namespace Halo.Data.Migrations
b.ToTable("Settings");
});
modelBuilder.Entity("Halo.Core.Users.User", b =>
modelBuilder.Entity("Flaro.Core.Users.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -425,54 +425,54 @@ namespace Halo.Data.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.HasOne("Halo.Core.Users.User", null)
b.HasOne("Flaro.Core.Users.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.HasOne("Halo.Core.Library.Series", null)
b.HasOne("Flaro.Core.Library.Series", null)
.WithMany()
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaStream", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
{
b.HasOne("Halo.Core.Library.MediaFile", null)
b.HasOne("Flaro.Core.Library.MediaFile", null)
.WithMany()
.HasForeignKey("MediaFileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
/// <inheritdoc />
public partial class Spuren : Migration
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using Halo.Data;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -8,9 +8,9 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
[DbContext(typeof(HaloContext))]
[DbContext(typeof(FlaroContext))]
[Migration("20261003165210_Zuordnung")]
partial class Zuordnung
{
@@ -20,7 +20,7 @@ namespace Halo.Data.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
modelBuilder.Entity("Halo.Core.Auth.AuthCode", b =>
modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -68,7 +68,7 @@ namespace Halo.Data.Migrations
b.ToTable("AuthCodes");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -111,7 +111,7 @@ namespace Halo.Data.Migrations
b.ToTable("DeviceTokens");
});
modelBuilder.Entity("Halo.Core.Libraries.Library", b =>
modelBuilder.Entity("Flaro.Core.Libraries.Library", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -140,7 +140,7 @@ namespace Halo.Data.Migrations
b.ToTable("Libraries");
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -176,7 +176,7 @@ namespace Halo.Data.Migrations
b.ToTable("Episodes");
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -230,7 +230,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaFiles");
});
modelBuilder.Entity("Halo.Core.Library.MediaStream", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -282,7 +282,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaStreams");
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -337,7 +337,7 @@ namespace Halo.Data.Migrations
b.ToTable("Movies");
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -392,7 +392,7 @@ namespace Halo.Data.Migrations
b.ToTable("Series");
});
modelBuilder.Entity("Halo.Core.ServerSetting", b =>
modelBuilder.Entity("Flaro.Core.ServerSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -408,7 +408,7 @@ namespace Halo.Data.Migrations
b.ToTable("Settings");
});
modelBuilder.Entity("Halo.Core.Users.User", b =>
modelBuilder.Entity("Flaro.Core.Users.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -439,54 +439,54 @@ namespace Halo.Data.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.HasOne("Halo.Core.Users.User", null)
b.HasOne("Flaro.Core.Users.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.HasOne("Halo.Core.Library.Series", null)
b.HasOne("Flaro.Core.Library.Series", null)
.WithMany()
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaStream", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
{
b.HasOne("Halo.Core.Library.MediaFile", null)
b.HasOne("Flaro.Core.Library.MediaFile", null)
.WithMany()
.HasForeignKey("MediaFileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
@@ -2,7 +2,7 @@
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
/// <inheritdoc />
public partial class Zuordnung : Migration
@@ -1,6 +1,6 @@
// <auto-generated />
using System;
using Halo.Data;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
@@ -8,9 +8,9 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
[DbContext(typeof(HaloContext))]
[DbContext(typeof(FlaroContext))]
[Migration("20261003165918_GeparstesJahr")]
partial class GeparstesJahr
{
@@ -20,7 +20,7 @@ namespace Halo.Data.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
modelBuilder.Entity("Halo.Core.Auth.AuthCode", b =>
modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -68,7 +68,7 @@ namespace Halo.Data.Migrations
b.ToTable("AuthCodes");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -111,7 +111,7 @@ namespace Halo.Data.Migrations
b.ToTable("DeviceTokens");
});
modelBuilder.Entity("Halo.Core.Libraries.Library", b =>
modelBuilder.Entity("Flaro.Core.Libraries.Library", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -140,7 +140,7 @@ namespace Halo.Data.Migrations
b.ToTable("Libraries");
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -176,7 +176,7 @@ namespace Halo.Data.Migrations
b.ToTable("Episodes");
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -230,7 +230,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaFiles");
});
modelBuilder.Entity("Halo.Core.Library.MediaStream", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -282,7 +282,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaStreams");
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -340,7 +340,7 @@ namespace Halo.Data.Migrations
b.ToTable("Movies");
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -398,7 +398,7 @@ namespace Halo.Data.Migrations
b.ToTable("Series");
});
modelBuilder.Entity("Halo.Core.ServerSetting", b =>
modelBuilder.Entity("Flaro.Core.ServerSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -414,7 +414,7 @@ namespace Halo.Data.Migrations
b.ToTable("Settings");
});
modelBuilder.Entity("Halo.Core.Users.User", b =>
modelBuilder.Entity("Flaro.Core.Users.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -445,54 +445,54 @@ namespace Halo.Data.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.HasOne("Halo.Core.Users.User", null)
b.HasOne("Flaro.Core.Users.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.HasOne("Halo.Core.Library.Series", null)
b.HasOne("Flaro.Core.Library.Series", null)
.WithMany()
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaStream", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
{
b.HasOne("Halo.Core.Library.MediaFile", null)
b.HasOne("Flaro.Core.Library.MediaFile", null)
.WithMany()
.HasForeignKey("MediaFileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
@@ -2,7 +2,7 @@
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
/// <inheritdoc />
public partial class GeparstesJahr : Migration
@@ -1,23 +1,23 @@
// <auto-generated />
using System;
using Halo.Data;
using Flaro.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Halo.Data.Migrations
namespace Flaro.Data.Migrations
{
[DbContext(typeof(HaloContext))]
partial class HaloContextModelSnapshot : ModelSnapshot
[DbContext(typeof(FlaroContext))]
partial class FlaroContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "10.0.12");
modelBuilder.Entity("Halo.Core.Auth.AuthCode", b =>
modelBuilder.Entity("Flaro.Core.Auth.AuthCode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -65,7 +65,7 @@ namespace Halo.Data.Migrations
b.ToTable("AuthCodes");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -108,7 +108,7 @@ namespace Halo.Data.Migrations
b.ToTable("DeviceTokens");
});
modelBuilder.Entity("Halo.Core.Libraries.Library", b =>
modelBuilder.Entity("Flaro.Core.Libraries.Library", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -137,7 +137,7 @@ namespace Halo.Data.Migrations
b.ToTable("Libraries");
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -173,7 +173,7 @@ namespace Halo.Data.Migrations
b.ToTable("Episodes");
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -227,7 +227,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaFiles");
});
modelBuilder.Entity("Halo.Core.Library.MediaStream", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -279,7 +279,7 @@ namespace Halo.Data.Migrations
b.ToTable("MediaStreams");
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -337,7 +337,7 @@ namespace Halo.Data.Migrations
b.ToTable("Movies");
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -395,7 +395,7 @@ namespace Halo.Data.Migrations
b.ToTable("Series");
});
modelBuilder.Entity("Halo.Core.ServerSetting", b =>
modelBuilder.Entity("Flaro.Core.ServerSetting", b =>
{
b.Property<string>("Key")
.HasMaxLength(128)
@@ -411,7 +411,7 @@ namespace Halo.Data.Migrations
b.ToTable("Settings");
});
modelBuilder.Entity("Halo.Core.Users.User", b =>
modelBuilder.Entity("Flaro.Core.Users.User", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
@@ -442,54 +442,54 @@ namespace Halo.Data.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("Halo.Core.Auth.DeviceToken", b =>
modelBuilder.Entity("Flaro.Core.Auth.DeviceToken", b =>
{
b.HasOne("Halo.Core.Users.User", null)
b.HasOne("Flaro.Core.Users.User", null)
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Episode", b =>
modelBuilder.Entity("Flaro.Core.Library.Episode", b =>
{
b.HasOne("Halo.Core.Library.Series", null)
b.HasOne("Flaro.Core.Library.Series", null)
.WithMany()
.HasForeignKey("SeriesId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaFile", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaFile", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.MediaStream", b =>
modelBuilder.Entity("Flaro.Core.Library.MediaStream", b =>
{
b.HasOne("Halo.Core.Library.MediaFile", null)
b.HasOne("Flaro.Core.Library.MediaFile", null)
.WithMany()
.HasForeignKey("MediaFileId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Movie", b =>
modelBuilder.Entity("Flaro.Core.Library.Movie", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity("Halo.Core.Library.Series", b =>
modelBuilder.Entity("Flaro.Core.Library.Series", b =>
{
b.HasOne("Halo.Core.Libraries.Library", null)
b.HasOne("Flaro.Core.Libraries.Library", null)
.WithMany()
.HasForeignKey("LibraryId")
.OnDelete(DeleteBehavior.Cascade)
@@ -1,4 +1,4 @@
namespace Halo.Media;
namespace Flaro.Media;
/// <summary>Wo ffmpeg liegt und wie lange es brauchen darf.</summary>
///
@@ -20,6 +20,6 @@ public sealed record FfmpegOptions(string Ffmpeg = "ffmpeg", string Ffprobe = "f
public TimeSpan ProbeTimeout { get; init; } = TimeSpan.FromSeconds(30);
public static FfmpegOptions FromEnvironment() => new(
Environment.GetEnvironmentVariable("HALO_FFMPEG") is { Length: > 0 } f ? f : "ffmpeg",
Environment.GetEnvironmentVariable("HALO_FFPROBE") is { Length: > 0 } p ? p : "ffprobe");
Environment.GetEnvironmentVariable("FLARO_FFMPEG") is { Length: > 0 } f ? f : "ffmpeg",
Environment.GetEnvironmentVariable("FLARO_FFPROBE") is { Length: > 0 } p ? p : "ffprobe");
}
@@ -2,7 +2,7 @@ using System.Diagnostics;
using System.Globalization;
using Microsoft.Extensions.Logging;
namespace Halo.Media;
namespace Flaro.Media;
public sealed record RemuxOutcome(bool Success, string? Error);
@@ -1,10 +1,10 @@
using System.Diagnostics;
using System.Globalization;
using System.Text.Json;
using Halo.Core.Library;
using Flaro.Core.Library;
using Microsoft.Extensions.Logging;
namespace Halo.Media;
namespace Flaro.Media;
/// <summary>Liest die technischen Angaben einer Datei mit ffprobe.</summary>
///
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Halo.Core\Halo.Core.csproj" />
<ProjectReference Include="..\Flaro.Core\Flaro.Core.csproj" />
</ItemGroup>
<ItemGroup>
@@ -1,7 +1,7 @@
using Halo.Core.Delivery;
using Halo.Core.Library;
using Flaro.Core.Delivery;
using Flaro.Core.Library;
namespace Halo.Media;
namespace Flaro.Media;
public sealed record RemuxRequest(
string SourcePath,
@@ -1,20 +1,20 @@
using Halo.Core.Library;
using Flaro.Core.Library;
using Microsoft.Extensions.Logging;
namespace Halo.Media;
namespace Flaro.Media;
/// <summary>Ob Halo überhaupt in Medienordner schreiben darf.</summary>
/// <summary>Ob Flaro überhaupt in Medienordner schreiben darf.</summary>
///
/// <remarks>
/// <b>Standardmässig aus.</b> In fremde Medienordner zu schreiben ist nichts,
/// was ein Server ungefragt tun sollte — und für viele Aufbauten ist die
/// Einhängung ohnehin schreibgeschützt. Wer es will, setzt
/// <c>HALO_WRITE_SIDECARS=true</c> und hängt die Medien ohne <c>:ro</c> ein.
/// <c>FLARO_WRITE_SIDECARS=true</c> und hängt die Medien ohne <c>:ro</c> ein.
/// </remarks>
public sealed record SidecarOptions(bool Enabled)
{
public static SidecarOptions FromEnvironment() => new(
Environment.GetEnvironmentVariable("HALO_WRITE_SIDECARS")?.Trim()
Environment.GetEnvironmentVariable("FLARO_WRITE_SIDECARS")?.Trim()
.Equals("true", StringComparison.OrdinalIgnoreCase) == true);
}
@@ -52,7 +52,7 @@ public sealed class SidecarWriter(SidecarOptions optionen, ILogger<SidecarWriter
{
// **Gelesen wird auch, wenn nicht geschrieben werden darf.** Eine
// vorhandene NFO ist eine Zuordnung, die jemand getroffen hat — die
// zu übergehen, bloss weil Halo selbst nicht schreiben soll, wäre
// zu übergehen, bloss weil Flaro selbst nicht schreiben soll, wäre
// verkehrt herum.
Pruefen(beiwerkName);
var pfad = Pfad(mediendatei, beiwerkName);
@@ -92,7 +92,7 @@ public sealed class SidecarWriter(SidecarOptions optionen, ILogger<SidecarWriter
if (!optionen.Enabled) return;
var ziel = Pfad(mediendatei, beiwerkName);
var halbfertig = ziel + ".halo-tmp";
var halbfertig = ziel + ".flaro-tmp";
try
{
@@ -117,7 +117,7 @@ public sealed class SidecarWriter(SidecarOptions optionen, ILogger<SidecarWriter
if (SidecarNames.MayWrite(beiwerkName)) return;
throw new InvalidOperationException(
$"„{beiwerkName}“ steht nicht auf der Liste der Dateien, die Halo anlegen darf. " +
$"„{beiwerkName}“ steht nicht auf der Liste der Dateien, die Flaro anlegen darf. " +
"Siehe SidecarNames — das ist der Schutz des Bestands, und er wird nicht umgangen.");
}
}
@@ -1,4 +1,4 @@
namespace Halo.Metadata;
namespace Flaro.Metadata;
/// <summary>Was TMDB verlangt, wenn man ihre Daten benutzt.</summary>
///
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<ProjectReference Include="..\Halo.Core\Halo.Core.csproj" />
<ProjectReference Include="..\Flaro.Core\Flaro.Core.csproj" />
</ItemGroup>
<ItemGroup>
@@ -1,9 +1,9 @@
using System.Security.Cryptography;
using System.Text;
using Halo.Core.Metadata;
using Flaro.Core.Metadata;
using Microsoft.Extensions.Logging;
namespace Halo.Metadata;
namespace Flaro.Metadata;
public sealed record ImageCacheOptions(string Root);
@@ -80,7 +80,7 @@ public sealed class ImageCache(
///
/// <remarks>
/// <b>Die Prüfung ist kein Schmuck.</b> Der Schlüssel kommt aus einer
/// Adresszeile. Ohne sie wäre <c>/api/v1/images/..%2f..%2fconfig%2fhalo.db</c>
/// Adresszeile. Ohne sie wäre <c>/api/v1/images/..%2f..%2fconfig%2fflaro.db</c>
/// ein Weg, die Datenbank herunterzuladen — samt aller Token-Fingerabdrücke.
/// </remarks>
public string? PfadFür(string schlüssel)
@@ -1,10 +1,10 @@
using System.Net;
using System.Text.Json;
using System.Text.Json.Serialization;
using Halo.Core.Metadata;
using Flaro.Core.Metadata;
using Microsoft.Extensions.Logging;
namespace Halo.Metadata;
namespace Flaro.Metadata;
/// <summary>Fragt TMDB nach Titeln, Beschreibungen und Bildern.</summary>
///
@@ -1,9 +1,9 @@
namespace Halo.Metadata;
namespace Flaro.Metadata;
/// <summary>Was TMDB braucht, um zu antworten.</summary>
public sealed record TmdbSettings(string? ApiKey, string Language, bool UsingProjectKey = false)
{
/// <summary>Englisch als Voreinstellung, weil Halo nicht nur hier läuft.</summary>
/// <summary>Englisch als Voreinstellung, weil Flaro nicht nur hier läuft.</summary>
///
/// <remarks>
/// Der Betreiber stellt in der Oberfläche um. Eine deutsche Voreinstellung
@@ -16,8 +16,8 @@ public sealed record TmdbSettings(string? ApiKey, string Language, bool UsingPro
///
/// <remarks>
/// <para>
/// <b>Hier gehört der Schlüssel hinein, den Halo ausliefert.</b> Ist er
/// leer, verlangt Halo von jedem Betreiber einen eigenen — die Mediathek
/// <b>Hier gehört der Schlüssel hinein, den Flaro ausliefert.</b> Ist er
/// leer, verlangt Flaro von jedem Betreiber einen eigenen — die Mediathek
/// läuft dann trotzdem, nur mit Dateinamen statt Titeln.
/// </para>
/// <para>
@@ -1,6 +1,6 @@
using Halo.Core.Metadata;
using Flaro.Core.Metadata;
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
/// <summary>Ein Anbieter, der nichts weiss und nichts fragt.</summary>
///
@@ -1,19 +1,19 @@
using System.Net;
using System.Net.Http.Json;
using Halo.Api.Endpoints;
using Flaro.Api.Endpoints;
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
/// <summary>Anmelden am Fernseher, ohne ein Passwort zu tippen.</summary>
public class CodeSignInTests
{
private static Task<HttpResponseMessage> CodeAnfordern(HaloServer server) =>
private static Task<HttpResponseMessage> CodeAnfordern(FlaroServer server) =>
server.Client.PostAsJsonAsync("/api/v1/auth/code", new CodeRequest("Wohnzimmer", "tv-1"));
[Test]
public async Task Der_ganze_Weg_vom_Bildschirm_zum_Token()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var telefon = await server.SetUpAndSignIn();
// 1. Der Fernseher fordert an und zeigt den Code.
@@ -52,7 +52,7 @@ public class CodeSignInTests
[Test]
public async Task Ein_fremder_Abholschein_bekommt_nichts()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var telefon = await server.SetUpAndSignIn();
var echt = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync<CodeResponse>();
@@ -69,7 +69,7 @@ public class CodeSignInTests
[Test]
public async Task Der_Code_ist_kein_Abholschein()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var telefon = await server.SetUpAndSignIn();
var echt = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync<CodeResponse>();
@@ -82,7 +82,7 @@ public class CodeSignInTests
[Test]
public async Task Ein_abgeholtes_Token_gibt_es_kein_zweites_Mal()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var telefon = await server.SetUpAndSignIn();
var angefordert = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync<CodeResponse>();
@@ -100,7 +100,7 @@ public class CodeSignInTests
[Test]
public async Task Ein_Unangemeldeter_kann_nichts_bestaetigen()
{
using var server = new HaloServer();
using var server = new FlaroServer();
await server.SetUpAndSignIn();
var angefordert = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync<CodeResponse>();
@@ -114,7 +114,7 @@ public class CodeSignInTests
[Test]
public async Task Ein_erfundener_Code_laesst_sich_nicht_bestaetigen()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var telefon = await server.SetUpAndSignIn();
var versuch = await server.AlsAngemeldeter(telefon.Token).PostAsync("/api/v1/auth/code/000000", null);
@@ -125,7 +125,7 @@ public class CodeSignInTests
[Test]
public async Task Die_Bestaetigung_nennt_das_Geraet()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var telefon = await server.SetUpAndSignIn();
var angefordert = await (await CodeAnfordern(server)).Content.ReadFromJsonAsync<CodeResponse>();
@@ -2,9 +2,9 @@ using System.Diagnostics;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Halo.Api.Endpoints;
using Flaro.Api.Endpoints;
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
/// <summary>Der Weg vom Scan bis zur heruntergeladenen Datei.</summary>
public class DeliveryTests
@@ -21,7 +21,7 @@ public class DeliveryTests
{
if (Werkzeug("ffmpeg") is null) Assert.Ignore("ffmpeg ist auf diesem Rechner nicht installiert.");
_bestand = Directory.CreateDirectory(
Path.Combine(Path.GetTempPath(), "halo-liefern-" + Guid.CreateVersion7().ToString("N"))).FullName;
Path.Combine(Path.GetTempPath(), "flaro-liefern-" + Guid.CreateVersion7().ToString("N"))).FullName;
}
[TearDown]
@@ -56,7 +56,7 @@ public class DeliveryTests
private async Task<(HttpClient Client, MediaFileResponse Datei)> Eingelesen()
{
var server = _server = new HaloServer();
var server = _server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var client = server.AlsAngemeldeter(admin.Token);
@@ -78,7 +78,7 @@ public class DeliveryTests
return (client, filme.Items[0].Files.Single());
}
private HaloServer? _server;
private FlaroServer? _server;
[TearDown]
public void ServerSchliessen() { _server?.Dispose(); _server = null; }
@@ -22,9 +22,9 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Halo.Api\Halo.Api.csproj" />
<ProjectReference Include="..\..\src\Halo.Data\Halo.Data.csproj" />
<ProjectReference Include="..\..\src\Halo.Core\Halo.Core.csproj" />
<ProjectReference Include="..\..\src\Flaro.Api\Flaro.Api.csproj" />
<ProjectReference Include="..\..\src\Flaro.Data\Flaro.Data.csproj" />
<ProjectReference Include="..\..\src\Flaro.Core\Flaro.Core.csproj" />
</ItemGroup>
</Project>
@@ -1,9 +1,9 @@
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
/// <summary>Was beim allerersten Start passiert, bevor irgendetwas anderes greift.</summary>
public class HaloPathsTests
public class FlaroPathsTests
{
private static readonly string[] Variablen = ["HALO_DATA", "HALO_CACHE", "HALO_TRANSCODE"];
private static readonly string[] Variablen = ["FLARO_DATA", "FLARO_CACHE", "FLARO_TRANSCODE"];
private string _wurzel = "";
private Dictionary<string, string?> _vorher = [];
@@ -26,7 +26,7 @@ public class HaloPathsTests
{
_vorher = Variablen.ToDictionary(v => v, Environment.GetEnvironmentVariable);
_wurzel = Directory.CreateDirectory(
Path.Combine(Path.GetTempPath(), "halo-pfade-" + Guid.CreateVersion7().ToString("N"))).FullName;
Path.Combine(Path.GetTempPath(), "flaro-pfade-" + Guid.CreateVersion7().ToString("N"))).FullName;
}
[TearDown]
@@ -43,7 +43,7 @@ public class HaloPathsTests
foreach (var (name, wert) in _vorher) Environment.SetEnvironmentVariable(name, wert);
}
private HaloPaths Pfade(string? daten = null) => new()
private FlaroPaths Pfade(string? daten = null) => new()
{
Data = daten ?? Path.Combine(_wurzel, "config"),
Cache = Path.Combine(_wurzel, "cache"),
@@ -90,7 +90,7 @@ public class HaloPathsTests
var fehler = Assert.Throws<InvalidOperationException>(() => Pfade(daten: gesperrt).EnsureExists());
Assert.That(fehler!.Message, Does.Contain("HALO_DATA"), "Welche Einstellung gemeint ist");
Assert.That(fehler!.Message, Does.Contain("FLARO_DATA"), "Welche Einstellung gemeint ist");
Assert.That(fehler.Message, Does.Contain(gesperrt), "Welches Verzeichnis");
Assert.That(fehler.Message, Does.Contain("uid="), "Als wer der Server läuft");
Assert.That(fehler.Message, Does.Contain("chown"), "Was zu tun ist");
@@ -99,20 +99,20 @@ public class HaloPathsTests
[Test]
public void Ohne_Umgebungsvariablen_liegt_alles_neben_dem_Programm()
{
foreach (var name in new[] { "HALO_DATA", "HALO_CACHE", "HALO_TRANSCODE" })
foreach (var name in new[] { "FLARO_DATA", "FLARO_CACHE", "FLARO_TRANSCODE" })
Environment.SetEnvironmentVariable(name, null);
var pfade = HaloPaths.FromEnvironment(_wurzel);
var pfade = FlaroPaths.FromEnvironment(_wurzel);
Assert.That(pfade.Data, Is.EqualTo(Path.Combine(_wurzel, "data")));
Assert.That(pfade.DatabaseFile, Does.EndWith("halo.db"));
Assert.That(pfade.DatabaseFile, Does.EndWith("flaro.db"));
}
[Test]
public void Die_Umgebung_sticht_die_Voreinstellung()
{
Environment.SetEnvironmentVariable("HALO_DATA", "/woanders");
Assert.That(HaloPaths.FromEnvironment(_wurzel).Data, Is.EqualTo("/woanders"));
Environment.SetEnvironmentVariable("FLARO_DATA", "/woanders");
Assert.That(FlaroPaths.FromEnvironment(_wurzel).Data, Is.EqualTo("/woanders"));
// Zurückgesetzt wird im TearDown, für alle drei Variablen gemeinsam.
}
}
@@ -1,12 +1,12 @@
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Halo.Api.Endpoints;
using Flaro.Api.Endpoints;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Halo.Core.Metadata;
using Flaro.Core.Metadata;
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
/// <summary>Ein frisch gestarteter Server mit leerer Datenbank.</summary>
///
@@ -16,7 +16,7 @@ namespace Halo.Api.Tests;
/// Migration wandern lassen, Einrichtung anbieten. Genau diese Kette sieht ein
/// Fremder als Erstes — und genau sie würde ein Ersatzspeicher überspringen.
/// </remarks>
public sealed class HaloServer : IDisposable
public sealed class FlaroServer : IDisposable
{
private readonly WebApplicationFactory<Program> _grundlage;
private readonly WebApplicationFactory<Program> _fabrik;
@@ -24,16 +24,16 @@ public sealed class HaloServer : IDisposable
public HttpClient Client { get; }
public HaloServer(Action<IServiceCollection>? dienste = null)
public FlaroServer(Action<IServiceCollection>? dienste = null)
{
_verzeichnis = Path.Combine(Path.GetTempPath(), "halo-test-" + Guid.CreateVersion7().ToString("N"));
Environment.SetEnvironmentVariable("HALO_DATA", Path.Combine(_verzeichnis, "config"));
Environment.SetEnvironmentVariable("HALO_CACHE", Path.Combine(_verzeichnis, "cache"));
Environment.SetEnvironmentVariable("HALO_TRANSCODE", Path.Combine(_verzeichnis, "transcode"));
_verzeichnis = Path.Combine(Path.GetTempPath(), "flaro-test-" + Guid.CreateVersion7().ToString("N"));
Environment.SetEnvironmentVariable("FLARO_DATA", Path.Combine(_verzeichnis, "config"));
Environment.SetEnvironmentVariable("FLARO_CACHE", Path.Combine(_verzeichnis, "cache"));
Environment.SetEnvironmentVariable("FLARO_TRANSCODE", Path.Combine(_verzeichnis, "transcode"));
// Die Zugriffsgrenze zählt je Adresse, und im Test kommt alles von
// derselben. Ohne das Hochsetzen prüfte man hier die Grenze statt der
// Anmeldung.
Environment.SetEnvironmentVariable("HALO_RATELIMIT_SIGNIN", "10000");
Environment.SetEnvironmentVariable("FLARO_RATELIMIT_SIGNIN", "10000");
_grundlage = new WebApplicationFactory<Program>();
_fabrik = _grundlage.WithWebHostBuilder(b => b.ConfigureServices(sammlung =>
@@ -1,15 +1,15 @@
using System.Net;
using System.Net.Http.Json;
using Halo.Api.Endpoints;
using Flaro.Api.Endpoints;
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
public class ManagementTests
{
[Test]
public async Task Ein_Administrator_legt_Benutzer_an()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var client = server.AlsAngemeldeter(admin.Token);
@@ -25,7 +25,7 @@ public class ManagementTests
[Test]
public async Task Ein_gewoehnlicher_Benutzer_darf_keine_Benutzer_anlegen()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
await server.AlsAngemeldeter(admin.Token).PostAsJsonAsync("/api/v1/users",
new CreateUserRequest("mitbewohner", "auch-ein-langes-passwort", false));
@@ -40,7 +40,7 @@ public class ManagementTests
[Test]
public async Task Derselbe_Benutzername_zweimal_wird_abgelehnt()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var client = server.AlsAngemeldeter(admin.Token);
@@ -60,7 +60,7 @@ public class ManagementTests
[Test]
public async Task Eine_Bibliothek_auf_einem_Pfad_den_es_nicht_gibt_wird_abgelehnt()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var versuch = await server.AlsAngemeldeter(admin.Token).PostAsJsonAsync("/api/v1/libraries",
@@ -73,7 +73,7 @@ public class ManagementTests
[Test]
public async Task Eine_Bibliothek_auf_einem_vorhandenen_Pfad_wird_angelegt()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var client = server.AlsAngemeldeter(admin.Token);
var pfad = Path.GetTempPath();
@@ -89,7 +89,7 @@ public class ManagementTests
[Test]
public async Task Eine_unbekannte_Art_wird_abgelehnt()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var versuch = await server.AlsAngemeldeter(admin.Token).PostAsJsonAsync("/api/v1/libraries",
@@ -1,20 +1,20 @@
using System.Diagnostics;
using System.Net;
using System.Net.Http.Json;
using Halo.Api.Endpoints;
using Halo.Core.Library;
using Halo.Core.Metadata;
using Halo.Media;
using Flaro.Api.Endpoints;
using Flaro.Core.Library;
using Flaro.Core.Metadata;
using Flaro.Media;
using Microsoft.Extensions.DependencyInjection;
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
/// <summary>Eine falsche Zuordnung richtigstellen — und sie festschreiben.</summary>
public class MatchCorrectionTests
{
private string _bestand = "";
private string _filmOrdner = "";
private HaloServer? _server;
private FlaroServer? _server;
private static string? Werkzeug(string name) =>
(Environment.GetEnvironmentVariable("PATH") ?? "").Split(Path.PathSeparator)
@@ -26,7 +26,7 @@ public class MatchCorrectionTests
{
if (Werkzeug("ffmpeg") is null) Assert.Ignore("ffmpeg ist auf diesem Rechner nicht installiert.");
_bestand = Directory.CreateDirectory(
Path.Combine(Path.GetTempPath(), "halo-korrektur-" + Guid.CreateVersion7().ToString("N"))).FullName;
Path.Combine(Path.GetTempPath(), "flaro-korrektur-" + Guid.CreateVersion7().ToString("N"))).FullName;
_filmOrdner = Directory.CreateDirectory(Path.Combine(_bestand, "Dark (2017)")).FullName;
var start = new ProcessStartInfo(Werkzeug("ffmpeg")!) { RedirectStandardError = true, UseShellExecute = false };
@@ -87,7 +87,7 @@ public class MatchCorrectionTests
private async Task<(HttpClient Client, MovieResponse Film)> Eingelesen(bool beiwerkSchreiben)
{
_server = new HaloServer(dienste =>
_server = new FlaroServer(dienste =>
{
dienste.AddScoped<IMetadataProvider, ZweiTreffer>();
dienste.AddSingleton<ISidecarStore>(
@@ -213,13 +213,13 @@ public class MatchCorrectionTests
(await client.PutAsJsonAsync($"/api/v1/movies/{film.Id}/match", new ApplyMatchRequest(613)))
.EnsureSuccessStatusCode();
var bibliotheken = await client.GetFromJsonAsync<List<LibraryResponse>>("/api/v1/libraries");
(await client.PostAsync($"/api/v1/libraries/{bibliotheken!.Single().Id}/scan", null))
var bibliothek = (await client.GetFromJsonAsync<List<LibraryResponse>>("/api/v1/libraries"))!.Single();
(await client.PostAsync($"/api/v1/libraries/{bibliothek.Id}/scan", null))
.EnsureSuccessStatusCode();
for (var i = 0; i < 150; i++)
{
var stand = await client.GetFromJsonAsync<ScanStatus>(
$"/api/v1/libraries/{bibliotheken.Single().Id}/scan");
$"/api/v1/libraries/{bibliothek.Id}/scan");
if (stand!.State == "finished") break;
await Task.Delay(50);
}
@@ -1,10 +1,10 @@
using System.Net;
using System.Net.Http.Json;
using Halo.Api.Endpoints;
using Halo.Core.Metadata;
using Flaro.Api.Endpoints;
using Flaro.Core.Metadata;
using Microsoft.Extensions.DependencyInjection;
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
/// <summary>Ein Anbieter, der antwortet, was der Test vorgibt.</summary>
public sealed class VorgegebenerAnbieter : IMetadataProvider
@@ -84,7 +84,7 @@ public class MetadataTests
[SetUp]
public void Anlegen() => _bestand = Directory.CreateDirectory(
Path.Combine(Path.GetTempPath(), "halo-meta-" + Guid.CreateVersion7().ToString("N"))).FullName;
Path.Combine(Path.GetTempPath(), "flaro-meta-" + Guid.CreateVersion7().ToString("N"))).FullName;
[TearDown]
public void Aufraeumen()
@@ -99,14 +99,14 @@ public class MetadataTests
File.WriteAllBytes(voll, new byte[1024]);
}
private HaloServer MitAnbieter(VorgegebenerAnbieter anbieter, VorgegebeneBilder? bilder = null) =>
private FlaroServer MitAnbieter(VorgegebenerAnbieter anbieter, VorgegebeneBilder? bilder = null) =>
new(dienste =>
{
dienste.AddScoped<IMetadataProvider>(_ => anbieter);
dienste.AddScoped<IImageStore>(_ => bilder ?? new VorgegebeneBilder());
});
private async Task<(HttpClient Admin, Guid Library)> Einrichten(HaloServer server, string art)
private async Task<(HttpClient Admin, Guid Library)> Einrichten(FlaroServer server, string art)
{
var admin = await server.SetUpAndSignIn();
var client = server.AlsAngemeldeter(admin.Token);
@@ -282,7 +282,7 @@ public class MetadataSettingsTests
[Test]
public async Task Der_Schluessel_kommt_nicht_zurueck()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var client = server.AlsAngemeldeter(admin.Token);
@@ -308,12 +308,12 @@ public class MetadataSettingsTests
[Test]
public async Task Ohne_eigenen_Schluessel_greift_der_mitgelieferte()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var stand = await server.AlsAngemeldeter(admin.Token)
.GetFromJsonAsync<MetadataSettingsResponse>("/api/v1/settings/metadata");
var mitgeliefert = Halo.Metadata.TmdbSettings.ProjectApiKey.Length > 0;
var mitgeliefert = Flaro.Metadata.TmdbSettings.ProjectApiKey.Length > 0;
Assert.That(stand!.HasApiKey, Is.EqualTo(mitgeliefert));
Assert.That(stand.UsingProjectKey, Is.EqualTo(mitgeliefert));
Assert.That(stand.Language, Is.EqualTo("en-US"));
@@ -322,7 +322,7 @@ public class MetadataSettingsTests
[Test]
public async Task Ein_eigener_Schluessel_loest_den_mitgelieferten_ab()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var client = server.AlsAngemeldeter(admin.Token);
@@ -338,7 +338,7 @@ public class MetadataSettingsTests
[Test]
public async Task Der_Pflichthinweis_zu_TMDB_steht_in_der_Systemauskunft()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var info = await server.Client.GetFromJsonAsync<SystemInfo>("/api/v1/system/info");
var tmdb = info!.Attributions.Single();
@@ -352,7 +352,7 @@ public class MetadataSettingsTests
[TestCase("de-de")]
public async Task Eine_unsinnige_Sprachkennung_wird_abgelehnt(string sprache)
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var antwort = await server.AlsAngemeldeter(admin.Token)
@@ -364,7 +364,7 @@ public class MetadataSettingsTests
[Test]
public async Task Ein_gewoehnlicher_Benutzer_sieht_die_Einstellungen_nicht()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
await server.AlsAngemeldeter(admin.Token).PostAsJsonAsync("/api/v1/users",
new CreateUserRequest("gast", "auch-ein-langes-passwort", false));
@@ -380,7 +380,7 @@ public class ImageEndpointTests
[Test]
public async Task Bilder_gibt_es_nur_fuer_Angemeldete()
{
using var server = new HaloServer();
using var server = new FlaroServer();
await server.SetUpAndSignIn();
var antwort = await server.Client.GetAsync($"/api/v1/images/{new string('a', 64)}");
@@ -388,12 +388,12 @@ public class ImageEndpointTests
}
/// <summary>Der Weg, der sonst die Datenbank herausgäbe.</summary>
[TestCase("..%2F..%2Fconfig%2Fhalo.db")]
[TestCase("..%2F..%2Fconfig%2Fflaro.db")]
[TestCase("nichtsechzigvierzeichen")]
[TestCase("ZZZZ567890123456789012345678901234567890123456789012345678901234")]
public async Task Ein_Schluessel_der_keiner_ist_fuehrt_ins_Leere(string versuch)
{
using var server = new HaloServer();
using var server = new FlaroServer();
var admin = await server.SetUpAndSignIn();
var antwort = await server.AlsAngemeldeter(admin.Token).GetAsync($"/api/v1/images/{versuch}");
@@ -1,8 +1,8 @@
using System.Net;
using System.Net.Http.Json;
using Halo.Api.Endpoints;
using Flaro.Api.Endpoints;
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
/// <summary>Scan gegen einen echten Ordner mit echten Dateien.</summary>
public class ScanTests
@@ -11,7 +11,7 @@ public class ScanTests
[SetUp]
public void Anlegen() => _bestand = Directory.CreateDirectory(
Path.Combine(Path.GetTempPath(), "halo-medien-" + Guid.CreateVersion7().ToString("N"))).FullName;
Path.Combine(Path.GetTempPath(), "flaro-medien-" + Guid.CreateVersion7().ToString("N"))).FullName;
[TearDown]
public void Aufraeumen()
@@ -26,7 +26,7 @@ public class ScanTests
File.WriteAllBytes(voll, new byte[bytes]);
}
private async Task<(HttpClient Admin, Guid LibraryId)> MitBibliothek(HaloServer server, string art)
private async Task<(HttpClient Admin, Guid LibraryId)> MitBibliothek(FlaroServer server, string art)
{
var admin = await server.SetUpAndSignIn();
var client = server.AlsAngemeldeter(admin.Token);
@@ -62,7 +62,7 @@ public class ScanTests
Datei("The Matrix (1999).srt"); // zählt nicht
Datei("@eaDir/The.Matrix.1999.mkv"); // zählt auch nicht
using var server = new HaloServer();
using var server = new FlaroServer();
var (client, bibliothek) = await MitBibliothek(server, "Movies");
var stand = await Scannen(client, bibliothek);
@@ -89,7 +89,7 @@ public class ScanTests
Datei("The Matrix (1999).mkv");
Datei("Inception (2010).mkv");
using var server = new HaloServer();
using var server = new FlaroServer();
var (client, bibliothek) = await MitBibliothek(server, "Movies");
await Scannen(client, bibliothek);
@@ -106,7 +106,7 @@ public class ScanTests
Datei("The Matrix (1999).mkv");
Datei("Inception (2010).mkv");
using var server = new HaloServer();
using var server = new FlaroServer();
var (client, bibliothek) = await MitBibliothek(server, "Movies");
await Scannen(client, bibliothek);
@@ -125,7 +125,7 @@ public class ScanTests
Datei("The Matrix (1999) - 1080p.mkv");
Datei("The Matrix (1999) - 2160p.mkv");
using var server = new HaloServer();
using var server = new FlaroServer();
var (client, bibliothek) = await MitBibliothek(server, "Movies");
await Scannen(client, bibliothek);
@@ -142,7 +142,7 @@ public class ScanTests
Datei("Dark/Staffel 2/Dark - S02E01 - Anfänge.mkv");
Datei("Notruf.Hafenkante.S01E01.German.1080p.mkv");
using var server = new HaloServer();
using var server = new FlaroServer();
var (client, bibliothek) = await MitBibliothek(server, "Shows");
await Scannen(client, bibliothek);
@@ -169,7 +169,7 @@ public class ScanTests
Datei("Dark/Dark - S01E01.mkv");
Datei("Dark/Irgendein Mitschnitt.mkv");
using var server = new HaloServer();
using var server = new FlaroServer();
var (client, bibliothek) = await MitBibliothek(server, "Shows");
var stand = await Scannen(client, bibliothek);
@@ -185,7 +185,7 @@ public class ScanTests
Datei("The Matrix Reloaded (2003).mkv");
Datei("Inception (2010).mkv");
using var server = new HaloServer();
using var server = new FlaroServer();
var (client, bibliothek) = await MitBibliothek(server, "Movies");
await Scannen(client, bibliothek);
@@ -199,7 +199,7 @@ public class ScanTests
{
for (var i = 1; i <= 5; i++) Datei($"Film {i} (200{i}).mkv");
using var server = new HaloServer();
using var server = new FlaroServer();
var (client, bibliothek) = await MitBibliothek(server, "Movies");
await Scannen(client, bibliothek);
@@ -215,7 +215,7 @@ public class ScanTests
[Test]
public async Task Vor_dem_ersten_Lauf_steht_der_Scan_auf_untaetig()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var (client, bibliothek) = await MitBibliothek(server, "Movies");
var stand = await client.GetFromJsonAsync<ScanStatus>($"/api/v1/libraries/{bibliothek}/scan");
@@ -225,7 +225,7 @@ public class ScanTests
[Test]
public async Task Ein_gewoehnlicher_Benutzer_stoesst_keinen_Scan_an()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var (admin, bibliothek) = await MitBibliothek(server, "Movies");
await admin.PostAsJsonAsync("/api/v1/users",
new CreateUserRequest("gast", "auch-ein-langes-passwort", false));
@@ -240,7 +240,7 @@ public class ScanTests
[Test]
public async Task Eine_Bibliothek_die_es_nicht_gibt_laesst_sich_nicht_scannen()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var (client, _) = await MitBibliothek(server, "Movies");
var versuch = await client.PostAsync($"/api/v1/libraries/{Guid.CreateVersion7()}/scan", null);
@@ -1,15 +1,15 @@
using System.Net;
using System.Net.Http.Json;
using Halo.Api.Endpoints;
using Flaro.Api.Endpoints;
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
public class SetupTests
{
[Test]
public async Task Ein_frischer_Server_verlangt_Einrichtung()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var info = await server.Client.GetFromJsonAsync<SystemInfo>("/api/v1/system/info");
Assert.That(info!.NeedsSetup, Is.True);
}
@@ -17,7 +17,7 @@ public class SetupTests
[Test]
public async Task Die_Einrichtung_legt_einen_Administrator_an()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var antwort = await server.Client.PostAsJsonAsync("/api/v1/system/setup",
new SetupRequest("guido", "ein-langes-passwort"));
@@ -40,7 +40,7 @@ public class SetupTests
[Test]
public async Task Ein_zweites_Mal_Einrichten_wird_abgelehnt()
{
using var server = new HaloServer();
using var server = new FlaroServer();
await server.SetUpAndSignIn();
var zweiterVersuch = await server.Client.PostAsJsonAsync("/api/v1/system/setup",
@@ -52,7 +52,7 @@ public class SetupTests
[Test]
public async Task Ein_zu_kurzes_Passwort_wird_abgelehnt()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var antwort = await server.Client.PostAsJsonAsync("/api/v1/system/setup",
new SetupRequest("guido", "kurz"));
@@ -64,7 +64,7 @@ public class SetupTests
[Test]
public async Task Die_Gesundheitsfrage_braucht_keine_Anmeldung()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var antwort = await server.Client.GetAsync("/health");
Assert.That(antwort.StatusCode, Is.EqualTo(HttpStatusCode.OK));
}
@@ -1,16 +1,16 @@
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using Halo.Api.Endpoints;
using Flaro.Api.Endpoints;
namespace Halo.Api.Tests;
namespace Flaro.Api.Tests;
public class SignInTests
{
[Test]
public async Task Mit_richtigem_Passwort_gibt_es_ein_Token()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var anmeldung = await server.SetUpAndSignIn();
Assert.That(anmeldung.Token, Is.Not.Empty);
@@ -20,7 +20,7 @@ public class SignInTests
[Test]
public async Task Mit_falschem_Passwort_gibt_es_keines()
{
using var server = new HaloServer();
using var server = new FlaroServer();
await server.SetUpAndSignIn();
var antwort = await server.Client.PostAsJsonAsync("/api/v1/auth/login",
@@ -33,7 +33,7 @@ public class SignInTests
[Test]
public async Task Ein_unbekannter_Name_antwortet_wie_ein_falsches_Passwort()
{
using var server = new HaloServer();
using var server = new FlaroServer();
await server.SetUpAndSignIn();
var unbekannt = await server.Client.PostAsJsonAsync("/api/v1/auth/login",
@@ -55,7 +55,7 @@ public class SignInTests
[Test]
public async Task Mit_Token_beantwortet_der_Server_wer_man_ist()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var anmeldung = await server.SetUpAndSignIn();
var ich = await server.AlsAngemeldeter(anmeldung.Token)
@@ -68,7 +68,7 @@ public class SignInTests
[Test]
public async Task Ohne_Token_antwortet_er_nicht()
{
using var server = new HaloServer();
using var server = new FlaroServer();
await server.SetUpAndSignIn();
var antwort = await server.Client.GetAsync("/api/v1/auth/me");
@@ -78,7 +78,7 @@ public class SignInTests
[Test]
public async Task Ein_erfundenes_Token_gilt_nicht()
{
using var server = new HaloServer();
using var server = new FlaroServer();
await server.SetUpAndSignIn();
var antwort = await server.AlsAngemeldeter("ausgedacht-aber-lang-genug").GetAsync("/api/v1/auth/me");
@@ -95,7 +95,7 @@ public class SignInTests
[Test]
public async Task Abmelden_entwertet_das_Token_augenblicklich()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var anmeldung = await server.SetUpAndSignIn();
var client = server.AlsAngemeldeter(anmeldung.Token);
@@ -109,7 +109,7 @@ public class SignInTests
[Test]
public async Task Eine_erneute_Anmeldung_desselben_Geraets_entwertet_das_alte_Token()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var erste = await server.SetUpAndSignIn();
var zweite = await server.SignIn("guido", "ein-langes-passwort", "iPhone", "geraet-1");
@@ -124,7 +124,7 @@ public class SignInTests
[Test]
public async Task Ein_anderes_Geraet_laesst_das_erste_in_Ruhe()
{
using var server = new HaloServer();
using var server = new FlaroServer();
var telefon = await server.SetUpAndSignIn();
var fernseher = await server.SignIn("guido", "ein-langes-passwort", "Apple TV", "geraet-2");
@@ -1,6 +1,6 @@
using Halo.Core.Auth;
using Flaro.Core.Auth;
namespace Halo.Core.Tests;
namespace Flaro.Core.Tests;
public class SecretsTests
{
@@ -1,7 +1,7 @@
using Halo.Core.Delivery;
using Halo.Core.Library;
using Flaro.Core.Delivery;
using Flaro.Core.Library;
namespace Halo.Core.Tests;
namespace Flaro.Core.Tests;
public class DeliveryDeciderTests
{
@@ -1,6 +1,6 @@
using Halo.Core.Library;
using Flaro.Core.Library;
namespace Halo.Core.Tests;
namespace Flaro.Core.Tests;
public class FilmNamenTests
{
@@ -21,7 +21,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Halo.Core\Halo.Core.csproj" />
<ProjectReference Include="..\..\src\Flaro.Core\Flaro.Core.csproj" />
</ItemGroup>
</Project>
@@ -1,6 +1,6 @@
using Halo.Core.Library;
using Flaro.Core.Library;
namespace Halo.Core.Tests;
namespace Flaro.Core.Tests;
public class LibraryWalkerTests
{
@@ -8,7 +8,7 @@ public class LibraryWalkerTests
[SetUp]
public void Anlegen() => _wurzel = Directory.CreateDirectory(
Path.Combine(Path.GetTempPath(), "halo-bestand-" + Guid.CreateVersion7().ToString("N"))).FullName;
Path.Combine(Path.GetTempPath(), "flaro-bestand-" + Guid.CreateVersion7().ToString("N"))).FullName;
[TearDown]
public void Aufraeumen()
@@ -1,6 +1,6 @@
using Halo.Core.Users;
using Flaro.Core.Users;
namespace Halo.Core.Tests;
namespace Flaro.Core.Tests;
public class PasswordHasherTests
{
@@ -1,8 +1,8 @@
using Halo.Core.Library;
using Flaro.Core.Library;
namespace Halo.Core.Tests;
namespace Flaro.Core.Tests;
/// <summary>Was Halo in einen Medienordner schreiben darf.</summary>
/// <summary>Was Flaro in einen Medienordner schreiben darf.</summary>
///
/// <remarks>
/// <b>Die wichtigste Prüfdatei des Projekts.</b> Damit Plakate neben dem Film
@@ -86,7 +86,7 @@ public class SidecarNamesTests
///
/// <remarks>
/// Die beiden Hälften könnten auseinanderlaufen — jemand ändert das Muster
/// für Folgen und vergisst die Liste. Dann schriebe Halo nichts mehr und
/// für Folgen und vergisst die Liste. Dann schriebe Flaro nichts mehr und
/// niemand wüsste, warum.
/// </remarks>
[TestCase("Dark - S01E01.mkv")]
@@ -190,12 +190,12 @@ public class NfoTests
Assert.That(Nfo.ReadLocked(kaputt), Is.False);
}
/// <summary>Eine NFO aus dem Medienordner hat Halo nicht geschrieben.</summary>
/// <summary>Eine NFO aus dem Medienordner hat Flaro nicht geschrieben.</summary>
///
/// <remarks>
/// <b>Ohne abgeschaltete DTDs wäre das ein Weg, fremde Dateien zu lesen.</b>
/// Eine untergeschobene NFO mit einer externen Entität könnte den Server
/// dazu bringen, <c>/config/halo.db</c> einzulesen und den Inhalt als
/// dazu bringen, <c>/config/flaro.db</c> einzulesen und den Inhalt als
/// Titel auszuliefern.
/// </remarks>
[Test]
@@ -1,7 +1,7 @@
using Halo.Core.Library;
using Flaro.Core.Library;
using Microsoft.Extensions.Logging.Abstractions;
namespace Halo.Media.Tests;
namespace Flaro.Media.Tests;
public class FfprobeRunnerTests
{
@@ -13,7 +13,7 @@ public class FfprobeRunnerTests
{
if (!Testdateien.FfmpegVorhanden) Assert.Ignore("ffmpeg ist auf diesem Rechner nicht installiert.");
_ordner = Directory.CreateDirectory(
Path.Combine(Path.GetTempPath(), "halo-probe-" + Guid.CreateVersion7().ToString("N"))).FullName;
Path.Combine(Path.GetTempPath(), "flaro-probe-" + Guid.CreateVersion7().ToString("N"))).FullName;
_ffprobe = new FfprobeRunner(
new FfmpegOptions(Testdateien.Suchen("ffmpeg")!, Testdateien.Suchen("ffprobe")!),
NullLogger<FfprobeRunner>.Instance);
@@ -21,8 +21,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Halo.Media\Halo.Media.csproj" />
<ProjectReference Include="..\..\src\Halo.Core\Halo.Core.csproj" />
<ProjectReference Include="..\..\src\Flaro.Media\Flaro.Media.csproj" />
<ProjectReference Include="..\..\src\Flaro.Core\Flaro.Core.csproj" />
</ItemGroup>
</Project>
@@ -1,7 +1,7 @@
using Halo.Core.Delivery;
using Halo.Core.Library;
using Flaro.Core.Delivery;
using Flaro.Core.Library;
namespace Halo.Media.Tests;
namespace Flaro.Media.Tests;
/// <summary>Die ffmpeg-Zeile, betrachtet ohne sie auszuführen.</summary>
public class RemuxArgumentsTests

Some files were not shown because too many files have changed in this diff Show More