Skip to content

Commit 2f8e0ee

Browse files
authored
Merge pull request #2 from snturk/feature/1/sync_integration
feature: Sync Support
2 parents 645ca94 + 4289ae2 commit 2f8e0ee

14 files changed

+401
-16
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ go.work
2727
.DS_Store
2828

2929
# Time entries for Timmy
30-
.timmy.entries/
30+
.timmy.entries/
31+
timmy

README.md

+45-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,50 @@ TODO
1212

1313
## Usage
1414

15+
16+
### Define environment variables
17+
18+
Timmy uses environment variables to connect and sync to toggl and place time entries. You can define these variables in your `.bashrc` or `.zshrc` file.
19+
20+
```bash
21+
# Timmy environment variables
22+
# Optional, by default it will use $HOME/.timmy-entries
23+
export TIMMY_PATH="path/to/.timmy-entries"
24+
export TOGGL_WORKSPACE_ID="WORKSPACE_ID"
25+
export TOGGL_API_TOKEN="API_TOKEN"
26+
```
27+
### Commands
28+
29+
`start` starts a new time entry with a task name `--task`.
30+
31+
```bash
32+
timmy start --task "My task"
33+
```
34+
35+
`stop` stops the current time entry.
36+
37+
```bash
38+
timmy stop
39+
```
40+
41+
`current` shows the current time entry, if any.
42+
43+
```bash
44+
timmy current
45+
```
46+
47+
`today` shows all time entries for today.
48+
49+
```bash
50+
timmy today
51+
```
52+
53+
`sync` syncs non-synced time entries with toggl, if you defined `TOGGL_WORKSPACE_ID` and `TOGGL_API_TOKEN` environment variables.
54+
55+
```bash
56+
timmy sync
57+
```
58+
1559
Just build and run the executable. It will create a `.timmy-entries` directory in your home directory and store all entries there.
1660

