├── gthttp ├── mock │ ├── DELETE │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ └── time_entries │ │ │ └── 1.json │ ├── POST │ │ └── api │ │ │ └── v8 │ │ │ ├── reset_token.json │ │ │ ├── clients.json │ │ │ ├── projects.json │ │ │ └── signups.json │ ├── GET │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ ├── clients.json │ │ │ ├── time_entries │ │ │ └── 1.json │ │ │ ├── workspaces │ │ │ └── 1.json │ │ │ ├── projects.json │ │ │ ├── time_entries.json │ │ │ ├── me.json │ │ │ └── workspaces.json │ └── PUT │ │ └── api │ │ └── v8 │ │ ├── clients │ │ └── 1.json │ │ ├── projects │ │ └── 1.json │ │ ├── time_entries.json │ │ ├── time_entries │ │ └── 1.json │ │ ├── workspaces │ │ └── 1.json │ │ └── me.json ├── logger.go ├── httpclient_test.go └── httpclient.go ├── gtuser ├── mock │ ├── DELETE │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ └── time_entries │ │ │ └── 1.json │ ├── POST │ │ └── api │ │ │ └── v8 │ │ │ ├── reset_token.json │ │ │ ├── clients.json │ │ │ ├── projects.json │ │ │ └── signups.json │ ├── GET │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ ├── clients.json │ │ │ ├── time_entries │ │ │ └── 1.json │ │ │ ├── workspaces │ │ │ └── 1.json │ │ │ ├── projects.json │ │ │ ├── time_entries.json │ │ │ ├── me.json │ │ │ └── workspaces.json │ └── PUT │ │ └── api │ │ └── v8 │ │ ├── clients │ │ └── 1.json │ │ ├── projects │ │ └── 1.json │ │ ├── time_entries.json │ │ ├── time_entries │ │ └── 1.json │ │ ├── workspaces │ │ └── 1.json │ │ └── me.json ├── user_test.go └── user.go ├── gtclient ├── mock │ ├── DELETE │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ └── time_entries │ │ │ └── 1.json │ ├── POST │ │ └── api │ │ │ └── v8 │ │ │ ├── reset_token.json │ │ │ ├── clients.json │ │ │ ├── projects.json │ │ │ └── signups.json │ ├── GET │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ ├── clients.json │ │ │ ├── time_entries │ │ │ └── 1.json │ │ │ ├── workspaces │ │ │ └── 1.json │ │ │ ├── projects.json │ │ │ ├── time_entries.json │ │ │ ├── me.json │ │ │ └── workspaces.json │ └── PUT │ │ └── api │ │ └── v8 │ │ ├── clients │ │ └── 1.json │ │ ├── projects │ │ └── 1.json │ │ ├── time_entries.json │ │ ├── time_entries │ │ └── 1.json │ │ ├── workspaces │ │ └── 1.json │ │ └── me.json ├── client_test.go └── client.go ├── gtproject ├── mock │ ├── DELETE │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ └── time_entries │ │ │ └── 1.json │ ├── POST │ │ └── api │ │ │ └── v8 │ │ │ ├── reset_token.json │ │ │ ├── clients.json │ │ │ ├── projects.json │ │ │ └── signups.json │ ├── GET │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ ├── clients.json │ │ │ ├── time_entries │ │ │ └── 1.json │ │ │ ├── workspaces │ │ │ └── 1.json │ │ │ ├── projects.json │ │ │ ├── time_entries.json │ │ │ ├── me.json │ │ │ └── workspaces.json │ └── PUT │ │ └── api │ │ └── v8 │ │ ├── clients │ │ └── 1.json │ │ ├── projects │ │ └── 1.json │ │ ├── time_entries.json │ │ ├── time_entries │ │ └── 1.json │ │ ├── workspaces │ │ └── 1.json │ │ └── me.json ├── project_test.go └── project.go ├── gttimentry ├── mock │ ├── DELETE │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ └── time_entries │ │ │ └── 1.json │ ├── POST │ │ └── api │ │ │ └── v8 │ │ │ ├── reset_token.json │ │ │ ├── clients.json │ │ │ ├── time_entries.json │ │ │ ├── projects.json │ │ │ └── signups.json │ ├── GET │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ ├── clients.json │ │ │ ├── time_entries │ │ │ └── 1.json │ │ │ ├── workspaces │ │ │ └── 1.json │ │ │ ├── projects.json │ │ │ ├── time_entries.json │ │ │ ├── time_entries_with_parameters.json │ │ │ ├── me.json │ │ │ └── workspaces.json │ └── PUT │ │ └── api │ │ └── v8 │ │ ├── clients │ │ └── 1.json │ │ ├── projects │ │ └── 1.json │ │ ├── time_entries.json │ │ ├── time_entries │ │ └── 1.json │ │ ├── workspaces │ │ └── 1.json │ │ └── me.json ├── timeentry_test.go └── timeentry.go ├── gtworkspace ├── mock │ ├── DELETE │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ └── time_entries │ │ │ └── 1.json │ ├── POST │ │ └── api │ │ │ └── v8 │ │ │ ├── reset_token.json │ │ │ ├── clients.json │ │ │ ├── projects.json │ │ │ └── signups.json │ ├── GET │ │ └── api │ │ │ └── v8 │ │ │ ├── clients │ │ │ └── 1.json │ │ │ ├── projects │ │ │ └── 1.json │ │ │ ├── clients.json │ │ │ ├── time_entries │ │ │ └── 1.json │ │ │ ├── workspaces │ │ │ └── 1.json │ │ │ ├── projects.json │ │ │ ├── time_entries.json │ │ │ ├── me.json │ │ │ └── workspaces.json │ └── PUT │ │ └── api │ │ └── v8 │ │ ├── clients │ │ └── 1.json │ │ ├── projects │ │ └── 1.json │ │ ├── time_entries.json │ │ ├── time_entries │ │ └── 1.json │ │ ├── workspaces │ │ └── 1.json │ │ └── me.json ├── workspace_test.go └── workspace.go ├── .travis.yml ├── .gitignore ├── .readme.yaml ├── LICENSE ├── readme-template.md ├── README.md ├── test └── gtest.go └── doc.go /gthttp/mock/DELETE/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gthttp/mock/DELETE/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtuser/mock/DELETE/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtuser/mock/DELETE/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtclient/mock/DELETE/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtclient/mock/DELETE/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gthttp/mock/DELETE/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtproject/mock/DELETE/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtproject/mock/DELETE/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gttimentry/mock/DELETE/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gttimentry/mock/DELETE/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtuser/mock/DELETE/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtworkspace/mock/DELETE/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtworkspace/mock/DELETE/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtclient/mock/DELETE/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtclient/mock/POST/api/v8/reset_token.json: -------------------------------------------------------------------------------- 1 | "123456789" -------------------------------------------------------------------------------- /gthttp/mock/POST/api/v8/reset_token.json: -------------------------------------------------------------------------------- 1 | "123456789" -------------------------------------------------------------------------------- /gtproject/mock/DELETE/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gttimentry/mock/DELETE/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtuser/mock/POST/api/v8/reset_token.json: -------------------------------------------------------------------------------- 1 | "123456789" -------------------------------------------------------------------------------- /gtworkspace/mock/DELETE/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /gtproject/mock/POST/api/v8/reset_token.json: -------------------------------------------------------------------------------- 1 | "123456789" -------------------------------------------------------------------------------- /gttimentry/mock/POST/api/v8/reset_token.json: -------------------------------------------------------------------------------- 1 | "123456789" -------------------------------------------------------------------------------- /gtworkspace/mock/POST/api/v8/reset_token.json: -------------------------------------------------------------------------------- 1 | "123456789" -------------------------------------------------------------------------------- /gtclient/mock/GET/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "Id 1", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gthttp/mock/GET/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "Id 1", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gthttp/mock/PUT/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "new name", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gtproject/mock/GET/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "Id 1", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gttimentry/mock/GET/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "Id 1", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gtuser/mock/GET/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "Id 1", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gtuser/mock/PUT/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "new name", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gtclient/mock/PUT/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "new name", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gtproject/mock/PUT/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "new name", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gttimentry/mock/PUT/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "new name", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gtworkspace/mock/GET/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "Id 1", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gtworkspace/mock/PUT/api/v8/clients/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id": 1, 4 | "wid": 777, 5 | "name": "new name", 6 | "notes": "something about the client" 7 | } 8 | } -------------------------------------------------------------------------------- /gtclient/mock/POST/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1239455, 4 | "wid":777, 5 | "name":"Very Big Company", 6 | "at":"2013-02-26T08:45:28+00:00" 7 | } 8 | } -------------------------------------------------------------------------------- /gthttp/mock/POST/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1239455, 4 | "wid":777, 5 | "name":"Very Big Company", 6 | "at":"2013-02-26T08:45:28+00:00" 7 | } 8 | } -------------------------------------------------------------------------------- /gtproject/mock/POST/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1239455, 4 | "wid":777, 5 | "name":"Very Big Company", 6 | "at":"2013-02-26T08:45:28+00:00" 7 | } 8 | } -------------------------------------------------------------------------------- /gttimentry/mock/POST/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1239455, 4 | "wid":777, 5 | "name":"Very Big Company", 6 | "at":"2013-02-26T08:45:28+00:00" 7 | } 8 | } -------------------------------------------------------------------------------- /gtuser/mock/POST/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1239455, 4 | "wid":777, 5 | "name":"Very Big Company", 6 | "at":"2013-02-26T08:45:28+00:00" 7 | } 8 | } -------------------------------------------------------------------------------- /gtworkspace/mock/POST/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1239455, 4 | "wid":777, 5 | "name":"Very Big Company", 6 | "at":"2013-02-26T08:45:28+00:00" 7 | } 8 | } -------------------------------------------------------------------------------- /gthttp/mock/PUT/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123398, 6 | "name":"new name", 7 | "billable":false, 8 | "active":true, 9 | "at":"2013-03-06T12:15:37+00:00", 10 | "template":true, 11 | "color":"6" 12 | } 13 | } -------------------------------------------------------------------------------- /gtuser/mock/PUT/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123398, 6 | "name":"new name", 7 | "billable":false, 8 | "active":true, 9 | "at":"2013-03-06T12:15:37+00:00", 10 | "template":true, 11 | "color":"6" 12 | } 13 | } -------------------------------------------------------------------------------- /gtclient/mock/PUT/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123398, 6 | "name":"new name", 7 | "billable":false, 8 | "active":true, 9 | "at":"2013-03-06T12:15:37+00:00", 10 | "template":true, 11 | "color":"6" 12 | } 13 | } -------------------------------------------------------------------------------- /gtproject/mock/PUT/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123398, 6 | "name":"new name", 7 | "billable":false, 8 | "active":true, 9 | "at":"2013-03-06T12:15:37+00:00", 10 | "template":true, 11 | "color":"6" 12 | } 13 | } -------------------------------------------------------------------------------- /gttimentry/mock/PUT/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123398, 6 | "name":"new name", 7 | "billable":false, 8 | "active":true, 9 | "at":"2013-03-06T12:15:37+00:00", 10 | "template":true, 11 | "color":"6" 12 | } 13 | } -------------------------------------------------------------------------------- /gtworkspace/mock/PUT/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123398, 6 | "name":"new name", 7 | "billable":false, 8 | "active":true, 9 | "at":"2013-03-06T12:15:37+00:00", 10 | "template":true, 11 | "color":"6" 12 | } 13 | } -------------------------------------------------------------------------------- /gthttp/mock/PUT/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":3, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "duration":1200, 10 | "description":"Meeting with possible clients", 11 | "tags":["billed"] 12 | } 13 | } -------------------------------------------------------------------------------- /gtuser/mock/PUT/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":3, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "duration":1200, 10 | "description":"Meeting with possible clients", 11 | "tags":["billed"] 12 | } 13 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - 1.11.x 5 | before_install: 6 | - go get github.com/mattn/goveralls 7 | - go get golang.org/x/tools/cmd/cover 8 | os: 9 | - linux 10 | script: 11 | - go build -race 12 | - go test ./... -coverprofile=cover.out 13 | after_success: 14 | - bash <(curl -s https://codecov.io/bash) 15 | -------------------------------------------------------------------------------- /gtclient/mock/PUT/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":3, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "duration":1200, 10 | "description":"Meeting with possible clients", 11 | "tags":["billed"] 12 | } 13 | } -------------------------------------------------------------------------------- /gtproject/mock/PUT/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":3, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "duration":1200, 10 | "description":"Meeting with possible clients", 11 | "tags":["billed"] 12 | } 13 | } -------------------------------------------------------------------------------- /gttimentry/mock/POST/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":3, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "duration":1200, 10 | "description":"Meeting with possible clients", 11 | "tags":["billed"] 12 | } 13 | } -------------------------------------------------------------------------------- /gttimentry/mock/PUT/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":3, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "duration":1200, 10 | "description":"Meeting with possible clients", 11 | "tags":["billed"] 12 | } 13 | } -------------------------------------------------------------------------------- /gtworkspace/mock/PUT/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":3, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "duration":1200, 10 | "description":"Meeting with possible clients", 11 | "tags":["billed"] 12 | } 13 | } -------------------------------------------------------------------------------- /gtclient/mock/GET/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gthttp/mock/GET/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gtuser/mock/GET/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gtproject/mock/GET/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gttimentry/mock/GET/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gtworkspace/mock/GET/api/v8/projects/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gthttp/mock/POST/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":3, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"An awesome project", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template_id":10237, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gtuser/mock/POST/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":3, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"An awesome project", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template_id":10237, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gtclient/mock/POST/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":3, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"An awesome project", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template_id":10237, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gtproject/mock/POST/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":3, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"An awesome project", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template_id":10237, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gttimentry/mock/POST/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":3, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"An awesome project", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template_id":10237, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gtworkspace/mock/POST/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":3, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"An awesome project", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template_id":10237, 12 | "color": "5" 13 | } 14 | } -------------------------------------------------------------------------------- /gtclient/mock/PUT/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":436694100, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "stop":"2013-03-05T08:58:58.000Z", 10 | "duration":1240, 11 | "description":"new", 12 | "at": "2013-03-05T12:34:50+00:00" 13 | } 14 | } -------------------------------------------------------------------------------- /gthttp/mock/PUT/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":436694100, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "stop":"2013-03-05T08:58:58.000Z", 10 | "duration":1240, 11 | "description":"new", 12 | "at": "2013-03-05T12:34:50+00:00" 13 | } 14 | } -------------------------------------------------------------------------------- /gtuser/mock/PUT/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":436694100, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "stop":"2013-03-05T08:58:58.000Z", 10 | "duration":1240, 11 | "description":"new", 12 | "at": "2013-03-05T12:34:50+00:00" 13 | } 14 | } -------------------------------------------------------------------------------- /gthttp/mock/GET/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "name":"Id 1", 6 | "notes":"something about the client", 7 | "at":"2013-02-26T08:55:28+00:00" 8 | }, { 9 | "id":2, 10 | "wid":777, 11 | "name":"Id 2", 12 | "notes":"Really cool people", 13 | "at":"2013-03-26T08:55:28+00:00" 14 | } 15 | ] -------------------------------------------------------------------------------- /gtproject/mock/PUT/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":436694100, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "stop":"2013-03-05T08:58:58.000Z", 10 | "duration":1240, 11 | "description":"new", 12 | "at": "2013-03-05T12:34:50+00:00" 13 | } 14 | } -------------------------------------------------------------------------------- /gttimentry/mock/PUT/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":436694100, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "stop":"2013-03-05T08:58:58.000Z", 10 | "duration":1240, 11 | "description":"new", 12 | "at": "2013-03-05T12:34:50+00:00" 13 | } 14 | } -------------------------------------------------------------------------------- /gtuser/mock/GET/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "name":"Id 1", 6 | "notes":"something about the client", 7 | "at":"2013-02-26T08:55:28+00:00" 8 | }, { 9 | "id":2, 10 | "wid":777, 11 | "name":"Id 2", 12 | "notes":"Really cool people", 13 | "at":"2013-03-26T08:55:28+00:00" 14 | } 15 | ] -------------------------------------------------------------------------------- /gtworkspace/mock/PUT/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": 3 | { 4 | "id":436694100, 5 | "pid":123, 6 | "wid":777, 7 | "billable":false, 8 | "start":"2013-03-05T07:58:58.000Z", 9 | "stop":"2013-03-05T08:58:58.000Z", 10 | "duration":1240, 11 | "description":"new", 12 | "at": "2013-03-05T12:34:50+00:00" 13 | } 14 | } -------------------------------------------------------------------------------- /gtclient/mock/GET/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "name":"Id 1", 6 | "notes":"something about the client", 7 | "at":"2013-02-26T08:55:28+00:00" 8 | }, { 9 | "id":2, 10 | "wid":777, 11 | "name":"Id 2", 12 | "notes":"Really cool people", 13 | "at":"2013-03-26T08:55:28+00:00" 14 | } 15 | ] -------------------------------------------------------------------------------- /gtproject/mock/GET/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "name":"Id 1", 6 | "notes":"something about the client", 7 | "at":"2013-02-26T08:55:28+00:00" 8 | }, { 9 | "id":2, 10 | "wid":777, 11 | "name":"Id 2", 12 | "notes":"Really cool people", 13 | "at":"2013-03-26T08:55:28+00:00" 14 | } 15 | ] -------------------------------------------------------------------------------- /gttimentry/mock/GET/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "name":"Id 1", 6 | "notes":"something about the client", 7 | "at":"2013-02-26T08:55:28+00:00" 8 | }, { 9 | "id":2, 10 | "wid":777, 11 | "name":"Id 2", 12 | "notes":"Really cool people", 13 | "at":"2013-03-26T08:55:28+00:00" 14 | } 15 | ] -------------------------------------------------------------------------------- /gtworkspace/mock/GET/api/v8/clients.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "name":"Id 1", 6 | "notes":"something about the client", 7 | "at":"2013-02-26T08:55:28+00:00" 8 | }, { 9 | "id":2, 10 | "wid":777, 11 | "name":"Id 2", 12 | "notes":"Really cool people", 13 | "at":"2013-03-26T08:55:28+00:00" 14 | } 15 | ] -------------------------------------------------------------------------------- /gthttp/mock/GET/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":1, 4 | "wid":777, 5 | "pid":193791, 6 | "tid":13350500, 7 | "billable":false, 8 | "start":"2013-02-27T01:24:00+00:00", 9 | "stop":"2013-02-27T07:24:00+00:00", 10 | "duration":21600, 11 | "description":"Some serious work", 12 | "tags":["billed"], 13 | "at":"2013-02-27T13:49:18+00:00" 14 | } 15 | } -------------------------------------------------------------------------------- /gtuser/mock/GET/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":1, 4 | "wid":777, 5 | "pid":193791, 6 | "tid":13350500, 7 | "billable":false, 8 | "start":"2013-02-27T01:24:00+00:00", 9 | "stop":"2013-02-27T07:24:00+00:00", 10 | "duration":21600, 11 | "description":"Some serious work", 12 | "tags":["billed"], 13 | "at":"2013-02-27T13:49:18+00:00" 14 | } 15 | } -------------------------------------------------------------------------------- /gtclient/mock/GET/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":1, 4 | "wid":777, 5 | "pid":193791, 6 | "tid":13350500, 7 | "billable":false, 8 | "start":"2013-02-27T01:24:00+00:00", 9 | "stop":"2013-02-27T07:24:00+00:00", 10 | "duration":21600, 11 | "description":"Some serious work", 12 | "tags":["billed"], 13 | "at":"2013-02-27T13:49:18+00:00" 14 | } 15 | } -------------------------------------------------------------------------------- /gtproject/mock/GET/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":1, 4 | "wid":777, 5 | "pid":193791, 6 | "tid":13350500, 7 | "billable":false, 8 | "start":"2013-02-27T01:24:00+00:00", 9 | "stop":"2013-02-27T07:24:00+00:00", 10 | "duration":21600, 11 | "description":"Some serious work", 12 | "tags":["billed"], 13 | "at":"2013-02-27T13:49:18+00:00" 14 | } 15 | } -------------------------------------------------------------------------------- /gttimentry/mock/GET/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":1, 4 | "wid":777, 5 | "pid":193791, 6 | "tid":13350500, 7 | "billable":false, 8 | "start":"2013-02-27T01:24:00+00:00", 9 | "stop":"2013-02-27T07:24:00+00:00", 10 | "duration":21600, 11 | "description":"Some serious work", 12 | "tags":["billed"], 13 | "at":"2013-02-27T13:49:18+00:00" 14 | } 15 | } -------------------------------------------------------------------------------- /gtworkspace/mock/GET/api/v8/time_entries/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":1, 4 | "wid":777, 5 | "pid":193791, 6 | "tid":13350500, 7 | "billable":false, 8 | "start":"2013-02-27T01:24:00+00:00", 9 | "stop":"2013-02-27T07:24:00+00:00", 10 | "duration":21600, 11 | "description":"Some serious work", 12 | "tags":["billed"], 13 | "at":"2013-02-27T13:49:18+00:00" 14 | } 15 | } -------------------------------------------------------------------------------- /gtclient/mock/PUT/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"new name", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":50, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":true, 11 | "rounding":1, 12 | "rounding_minutes":60, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gthttp/mock/PUT/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"new name", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":50, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":true, 11 | "rounding":1, 12 | "rounding_minutes":60, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gtproject/mock/PUT/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"new name", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":50, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":true, 11 | "rounding":1, 12 | "rounding_minutes":60, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gtuser/mock/PUT/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"new name", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":50, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":true, 11 | "rounding":1, 12 | "rounding_minutes":60, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gttimentry/mock/PUT/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"new name", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":50, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":true, 11 | "rounding":1, 12 | "rounding_minutes":60, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gtworkspace/mock/PUT/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"new name", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":50, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":true, 11 | "rounding":1, 12 | "rounding_minutes":60, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gthttp/mock/GET/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"John's personal ws", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":150, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":false, 11 | "rounding":1, 12 | "rounding_minutes":15, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gtuser/mock/GET/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"John's personal ws", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":150, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":false, 11 | "rounding":1, 12 | "rounding_minutes":15, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gtclient/mock/GET/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"John's personal ws", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":150, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":false, 11 | "rounding":1, 12 | "rounding_minutes":15, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gtproject/mock/GET/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"John's personal ws", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":150, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":false, 11 | "rounding":1, 12 | "rounding_minutes":15, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gttimentry/mock/GET/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"John's personal ws", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":150, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":false, 11 | "rounding":1, 12 | "rounding_minutes":15, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gtworkspace/mock/GET/api/v8/workspaces/1.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "name":"John's personal ws", 5 | "premium":true, 6 | "admin":true, 7 | "default_hourly_rate":150, 8 | "default_currency":"USD", 9 | "only_admins_may_create_projects":false, 10 | "only_admins_see_billable_rates":false, 11 | "rounding":1, 12 | "rounding_minutes":15, 13 | "at":"2013-08-28T16:22:21+03:00", 14 | "logo_url":"my_logo.png" 15 | } 16 | } -------------------------------------------------------------------------------- /gtclient/mock/POST/api/v8/signups.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":3, 4 | "api_token":"808lolae4eab897cce9729a53642124effe", 5 | "default_wid":983493, 6 | "email":"signup@blah.com", 7 | "fullname":"Test User", 8 | "jquery_timeofday_format":"", 9 | "jquery_date_format":"", 10 | "timeofday_format":"", 11 | "date_format":"", 12 | "store_start_and_stop_time":false, 13 | "beginning_of_week":0, 14 | "sidebar_piechart":false, 15 | "timeline_experiment":false, 16 | "new_blog_post":{}, 17 | "invitation":{} 18 | } 19 | } -------------------------------------------------------------------------------- /gthttp/mock/POST/api/v8/signups.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":3, 4 | "api_token":"808lolae4eab897cce9729a53642124effe", 5 | "default_wid":983493, 6 | "email":"signup@blah.com", 7 | "fullname":"Test User", 8 | "jquery_timeofday_format":"", 9 | "jquery_date_format":"", 10 | "timeofday_format":"", 11 | "date_format":"", 12 | "store_start_and_stop_time":false, 13 | "beginning_of_week":0, 14 | "sidebar_piechart":false, 15 | "timeline_experiment":false, 16 | "new_blog_post":{}, 17 | "invitation":{} 18 | } 19 | } -------------------------------------------------------------------------------- /gtuser/mock/POST/api/v8/signups.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":3, 4 | "api_token":"808lolae4eab897cce9729a53642124effe", 5 | "default_wid":983493, 6 | "email":"signup@blah.com", 7 | "fullname":"Test User", 8 | "jquery_timeofday_format":"", 9 | "jquery_date_format":"", 10 | "timeofday_format":"", 11 | "date_format":"", 12 | "store_start_and_stop_time":false, 13 | "beginning_of_week":0, 14 | "sidebar_piechart":false, 15 | "timeline_experiment":false, 16 | "new_blog_post":{}, 17 | "invitation":{} 18 | } 19 | } -------------------------------------------------------------------------------- /gtproject/mock/POST/api/v8/signups.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":3, 4 | "api_token":"808lolae4eab897cce9729a53642124effe", 5 | "default_wid":983493, 6 | "email":"signup@blah.com", 7 | "fullname":"Test User", 8 | "jquery_timeofday_format":"", 9 | "jquery_date_format":"", 10 | "timeofday_format":"", 11 | "date_format":"", 12 | "store_start_and_stop_time":false, 13 | "beginning_of_week":0, 14 | "sidebar_piechart":false, 15 | "timeline_experiment":false, 16 | "new_blog_post":{}, 17 | "invitation":{} 18 | } 19 | } -------------------------------------------------------------------------------- /gttimentry/mock/POST/api/v8/signups.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":3, 4 | "api_token":"808lolae4eab897cce9729a53642124effe", 5 | "default_wid":983493, 6 | "email":"signup@blah.com", 7 | "fullname":"Test User", 8 | "jquery_timeofday_format":"", 9 | "jquery_date_format":"", 10 | "timeofday_format":"", 11 | "date_format":"", 12 | "store_start_and_stop_time":false, 13 | "beginning_of_week":0, 14 | "sidebar_piechart":false, 15 | "timeline_experiment":false, 16 | "new_blog_post":{}, 17 | "invitation":{} 18 | } 19 | } -------------------------------------------------------------------------------- /gtworkspace/mock/POST/api/v8/signups.json: -------------------------------------------------------------------------------- 1 | { 2 | "data":{ 3 | "id":3, 4 | "api_token":"808lolae4eab897cce9729a53642124effe", 5 | "default_wid":983493, 6 | "email":"signup@blah.com", 7 | "fullname":"Test User", 8 | "jquery_timeofday_format":"", 9 | "jquery_date_format":"", 10 | "timeofday_format":"", 11 | "date_format":"", 12 | "store_start_and_stop_time":false, 13 | "beginning_of_week":0, 14 | "sidebar_piechart":false, 15 | "timeline_experiment":false, 16 | "new_blog_post":{}, 17 | "invitation":{} 18 | } 19 | } -------------------------------------------------------------------------------- /gtclient/mock/GET/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | }, 14 | { 15 | "id":2, 16 | "wid":777, 17 | "cid":123397, 18 | "name":"Id 2", 19 | "billable":false, 20 | "is_private":true, 21 | "active":true, 22 | "at":"2013-03-06T12:15:37+00:00", 23 | "template":true, 24 | "color": "5" 25 | } 26 | 27 | ] -------------------------------------------------------------------------------- /gthttp/mock/GET/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | }, 14 | { 15 | "id":2, 16 | "wid":777, 17 | "cid":123397, 18 | "name":"Id 2", 19 | "billable":false, 20 | "is_private":true, 21 | "active":true, 22 | "at":"2013-03-06T12:15:37+00:00", 23 | "template":true, 24 | "color": "5" 25 | } 26 | 27 | ] -------------------------------------------------------------------------------- /gtproject/mock/GET/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | }, 14 | { 15 | "id":2, 16 | "wid":777, 17 | "cid":123397, 18 | "name":"Id 2", 19 | "billable":false, 20 | "is_private":true, 21 | "active":true, 22 | "at":"2013-03-06T12:15:37+00:00", 23 | "template":true, 24 | "color": "5" 25 | } 26 | 27 | ] -------------------------------------------------------------------------------- /gttimentry/mock/GET/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | }, 14 | { 15 | "id":2, 16 | "wid":777, 17 | "cid":123397, 18 | "name":"Id 2", 19 | "billable":false, 20 | "is_private":true, 21 | "active":true, 22 | "at":"2013-03-06T12:15:37+00:00", 23 | "template":true, 24 | "color": "5" 25 | } 26 | 27 | ] -------------------------------------------------------------------------------- /gtuser/mock/GET/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | }, 14 | { 15 | "id":2, 16 | "wid":777, 17 | "cid":123397, 18 | "name":"Id 2", 19 | "billable":false, 20 | "is_private":true, 21 | "active":true, 22 | "at":"2013-03-06T12:15:37+00:00", 23 | "template":true, 24 | "color": "5" 25 | } 26 | 27 | ] -------------------------------------------------------------------------------- /gtworkspace/mock/GET/api/v8/projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "cid":123397, 6 | "name":"Id 1", 7 | "billable":false, 8 | "is_private":true, 9 | "active":true, 10 | "at":"2013-03-06T12:15:37+00:00", 11 | "template":true, 12 | "color": "5" 13 | }, 14 | { 15 | "id":2, 16 | "wid":777, 17 | "cid":123397, 18 | "name":"Id 2", 19 | "billable":false, 20 | "is_private":true, 21 | "active":true, 22 | "at":"2013-03-06T12:15:37+00:00", 23 | "template":true, 24 | "color": "5" 25 | } 26 | 27 | ] -------------------------------------------------------------------------------- /gthttp/mock/GET/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "pid":123, 6 | "billable":true, 7 | "start":"2013-03-11T11:36:00+00:00", 8 | "stop":"2013-03-11T15:36:00+00:00", 9 | "duration":14400, 10 | "description":"Meeting with the client", 11 | "tags":[""], 12 | "at":"2013-03-11T15:36:58+00:00" 13 | },{ 14 | "id":2, 15 | "wid":777, 16 | "billable":false, 17 | "start":"2013-03-12T10:32:43+00:00", 18 | "stop":"2013-03-12T14:32:43+00:00", 19 | "duration":18400, 20 | "description":"important work", 21 | "tags":[""], 22 | "at":"2013-03-12T14:32:43+00:00" 23 | } 24 | ] -------------------------------------------------------------------------------- /gtuser/mock/GET/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "pid":123, 6 | "billable":true, 7 | "start":"2013-03-11T11:36:00+00:00", 8 | "stop":"2013-03-11T15:36:00+00:00", 9 | "duration":14400, 10 | "description":"Meeting with the client", 11 | "tags":[""], 12 | "at":"2013-03-11T15:36:58+00:00" 13 | },{ 14 | "id":2, 15 | "wid":777, 16 | "billable":false, 17 | "start":"2013-03-12T10:32:43+00:00", 18 | "stop":"2013-03-12T14:32:43+00:00", 19 | "duration":18400, 20 | "description":"important work", 21 | "tags":[""], 22 | "at":"2013-03-12T14:32:43+00:00" 23 | } 24 | ] -------------------------------------------------------------------------------- /gtclient/mock/GET/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "pid":123, 6 | "billable":true, 7 | "start":"2013-03-11T11:36:00+00:00", 8 | "stop":"2013-03-11T15:36:00+00:00", 9 | "duration":14400, 10 | "description":"Meeting with the client", 11 | "tags":[""], 12 | "at":"2013-03-11T15:36:58+00:00" 13 | },{ 14 | "id":2, 15 | "wid":777, 16 | "billable":false, 17 | "start":"2013-03-12T10:32:43+00:00", 18 | "stop":"2013-03-12T14:32:43+00:00", 19 | "duration":18400, 20 | "description":"important work", 21 | "tags":[""], 22 | "at":"2013-03-12T14:32:43+00:00" 23 | } 24 | ] -------------------------------------------------------------------------------- /gtproject/mock/GET/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "pid":123, 6 | "billable":true, 7 | "start":"2013-03-11T11:36:00+00:00", 8 | "stop":"2013-03-11T15:36:00+00:00", 9 | "duration":14400, 10 | "description":"Meeting with the client", 11 | "tags":[""], 12 | "at":"2013-03-11T15:36:58+00:00" 13 | },{ 14 | "id":2, 15 | "wid":777, 16 | "billable":false, 17 | "start":"2013-03-12T10:32:43+00:00", 18 | "stop":"2013-03-12T14:32:43+00:00", 19 | "duration":18400, 20 | "description":"important work", 21 | "tags":[""], 22 | "at":"2013-03-12T14:32:43+00:00" 23 | } 24 | ] -------------------------------------------------------------------------------- /gttimentry/mock/GET/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "pid":123, 6 | "billable":true, 7 | "start":"2013-03-11T11:36:00+00:00", 8 | "stop":"2013-03-11T15:36:00+00:00", 9 | "duration":14400, 10 | "description":"Meeting with the client", 11 | "tags":[""], 12 | "at":"2013-03-11T15:36:58+00:00" 13 | },{ 14 | "id":2, 15 | "wid":777, 16 | "billable":false, 17 | "start":"2013-03-12T10:32:43+00:00", 18 | "stop":"2013-03-12T14:32:43+00:00", 19 | "duration":18400, 20 | "description":"important work", 21 | "tags":[""], 22 | "at":"2013-03-12T14:32:43+00:00" 23 | } 24 | ] -------------------------------------------------------------------------------- /gtworkspace/mock/GET/api/v8/time_entries.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "wid":777, 5 | "pid":123, 6 | "billable":true, 7 | "start":"2013-03-11T11:36:00+00:00", 8 | "stop":"2013-03-11T15:36:00+00:00", 9 | "duration":14400, 10 | "description":"Meeting with the client", 11 | "tags":[""], 12 | "at":"2013-03-11T15:36:58+00:00" 13 | },{ 14 | "id":2, 15 | "wid":777, 16 | "billable":false, 17 | "start":"2013-03-12T10:32:43+00:00", 18 | "stop":"2013-03-12T14:32:43+00:00", 19 | "duration":18400, 20 | "description":"important work", 21 | "tags":[""], 22 | "at":"2013-03-12T14:32:43+00:00" 23 | } 24 | ] -------------------------------------------------------------------------------- /gttimentry/mock/GET/api/v8/time_entries_with_parameters.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 4, 4 | "wid": 777, 5 | "pid": 123, 6 | "billable": true, 7 | "start": "2013-03-11T11:36:00+00:00", 8 | "stop": "2013-03-11T15:36:00+00:00", 9 | "duration": 14400, 10 | "description": "Meeting with the client", 11 | "tags": [ 12 | "" 13 | ], 14 | "at": "2013-03-11T15:36:58+00:00" 15 | }, 16 | { 17 | "id": 5, 18 | "wid": 777, 19 | "billable": false, 20 | "start": "2013-03-12T10:32:43+00:00", 21 | "stop": "2013-03-12T14:32:43+00:00", 22 | "duration": 18400, 23 | "description": "important work", 24 | "tags": [ 25 | "" 26 | ], 27 | "at": "2013-03-12T14:32:43+00:00" 28 | } 29 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cover.out 2 | *.orig 3 | gtoggl/gtoggl 4 | 5 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 6 | 7 | ## Directory-based project format 8 | .idea/ 9 | # if you remove the above rule, at least ignore user-specific stuff: 10 | # .idea/workspace.xml 11 | # .idea/tasks.xml 12 | # and these sensitive or high-churn files: 13 | # .idea/dataSources.ids 14 | # .idea/dataSources.xml 15 | # .idea/sqlDataSources.xml 16 | # .idea/dynamic.xml 17 | 18 | ## File-based project format 19 | *.ipr 20 | *.iws 21 | *.iml 22 | 23 | ## Additional for IntelliJ 24 | out/ 25 | 26 | #Eclipse 27 | 28 | .classpath 29 | .settings 30 | .project 31 | .metadata 32 | bin/ 33 | 34 | # generated by mpeltonen/sbt-idea plugin 35 | .idea_modules/ 36 | 37 | # generated by emacs 38 | *~ 39 | \#*\# 40 | 41 | #generated by the build process for sample-projects 42 | .repo/ 43 | 44 | 45 | .DS_Store 46 | -------------------------------------------------------------------------------- /.readme.yaml: -------------------------------------------------------------------------------- 1 | title: 2 | name: Toggl API for golang 3 | description: "Throttle API for [toggle](https://github.com/toggl/toggl_api_docs/blob/master/toggl_api.md)" 4 | user: dougEfresh 5 | project: gtoggl-api 6 | installation: >- 7 | ```shell 8 | 9 | $ go get -u github.com/dougEfresh/gtoggl-api 10 | 11 | ``` 12 | quickStart: 13 | code: | 14 | ```go 15 | import "github.com/dougEfresh/gtoggl" 16 | import "github.com/dougEfresh/gtoggl-api/gtproject" 17 | 18 | func main() { 19 | thc, err := gtoggl.NewClient("token") 20 | ... 21 | tc, err := gtproject.NewClient(thc) 22 | ... 23 | project,err := tc.Get(1) 24 | if err == nil { 25 | panic(err) 26 | } 27 | } 28 | ``` 29 | description: "The gtoggl clients provides throttling" 30 | examples: 31 | - "See [godoc][doc] for more examples" 32 | tests: 33 | - | 34 | ```shell 35 | $ go test -v ./... 36 | 37 | ``` 38 | -------------------------------------------------------------------------------- /gthttp/mock/PUT/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 5 | "default_wid":777, 6 | "email":"newemail@swift.com", 7 | "fullname":"John Swift", 8 | "jquery_timeofday_format":"h:i A", 9 | "jquery_date_format":"m/d/Y", 10 | "timeofday_format":"h:mm A", 11 | "date_format":"MM/DD/YYYY", 12 | "store_start_and_stop_time":true, 13 | "beginning_of_week":0, 14 | "language":"en_US", 15 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 16 | "sidebar_piechart":false, 17 | "at":"2013-03-06T12:18:42+00:00", 18 | "retention":9, 19 | "record_timeline":true, 20 | "render_timeline":true, 21 | "timeline_enabled":true, 22 | "timeline_experiment":true, 23 | "new_blog_post":{}, 24 | "invitation":{} 25 | } 26 | } -------------------------------------------------------------------------------- /gtuser/mock/PUT/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 5 | "default_wid":777, 6 | "email":"newemail@swift.com", 7 | "fullname":"John Swift", 8 | "jquery_timeofday_format":"h:i A", 9 | "jquery_date_format":"m/d/Y", 10 | "timeofday_format":"h:mm A", 11 | "date_format":"MM/DD/YYYY", 12 | "store_start_and_stop_time":true, 13 | "beginning_of_week":0, 14 | "language":"en_US", 15 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 16 | "sidebar_piechart":false, 17 | "at":"2013-03-06T12:18:42+00:00", 18 | "retention":9, 19 | "record_timeline":true, 20 | "render_timeline":true, 21 | "timeline_enabled":true, 22 | "timeline_experiment":true, 23 | "new_blog_post":{}, 24 | "invitation":{} 25 | } 26 | } -------------------------------------------------------------------------------- /gtclient/mock/PUT/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 5 | "default_wid":777, 6 | "email":"newemail@swift.com", 7 | "fullname":"John Swift", 8 | "jquery_timeofday_format":"h:i A", 9 | "jquery_date_format":"m/d/Y", 10 | "timeofday_format":"h:mm A", 11 | "date_format":"MM/DD/YYYY", 12 | "store_start_and_stop_time":true, 13 | "beginning_of_week":0, 14 | "language":"en_US", 15 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 16 | "sidebar_piechart":false, 17 | "at":"2013-03-06T12:18:42+00:00", 18 | "retention":9, 19 | "record_timeline":true, 20 | "render_timeline":true, 21 | "timeline_enabled":true, 22 | "timeline_experiment":true, 23 | "new_blog_post":{}, 24 | "invitation":{} 25 | } 26 | } -------------------------------------------------------------------------------- /gtproject/mock/PUT/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 5 | "default_wid":777, 6 | "email":"newemail@swift.com", 7 | "fullname":"John Swift", 8 | "jquery_timeofday_format":"h:i A", 9 | "jquery_date_format":"m/d/Y", 10 | "timeofday_format":"h:mm A", 11 | "date_format":"MM/DD/YYYY", 12 | "store_start_and_stop_time":true, 13 | "beginning_of_week":0, 14 | "language":"en_US", 15 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 16 | "sidebar_piechart":false, 17 | "at":"2013-03-06T12:18:42+00:00", 18 | "retention":9, 19 | "record_timeline":true, 20 | "render_timeline":true, 21 | "timeline_enabled":true, 22 | "timeline_experiment":true, 23 | "new_blog_post":{}, 24 | "invitation":{} 25 | } 26 | } -------------------------------------------------------------------------------- /gttimentry/mock/PUT/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 5 | "default_wid":777, 6 | "email":"newemail@swift.com", 7 | "fullname":"John Swift", 8 | "jquery_timeofday_format":"h:i A", 9 | "jquery_date_format":"m/d/Y", 10 | "timeofday_format":"h:mm A", 11 | "date_format":"MM/DD/YYYY", 12 | "store_start_and_stop_time":true, 13 | "beginning_of_week":0, 14 | "language":"en_US", 15 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 16 | "sidebar_piechart":false, 17 | "at":"2013-03-06T12:18:42+00:00", 18 | "retention":9, 19 | "record_timeline":true, 20 | "render_timeline":true, 21 | "timeline_enabled":true, 22 | "timeline_experiment":true, 23 | "new_blog_post":{}, 24 | "invitation":{} 25 | } 26 | } -------------------------------------------------------------------------------- /gtworkspace/mock/PUT/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "id":1, 4 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 5 | "default_wid":777, 6 | "email":"newemail@swift.com", 7 | "fullname":"John Swift", 8 | "jquery_timeofday_format":"h:i A", 9 | "jquery_date_format":"m/d/Y", 10 | "timeofday_format":"h:mm A", 11 | "date_format":"MM/DD/YYYY", 12 | "store_start_and_stop_time":true, 13 | "beginning_of_week":0, 14 | "language":"en_US", 15 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 16 | "sidebar_piechart":false, 17 | "at":"2013-03-06T12:18:42+00:00", 18 | "retention":9, 19 | "record_timeline":true, 20 | "render_timeline":true, 21 | "timeline_enabled":true, 22 | "timeline_experiment":true, 23 | "new_blog_post":{}, 24 | "invitation":{} 25 | } 26 | } -------------------------------------------------------------------------------- /gthttp/mock/GET/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "since":1362575771, 3 | "data": { 4 | "id":1, 5 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 6 | "default_wid":777, 7 | "email":"johnt@swift.com", 8 | "fullname":"John Swift", 9 | "jquery_timeofday_format":"h:i A", 10 | "jquery_date_format":"m/d/Y", 11 | "timeofday_format":"h:mm A", 12 | "date_format":"MM/DD/YYYY", 13 | "store_start_and_stop_time":true, 14 | "beginning_of_week":0, 15 | "language":"en_US", 16 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 17 | "sidebar_piechart":false, 18 | "at":"2013-03-06T12:18:42+00:00", 19 | "retention":9, 20 | "record_timeline":true, 21 | "render_timeline":true, 22 | "timeline_enabled":true, 23 | "timeline_experiment":true, 24 | "new_blog_post":{}, 25 | "invitation":{} 26 | } 27 | } -------------------------------------------------------------------------------- /gtuser/mock/GET/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "since":1362575771, 3 | "data": { 4 | "id":1, 5 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 6 | "default_wid":777, 7 | "email":"johnt@swift.com", 8 | "fullname":"John Swift", 9 | "jquery_timeofday_format":"h:i A", 10 | "jquery_date_format":"m/d/Y", 11 | "timeofday_format":"h:mm A", 12 | "date_format":"MM/DD/YYYY", 13 | "store_start_and_stop_time":true, 14 | "beginning_of_week":0, 15 | "language":"en_US", 16 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 17 | "sidebar_piechart":false, 18 | "at":"2013-03-06T12:18:42+00:00", 19 | "retention":9, 20 | "record_timeline":true, 21 | "render_timeline":true, 22 | "timeline_enabled":true, 23 | "timeline_experiment":true, 24 | "new_blog_post":{}, 25 | "invitation":{} 26 | } 27 | } -------------------------------------------------------------------------------- /gtclient/mock/GET/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "since":1362575771, 3 | "data": { 4 | "id":1, 5 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 6 | "default_wid":777, 7 | "email":"johnt@swift.com", 8 | "fullname":"John Swift", 9 | "jquery_timeofday_format":"h:i A", 10 | "jquery_date_format":"m/d/Y", 11 | "timeofday_format":"h:mm A", 12 | "date_format":"MM/DD/YYYY", 13 | "store_start_and_stop_time":true, 14 | "beginning_of_week":0, 15 | "language":"en_US", 16 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 17 | "sidebar_piechart":false, 18 | "at":"2013-03-06T12:18:42+00:00", 19 | "retention":9, 20 | "record_timeline":true, 21 | "render_timeline":true, 22 | "timeline_enabled":true, 23 | "timeline_experiment":true, 24 | "new_blog_post":{}, 25 | "invitation":{} 26 | } 27 | } -------------------------------------------------------------------------------- /gtproject/mock/GET/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "since":1362575771, 3 | "data": { 4 | "id":1, 5 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 6 | "default_wid":777, 7 | "email":"johnt@swift.com", 8 | "fullname":"John Swift", 9 | "jquery_timeofday_format":"h:i A", 10 | "jquery_date_format":"m/d/Y", 11 | "timeofday_format":"h:mm A", 12 | "date_format":"MM/DD/YYYY", 13 | "store_start_and_stop_time":true, 14 | "beginning_of_week":0, 15 | "language":"en_US", 16 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 17 | "sidebar_piechart":false, 18 | "at":"2013-03-06T12:18:42+00:00", 19 | "retention":9, 20 | "record_timeline":true, 21 | "render_timeline":true, 22 | "timeline_enabled":true, 23 | "timeline_experiment":true, 24 | "new_blog_post":{}, 25 | "invitation":{} 26 | } 27 | } -------------------------------------------------------------------------------- /gttimentry/mock/GET/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "since":1362575771, 3 | "data": { 4 | "id":1, 5 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 6 | "default_wid":777, 7 | "email":"johnt@swift.com", 8 | "fullname":"John Swift", 9 | "jquery_timeofday_format":"h:i A", 10 | "jquery_date_format":"m/d/Y", 11 | "timeofday_format":"h:mm A", 12 | "date_format":"MM/DD/YYYY", 13 | "store_start_and_stop_time":true, 14 | "beginning_of_week":0, 15 | "language":"en_US", 16 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 17 | "sidebar_piechart":false, 18 | "at":"2013-03-06T12:18:42+00:00", 19 | "retention":9, 20 | "record_timeline":true, 21 | "render_timeline":true, 22 | "timeline_enabled":true, 23 | "timeline_experiment":true, 24 | "new_blog_post":{}, 25 | "invitation":{} 26 | } 27 | } -------------------------------------------------------------------------------- /gtworkspace/mock/GET/api/v8/me.json: -------------------------------------------------------------------------------- 1 | { 2 | "since":1362575771, 3 | "data": { 4 | "id":1, 5 | "api_token":"1971800d4d82861d8f2c1651fea4d212", 6 | "default_wid":777, 7 | "email":"johnt@swift.com", 8 | "fullname":"John Swift", 9 | "jquery_timeofday_format":"h:i A", 10 | "jquery_date_format":"m/d/Y", 11 | "timeofday_format":"h:mm A", 12 | "date_format":"MM/DD/YYYY", 13 | "store_start_and_stop_time":true, 14 | "beginning_of_week":0, 15 | "language":"en_US", 16 | "image_url":"https://www.toggl.com/system/avatars/9000/small/open-uri20121116-2767-b1qr8l.png", 17 | "sidebar_piechart":false, 18 | "at":"2013-03-06T12:18:42+00:00", 19 | "retention":9, 20 | "record_timeline":true, 21 | "render_timeline":true, 22 | "timeline_enabled":true, 23 | "timeline_experiment":true, 24 | "new_blog_post":{}, 25 | "invitation":{} 26 | } 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright © 2015-2016 Douglas Chimento 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the “Software”), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included 12 | in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /gthttp/logger.go: -------------------------------------------------------------------------------- 1 | package gthttp 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httputil" 6 | ) 7 | 8 | // Logger specifies the interface for all log operations. 9 | type Logger interface { 10 | Printf(format string, v ...interface{}) 11 | } 12 | 13 | // errorf logs to the error log. 14 | func (c *TogglHttpClient) errorf(format string, args ...interface{}) { 15 | if c.errorLog != nil { 16 | c.errorLog.Printf(format, args...) 17 | } 18 | } 19 | 20 | // infof logs informational messages. 21 | func (c *TogglHttpClient) infof(format string, args ...interface{}) { 22 | if c.infoLog != nil { 23 | c.infoLog.Printf(format, args...) 24 | } 25 | } 26 | 27 | // tracef logs to the trace log. 28 | func (c *TogglHttpClient) tracef(format string, args ...interface{}) { 29 | if c.traceLog != nil { 30 | c.traceLog.Printf(format, args...) 31 | } 32 | } 33 | 34 | // dumpRequest dumps the given HTTP request to the trace log. 35 | func (c *TogglHttpClient) dumpRequest(r *http.Request) { 36 | if c.traceLog != nil { 37 | out, err := httputil.DumpRequestOut(r, true) 38 | if err == nil { 39 | c.tracef("\n\n%s\n", string(out)) 40 | } 41 | } 42 | 43 | } 44 | 45 | // dumpResponse dumps the given HTTP response to the trace log. 46 | func (c *TogglHttpClient) dumpResponse(resp *http.Response) { 47 | if c.traceLog != nil { 48 | out, err := httputil.DumpResponse(resp, true) 49 | if err == nil { 50 | c.tracef("\n\n%s\n", string(out)) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /readme-template.md: -------------------------------------------------------------------------------- 1 | # {{ .title.name }} 2 | 3 | {{ .title.description }} 4 | 5 | [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report][report-img]][report] 6 | 7 | ## Installation 8 | {{ .installation }} 9 | 10 | ## Quick Start 11 | 12 | {{ .quickStart.code}} 13 | 14 | {{ .quickStart.description }} 15 | 16 | ## Usage 17 | 18 | {{ .usage }} 19 | 20 | ## Examples 21 | 22 | {{- range .examples }} 23 | 24 | {{.}} 25 | 26 | {{- end }} 27 | 28 | 29 | ## Prerequisites 30 | 31 | go 1.x 32 | 33 | ## Tests 34 | 35 | {{- range .tests }} 36 | 37 | {{.}} 38 | 39 | {{- end }} 40 | 41 | ## Deployment 42 | 43 | ## Contributing 44 | All PRs are welcome 45 | 46 | ## Authors 47 | 48 | * **Douglas Chimento** - [{{.user}}][me] 49 | 50 | ## License 51 | 52 | This project is licensed under the Apache License - see the [LICENSE](LICENSE) file for details 53 | 54 | ## Acknowledgments 55 | 56 | ### TODO 57 | 58 | [doc-img]: https://godoc.org/github.com/{{.user}}/{{.project}}?status.svg 59 | [doc]: https://godoc.org/github.com/{{.user}}/{{.project}} 60 | [ci-img]: https://travis-ci.org/{{.user}}/{{.project}}.svg?branch=master 61 | [ci]: https://travis-ci.org/{{.user}}/{{.project}} 62 | [cov-img]: https://codecov.io/gh/{{.user}}/{{.project}}/branch/master/graph/badge.svg 63 | [cov]: https://codecov.io/gh/{{.user}}/{{.project}} 64 | [glide.lock]: https://github.com/uber-go/zap/blob/master/glide.lock 65 | [zap]: https://github.com/uber-go/zap 66 | [me]: https://github.com/{{.user}} 67 | [report-img]: https://goreportcard.com/badge/github.com/{{.user}}/{{.project}} 68 | [report]: https://goreportcard.com/report/github.com/{{.user}}/{{.project}} -------------------------------------------------------------------------------- /gtworkspace/workspace_test.go: -------------------------------------------------------------------------------- 1 | package gtworkspace 2 | 3 | import ( 4 | "github.com/dougEfresh/gtoggl-api/test" 5 | "testing" 6 | ) 7 | 8 | func workspaceClient(t *testing.T) *WorkspaceClient { 9 | tu := >test.TestUtil{} 10 | client := tu.MockClient(t) 11 | return NewClient(client) 12 | } 13 | 14 | func TestWorkspaceList(t *testing.T) { 15 | workspaceClient := workspaceClient(t) 16 | workspaces, err := workspaceClient.List() 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | if len(workspaces) != 2 { 21 | t.Fatal("Workspace is not 2") 22 | } 23 | if workspaces[0].Id != 1 { 24 | t.Error("Workspace Id is not 1") 25 | } 26 | if workspaces[0].Name != "Id 1" { 27 | t.Error("Workspace name not Id ") 28 | } 29 | if !workspaces[0].Premium { 30 | t.Error("Workspace is not premium ") 31 | } 32 | 33 | if workspaces[1].Id != 2 { 34 | t.Error("Workspace Id is not 2") 35 | } 36 | if workspaces[1].Name != "Id 2" { 37 | t.Error("Workspace name") 38 | } 39 | if workspaces[1].Premium { 40 | t.Error("Workspace is not premium ") 41 | } 42 | 43 | } 44 | 45 | func TestWorkspaceGet(t *testing.T) { 46 | workspaceClient := workspaceClient(t) 47 | 48 | workspace, err := workspaceClient.Get(1) 49 | if err != nil { 50 | t.Fatal(err) 51 | } 52 | if workspace.Id != 1 { 53 | t.Error("Workspace id != 1") 54 | } 55 | } 56 | 57 | func TestWorkspaceUpdate(t *testing.T) { 58 | workspaceClient := workspaceClient(t) 59 | workspace, err := workspaceClient.Get(1) 60 | 61 | if err != nil { 62 | t.Error(err) 63 | } 64 | workspace.Name = "new name" 65 | workspace, err = workspaceClient.Update(workspace) 66 | 67 | if err != nil { 68 | t.Error(err) 69 | } 70 | 71 | if workspace.Name != "new name" { 72 | t.Error("Wrong name") 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /gtuser/user_test.go: -------------------------------------------------------------------------------- 1 | package gtuser 2 | 3 | import ( 4 | "github.com/dougEfresh/gtoggl-api/test" 5 | "testing" 6 | ) 7 | 8 | func togglClient(t *testing.T) *UserClient { 9 | tu := >test.TestUtil{} 10 | client := tu.MockClient(t) 11 | return NewClient(client) 12 | } 13 | 14 | func TestUserCreate(t *testing.T) { 15 | tClient := togglClient(t) 16 | nc, err := tClient.Create("signup@blah.com", "StrongPasswrod", "UTC") 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | if nc.Id != 3 { 22 | t.Fatal("!= 3") 23 | } 24 | 25 | if nc.Email != "signup@blah.com" { 26 | t.Fatal("!= signup@blah.com") 27 | } 28 | 29 | if nc.ApiToken != "808lolae4eab897cce9729a53642124effe" { 30 | t.Fatal("!= 808lolae4eab897cce9729a53642124effe") 31 | } 32 | } 33 | 34 | func TestUserUpdate(t *testing.T) { 35 | tClient := togglClient(t) 36 | c := &User{Id: 1, FullName: "John Swift", Email: "newemail@swift.com"} 37 | nc, err := tClient.Update(c) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | if nc.Email != "newemail@swift.com" { 43 | t.Fatal("!= newemail@swift.com") 44 | } 45 | } 46 | 47 | func TestUserReset(t *testing.T) { 48 | tClient := togglClient(t) 49 | token, err := tClient.ResetToken() 50 | if err != nil { 51 | t.Fatal(err) 52 | } 53 | 54 | if token != "123456789" { 55 | t.Fatal("!= 123456789") 56 | } 57 | } 58 | 59 | func TestUserGet(t *testing.T) { 60 | tClient := togglClient(t) 61 | 62 | client, err := tClient.Get(false) 63 | if err != nil { 64 | t.Fatal(err) 65 | } 66 | if client.Id != 1 { 67 | t.Error("!= 1") 68 | } 69 | 70 | if client.FullName != "John Swift" { 71 | t.Error("!= John Swift: " + client.FullName) 72 | } 73 | 74 | if client.ApiToken != "1971800d4d82861d8f2c1651fea4d212" { 75 | t.Error("!= J1971800d4d82861d8f2c1651fea4d212: " + client.ApiToken) 76 | } 77 | 78 | if client.Email != "johnt@swift.com" { 79 | t.Error("!= johnt@swift.com" + client.Email) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /gtclient/client_test.go: -------------------------------------------------------------------------------- 1 | package gtclient 2 | 3 | import ( 4 | "github.com/dougEfresh/gtoggl-api/test" 5 | "testing" 6 | ) 7 | 8 | func togglClient(t *testing.T) *TClient { 9 | tu := >test.TestUtil{} 10 | return NewClient(tu.MockClient(t)) 11 | } 12 | 13 | func TestClientCreate(t *testing.T) { 14 | tClient := togglClient(t) 15 | c := &Client{Name: "Very Big Company", WId: 777} 16 | nc, err := tClient.Create(c) 17 | if err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | if nc.Name != "Very Big Company" { 22 | t.Fatal("!= Very Big Company") 23 | } 24 | 25 | if nc.Id != 1239455 { 26 | t.Fatal("!= 1239455") 27 | } 28 | 29 | if nc.WId != 777 { 30 | t.Fatal("!= 777") 31 | } 32 | } 33 | 34 | func TestClientUpdate(t *testing.T) { 35 | tClient := togglClient(t) 36 | c := &Client{Id: 1, Name: "new name", WId: 777} 37 | nc, err := tClient.Update(c) 38 | if err != nil { 39 | t.Fatal(err) 40 | } 41 | 42 | if nc.Name != "new name" { 43 | t.Fatal("!= new name") 44 | } 45 | } 46 | 47 | func TestClientDelete(t *testing.T) { 48 | tClient := togglClient(t) 49 | c := &Client{Id: 1, Name: "new name", WId: 777} 50 | err := tClient.Delete(c.Id) 51 | if err != nil { 52 | t.Fatal(err) 53 | } 54 | } 55 | 56 | func TestClientList(t *testing.T) { 57 | tClient := togglClient(t) 58 | clients, err := tClient.List() 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | if len(clients) != 2 { 63 | t.Fatal("Workspace is not 2") 64 | } 65 | if clients[0].Id != 1 { 66 | t.Error("Workspace Id is not 1") 67 | } 68 | if clients[0].Name != "Id 1" { 69 | t.Error("Workspace name not Id ") 70 | } 71 | 72 | if clients[1].Id != 2 { 73 | t.Error("Workspace Id is not 2") 74 | } 75 | if clients[1].Name != "Id 2" { 76 | t.Error("Workspace name") 77 | } 78 | } 79 | 80 | func TestClientGet(t *testing.T) { 81 | tClient := togglClient(t) 82 | 83 | client, err := tClient.Get(1) 84 | if err != nil { 85 | t.Fatal(err) 86 | } 87 | if client.Id != 1 { 88 | t.Error("!= 1") 89 | } 90 | 91 | if client.Name != "Id 1" { 92 | t.Error("!= Id 1: " + client.Name) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /gthttp/mock/GET/api/v8/workspaces.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Id 1", 5 | "profile": 13, 6 | "premium": true, 7 | "admin": true, 8 | "default_hourly_rate": 0, 9 | "default_currency": "USD", 10 | "only_admins_may_create_projects": false, 11 | "only_admins_see_billable_rates": false, 12 | "only_admins_see_team_dashboard": false, 13 | "projects_billable_by_default": false, 14 | "rounding": 0, 15 | "rounding_minutes": 0, 16 | "api_token": "7d475340128f8ac760a532c2c1d60998", 17 | "at": "2016-01-27T23:18:51+00:00", 18 | "logo_url": "https://assets.toggl.com/logos/be108a1b6e916c0b6880b8b56aeef235.png", 19 | "ical_url": "/ical/workspace_user/8138a50b0c407841b5a9b706eeee01a7", 20 | "ical_enabled": true, 21 | "subscription": { 22 | "workspace_id": 0, 23 | "deleted_at": null, 24 | "created_at": "0001-01-01T00:00:00Z", 25 | "updated_at": null, 26 | "vat_valid": false, 27 | "vat_validated_at": null, 28 | "vat_invalid_accepted_at": null, 29 | "vat_invalid_accepted_by": null, 30 | "description": "Old Pro monthly", 31 | "vat_applicable": false 32 | } 33 | }, 34 | { 35 | "id": 2, 36 | "name": "Id 2", 37 | "profile": 0, 38 | "premium": false, 39 | "admin": false, 40 | "default_hourly_rate": 0, 41 | "default_currency": "USD", 42 | "only_admins_may_create_projects": false, 43 | "only_admins_see_billable_rates": false, 44 | "only_admins_see_team_dashboard": false, 45 | "projects_billable_by_default": true, 46 | "rounding": 1, 47 | "rounding_minutes": 0, 48 | "at": "2016-01-29T16:27:18+00:00", 49 | "ical_enabled": true, 50 | "subscription": { 51 | "workspace_id": 0, 52 | "deleted_at": null, 53 | "created_at": "0001-01-01T00:00:00Z", 54 | "updated_at": null, 55 | "vat_valid": false, 56 | "vat_validated_at": null, 57 | "vat_invalid_accepted_at": null, 58 | "vat_invalid_accepted_by": null, 59 | "description": "Free", 60 | "vat_applicable": false 61 | } 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /gtuser/mock/GET/api/v8/workspaces.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Id 1", 5 | "profile": 13, 6 | "premium": true, 7 | "admin": true, 8 | "default_hourly_rate": 0, 9 | "default_currency": "USD", 10 | "only_admins_may_create_projects": false, 11 | "only_admins_see_billable_rates": false, 12 | "only_admins_see_team_dashboard": false, 13 | "projects_billable_by_default": false, 14 | "rounding": 0, 15 | "rounding_minutes": 0, 16 | "api_token": "7d475340128f8ac760a532c2c1d60998", 17 | "at": "2016-01-27T23:18:51+00:00", 18 | "logo_url": "https://assets.toggl.com/logos/be108a1b6e916c0b6880b8b56aeef235.png", 19 | "ical_url": "/ical/workspace_user/8138a50b0c407841b5a9b706eeee01a7", 20 | "ical_enabled": true, 21 | "subscription": { 22 | "workspace_id": 0, 23 | "deleted_at": null, 24 | "created_at": "0001-01-01T00:00:00Z", 25 | "updated_at": null, 26 | "vat_valid": false, 27 | "vat_validated_at": null, 28 | "vat_invalid_accepted_at": null, 29 | "vat_invalid_accepted_by": null, 30 | "description": "Old Pro monthly", 31 | "vat_applicable": false 32 | } 33 | }, 34 | { 35 | "id": 2, 36 | "name": "Id 2", 37 | "profile": 0, 38 | "premium": false, 39 | "admin": false, 40 | "default_hourly_rate": 0, 41 | "default_currency": "USD", 42 | "only_admins_may_create_projects": false, 43 | "only_admins_see_billable_rates": false, 44 | "only_admins_see_team_dashboard": false, 45 | "projects_billable_by_default": true, 46 | "rounding": 1, 47 | "rounding_minutes": 0, 48 | "at": "2016-01-29T16:27:18+00:00", 49 | "ical_enabled": true, 50 | "subscription": { 51 | "workspace_id": 0, 52 | "deleted_at": null, 53 | "created_at": "0001-01-01T00:00:00Z", 54 | "updated_at": null, 55 | "vat_valid": false, 56 | "vat_validated_at": null, 57 | "vat_invalid_accepted_at": null, 58 | "vat_invalid_accepted_by": null, 59 | "description": "Free", 60 | "vat_applicable": false 61 | } 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /gtclient/mock/GET/api/v8/workspaces.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Id 1", 5 | "profile": 13, 6 | "premium": true, 7 | "admin": true, 8 | "default_hourly_rate": 0, 9 | "default_currency": "USD", 10 | "only_admins_may_create_projects": false, 11 | "only_admins_see_billable_rates": false, 12 | "only_admins_see_team_dashboard": false, 13 | "projects_billable_by_default": false, 14 | "rounding": 0, 15 | "rounding_minutes": 0, 16 | "api_token": "7d475340128f8ac760a532c2c1d60998", 17 | "at": "2016-01-27T23:18:51+00:00", 18 | "logo_url": "https://assets.toggl.com/logos/be108a1b6e916c0b6880b8b56aeef235.png", 19 | "ical_url": "/ical/workspace_user/8138a50b0c407841b5a9b706eeee01a7", 20 | "ical_enabled": true, 21 | "subscription": { 22 | "workspace_id": 0, 23 | "deleted_at": null, 24 | "created_at": "0001-01-01T00:00:00Z", 25 | "updated_at": null, 26 | "vat_valid": false, 27 | "vat_validated_at": null, 28 | "vat_invalid_accepted_at": null, 29 | "vat_invalid_accepted_by": null, 30 | "description": "Old Pro monthly", 31 | "vat_applicable": false 32 | } 33 | }, 34 | { 35 | "id": 2, 36 | "name": "Id 2", 37 | "profile": 0, 38 | "premium": false, 39 | "admin": false, 40 | "default_hourly_rate": 0, 41 | "default_currency": "USD", 42 | "only_admins_may_create_projects": false, 43 | "only_admins_see_billable_rates": false, 44 | "only_admins_see_team_dashboard": false, 45 | "projects_billable_by_default": true, 46 | "rounding": 1, 47 | "rounding_minutes": 0, 48 | "at": "2016-01-29T16:27:18+00:00", 49 | "ical_enabled": true, 50 | "subscription": { 51 | "workspace_id": 0, 52 | "deleted_at": null, 53 | "created_at": "0001-01-01T00:00:00Z", 54 | "updated_at": null, 55 | "vat_valid": false, 56 | "vat_validated_at": null, 57 | "vat_invalid_accepted_at": null, 58 | "vat_invalid_accepted_by": null, 59 | "description": "Free", 60 | "vat_applicable": false 61 | } 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /gtproject/mock/GET/api/v8/workspaces.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Id 1", 5 | "profile": 13, 6 | "premium": true, 7 | "admin": true, 8 | "default_hourly_rate": 0, 9 | "default_currency": "USD", 10 | "only_admins_may_create_projects": false, 11 | "only_admins_see_billable_rates": false, 12 | "only_admins_see_team_dashboard": false, 13 | "projects_billable_by_default": false, 14 | "rounding": 0, 15 | "rounding_minutes": 0, 16 | "api_token": "7d475340128f8ac760a532c2c1d60998", 17 | "at": "2016-01-27T23:18:51+00:00", 18 | "logo_url": "https://assets.toggl.com/logos/be108a1b6e916c0b6880b8b56aeef235.png", 19 | "ical_url": "/ical/workspace_user/8138a50b0c407841b5a9b706eeee01a7", 20 | "ical_enabled": true, 21 | "subscription": { 22 | "workspace_id": 0, 23 | "deleted_at": null, 24 | "created_at": "0001-01-01T00:00:00Z", 25 | "updated_at": null, 26 | "vat_valid": false, 27 | "vat_validated_at": null, 28 | "vat_invalid_accepted_at": null, 29 | "vat_invalid_accepted_by": null, 30 | "description": "Old Pro monthly", 31 | "vat_applicable": false 32 | } 33 | }, 34 | { 35 | "id": 2, 36 | "name": "Id 2", 37 | "profile": 0, 38 | "premium": false, 39 | "admin": false, 40 | "default_hourly_rate": 0, 41 | "default_currency": "USD", 42 | "only_admins_may_create_projects": false, 43 | "only_admins_see_billable_rates": false, 44 | "only_admins_see_team_dashboard": false, 45 | "projects_billable_by_default": true, 46 | "rounding": 1, 47 | "rounding_minutes": 0, 48 | "at": "2016-01-29T16:27:18+00:00", 49 | "ical_enabled": true, 50 | "subscription": { 51 | "workspace_id": 0, 52 | "deleted_at": null, 53 | "created_at": "0001-01-01T00:00:00Z", 54 | "updated_at": null, 55 | "vat_valid": false, 56 | "vat_validated_at": null, 57 | "vat_invalid_accepted_at": null, 58 | "vat_invalid_accepted_by": null, 59 | "description": "Free", 60 | "vat_applicable": false 61 | } 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /gttimentry/mock/GET/api/v8/workspaces.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Id 1", 5 | "profile": 13, 6 | "premium": true, 7 | "admin": true, 8 | "default_hourly_rate": 0, 9 | "default_currency": "USD", 10 | "only_admins_may_create_projects": false, 11 | "only_admins_see_billable_rates": false, 12 | "only_admins_see_team_dashboard": false, 13 | "projects_billable_by_default": false, 14 | "rounding": 0, 15 | "rounding_minutes": 0, 16 | "api_token": "7d475340128f8ac760a532c2c1d60998", 17 | "at": "2016-01-27T23:18:51+00:00", 18 | "logo_url": "https://assets.toggl.com/logos/be108a1b6e916c0b6880b8b56aeef235.png", 19 | "ical_url": "/ical/workspace_user/8138a50b0c407841b5a9b706eeee01a7", 20 | "ical_enabled": true, 21 | "subscription": { 22 | "workspace_id": 0, 23 | "deleted_at": null, 24 | "created_at": "0001-01-01T00:00:00Z", 25 | "updated_at": null, 26 | "vat_valid": false, 27 | "vat_validated_at": null, 28 | "vat_invalid_accepted_at": null, 29 | "vat_invalid_accepted_by": null, 30 | "description": "Old Pro monthly", 31 | "vat_applicable": false 32 | } 33 | }, 34 | { 35 | "id": 2, 36 | "name": "Id 2", 37 | "profile": 0, 38 | "premium": false, 39 | "admin": false, 40 | "default_hourly_rate": 0, 41 | "default_currency": "USD", 42 | "only_admins_may_create_projects": false, 43 | "only_admins_see_billable_rates": false, 44 | "only_admins_see_team_dashboard": false, 45 | "projects_billable_by_default": true, 46 | "rounding": 1, 47 | "rounding_minutes": 0, 48 | "at": "2016-01-29T16:27:18+00:00", 49 | "ical_enabled": true, 50 | "subscription": { 51 | "workspace_id": 0, 52 | "deleted_at": null, 53 | "created_at": "0001-01-01T00:00:00Z", 54 | "updated_at": null, 55 | "vat_valid": false, 56 | "vat_validated_at": null, 57 | "vat_invalid_accepted_at": null, 58 | "vat_invalid_accepted_by": null, 59 | "description": "Free", 60 | "vat_applicable": false 61 | } 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /gtworkspace/mock/GET/api/v8/workspaces.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": 1, 4 | "name": "Id 1", 5 | "profile": 13, 6 | "premium": true, 7 | "admin": true, 8 | "default_hourly_rate": 0, 9 | "default_currency": "USD", 10 | "only_admins_may_create_projects": false, 11 | "only_admins_see_billable_rates": false, 12 | "only_admins_see_team_dashboard": false, 13 | "projects_billable_by_default": false, 14 | "rounding": 0, 15 | "rounding_minutes": 0, 16 | "api_token": "7d475340128f8ac760a532c2c1d60998", 17 | "at": "2016-01-27T23:18:51+00:00", 18 | "logo_url": "https://assets.toggl.com/logos/be108a1b6e916c0b6880b8b56aeef235.png", 19 | "ical_url": "/ical/workspace_user/8138a50b0c407841b5a9b706eeee01a7", 20 | "ical_enabled": true, 21 | "subscription": { 22 | "workspace_id": 0, 23 | "deleted_at": null, 24 | "created_at": "0001-01-01T00:00:00Z", 25 | "updated_at": null, 26 | "vat_valid": false, 27 | "vat_validated_at": null, 28 | "vat_invalid_accepted_at": null, 29 | "vat_invalid_accepted_by": null, 30 | "description": "Old Pro monthly", 31 | "vat_applicable": false 32 | } 33 | }, 34 | { 35 | "id": 2, 36 | "name": "Id 2", 37 | "profile": 0, 38 | "premium": false, 39 | "admin": false, 40 | "default_hourly_rate": 0, 41 | "default_currency": "USD", 42 | "only_admins_may_create_projects": false, 43 | "only_admins_see_billable_rates": false, 44 | "only_admins_see_team_dashboard": false, 45 | "projects_billable_by_default": true, 46 | "rounding": 1, 47 | "rounding_minutes": 0, 48 | "at": "2016-01-29T16:27:18+00:00", 49 | "ical_enabled": true, 50 | "subscription": { 51 | "workspace_id": 0, 52 | "deleted_at": null, 53 | "created_at": "0001-01-01T00:00:00Z", 54 | "updated_at": null, 55 | "vat_valid": false, 56 | "vat_validated_at": null, 57 | "vat_invalid_accepted_at": null, 58 | "vat_invalid_accepted_by": null, 59 | "description": "Free", 60 | "vat_applicable": false 61 | } 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /gtproject/project_test.go: -------------------------------------------------------------------------------- 1 | package gtproject 2 | 3 | import ( 4 | "github.com/dougEfresh/gtoggl-api/test" 5 | "testing" 6 | ) 7 | 8 | func togglClient(t *testing.T) *ProjectClient { 9 | tu := >test.TestUtil{} 10 | client := tu.MockClient(t) 11 | return NewClient(client) 12 | } 13 | 14 | func TestProjectCreate(t *testing.T) { 15 | tClient := togglClient(t) 16 | c := &Project{Name: "Very Big Company", WId: 777} 17 | nc, err := tClient.Create(c) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | if nc.Name != "An awesome project" { 23 | t.Fatal("!= An awesome project") 24 | } 25 | 26 | if nc.Id != 3 { 27 | t.Fatal("!= 3") 28 | } 29 | 30 | if nc.WId != 777 { 31 | t.Fatal("!= 777") 32 | } 33 | } 34 | 35 | func TestProjectUpdate(t *testing.T) { 36 | tClient := togglClient(t) 37 | c := &Project{Id: 1, Name: "new name", WId: 777} 38 | nc, err := tClient.Update(c) 39 | if err != nil { 40 | t.Fatal(err) 41 | } 42 | 43 | if nc.Name != "new name" { 44 | t.Fatal("!= new name") 45 | } 46 | } 47 | 48 | func TestProjectDelete(t *testing.T) { 49 | tClient := togglClient(t) 50 | c := &Project{Id: 1, Name: "new name", WId: 777} 51 | err := tClient.Delete(c.Id) 52 | if err != nil { 53 | t.Fatal(err) 54 | } 55 | } 56 | 57 | func TestProjectList(t *testing.T) { 58 | tClient := togglClient(t) 59 | clients, err := tClient.List() 60 | if err != nil { 61 | t.Fatal(err) 62 | } 63 | if len(clients) != 2 { 64 | t.Fatal("Workspace is not 2") 65 | } 66 | if clients[0].Id != 1 { 67 | t.Error("Workspace Id is not 1") 68 | } 69 | if clients[0].Name != "Id 1" { 70 | t.Error("Workspace name not Id ") 71 | } 72 | 73 | if clients[1].Id != 2 { 74 | t.Error("Workspace Id is not 2") 75 | } 76 | if clients[1].Name != "Id 2" { 77 | t.Error("Workspace name") 78 | } 79 | } 80 | 81 | func TestProjectGet(t *testing.T) { 82 | tClient := togglClient(t) 83 | 84 | client, err := tClient.Get(1) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | if client.Id != 1 { 89 | t.Error("!= 1") 90 | } 91 | 92 | if client.Name != "Id 1" { 93 | t.Error("!= Id 1: " + client.Name) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toggl API for golang 2 | 3 | Throttle API for [toggle](https://github.com/toggl/toggl_api_docs/blob/master/toggl_api.md) 4 | 5 | [![GoDoc][doc-img]][doc] [![Build Status][ci-img]][ci] [![Coverage Status][cov-img]][cov] [![Go Report][report-img]][report] 6 | 7 | ## Installation 8 | ```shell 9 | $ go get -u github.com/dougEfresh/gtoggl-api 10 | ``` 11 | 12 | ## Quick Start 13 | 14 | ```go 15 | import "github.com/dougEfresh/gtoggl" 16 | import "github.com/dougEfresh/gtoggl-api/gtproject" 17 | 18 | func main() { 19 | thc, err := gtoggl.NewClient("token") 20 | ... 21 | tc, err := gtproject.NewClient(thc) 22 | ... 23 | project,err := tc.Get(1) 24 | if err == nil { 25 | panic(err) 26 | } 27 | } 28 | ``` 29 | 30 | 31 | The gtoggl clients provides throttling 32 | 33 | ## Usage 34 | 35 | See [gtoggl cli](https://github.com/dougEfresh/gtoggl) 36 | 37 | ## Examples 38 | 39 | See [godoc][doc] for more examples 40 | 41 | 42 | ## Prerequisites 43 | 44 | go 1.x 45 | 46 | ## Tests 47 | 48 | ```shell 49 | $ go test -v ./... 50 | 51 | ``` 52 | 53 | 54 | ## Deployment 55 | 56 | ## Contributing 57 | All PRs are welcome 58 | 59 | ## Authors 60 | 61 | * **Douglas Chimento** - [dougEfresh][me] 62 | 63 | ## License 64 | 65 | This project is licensed under the Apache License - see the [LICENSE](LICENSE) file for details 66 | 67 | ## Acknowledgments 68 | 69 | ### TODO 70 | 71 | [doc-img]: https://godoc.org/github.com/dougEfresh/gtoggl-api?status.svg 72 | [doc]: https://godoc.org/github.com/dougEfresh/gtoggl-api 73 | [ci-img]: https://travis-ci.org/dougEfresh/gtoggl-api.svg?branch=master 74 | [ci]: https://travis-ci.org/dougEfresh/gtoggl-api 75 | [cov-img]: https://codecov.io/gh/dougEfresh/gtoggl-api/branch/master/graph/badge.svg 76 | [cov]: https://codecov.io/gh/dougEfresh/gtoggl-api 77 | [glide.lock]: https://github.com/uber-go/zap/blob/master/glide.lock 78 | [zap]: https://github.com/uber-go/zap 79 | [me]: https://github.com/dougEfresh 80 | [report-img]: https://goreportcard.com/badge/github.com/dougEfresh/gtoggl-api 81 | [report]: https://goreportcard.com/report/github.com/dougEfresh/gtoggl-api -------------------------------------------------------------------------------- /gtclient/client.go: -------------------------------------------------------------------------------- 1 | package gtclient 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/dougEfresh/gtoggl-api/gthttp" 7 | ) 8 | 9 | // Toggl Client Definition 10 | type Client struct { 11 | Id uint64 `json:"id"` 12 | WId uint64 `json:"wid"` 13 | Name string `json:"name"` 14 | Currency string `json:"currency"` 15 | } 16 | 17 | type Clients []Client 18 | 19 | const Endpoint = "/clients" 20 | 21 | //Return a Toggl Client. An error is also returned when some configuration option is invalid 22 | // thc,err := gtoggl.NewClient("token") 23 | // tc,err := gclient.NewClient(tc) 24 | func NewClient(thc *gthttp.TogglHttpClient) *TClient { 25 | tc := &TClient{ 26 | thc: thc, 27 | } 28 | tc.endpoint = thc.Url + Endpoint 29 | return tc 30 | } 31 | 32 | type TClient struct { 33 | thc *gthttp.TogglHttpClient 34 | endpoint string 35 | } 36 | 37 | func (tc *TClient) List() (Clients, error) { 38 | body, err := tc.thc.GetRequest(tc.endpoint) 39 | if err != nil { 40 | return nil, err 41 | } 42 | var clients Clients 43 | err = json.Unmarshal(*body, &clients) 44 | return clients, err 45 | } 46 | 47 | func (tc *TClient) Get(id uint64) (*Client, error) { 48 | return clientResponse(tc.thc.GetRequest(fmt.Sprintf("%s/%d", tc.endpoint, id))) 49 | } 50 | 51 | func (tc *TClient) Create(c *Client) (*Client, error) { 52 | body := map[string]interface{}{"client": c} 53 | return clientResponse(tc.thc.PostRequest(tc.endpoint, body)) 54 | } 55 | 56 | func (tc *TClient) Update(c *Client) (*Client, error) { 57 | body := map[string]interface{}{"client": c} 58 | return clientResponse(tc.thc.PutRequest(fmt.Sprintf("%s/%d", tc.endpoint, c.Id), body)) 59 | } 60 | 61 | func (tc *TClient) Delete(id uint64) error { 62 | _, err := tc.thc.DeleteRequest(fmt.Sprintf("%s/%d", tc.endpoint, id), nil) 63 | return err 64 | } 65 | 66 | func clientResponse(response *json.RawMessage, error error) (*Client, error) { 67 | if error != nil { 68 | return nil, error 69 | } 70 | if response == nil { 71 | return nil, nil 72 | } 73 | var tResp gthttp.TogglResponse 74 | err := json.Unmarshal(*response, &tResp) 75 | if err != nil { 76 | return nil, err 77 | } 78 | var cl Client 79 | err = json.Unmarshal(*tResp.Data, &cl) 80 | if err != nil { 81 | return nil, err 82 | } 83 | return &cl, err 84 | } 85 | -------------------------------------------------------------------------------- /gtworkspace/workspace.go: -------------------------------------------------------------------------------- 1 | package gtworkspace 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/dougEfresh/gtoggl-api/gthttp" 7 | ) 8 | 9 | type Workspace struct { 10 | Id uint64 `json:"id"` 11 | Name string `json:"name"` 12 | Premium bool `json:"premium"` 13 | } 14 | 15 | type Workspaces []Workspace 16 | 17 | const Endpoint = "/workspaces" 18 | 19 | //Return a Workspace Cilent. An error is also returned when some configuration option is invalid 20 | // tc,err := gtoggl.NewClient("token") 21 | // wsc,err := gtoggl.NewWorkspaceClient(tc) 22 | func NewClient(tc *gthttp.TogglHttpClient) *WorkspaceClient { 23 | ws := &WorkspaceClient{ 24 | tc: tc, 25 | } 26 | ws.endpoint = tc.Url + Endpoint 27 | return ws 28 | } 29 | 30 | type WorkspaceClient struct { 31 | tc *gthttp.TogglHttpClient 32 | endpoint string 33 | } 34 | 35 | //GET https://www.toggl.com/api/v8/workspaces/123213 36 | func (wc *WorkspaceClient) Get(id uint64) (*Workspace, error) { 37 | return workspaceResponse(wc.tc.GetRequest(fmt.Sprintf("%s/%d", wc.endpoint, id))) 38 | } 39 | 40 | //PUT https://www.toggl.com/api/v8/workspaces 41 | func (wc *WorkspaceClient) Update(ws *Workspace) (*Workspace, error) { 42 | put := map[string]interface{}{"workspace": ws} 43 | body, err := json.Marshal(put) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return workspaceResponse(wc.tc.PutRequest(fmt.Sprintf("%s/%d", wc.endpoint, ws.Id), body)) 48 | } 49 | 50 | //GET https://www.toggl.com/api/v8/workspaces 51 | func (wc *WorkspaceClient) List() (Workspaces, error) { 52 | body, err := wc.tc.GetRequest(wc.endpoint) 53 | var workspaces Workspaces 54 | if err != nil { 55 | return workspaces, err 56 | } 57 | if body == nil { 58 | return nil, nil 59 | } 60 | err = json.Unmarshal(*body, &workspaces) 61 | return workspaces, err 62 | } 63 | 64 | func workspaceResponse(response *json.RawMessage, error error) (*Workspace, error) { 65 | if error != nil { 66 | return nil, error 67 | } 68 | if response == nil { 69 | return nil, nil 70 | } 71 | var tResp gthttp.TogglResponse 72 | var ws Workspace 73 | err := json.Unmarshal(*response, &tResp) 74 | if err != nil { 75 | return nil, err 76 | } 77 | err = json.Unmarshal(*tResp.Data, &ws) 78 | if err != nil { 79 | return nil, err 80 | } 81 | return &ws, err 82 | } 83 | -------------------------------------------------------------------------------- /gtproject/project.go: -------------------------------------------------------------------------------- 1 | package gtproject 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/dougEfresh/gtoggl-api/gthttp" 7 | ) 8 | 9 | // Toggl Project Definition 10 | type Project struct { 11 | Id uint64 `json:"id"` 12 | WId uint64 `json:"wid"` 13 | CId uint64 `json:"cid"` 14 | Name string `json:"name"` 15 | IsPrivate *bool `json:"is_private,omitempty"` 16 | } 17 | 18 | type Projects []Project 19 | 20 | const Endpoint = "/projects" 21 | 22 | //Return a ProjectClient. An error is also returned when some configuration option is invalid 23 | // thc,err := gtoggl.NewClient("token") 24 | // pc,err := gproject.NewClient(tc) 25 | func NewClient(thc *gthttp.TogglHttpClient) *ProjectClient { 26 | tc := &ProjectClient{ 27 | thc: thc, 28 | } 29 | tc.endpoint = thc.Url + Endpoint 30 | return tc 31 | } 32 | 33 | type ProjectClient struct { 34 | thc *gthttp.TogglHttpClient 35 | endpoint string 36 | } 37 | 38 | func (tc *ProjectClient) List() (Projects, error) { 39 | body, err := tc.thc.GetRequest(tc.endpoint) 40 | var projects []Project 41 | if err != nil { 42 | return nil, err 43 | } 44 | if body == nil { 45 | return projects, nil 46 | } 47 | err = json.Unmarshal(*body, &projects) 48 | return projects, err 49 | } 50 | 51 | func (tc *ProjectClient) Get(id uint64) (*Project, error) { 52 | return projectResponse(tc.thc.GetRequest(fmt.Sprintf("%s/%d", tc.endpoint, id))) 53 | } 54 | 55 | func (tc *ProjectClient) Create(p *Project) (*Project, error) { 56 | put := map[string]interface{}{"project": p} 57 | return projectResponse(tc.thc.PostRequest(tc.endpoint, put)) 58 | } 59 | 60 | func (tc *ProjectClient) Update(p *Project) (*Project, error) { 61 | put := map[string]interface{}{"project": p} 62 | return projectResponse(tc.thc.PutRequest(fmt.Sprintf("%s/%d", tc.endpoint, p.Id), put)) 63 | } 64 | 65 | func (tc *ProjectClient) Delete(id uint64) error { 66 | _, err := tc.thc.DeleteRequest(fmt.Sprintf("%s/%d", tc.endpoint, id), nil) 67 | return err 68 | } 69 | 70 | func projectResponse(response *json.RawMessage, error error) (*Project, error) { 71 | if error != nil { 72 | return nil, error 73 | } 74 | if response == nil { 75 | return nil, nil 76 | } 77 | var tResp gthttp.TogglResponse 78 | err := json.Unmarshal(*response, &tResp) 79 | if err != nil { 80 | return nil, err 81 | } 82 | var p Project 83 | err = json.Unmarshal(*tResp.Data, &p) 84 | if err != nil { 85 | return nil, err 86 | } 87 | return &p, err 88 | } 89 | -------------------------------------------------------------------------------- /gttimentry/timeentry_test.go: -------------------------------------------------------------------------------- 1 | package gttimeentry 2 | 3 | import ( 4 | "github.com/dougEfresh/gtoggl-api/test" 5 | "testing" 6 | "time" 7 | ) 8 | 9 | func togglClient(t *testing.T) *TimeEntryClient { 10 | tu := >test.TestUtil{} 11 | client := tu.MockClient(t) 12 | return NewClient(client) 13 | } 14 | 15 | func TestTimeEntryDelete(t *testing.T) { 16 | tClient := togglClient(t) 17 | err := tClient.Delete(1) 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | } 22 | 23 | func TestTimeEntryList(t *testing.T) { 24 | tClient := togglClient(t) 25 | te, err := tClient.List() 26 | if err != nil { 27 | t.Fatal(err) 28 | } 29 | if len(te) < 1 { 30 | t.Fatal("<1") 31 | } 32 | 33 | } 34 | 35 | func TestTimeEntryCreate(t *testing.T) { 36 | tClient := togglClient(t) 37 | 38 | te := &TimeEntry{} 39 | te.Billable = false 40 | te.Duration = 1200 41 | te.Pid = 123 42 | te.Wid = 777 43 | te.Description = "Meeting with possible clients" 44 | te.Tags = []string{"billed"} 45 | 46 | nTe, err := tClient.Create(te) 47 | 48 | if err != nil { 49 | t.Fatal(err) 50 | } 51 | if nTe.Id != 3 { 52 | t.Error("!= 3") 53 | } 54 | } 55 | 56 | func TestTimeEntryUpdate(t *testing.T) { 57 | tClient := togglClient(t) 58 | te, err := tClient.Get(1) 59 | if err != nil { 60 | t.Fatal(err) 61 | } 62 | te.Description = "new" 63 | nTe, err := tClient.Update(te) 64 | if err != nil { 65 | t.Fatal(err) 66 | } 67 | if nTe.Description != "new" { 68 | t.Error("!= new") 69 | } 70 | } 71 | 72 | func TestTimeEntryGet(t *testing.T) { 73 | tClient := togglClient(t) 74 | 75 | timeentry, err := tClient.Get(1) 76 | if err != nil { 77 | t.Fatal(err) 78 | } 79 | if timeentry.Id != 1 { 80 | t.Error("!= 1") 81 | } 82 | 83 | st, err := time.Parse(time.RFC3339, "2013-02-27T01:24:00+00:00") 84 | 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | diff := st.Sub(timeentry.Start) 89 | if diff != 0 { 90 | t.Errorf("!= %s", diff) 91 | } 92 | st, err = time.Parse(time.RFC3339, "2013-02-27T07:24:00+00:00") 93 | diff = st.Sub(timeentry.Stop) 94 | if diff != 0 { 95 | t.Errorf("!= %s", diff) 96 | } 97 | } 98 | 99 | /* Disabled because doesn't work on Windoze 100 | func TestTimeEntryGetTimeRange(t *testing.T) { 101 | tClient := togglClient(t) 102 | 103 | start, _ := time.Parse(time.RFC3339, "2013-03-10T15:42:46+02:00") 104 | end, _ := time.Parse(time.RFC3339, "2013-03-12T15:42:46+02:00") 105 | 106 | te, err := tClient.GetRange(start, end) 107 | if err != nil { 108 | t.Fatal(err) 109 | } 110 | if len(te) < 1 { 111 | t.Fatal("<1") 112 | } 113 | timeentry := te[0] 114 | if timeentry.Id != 4 { 115 | t.Error("!= 4") 116 | } 117 | } 118 | */ 119 | 120 | func BenchmarkTimeEntryClient_Get(b *testing.B) { 121 | b.ReportAllocs() 122 | tClient := togglClient(nil) 123 | 124 | for i := 0; i < b.N; i++ { 125 | if _, err := tClient.Get(1); err != nil { 126 | b.Fatalf("Get: %v", err) 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /test/gtest.go: -------------------------------------------------------------------------------- 1 | package gttest 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "testing" 12 | 13 | "github.com/dougEfresh/gtoggl-api/gthttp" 14 | ) 15 | 16 | type TestUtil struct { 17 | } 18 | 19 | type TestLogger struct { 20 | Testing *testing.T 21 | } 22 | 23 | func (l *TestLogger) Printf(format string, v ...interface{}) { 24 | if l.Testing != nil { 25 | l.Testing.Logf(format, v...) 26 | } 27 | } 28 | 29 | var mockResponses = make(map[string][]byte) 30 | 31 | type mockFunc func(req *http.Request) []byte 32 | 33 | type mockTransport struct { 34 | mock mockFunc 35 | } 36 | 37 | func newMockTransport(f mockFunc) http.RoundTripper { 38 | return &mockTransport{mock: f} 39 | } 40 | 41 | func visit(path string, f os.FileInfo, err error) error { 42 | if !f.IsDir() { 43 | mockResponses[path], err = ioutil.ReadFile(path) 44 | if err != nil { 45 | return err 46 | } 47 | } 48 | return nil 49 | } 50 | 51 | func getResponse() mockFunc { 52 | return func(req *http.Request) []byte { 53 | var r string 54 | if req.URL.RawQuery == "" { 55 | r = fmt.Sprintf("mock/%s%s.json", req.Method, req.URL.Path) 56 | } else { 57 | r = fmt.Sprintf("mock/%s%s?%s.json", req.Method, req.URL.Path, req.URL.RawQuery) 58 | } 59 | resp := mockResponses[r] 60 | if resp == nil { 61 | panic("Unknown request " + r) 62 | } 63 | return resp 64 | } 65 | } 66 | 67 | func load() { 68 | if len(mockResponses) > 0 { 69 | return 70 | } 71 | err := filepath.Walk("./mock", visit) 72 | if err != nil { 73 | panic(err) 74 | } 75 | } 76 | 77 | var nullResponse = []byte("") 78 | 79 | func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { 80 | // Create mocked http.Response 81 | response := &http.Response{Header: make(http.Header), Request: req, StatusCode: http.StatusOK} 82 | response.Header.Set("Content-Type", "application/json") 83 | response.Header.Set("Set-Cookie", "__Host-timer-session=MTM2MzA4MJa8jA3OHxEdi1CQkFFQ180SUFBUkFCRUFBQVlQLUNBQUVHYzNSeWFXNW5EQXdBQ25ObGMzTnBiMjVmYVdRR2MzUnlhVzVuREQ0QVBIUnZaMmRzTFdGd2FTMXpaWE56YVc5dUxUSXRaalU1WmpaalpEUTVOV1ZsTVRoaE1UaGhaalpqWkRkbU5XWTJNV0psWVRnd09EWmlPVEV3WkE9PXweAkG7kI6NBG-iqvhNn1MSDhkz2Pz_UYTzdBvZjCaA==; Path=/; Expires=Wed, 13 Mar 2013 09:54:38 UTC; Max-Age=86400; HttpOnly") 84 | if strings.Contains(req.URL.Path, "/sessions") { 85 | response.Body = ioutil.NopCloser(bytes.NewReader(nullResponse)) 86 | return response, nil 87 | } 88 | responseBody := t.mock(req) 89 | response.Body = ioutil.NopCloser(bytes.NewReader(responseBody)) 90 | return response, nil 91 | } 92 | 93 | func (tu TestUtil) MockClient(t *testing.T) *gthttp.TogglHttpClient { 94 | load() 95 | l := &TestLogger{Testing: t} 96 | httpClient := &http.Client{Transport: newMockTransport(getResponse())} 97 | optionsWithClient := []gthttp.ClientOptionFunc{gthttp.SetHttpClient(httpClient), gthttp.SetTraceLogger(l)} 98 | client, err := gthttp.NewClient("abc1234567890def", optionsWithClient...) 99 | if err != nil { 100 | panic(err) 101 | } 102 | return client 103 | } 104 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Douglas Chimento. All rights reserved. 2 | 3 | /* 4 | Package gclient provides access to toggl REST API. 5 | 6 | Example: 7 | import "gopkg.in/dougEfresh/gtoggl.v8" 8 | import "gopkg.in/dougEfresh/toggl-client.v8" 9 | 10 | func main() { 11 | thc, err := gtoggl.NewClient("token") 12 | ... 13 | tc, err := gclient.NewClient(thc) 14 | ... 15 | client,err := tc.Get(1) 16 | if err == nil { 17 | panic(err) 18 | } 19 | } 20 | */ 21 | package gtogglapi 22 | 23 | // Copyright 2016 Douglas Chimento. All rights reserved. 24 | 25 | /* 26 | 27 | Example: 28 | import "gopkg.in/dougEfresh/gtoggl.v8" 29 | import "gopkg.in/dougEfresh/toggl-timeentry.v8" 30 | 31 | func main() { 32 | thc, err := gtoggl.NewClient("token") 33 | ... 34 | tc, err := gtimeentry.NewClient(thc) 35 | ... 36 | timeentry,err := tc.Get(1) 37 | if err == nil { 38 | panic(err) 39 | } 40 | } 41 | */ 42 | // Copyright 2016 Douglas Chimento. All rights reserved. 43 | 44 | /* 45 | Package gproject provides access to toggl REST API 46 | 47 | 48 | Example: 49 | import "gopkg.in/dougEfresh/gtoggl.v8" 50 | import "gopkg.in/dougEfresh/toggl-project.v8" 51 | 52 | func main() { 53 | thc, err := gtoggl.NewClient("token") 54 | ... 55 | tc, err := gproject.NewClient(thc) 56 | ... 57 | project,err := tc.Get(1) 58 | if err == nil { 59 | panic(err) 60 | } 61 | } 62 | */ 63 | /// Copyright 2016 Douglas Chimento. All rights reserved. 64 | 65 | /* 66 | Package gtest provides test utils for gtoggl 67 | */ 68 | // Copyright 2016 Douglas Chimento. All rights reserved. 69 | 70 | /* 71 | Package gtimeentry provides access to toggl REST API 72 | 73 | 74 | Example: 75 | import "gopkg.in/dougEfresh/gtoggl.v8" 76 | import "gopkg.in/dougEfresh/toggl-timeentry.v8" 77 | 78 | func main() { 79 | thc, err := gtoggl.NewClient("token") 80 | ... 81 | tc, err := gtimeentry.NewClient(thc) 82 | ... 83 | timeentry,err := tc.Get(1) 84 | if err == nil { 85 | panic(err) 86 | } 87 | } 88 | */ 89 | // Copyright 2016 Douglas Chimento. All rights reserved. 90 | 91 | /* 92 | Package guser provides access to toggl REST API 93 | 94 | 95 | Example: 96 | import "gopkg.in/dougEfresh/gtoggl.v8" 97 | import "gopkg.in/dougEfresh/toggl-user.v8" 98 | 99 | func main() { 100 | thc, err := gtoggl.NewClient("token") 101 | ... 102 | tc, err := gtimeentry.NewClient(thc) 103 | ... 104 | me,err := tc.Get(false) 105 | if err == nil { 106 | panic(err) 107 | } 108 | } 109 | */ 110 | // Copyright 2016 Douglas Chimento. All rights reserved. 111 | 112 | /* 113 | Package gworkspace provides access to toggl REST API 114 | 115 | 116 | Example: 117 | import "gopkg.in/dougEfresh/gtoggl.v8" 118 | import "gopkg.in/dougEfresh/toggl-workspace.v8" 119 | 120 | func main() { 121 | thc, err := gtoggl.NewClient("token") 122 | ... 123 | tc, err := gworkspace.NewClient(thc) 124 | ... 125 | workspace,err := tc.Get(1) 126 | if err == nil { 127 | panic(err) 128 | } 129 | } 130 | */ 131 | -------------------------------------------------------------------------------- /gtuser/user.go: -------------------------------------------------------------------------------- 1 | package gtuser 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/dougEfresh/gtoggl-api/gtclient" 6 | "github.com/dougEfresh/gtoggl-api/gthttp" 7 | "github.com/dougEfresh/gtoggl-api/gtproject" 8 | "github.com/dougEfresh/gtoggl-api/gtworkspace" 9 | ) 10 | 11 | // Toggl User Definition 12 | type User struct { 13 | Id uint64 `json:"id"` 14 | ApiToken string `json:"api_token"` 15 | Email string `json:"email"` 16 | FullName string `json:"fullname"` 17 | Timezone string `json:"timezone"` 18 | Clients gtclient.Clients `json:"clients"` 19 | Projects gtproject.Projects `json:"projects"` 20 | Workspaces gtworkspace.Workspaces `json:"workspaces"` 21 | } 22 | type UserUpdate struct { 23 | Email string `json:"email"` 24 | FullName string `json:"fullname"` 25 | } 26 | type UserCreate struct { 27 | Email string `json:"email"` 28 | Password string `json:"password"` 29 | Timezone string `json:"timezone"` 30 | CreatedWith string `json:"created_with"` 31 | } 32 | 33 | type Users []User 34 | 35 | const Endpoint = "/me" 36 | const SignupEndpoint = "/signups" 37 | const ResetEndpoint = "/reset_token" 38 | const MeWithRelatedData = "/me?with_related_data=true" 39 | 40 | //Return a UserClient. An error is also returned when some configuration option is invalid 41 | // thc,err := gtoggl.NewClient("token") 42 | // uc,err := guser.NewClient(thc) 43 | func NewClient(thc *gthttp.TogglHttpClient) *UserClient { 44 | tc := &UserClient{ 45 | thc: thc, 46 | } 47 | tc.endpoint = thc.Url + Endpoint 48 | tc.signupEndpoint = thc.Url + SignupEndpoint 49 | tc.resetEndpoint = thc.Url + ResetEndpoint 50 | tc.relatedEndpoint = thc.Url + MeWithRelatedData 51 | return tc 52 | } 53 | 54 | type UserClient struct { 55 | thc *gthttp.TogglHttpClient 56 | endpoint string 57 | resetEndpoint string 58 | signupEndpoint string 59 | relatedEndpoint string 60 | } 61 | 62 | func (c *UserClient) Get(realatedData bool) (*User, error) { 63 | if realatedData { 64 | return userResponse(c.thc.GetRequest(c.relatedEndpoint)) 65 | } 66 | return userResponse(c.thc.GetRequest(c.endpoint)) 67 | } 68 | 69 | func (c *UserClient) Create(email, password, timezone string) (*User, error) { 70 | up := &UserCreate{Password: password, Email: email, Timezone: timezone, CreatedWith: "gtoggl"} 71 | put := map[string]interface{}{"user": up} 72 | return userResponse(c.thc.PostRequest(c.signupEndpoint, put)) 73 | } 74 | 75 | func (c *UserClient) Update(u *User) (*User, error) { 76 | up := &UserUpdate{FullName: u.FullName, Email: u.Email} 77 | put := map[string]interface{}{"user": up} 78 | return userResponse(c.thc.PutRequest(c.endpoint, put)) 79 | } 80 | 81 | func (c *UserClient) ResetToken() (string, error) { 82 | response, err := c.thc.PostRequest(c.resetEndpoint, nil) 83 | if err != nil { 84 | return "", err 85 | } 86 | var aux string 87 | err = json.Unmarshal(*response, &aux) 88 | return aux, nil 89 | 90 | } 91 | 92 | func userResponse(response *json.RawMessage, error error) (*User, error) { 93 | if error != nil { 94 | return nil, error 95 | } 96 | var tResp gthttp.TogglResponse 97 | err := json.Unmarshal(*response, &tResp) 98 | if err != nil { 99 | return nil, err 100 | } 101 | var u User 102 | err = json.Unmarshal(*tResp.Data, &u) 103 | if err != nil { 104 | return nil, err 105 | } 106 | return &u, err 107 | } 108 | -------------------------------------------------------------------------------- /gttimentry/timeentry.go: -------------------------------------------------------------------------------- 1 | package gttimeentry 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "github.com/dougEfresh/gtoggl-api/gthttp" 7 | "github.com/dougEfresh/gtoggl-api/gtproject" 8 | "github.com/dougEfresh/gtoggl-api/gtworkspace" 9 | "net/url" 10 | "time" 11 | ) 12 | 13 | type TimeEntry struct { 14 | Id uint64 `json:"id,omitempty"` 15 | Description string `json:"description"` 16 | Project *gtproject.Project `json:"project"` 17 | Start time.Time `json:"start"` 18 | Stop time.Time `json:"stop"` 19 | Duration int64 `json:"duration"` 20 | Billable bool `json:"billable"` 21 | Workspace *gtworkspace.Workspace `json:"workspace"` 22 | Tags []string `json:"tags"` 23 | Pid uint64 `json:"pid"` 24 | Wid uint64 `json:"wid"` 25 | Tid uint64 `json:"tid"` 26 | CreatedWith string `json:"created_with,omitempty" ` 27 | } 28 | 29 | type TimeEntries []TimeEntry 30 | 31 | const Endpoint = "/time_entries" 32 | const EndpointCurrent = Endpoint + "/current" 33 | const EndpointStart = Endpoint + "/start" 34 | 35 | //Return a UserClient. An error is also returned when some configuration option is invalid 36 | // thc,err := gtoggl.NewClient("token") 37 | // uc,err := guser.NewClient(thc) 38 | func NewClient(thc *gthttp.TogglHttpClient) *TimeEntryClient { 39 | tc := &TimeEntryClient{ 40 | thc: thc, 41 | } 42 | tc.endpoint = thc.Url + Endpoint 43 | tc.currentEndpoint = thc.Url + EndpointCurrent 44 | tc.startEndpoint = thc.Url + EndpointStart 45 | return tc 46 | } 47 | 48 | type TimeEntryClient struct { 49 | thc *gthttp.TogglHttpClient 50 | endpoint string 51 | startEndpoint string 52 | currentEndpoint string 53 | } 54 | 55 | func (c *TimeEntryClient) Get(tid uint64) (*TimeEntry, error) { 56 | return timeEntryResponse(c.thc.GetRequest(fmt.Sprintf("%s/%d", c.endpoint, tid))) 57 | } 58 | 59 | func (c *TimeEntryClient) Current() (*TimeEntry, error) { 60 | return timeEntryResponse(c.thc.GetRequest(c.currentEndpoint)) 61 | } 62 | 63 | func (tc *TimeEntryClient) Delete(id uint64) error { 64 | _, err := tc.thc.DeleteRequest(fmt.Sprintf("%s/%d", tc.endpoint, id), nil) 65 | return err 66 | } 67 | 68 | func (c *TimeEntryClient) List() (TimeEntries, error) { 69 | body, err := c.thc.GetRequest(c.endpoint) 70 | var te TimeEntries 71 | if err != nil { 72 | return te, err 73 | } 74 | if body == nil { 75 | return nil, nil 76 | } 77 | err = json.Unmarshal(*body, &te) 78 | return te, err 79 | } 80 | 81 | func (c *TimeEntryClient) Create(t *TimeEntry) (*TimeEntry, error) { 82 | if len(t.CreatedWith) < 0 { 83 | t.CreatedWith = "gtoggl" 84 | } 85 | up := map[string]interface{}{"time_entry": t} 86 | return timeEntryResponse(c.thc.PostRequest(c.endpoint, up)) 87 | } 88 | 89 | func (c *TimeEntryClient) Start(t *TimeEntry) (*TimeEntry, error) { 90 | if len(t.CreatedWith) < 0 { 91 | t.CreatedWith = "gtoggl" 92 | } 93 | up := map[string]interface{}{"time_entry": t} 94 | return timeEntryResponse(c.thc.PostRequest(c.startEndpoint, up)) 95 | } 96 | 97 | func (c *TimeEntryClient) Stop(t *TimeEntry) (*TimeEntry, error) { 98 | return timeEntryResponse(c.thc.PutRequest(fmt.Sprintf("%s/%d/stop", c.endpoint, t.Id), nil)) 99 | } 100 | 101 | func (c *TimeEntryClient) Update(t *TimeEntry) (*TimeEntry, error) { 102 | up := map[string]interface{}{"time_entry": t} 103 | return timeEntryResponse(c.thc.PutRequest(fmt.Sprintf("%s/%d", c.endpoint, t.Id), up)) 104 | } 105 | 106 | func (c *TimeEntryClient) GetRange(start time.Time, end time.Time) (TimeEntries, error) { 107 | v := url.Values{} 108 | v.Set("start_date", start.Format(time.RFC3339)) 109 | v.Set("end_date", end.Format(time.RFC3339)) 110 | body, err := c.thc.GetRequest(fmt.Sprintf("%s?%s", c.endpoint, v.Encode())) 111 | var te TimeEntries 112 | if err != nil { 113 | return te, err 114 | } 115 | if body == nil { 116 | return nil, nil 117 | } 118 | err = json.Unmarshal(*body, &te) 119 | return te, err 120 | } 121 | 122 | func timeEntryResponse(response *json.RawMessage, error error) (*TimeEntry, error) { 123 | if error != nil { 124 | return nil, error 125 | } 126 | if response == nil { 127 | return nil, nil 128 | } 129 | var tResp gthttp.TogglResponse 130 | err := json.Unmarshal(*response, &tResp) 131 | if err != nil { 132 | return nil, err 133 | } 134 | var t TimeEntry 135 | err = json.Unmarshal(*tResp.Data, &t) 136 | if err != nil { 137 | return nil, err 138 | } 139 | return &t, err 140 | } 141 | -------------------------------------------------------------------------------- /gthttp/httpclient_test.go: -------------------------------------------------------------------------------- 1 | package gthttp 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "net/http" 8 | "net/http/httptest" 9 | "strings" 10 | "testing" 11 | ) 12 | 13 | type TestLogger struct { 14 | Testing *testing.T 15 | } 16 | 17 | func (l *TestLogger) Printf(format string, v ...interface{}) { 18 | l.Testing.Logf(format, v...) 19 | } 20 | 21 | var mockRequest = struct { 22 | path, query string // request 23 | contenttype, body string // response 24 | }{ 25 | path: DefaultUrl + "/projects", 26 | contenttype: "application/json", 27 | body: "{ \"data\": {\"nothing\": true}}", 28 | } 29 | var handler = func(w http.ResponseWriter, r *http.Request) { 30 | w.Header().Set("Content-Type", mockRequest.contenttype) 31 | if strings.Contains(r.URL.Path, "/sessions") { 32 | w.Header().Set("Set-Cookie", "__Host-timer-session=MTM2MzA4MJa8jA3OHxEdi1CQkFFQ180SUFBUkFCRUFBQVlQLUNBQUVHYzNSeWFXNW5EQXdBQ25ObGMzTnBiMjVmYVdRR2MzUnlhVzVuREQ0QVBIUnZaMmRzTFdGd2FTMXpaWE56YVc5dUxUSXRaalU1WmpaalpEUTVOV1ZsTVRoaE1UaGhaalpqWkRkbU5XWTJNV0psWVRnd09EWmlPVEV3WkE9PXweAkG7kI6NBG-iqvhNn1MSDhkz2Pz_UYTzdBvZjCaA==; Path=/; Expires=Wed, 13 Mar 2013 09:54:38 UTC; Max-Age=86400; HttpOnly") 33 | io.WriteString(w, "") 34 | } else { 35 | io.WriteString(w, mockRequest.body) 36 | } 37 | } 38 | 39 | func TestHandleGet(t *testing.T) { 40 | 41 | server := httptest.NewServer(http.HandlerFunc(handler)) 42 | defer server.Close() 43 | testLogger := &TestLogger{t} 44 | tc, err := NewClient("token", SetURL(server.URL), SetErrorLogger(testLogger)) 45 | if err != nil { 46 | t.Fatalf("Get: %v", err) 47 | } 48 | raw, err := tc.GetRequest(server.URL + "/any") 49 | if err != nil { 50 | t.Fatalf("Get: %v", err) 51 | } 52 | var tResp TogglResponse 53 | err = json.Unmarshal(*raw, &tResp) 54 | if err != nil { 55 | t.Fatalf("Get: %v", err) 56 | } 57 | 58 | if tResp.Data == nil { 59 | t.Fatalf("Get: %v", err) 60 | } 61 | } 62 | 63 | func TestHandlePost(t *testing.T) { 64 | server := httptest.NewServer(http.HandlerFunc(handler)) 65 | defer server.Close() 66 | testLogger := &TestLogger{t} 67 | tc, err := NewClient("token", SetURL(server.URL), SetErrorLogger(testLogger)) 68 | if err != nil { 69 | t.Fatalf("Get: %v", err) 70 | } 71 | raw, err := tc.PostRequest(server.URL+"/any", &TogglResponse{Data: nil}) 72 | if err != nil { 73 | t.Fatalf("Get: %v", err) 74 | } 75 | var tResp TogglResponse 76 | err = json.Unmarshal(*raw, &tResp) 77 | if err != nil { 78 | t.Fatalf("Get: %v", err) 79 | } 80 | if tResp.Data == nil { 81 | t.Fatalf("Get: %v", err) 82 | } 83 | } 84 | 85 | func TestHandlePut(t *testing.T) { 86 | server := httptest.NewServer(http.HandlerFunc(handler)) 87 | defer server.Close() 88 | testLogger := &TestLogger{t} 89 | tc, err := NewClient("token", SetURL(server.URL), SetErrorLogger(testLogger)) 90 | if err != nil { 91 | t.Fatalf("Get: %v", err) 92 | } 93 | raw, err := tc.PutRequest(server.URL+"/any", &TogglResponse{Data: nil}) 94 | if err != nil { 95 | t.Fatalf("Get: %v", err) 96 | } 97 | var tResp TogglResponse 98 | err = json.Unmarshal(*raw, &tResp) 99 | if err != nil { 100 | t.Fatalf("Get: %v", err) 101 | } 102 | if tResp.Data == nil { 103 | t.Fatalf("Get: %v", err) 104 | } 105 | } 106 | 107 | func TestHandleDelete(t *testing.T) { 108 | server := httptest.NewServer(http.HandlerFunc(handler)) 109 | defer server.Close() 110 | testLogger := &TestLogger{t} 111 | tc, err := NewClient("token", SetURL(server.URL), SetErrorLogger(testLogger)) 112 | if err != nil { 113 | t.Fatalf("Get: %v", err) 114 | } 115 | raw, err := tc.DeleteRequest(server.URL+"/any", nil) 116 | if err != nil { 117 | t.Fatalf("Get: %v", err) 118 | } 119 | var tResp TogglResponse 120 | err = json.Unmarshal(*raw, &tResp) 121 | if err != nil { 122 | t.Fatalf("Get: %v", err) 123 | } 124 | if tResp.Data == nil { 125 | t.Fatalf("Get: %v", err) 126 | } 127 | } 128 | 129 | func TestClientDefaults(t *testing.T) { 130 | client, err := NewClient("") 131 | if err == nil { 132 | t.Fatal("Error should have been thrown. No Token given") 133 | } 134 | server := httptest.NewServer(http.HandlerFunc(handler)) 135 | defer server.Close() 136 | 137 | httpClient := &http.Client{} 138 | testLogger := &TestLogger{t} 139 | client, err = NewClient("abc1234567890def", SetURL(server.URL), SetErrorLogger(testLogger), SetTraceLogger(testLogger), SetInfoLogger(testLogger), SetHttpClient(httpClient)) 140 | if err != nil { 141 | t.Fatal(err) 142 | } 143 | if client.Url != server.URL { 144 | t.Error("Url not blah; " + client.Url) 145 | } 146 | if client.traceLog != testLogger || client.errorLog != testLogger || client.infoLog != testLogger { 147 | t.Error("Loggers not set ") 148 | } 149 | if client.cookie == nil { 150 | t.Errorf("Session Cookie not found not defined\n") 151 | } 152 | } 153 | 154 | func Test400(t *testing.T) { 155 | var h = func(w http.ResponseWriter, r *http.Request) { 156 | w.Header().Set("Content-Type", mockRequest.contenttype) 157 | w.WriteHeader(400) 158 | } 159 | server := httptest.NewServer(http.HandlerFunc(h)) 160 | defer server.Close() 161 | testLogger := &TestLogger{t} 162 | client, err := NewClient("abc1234567890def", SetURL(server.URL), SetTraceLogger(testLogger)) 163 | fmt.Printf("%s\n", err) 164 | if err == nil { 165 | t.Fatal("Should be 400") 166 | } 167 | 168 | if client != nil { 169 | t.Fatal("client should be null") 170 | } 171 | 172 | } 173 | 174 | func Test404(t *testing.T) { 175 | var h = func(w http.ResponseWriter, r *http.Request) { 176 | w.Header().Set("Content-Type", mockRequest.contenttype) 177 | if strings.Contains(r.URL.Path, "/sessions") { 178 | w.Header().Set("Set-Cookie", "__Host-timer-session=MTM2MzA4MJa8jA3OHxEdi1CQkFFQ180SUFBUkFCRUFBQVlQLUNBQUVHYzNSeWFXNW5EQXdBQ25ObGMzTnBiMjVmYVdRR2MzUnlhVzVuREQ0QVBIUnZaMmRzTFdGd2FTMXpaWE56YVc5dUxUSXRaalU1WmpaalpEUTVOV1ZsTVRoaE1UaGhaalpqWkRkbU5XWTJNV0psWVRnd09EWmlPVEV3WkE9PXweAkG7kI6NBG-iqvhNn1MSDhkz2Pz_UYTzdBvZjCaA==; Path=/; Expires=Wed, 13 Mar 2013 09:54:38 UTC; Max-Age=86400; HttpOnly") 179 | io.WriteString(w, "") 180 | } else { 181 | w.WriteHeader(404) 182 | } 183 | } 184 | server := httptest.NewServer(http.HandlerFunc(h)) 185 | defer server.Close() 186 | testLogger := &TestLogger{t} 187 | client, err := NewClient("abc1234567890def", SetURL(server.URL), SetTraceLogger(testLogger)) 188 | 189 | if err != nil { 190 | t.Fatal("Error") 191 | } 192 | r, err := client.GetRequest(server.URL + "/whatever") 193 | if r != nil { 194 | t.Fatal("r should be null") 195 | } 196 | if err != nil { 197 | t.Fatalf("err should be null %v", err) 198 | } 199 | 200 | } 201 | -------------------------------------------------------------------------------- /gthttp/httpclient.go: -------------------------------------------------------------------------------- 1 | package gthttp 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "github.com/throttled/throttled" 9 | "github.com/throttled/throttled/store/memstore" 10 | "io/ioutil" 11 | "net/http" 12 | "strconv" 13 | "time" 14 | ) 15 | 16 | const ( 17 | DefaultAuthPassword = "api_token" 18 | DefaultMaxRetries = 5 19 | DefaultGzipEnabled = false 20 | DefaultUrl = "https://www.toggl.com/api/v8" 21 | DefaultVersion = "v8" 22 | SessionCookieName = "__Host-timer-session" 23 | defaultBucket = "toggl" 24 | DefaultRateLimitPerSecond = 3 25 | ) 26 | 27 | // Client is an Toggl REST client. Created by calling NewClient. 28 | type TogglHttpClient struct { 29 | client *http.Client // net/http Client to use for requests 30 | version string // v8 31 | Url string // set of URLs passed initially to the client 32 | errorLog Logger // error log for critical messages 33 | infoLog Logger // information log for e.g. response times 34 | traceLog Logger // trace log for debugging 35 | password string // password for HTTP Basic Auth 36 | maxRetries uint 37 | gzipEnabled bool // gzip compression enabled or disabled (default) 38 | rateLimiter *throttled.GCRARateLimiter 39 | perSec int 40 | cookie *http.Cookie 41 | } 42 | 43 | type TogglError struct { 44 | Code int 45 | Status string 46 | Msg string 47 | } 48 | 49 | type TogglResponse struct { 50 | Data *json.RawMessage `json:"data"` 51 | } 52 | 53 | func (e *TogglError) Error() string { 54 | return fmt.Sprintf("%s\t%s\n", e.Status, e.Msg) 55 | } 56 | 57 | // ClientOptionFunc is a function that configures a Client. 58 | // It is used in NewClient. 59 | type ClientOptionFunc func(*TogglHttpClient) error 60 | 61 | // Return a new TogglHttpClient . An error is also returned when some configuration option is invalid 62 | // tc,err := gtoggl.NewClient("token") 63 | func NewClient(key string, options ...ClientOptionFunc) (*TogglHttpClient, error) { 64 | // Set up the client 65 | 66 | c := &TogglHttpClient{ 67 | client: http.DefaultClient, 68 | maxRetries: DefaultMaxRetries, 69 | Url: DefaultUrl, 70 | version: DefaultVersion, 71 | gzipEnabled: DefaultGzipEnabled, 72 | password: DefaultAuthPassword, 73 | errorLog: defaultLogger, 74 | infoLog: defaultLogger, 75 | traceLog: defaultLogger, 76 | } 77 | 78 | err := SetRateLimit(DefaultRateLimitPerSecond)(c) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | // Run the options on it 84 | for _, option := range options { 85 | if err := option(c); err != nil { 86 | return nil, err 87 | } 88 | } 89 | c.infoLog.Printf("Logging in with token: %s\n", key) 90 | 91 | if len(key) < 1 { 92 | c.errorLog.Printf("%s\n", "valid token required") 93 | return nil, errors.New("Token required") 94 | } 95 | 96 | if _, err = c.authenticate(key); err != nil { 97 | return nil, err 98 | } 99 | 100 | return c, nil 101 | } 102 | 103 | // SetHttpClient can be used to specify the http.Client to use when making 104 | // HTTP requests to Toggl 105 | func SetHttpClient(httpClient *http.Client) ClientOptionFunc { 106 | return func(c *TogglHttpClient) error { 107 | if httpClient != nil { 108 | c.client = httpClient 109 | } else { 110 | c.client = http.DefaultClient 111 | } 112 | return nil 113 | } 114 | } 115 | 116 | // SetURL defines the base URL. See DefaultUrl 117 | func SetURL(url string) ClientOptionFunc { 118 | return func(c *TogglHttpClient) error { 119 | c.Url = url 120 | return nil 121 | } 122 | } 123 | 124 | // SetRateLimit Set custom rate limit per second 125 | func SetRateLimit(perSec int) ClientOptionFunc { 126 | return func(c *TogglHttpClient) error { 127 | store, err := memstore.New(65536) 128 | if err != nil { 129 | return err 130 | } 131 | quota := throttled.RateQuota{throttled.PerSec(perSec), 1} 132 | c.rateLimiter, err = throttled.NewGCRARateLimiter(store, quota) 133 | if err != nil { 134 | return err 135 | } 136 | c.perSec = perSec 137 | return nil 138 | } 139 | } 140 | 141 | //Custom logger to print HTTP requests 142 | func SetTraceLogger(l Logger) ClientOptionFunc { 143 | return func(c *TogglHttpClient) error { 144 | c.traceLog = l 145 | return nil 146 | } 147 | } 148 | 149 | //Custom logger to handle error messages 150 | func SetErrorLogger(l Logger) ClientOptionFunc { 151 | return func(c *TogglHttpClient) error { 152 | c.errorLog = l 153 | return nil 154 | } 155 | } 156 | 157 | //Custom logger to handle info messages 158 | func SetInfoLogger(l Logger) ClientOptionFunc { 159 | return func(c *TogglHttpClient) error { 160 | c.infoLog = l 161 | return nil 162 | } 163 | } 164 | 165 | type nullLogger struct{} 166 | 167 | func (l *nullLogger) Printf(format string, v ...interface{}) { 168 | } 169 | 170 | var defaultLogger = &nullLogger{} 171 | 172 | func (c *TogglHttpClient) authenticate(key string) ([]byte, error) { 173 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/%s", c.Url, "sessions"), nil) 174 | if err != nil { 175 | return nil, err 176 | } 177 | c.dumpRequest(req) 178 | req.SetBasicAuth(key, DefaultAuthPassword) 179 | resp, err := c.client.Do(req) 180 | if err != nil { 181 | return nil, err 182 | } 183 | c.dumpResponse(resp) 184 | 185 | if resp.StatusCode >= 400 { 186 | defer resp.Body.Close() 187 | b, _ := ioutil.ReadAll(resp.Body) 188 | return nil, &TogglError{Code: resp.StatusCode, Status: resp.Status, Msg: string(b)} 189 | } 190 | 191 | hasSessionCookie := false 192 | for _, value := range resp.Cookies() { 193 | if value.Name == SessionCookieName { 194 | c.infoLog.Printf("Setting Cookie\n") 195 | c.cookie = value 196 | hasSessionCookie = true 197 | break 198 | } 199 | } 200 | 201 | if !hasSessionCookie { 202 | return nil, fmt.Errorf("Auth cookie %q not found on authentication response", SessionCookieName) 203 | } 204 | 205 | return nil, nil 206 | } 207 | 208 | func requestWithLimit(c *TogglHttpClient, method, endpoint string, b interface{}, attempt int) (*json.RawMessage, error) { 209 | c.infoLog.Printf("Request attempt %d for %s %s\n", attempt, method, endpoint) 210 | if attempt > DefaultMaxRetries { 211 | return nil, errors.New("Max Retries exceeded: " + strconv.FormatInt(DefaultMaxRetries, 10)) 212 | } 213 | var body []byte 214 | var err error 215 | 216 | limited, reason, err := c.rateLimiter.RateLimit(defaultBucket, 1) 217 | if err != nil { 218 | return nil, err 219 | } 220 | 221 | if limited { 222 | c.traceLog.Printf("Hit rate limit. Sleeping for %f ms.\n", float64(reason.RetryAfter)/1000000) 223 | time.Sleep(reason.RetryAfter) 224 | return requestWithLimit(c, method, endpoint, b, attempt+1) 225 | } 226 | 227 | if b != nil { 228 | if body, err = json.Marshal(b); err != nil { 229 | return nil, err 230 | } 231 | } 232 | 233 | req, err := http.NewRequest(method, endpoint, bytes.NewBuffer(body)) 234 | if err != nil { 235 | return nil, err 236 | } 237 | req.AddCookie(c.cookie) 238 | c.dumpRequest(req) 239 | resp, err := c.client.Do(req) 240 | if err != nil { 241 | return nil, err 242 | } 243 | c.dumpResponse(resp) 244 | defer resp.Body.Close() 245 | 246 | js, err := ioutil.ReadAll(resp.Body) 247 | if err != nil { 248 | return nil, err 249 | } 250 | if resp.StatusCode == 429 { 251 | c.errorLog.Printf("Hit (429) rate limit. Sleeping for %d ms.\n", attempt*1000) 252 | time.Sleep(time.Millisecond * time.Duration(attempt*1000)) 253 | return requestWithLimit(c, method, endpoint, b, attempt+1) 254 | } 255 | if resp.StatusCode == 404 { 256 | return nil, nil 257 | } 258 | if resp.StatusCode >= 400 { 259 | return nil, &TogglError{Code: resp.StatusCode, Status: resp.Status, Msg: string(js)} 260 | } 261 | var raw json.RawMessage 262 | if json.Unmarshal(js, &raw) != nil { 263 | return nil, err 264 | } 265 | return &raw, err 266 | } 267 | 268 | func request(c *TogglHttpClient, method, endpoint string, b interface{}) (*json.RawMessage, error) { 269 | return requestWithLimit(c, method, endpoint, b, 1) 270 | } 271 | 272 | // Utility to POST requests 273 | func (c *TogglHttpClient) PostRequest(endpoint string, body interface{}) (*json.RawMessage, error) { 274 | return request(c, "POST", endpoint, body) 275 | } 276 | 277 | // Utility to DELETE requests 278 | func (c *TogglHttpClient) DeleteRequest(endpoint string, body interface{}) (*json.RawMessage, error) { 279 | return request(c, "DELETE", endpoint, body) 280 | } 281 | 282 | // Utility to PUT requests 283 | func (c *TogglHttpClient) PutRequest(endpoint string, body interface{}) (*json.RawMessage, error) { 284 | return request(c, "PUT", endpoint, body) 285 | } 286 | 287 | // Utility to GET requests 288 | func (c *TogglHttpClient) GetRequest(endpoint string) (*json.RawMessage, error) { 289 | return request(c, "GET", endpoint, nil) 290 | } 291 | --------------------------------------------------------------------------------