From d77d28b101a53f7bf533d08653c585f183b8cc60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Richard=20Roch=C3=A9?= Date: Wed, 17 Feb 2021 18:26:29 +0200 Subject: [PATCH] User collection API additions (#42) User collection endpoint --- README.md | 50 +++++++++--- discogs.go | 3 + discogs_test.go | 1 + errors.go | 3 + models.go | 9 ++- testing_data.go | 8 ++ user_collection.go | 135 +++++++++++++++++++++++++++++++ user_collection_test.go | 174 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 370 insertions(+), 13 deletions(-) create mode 100644 user_collection.go create mode 100644 user_collection_test.go diff --git a/README.md b/README.md index 109e28c..2d344b6 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,22 @@ go-discogs is a Go client library for the [Discogs API](https://www.discogs.com/ The lib is under MIT but be sure you are familiar with [Discogs API Terms of Use](https://support.discogs.com/hc/en-us/articles/360009334593-API-Terms-of-Use). -### Feauteres +### Features * Database - * [Releases](#releases) - * Release Rating - * Master Releases - * Master Versions - * Artists - * Artist Releases - * Label - * All Label Releases + * [Releases](#releases) + * Release Rating + * Master Releases + * Master Versions + * Artists + * Artist Releases + * Label + * All Label Releases * [Search](#search) + * [User Collection](#user-collection) + * Collection Folders + * Folder + * Collection Items by Folder + * Collection Items by Release Install -------- @@ -30,7 +35,7 @@ First of all import library and init client variable. According to discogs api d import "github.com/irlndts/go-discogs" ``` -Some requests require authentification (as any user). According to [Discogs](https://www.discogs.com/developers/#page:authentication,header:authentication-discogs-auth-flow), to send requests with Discogs Auth, you have two options: sending your credentials in the query string with key and secret parameters or a [token parameter](https://www.discogs.com/settings/developers). +Some requests require authentication (as any user). According to [Discogs](https://www.discogs.com/developers/#page:authentication,header:authentication-discogs-auth-flow), to send requests with Discogs Auth, you have two options: sending your credentials in the query string with key and secret parameters or a [token parameter](https://www.discogs.com/settings/developers). ```go client, err := discogs.New(&discogs.Options{ @@ -88,3 +93,28 @@ Example fmt.Println(r.Title) } ``` + +#### User Collection + +Query a users [collection](https://www.discogs.com/developers#page:user-collection). + +##### Collection Folders +Example +```go + collection, err := client.CollectionFolders("my_user") +``` +##### Folder +Example +```go + folder, err := client.Folder("my_user", 0) +``` +##### Collection Items by Folder +Example +```go + items, err := client.CollectionItemsByFolder("my_user", 0, &Pagination{Sort: "artist", SortOrder: "desc", PerPage: 2}) +``` +##### Collection Items by Release +Example +```go + items, err := client.CollectionItemsByRelease("my_user", 12934893) +``` diff --git a/discogs.go b/discogs.go index dae8e16..c274f1d 100644 --- a/discogs.go +++ b/discogs.go @@ -26,11 +26,13 @@ type Options struct { // Discogs is an interface for making Discogs API requests. type Discogs interface { + CollectionService DatabaseService SearchService } type discogs struct { + CollectionService DatabaseService SearchService } @@ -62,6 +64,7 @@ func New(o *Options) (Discogs, error) { } return discogs{ + newCollectionService(o.URL + "/users"), newDatabaseService(o.URL, cur), newSearchService(o.URL + "/database/search"), }, nil diff --git a/discogs_test.go b/discogs_test.go index 4803588..7714a85 100644 --- a/discogs_test.go +++ b/discogs_test.go @@ -6,6 +6,7 @@ import ( const ( testUserAgent = "UnitTestClient/0.0.2" + testUsername = "test_user" testToken = "" ) diff --git a/errors.go b/errors.go index d4a415f..ed7d9a4 100644 --- a/errors.go +++ b/errors.go @@ -19,4 +19,7 @@ var ( ErrUnauthorized = &Error{"authentication required"} ErrCurrencyNotSupported = &Error{"currency does not supported"} ErrUserAgentInvalid = &Error{"invalid user-agent"} + ErrInvalidReleaseID = &Error{"invalid release id"} + ErrInvalidSortKey = &Error{"invalid sort key"} + ErrInvalidUsername = &Error{"invalid username"} ) diff --git a/models.go b/models.go index bfc336e..42b06d8 100644 --- a/models.go +++ b/models.go @@ -78,6 +78,7 @@ type Format struct { Descriptions []string `json:"descriptions"` Name string `json:"name"` Qty string `json:"qty"` + Text string `json:"text,omitempty"` } // Company ... @@ -130,8 +131,8 @@ type Page struct { // URLsList ... type URLsList struct { - Last string `json:"last"` - Next string `json:"next"` + Last string `json:"last,omitempty"` + Next string `json:"next,omitempty"` } // Version ... @@ -188,7 +189,9 @@ type ReleaseSource struct { // Pagination ... type Pagination struct { - Sort string // year, title, format + // TODO(irlndts): validate requested Sort + Sort string // year, title, format etc + // TODO(irlndts): validate requested SortOrder SortOrder string // asc, desc Page int PerPage int diff --git a/testing_data.go b/testing_data.go index 0e0ce97..cd8694a 100644 --- a/testing_data.go +++ b/testing_data.go @@ -7,3 +7,11 @@ const masterJson = `{"styles": ["Hardcore Hip-Hop", "Boom Bap"], "genres": ["Hip const releaseJson = `{"styles": ["Ska", "Reggae"], "videos": [{"duration": 301, "description": "ST.PETERSBURG SKA JAZZ REVIEW - WATER TAXI (BalconyTV)", "embed": true, "uri": "https://www.youtube.com/watch?v=i4_kwCTrTRs", "title": "ST.PETERSBURG SKA JAZZ REVIEW - WATER TAXI (BalconyTV)"}, {"duration": 292, "description": "St.Petersburg Ska-Jazz Review - Action Movie", "embed": true, "uri": "https://www.youtube.com/watch?v=IaQA8uiZUUc", "title": "St.Petersburg Ska-Jazz Review - Action Movie"}, {"duration": 320, "description": "St.Petersburg Ska-Jazz Review - Misterioso", "embed": true, "uri": "https://www.youtube.com/watch?v=2u5UtZNXugc", "title": "St.Petersburg Ska-Jazz Review - Misterioso"}, {"duration": 209, "description": "St.Petersburg Ska-Jazz Review - Perfidia", "embed": true, "uri": "https://www.youtube.com/watch?v=s3m6QY_JKnE", "title": "St.Petersburg Ska-Jazz Review - Perfidia"}, {"duration": 201, "description": "St.Petersburg Ska-Jazz Review - Volga River Boat Man", "embed": true, "uri": "https://www.youtube.com/watch?v=d-I-4O6JrMs", "title": "St.Petersburg Ska-Jazz Review - Volga River Boat Man"}], "series": [{"name": "Original Jazz Classics", "entity_type": "2", "catno": "", "resource_url": "https://api.discogs.com/labels/34231", "id": 34231, "entity_type_name": "Series"}], "labels": [{"name": "Magnetic Loft Records", "entity_type": "1", "catno": "MLR-007", "resource_url": "https://api.discogs.com/labels/890477", "id": 890477, "entity_type_name": "Label"}], "year": 2016, "community": {"status": "Accepted", "rating": {"count": 11, "average": 4.91}, "have": 73, "contributors": [{"username": "magnetic-loft-music", "resource_url": "https://api.discogs.com/users/magnetic-loft-music"}, {"username": "Shveiker", "resource_url": "https://api.discogs.com/users/Shveiker"}], "want": 18, "submitter": {"username": "magnetic-loft-music", "resource_url": "https://api.discogs.com/users/magnetic-loft-music"}, "data_quality": "Needs Vote"}, "artists": [{"join": "", "name": "St. Petersburg Ska-Jazz Review", "anv": "SPB Ska-Jazz Review", "tracks": "", "role": "", "resource_url": "https://api.discogs.com/artists/794217", "id": 794217}], "images": [{"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "primary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}], "format_quantity": 1, "id": 8138518, "artists_sort": "St. Petersburg Ska-Jazz Review", "genres": ["Jazz", "Reggae"], "thumb": "", "num_for_sale": 8, "title": "Elephant Riddim", "date_changed": "2018-01-30T13:32:46-08:00", "master_id": 960657, "lowest_price": 10.0, "status": "Accepted", "released_formatted": "18 Feb 2016", "estimated_weight": 230, "master_url": "https://api.discogs.com/masters/960657", "released": "2016-02-18", "date_added": "2016-02-19T01:49:21-08:00", "tracklist": [{"duration": "", "position": "A1", "type_": "track", "title": "Action Movie"}, {"duration": "", "position": "A2", "type_": "track", "title": "Elephant Riddim"}, {"duration": "", "position": "A3", "type_": "track", "title": "Ceora"}, {"duration": "", "position": "A4", "type_": "track", "title": "Doop"}, {"duration": "", "position": "A5", "type_": "track", "title": "52d Street Theme"}, {"duration": "", "position": "B1", "type_": "track", "title": "Fly Away"}, {"duration": "", "position": "B2", "type_": "track", "title": "Water Taxi"}, {"duration": "", "position": "B3", "type_": "track", "title": "Misterioso"}, {"duration": "", "position": "B4", "type_": "track", "title": "Keep On Going"}, {"duration": "", "position": "B5", "type_": "track", "title": "Filho Maravilha / Taj Mahal"}], "extraartists": [{"join": "", "name": "Michael Gavrichkov", "anv": "", "tracks": "", "role": "Artwork By", "resource_url": "https://api.discogs.com/artists/4540627", "id": 4540627}, {"join": "", "name": "Stu Allotropia", "anv": "", "tracks": "", "role": "Design", "resource_url": "https://api.discogs.com/artists/4894261", "id": 4894261}], "country": "Russia", "identifiers": [{"type": "Matrix / Runout", "value": "134985E1/A", "description": "Side A - handwritten etched"}, {"type": "Matrix / Runout", "value": "134985E2/A"}], "companies": [{"name": "GZ Media", "entity_type": "17", "catno": "134985E", "resource_url": "https://api.discogs.com/labels/430654", "id": 430654, "entity_type_name": "Pressed By"}], "uri": "https://www.discogs.com/SPB-Ska-Jazz-Review-Elephant-Riddim/release/8138518", "formats": [{"descriptions": ["LP", "Album", "Stereo"], "name": "Vinyl", "qty": "1"}], "resource_url": "https://api.discogs.com/releases/8138518", "data_quality": "Needs Vote"}` const artistJson = `{"profile": "Marshall Bruce Mathers III (born October 17, 1972, St. Joseph, Missouri), known by his primary stage name Eminem, or by his alter ego Slim Shady, is an American rapper and record producer who grew up in Detroit, Michigan. He began his professional music career as a member of Soul Intent along with Proof in 1992. He also started his first record label with his group that same year called Mashin' Duck Records.", "realname": "Marshall Bruce Mathers III", "releases_url": "https://api.discogs.com/artists/38661/releases", "name": "Eminem", "uri": "https://www.discogs.com/artist/38661-Eminem", "urls": ["http://www.eminem.com", "http://www.instagram.com/eminem", "http://twitter.com/Eminem", "https://twitter.com/AskAboutREVIVAL", "http://www.facebook.com/eminem", "http://www.imdb.com/name/nm0004896", "http://www.myspace.com/eminem", "https://www.youtube.com/user/EminemMusic", "https://www.youtube.com/user/EminemVEVO", "https://www.filmo.gs/credit/16526-eminem", "https://www.bookogs.com/credit/229267-eminem", "http://eminem.tumblr.com", "http://en.wikipedia.org/wiki/Eminem", "http://equipboard.com/pros/eminem", "https://genius.com/eminem"], "images": [{"uri": "", "height": 607, "width": 600, "resource_url": "", "type": "primary", "uri150": ""}, {"uri": "", "height": 610, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 625, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 503, "width": 409, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 652, "width": 452, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 326, "width": 251, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 397, "width": 441, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 348, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 442, "width": 319, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 740, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 446, "width": 299, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 288, "width": 288, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 360, "width": 468, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 372, "width": 500, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 404, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 444, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 604, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 642, "width": 500, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 253, "width": 199, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 550, "width": 400, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 160, "width": 236, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 400, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 821, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 258, "width": 195, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 746, "width": 517, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 170, "width": 220, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 500, "width": 300, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 347, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 281, "width": 500, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 552, "width": 435, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 444, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 507, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 488, "width": 300, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 409, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 515, "width": 578, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 387, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 310, "width": 266, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 800, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 613, "width": 454, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 751, "width": 500, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 657, "width": 485, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 543, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 490, "width": 376, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 450, "width": 403, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 400, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 480, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 532, "width": 415, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 500, "width": 444, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 400, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 256, "width": 256, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 718, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 440, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 400, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 905, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 300, "width": 202, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 552, "width": 435, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 578, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}, {"uri": "", "height": 600, "width": 600, "resource_url": "", "type": "secondary", "uri150": ""}], "resource_url": "https://api.discogs.com/artists/38661", "aliases": [{"resource_url": "https://api.discogs.com/artists/108184", "id": 108184, "name": "Slim Shady"}, {"resource_url": "https://api.discogs.com/artists/644153", "id": 644153, "name": "Marshall Mathers"}, {"resource_url": "https://api.discogs.com/artists/787714", "id": 787714, "name": "Ken Kaniff"}], "id": 38661, "data_quality": "Needs Vote", "namevariations": ["E. Minem", "Em", "Emiem", "Emine", "EMINEM", "Eminem Show", "Eminen", "Enimen", "M & M", "M. Mathers", "M.N.M", "M&M", "MC Double M", "\u30a8\u30df\u30cd\u30e0"]}` + +const folderJson = `{"id": 0, "name": "All", "count": 95, "resource_url": "https://api.discogs.com/users/test_user/collection/folders/0"}` + +const collectionJson = `{"folders": [{"id": 0, "name": "All", "count": 95, "resource_url": "https://api.discogs.com/users/test_user/collection/folders/0"}]}` + +const collectionItemsByFolderJson = `{"pagination": {"page": 1, "pages": 48, "per_page": 2, "items": 95, "urls": {"last": "https://api.discogs.com/users/test_user/collection/folders/0/releases?sort=artist&sort_order=desc&per_page=2&page=48", "next": "https://api.discogs.com/users/test_user/collection/folders/0/releases?sort=artist&sort_order=desc&per_page=2&page=2"}}, "releases": [{"id": 12934893, "instance_id": 431009995, "date_added": "2020-01-19T14:19:11-08:00", "rating": 0, "basic_information": {"id": 12934893, "master_id": 0, "master_url": null, "resource_url": "https://api.discogs.com/releases/12934893", "thumb": "", "cover_image": "", "title": "Zonk", "year": 2018, "formats": [{"name": "Vinyl", "qty": "1", "text": "Purple", "descriptions": ["LP", "Album"]}], "labels": [{"name": "Permanent Record", "catno": "PR014", "entity_type": "1", "entity_type_name": "Label", "id": 833694, "resource_url": "https://api.discogs.com/labels/833694"}], "artists": [{"name": "Zoo Lake", "anv": "", "join": "", "role": "", "tracks": "", "id": 6868154, "resource_url": "https://api.discogs.com/artists/6868154"}], "genres": ["Rock"], "styles": ["Post-Punk", "Shoegaze", "Psychedelic Rock", "Noise", "Garage Rock", "Lo-Fi"]}}, {"id": 4825435, "instance_id": 146424864, "date_added": "2015-11-08T14:42:02-08:00", "rating": 0, "basic_information": {"id": 4825435, "master_id": 17200, "master_url": "https://api.discogs.com/masters/17200", "resource_url": "https://api.discogs.com/releases/4825435", "thumb": "", "cover_image": "", "title": "You And Me Both", "year": 1983, "formats": [{"name": "Vinyl", "qty": "1", "descriptions": ["LP", "Album"]}], "labels": [{"name": "Mute", "catno": "STUMM 12", "entity_type": "1", "entity_type_name": "Label", "id": 26391, "resource_url": "https://api.discogs.com/labels/26391"}, {"name": "CBS", "catno": "DNW 2885", "entity_type": "1", "entity_type_name": "Label", "id": 3072, "resource_url": "https://api.discogs.com/labels/3072"}], "artists": [{"name": "Yazoo", "anv": "", "join": "", "role": "", "tracks": "", "id": 2713, "resource_url": "https://api.discogs.com/artists/2713"}], "genres": ["Electronic"], "styles": ["Synth-pop"]}}]}` + +const collectionItemsByRelease = `{"pagination": {"page": 1, "pages": 1, "per_page": 50, "items": 1, "urls": {}}, "releases": [{"id": 12934893, "instance_id": 431009995, "date_added": "2020-01-19T14:19:11-08:00", "rating": 0, "basic_information": {"id": 12934893, "master_id": 0, "master_url": null, "resource_url": "https://api.discogs.com/releases/12934893", "thumb": "", "cover_image": "", "title": "Zonk", "year": 2018, "formats": [{"name": "Vinyl", "qty": "1", "text": "Purple", "descriptions": ["LP", "Album"]}], "labels": [{"name": "Permanent Record", "catno": "PR014", "entity_type": "1", "entity_type_name": "Label", "id": 833694, "resource_url": "https://api.discogs.com/labels/833694"}], "artists": [{"name": "Zoo Lake", "anv": "", "join": "", "role": "", "tracks": "", "id": 6868154, "resource_url": "https://api.discogs.com/artists/6868154"}], "genres": ["Rock"], "styles": ["Post-Punk", "Shoegaze", "Psychedelic Rock", "Noise", "Garage Rock", "Lo-Fi"]}}]}` diff --git a/user_collection.go b/user_collection.go new file mode 100644 index 0000000..169068a --- /dev/null +++ b/user_collection.go @@ -0,0 +1,135 @@ +package discogs + +import ( + "strconv" +) + +// CollectionService is an interface to work with collection. +type CollectionService interface { + // Retrieve a list of folders in a user’s collection. + // If folder_id is not 0, authentication as the collection owner is required. + CollectionFolders(username string) (*CollectionFolders, error) + // Retrieve a list of items in a folder in a user’s collection. + // If folderID is not 0, authentication with token is required. + CollectionItemsByFolder(username string, folderID int, pagination *Pagination) (*CollectionItems, error) + // Retrieve the user’s collection folders which contain a specified release. + // The releaseID must be non-zero. + CollectionItemsByRelease(username string, releaseID int) (*CollectionItems, error) + // Retrieve metadata about a folder in a user’s collection. + Folder(username string, folderID int) (*Folder, error) +} + +type collectionService struct { + url string +} + +func newCollectionService(url string) CollectionService { + return &collectionService{ + url: url, + } +} + +// Folder serves folder response from discogs. +type Folder struct { + ID int `json:"id"` + Count int `json:"count"` + Name string `json:"name"` + ResourceURL string `json:"resource_url"` +} + +func (s *collectionService) Folder(username string, folderID int) (*Folder, error) { + if username == "" { + return nil, ErrInvalidUsername + } + var folder *Folder + err := request(s.url+"/"+username+"/collection/folders/"+strconv.Itoa(folderID), nil, &folder) + return folder, err +} + +// CollectionFolders serves collection response from discogs. +type CollectionFolders struct { + Folders []Folder `json:"folders"` +} + +func (s *collectionService) CollectionFolders(username string) (*CollectionFolders, error) { + if username == "" { + return nil, ErrInvalidUsername + } + var collection *CollectionFolders + err := request(s.url+"/"+username+"/collection/folders", nil, &collection) + return collection, err +} + +// CollectionItemSource ... +type CollectionItemSource struct { + ID int `json:"id"` + BasicInformation BasicInformation `json:"basic_information"` + DateAdded string `json:"date_added"` + FolderID int `json:"folder_id,omitempty"` + InstanceID int `json:"instance_id"` + Notes string `json:"notes,omitempty"` + Rating int `json:"rating"` +} + +// BasicInformation ... +type BasicInformation struct { + ID int `json:"id"` + Artists []ArtistSource `json:"artists"` + CoverImage string `json:"cover_image"` + Formats []Format `json:"formats"` + Labels []LabelSource `json:"labels"` + Genres []string `json:"genres"` + MasterID int `json:"master_id"` + MasterURL *string `json:"master_url"` + ResourceURL string `json:"resource_url"` + Styles []string `json:"styles"` + Thumb string `json:"thumb"` + Title string `json:"title"` + Year int `json:"year"` +} + +// CollectionItems list of items in a user’s collection +type CollectionItems struct { + Pagination Page `json:"pagination"` + Items []CollectionItemSource `json:"releases"` +} + +// valid sort keys +// https://www.discogs.com/developers#page:user-collection,header:user-collection-collection-items-by-folder +var validItemsByFolderSort = map[string]struct{}{ + "": struct{}{}, + "label": struct{}{}, + "artist": struct{}{}, + "title": struct{}{}, + "catno": struct{}{}, + "format": struct{}{}, + "rating": struct{}{}, + "added": struct{}{}, + "year": struct{}{}, +} + +func (s *collectionService) CollectionItemsByFolder(username string, folderID int, pagination *Pagination) (*CollectionItems, error) { + if username == "" { + return nil, ErrInvalidUsername + } + if pagination != nil { + if _, ok := validItemsByFolderSort[pagination.Sort]; !ok { + return nil, ErrInvalidSortKey + } + } + var items *CollectionItems + err := request(s.url+"/"+username+"/collection/folders/"+strconv.Itoa(folderID)+"/releases", pagination.params(), &items) + return items, err +} + +func (s *collectionService) CollectionItemsByRelease(username string, releaseID int) (*CollectionItems, error) { + if username == "" { + return nil, ErrInvalidUsername + } + if releaseID == 0 { + return nil, ErrInvalidReleaseID + } + var items *CollectionItems + err := request(s.url+"/"+username+"/collection/releases/"+strconv.Itoa(releaseID), nil, &items) + return items, err +} diff --git a/user_collection_test.go b/user_collection_test.go new file mode 100644 index 0000000..94d3111 --- /dev/null +++ b/user_collection_test.go @@ -0,0 +1,174 @@ +package discogs + +import ( + "encoding/json" + "io" + "net/http" + "net/http/httptest" + "testing" +) + +func CollectionServer(w http.ResponseWriter, r *http.Request) { + if r.Method != "GET" { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + + switch r.URL.Path { + case "/users/" + testUsername + "/collection/folders": + w.WriteHeader(http.StatusOK) + if _, err := io.WriteString(w, collectionJson); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + case "/users/" + testUsername + "/collection/folders/0": + w.WriteHeader(http.StatusOK) + if _, err := io.WriteString(w, folderJson); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + case "/users/" + testUsername + "/collection/folders/0/releases": + w.WriteHeader(http.StatusOK) + if _, err := io.WriteString(w, collectionItemsByFolderJson); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + case "/users/" + testUsername + "/collection/releases/12934893": + w.WriteHeader(http.StatusOK) + if _, err := io.WriteString(w, collectionItemsByRelease); err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + + default: + w.WriteHeader(http.StatusMethodNotAllowed) + } +} + +func TestCollectionServiceFolder(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(CollectionServer)) + defer ts.Close() + + d := initDiscogsClient(t, &Options{URL: ts.URL}) + + folder, err := d.Folder(testUsername, 0) + if err != nil { + t.Fatalf("failed to get folder: %s", err) + } + + json, err := json.Marshal(folder) + if err != nil { + t.Fatalf("failed to marshal folder: %s", err) + } + + compareJson(t, string(json), folderJson) +} + +func TestCollectionServiceCollectionFolders(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(CollectionServer)) + defer ts.Close() + + d := initDiscogsClient(t, &Options{URL: ts.URL}) + + collection, err := d.CollectionFolders(testUsername) + if err != nil { + t.Fatalf("failed to get collection: %s", err) + } + + json, err := json.Marshal(collection) + if err != nil { + t.Fatalf("failed to marshal collection: %s", err) + } + + compareJson(t, string(json), collectionJson) +} + +func TestCollectionServiceCollectionItemsByFolder(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(CollectionServer)) + defer ts.Close() + + d := initDiscogsClient(t, &Options{URL: ts.URL}) + + items, err := d.CollectionItemsByFolder(testUsername, 0, &Pagination{Sort: "artist", SortOrder: "desc", PerPage: 2}) + + if err != nil { + t.Fatalf("failed to get collection items: %s", err) + } + + json, err := json.Marshal(items) + if err != nil { + t.Fatalf("failed to marshal collection items: %s", err) + } + + compareJson(t, string(json), collectionItemsByFolderJson) +} + +func TestCollectionServiceCollectionItemsByFolderError(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(CollectionServer)) + defer ts.Close() + + d := initDiscogsClient(t, &Options{URL: ts.URL}) + + _, err := d.CollectionItemsByFolder(testUsername, 0, &Pagination{Sort: "invalid"}) + if err != ErrInvalidSortKey { + t.Fatalf("err got=%s; want=%s", err, ErrInvalidSortKey) + } +} + +func TestCollectionServiceCollectionItemsByRelease(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(CollectionServer)) + defer ts.Close() + + d := initDiscogsClient(t, &Options{URL: ts.URL}) + + items, err := d.CollectionItemsByRelease(testUsername, 12934893) + + if err != nil { + t.Fatalf("failed to get collection items: %s", err) + } + + json, err := json.Marshal(items) + if err != nil { + t.Fatalf("failed to marshal collection items: %s", err) + } + + compareJson(t, string(json), collectionItemsByRelease) +} + +func TestCollectionServiceCollectionItemsByReleaseErrors(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(CollectionServer)) + defer ts.Close() + d := initDiscogsClient(t, &Options{URL: ts.URL}) + + type testCase struct { + username string + releaseID int + err error + } + + testCases := map[string]testCase{ + "invalid username": testCase{ + username: "", + releaseID: 1, + err: ErrInvalidUsername, + }, + "invalid release id": testCase{ + username: "test-username", + releaseID: 0, + err: ErrInvalidReleaseID, + }, + } + + for name, tc := range testCases { + tc := tc + t.Run(name, func(t *testing.T) { + _, err := d.CollectionItemsByRelease(tc.username, tc.releaseID) + if err != tc.err { + t.Fatalf("err got=%s; want=%s", err, tc.err) + } + }) + } +}