1
0
mirror of https://github.com/taigrr/wtf synced 2025-01-18 04:03:14 -08:00

Implementation of WTF devto module

This commit is contained in:
vavelar
2019-09-20 17:31:11 +02:00
parent d76db44b72
commit 425a08522e
50 changed files with 611 additions and 11710 deletions

View File

@@ -0,0 +1,105 @@
package devto
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/google/go-querystring/query"
)
//ArticlesResource implements the APIResource interface
//for devto articles.
type ArticlesResource struct {
API *Client
}
//List will return the articles uploaded to devto, the result
//can be narrowed down, filtered or enhanced using query
// parameters as specified on the documentation.
//See: https://docs.dev.to/api/#tag/articles/paths/~1articles/get
func (ar *ArticlesResource) List(ctx context.Context, opt ArticleListOptions) ([]Article, error) {
var l []Article
q, err := query.Values(opt)
if err != nil {
return nil, err
}
req, _ := ar.API.NewRequest(http.MethodGet, fmt.Sprintf("api/articles?%s", q.Encode()), nil)
res, _ := ar.API.HTTPClient.Do(req)
cont, err := ioutil.ReadAll(res.Body)
if err != nil {
return nil, err
}
json.Unmarshal(cont, &l)
return l, nil
}
//Find will retrieve an Article matching the ID passed.
func (ar *ArticlesResource) Find(ctx context.Context, id uint32) (Article, error) {
var art Article
req, _ := ar.API.NewRequest(http.MethodGet, fmt.Sprintf("api/articles/%d", id), nil)
res, err := ar.API.HTTPClient.Do(req)
if err != nil {
return art, err
}
cont, err := ioutil.ReadAll(res.Body)
if err != nil {
return art, err
}
json.Unmarshal(cont, &art)
return art, nil
}
//New will create a new article on dev.to
func (ar *ArticlesResource) New(ctx context.Context, a Article) (Article, error) {
if ar.API.Config.InsecureOnly {
return a, ErrProtectedEndpoint
}
cont, err := json.Marshal(a)
if err != nil {
return a, err
}
req, err := ar.API.NewRequest(http.MethodPost, "api/articles", strings.NewReader(string(cont)))
if err != nil {
return a, err
}
req.Header.Add(APIKeyHeader, ar.API.Config.APIKey)
res, err := ar.API.HTTPClient.Do(req)
if err != nil {
return a, err
}
content, err := ioutil.ReadAll(res.Body)
if err != nil {
return a, err
}
json.Unmarshal(content, &a)
return a, nil
}
func (ar *ArticlesResource) Update(ctx context.Context, a Article) (Article, error) {
if ar.API.Config.InsecureOnly {
return a, ErrProtectedEndpoint
}
cont, err := json.Marshal(a)
if err != nil {
return a, err
}
req, err := ar.API.NewRequest(http.MethodPut, fmt.Sprintf("api/articles/%d", a.ID), strings.NewReader(string(cont)))
if err != nil {
return a, err
}
req.Header.Add(APIKeyHeader, ar.API.Config.APIKey)
res, err := ar.API.HTTPClient.Do(req)
if err != nil {
return a, err
}
content, err := ioutil.ReadAll(res.Body)
if err != nil {
return a, err
}
json.Unmarshal(content, &a)
return a, nil
}

View File

@@ -0,0 +1,33 @@
package devto
import "errors"
//Confugration errors
var (
ErrMissingRequiredParameter = errors.New("a required parameter is missing")
)
//Config contains the elements required to initialize a
// devto client.
type Config struct {
APIKey string
InsecureOnly bool
}
//NewConfig build a devto configuration instance with the
//required parameters.
//
//It takes a boolean (p) as first parameter to indicate if
//you need access to endpoints which require authentication,
//and a API key as second paramenter, if p is set to true and
//you don't provide an API key, it will return an error.
func NewConfig(p bool, k string) (c *Config, err error) {
if p == true && k == "" {
return nil, ErrMissingRequiredParameter
}
return &Config{
InsecureOnly: !p,
APIKey: k,
}, nil
}

View File

