Skip to content

feat: implement bl project image (GET /api/v2/projects/{projectIdOrKey}/image)#123

Merged
23prime merged 2 commits intomainfrom
feature/119-project-image
Mar 22, 2026
Merged

feat: implement bl project image (GET /api/v2/projects/{projectIdOrKey}/image)#123
23prime merged 2 commits intomainfrom
feature/119-project-image

Conversation

@23prime
Copy link
Owner

@23prime 23prime commented Mar 22, 2026

Checklist

  • Target branch is main
  • Status checks are passing
  • Documentation updated if user-visible behavior changed (website/docs/, website/i18n/ja/, README.md)

Summary

  • Add bl project image <id-or-key> command to download the project icon image
  • Supports --output / -o to specify the save path; defaults to the server-provided filename (or project_image if the server returns no filename / a generic attachment placeholder)

Reason for change

Implements GET /api/v2/projects/{projectIdOrKey}/image so all published Backlog API v2 endpoints in the Project category are covered.

Changes

  • src/api/project.rs — add download_project_image(key)
  • src/api/mod.rs — add download_project_image to BacklogApi trait and impl BacklogApi for BacklogClient
  • src/cmd/project/image.rs — new command with image() / image_with() / default_output_path() and unit tests
  • src/cmd/project/mod.rs — re-export ProjectImageArgs and image
  • src/main.rs — add ProjectCommands::Image variant and dispatch
  • website/docs/commands.md, website/i18n/ja/.../commands.md — add command docs and mark as implemented

Notes

Tested against real Backlog API: bl project image SUKSaved: logo_mark.png (769 bytes).

Closes #119

Copilot AI review requested due to automatic review settings March 22, 2026 14:03
@coderabbitai
Copy link

coderabbitai bot commented Mar 22, 2026

📝 Walkthrough

Walkthrough

Implement project image download feature by adding download_project_image method to BacklogApi trait and BacklogClient, with a new CLI subcommand bl project image <id-or-key> that fetches and saves images to disk with customizable output path.

Changes

Cohort / File(s) Summary
API Layer
src/api/mod.rs, src/api/project.rs
Added download_project_image(&self, key: &str) -> Result<(Vec<u8>, String)> method to BacklogApi trait and implemented in BacklogClient to download project icon image from /projects/{key}/image endpoint.
CLI Command Handler
src/cmd/project/image.rs
New module implementing project image download logic with ProjectImageArgs struct, image() entry point, image_with() API-testable handler, and default_output_path() utility for filename normalization (fallback to project_image for generic/empty names). Includes unit tests for explicit/default path handling, API errors, and filename edge cases.
CLI Wiring
src/main.rs, src/cmd/project/mod.rs
Added ProjectCommands::Image subcommand variant with id_or_key and optional --output flag; updated dispatch to invoke command handler and re-exported ProjectImageArgs and image from image module.
Documentation
website/docs/commands.md, website/i18n/ja/docusaurus-plugin-content-docs/current/commands.md
Documented new bl project image <id-or-key> subcommand with --output/-o flag behavior and updated API coverage table to reflect implemented endpoint status (English and Japanese).

Sequence Diagram

sequenceDiagram
    participant User
    participant CLI
    participant BacklogApi
    participant Filesystem

    User->>CLI: bl project image <key> --output <path>
    CLI->>BacklogApi: download_project_image(key)
    BacklogApi->>BacklogApi: GET /projects/{key}/image
    BacklogApi-->>CLI: (Vec<u8>, filename)
    
    alt output path provided
        CLI->>Filesystem: write bytes to args.output
    else output path not provided
        CLI->>CLI: compute default_output_path(filename)
        CLI->>Filesystem: write bytes to computed path
    end
    
    Filesystem-->>CLI: success
    CLI-->>User: print success message with path & size
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.43% 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
Title check ✅ Passed The title clearly and concisely describes the main feature: implementing the bl project image command for the GET /api/v2/projects/{projectIdOrKey}/image endpoint.
Linked Issues check ✅ Passed All coding requirements from issue #119 are met: API endpoint implementation [#119], CLI subcommand with --output support [#119], binary image handling [#119], and documentation updates [#119].
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing the bl project image command and its supporting infrastructure; no unrelated modifications detected.
Description check ✅ Passed The pull request description provides a clear, detailed explanation of the changes and their purpose, directly related to the changeset.

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

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feature/119-project-image

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.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds support for downloading a project’s icon image via Backlog API v2 and exposes it as a new bl project image <id-or-key> CLI command, completing coverage for the Project-image endpoint.

Changes:

  • Implement GET /api/v2/projects/{projectIdOrKey}/image download support in the API layer and wire it into BacklogApi.
  • Add bl project image command with --output/-o handling and unit tests, plus CLI dispatch.
  • Update English/Japanese command docs and the command-coverage tables to mark the endpoint as implemented.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/api/project.rs Adds BacklogClient::download_project_image() endpoint wrapper.
src/api/mod.rs Extends BacklogApi trait + BacklogClient impl to expose project image download.
src/cmd/project/image.rs New command implementation + default output path logic + unit tests.
src/cmd/project/mod.rs Re-exports the new project image command/args.
src/main.rs Adds project image subcommand variant and dispatch plumbing.
website/docs/commands.md Documents bl project image and marks endpoint implemented in coverage table.
website/i18n/ja/.../commands.md Japanese documentation + coverage table update for the new command.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/api/project.rs (1)

608-610: Add API-layer tests for the new download endpoint wrapper.

This wrapper is correct, but it currently has no dedicated API-layer coverage in this file. Adding one success-path and one fallback-filename test would protect the /projects/{key}/image contract.

