Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
Signed-off-by: Raphaël Pinson <[email protected]>
  • Loading branch information
raphink committed Sep 11, 2024
1 parent 471d6ae commit 676257b
Show file tree
Hide file tree
Showing 9 changed files with 751 additions and 0 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
name: Go

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: 1.23 # Specify a compatible Go version, like 1.20

- name: Install dependencies
run: go mod tidy

- name: Run tests
run: go test ./... -v

170 changes: 170 additions & 0 deletions credly/badge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
package credly

import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)

// issueBadgeResponse represents the response structure when a badge is issued.
// see https://www.credly.com/docs/issued_badges
type issueBadgeResponse struct {
Data BadgeInfo `json:"data"`
}

// getBadgesResponse represents the response structure when fetching multiple badges.
type getBadgesResponse struct {
Data []BadgeInfo `json:"data"`
}


// BadgeInfo represents the details of an issued badge.
type BadgeInfo struct {
Id string `json:"id"`
ImageUrl string `json:"image_url"`
Url string `json:"badge_url"`
IssuedAt time.Time `json:"issued_at"`
State string `json:"state"`

Image struct {
Url string `json:"url"`
} `json:"image"`

Template BadgeTemplate `json:"badge_template"`

User struct {
Id string `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
Url string `json:"url"`
} `json:"user"`
}


// IssueBadge issues a new badge to a user based on their email and personal details.
//
// templateId: The ID of the badge template to be issued.
// email: The recipient's email address.
// firstName: The recipient's first name.
// lastName: The recipient's last name.
// Returns: BadgeInfo representing the issued badge, or an error if the operation fails.
func (c *Client) IssueBadge(templateId, email, firstName, lastName string) (i BadgeInfo, err error) {
url := fmt.Sprintf("https://api.credly.com/v1/organizations/%s/badges", c.OrganizationId)

now := time.Now()
issuedAt := now.Format("2006-01-02 15:04:05 -0700")

params := map[string]interface{}{
"badge_template_id": templateId,
"recipient_email": email,
"issued_to_first_name": firstName,
"issued_to_last_name": lastName,
"issued_at": issuedAt,
}
reqBody, err := json.Marshal(params)
if err != nil {
return i, fmt.Errorf("[credly.IssueBadge] Failed to marshal parameters: %v", err)
}

req, err := http.NewRequest("POST", url, bytes.NewBuffer(reqBody))
if err != nil {
return i, err
}

resp, err := c.Do(req)
if err != nil {
return i, err
}

defer resp.Body.Close()

if resp.StatusCode == http.StatusUnprocessableEntity {
// Contact already has badge
return i, fmt.Errorf(ErrBadgeAlreadyIssued)
}

if resp.StatusCode != http.StatusCreated {
return i, fmt.Errorf("[credly.IssueBadge] API request failed with status code: %d", resp.StatusCode)
}

var badgeResp issueBadgeResponse
if err := json.NewDecoder(resp.Body).Decode(&badgeResp); err != nil {
return i, fmt.Errorf("[credly.IssueBadge] Failed to parse JSON data: %v", err)
}

return badgeResp.Data, nil
}

// GetBadges retrieves all badges for a given email, optionally filtered by collections.
//
// email: The recipient's email address.
// collections: A list of collection tags to filter badges.
// Returns: A slice of BadgeInfo representing the retrieved badges, or an error if the operation fails.
func (c *Client) GetBadges(email string, collections []string) (b []BadgeInfo, err error) {
qUrl := fmt.Sprintf("https://api.credly.com/v1/organizations/%s/badges", c.OrganizationId)
qUrl = fmt.Sprintf("%s?filter=recipient_email_all::%s", qUrl, url.QueryEscape(email))

if len(collections) > 0 {
colFilter := fmt.Sprintf("|badge_templates[reporting_tags]::%s", strings.Join(collections, ","))
qUrl = fmt.Sprintf("%s%s", qUrl, url.QueryEscape(colFilter))
}

req, err := http.NewRequest("GET", qUrl, nil)
if err != nil {
return b, err
}

resp, err := c.Do(req)
if err != nil {
return b, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return b, fmt.Errorf("[credly.GetBadges] API request failed with status code: %d", resp.StatusCode)
}

var badgesResp getBadgesResponse
if err := json.NewDecoder(resp.Body).Decode(&badgesResp); err != nil {
return b, fmt.Errorf("[credly.GetBadges] Failed to parse JSON data: %v", err)
}

return badgesResp.Data, nil
}

// GetBadge retrieves a specific badge for a given email and badge ID.
//
// email: The recipient's email address.
// badgeId: The ID of the badge to be retrieved.
// Returns: A BadgeInfo representing the retrieved badge, or an error if the operation fails.
func (c *Client) GetBadge(email, badgeId string) (b BadgeInfo, err error) {
url := fmt.Sprintf("https://api.credly.com/v1/organizations/%s/badges", c.OrganizationId)
url = fmt.Sprintf("%s?filter=recipient_email_all::%s|badge_template_id::%s", url, email, badgeId)

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return b, err
}

resp, err := c.Do(req)
if err != nil {
return b, err
}
defer resp.Body.Close()

var badgesResp getBadgesResponse
if err := json.NewDecoder(resp.Body).Decode(&badgesResp); err != nil {
return b, fmt.Errorf("Failed to parse JSON data: %v", err)
}

if len(badgesResp.Data) == 0 {
return b, nil
}

return badgesResp.Data[0], nil
}
87 changes: 87 additions & 0 deletions credly/badge_template.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@

package credly

import (
"encoding/json"
"fmt"
"net/http"
)

// getBadgeTemplateResponse represents the response structure when fetching a specific badge template.
type getBadgeTemplateResponse struct {
Data BadgeTemplate `json:"data"`
}

// getBadgeTemplatesResponse represents the response structure when fetching multiple badge templates.
type getBadgeTemplatesResponse struct {
Data []BadgeTemplate `json:"data"`
}

// BadgeTemplate represents the details of a badge template in Credly.
type BadgeTemplate struct {
Id string `json:"id,omitempty"`
Name string `json:"name"`
Skills []string `json:"skills"`
Url string `json:"url"`
ImageUrl string `json:"image_url"`
VanitySlug string `json:"vanity_slug"`
}

// GetBadgeTemplate retrieves a specific badge template by its ID.
//
// templateId: The ID of the badge template to be retrieved.
// Returns: A BadgeTemplate representing the retrieved template, or an error if the operation fails.
func (c *Client) GetBadgeTemplate(templateId string) (b BadgeTemplate, err error) {
url := fmt.Sprintf("https://api.credly.com/v1/organizations/%s/badge_templates/%s", c.OrganizationId, templateId)

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return b, err
}

resp, err := c.Do(req)
if err != nil {
return b, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return b, fmt.Errorf("[credly.GetBadgeTemplate] API request failed with status code: %d", resp.StatusCode)
}

var badgeResp getBadgeTemplateResponse
if err := json.NewDecoder(resp.Body).Decode(&badgeResp); err != nil {
return b, fmt.Errorf("[credly.GetBadgeTemplate] Failed to parse JSON data: %v", err)
}

return badgeResp.Data, nil
}

// GetBadgeTemplates retrieves all badge templates for the organization.
//
// Returns: A slice of BadgeTemplate representing all templates, or an error if the operation fails.
func (c *Client) GetBadgeTemplates() (b []BadgeTemplate, err error) {
url := fmt.Sprintf("https://api.credly.com/v1/organizations/%s/badge_templates", c.OrganizationId)

req, err := http.NewRequest("GET", url, nil)
if err != nil {
return b, err
}

resp, err := c.Do(req)
if err != nil {
return b, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
return b, fmt.Errorf("[credly.GetBadgeTemplates] API request failed with status code: %d", resp.StatusCode)
}

var badgeResp getBadgeTemplatesResponse
if err := json.NewDecoder(resp.Body).Decode(&badgeResp); err != nil {
return b, fmt.Errorf("[credly.GetBadgeTemplates] Failed to parse JSON data: %v", err)
}

return badgeResp.Data, nil
}
97 changes: 97 additions & 0 deletions credly/badge_template_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package credly

import (
"bytes"
"encoding/base64"
"encoding/json"
"io"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func TestGetBadgeTemplate(t *testing.T) {
mockClient := new(MockHTTPClient)
client := &Client{
HTTPClient: mockClient,
authToken: base64.StdEncoding.EncodeToString([]byte("test-token" + "|")),
}

templateId := "template-123"

expectedTemplate := BadgeTemplate{
Id: "template-123",
Name: "Test Badge",
ImageUrl: "http://image.url",
}

responseBody, _ := json.Marshal(getBadgeTemplateResponse{
Data: expectedTemplate,
})

// Simulate a successful response
mockClient.On("Do", mock.Anything).Return(&http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(responseBody)),
}, nil)

template, err := client.GetBadgeTemplate(templateId)

assert.NoError(t, err)
assert.Equal(t, expectedTemplate, template)
mockClient.AssertExpectations(t)
}

func TestGetBadgeTemplates(t *testing.T) {
mockClient := new(MockHTTPClient)
client := &Client{
HTTPClient: mockClient,
authToken: base64.StdEncoding.EncodeToString([]byte("test-token" + "|")),
}

expectedTemplates := []BadgeTemplate{
{Id: "template-123", Name: "Badge 1", ImageUrl: "http://image1.url"},
{Id: "template-456", Name: "Badge 2", ImageUrl: "http://image2.url"},
}

responseBody, _ := json.Marshal(getBadgeTemplatesResponse{
Data: expectedTemplates,
})

// Simulate a successful response
mockClient.On("Do", mock.Anything).Return(&http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(bytes.NewReader(responseBody)),
}, nil)

templates, err := client.GetBadgeTemplates()

assert.NoError(t, err)
assert.Equal(t, expectedTemplates, templates)
mockClient.AssertExpectations(t)
}

func TestGetBadgeTemplate_Failure(t *testing.T) {
mockClient := new(MockHTTPClient)
client := &Client{
HTTPClient: mockClient,
authToken: base64.StdEncoding.EncodeToString([]byte("test-token" + "|")),
}

templateId := "template-123"

// Simulate a failure response
mockClient.On("Do", mock.Anything).Return(&http.Response{
StatusCode: http.StatusInternalServerError,
Body: io.NopCloser(bytes.NewBufferString("")),
}, nil)

template, err := client.GetBadgeTemplate(templateId)

assert.Error(t, err)
assert.Contains(t, err.Error(), "API request failed")
assert.Empty(t, template)
mockClient.AssertExpectations(t)
}
Loading

0 comments on commit 676257b

Please sign in to comment.