Skip to content

Commit

Permalink
Merge pull request #4 from moul/dev/moul/v1
Browse files Browse the repository at this point in the history
feat: v1
  • Loading branch information
moul committed Aug 10, 2022
2 parents 9b25a39 + 0b3dbe1 commit 44fa515
Show file tree
Hide file tree
Showing 18 changed files with 413 additions and 118 deletions.
13 changes: 13 additions & 0 deletions .asanaman-cache/0a2d2cb3b5c453de7a5051564c45cf3e
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
HTTP/1.1 302 Found
Connection: keep-alive
Datacenter-Time-End: 1660117185.120
Date: Wed, 10 Aug 2022 07:39:45 GMT
Location: /-/login?u=%2Fusers%2Fme
Server: nginx
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Asana-Pageload-App: pageload-prod
X-Asana-Pageload-Pod: pageload-5b6b5cfcf-bdjcw
X-Loadbalancer: prod-lb021.ec2
X-Robots-Tag: none
Content-Length: 0

2 changes: 1 addition & 1 deletion .goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ builds:
- "-a"
ldflags:
- '-extldflags "-static"'
main: ./
main: ./cmd/asanaman
checksum:
name_template: '{{.ProjectName}}_checksums.txt'
changelog:
Expand Down
9 changes: 0 additions & 9 deletions AUTHORS

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 7 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
GOPKG ?= moul.io/asanaman
DOCKER_IMAGE ?= moul/asanaman
GOBINS ?= .
GOBINS ?= ./cmd/asanaman
NPM_PACKAGES ?= .

include rules.mk

generate: install
run: fmt
@# requires $ASANAMAN_TOKEN
go run ./cmd/asanaman --debug me

generate:
GO111MODULE=off go get github.com/campoy/embedmd
mkdir -p .tmp
echo 'foo@bar:~$$ asanaman' > .tmp/usage.txt
asanaman 2>&1 >> .tmp/usage.txt
(ASANAMAN_TOKEN=foo ASANAMAN_DOMAIN=foo go run ./cmd/asanaman 2>&1 || true) >> .tmp/usage.txt
embedmd -w README.md
rm -rf .tmp
.PHONY: generate
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@
[embedmd]:# (.tmp/usage.txt console)
```console
foo@bar:~$ asanaman
_ _ _ _
__ _ ___ | | __ _ _ _ __ _ ___ _ _ ___ _ __ ___ ___ | |_ ___ _ __ _ __ | | __ _ | |_ ___
/ _` |/ _ \| |/ _` || ' \ / _` ||___|| '_|/ -_)| '_ \/ _ \|___|| _|/ -_)| ' \ | '_ \| |/ _` || _|/ -_)
\__, |\___/|_|\__,_||_||_|\__, | |_| \___|| .__/\___/ \__|\___||_|_|_|| .__/|_|\__,_| \__|\___|
|___/ |___/ |_| |_|
12 CPUs, /home/moul/.local/bin/asanaman, fwrz, go1.16.5
[]
USAGE
asanaman [global flags] <subcommand> [flags] [args]

SUBCOMMANDS
me
project-list

FLAGS
-cache-path .asanaman-cache cache path
-debug false debug mode
-domain ... Asana workspace
-token ... Asana token
exit status 1
```

## Install
Expand Down
150 changes: 150 additions & 0 deletions asana/asana.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package asana

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"strings"

"github.com/gregjones/httpcache"
"github.com/gregjones/httpcache/diskcache"
"go.uber.org/multierr"
"go.uber.org/zap"
"moul.io/hcfilters"
)

const (
defaultUserAgent = "asanaman/1.0"
defaultBaseURL = "https://app.asana.com/api/1.0/"
)

type Client struct {
// args
token string
domain string
cachePath string
logger *zap.Logger

// internal
http *http.Client
baseURL *url.URL
userAgent string
}

func New(token, domain, cachePath string, logger *zap.Logger) (*Client, error) {
client := Client{
token: token,
domain: domain,
cachePath: cachePath,
userAgent: defaultUserAgent,
logger: logger,
}
client.baseURL, _ = url.Parse(defaultBaseURL)

client.http = &http.Client{
Transport: httpcache.NewTransport(
hcfilters.MaxSize( // skip caching results > 10Mb
diskcache.New(cachePath),
10*1024*1024, // nolint:gomnd
),
),
}

return &client, nil
}

