feat: persistent GitHub release download sources - #6
Conversation
Store custom GitHub Releases sources in SQLite so they survive restarts,
then stream the pinned asset of the latest stable release through DevBox
with one click from the new Releases page.
- store: release_sources table (case-insensitive owner/repo, unique per
owner/repo/asset) with create/list/get/delete helpers
- dashboard: /api/release-sources CRUD, one-time download tickets and a
streaming /api/release-download endpoint
- dashboard: validate github.com/{owner}/{repo}/releases URLs and asset
names, cache latest-release lookups for 5m to save GitHub API quota
- dashboard: resolve assets by verified asset ID and restrict download
redirects to github.com and *.githubusercontent.com
- web: Releases page with add form, source list and download/delete
- tests: store CRUD/persistence plus handler, ticket and redirect tests
📝 WalkthroughWalkthroughAdds persistent GitHub Release sources, authenticated APIs, secure ticket-based asset downloads, backend tests, and Releases dashboard integration with navigation, API helpers, and documentation. ChangesGitHub Release downloads
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Browser
participant Dashboard
participant GitHub
participant Store
Browser->>Dashboard: create release source
Dashboard->>GitHub: fetch latest release
Dashboard->>Store: persist validated source
Browser->>Dashboard: request download ticket
Dashboard->>GitHub: resolve release asset
Dashboard-->>Browser: return ticket
Browser->>Dashboard: download with ticket
Dashboard->>GitHub: stream release asset
Dashboard-->>Browser: return file
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/dashboard/release.go`:
- Line 191: Update createDownloadTicket and createReleaseSource to call
latestRelease without unconditionally forcing a refresh, so the existing
five-minute release cache is reused and GitHub is queried only when the cached
asset is missing or stale. Preserve refresh behavior where required for cache
population or invalidation, and ensure normal download requests do not consume
an API call on every invocation.
- Around line 456-469: Update newReleaseDownloadClient to bound connection
establishment and response-header waiting by configuring an http.Transport with
net.Dialer connection timeouts and ResponseHeaderTimeout. Keep the client’s
total Timeout unset so large release downloads are not interrupted, and preserve
the existing redirect validation behavior.
In `@web/src/views/Releases.vue`:
- Around line 115-125: Update formatBytes to treat only undefined or otherwise
absent values as missing, while preserving zero as a valid input that returns “0
B”. Keep the existing unit conversion and formatting for positive byte values
unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 90626f1d-f360-46d0-947e-aa93c7c2843a
📒 Files selected for processing (12)
README.mdinternal/dashboard/dashboard.gointernal/dashboard/release.gointernal/dashboard/release_test.gointernal/server/server.gointernal/store/sqlite.gointernal/store/sqlite_test.goweb/src/App.vueweb/src/api/client.tsweb/src/router/index.tsweb/src/views/Releases.vueweb/src/views/Settings.vue
- dashboard: resolve the pinned asset from the cached latest release and only re-query GitHub when the cache is cold or no longer lists it, so downloads stop spending an API call each time - dashboard: bound dial, TLS handshake and response-header waits on the download client while leaving Client.Timeout unset for large assets - web: keep a zero asset size rendering as "0 B" instead of "-" - tests: assert the warm cache serves tickets, stale caches refresh and the download client keeps bounded connection timeouts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/dashboard/release_test.go`:
- Around line 144-149: Correct TestLatestReleaseAssetRefreshesStaleCache to
match its fixture: either rename it to describe refreshing a cached release
missing the requested asset, or change expiresAt to a time before now and add
assertions verifying TTL-based refresh. Keep the test’s intended behavior and
assertions aligned with the selected scenario.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: ea8b75bc-b92b-4504-8b02-7269539758cb
📒 Files selected for processing (3)
internal/dashboard/release.gointernal/dashboard/release_test.goweb/src/views/Releases.vue
TestLatestReleaseAssetRefreshesStaleCache seeded an unexpired entry, so it never exercised the TTL path its name promised. - rename it to TestLatestReleaseAssetRefreshesWhenCachedAssetMissing, which is what the fixture actually covers - add TestLatestReleaseAssetRefreshesExpiredCache for the TTL path: an expired entry that still lists the asset must be re-queried and recached
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@internal/dashboard/release_test.go`:
- Around line 163-165: Strengthen the missing-asset assertion around
dashboard.latestReleaseAsset by also requiring the GitHub mock call count to
equal 2, confirming the missing-asset path refreshes metadata before returning
errAssetNotFound.
🪄 Autofix (Beta)
✅ Autofix completed
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f588e4f9-e78b-4b0f-865c-bab5735f9100
📒 Files selected for processing (1)
internal/dashboard/release_test.go
| if _, _, err := dashboard.latestReleaseAsset(req, "moesnow", "March7thAssistant", "missing.7z"); !errors.Is(err, errAssetNotFound) { | ||
| t.Fatalf("expected errAssetNotFound, got %v", err) | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Assert that the missing-asset path actually refreshes metadata.
This assertion would pass even if the implementation returned errAssetNotFound without re-querying GitHub. Since the test promises refresh behavior, also require calls == 2.
Proposed fix
- if _, _, err := dashboard.latestReleaseAsset(req, "moesnow", "March7thAssistant", "missing.7z"); !errors.Is(err, errAssetNotFound) {
- t.Fatalf("expected errAssetNotFound, got %v", err)
+ if _, _, err := dashboard.latestReleaseAsset(req, "moesnow", "March7thAssistant", "missing.7z"); !errors.Is(err, errAssetNotFound) || calls != 2 {
+ t.Fatalf("expected refresh and errAssetNotFound, calls=%d err=%v", calls, err)
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if _, _, err := dashboard.latestReleaseAsset(req, "moesnow", "March7thAssistant", "missing.7z"); !errors.Is(err, errAssetNotFound) { | |
| t.Fatalf("expected errAssetNotFound, got %v", err) | |
| } | |
| if _, _, err := dashboard.latestReleaseAsset(req, "moesnow", "March7thAssistant", "missing.7z"); !errors.Is(err, errAssetNotFound) || calls != 2 { | |
| t.Fatalf("expected refresh and errAssetNotFound, calls=%d err=%v", calls, err) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@internal/dashboard/release_test.go` around lines 163 - 165, Strengthen the
missing-asset assertion around dashboard.latestReleaseAsset by also requiring
the GitHub mock call count to equal 2, confirming the missing-asset path
refreshes metadata before returning errAssetNotFound.
|
Note Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it. Fixes Applied SuccessfullyFixed 1 file(s) based on 1 unresolved review comment. Files modified:
Commit: The changes have been pushed to the Time taken: |
Fixed 1 file(s) based on 1 unresolved review comment. Co-authored-by: CodeRabbit <noreply@coderabbit.ai>
概述
在面板新增
Releases页面:一次添加 GitHub Release 源后长期保存,下次打开点击即可通过 DevBox 中转下载该仓库最新稳定版本中的固定资产(例如moesnow/March7thAssistant的update.7z)。改动
后端
internal/store/sqlite.go:新增release_sources表(owner/repo使用COLLATE NOCASE,owner+repo+asset_name唯一)与 CRUD 方法;数据存于/data/devbox.db,重启后保留。顺带把New()中手写的路径裁剪改为filepath.Dir。internal/dashboard/release.go(新增):GET/POST /api/release-sources、DELETE /api/release-sources/{id}、POST /api/release-sources/{id}/download-ticket、GET /api/release-download?ticket=。internal/server/server.go:注册上述路由(另有 4 行为 gofmt 修正既有缩进)。前端
web/src/views/Releases.vue与/releases路由、导航项:添加表单、来源列表(最新 tag、发布时间、大小、sha256、可用状态)、download / refresh / 行内二次确认 delete。web/src/api/client.ts新增对应 API 封装。安全设计
https://github.com/{owner}/{repo}/releases,拒绝其他 host、端口、userinfo、query、fragment;资产名禁止路径分隔符与控制字符。crypto/rand生成、2 分钟有效、消费即删除、总量上限 4096,因此面板 token 不会出现在下载 URL 中。api.github.com地址,不直接代理任意browser_download_url;重定向仅允许 HTTPS 的github.com与*.githubusercontent.com,并限制跳转次数。io.Copy流式转发,不落盘、不进内存;客户端断开时随请求上下文取消上游下载。测试
go build ./...、go vet ./...、go test ./...、pnpm run build全部通过。moesnow/March7thAssistant返回v2026.7.26/update.7z/ 177465850 字节 / sha256 一致;下载响应头Content-Length: 177465850、Content-Disposition: attachment; filename=update.7z,确认流式转发生效;票据重放与伪造票据均 401;非 github.com URL 400、重复源 409、资产不存在 422、删除 200 / 再删 404。未包含
govulncheck报 Go 标准库GO-2026-5856(需 go1.26.5),pnpm audit报 axios <1.18.0 与 postcss <=8.5.17。web/pnpm-lock.yaml因本地 pnpm 重新解析产生的无关改动已还原,未纳入提交。Summary by CodeRabbit
0no longer appear as missing./datapersistence details.