Skip to content

Add directory download support to backups and file download APIs - #18

Merged
cyberinferno merged 2 commits into
masterfrom
feat/dir-download
Jun 14, 2026
Merged

Add directory download support to backups and file download APIs#18
cyberinferno merged 2 commits into
masterfrom
feat/dir-download

Conversation

@cyberinferno

@cyberinferno cyberinferno commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Added backend support for directory download jobs, persistence, and routes
  • Extended backup and file tree UI to surface directory download actions
  • Updated OpenAPI and API client types to match the new download flow
  • Added and updated service and route tests for download behavior

Testing

  • Updated unit tests for backup service and file download routes
  • Not run (not requested)

Summary by CodeRabbit

Release Notes

New Features

  • Users can now download directories as ZIP archives directly from the file browser
  • Added ability to check download status and retrieve secure, expiring download links for directory archives
  • Implemented intelligent caching to reuse existing directory archives when source content remains unchanged, improving performance for repeated downloads

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cyberinferno, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 30 minutes and 14 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cff5509c-772a-42b9-b9b9-b50b53465ded

📥 Commits

Reviewing files that changed from the base of the PR and between a1e2d0a and 228cab3.

📒 Files selected for processing (6)
  • README.md
  • cmd/omnihance-a3-agent/docs/openapi.yml
  • cmd/omnihance-a3-agent/omnihance-a3-agent-ui/README.md
  • cmd/omnihance-a3-agent/omnihance-a3-agent-ui/src/components/file-tree.tsx
  • internal/services/backup_service.go
  • internal/services/backup_service_test.go
📝 Walkthrough

Walkthrough

This PR implements end-to-end directory ZIP download functionality. A new DB migration (015) creates the directory_download_archives table and adds a tag column to backup_jobs. The backup service gains PrepareDirectoryDownload and GetDirectoryDownloadStatus methods that create tagged backup jobs, compute source fingerprints, produce exclusion-aware ZIP archives with caching, and map run state to lifecycle results. Two new HTTP endpoints (POST /directory-download-link, GET /directory-downloads/{run_id}) wire these into the existing file-download link infrastructure. The frontend adds a polling mutation with toast feedback for directory rows and adapts the backup page to omit the destination field for directory-download jobs.

Changes

Directory Download Feature

Layer / File(s) Summary
Data model contracts and configuration
internal/config/config.go, internal/db/backup_jobs.go, internal/db/directory_downloads.go, internal/db/file_downloads.go, internal/db/internal_db.go
Adds DirectoryDownloadsDirectory env config, BackupJobTagDirectoryDownload/BackupRunTriggerDirectoryDownload/FileDownloadSourceDirectoryDownload constants, nullable Tag on BackupJob/BackupJobPayload, DirectoryDownloadArchive/DirectoryDownloadArchivePayload types, and four new method signatures on the InternalDB interface.
DB migration 015 and query implementations
internal/db/internal_db.go, internal/db/directory_downloads.go, internal/db/mock_InternalDB.go
Migration 015 creates directory_download_archives and adds tag column/indexes to backup_jobs. Implements GetBackupJobByTagAndSourcePath, GetRunningBackupRunForTag, GetDirectoryDownloadArchive, and UpsertDirectoryDownloadArchive. Autogenerated MockInternalDB stubs follow.
Backup service: PrepareDirectoryDownload, zip compression, and helpers
internal/services/backup_service.go, internal/services/backup_service_test.go, internal/services/mock_BackupService.go
Extends BackupService interface and implements PrepareDirectoryDownload (path normalization, in-progress reuse, tagged job/run creation) and GetDirectoryDownloadStatus (run-state-to-result mapping). Adds runDirectoryDownloadBackup (fingerprint + exclusion-aware zip + archive upsert), createZipArchiveWithExclusions, path exclusion helpers, and a suite of directory-download utility functions. Refactors RunJob into runJobLocked. Integration tests and mock service stubs included.
HTTP handlers, routing, and server tests
internal/server/file_system_routes.go, internal/server/file_download_routes.go, internal/server/file_download_routes_test.go
Registers POST /directory-download-link and GET /directory-downloads/{run_id} routes. Handlers enforce permissions, call backup service, and return 200/202. writeDirectoryDownloadError maps errors to 409/404/400/500. directoryDownloadResponse creates reusable download links when ready. Tests verify 202 in-progress and 200 ready with link-token DB assertions.
OpenAPI spec
cmd/omnihance-a3-agent/docs/openapi.yml
Documents both new endpoints with 200/202 shapes, the DirectoryDownloadResponse schema, and the optional tag field added to BackupJob.
Frontend API client: routes, schemas, and functions
cmd/.../src/lib/api.ts, cmd/.../src/constants.ts
Adds DIRECTORY_DOWNLOAD_LINK/DIRECTORY_DOWNLOAD_STATUS API routes, Zod DirectoryDownloadResponseSchema union across all lifecycle states, createDirectoryDownloadLink/getDirectoryDownloadStatus functions, nullable tag on BackupJobSchema, and queryKeys.directoryDownload factory.
Frontend UI: file-tree download flow and backup-page tag handling
cmd/.../src/components/file-tree.tsx, cmd/.../src/components/backup-page.tsx
file-tree.tsx adds downloadDirectoryMutation, polling useQuery, toast lifecycle state/refs, and per-row download buttons with spinners for both files and directories. backup-page.tsx adds isDirectoryDownloadBackupJob, conditionally omits the Destination field, and introduces formatBackupTriggerLabel.