🧪 Suggested test additions
 #[cfg(test)]
 mod tests {
     use super::*;
     use httpmock::prelude::*;
     use serde_json::json;
@@
+    #[test]
+    fn download_project_image_returns_bytes_and_filename() {
+        let server = MockServer::start();
+        server.mock(|when, then| {
+            when.method(GET).path("/projects/TEST/image");
+            then.status(200)
+                .header("content-disposition", "attachment; filename=\"logo_mark.png\"")
+                .body("PNG");
+        });
+
+        let client = BacklogClient::new_with(&server.base_url(), "test-key").unwrap();
+        let (bytes, filename) = client.download_project_image("TEST").unwrap();
+        assert_eq!(filename, "logo_mark.png");
+        assert_eq!(bytes, b"PNG".to_vec());
+    }
+
+    #[test]
+    fn download_project_image_falls_back_to_attachment_filename() {
+        let server = MockServer::start();
+        server.mock(|when, then| {
+            when.method(GET).path("/projects/TEST/image");
+            then.status(200).body("IMG");
+        });
+
+        let client = BacklogClient::new_with(&server.base_url(), "test-key").unwrap();
+        let (_bytes, filename) = client.download_project_image("TEST").unwrap();
+        assert_eq!(filename, "attachment");
+    }
As per coding guidelines: “For `api/` layer tests, use `httpmock` to spin up a local HTTP server and construct `BacklogClient::new_with(base_url, api_key)` instead of calling `BacklogClient::from_config()`.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/project.rs` around lines 608 - 610, Add two httpmock-based API-layer
tests for the download_project_image wrapper: (1) success-path: spin up an
httpmock server, create BacklogClient::new_with(mock.base_url(), "api_key"), set
an expectation for GET /projects/{key}/image to return image bytes with a
Content-Disposition header (e.g., attachment; filename="img.png"), call
BacklogClient::download_project_image(key) and assert the returned Vec<u8>
equals the body and the returned filename equals "img.png"; (2)
fallback-filename: same setup but return image bytes without Content-Disposition
and assert the returned filename falls back to "{key}.png" (or whatever fallback
logic the download function uses). Ensure both tests exercise
BacklogClient::download (indirectly via download_project_image) and use httpmock
server URL with BacklogClient::new_with as recommended.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/cmd/project/image.rs`:
- Around line 34-47: default_output_path currently checks for "attachment" on
the full input before extracting the basename, so paths like
"foo/attachment.png" won't be detected; change the flow to first extract the
basename via file_name() (falling back to "project_image" when file_name is
None), convert that basename to a Rust &str/OsStr and then run the
lowercase/generic-attachment check on that basename; if the basename is empty or
matches "attachment" or starts_with "attachment.", return "project_image",
otherwise return the basename as PathBuf, keeping the function name
default_output_path and variables like normalized/effective/base in mind to
locate where to reorder the logic.

In `@website/docs/commands.md`:
- Line 931: Update the sentence explaining where binary responses are saved to
also state that the client will treat server-provided filenames of "attachment"
or "attachment.*" as missing and fall back to saving the file in the current
directory (using the returned filename or `project_image` if none is provided);
edit the existing line in commands.md that starts "The response is binary
data..." to explicitly mention the "attachment" / "attachment.*" fallback
behavior so readers know that those server filenames are treated the same as no
filename.

In `@website/i18n/ja/docusaurus-plugin-content-docs/current/commands.md`:
- Around line 931-942: The documented fallback filename ("project_image") and
the example output ("Saved: project_image.png") are inconsistent; update the
documentation for the command examples so they match: either state that the
fallback is "project_image.png" (if the CLI appends an extension) or change the
example output to "Saved: project_image (1234 bytes)" to reflect a no-extension
fallback; modify the text mentioning `project_image` and the example line
`Saved: project_image.png` so both use the same fallback behavior.

---

Nitpick comments:
In `@src/api/project.rs`:
- Around line 608-610: Add two httpmock-based API-layer tests for the
download_project_image wrapper: (1) success-path: spin up an httpmock server,
create BacklogClient::new_with(mock.base_url(), "api_key"), set an expectation
for GET /projects/{key}/image to return image bytes with a Content-Disposition
header (e.g., attachment; filename="img.png"), call
BacklogClient::download_project_image(key) and assert the returned Vec<u8>
equals the body and the returned filename equals "img.png"; (2)
fallback-filename: same setup but return image bytes without Content-Disposition
and assert the returned filename falls back to "{key}.png" (or whatever fallback
logic the download function uses). Ensure both tests exercise
BacklogClient::download (indirectly via download_project_image) and use httpmock
server URL with BacklogClient::new_with as recommended.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5811f7de-ff5a-4101-8092-3daaf939406b

📥 Commits

Reviewing files that changed from the base of the PR and between 28bf878 and c5790aa.

📒 Files selected for processing (7)
  • src/api/mod.rs
  • src/api/project.rs
  • src/cmd/project/image.rs
  • src/cmd/project/mod.rs
  • src/main.rs
  • website/docs/commands.md
  • website/i18n/ja/docusaurus-plugin-content-docs/current/commands.md

… align docs

Addresses review comments: basename-first attachment detection, CWD-mutation test removal, and doc clarification for attachment fallback
@23prime 23prime merged commit 89bbd1e into main Mar 22, 2026
8 checks passed
@23prime 23prime deleted the feature/119-project-image branch March 22, 2026 14:15
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.

feat: implement bl project image (GET /api/v2/projects/{projectIdOrKey}/image)

2 participants