type ReqOpts struct {
Method string
Path string
Data interface{}
Form url.Values
// opt *Filter
}

type request struct {
Data interface{} `json:"data,omitempty"`
}

type response struct {
Data interface{} `json:"data,omitempty"`
Errors []asanaError `json:"errors,omitempty"`
}

type asanaError struct {
Phrase string `json:"phrase,omitempty"`
Message string `json:"message,omitempty"`
}

func (e asanaError) Error() string {
return fmt.Sprintf("%v - %v", e.Message, e.Phrase)
}

func (c *Client) Request(ctx context.Context, opts ReqOpts, ret interface{}) error {
logger := c.logger
defer func() { logger.Debug("req") }()
// compute path
rel, err := url.Parse(opts.Path)
if err != nil {
return fmt.Errorf("invalid path: %w", err)
}
u := c.baseURL.ResolveReference(rel)
logger = logger.With(zap.String("u", u.String()))

// compute input body
var body io.Reader
switch {
case opts.Data != nil:
b, err := json.Marshal(request{Data: opts.Data})
if err != nil {
return fmt.Errorf("encode body: %w", err)
}
c.logger.Debug("")
body = bytes.NewReader(b)
case opts.Form != nil:
body = strings.NewReader(opts.Form.Encode())
}

// init request
req, err := http.NewRequest(opts.Method, u.String(), body)
if err != nil {
return fmt.Errorf("create request: %w", err)
}
req.Header.Set("User-Agent", c.userAgent)
req.Header.Add("Authorization", "Bearer "+c.token)

// compute input body
switch {
case opts.Data != nil:
req.Header.Set("Content-Type", "application/json")
case opts.Form != nil:
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
}

// perform request
resp, err := c.http.Do(req.WithContext(ctx))
if err != nil {
return fmt.Errorf("HTTP request: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusUnauthorized {
return ErrUnauthorized
}

// compute response
res := &response{Data: ret}
err = json.NewDecoder(resp.Body).Decode(res)
if err != nil {
return fmt.Errorf("cannot parse response: %w", err)
}

// error handling
var errs error
for _, err := range res.Errors {
errs = multierr.Append(errs, err)
}
return err
}
15 changes: 15 additions & 0 deletions asana/endpoints.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package asana

import "context"

func (c *Client) Me(ctx context.Context) (*User, error) {
var user User
return &user, c.Request(ctx, ReqOpts{Path: "users/me"}, &user)
}

type ProjectListOpts struct{}

func (c *Client) ProjectList(ctx context.Context, opts ProjectListOpts) (*Projects, error) {
var projects Projects
return &projects, c.Request(ctx, ReqOpts{Path: "projects"}, &projects)
}
5 changes: 5 additions & 0 deletions asana/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package asana

import "errors"

var ErrUnauthorized = errors.New("asana: unauthorized")
30 changes: 30 additions & 0 deletions asana/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package asana

type Workspace struct {
GID string `json:"gid,omitempty"`
Name string `json:"name,omitempty"`
ResourceType string `json:"resource_type,omitempty"`
}

type User struct {
GID string `json:"gid,omitempty"`
Email string `json:"email,omitempty"`
Name string `json:"name,omitempty"`
Photo struct {
Image2121 string `json:"image_21x21,omitempty"`
Image2727 string `json:"image_27x27,omitempty"`
Image3636 string `json:"image_36x36,omitempty"`
Image6060 string `json:"image_60x60,omitempty"`
Image128128 string `json:"image_128x128,omitempty"`
} `json:"photo,omitempty"`
ResourceType string `json:"resource_type,omitempty"`
Workspaces []Workspace `json:"workspaces,omitempty"`
}

type Project struct {
GID string `json:"gid,omitempty"`
Name string `json:"name,omitempty"`
ResourceType string `json:"resource_type,omitempty"`
}

type Projects []Project

0 comments on commit 44fa515

Please sign in to comment.