Sequence Diagram

sequenceDiagram
  participant User
  participant FileTree
  participant GoServer as Go HTTP Server
  participant BackupService
  participant SQLite

  User->>FileTree: clicks Download on directory row
  FileTree->>GoServer: POST /api/file-tree/directory-download-link?path=...
  GoServer->>BackupService: PrepareDirectoryDownload(ctx, path, userID)
  BackupService->>SQLite: GetRunningBackupRunForTag("directory_download")
  alt archive already cached and ready
    BackupService->>SQLite: GetDirectoryDownloadArchive(normalizedPath, fingerprint)
    SQLite-->>BackupService: DirectoryDownloadArchive
    BackupService-->>GoServer: DirectoryDownloadResult{Status: Ready}
    GoServer->>SQLite: CreateFileDownloadLink(source=directory_download)
    GoServer-->>FileTree: 200 + download_url
    FileTree->>User: navigates to download_url
  else no cached archive
    BackupService->>SQLite: CreateBackupJob(tag=directory_download) + CreateBackupRun
    BackupService-->>GoServer: DirectoryDownloadResult{Status: Started, RunID}
    GoServer-->>FileTree: 202 + run_id
    loop poll until ready
      FileTree->>GoServer: GET /api/file-tree/directory-downloads/{run_id}
      GoServer->>BackupService: GetDirectoryDownloadStatus(ctx, runID)
      BackupService->>SQLite: GetBackupRun + GetDirectoryDownloadArchive
      GoServer-->>FileTree: 200 {status: ready, download_url} or 202
    end
    FileTree->>User: navigates to download_url
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • omnihance/omnihance-a3-agent#17: Introduced the FileDownloadLink secure-link infrastructure and FileDownloadSourceFileBrowser/FileDownloadSourceBackup constants that this PR extends with FileDownloadSourceDirectoryDownload and reuses for serving directory archive tokens.

Poem

🐇 Hop, hop, through the folder tree,
A whole directory zipped just for me!
The fingerprint hashes, the archive is spun,
Poll till it's ready — the download is done!
Exclusions in place so no loops appear,
Cache it for next time, my burrow is near. 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding directory download support to the backup and file download APIs, which aligns with the substantial additions across backend routes, database models, services, and UI components.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dir-download

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
cmd/omnihance-a3-agent/docs/openapi.yml (1)

4622-4661: ⚡ Quick win

Make core DirectoryDownloadResponse fields required to match consumers.

Line 4622 currently leaves all properties optional. The consumer contract expects status, message, job_id, and run_id to always exist; optionalizing them weakens generated types and can drift from runtime expectations.

🛠️ Proposed OpenAPI patch
     DirectoryDownloadResponse:
       type: object
+      required:
+        - status
+        - message
+        - job_id
+        - run_id
       properties:
         status:
           type: string
