From 1a69cd2e8b919ab450b42802ae578c2d23948a07 Mon Sep 17 00:00:00 2001 From: Cara Salter Date: Sun, 3 Sep 2023 11:51:38 -0400 Subject: Initial commit Create API client for grocy --- .gitignore | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ go.sum | 2 ++ grocy/chores.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ grocy/grocy.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ grocy/user.go | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 37 +++++++++++++++++++++++++++++++++++++ 6 files changed, 253 insertions(+) create mode 100644 .gitignore create mode 100644 go.sum create mode 100644 grocy/chores.go create mode 100644 grocy/grocy.go create mode 100644 grocy/user.go create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2400acd --- /dev/null +++ b/.gitignore @@ -0,0 +1,51 @@ +# Created by https://www.toptal.com/developers/gitignore/api/go,vim,dotenv +# Edit at https://www.toptal.com/developers/gitignore?templates=go,vim,dotenv + +### dotenv ### +.env + +### Go ### +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +### Vim ### +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +# End of https://www.toptal.com/developers/gitignore/api/go,vim,dotenv diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d61b19e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= diff --git a/grocy/chores.go b/grocy/chores.go new file mode 100644 index 0000000..3dcdf2e --- /dev/null +++ b/grocy/chores.go @@ -0,0 +1,53 @@ +package grocy + +import ( + "context" + "fmt" + "net/http" +) + +type Chore struct { + ID int `json:"id"` + ChoreID int `json:"chore_id"` + ChoreName string `json:"chore_name"` + LastTrackedTime string `json:"last_tracked_time"` + NextEstimatedExecutionTime string `json:"next_estimated_execution_time"` + TrackDateOnly int `json:"track_date_only"` + NextExecutionAssignedToUserID int `json:"next_execution_assigned_to_user_id"` + IsRescheduled int `json:"is_rescheduled"` + IsReassigned int `json:"is_reassigned"` + NextExecutionAssignedUser User `json:"next_execution_assigned_user"` +} + +type ChoreList []Chore + +func (c *Client) GetChores(ctx context.Context) (ChoreList, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/chores", c.BaseUrl), nil) + + if err != nil { + return nil, err + } + req.WithContext(ctx) + + var res ChoreList + if err := c.sendRequest(req, &res); err != nil { + return nil, err + } + + return res, nil +} + +func (c *Client) GetChore(ctx context.Context, choreId int) (Chore, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/chores/%d", c.BaseUrl, choreId), nil) + + if err != nil { + return *new(Chore), err + } + req.WithContext(ctx) + + var res Chore + if err := c.sendRequest(req, &res); err != nil { + return *new(Chore), err + } + return res, nil +} diff --git a/grocy/grocy.go b/grocy/grocy.go new file mode 100644 index 0000000..55c3d2d --- /dev/null +++ b/grocy/grocy.go @@ -0,0 +1,57 @@ +package grocy + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "time" +) + +type Client struct { + BaseUrl string + apiKey string + HTTPClient *http.Client +} + +func NewClient(baseUrl, apiKey string) *Client { + return &Client{ + BaseUrl: baseUrl, + apiKey: apiKey, + HTTPClient: &http.Client{ + Timeout: time.Minute, + }, + } +} + +type ErrorResponse struct { + ErrorMessage string `json:"error_message"` +} + +func (c *Client) sendRequest(req *http.Request, v interface{}) error { + req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Accept", "application/json; charset=utf-8") + req.Header.Set("GROCY-API-KEY", c.apiKey) + + res, err := c.HTTPClient.Do(req) + if err != nil { + return err + } + + defer res.Body.Close() + + if res.StatusCode < http.StatusOK || res.StatusCode >= http.StatusBadRequest { + var errRes ErrorResponse + if err = json.NewDecoder(res.Body).Decode(&errRes); err == nil { + return errors.New(errRes.ErrorMessage) + } + + return fmt.Errorf("unknown error, status code: %d", res.StatusCode) + } + + if err = json.NewDecoder(res.Body).Decode(&v); err != nil { + return err + } + + return nil +} diff --git a/grocy/user.go b/grocy/user.go new file mode 100644 index 0000000..58d3d08 --- /dev/null +++ b/grocy/user.go @@ -0,0 +1,53 @@ +package grocy + +import ( + "context" + "fmt" + "net/http" +) + +type ( + User struct { + Id int `json:"id"` + Username string `json:"username"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + RowCreated string `json:"row_created_timestamp"` + DisplayName string `json:"display_name"` + PictureFileName string `json:"picture_file_name"` + } +) + +func (c *Client) GetUsers(ctx context.Context) ([]User, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/users", c.BaseUrl), nil) + + if err != nil { + return nil, err + } + req.Header.Add("GROCY-API-KEY", c.apiKey) + req.WithContext(ctx) + + var res []User + if err := c.sendRequest(req, &res); err != nil { + return nil, err + } + + return res, nil +} + +func (c *Client) GetUserFields(ctx context.Context, userId int) (map[string]string, error) { + req, err := http.NewRequest("GET", fmt.Sprintf("%s/userfields/users/%d", c.BaseUrl, userId), nil) + + if err != nil { + return nil, err + } + + req.WithContext(ctx) + + var res map[string]string + if err := c.sendRequest(req, &res); err != nil { + return nil, err + } + + return res, nil +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..e0777ab --- /dev/null +++ b/main.go @@ -0,0 +1,37 @@ +package main + +import ( + "context" + "fmt" + "log" + "os" + + "git.devcara.com/grocy-reminders/grocy" + "github.com/joho/godotenv" +) + +func main() { + err := godotenv.Load(".env") + if err != nil { + log.Panic(err) + } + apiClient := grocy.NewClient(os.Getenv("GROCY_BASE_URL"), os.Getenv("GROCY_API_KEY")) + + res, err := apiClient.GetUsers(context.Background()) + + if err != nil { + log.Panic(err) + } + + for _, u := range res { + + userfield, err := apiClient.GetUserFields(context.Background(), u.Id) + + if err != nil { + log.Panic(err) + } + + fmt.Printf("%s (%s): %s\n", u.Username, u.DisplayName, userfield["discordid"]) + } + +} -- cgit v1.2.3