-
Notifications
You must be signed in to change notification settings - Fork 0
Implement records CLI commands (list, search, get, versions) #36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,187 @@ | ||||||
| package cli | ||||||
|
|
||||||
| import ( | ||||||
| "fmt" | ||||||
| "os" | ||||||
| "strconv" | ||||||
|
|
||||||
| "github.com/ran-codes/zenodo-cli/internal/api" | ||||||
| "github.com/ran-codes/zenodo-cli/internal/model" | ||||||
| "github.com/ran-codes/zenodo-cli/internal/output" | ||||||
| "github.com/spf13/cobra" | ||||||
| ) | ||||||
|
|
||||||
| var recordsCmd = &cobra.Command{ | ||||||
| Use: "records", | ||||||
| Short: "List, search, and view records", | ||||||
| } | ||||||
|
|
||||||
| var recordsListCmd = &cobra.Command{ | ||||||
| Use: "list", | ||||||
| Short: "List your records and drafts", | ||||||
| Long: `List the authenticated user's records and drafts. | ||||||
|
|
||||||
| Examples: | ||||||
| zenodo records list | ||||||
| zenodo records list --status draft | ||||||
| zenodo records list --community my-org --all`, | ||||||
| RunE: func(cmd *cobra.Command, args []string) error { | ||||||
| client := api.NewClient(appCtx.BaseURL, appCtx.Token) | ||||||
| status, _ := cmd.Flags().GetString("status") | ||||||
| community, _ := cmd.Flags().GetString("community") | ||||||
| all, _ := cmd.Flags().GetBool("all") | ||||||
|
|
||||||
| params := api.RecordListParams{ | ||||||
| Status: status, | ||||||
| Community: community, | ||||||
| } | ||||||
|
|
||||||
| if all { | ||||||
| records, total, err := api.PaginateAll(func(page int) (*model.RecordSearchResult, error) { | ||||||
| params.Page = page | ||||||
| return client.ListUserRecords(params) | ||||||
| }) | ||||||
| if err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "Warning: %v\n", err) | ||||||
| } | ||||||
| fmt.Fprintf(os.Stderr, "Total: %d records\n", total) | ||||||
| return output.Format(os.Stdout, records, appCtx.Output, appCtx.Fields) | ||||||
| } | ||||||
|
|
||||||
| result, err := client.ListUserRecords(params) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
| fmt.Fprintf(os.Stderr, "Showing %d of %d records\n", len(result.Hits.Hits), result.Hits.Total) | ||||||
| return output.Format(os.Stdout, result.Hits.Hits, appCtx.Output, appCtx.Fields) | ||||||
| }, | ||||||
| } | ||||||
|
|
||||||
| var recordsSearchCmd = &cobra.Command{ | ||||||
| Use: "search <query>", | ||||||
| Short: "Search published records", | ||||||
| Long: `Search all published records using Elasticsearch query syntax. | ||||||
|
|
||||||
| Examples: | ||||||
| zenodo records search "climate change" | ||||||
| zenodo records search --community my-org "dataset" | ||||||
| zenodo records search "publication_date:[2024-01-01 TO 2024-12-31]" --all`, | ||||||
| Args: cobra.ExactArgs(1), | ||||||
| RunE: func(cmd *cobra.Command, args []string) error { | ||||||
| client := api.NewClient(appCtx.BaseURL, appCtx.Token) | ||||||
| query := args[0] | ||||||
| community, _ := cmd.Flags().GetString("community") | ||||||
| all, _ := cmd.Flags().GetBool("all") | ||||||
|
|
||||||
| params := api.RecordListParams{ | ||||||
| Community: community, | ||||||
| } | ||||||
|
|
||||||
| if all { | ||||||
| records, total, err := api.PaginateAll(func(page int) (*model.RecordSearchResult, error) { | ||||||
| params.Page = page | ||||||
| return client.SearchRecords(query, params) | ||||||
| }) | ||||||
| if err != nil { | ||||||
| fmt.Fprintf(os.Stderr, "Warning: %v\n", err) | ||||||
| } | ||||||
| fmt.Fprintf(os.Stderr, "Total: %d records\n", total) | ||||||
| return output.Format(os.Stdout, records, appCtx.Output, appCtx.Fields) | ||||||
| } | ||||||
|
|
||||||
| result, err := client.SearchRecords(query, params) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
| fmt.Fprintf(os.Stderr, "Showing %d of %d records\n", len(result.Hits.Hits), result.Hits.Total) | ||||||
| return output.Format(os.Stdout, result.Hits.Hits, appCtx.Output, appCtx.Fields) | ||||||
| }, | ||||||
| } | ||||||
|
|
||||||
| var recordsGetCmd = &cobra.Command{ | ||||||
| Use: "get <id>", | ||||||
| Short: "Get a record by ID", | ||||||
| Long: `Retrieve full details of a published record. | ||||||
|
|
||||||
| Examples: | ||||||
| zenodo records get 12345 | ||||||
| zenodo records get 12345 --output json | ||||||
| zenodo records get 12345 --format bibtex`, | ||||||
| Args: cobra.ExactArgs(1), | ||||||
| RunE: func(cmd *cobra.Command, args []string) error { | ||||||
| id, err := strconv.Atoi(args[0]) | ||||||
| if err != nil { | ||||||
| return fmt.Errorf("invalid record ID: %s", args[0]) | ||||||
| } | ||||||
|
|
||||||
| client := api.NewClient(appCtx.BaseURL, appCtx.Token) | ||||||
| format, _ := cmd.Flags().GetString("format") | ||||||
|
|
||||||
| // Handle non-JSON formats via Accept header. | ||||||
| switch format { | ||||||
| case "bibtex": | ||||||
| data, err := client.GetRaw(fmt.Sprintf("/records/%d", id), "application/x-bibtex") | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
| fmt.Println(string(data)) | ||||||
| return nil | ||||||
| case "datacite": | ||||||
| data, err := client.GetRaw(fmt.Sprintf("/records/%d", id), "application/vnd.datacite.datacite+xml") | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
| fmt.Println(string(data)) | ||||||
| return nil | ||||||
| } | ||||||
|
Comment on lines
+118
to
+136
|
||||||
|
|
||||||
| record, err := client.GetRecord(id) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
| return output.Format(os.Stdout, record, appCtx.Output, appCtx.Fields) | ||||||
| }, | ||||||
| } | ||||||
|
|
||||||
| var recordsVersionsCmd = &cobra.Command{ | ||||||
| Use: "versions <id>", | ||||||
| Short: "List all versions of a record", | ||||||
| Long: `List all versions of a record by its ID. | ||||||
|
|
||||||
| Examples: | ||||||
| zenodo records versions 12345`, | ||||||
| Args: cobra.ExactArgs(1), | ||||||
| RunE: func(cmd *cobra.Command, args []string) error { | ||||||
| id, err := strconv.Atoi(args[0]) | ||||||
| if err != nil { | ||||||
| return fmt.Errorf("invalid record ID: %s", args[0]) | ||||||
| } | ||||||
|
|
||||||
| client := api.NewClient(appCtx.BaseURL, appCtx.Token) | ||||||
| result, err := client.ListVersions(id) | ||||||
| if err != nil { | ||||||
| return err | ||||||
| } | ||||||
| return output.Format(os.Stdout, result.Hits.Hits, appCtx.Output, appCtx.Fields) | ||||||
| }, | ||||||
| } | ||||||
|
|
||||||
| func init() { | ||||||
| // records list flags | ||||||
| recordsListCmd.Flags().String("status", "", "Filter by status: draft, published") | ||||||
| recordsListCmd.Flags().String("community", "", "Filter by community ID") | ||||||
| recordsListCmd.Flags().Bool("all", false, "Fetch all pages (up to 10k results)") | ||||||
|
|
||||||
| // records search flags | ||||||
| recordsSearchCmd.Flags().String("community", "", "Filter by community ID") | ||||||
| recordsSearchCmd.Flags().Bool("all", false, "Fetch all pages (up to 10k results)") | ||||||
|
|
||||||
| // records get flags | ||||||
| recordsGetCmd.Flags().String("format", "", "Response format: json, bibtex, datacite (default: uses --output)") | ||||||
|
||||||
| recordsGetCmd.Flags().String("format", "", "Response format: json, bibtex, datacite (default: uses --output)") | |
| recordsGetCmd.Flags().String("format", "", "Response format: table, json, bibtex, datacite. When empty, uses --output; bibtex/datacite are requested via Accept headers.") |
Copilot
AI
Feb 17, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new records CLI commands lack test coverage. While the underlying API layer (internal/api/records.go) and output formatters (internal/output/) have comprehensive tests, the CLI commands themselves are untested. Consider adding tests that verify:
- Flag parsing and validation (e.g., status, community, all, format)
- Error handling when API calls fail
- Correct delegation to API client methods
- Output formatting via the output package
- stderr vs stdout output separation
This would align with the testing patterns established in other packages like internal/api, internal/config, and internal/output which all have comprehensive test files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The format flag handling has an inconsistency. When format is "json", it falls through to the default case and uses the output formatter with appCtx.Output, instead of using the Accept header approach like bibtex and datacite. This means --format json and --output json would behave differently under the hood. Consider adding a case for "json" that uses GetRaw with "application/json" Accept header for consistency, or explicitly document that --format json is equivalent to --output json.