🤖 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 `@cmd/omnihance-a3-agent/docs/openapi.yml` around lines 4622 - 4661, The
DirectoryDownloadResponse schema in the OpenAPI specification currently has all
properties optional, but the consumer contract expects status, message, job_id,
and run_id to always be present. Add a required array property to the
DirectoryDownloadResponse object that lists these four fields (status, message,
job_id, run_id) to enforce them as mandatory in the generated types and match
runtime expectations.
🤖 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 `@cmd/omnihance-a3-agent/docs/openapi.yml`:
- Around line 341-392: The secured endpoints in the OpenAPI specification
declare ApiKeyAuth but are missing documentation for the 401 Unauthorized
response, which is required to complete the auth contract for client generation.
Add a 401 response definition to both the /api/file-tree/directory-download-link
endpoint (at lines 341-392) and the other affected endpoint (at lines 393-438)
that use ApiKeyAuth security. Each 401 response should reference the
ErrorResponse schema and describe authentication failure as the reason.

In `@cmd/omnihance-a3-agent/omnihance-a3-agent-ui/src/components/file-tree.tsx`:
- Around line 609-629: The refetchInterval callback in the useQuery
configuration does not handle query errors, causing endless polling when the
status fetch fails because query.state.data is empty. Modify the refetchInterval
function to check if query.state.error exists before checking the status; if an
error is present, return false to stop polling. Only return the 2000ms interval
if there is valid data and the status is not one of the terminal states (ready,
failed, or cancelled).
- Around line 892-898: The isDownloadPending state calculation for directories
only checks if the downloadDirectoryMutation is currently in-flight, but does
not account for the polling state that continues after the mutation completes.
To fix this, extend the pending state check for directories to also include a
check for any active polling operation associated with the same itemPath.
Specifically, add a condition that checks whether there is an ongoing poll for
the directory (likely checking a polling state variable or hook) in addition to
the existing downloadDirectoryMutation.isPending check, so that the row remains
in a pending state while the directory download is being polled/processed in the
background.

In `@internal/services/backup_service.go`:
- Around line 452-459: The issue is that in the PrepareDirectoryDownload
function, the call to runningDirectoryDownloadResult happens before checking the
fingerprint and archive cache for reusable cached archives. This causes
unnecessary 409 conflict errors when a ready cached archive exists. Reorder the
logic so that the archive cache lookup occurs before the
runningDirectoryDownloadResult check, allowing cached archives to be reused
immediately without being blocked by running jobs for other directories.

---

Nitpick comments:
In `@cmd/omnihance-a3-agent/docs/openapi.yml`:
- Around line 4622-4661: The DirectoryDownloadResponse schema in the OpenAPI
specification currently has all properties optional, but the consumer contract
expects status, message, job_id, and run_id to always be present. Add a required
array property to the DirectoryDownloadResponse object that lists these four
fields (status, message, job_id, run_id) to enforce them as mandatory in the
generated types and match runtime expectations.
🪄 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: 5b216af9-77d8-423c-8357-26304a6ae96d

📥 Commits

Reviewing files that changed from the base of the PR and between 72936c6 and a1e2d0a.

📒 Files selected for processing (17)
  • cmd/omnihance-a3-agent/docs/openapi.yml
  • cmd/omnihance-a3-agent/omnihance-a3-agent-ui/src/components/backup-page.tsx
  • cmd/omnihance-a3-agent/omnihance-a3-agent-ui/src/components/file-tree.tsx
  • cmd/omnihance-a3-agent/omnihance-a3-agent-ui/src/constants.ts
  • cmd/omnihance-a3-agent/omnihance-a3-agent-ui/src/lib/api.ts
  • internal/config/config.go
  • internal/db/backup_jobs.go
  • internal/db/directory_downloads.go
  • internal/db/file_downloads.go
  • internal/db/internal_db.go
  • internal/db/mock_InternalDB.go
  • internal/server/file_download_routes.go
  • internal/server/file_download_routes_test.go
  • internal/server/file_system_routes.go
  • internal/services/backup_service.go
  • internal/services/backup_service_test.go
  • internal/services/mock_BackupService.go

Comment thread cmd/omnihance-a3-agent/docs/openapi.yml
Comment thread cmd/omnihance-a3-agent/omnihance-a3-agent-ui/src/components/file-tree.tsx Outdated
Comment thread cmd/omnihance-a3-agent/omnihance-a3-agent-ui/src/components/file-tree.tsx Outdated
Comment thread internal/services/backup_service.go Outdated
@cyberinferno
cyberinferno merged commit af5ac2f into master Jun 14, 2026
2 checks passed
@cyberinferno
cyberinferno deleted the feat/dir-download branch June 14, 2026 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant