TwitchDownloader/UpgradePlan2.md

78 lines
No EOL
6.7 KiB
Markdown

<div class="page"> <nav class="sidebar"><NavMenu /></nav> <main class="main">@Body</main> </div> ``` Add a top bar (`<header class="topbar">`) with the app title "Twitch Archive" and a hamburger toggle `<button>` that toggles a `sidebarCollapsed` bool field to add/remove a CSS class.
Create (or extend) dotnet/src/TwitchArchive.Web/wwwroot/css/app.css:
.page: display:flex; height:100vh
.sidebar: width:220px; flex-shrink:0; background:#1e1e2e; color:#cdd6f4; overflow-y:auto
.main: flex:1; overflow-y:auto; padding:1.5rem
.nav-link: display:block; padding:0.6rem 1rem; color:#cdd6f4; text-decoration:none
.nav-link.active: background:#313244; border-left:3px solid #89b4fa
Media query @media(max-width:768px): .sidebar.collapsed { display:none }, .topbar { display:flex }, else .topbar { display:none }
Step G — Full Blazor UI pages (Plan Step 8)
Goal: implement the missing pages referenced by the NavMenu.
Steps
Dashboard.razor (/) — replace dotnet/src/TwitchArchive.Web/Pages/Index.razor. Display a CSS grid of streamer cards, each showing: username (link to /streamer/{username}), live/offline <span> badge, current RecoveryState text from WorkerManager.GetState(username), last session start from ISessionRepository, Start/Stop buttons. Poll every 10 s via PeriodicTimer in OnInitializedAsync, disposed in IAsyncDisposable.DisposeAsync.
StreamerDetail.razor (/streamer/{username}) — new file. Live status badge, pipeline step bar (Record → Process → Upload using CSS flex row), <ProcessConsole Streamer="@Username" />. Route parameter [Parameter] public string Username.
GlobalConfig.razor (/config/global) — new file. <EditForm> bound to a GlobalConfig loaded via IConfigurationService.LoadGlobal(). On valid submit: await ConfigService.SaveGlobal(model), show a dismissible success alert.
StreamerConfig.razor (/config/{username}) — new file. Per-field nullable override: each field has an <InputCheckbox> "Override" toggle; when unchecked the <InputText> is disabled and shows the global default as placeholder. Save calls SaveStreamer. Delete button removes the config file and navigates to /.
AddStreamer.razor (/config/new) — new file. Two fields: Username (required, lowercase) and Enabled checkbox. On submit: await ConfigService.SaveStreamer(new StreamerConfig { Username, Enabled }), then Nav.NavigateTo($"/config/{model.Username}").
AppSettings.razor (/settings) — new file. Tool-path fields bound to AppSettings. Change-password section: current password (validated against BCrypt hash) + new + confirm fields. On save: update appsettings.json and call IAuthService to refresh the cached hash.
Step H — Authentication (Plan Step 8)
Goal: single-password BCrypt cookie auth protecting all pages except /login.
Steps
Add <PackageReference Include="BCrypt.Net-Next" Version="4.*" /> to TwitchArchive.Web.csproj.
Create dotnet/src/TwitchArchive.Web/Services/IAuthService.cs + AuthService.cs — ValidatePassword(string plain) → bool using BCrypt.Net.BCrypt.Verify against AppSettings.PasswordHash. If hash is empty (first-run), any password is accepted.
Create dotnet/src/TwitchArchive.Web/Pages/Login.razor (/login) — password <InputText type="password"> in an <EditForm>. Posts to /auth/login minimal-API endpoint via form navigation.
Add minimal API endpoint POST /auth/login in Program.cs — reads password from form body, calls IAuthService.ValidatePassword, calls HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, ...), redirects to /. On failure, redirects to /login?error=1.
In Program.cs add builder.Services.AddAuthentication(...).AddCookie(opt => opt.LoginPath = "/login"), app.UseAuthentication(), app.UseAuthorization().
Update App.razor — replace <RouteView> with <AuthorizeRouteView>, wrap in <CascadingAuthenticationState>. Add @attribute [Authorize] to all pages except Login.razor.
Step I — Docker (Plan Step 10)
Steps
Create dotnet/Dockerfile:
Build stage (sdk:10.0): dotnet publish src/TwitchArchive.Web -c Release -o /app/publish
Runtime stage (aspnet:10.0): apt-get install -y ffmpeg python3-pip rclone, pip3 install streamlink, download TwitchDownloaderCLI linux-x64 binary to /app/bin/ and chmod +x.
EXPOSE 8080, ENV ASPNETCORE_URLS=http://+:8080, ENTRYPOINT ["dotnet","TwitchArchive.Web.dll"]
Create dotnet/docker-compose.yml:
Create dotnet/src/TwitchArchive.Core/Config/ToolPathResolver.cs — static helper using RuntimeInformation.IsOSPlatform(OSPlatform.Windows) to resolve default binary paths; used by AppSettings property defaults and RecorderService/ProcessorService.
Step J — Extended unit tests (Plan Step 11)
Steps
ConfigurationServiceTests.cs — load/save/merge in Path.GetTempPath() temp dir; assert roundtrip and merge precedence.
TwitchApiClientTests.cs — mock HttpMessageHandler; token caching, GQL stream-status (live + offline), GetLatestVodAsync, network error → null.
FileManagerServiceTests.cs — InitializeDirectories, GetPaths, GetUniquePath collision suffix on temp dirs.
RecorderServiceTests.cs — mock IProcessRunner capturing ProcessRunOptions.Arguments; assert --hls-live-restart and resolved quality.
UploadServiceTests.cs — mock IProcessRunner; assert rclone arguments contain copy --files-from and correct dest; assert temp list file is cleaned up.
EffectiveConfigTests.cs — all merge-precedence cases: null streamer field → global default; non-null streamer field → override wins.
SessionRepositoryTests.cs — EF in-memory; AddSessionAsync, GetRecentSessionsAsync, UpdateSessionAsync status change.
Verification
Manual checks:
All NavMenu links resolve without 404; active link is highlighted
Login page blocks unauthenticated access; correct password grants access; wrong password shows error
Dashboard renders streamer cards; Start/Stop toggles update Recovery State badge
Global Config saves to global.json; reload confirms persistence
Add Streamer creates config/streamers/{name}.json and redirects to per-streamer config page
Sessions page shows rows after a recording completes with expandable job list
Live Monitor shows real-time ProcessConsole output via SignalR
Decisions
NavMenu uses Blazor's built-in NavLink with pure CSS sidebar — no Bootstrap dependency to keep the bundle small
Config service reads/writes the same config directory as the Python side — both runtimes can share config files without conversion
EnsureCreated → Migrate() — migrations support future schema changes without data loss
BCrypt single-password auth over full Identity — this is a single-user self-hosted tool; Identity adds unnecessary overhead
IRecorderService extracted from StreamWorker — the recording path becomes independently testable without running the full state machine
Or simply paste the content above into a new file UpgradePlan-Part2.md at the workspace root. Once terminal/file-edit tools are re-enabled, I can write it directly.