Skip to content

Commit

Permalink
Merge pull request #1 from agallou/history
Browse files Browse the repository at this point in the history
on permet de récupérer les entrées plus de 3 mois avant
  • Loading branch information
agallou committed Feb 26, 2023
2 parents 18bda20 + 77d0699 commit 714f320
Showing 1 changed file with 111 additions and 102 deletions.
213 changes: 111 additions & 102 deletions gotoggl/gotoggl.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import (
"io/ioutil"
"log"
"net/http"
"strconv"
"time"
"strings"
"io"
)

var _ = json.Unmarshal
Expand All @@ -22,38 +23,18 @@ const (
UserAgent = "github.com/roessland/gotoggl"
)

// Duration encapsulates the standard Duration in an anonymous field. Toggl
// returns durations in seconds, but time.Duration uses nanoseconds. Therefore
// we have to implement a custom UnmarshalJSON.
type Duration struct{ time.Duration }

// UnmarshalJSON loads a Toggl duration into a Go duration. Toggl durations are
// given in seconds.
func (d *Duration) UnmarshalJSON(data []byte) error {
seconds, err := strconv.ParseInt(string(data), 10, 64)
if err != nil {
fmt.Errorf("Couldn't unmarshal toggl.Duration: %v\n", err)
}
d.Duration = time.Duration(seconds * int64(time.Second))
return nil
}

// TimeEntry contains the data returned for a single time entry.
type TimeEntry struct {
Id int
Description string
WorkspaceId int `json:"wid"`
ProjectId int `json:"pid"`
Guid string
Billable bool
Start time.Time
Stop time.Time
Duration Duration
DurOnly bool
UserId int `json:"uid"`
CreatedWith string `json:"created_with"`
Duration time.Duration
Tags []string
At string
}

type Me struct {
Id int
DefaultWorkspaceId int `json:"default_workspace_id"`
}

// TimeEntryResponse is a wrapper for the data returned by /time_entries
Expand All @@ -69,29 +50,108 @@ type TimeEntriesService struct {
client *Client
}

