First checkin
This commit is contained in:
247
mite/mite.go
Normal file
247
mite/mite.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package mite
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type APIClient struct {
|
||||
domain string
|
||||
apiKey string
|
||||
}
|
||||
|
||||
func NewClient(domain, apikey string) APIClient {
|
||||
return APIClient{
|
||||
domain: domain,
|
||||
apiKey: apikey,
|
||||
}
|
||||
}
|
||||
|
||||
type APIObject interface {
|
||||
GetID() int
|
||||
GetName() string
|
||||
}
|
||||
|
||||
// https://demo.mite.de/
|
||||
//curl -H 'X-MiteApiKey: YOUR_API_KEY' https://demo.mite.de/projects.json
|
||||
|
||||
type APIProjects []ProjectHolder
|
||||
type ProjectHolder struct {
|
||||
Project Project `json:"project"`
|
||||
}
|
||||
type Projects []Project
|
||||
type Project struct {
|
||||
Budget int `json:"budget"`
|
||||
BudgetType string `json:"budget_type"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
CustomerID int `json:"customer_id"`
|
||||
HourlyRate int `json:"hourly_rate"`
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Note string `json:"note"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Archived bool `json:"archived"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
}
|
||||
|
||||
func (p Project) String() string {
|
||||
return fmt.Sprintf("%d: %s (%s)", p.ID, p.Name, p.CustomerName)
|
||||
}
|
||||
func (p Project) GetID() int { return p.ID }
|
||||
func (p Project) GetName() string { return p.Name }
|
||||
|
||||
func (a APIClient) GetProjects() (Projects, error) {
|
||||
// GET /projects.json
|
||||
p := APIProjects{}
|
||||
err := get(a.domain, a.apiKey, "/projects.json", &p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := Projects{}
|
||||
for i := range p {
|
||||
out = append(out, p[i].Project)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type apiCustomers []customerHolder
|
||||
type customerHolder struct {
|
||||
Customer Customer `json:"customer"`
|
||||
}
|
||||
|
||||
type Customers []Customer
|
||||
type Customer struct {
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
HourlyRate int `json:"hourly_rate"`
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Note string `json:"note"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Archived bool `json:"archived"`
|
||||
ActiveHourlyRate string `json:"active_hourly_rate"`
|
||||
HourlyRatesPerService []struct {
|
||||
ServiceID int `json:"service_id"`
|
||||
HourlyRate int `json:"hourly_rate"`
|
||||
} `json:"hourly_rates_per_service"`
|
||||
}
|
||||
|
||||
func (c Customer) String() string {
|
||||
return fmt.Sprintf("%d: %s", c.ID, c.Name)
|
||||
}
|
||||
func (c Customer) GetID() int { return c.ID }
|
||||
func (c Customer) GetName() string { return c.Name }
|
||||
|
||||
func (a APIClient) GetCustomers() (Customers, error) {
|
||||
// GET /customers.json
|
||||
p := apiCustomers{}
|
||||
err := get(a.domain, a.apiKey, "/customers.json", &p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := Customers{}
|
||||
for i := range p {
|
||||
out = append(out, p[i].Customer)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type apiServices []serviceHolder
|
||||
type serviceHolder struct {
|
||||
Service Service `json:"service"`
|
||||
}
|
||||
type Services []Service
|
||||
type Service struct {
|
||||
Billable bool `json:"billable"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
HourlyRate int `json:"hourly_rate"`
|
||||
ID int `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Note string `json:"note"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Archived bool `json:"archived"`
|
||||
}
|
||||
|
||||
func (s Service) String() string {
|
||||
return fmt.Sprintf("%d: %s", s.ID, s.Name)
|
||||
}
|
||||
func (s Service) GetID() int { return s.ID }
|
||||
func (s Service) GetName() string { return s.Name }
|
||||
|
||||
func (a APIClient) GetServices() (Services, error) {
|
||||
// GET /services.json
|
||||
p := apiServices{}
|
||||
err := get(a.domain, a.apiKey, "/services.json", &p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := Services{}
|
||||
for i := range p {
|
||||
out = append(out, p[i].Service)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
type apiTimeEntry []timeEntryHolder
|
||||
type timeEntryHolder struct {
|
||||
TimeEntry TimeEntry `json:"time_entry"`
|
||||
}
|
||||
type TimeEntries []TimeEntry
|
||||
type TimeEntry struct {
|
||||
Billable bool `json:"billable"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
DateAt string `json:"date_at"`
|
||||
ID int `json:"id"`
|
||||
Locked bool `json:"locked"`
|
||||
Minutes int `json:"minutes"`
|
||||
StartedTime interface{} `json:"started_time"`
|
||||
ProjectID int `json:"project_id"`
|
||||
Revenue float64 `json:"revenue"`
|
||||
HourlyRate int `json:"hourly_rate"`
|
||||
ServiceID int `json:"service_id"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
UserID int `json:"user_id"`
|
||||
Note string `json:"note"`
|
||||
UserName string `json:"user_name"`
|
||||
CustomerID int `json:"customer_id"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
ProjectName string `json:"project_name"`
|
||||
ServiceName string `json:"service_name"`
|
||||
}
|
||||
|
||||
func (t TimeEntry) String() string {
|
||||
return fmt.Sprintf("%d: %s - %s for %dm", t.ID, t.DateAt, t.CustomerName, t.Minutes)
|
||||
}
|
||||
|
||||
func (te TimeEntries) ByDate(t time.Time) TimeEntries {
|
||||
ymd := t.Format(time.DateOnly)
|
||||
out := TimeEntries{}
|
||||
for i := range te {
|
||||
if te[i].DateAt == ymd {
|
||||
out = append(out, te[i])
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (te TimeEntries) MinutesByDate() map[string]int {
|
||||
out := map[string]int{}
|
||||
for i := range te {
|
||||
dt := te[i].DateAt
|
||||
if _, ok := out[dt]; ok {
|
||||
out[dt] += te[i].Minutes
|
||||
} else {
|
||||
out[dt] = te[i].Minutes
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// GetTimeEntries the "to" time is inclusive - ie time entries on that date are included
|
||||
func (a APIClient) GetTimeEntries(from, to time.Time) (TimeEntries, error) {
|
||||
// GET /... YYYY-MM-DD from= to=
|
||||
p := apiTimeEntry{}
|
||||
u := fmt.Sprintf("/time_entries.json?from=%s&to=%s", from.Format(time.DateOnly), to.Format(time.DateOnly))
|
||||
|
||||
err := get(a.domain, a.apiKey, u, &p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := TimeEntries{}
|
||||
for i := range p {
|
||||
out = append(out, p[i].TimeEntry)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func get(domain, apiKey, path string, data any) error {
|
||||
req, err := http.NewRequest("GET", baseurl(domain, path), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Add("X-MiteApiKey", apiKey)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode >= 300 {
|
||||
return fmt.Errorf("expected 2XX, got %d", resp.StatusCode)
|
||||
}
|
||||
err = json.NewDecoder(resp.Body).Decode(&data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func baseurl(domain, path string) string {
|
||||
return fmt.Sprintf("https://%s.mite.de/%s", domain, path)
|
||||
}
|
||||
Reference in New Issue
Block a user