@@ -0,0 +1,81 @@
package devto
import (
"context"
"errors"
"io"
"net/http"
"net/url"
)
//Configuration constants
const (
BaseURL string = "https://dev.to"
APIVersion string = "0.5.1"
APIKeyHeader string = "api-key"
)
//devto client errors
var (
ErrMissingConfig = errors.New("missing configuration")
ErrProtectedEndpoint = errors.New("to use this resource you need to provide an authentication method")
)
type httpClient interface {
Do(req *http.Request) (res *http.Response, err error)
}
//Client is the main data structure for performing actions
//against dev.to API
type Client struct {
Context context.Context
BaseURL *url.URL
HTTPClient httpClient
Config *Config
Articles *ArticlesResource
}
//NewClient takes a context, a configuration pointer and optionally a
//base http client (bc) to build an Client instance.
func NewClient(ctx context.Context, conf *Config, bc httpClient, bu string) (dev *Client, err error) {
if bc == nil {
bc = http.DefaultClient
}
if ctx == nil {
ctx = context.Background()
}
if conf == nil {
return nil, ErrMissingConfig
}
if bu == "" {
bu = BaseURL
}
u, _ := url.Parse(bu)
c := &Client{
Context: ctx,
BaseURL: u,
HTTPClient: bc,
Config: conf,
}
c.Articles = &ArticlesResource{API: c}
return c, nil
}
//NewRequest build the request relative to the client BaseURL
func (c *Client) NewRequest(method string, uri string, body io.Reader) (*http.Request, error) {
u, err := url.Parse(uri)
if err != nil {
return nil, err
}
fu := c.BaseURL.ResolveReference(u).String()
req, err := http.NewRequest(method, fu, body)
if err != nil {
return nil, err
}
return req, nil
}

View File

@@ -0,0 +1,87 @@
package devto
import (
"net/url"
"strings"
"time"
)
//User contains information about a devto account
type User struct {
Name string `json:"name,omitempty"`
Username string `json:"username,omitempty"`
TwitterUsername string `json:"twitter_username,omitempty"`
GithubUsername string `json:"github_username,omitempty"`
WebsiteURL *WebURL `json:"website_url,omitempty"`
ProfileImage *WebURL `json:"profile_image,omitempty"`
ProfileImage90 *WebURL `json:"profile_image_90,omitempty"`
}
//Organization describes a company or group that
//publishes content to devto.
type Organization struct {
Name string `json:"name,omitempty"`
Username string `json:"username,omitempty"`
Slug string `json:"slug,omitempty"`
ProfileImage *WebURL `json:"profile_image,omitempty"`
ProfileImage90 *WebURL `json:"profile_image_90,omitempty"`
}
//Tags are a group of topics related to an article
type Tags []string
//Article contains all the information related to a single
//information resource from devto.
type Article struct {
TypeOf string `json:"type_of,omitempty"`
ID uint32 `json:"id,omitempty"`
Title string `json:"title,omitempty"`
Description string `json:"description,omitempty"`
CoverImage *WebURL `json:"cover_image,omitempty"`
SocialImage *WebURL `json:"social_image,omitempty"`
PublishedAt *time.Time `json:"published_at,omitempty"`
EditedAt *time.Time `json:"edited_at,omitempty"`
CrossPostedAt *time.Time `json:"crossposted_at,omitempty"`
LastCommentAt *time.Time `json:"last_comment_at,omitempty"`
TagList Tags `json:"tag_list,omitempty"`
Tags string `json:"tags,omitempty"`
Slug string `json:"slug,omitempty"`
Path *WebURL `json:"path,omitempty"`
URL *WebURL `json:"url,omitempty"`
CanonicalURL *WebURL `json:"canonical_url,omitempty"`
CommentsCount uint `json:"comments_count,omitempty"`
PositiveReactionsCount uint `json:"positive_reactions_count,omitempty"`
PublishedTimestamp *time.Time `json:"published_timestamp,omitempty"`
User User `json:"user,omitempty"`
Organization Organization `json:"organization,omitempty"`
BodyHTML string `json:"body_html,omitempty"`
BodyMarkdown string `json:"body_markdown,omitempty"`
Published bool `json:"published,omitempty"`
}
//ArticleListOptions holds the query values to pass as
//query string parameter to the Articles List action.
type ArticleListOptions struct {
Tags string `url:"tag,omitempty"`
Username string `url:"username,omitempty"`
State string `url:"state,omitempty"`
Top string `url:"top,omitempty"`
Page int `url:"page,omitempty"`
}
type WebURL struct {
*url.URL
}
//UnmarshalJSON overrides the default unmarshal behaviour
//for URL
func (s *WebURL) UnmarshalJSON(b []byte) error {
c := string(b)
c = strings.Trim(c, "\"")
uri, err := url.Parse(c)
if err != nil {
return err
}
s.URL = uri
return nil
}