From b396bf36edeaf09cc9aa1bbd8c69d6b0d8d75169 Mon Sep 17 00:00:00 2001 From: Martin Fenner Date: Sat, 22 Feb 2025 11:06:19 +0100 Subject: [PATCH] add inveniordm delete draft record cli command --- cmd/delete.go | 74 ++++++++++++++++++++++++++++++++++++++++++++ cmd/root.go | 2 +- inveniordm/reader.go | 2 ++ inveniordm/writer.go | 22 +++++++++++++ utils/utils.go | 20 +++++++++++- utils/utils_test.go | 8 +++++ 6 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 cmd/delete.go diff --git a/cmd/delete.go b/cmd/delete.go new file mode 100644 index 0000000..0aebc6b --- /dev/null +++ b/cmd/delete.go @@ -0,0 +1,74 @@ +/* +Copyright © 2025 Front Matter +*/ +package cmd + +import ( + "bytes" + "encoding/json" + "fmt" + "time" + + "github.com/front-matter/commonmeta/commonmeta" + "github.com/front-matter/commonmeta/inveniordm" + "github.com/front-matter/commonmeta/utils" + "github.com/spf13/cobra" + "golang.org/x/time/rate" +) + +// deleteCmd represents the decode command +var deleteCmd = &cobra.Command{ + Use: "delete", + Short: "Deletes a InvenioRDM record.", + Long: `Deletes a InvenioRDM record by id. + + Example usage: + + commonmeta delete fh8y2-aef76`, + Run: func(cmd *cobra.Command, args []string) { + host, _ := cmd.Flags().GetString("host") + token, _ := cmd.Flags().GetString("token") + + var record commonmeta.APIResponse + + if len(args) == 0 { + fmt.Println("Please provide an input") + return + } + input := args[0] + rid, ok := utils.ValidateRID(input) + if !ok { + fmt.Println("Please provide a valid input") + return + } + if host == "" || token == "" { + fmt.Println("Please provide an inveniordm host and token") + return + } + rl := rate.NewLimiter(rate.Every(60*time.Second), 900) // 900 request every 60 seconds + client := inveniordm.NewClient(rl, host) + record.ID = rid + record, err := inveniordm.DeleteDraftRecord(record, client, token) + if err != nil { + cmd.PrintErr(err) + } + + var output []byte + var out bytes.Buffer + output, err = json.Marshal(record) + if err != nil { + cmd.PrintErr(err) + } + + json.Indent(&out, output, "", " ") + cmd.Println(out.String()) + + if err != nil { + cmd.PrintErr(err) + } + }, +} + +func init() { + rootCmd.AddCommand(deleteCmd) +} diff --git a/cmd/root.go b/cmd/root.go index 2355afa..dc29479 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,7 +13,7 @@ import ( // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "commonmeta", - Version: "v0.6.64", + Version: "v0.6.65", Short: "Convert scholarly metadata from one format to another", Long: `Convert scholarly metadata between formats. Currently supported input formats are Crossref and DataCite DOIs, currently diff --git a/inveniordm/reader.go b/inveniordm/reader.go index 25cd5e3..2626c42 100644 --- a/inveniordm/reader.go +++ b/inveniordm/reader.go @@ -562,6 +562,8 @@ var CommunityTranslations = map[string]string{ "papers": "researchblogging", "urheberrecht": "copyright", "veranstaltungshinweise": "events", + "asapbio": "preprints", + "biorxiv": "preprints", } // Fetch fetches InvenioRDM metadata and returns Commonmeta metadata. diff --git a/inveniordm/writer.go b/inveniordm/writer.go index bcacbbc..928158c 100644 --- a/inveniordm/writer.go +++ b/inveniordm/writer.go @@ -711,6 +711,28 @@ func PublishDraftRecord(record commonmeta.APIResponse, client *InvenioRDMClient, return record, nil } +// DeleteDraftRecord publishes a draft record in InvenioRDM. +func DeleteDraftRecord(record commonmeta.APIResponse, client *InvenioRDMClient, apiKey string) (commonmeta.APIResponse, error) { + requestURL := fmt.Sprintf("https://%s/api/records/%s/draft", client.Host, record.ID) + req, _ := http.NewRequest(http.MethodDelete, requestURL, nil) + req.Header = http.Header{ + "Content-Type": {"application/json"}, + "Authorization": {"Bearer " + apiKey}, + } + resp, err := client.Do(req) + if err != nil { + return record, err + } + defer resp.Body.Close() + body, _ := io.ReadAll(resp.Body) + if resp.StatusCode != 204 { + fmt.Println(string(body), record) + return record, err + } + record.Status = "deleted" + return record, nil +} + // CreateCommunity creates a community in InvenioRDM. func CreateCommunity(community string, client *InvenioRDMClient, apiKey string) (string, error) { type Response struct { diff --git a/utils/utils.go b/utils/utils.go index 786b86a..1d152c9 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -502,7 +502,7 @@ func GetROR(ror string) (ROR, error) { } // ValidateID validates an identifier and returns the type -// Can be DOI, UUID, ISSN, ORCID, ROR, URL +// Can be DOI, UUID, ISSN, ORCID, ROR, URL, RID func ValidateID(id string) (string, string) { doi, ok := doiutils.ValidateDOI(id) if ok { @@ -516,6 +516,10 @@ func ValidateID(id string) (string, string) { if ok { return uuid, "UUID" } + rid, ok := ValidateRID(id) + if ok { + return rid, "RID" + } orcid, ok := ValidateORCID(id) if ok { return orcid, "ORCID" @@ -568,6 +572,16 @@ func ValidateUUID(uuid string) (string, bool) { return r.FindString(uuid), true } +// ValidateRID validates a RID +// RID is the unique identifier used by the InvenioRDM platform +func ValidateRID(rid string) (string, bool) { + r := regexp.MustCompile("^[0-9a-z]{5}-[0-9a-z]{3}[0-9]{2}$") + if !r.MatchString(rid) { + return "", false + } + return r.FindString(rid), true +} + func CamelCaseToWords(str string) string { var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)") var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])") @@ -642,6 +656,10 @@ func DecodeID(id string) (int64, error) { // ROR ID is a 9-character string that starts with 0 // and is a base32-encoded number with a mod 97-1 number, err = crockford.Decode(identifier, true) + } else if identifierType == "RID" { + // RID is a 10-character string with a hyphen after five digits. + // It is a base32-encoded numbers with checksum. + number, err = crockford.Decode(identifier, true) } else if identifierType == "ORCID" { str := identifier identifier = strings.ReplaceAll(identifier, "-", "") diff --git a/utils/utils_test.go b/utils/utils_test.go index 4394ac8..39a91bb 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -324,6 +324,13 @@ func ExampleValidateUUID() { // 2491b2d5-7daf-486b-b78b-e5aab48064c1 } +func ExampleValidateRID() { + s, _ := utils.ValidateRID("nryd8-14284") + fmt.Println(s) + // Output: + // nryd8-14284 +} + func TestSanitize(t *testing.T) { t.Parallel() type testCase struct { @@ -382,6 +389,7 @@ func TestValidateID(t *testing.T) { {input: "https://doi.org/10.7554/eLife.01567", want: "DOI"}, {input: "10.1101/097196", want: "DOI"}, {input: "2491b2d5-7daf-486b-b78b-e5aab48064c1", want: "UUID"}, + {input: "nryd8-14284", want: "RID"}, {input: "https://ror.org/0342dzm54", want: "ROR"}, {input: "https://orcid.org/0000-0002-1825-0097", want: "ORCID"}, {input: "https://datadryad.org/stash/dataset/doi:10.5061/dryad.8515", want: "URL"},