diff --git a/board.go b/board.go index f8a2687..55ce3ac 100644 --- a/board.go +++ b/board.go @@ -18,6 +18,7 @@ package trello import ( "encoding/json" + "fmt" "net/url" "strconv" ) @@ -31,12 +32,15 @@ type Board struct { DescData struct { Emoji struct{} `json:"emoji"` } `json:"descData"` - Closed bool `json:"closed"` - IDOrganization string `json:"idOrganization"` - Pinned bool `json:"pinned"` - URL string `json:"url"` - ShortURL string `json:"shortUrl"` - Prefs struct { + Closed bool `json:"closed"` + IDMemberCreator string `json:"idMemberCreator"` + IDOrganization string `json:"idOrganization"` + Members []*Member `json:"members"` + Memberships []*Membership `json:"memberships"` + Pinned bool `json:"pinned"` + URL string `json:"url"` + ShortURL string `json:"shortUrl"` + Prefs struct { PermissionLevel string `json:"permissionLevel"` Voting string `json:"voting"` Comments string `json:"comments"` @@ -76,9 +80,9 @@ type BoardBackground struct { // Board - Get board by boardID // - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-id-get func (c *Client) Board(boardID string) (board *Board, err error) { - board = &Board{} body, err := c.Get("/boards/" + boardID) if err == nil { + board = &Board{} err = parseBoard(body, board, c) } return @@ -87,12 +91,12 @@ func (c *Client) Board(boardID string) (board *Board, err error) { // CreateBoard - Create Board // - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-post func (c *Client) CreateBoard(name string) (board *Board, err error) { - board = &Board{} payload := url.Values{} payload.Set("name", name) body, err := c.Post("/boards", payload) if err == nil { + board = &Board{} err = parseBoard(body, board, c) } return @@ -101,7 +105,6 @@ func (c *Client) CreateBoard(name string) (board *Board, err error) { // Duplicate - Duplicate (Copy) Board // - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-post func (b *Board) Duplicate(name string, keepCards bool) (board *Board, err error) { - board = &Board{} keepFromSource := "none" if keepCards { keepFromSource = "cards" @@ -113,6 +116,7 @@ func (b *Board) Duplicate(name string, keepCards bool) (board *Board, err error) body, err := b.client.Post("/boards", payload) if err == nil { + board = &Board{} err = parseBoard(body, board, b.client) } return @@ -160,16 +164,104 @@ func (b *Board) Lists() (lists []List, err error) { return } -// Members - Get the members of a board +// GetMembers - Get the members of a board // - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-id-members-get -func (b *Board) Members() (members []Member, err error) { - body, err := b.client.Get("/boards/" + b.ID + "/members") +func (b *Board) GetMembers() (members []*Member, err error) { + if len(b.Members) == 0 { + body, err := b.client.Get("/boards/" + b.ID + "/members") + if err == nil { + members, err = parseListMembers(body, b.client) + + } + } else { + members = b.Members + } + return +} + +// AddMember - Add a Member to a board +// memberType can be admin, normal or observer (if left blank will default to normal) +// - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-id-members-idmember-put +func (b *Board) AddMember(member *Member, memberType string) (err error) { + if memberType == "" { + memberType = "normal" // default to "normal" + } + payload := url.Values{} + payload.Set("type", memberType) + body, err := b.client.Put("/boards/"+b.ID+"/members/"+member.ID, payload) + if err == nil { + err = parseBoard(body, b, b.client) + } + return +} + +// RemoveMember - Remove a Member from a board +// - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-id-members-idmember-put +func (b *Board) RemoveMember(member *Member) (err error) { + body, err := b.client.Delete("/boards/" + b.ID + "/members/" + member.ID) + if err == nil { + err = parseBoard(body, b, b.client) + } + return +} + +// GetMembership - Get a Membership of a board by ID +// Get information about the memberships users have to the board. +// filter: Valid Values: admins, all, none, normal (default: all) +// - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-id-memberships-get +func (b *Board) GetMembership(id string) (membership *Membership, err error) { + body, err := b.client.Get("/boards/" + b.ID + "/memberships/" + id) if err == nil { - members, err = parseListMembers(body, b.client) + membership = &Membership{} + err = parseMembership(body, membership, b) + } + return +} + +// GetMemberships - Get Memberships of a board +// Get information about the memberships users have to the board. +// filter: Valid Values: admins, all, none, normal (default: all) +// - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-id-memberships-get +func (b *Board) GetMemberships() (memberships []*Membership, err error) { + if len(b.Memberships) == 0 { + body, err := b.client.Get("/boards/" + b.ID + "/memberships") + if err == nil { + memberships, err = parseListMemberships(body, b) + if err == nil { + b.Memberships = memberships + } + } + } else { + memberships = b.Memberships } return } +// GetMembershipForMember - Return a Membership that matches a specific Member +func (b *Board) GetMembershipForMember(member *Member) (membership *Membership, err error) { + memberships, err := b.GetMemberships() + if err == nil { + for _, ms := range memberships { + if ms.IDMember == member.ID { + membership = ms + } + } + if membership == nil { + err = fmt.Errorf("ERROR: No membership found for member: " + member.ID) + } + } + return +} + +// IsAdmin - Check to see if a member is an admin +func (b *Board) IsAdmin(member *Member) (isAdmin bool) { + ms, err := b.GetMembershipForMember(member) + if err == nil { + return ms.MemberType == "admin" + } + return false +} + // Cards - Get cards on a board // - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-id-cards-get func (b *Board) Cards() (cards []Card, err error) { @@ -273,6 +365,13 @@ func parseBoard(body []byte, board *Board, client *Client) (err error) { err = json.Unmarshal(body, &board) if err == nil { board.client = client + for i := range board.Members { + board.Members[i].client = client + } + for i := range board.Memberships { + board.Memberships[i].client = client + board.Memberships[i].Board = board + } } return } @@ -281,7 +380,15 @@ func parseListBoards(body []byte, client *Client) (boards []Board, err error) { err = json.Unmarshal(body, &boards) if err == nil { for i := range boards { - boards[i].client = client + board := boards[i] + board.client = client + // List of boards will not have "Members" + // for i := range board.Members { + // board.Members[i].client = client + // } + for i := range board.Memberships { + board.Memberships[i].client = client + } } } return diff --git a/board_test.go b/board_test.go index fa5bb47..2bdb582 100644 --- a/board_test.go +++ b/board_test.go @@ -31,26 +31,25 @@ func TestBoard(t *testing.T) { g.Describe("Board tests", func() { var board *Board + var member *Member var label *Label var testBoardName string g.Before(func() { testBoardName = fmt.Sprintf("GoTestTrello-Board-%v", time.Now().Unix()) - - }) - - g.It("should create a board", func() { board, err = client.CreateBoard(testBoardName) Expect(err).To(BeNil()) Expect(board).NotTo(BeNil()) - Expect(board.Name).To(Equal(testBoardName)) + member, err = client.Member("trello") + Expect(err).To(BeNil()) + Expect(member).NotTo(BeNil()) }) g.It("should get a board by ID", func() { - board, err = client.Board(board.ID) + b, err := client.Board(board.ID) Expect(err).To(BeNil()) - Expect(board).NotTo(BeNil()) - Expect(board.Name).To(Equal(testBoardName)) + Expect(b).NotTo(BeNil()) + Expect(b.Name).To(Equal(testBoardName)) }) g.It("should change the board background to red", func() { @@ -65,6 +64,59 @@ func TestBoard(t *testing.T) { Expect(board.Desc).To(Equal("something")) }) + g.It("should get the members of a board", func() { + members, err := board.GetMembers() + Expect(err).To(BeNil()) + Expect(members).NotTo(BeNil()) + }) + + // This needs to come before "AddMember" for code coverage reasons + g.It("should get memberships on a board", func() { + ms, err := board.GetMemberships() + Expect(err).To(BeNil()) + Expect(len(ms)).To(BeNumerically(">", 0)) + }) + + g.It("should add a member (trello) to a board", func() { + err := board.AddMember(member, "") + Expect(err).To(BeNil()) + // TODO: Check to be sure trello *is* a member ... for now this should work + }) + + g.It("should check to see if member is an admin", func() { + isAdmin := board.IsAdmin(member) + Expect(isAdmin).To(BeFalse()) + }) + + g.It("should check to see if an invalid member is an admin", func() { + invalidMember, err := client.Member("test") + Expect(err).To(BeNil()) + Expect(invalidMember).NotTo(BeNil()) + isAdmin := board.IsAdmin(invalidMember) + Expect(isAdmin).To(BeFalse()) + }) + + g.It("should check to see if itself (me) is an admin", func() { + me, err := client.Member("me") + Expect(err).To(BeNil()) + Expect(me).NotTo(BeNil()) + isAdmin := board.IsAdmin(me) + Expect(isAdmin).To(BeTrue()) + }) + + g.It("should remove a member from a board", func() { + g.Timeout(10 * time.Second) // The delete seems to take longer than 5 seconds + err := board.RemoveMember(member) + Expect(err).To(BeNil()) + // TODO: Check to be sure trello is *not* a member ... for now this should work + }) + + g.It("should get the members of a board (after adding/removing)", func() { + members, err := board.GetMembers() + Expect(err).To(BeNil()) + Expect(members).NotTo(BeNil()) + }) + g.It("should get the lists in a board", func() { lists, err := board.Lists() Expect(err).To(BeNil()) @@ -74,11 +126,6 @@ func TestBoard(t *testing.T) { Expect(lists[2].Name).To(Equal("Done")) }) - g.It("should get the members of a board", func() { - _, err := board.Members() - Expect(err).To(BeNil()) - }) - g.It("should get all the actions in a board", func() { _, err := board.Actions() Expect(err).To(BeNil()) @@ -129,6 +176,7 @@ func TestBoard(t *testing.T) { g.It("should duplicate (copy) the board", func() { new, err := board.Duplicate("DUP-"+testBoardName, true) Expect(err).To(BeNil()) + Expect(new).NotTo(BeNil()) Expect(new.ID).NotTo(Equal(board.ID)) // and cleanup err = new.Delete() diff --git a/card.go b/card.go index 8447502..1843907 100644 --- a/card.go +++ b/card.go @@ -90,7 +90,7 @@ func (c *Card) Checklists() (checklists []Checklist, err error) { // Members - Get the Members of a card // - https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-members-get -func (c *Card) Members() (members []Member, err error) { +func (c *Card) Members() (members []*Member, err error) { body, err := c.client.Get("/cards/" + c.ID + "/members") if err == nil { members, err = parseListMembers(body, c.client) @@ -103,7 +103,7 @@ func (c *Card) Members() (members []Member, err error) { // The AddMember function requires a member (pointer) to add // It returns the resulting member-list // https://developers.trello.com/v1.0/reference#cardsididmembers -func (c *Card) AddMember(member *Member) (members []Member, err error) { +func (c *Card) AddMember(member *Member) (members []*Member, err error) { payload := url.Values{} payload.Set("value", member.ID) body, err := c.client.Post("/cards/"+c.ID+"/idMembers", payload) @@ -117,7 +117,7 @@ func (c *Card) AddMember(member *Member) (members []Member, err error) { // - https://developer.atlassian.com/cloud/trello/rest/api-group-cards/#api-cards-id-idmembers-idmember-delete // The RemoveMember function requires a member (pointer) to delete // It returns the resulting member-list -func (c *Card) RemoveMember(member *Member) (members []Member, err error) { +func (c *Card) RemoveMember(member *Member) (members []*Member, err error) { body, err := c.client.Delete("/cards/" + c.ID + "/idMembers/" + member.ID) if err == nil { members, err = parseListMembers(body, c.client) diff --git a/client_test.go b/client_test.go index 6a168ef..7426993 100644 --- a/client_test.go +++ b/client_test.go @@ -17,9 +17,11 @@ limitations under the License. package trello import ( + "fmt" "net/http" "os" "testing" + "time" goblin "github.com/franela/goblin" . "github.com/onsi/gomega" @@ -30,6 +32,12 @@ func TestClient(t *testing.T) { RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) g.Describe("client tests", func() { + var board *Board + var testBoardName string + + g.Before(func() { + testBoardName = fmt.Sprintf("GoTestTrello-Board-%v", time.Now().Unix()) + }) g.It("should create a default client", func() { _, err = NewClient() @@ -53,7 +61,18 @@ func TestClient(t *testing.T) { Expect(ver).To(Equal("1")) }) - // NOTE: Other methods will be tested as part of other tests + g.It("should create a board", func() { + board, err = client.CreateBoard(testBoardName) + Expect(err).To(BeNil()) + Expect(board).NotTo(BeNil()) + Expect(board.Name).To(Equal(testBoardName)) + }) + + g.After(func() { + Expect(board).NotTo(BeNil()) + err = board.Delete() + Expect(err).To(BeNil()) + }) }) } diff --git a/member.go b/member.go index bb07336..dd99eb1 100644 --- a/member.go +++ b/member.go @@ -117,7 +117,7 @@ func parseMember(body []byte, member *Member, client *Client) (err error) { return } -func parseListMembers(body []byte, client *Client) (members []Member, err error) { +func parseListMembers(body []byte, client *Client) (members []*Member, err error) { err = json.Unmarshal(body, &members) for i := range members { members[i].client = client diff --git a/membership.go b/membership.go new file mode 100644 index 0000000..f5e5585 --- /dev/null +++ b/membership.go @@ -0,0 +1,71 @@ +/* +Copyright 2014 go-trello authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package trello + +import ( + "encoding/json" + "net/url" +) + +// Membership tello membership struct +// - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-id-memberships-get +type Membership struct { + client *Client + Board *Board + ID string `json:"id" pattern:"^[0-9a-fA-F]{32}$"` // Pattern: ^[0-9a-fA-F]{32}$ + IDMember string `json:"idMember"` + MemberType string `json:"memberType"` + Unconfirmed bool `json:"unconfirmed"` + Deactivated bool `json:"deactivated"` +} + +// Update - Update Membership of Member on a Board +// memberType can be admin, normal or observer (if left blank will default to normal) +// - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-id-memberships-idmembership-put +func (m *Membership) Update(memberType, memberFields string) (err error) { + if memberType == "" { + memberType = "normal" + } + payload := url.Values{} + payload.Set("type", memberType) + if memberFields != "" { + payload.Set("member_fields", memberFields) + } + body, err := m.client.Put("/boards/"+m.Board.ID+"/memberships/"+m.ID, payload) + if err == nil { + err = parseMembership(body, m, m.Board) + } + return +} + +func parseMembership(body []byte, membership *Membership, board *Board) (err error) { + err = json.Unmarshal(body, &membership) + if err == nil { + membership.client = board.client + membership.Board = board + } + return +} + +func parseListMemberships(body []byte, board *Board) (memberships []*Membership, err error) { + err = json.Unmarshal(body, &memberships) + for i := range memberships { + memberships[i].client = board.client + memberships[i].Board = board + } + return +} diff --git a/membership_test.go b/membership_test.go new file mode 100644 index 0000000..8da0486 --- /dev/null +++ b/membership_test.go @@ -0,0 +1,83 @@ +/* +Copyright 2014 go-trello authors. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package trello + +import ( + "fmt" + "testing" + "time" + + goblin "github.com/franela/goblin" + . "github.com/onsi/gomega" +) + +func TestMembership(t *testing.T) { + g := goblin.Goblin(t) + RegisterFailHandler(func(m string, _ ...int) { g.Fail(m) }) + + g.Describe("Membership tests", func() { + var board *Board + var member *Member + var membership *Membership + var testBoardName string + + g.Before(func() { + testBoardName = fmt.Sprintf("GoTestTrello-Membership-%v", time.Now().Unix()) + board, err = client.CreateBoard(testBoardName) + Expect(err).To(BeNil()) + Expect(board).NotTo(BeNil()) + member, err = client.Member("trello") + Expect(err).To(BeNil()) + Expect(member).NotTo(BeNil()) + err = board.AddMember(member, "") + Expect(err).To(BeNil()) + membership, err = board.GetMembershipForMember(member) + Expect(err).To(BeNil()) + Expect(membership).NotTo(BeNil()) + }) + + // Putting a board test here due to prerequisites + g.It("should get a membership by id", func() { + ms, err := board.GetMembership(membership.ID) + Expect(err).To(BeNil()) + Expect(membership).NotTo(BeNil()) + Expect(ms.ID).To(Equal(membership.ID)) + }) + + g.It("should update fields membership for a member on a board", func() { + err = membership.Update("", "fullName, username, bio") + Expect(err).To(BeNil()) + // TODO: Check to be sure trello membership has changed? + // Expect(len(membershipResponse.Memberships)).To(BeNumerically(">", 1)) + }) + + g.It("should update type of membership for a member on a board", func() { + err = membership.Update("admin", "") + Expect(err).To(BeNil()) + // TODO: Check to be sure trello membership has changed? + // Expect(len(membershipResponse.Memberships)).To(BeNumerically(">", 1)) + }) + + // Keep this test LAST for obvious reasons + g.After(func() { + err = board.Delete() + Expect(err).To(BeNil()) + }) + + }) + +} diff --git a/organization.go b/organization.go index 0da755b..4d7a34b 100644 --- a/organization.go +++ b/organization.go @@ -51,7 +51,7 @@ func (c *Client) Organization(orgID string) (organization *Organization, err err // Members - Get the Members of an Organization // - https://developer.atlassian.com/cloud/trello/rest/api-group-organizations/#api-organizations-id-members-get -func (o *Organization) Members() (members []Member, err error) { +func (o *Organization) Members() (members []*Member, err error) { body, err := o.client.Get("/organization/" + o.ID + "/members") if err == nil { members, err = parseListMembers(body, o.client) @@ -72,13 +72,13 @@ func (o *Organization) Boards() (boards []Board, err error) { // AddBoard - Create a new Board in the organization // - https://developer.atlassian.com/cloud/trello/rest/api-group-boards/#api-boards-post func (o *Organization) AddBoard(name string) (board *Board, err error) { - board = &Board{} payload := url.Values{} payload.Set("name", name) payload.Set("idOrganization", o.ID) body, err := o.client.Post("/boards", payload) if err == nil { + board = &Board{} err = parseBoard(body, board, o.client) } return