User collection API additions (#42)

User collection endpoint
This commit is contained in:
Richard Roché 2021-02-17 18:26:29 +02:00 committed by GitHub
parent 858d552240
commit d77d28b101
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 370 additions and 13 deletions

View File

@ -6,7 +6,7 @@ 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). 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 * Database
* [Releases](#releases) * [Releases](#releases)
* Release Rating * Release Rating
@ -17,6 +17,11 @@ The lib is under MIT but be sure you are familiar with [Discogs API Terms of Use
* Label * Label
* All Label Releases * All Label Releases
* [Search](#search) * [Search](#search)
* [User Collection](#user-collection)
* Collection Folders
* Folder
* Collection Items by Folder
* Collection Items by Release
Install 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" 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 ```go
client, err := discogs.New(&discogs.Options{ client, err := discogs.New(&discogs.Options{
@ -88,3 +93,28 @@ Example
fmt.Println(r.Title) 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)
```

View File

@ -26,11 +26,13 @@ type Options struct {
// Discogs is an interface for making Discogs API requests. // Discogs is an interface for making Discogs API requests.
type Discogs interface { type Discogs interface {
CollectionService
DatabaseService DatabaseService
SearchService SearchService
} }
type discogs struct { type discogs struct {
CollectionService
DatabaseService DatabaseService
SearchService SearchService
} }
@ -62,6 +64,7 @@ func New(o *Options) (Discogs, error) {
} }
return discogs{ return discogs{
newCollectionService(o.URL + "/users"),
newDatabaseService(o.URL, cur), newDatabaseService(o.URL, cur),
newSearchService(o.URL + "/database/search"), newSearchService(o.URL + "/database/search"),
}, nil }, nil

View File

@ -6,6 +6,7 @@ import (
const ( const (
testUserAgent = "UnitTestClient/0.0.2" testUserAgent = "UnitTestClient/0.0.2"
testUsername = "test_user"
testToken = "" testToken = ""
) )

View File

@ -19,4 +19,7 @@ var (
ErrUnauthorized = &Error{"authentication required"} ErrUnauthorized = &Error{"authentication required"}
ErrCurrencyNotSupported = &Error{"currency does not supported"} ErrCurrencyNotSupported = &Error{"currency does not supported"}
ErrUserAgentInvalid = &Error{"invalid user-agent"} ErrUserAgentInvalid = &Error{"invalid user-agent"}
ErrInvalidReleaseID = &Error{"invalid release id"}
ErrInvalidSortKey = &Error{"invalid sort key"}
ErrInvalidUsername = &Error{"invalid username"}
) )

View File

@ -78,6 +78,7 @@ type Format struct {
Descriptions []string `json:"descriptions"` Descriptions []string `json:"descriptions"`
Name string `json:"name"` Name string `json:"name"`
Qty string `json:"qty"` Qty string `json:"qty"`
Text string `json:"text,omitempty"`
} }
// Company ... // Company ...
@ -130,8 +131,8 @@ type Page struct {
// URLsList ... // URLsList ...
type URLsList struct { type URLsList struct {
Last string `json:"last"` Last string `json:"last,omitempty"`
Next string `json:"next"` Next string `json:"next,omitempty"`
} }
// Version ... // Version ...
@ -188,7 +189,9 @@ type ReleaseSource struct {
// Pagination ... // Pagination ...
type Pagination struct { 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 SortOrder string // asc, desc
Page int Page int
PerPage int PerPage int

File diff suppressed because one or more lines are too long

135
user_collection.go Normal file
View File

@ -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 users 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 users collection.
// If folderID is not 0, authentication with token is required.
CollectionItemsByFolder(username string, folderID int, pagination *Pagination) (*CollectionItems, error)
// Retrieve the users 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 users 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 users 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
}

174
user_collection_test.go Normal file
View File

@ -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)
}
})
}
}