Skip to content

Commit 2961266

Browse files
committed
docs(17): create plan 17-01 — account storage foundation and list view
1 parent 60dfbea commit 2961266

1 file changed

Lines changed: 292 additions & 0 deletions

File tree

Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
---
2+
phase: 17-account-storage-foundation
3+
plan: 01
4+
type: execute
5+
wave: 1
6+
depends_on: []
7+
files_modified:
8+
- src/Ray.BiliBiliTool.Web/Program.cs
9+
- src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/IBiliAccountPageWorkflow.cs
10+
- src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/BiliAccountPageWorkflow.cs
11+
- src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/BiliAccountDto.cs
12+
- src/Ray.BiliBiliTool.Web/Extensions/ServiceCollectionExtension.cs
13+
- src/Ray.BiliBiliTool.Web/Components/Pages/BiliAccount.razor
14+
- src/Ray.BiliBiliTool.Web/Components/Layout/NavMenu.razor
15+
autonomous: true
16+
requirements:
17+
- ACCT-07
18+
- ACCT-01
19+
20+
must_haves:
21+
truths:
22+
- "Web host no longer loads config/cookies.json — SQLite bili_appsettings is the sole cookie config source for Web"
23+
- "Maintainer sees a 'Bili Account' top-level nav item in the sidebar"
24+
- "Maintainer sees all configured Bili accounts listed with UserId and full cookie string"
25+
- "Account list reflects the current state in SQLite bili_appsettings"
26+
artifacts:
27+
- path: "src/Ray.BiliBiliTool.Web/Program.cs"
28+
provides: "cookies.json config source removed"
29+
contains: "AddSqlite"
30+
- path: "src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/IBiliAccountPageWorkflow.cs"
31+
provides: "Workflow seam contract for Bili Account page"
32+
exports: ["IBiliAccountPageWorkflow"]
33+
- path: "src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/BiliAccountPageWorkflow.cs"
34+
provides: "Workflow implementation reading accounts from IConfiguration"
35+
- path: "src/Ray.BiliBiliTool.Web/Components/Pages/BiliAccount.razor"
36+
provides: "Account list page with MudTable"
37+
- path: "src/Ray.BiliBiliTool.Web/Components/Layout/NavMenu.razor"
38+
provides: "Bili Account nav entry"
39+
key_links:
40+
- from: "src/Ray.BiliBiliTool.Web/Components/Pages/BiliAccount.razor"
41+
to: "IBiliAccountPageWorkflow"
42+
via: "Inject and call GetAllAccountsAsync on init"
43+
pattern: "IBiliAccountPageWorkflow"
44+
- from: "src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/BiliAccountPageWorkflow.cs"
45+
to: "IConfiguration"
46+
via: "Read BiliBiliCookies section"
47+
pattern: "GetSection.*BiliBiliCookies"
48+
- from: "src/Ray.BiliBiliTool.Web/Extensions/ServiceCollectionExtension.cs"
49+
to: "IBiliAccountPageWorkflow"
50+
via: "DI registration"
51+
pattern: "IBiliAccountPageWorkflow"
52+
---
53+
54+
<objective>
55+
Remove `cookies.json` from the Web host configuration pipeline and create the Bili Account page with a read-only account list view.
56+
57+
Purpose: Establishes SQLite as the sole cookie config source for Web (ACCT-07) and gives the maintainer visibility into configured accounts (ACCT-01). This is the foundation for CRUD operations in Phase 18.
58+
59+
Output: Modified Program.cs (cookies.json removed), new workflow seam (`IBiliAccountPageWorkflow`), new Bili Account page with MudTable listing all accounts, NavMenu entry.
60+
</objective>
61+
62+
<execution_context>
63+
@~/.copilot/get-shit-done/workflows/execute-plan.md
64+
@~/.copilot/get-shit-done/templates/summary.md
65+
</execution_context>
66+
67+
<context>
68+
@.planning/PROJECT.md
69+
@.planning/ROADMAP.md
70+
@.planning/STATE.md
71+
@.planning/REQUIREMENTS.md
72+
@.planning/research/SUMMARY.md
73+
74+
<interfaces>
75+
<!-- Key types and contracts the executor needs. -->
76+
77+
From src/Ray.BiliBiliTool.Infrastructure/Cookie/CookieStrFactory.cs:
78+
```csharp
79+
// Reads BiliBiliCookies__0, BiliBiliCookies__1, etc. from IConfiguration
80+
// configuration.GetSection("BiliBiliCookies").Get<List<string>>() returns the cookie strings
81+
public class CookieStrFactory<TCookieInfo>(IConfiguration configuration) where TCookieInfo : CookieInfo
82+
{
83+
public int Count => CookieDictionary.Count;
84+
public TCookieInfo GetCookie(int index);
85+
}
86+
```
87+
88+
From src/Ray.BiliBiliTool.Agent/BiliCookie.cs:
89+
```csharp
90+
public class BiliCookie(Dictionary<string, string> cookieDic) : CookieInfo(cookieDic)
91+
{
92+
public string UserId => // reads "DedeUserID" from cookie dict
93+
public string SessData => // reads "SESSDATA"
94+
public string BiliJct => // reads "bili_jct"
95+
}
96+
```
97+
98+
From src/Ray.BiliBiliTool.Infrastructure/Cookie/CookieInfo.cs:
99+
```csharp
100+
public class CookieInfo(Dictionary<string, string> cookieDic)
101+
{
102+
public string CookieStr => // joins all cookie key=value pairs with "; "
103+
}
104+
```
105+
106+
From src/Ray.BiliBiliTool.Web/Services/Pages/Admin/IAdminPageWorkflow.cs (pattern to follow):
107+
```csharp
108+
public interface IAdminPageWorkflow
109+
{
110+
Task<AdminPasswordChangeResult> ChangePasswordAsync(AdminPasswordChangeRequest request);
111+
}
112+
```
113+
114+
From src/Ray.BiliBiliTool.Web/Extensions/ServiceCollectionExtension.cs:
115+
```csharp
116+
public static IServiceCollection AddWebServices(this IServiceCollection services)
117+
{
118+
services.AddScoped<IAuthService, AuthService>();
119+
services.AddScoped<ILoginPageStateFactory, LoginPageStateFactory>();
120+
services.AddScoped<IAdminPageWorkflow, AdminPageWorkflow>();
121+
// ... add IBiliAccountPageWorkflow here
122+
}
123+
```
124+
125+
From src/Ray.BiliBiliTool.Web/Program.cs (line 21 — the line to remove):
126+
```csharp
127+
builder.Configuration.AddJsonFile("config/cookies.json", optional: true, reloadOnChange: true);
128+
```
129+
130+
From src/Ray.BiliBiliTool.Web/Components/Layout/NavMenu.razor:
131+
```razor
132+
<!-- Add "Bili Account" nav item BEFORE the Configurations submenu -->
133+
<!-- Pattern: same as other nav items -->
134+
<div class="nav-item px-3">
135+
<NavLink class="nav-link" href="BiliAccount">
136+
<span class="bi bi-people-fill" aria-hidden="true"></span> Bili Account
137+
</NavLink>
138+
</div>
139+
```
140+
</interfaces>
141+
</context>
142+
143+
<tasks>
144+
145+
<task type="auto">
146+
<name>Task 1: Remove cookies.json source and create workflow seam</name>
147+
<files>
148+
src/Ray.BiliBiliTool.Web/Program.cs,
149+
src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/IBiliAccountPageWorkflow.cs,
150+
src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/BiliAccountPageWorkflow.cs,
151+
src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/BiliAccountDto.cs,
152+
src/Ray.BiliBiliTool.Web/Extensions/ServiceCollectionExtension.cs
153+
</files>
154+
<action>
155+
**ACCT-07 — Remove cookies.json from Web host:**
156+
157+
In `src/Ray.BiliBiliTool.Web/Program.cs`, delete line 21:
158+
```csharp
159+
builder.Configuration.AddJsonFile("config/cookies.json", optional: true, reloadOnChange: true);
160+
```
161+
Keep the `AddSqlite` block (lines 22-30) unchanged. The `AddJsonFile` for `appsettings.json` and `appsettings.Development.json` (if any) must NOT be touched — only the `cookies.json` line.
162+
163+
**Create workflow seam (following v4.0.0.6 IAdminPageWorkflow pattern):**
164+
165+
1. Create `src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/BiliAccountDto.cs`:
166+
```csharp
167+
namespace Ray.BiliBiliTool.Web.Services.Pages.BiliAccount;
168+
169+
public record BiliAccountDto(int Index, string UserId, string CookieStr);
170+
```
171+
172+
2. Create `src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/IBiliAccountPageWorkflow.cs`:
173+
```csharp
174+
namespace Ray.BiliBiliTool.Web.Services.Pages.BiliAccount;
175+
176+
public interface IBiliAccountPageWorkflow
177+
{
178+
Task<List<BiliAccountDto>> GetAllAccountsAsync();
179+
}
180+
```
181+
182+
3. Create `src/Ray.BiliBiliTool.Web/Services/Pages/BiliAccount/BiliAccountPageWorkflow.cs`:
183+
- Inject `IConfiguration` (not `CookieStrFactory` — the workflow reads the raw config section directly to avoid singleton lifetime issues)
184+
- `GetAllAccountsAsync()` reads `configuration.GetSection("BiliBiliCookies").Get<List<string>>() ?? []`
185+
- For each cookie string at index N, parse it into a `Dictionary<string, string>` (split on ";", then on "="), extract `DedeUserID` as UserId, and return `new BiliAccountDto(N, userId, cookieStr)`
186+
- Use the same parsing logic as `CookieStrFactory.CkStrToDictionary` — split on ";", trim, then split on first "=" to get key/value
187+
- If parsing fails for a cookie, still include it in the list with UserId="(unknown)"
188+
189+
4. Register in `src/Ray.BiliBiliTool.Web/Extensions/ServiceCollectionExtension.cs`:
190+
Add `services.AddScoped<IBiliAccountPageWorkflow, BiliAccountPageWorkflow>();` in `AddWebServices()`, after the existing workflow registrations.
191+
</action>
192+
<verify>
193+
<automated>cd "d:\Codes\My\Bili\branch\blazor\BiliBiliToolPro" && dotnet build src/Ray.BiliBiliTool.Web/Ray.BiliBiliTool.Web.csproj --no-restore 2>&1 | Select-String -Pattern "error|Error|Build succeeded"</automated>
194+
</verify>
195+
<done>
196+
- `cookies.json` AddJsonFile line removed from Program.cs
197+
- `IBiliAccountPageWorkflow` interface exists with `GetAllAccountsAsync()` method
198+
- `BiliAccountPageWorkflow` reads cookies from IConfiguration and returns List&lt;BiliAccountDto&gt;
199+
- `BiliAccountDto` record with Index, UserId, CookieStr
200+
- DI registration in ServiceCollectionExtension.cs
201+
- Build succeeds with 0 errors
202+
</done>
203+
</task>
204+
205+
<task type="auto">
206+
<name>Task 2: Create Bili Account page and NavMenu entry</name>
207+
<files>
208+
src/Ray.BiliBiliTool.Web/Components/Pages/BiliAccount.razor,
209+
src/Ray.BiliBiliTool.Web/Components/Layout/NavMenu.razor
210+
</files>
211+
<action>
212+
**Create Bili Account page:**
213+
214+
Create `src/Ray.BiliBiliTool.Web/Components/Pages/BiliAccount.razor`:
215+
- `@page "/BiliAccount"`
216+
- `@using Microsoft.AspNetCore.Authorization`
217+
- `@using Ray.BiliBiliTool.Web.Services.Pages.BiliAccount`
218+
- `@rendermode InteractiveServer`
219+
- `@attribute [Authorize]`
220+
- Inject `IBiliAccountPageWorkflow`
221+
- On `OnInitializedAsync`, call `_accounts = await workflow.GetAllAccountsAsync()`
222+
- Display accounts in a `MudTable<T="BiliAccountDto"` with columns:
223+
- **#** — `context.Index` (the BiliBiliCookies__N index)
224+
- **UserId** — `context.UserId`
225+
- **Cookie** — `context.CookieStr` (truncated display with tooltip for full value, use `MudTooltip` or just show first 80 chars + "...")
226+
- Show `MudProgressCircular` while loading
227+
- Show "No accounts configured" message if list is empty
228+
- Page title: "Bili Account"
229+
- Use MudBlazor components consistent with existing pages (MudContainer, MudText, MudTable, etc.)
230+
231+
**Add NavMenu entry:**
232+
233+
In `src/Ray.BiliBiliTool.Web/Components/Layout/NavMenu.razor`, add a new nav item BEFORE the Configurations submenu div (before the `<div class="nav-item px-3">` that contains the Configurations toggle). Use the same pattern as other nav items:
234+
235+
```razor
236+
<div class="nav-item px-3">
237+
<NavLink class="nav-link" href="BiliAccount">
238+
<span class="bi bi-people-fill" aria-hidden="true"></span> Bili Account
239+
</NavLink>
240+
</div>
241+
```
242+
243+
Place it after the "Schedules" nav item and before the "Configurations" submenu.
244+
</action>
245+
<verify>
246+
<automated>cd "d:\Codes\My\Bili\branch\blazor\BiliBiliToolPro" && dotnet build src/Ray.BiliBiliTool.Web/Ray.BiliBiliTool.Web.csproj --no-restore 2>&1 | Select-String -Pattern "error|Error|Build succeeded"</automated>
247+
</verify>
248+
<done>
249+
- BiliAccount.razor page exists at /BiliAccount route with @attribute [Authorize]
250+
- Page displays MudTable with columns: #, UserId, Cookie
251+
- Page shows loading spinner and empty state
252+
- NavMenu has "Bili Account" entry between Schedules and Configurations
253+
- Build succeeds with 0 errors
254+
</done>
255+
</task>
256+
257+
</tasks>
258+
259+
<threat_model>
260+
## Trust Boundaries
261+
262+
| Boundary | Description |
263+
|----------|-------------|
264+
| Browser → Blazor Server | Cookie strings displayed in the browser are sensitive session credentials |
265+
| Blazor Server → SQLite | Configuration writes must be validated before persistence |
266+
267+
## STRIDE Threat Register
268+
269+
| Threat ID | Category | Component | Disposition | Mitigation Plan |
270+
|-----------|----------|-----------|-------------|-----------------|
271+
| T-17-01 | Information Disclosure | BiliAccount.razor | accept | Cookie strings shown only to authenticated users (@attribute [Authorize]); page is admin-only by design |
272+
| T-17-02 | Tampering | Program.cs config pipeline | mitigate | Removing cookies.json eliminates stale/external config tampering; SQLite is the single source of truth |
273+
</threat_model>
274+
275+
<verification>
276+
1. `dotnet build src/Ray.BiliBiliTool.Web/Ray.BiliBiliTool.Web.csproj` — 0 errors
277+
2. `dotnet test test/Ray.BiliBiliTool.ArchitectureTests/` — all pass (no new dependency violations)
278+
3. `dotnet test test/Ray.BiliBiliTool.Host.IntegrationTests/` — all pass (config pipeline change doesn't break existing tests)
279+
4. Manual: navigate to /BiliAccount after login — table shows configured accounts
280+
</verification>
281+
282+
<success_criteria>
283+
- Web host no longer loads `config/cookies.json` (grep Program.cs confirms line removed)
284+
- "Bili Account" appears in NavMenu between Schedules and Configurations
285+
- BiliAccount.razor renders a MudTable with all configured accounts showing Index, UserId, CookieStr
286+
- `IBiliAccountPageWorkflow` follows the v4.0.0.6 seam pattern (interface + implementation + DI registration)
287+
- Build 0 errors | architecture tests pass | integration tests pass
288+
</success_criteria>
289+
290+
<output>
291+
After completion, create `.planning/phases/17-account-storage-foundation/17-01-SUMMARY.md`
292+
</output>

0 commit comments

Comments
 (0)