You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

258 lines
9.4 KiB
Go

package insight
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"strings"
)
const (
BaseApiUrl = "https://jds.projectinsight.net/api/"
insightTokenFile = "insight.token"
projectEndpoint = ""
projectListEndpoint = "project/list?ids="
timeEntryForUserInRangeEndpoint = "time-entry/user-date-range?startDate=2020-01-01&endDate=%s&userId=%s"
userEndpoint = "user/list-active?modelProperties=Id,FirstName,LastName,EmailAddress"
)
type (
Interconnect struct {
Client *IClient
DBConnector *DBConnector
}
IClient struct {
client *http.Client
tokens *map[string]string
}
Project struct {
BillableExpenseVarianceEarnedValueBillableVsWorkBillableTotalPercent float64 `json:"BillableExpense_VarianceEarnedValueBillableVsWorkBillableTotalPercent,omitempty"`
CompanyDefaultId string `json:"CompanyDefault_Id,omitempty"`
CustomFieldValueId string `json:"CustomFieldValue_Id,omitempty"`
DepartmentId string `json:"Department_Id,omitempty"`
DurationSeconds int64
DurationString string
EndDateTimeUTC string
EstimateAtCompletionToTargetTotalVariance float64 `json:"EstimateAtCompletionToTargetTotal_Variance,omitempty"`
EstimateAtCompletionToTargetTotalPercentVariance float64 `json:"EstimateAtCompletionToTargetTotalPercent_Variance,omitempty"`
IsActive bool
IsOpen bool
LocationDefaultId string `json:"LocationDefault_Id,omitempty"`
Name string
ProfitSalesOrderActual float64
ProjectPriorityId string `json:"ProjectPriority_Id,omitempty"`
ProjectState int64 `json:"project_state,omitempty"`
ProjectStatusId string `json:"ProjectStatus_Id,omitempty"`
ProjectTypeId string `json:"ProjectType_Id,omitempty"`
ScheduleStartDate string
StartDateTimeUTC string
UrlFull string
VarSalesOrderExpenseVsBillableExpense float64
VarSalesOrderExpenseVsBillableExpensePercent float64
VarSalesOrderExpenseVsInvoiceExpense float64
VarSalesOrderExpenseVsInvoiceExpensePercent float64
VarSalesOrderHoursVsBillableHours float64
VarSalesOrderHoursVsBillableHoursPercent float64
VarSalesOrderHoursVsInvoiceHours float64
VarSalesOrderHoursVsInvoiceHoursPercent float64
VarSalesOrderRateVsBillableRate float64
VarSalesOrderRateVsBillableRatePercent float64
VarSalesOrderRateVsInvoiceRate float64
VarSalesOrderRateVsInvoiceRatePercent float64
VarSalesOrderTimeVsBillableTime float64
VarSalesOrderTimeVsBillableTimePercent float64
VarSalesOrderTimeVsInvoiceTime float64
VarSalesOrderTimeVsInvoiceTimePercent float64
VarSalesOrderTotalVsBillableTotal float64
VarSalesOrderTotalVsBillableTotalPercent float64
VarSalesOrderTotalVsInvoiceTotal float64
VarSalesOrderTotalVsInvoiceTotalPercent float64
WorkPercentComplete float64
WorkSeconds int64
}
TimeEntry struct {
ActualHours float64
ActualHoursFormattedString string
ActualTimeString string
ActualTotal float64
BillableHours float64
BillableHoursFormattedString string
BillableTimeString string
BillableTotal float64
Date string
Description string
ProjectId string `json:"Project_Id,omitempty"`
RateBill float64
RateBurden float64
TaskId string `json:"Task_Id,omitempty"`
TimeSheetId string `json:"TimeSheet_Id,omitempty"`
UserId string `json:"User_Id,omitempty"`
}
User struct {
Id string
FirstName string
LastName string
EmailAddress string
}
)
//<editor-fold name="interconnect">
/*======================================================================================
interconnect
======================================================================================*/
func NewInsightConnect() *Interconnect {
connect := Interconnect{}
connect.Client = NewIClient()
connect.DBConnector = &DBConnector{}
return &connect
}
func (ic *Interconnect) ResetTables() {
ic.DBConnector.CreateTables()
}
func (ic *Interconnect) UpdateUsers() {
users := ic.Client.GetUsers()
ic.DBConnector.UpdateUsers(users)
}
func (ic *Interconnect) UpdateTimeEntries() {
users := ic.DBConnector.FetchUsers()
for _, userPtr := range *users {
user := *userPtr
entries := ic.Client.GetTimeAllTimeEntriesForUserThroughDate(user.Id, "2022-04-28")
ic.DBConnector.UpdateTimeEntries(entries)
}
}
//</editor-fold>
//<editor-fold name="IClient">
/*======================================================================================
IClient
======================================================================================*/
func NewIClient() *IClient {
c := IClient{
client: &http.Client{},
tokens: cacheTokens(),
}
return &c
}
func (iClient *IClient) GetTimeAllTimeEntriesForUserThroughDate(userId string, endDate string) *[]*TimeEntry {
urlString := fmt.Sprintf(timeEntryForUserInRangeEndpoint, endDate, userId)
log.Println(urlString)
req := iClient.createRequest(urlString, insightTokenFile)
rawBytes := iClient.doGet(req)
container := make([]*TimeEntry, 0, 1000) //do we want to make the default result size parametric?
err := json.Unmarshal(*rawBytes, &container)
if err != nil {
log.Fatal(err)
}
return &container
}
func (iClient *IClient) GetProjectsInList(projectIds []string) *[]Project {
ids := strings.Join(projectIds, ",")
req := iClient.createRequest(projectListEndpoint+ids, insightTokenFile)
rawBytes := iClient.doGet(req)
container := make([]Project, 0, 1000) //do we want to make the default result size parametric?
err := json.Unmarshal(*rawBytes, &container)
if err != nil {
log.Fatal(err)
}
return &container
}
func (iClient *IClient) GetUsers() *[]User {
users := iClient.getRawUsers()
for idx, user := range *users {
user.EmailAddress = strings.ToLower(user.EmailAddress)
user.EmailAddress = strings.ReplaceAll(user.EmailAddress, "jdsdesignonline.com", "jdsconsulting.net")
user.EmailAddress = strings.ReplaceAll(user.EmailAddress, "jdsfaulkner.com", "jdsconsulting.net")
(*users)[idx] = user
}
return users
}
func (iClient *IClient) getRawUsers() *[]User {
req := iClient.createRequest(userEndpoint, insightTokenFile)
rawBytes := iClient.doGet(req)
container := make([]User, 0, 150) //do we want to make the default result size parametric?
err := json.Unmarshal(*rawBytes, &container)
if err != nil {
log.Fatal(err)
}
return &container
}
func (iClient *IClient) doGet(req *http.Request) *[]byte {
resp, err := iClient.client.Do(req)
if err != nil {
log.Fatal(err)
}
bodyBytes, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
return &bodyBytes
}
func (iClient *IClient) createRequest(urlEndpoint string, tokenFile string) *http.Request {
reqUrl := BaseApiUrl + urlEndpoint
token := (*iClient.tokens)[tokenFile]
request, err := http.NewRequest("GET", reqUrl, nil)
if err != nil {
log.Fatal(err)
}
request.Header.Add("api-token", token)
return request
}
//</editor-fold>
//<editor-fold name="utility functions">
/*======================================================================================
utility functions
======================================================================================*/
func cacheTokens() *map[string]string {
files, err := os.ReadDir("./tokens/")
ret := make(map[string]string)
if err != nil {
panic(err)
}
for _, file := range files {
subPath := fmt.Sprintf("./tokens/%s", file.Name())
fileContents, err := os.ReadFile(subPath)
if err != nil {
log.Fatal(err)
}
ret[file.Name()] = string(fileContents)
}
return &ret
}
//</editor-fold>