ainsleyclark/workplace

View on GitHub
workplace.go

Summary

Maintainability
A
35 mins
Test Coverage
// Copyright 2022 Ainsley Clark. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package workplace

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

type (
    // Notifier defines the single method interface that
    // transmits messages to a workplace thread.
    Notifier interface {
        // Notify sends a transmission to the corresponding thread with
        // a preformatted message.
        //
        // Returns an error if the message could not be marshalled,
        // sent or there was an error creating the request.
        Notify(tx Transmission) error
    }
    // Client represents the HTTP client for posting and
    // returning data from the Workplace API.
    Client struct {
        client     *http.Client
        baseURL    string
        config     Config
        marshaller func(v any) ([]byte, error)
    }
    // Transmission represents the data needed in order to send
    // a message via the Workplace API.
    Transmission struct {
        // Thread is the corresponding thread ID to send via workplace.
        Thread string `json:"thread" validate:"required" example:"t_111222333"`
        // Message is the formatted text message that will be sent
        // via workplace.
        Message string `json:"message" validate:"required" example:"Hey there!"`
    }
    // transmission is the JSON structure for workplace transmissions.
    transmission struct {
        Recipient recipient `json:"recipient"`
        Message   message   `json:"message"`
    }
    // transmission is the JSON structure for workplace messages.
    message struct {
        Text string `json:"text"`
    }
    // transmission is the JSON structure for workplace recipients.
    recipient struct {
        ThreadKey string `json:"thread_key"`
    }
)

const (
    // Timeout specifies a time limit for requests made by the
    // Workplace Client.
    Timeout = time.Second * 20
)

// New returns a new Workplace Client.
func New(config Config) (Notifier, error) {
    err := config.Validate()
    if err != nil {
        return nil, err
    }
    return &Client{
        client: &http.Client{
            Timeout: Timeout,
        },
        baseURL:    "https://graph.workplace.com/me/messages",
        config:     config,
        marshaller: json.Marshal,
    }, nil
}

// Notify sends a transmission to the corresponding thread with
// a preformatted message.
//
// Returns an error if the message could not be marshalled,
// sent or there was an error creating the request.
func (c *Client) Notify(tx Transmission) error {
    chunks := ChunkMessage(tx.Message, 1900) // Text message must be UTF-8 and has a 2000-character limit.

    for index, chunk := range chunks {
        if len(chunks) > 1 {
            chunk = fmt.Sprintf("Page: %d/%d...\n%s", index+1, len(chunks), chunk)
        }

        t := transmission{
            Recipient: recipient{ThreadKey: tx.Thread},
            Message:   message{Text: chunk},
        }

        buf, err := c.marshaller(t)
        if err != nil {
            return err
        }

        req, err := http.NewRequest(http.MethodPost, c.baseURL, bytes.NewBuffer(buf))
        if err != nil {
            return err
        }

        // Add the appropriate auth headers.
        req.Header.Add("Content-Type", "application/json")
        req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", c.config.Token))

        resp, err := c.client.Do(req)
        if err != nil {
            return err
        } else if resp.StatusCode != http.StatusOK {
            return errors.New("invalid request with status code: " + resp.Status)
        }
        defer resp.Body.Close() // nolint
    }

    return nil
}