1761
```bash
@@ -23,4 +67,4 @@ go build
2367

2468
## Contributing
2569

26-
Pull requests are always welcome.
70+
Pull requests and issues are always welcome.

cmd/current.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
Copyright © 2024 Muratcan Senturk <[email protected]>
3+
*/
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"github.com/snturk/timmy/internal/service"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var currentCmd = &cobra.Command{
13+
Use: "current",
14+
Short: "Shows the current time entry, if any.",
15+
Long: `Shows the current time entry, if any.`,
16+
Run: func(cmd *cobra.Command, args []string) {
17+
err := service.PrintCurrent()
18+
if err != nil {
19+
fmt.Println(err)
20+
}
21+
},
22+
}
23+
24+
func init() {
25+
rootCmd.AddCommand(currentCmd)
26+
}

cmd/root.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
2+
Copyright © 2024 Muratcan Senturk <[email protected]>
33
*/
44
package cmd
55

cmd/start.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414
var startCmd = &cobra.Command{
1515
Use: "start",
1616
Short: "Start the time tracker",
17-
Long: `This will start the time tracker with provided task name.`,
17+
Long: `This will start the time tracker with provided task name. If there is a running time entry, it will return an error.`,
1818
Run: func(cmd *cobra.Command, args []string) {
1919
// Task name is required
2020
if len(args) < 1 {

cmd/stop.go

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
/*
2-
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
2+
Copyright © 2024 Muratcan Senturk <[email protected]>
33
*/
44
package cmd
55

66
import (
77
"fmt"
8+
89
"github.com/snturk/timmy/internal/service"
910
"github.com/spf13/cobra"
1011
)
1112

1213
// stopCmd represents the stop command
1314
var stopCmd = &cobra.Command{
1415
Use: "stop",
15-
Short: "A brief description of your command",
16-
Long: `A longer description that spans multiple lines and likely contains examples
17-
and usage of using your command. For example:
18-
19-
Cobra is a CLI library for Go that empowers applications.
20-
This application is a tool to generate the needed files
21-
to quickly create a Cobra application.`,
16+
Short: "Stops the currently running time entry.",
17+
Long: `Stops the currently running time entry. If there is no running time entry, it will return an error.`,
2218
Run: func(cmd *cobra.Command, args []string) {
2319
fmt.Print("Stopping time tracker... ")
2420
err := service.StopTimeEntry()

cmd/sync.go

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
3+
*/
4+
package cmd
5+
6+
import (
7+
"fmt"
8+
"github.com/snturk/timmy/internal/service"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
// syncCmd represents the sync command
13+
var syncCmd = &cobra.Command{
14+
Use: "sync",
15+
Short: "Sync local time entry file with Toggl.",
16+
Long: `Sync local time entry file with Toggl. Only the entries that are not fetched from Toggl will be fetched.`,
17+
Run: func(cmd *cobra.Command, args []string) {
18+
err := service.FetchTodayToToggl()
19+
if err != nil {
20+
fmt.Printf("error while fetching today's time entries to Toggl: %v\n", err)
21+
} else {
22+
fmt.Println("successfully fetched today's time entries to Toggl")
23+
}
24+
},
25+
}
26+
27+
func init() {
28+
rootCmd.AddCommand(syncCmd)
29+
30+
// Here you will define your flags and configuration settings.
31+
32+
// Cobra supports Persistent Flags which will work for this command
33+
// and all subcommands, e.g.:
34+
// syncCmd.PersistentFlags().String("foo", "", "A help for foo")
35+
36+
// Cobra supports local flags which will only run when this command
37+
// is called directly, e.g.:
38+
// syncCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
39+
}

cmd/today.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
/*
2-
Copyright © 2024 NAME HERE <EMAIL ADDRESS>
2+
Copyright © 2024 Muratcan Senturk <[email protected]>
33
*/
44
package cmd
55

66
import (
77
"fmt"
8+
89
"github.com/snturk/timmy/internal/service"
910
"github.com/spf13/cobra"
1011
)

internal/config/config.go

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package config
2+
3+
import (
4+
"os"
5+
"strconv"
6+
)
7+
8+
// Model represents the configurations of the application.
9+
type Model struct {
10+
// Toggl API token.
11+
TogglApiKey string `json:"toggl_api_key"`
12+
// Toggl workspace ID.
13+
TogglWorkspaceId int64 `json:"toggl_workspace_id"`
14+
}
15+
16+
// GetConfig returns the configuration of the application.
17+
func GetConfig() (Model, error) {
18+
var config Model
19+
workspaceId, err := strconv.ParseInt(os.Getenv("TOGGL_WORKSPACE_ID"), 10, 64)
20+
if err != nil {
21+
return config, err
22+
}
23+
24+
togglApiToken := os.Getenv("TOGGL_API_TOKEN")
25+
26+
config = Model{
27+
TogglApiKey: togglApiToken,
28+
TogglWorkspaceId: workspaceId,
29+
}
30+
31+
return config, nil
32+
}

internal/constants/common.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package constants
22

33
const (
4-
DateTimeFormat = "2006-01-02 15:04:05"
4+
DateTimeFormat = "2006-01-02T15:04:05-07:00"
55
FileDataDivider = " | "
66
)

internal/model/time-entry.go

+21-1
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ package model
22

33
import (
44
"bytes"
5-
"github.com/snturk/timmy/internal/constants"
65
"strconv"
76
"time"
7+
8+
"github.com/snturk/timmy/internal/constants"
89
)
910

1011
// TimeEntry represents a single time entry.
@@ -17,6 +18,8 @@ type TimeEntry struct {
1718
Task string `json:"task"`
1819
// Is the entry currently running?
1920
Running bool `json:"running"`
21+
// Is the entry fetched with Toggl?
22+
Fetched bool `json:"fetched"`
2023
}
2124

2225
// String returns a string representation of the time entry.
@@ -30,10 +33,21 @@ func (entry TimeEntry) String() string {
3033
entryString.WriteString(entry.Task)
3134
entryString.WriteString(constants.FileDataDivider)
3235
entryString.WriteString(strconv.FormatBool(entry.Running))
36+
entryString.WriteString(constants.FileDataDivider)
37+
entryString.WriteString(strconv.FormatBool(entry.Fetched))
3338

3439
return entryString.String()
3540
}
3641

42+
// GetDuration returns the duration of the time entry.
43+
func (entry TimeEntry) GetDuration() time.Duration {
44+
if entry.Running {
45+
return time.Now().Local().Sub(entry.Start)
46+
} else {
47+
return entry.End.Sub(entry.Start)
48+
}
49+
}
50+
3751
// ParseTimeEntry parses a string representation of a time entry.
3852
func ParseTimeEntry(entryString string) (TimeEntry, error) {
3953
var entry TimeEntry
@@ -63,5 +77,11 @@ func ParseTimeEntry(entryString string) (TimeEntry, error) {
6377
return entry, err
6478
}
6579

80+
// Parse the fetched status.
81+
entry.Fetched, err = strconv.ParseBool(string(entryData[4]))
82+
if err != nil {
83+
return entry, err
84+
}
85+
6686
return entry, nil
6787
}

internal/model/toggl.go

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package model
2+
3+
type TogglManualTimeRequest struct {
4+
Description string `json:"description"`
5+
Tags []string `json:"tags"`
6+
/**
7+
* Duration in seconds.
8+
*/
9+
Duration int `json:"duration"`
10+
Start string `json:"start"`
11+
WorkspaceId int `json:"workspace_id"`
12+
CreatedWith string `json:"created_with"`
13+
}

0 commit comments

Comments
 (0)