// Get returns details of a single time entry
func (tes *TimeEntriesService) Get(id int) (TimeEntry, error) {
panic("Get() Not yet implemented")
return TimeEntry{}, nil
type searchRequest struct {
EndDate string `json:"end_date"`
StartDate string `json:"start_date"`
}

// Current returns running time entry
func (tes *TimeEntriesService) Current() (TimeEntry, error) {
panic("Current() not yet implemented")
return TimeEntry{}, nil
type searchTimeEntry struct {
Description string
ProjectId int `json:"project_id"`
Billable bool
Start time.Time
TagIds []int `json:"tag_ids"`
TimeEntries []searchTimeEntryDetail `json:"time_entries"`
}

type searchTimeEntryDetail struct {
Start time.Time
Seconds int `json:"seconds"`
}

type TagItem struct {
Id int `json:"id"`
Name string `json:"name"`
}

// Range returns time entries started in a specific time range. Only the first
// 1000 found time entries are returned. There is no pagination.
func (tes *TimeEntriesService) Range(start, end time.Time) ([]TimeEntry, error) {
timeEntries := []TimeEntry{}
t0 := start.Format(time.RFC3339)
t1 := end.Format(time.RFC3339)
path := fmt.Sprintf("me/time_entries?start_date=%s&end_date=%s", t0, t1)
err := tes.client.GET(path, &timeEntries)
// On récupère le workspace par défaut
me := Me{}
pathMe := fmt.Sprintf("me")
errMe := tes.client.GET(pathMe, &me)
if errMe != nil {
return nil, fmt.Errorf("Couldn't get me: %v\n", errMe)
}

fmt.Println(fmt.Sprintf("Default workspace ID found : %d", me.DefaultWorkspaceId))


// on liste tous les ids
allTags := []TagItem{}
errTags := tes.client.GET(fmt.Sprintf("workspaces/%d/tags", me.DefaultWorkspaceId), &allTags)
if errTags != nil {
return nil, fmt.Errorf("Couldn't get tags: %v\n", errTags)
}

// on prépare le body pour récupérer les time entries
aa := searchRequest{
EndDate: end.Format("2006-01-02"),
StartDate: start.Format("2006-01-02"),
}

s, errM := json.Marshal(aa)
if errM != nil {
return nil, errM

}

// on recherche les time entries
searchTimeEntries := []searchTimeEntry{}
path := fmt.Sprintf("workspace/%d/search/time_entries", me.DefaultWorkspaceId)
err := tes.client.POSTOnV3(path, strings.NewReader(string(s)), &searchTimeEntries)
if err != nil {
return nil, fmt.Errorf("Couldn't get time entries: %v\n", err)
}


// on met en forme les timeEntries
timeEntries := []TimeEntry{}

for _, searchTimeEntry := range searchTimeEntries {
te := TimeEntry{}

te.Description = searchTimeEntry.Description
te.ProjectId = searchTimeEntry.ProjectId

for _, searchTimeEntryDetail := range searchTimeEntry.TimeEntries {
te.Start = searchTimeEntryDetail.Start
d, _ := time.ParseDuration(fmt.Sprintf("%ds", searchTimeEntryDetail.Seconds))
te.Duration = d
}

var tags []string
for _, tagId := range searchTimeEntry.TagIds {
var tagName string
for _, allTag := range allTags {
if (allTag.Id == tagId) {
tagName = allTag.Name
}
}
if (0 == len(tagName)) {
panic(fmt.Sprintf("Name not found for tag id %d", tagId))
}

tags = append(tags, tagName)
}

te.Tags = tags

timeEntries = append(timeEntries, te)
}


return timeEntries, nil
}

Expand Down Expand Up @@ -160,13 +220,21 @@ func NewClient(apiKey string) *Client {
return c
}

// GET does a GET operation to the main API (not the reports API) and
// unmarshals the result into the given interface.
func (c *Client) GET(path string, response interface{}) error {
return c.genericDoRequest("GET", TogglApi, path, nil, response)
}

func (c *Client) POSTOnV3(path string, body io.Reader, response interface{}) error {
return c.genericDoRequest("POST", "https://api.track.toggl.com/reports/api/v3/", path, body, response)
}

func (c *Client) genericDoRequest(method string, endpoint string, path string, body io.Reader, response interface{}) error {
if len(path) > 0 && path[0] == '/' {
log.Print("Warning: Do not include / at the start of path")
}
req, _ := http.NewRequest("GET", TogglApi+path, nil)

req, _ := http.NewRequest(method, endpoint + path, body)

req.SetBasicAuth(c.ApiKey, "api_token")
resp, err := c.client.Do(req)
if err != nil {
Expand All @@ -191,64 +259,5 @@ func (c *Client) GET(path string, response interface{}) error {
return nil
}

/*

type TogglTimeEntry struct {
Id int
Description string
WorkspaceId int `json:"wid"`
ProjectId int `json:"pid"`
Guid string
Billable bool
Start time.Time
Stop time.Time
Duration int
DurOnly bool
UserId int `json:"uid"`
CreatedWith string `json:"created_with"`
Tags []string
At string
}
type TogglTimeEntryResponse struct {
Data TogglTimeEntry
}
type TogglProject struct {
ID int
GUID string
WID int
CID int
Name string
Billable bool
IsPrivate bool `json:"is_private"`
Active bool
Template bool
At time.Time
CreatedAt time.Time `json:"created_at"`
Color string
AutoEstimates bool `json:"auto_estimates"`
ActualHours int `json:"actual_hours"`
}
type TogglProjectResponse struct {
Data TogglProject
}
type TogglProjectSummary struct {
Id int
// Items []???
Time int // Duration in milliseconds
Title struct {
Client string
Color string
HexColor string `json:"hex_color"`
Project string
}
// TotalCurrencies []Currency `json:"total_currencies"`
}

type TogglProjectSummariesResponse struct {
Data []TogglProjectSummary
}
*/

0 comments on commit 714f320

Please sign in to comment.