mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
feat: implement summaries compat endpoint (resolve #44)
fix: fix all time view model
This commit is contained in:
parent
a8009e107d
commit
21567e7601
2
main.go
2
main.go
@ -96,6 +96,7 @@ func main() {
|
|||||||
settingsHandler := routes.NewSettingsHandler(userService)
|
settingsHandler := routes.NewSettingsHandler(userService)
|
||||||
publicHandler := routes.NewIndexHandler(userService, keyValueService)
|
publicHandler := routes.NewIndexHandler(userService, keyValueService)
|
||||||
compatV1AllHandler := v1Routes.NewCompatV1AllHandler(summaryService)
|
compatV1AllHandler := v1Routes.NewCompatV1AllHandler(summaryService)
|
||||||
|
compatV1SummariesHandler := v1Routes.NewCompatV1SummariesHandler(summaryService)
|
||||||
|
|
||||||
// Setup Routers
|
// Setup Routers
|
||||||
router := mux.NewRouter()
|
router := mux.NewRouter()
|
||||||
@ -143,6 +144,7 @@ func main() {
|
|||||||
|
|
||||||
// Compat V1 API Routes
|
// Compat V1 API Routes
|
||||||
compatV1Router.Path("/users/{user}/all_time_since_today").Methods(http.MethodGet).HandlerFunc(compatV1AllHandler.ApiGet)
|
compatV1Router.Path("/users/{user}/all_time_since_today").Methods(http.MethodGet).HandlerFunc(compatV1AllHandler.ApiGet)
|
||||||
|
compatV1Router.Path("/users/{user}/summaries").Methods(http.MethodGet).HandlerFunc(compatV1SummariesHandler.ApiGet)
|
||||||
|
|
||||||
// Static Routes
|
// Static Routes
|
||||||
router.PathPrefix("/assets").Handler(http.FileServer(http.Dir("./static")))
|
router.PathPrefix("/assets").Handler(http.FileServer(http.Dir("./static")))
|
||||||
|
@ -1,13 +1,36 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/muety/wakapi/models"
|
||||||
|
"github.com/muety/wakapi/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// https://wakatime.com/developers#all_time_since_today
|
// https://wakatime.com/developers#all_time_since_today
|
||||||
|
|
||||||
type AllTimeViewModel struct {
|
type WakatimeAllTime struct {
|
||||||
Data *AllTimeViewModelData `json:"data"`
|
Data *wakatimeAllTimeData `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AllTimeViewModelData struct {
|
type wakatimeAllTimeData struct {
|
||||||
Seconds float32 `json:"seconds"` // total number of seconds logged since account created
|
TotalSeconds float32 `json:"total_seconds"` // total number of seconds logged since account created
|
||||||
Text string `json:"text"` // total time logged since account created as human readable string>
|
Text string `json:"text"` // total time logged since account created as human readable string>
|
||||||
IsUpToDate bool `json:"is_up_to_date"` // true if the stats are up to date; when false, a 202 response code is returned and stats will be refreshed soon>
|
IsUpToDate bool `json:"is_up_to_date"` // true if the stats are up to date; when false, a 202 response code is returned and stats will be refreshed soon>
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAllTimeFrom(summary *models.Summary, filters *Filters) *WakatimeAllTime {
|
||||||
|
var total time.Duration
|
||||||
|
if key := filters.Project; key != "" {
|
||||||
|
total = summary.TotalTimeByKey(models.SummaryProject, key)
|
||||||
|
} else {
|
||||||
|
total = summary.TotalTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WakatimeAllTime{
|
||||||
|
Data: &wakatimeAllTimeData{
|
||||||
|
TotalSeconds: float32(total.Seconds()),
|
||||||
|
Text: utils.FmtWakatimeDuration(total),
|
||||||
|
IsUpToDate: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
5
models/compat/v1/common.go
Normal file
5
models/compat/v1/common.go
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
type Filters struct {
|
||||||
|
Project string
|
||||||
|
}
|
147
models/compat/v1/summaries.go
Normal file
147
models/compat/v1/summaries.go
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/muety/wakapi/models"
|
||||||
|
"github.com/muety/wakapi/utils"
|
||||||
|
"math"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// https://wakatime.com/developers#summaries
|
||||||
|
// https://pastr.de/v/736450
|
||||||
|
|
||||||
|
type WakatimeSummaries struct {
|
||||||
|
Data []*wakatimeSummariesData `json:"data"`
|
||||||
|
End time.Time `json:"end"`
|
||||||
|
Start time.Time `json:"start"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wakatimeSummariesData struct {
|
||||||
|
Categories []*wakatimeSummariesEntry `json:"categories"`
|
||||||
|
Dependencies []*wakatimeSummariesEntry `json:"dependencies"`
|
||||||
|
Editors []*wakatimeSummariesEntry `json:"editors"`
|
||||||
|
Languages []*wakatimeSummariesEntry `json:"languages"`
|
||||||
|
Machines []*wakatimeSummariesEntry `json:"machines"`
|
||||||
|
OperatingSystems []*wakatimeSummariesEntry `json:"operating_systems"`
|
||||||
|
Projects []*wakatimeSummariesEntry `json:"projects"`
|
||||||
|
GrandTotal *wakatimeSummariesGrandTotal `json:"grand_total"`
|
||||||
|
Range *wakatimeSummariesRange `json:"range"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wakatimeSummariesEntry struct {
|
||||||
|
Digital string `json:"digital"`
|
||||||
|
Hours int `json:"hours"`
|
||||||
|
Minutes int `json:"minutes"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Percent float64 `json:"percent"`
|
||||||
|
Seconds int `json:"seconds"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
TotalSeconds float64 `json:"total_seconds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wakatimeSummariesGrandTotal struct {
|
||||||
|
Digital string `json:"digital"`
|
||||||
|
Hours int `json:"hours"`
|
||||||
|
Minutes int `json:"minutes"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
TotalSeconds float64 `json:"total_seconds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type wakatimeSummariesRange struct {
|
||||||
|
Date string `json:"date"`
|
||||||
|
End time.Time `json:"end"`
|
||||||
|
Start time.Time `json:"start"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Timezone string `json:"timezone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSummariesFrom(summaries []*models.Summary, filters *Filters) *WakatimeSummaries {
|
||||||
|
data := make([]*wakatimeSummariesData, len(summaries))
|
||||||
|
minDate, maxDate := time.Now().Add(1*time.Second), time.Time{}
|
||||||
|
|
||||||
|
for i, s := range summaries {
|
||||||
|
data[i] = newDataFrom(s)
|
||||||
|
|
||||||
|
if s.FromTime.Before(minDate) {
|
||||||
|
minDate = s.FromTime
|
||||||
|
}
|
||||||
|
if s.ToTime.After(maxDate) {
|
||||||
|
maxDate = s.ToTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &WakatimeSummaries{
|
||||||
|
Data: data,
|
||||||
|
End: maxDate,
|
||||||
|
Start: minDate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDataFrom(s *models.Summary) *wakatimeSummariesData {
|
||||||
|
zone, _ := time.Now().Zone()
|
||||||
|
total := s.TotalTime()
|
||||||
|
totalHrs, totalMins := int(total.Hours()), int((total - time.Duration(total.Hours())*time.Hour).Minutes())
|
||||||
|
|
||||||
|
data := &wakatimeSummariesData{
|
||||||
|
Categories: make([]*wakatimeSummariesEntry, 0),
|
||||||
|
Dependencies: make([]*wakatimeSummariesEntry, 0),
|
||||||
|
Editors: make([]*wakatimeSummariesEntry, len(s.Editors)),
|
||||||
|
Languages: make([]*wakatimeSummariesEntry, len(s.Languages)),
|
||||||
|
Machines: make([]*wakatimeSummariesEntry, len(s.Machines)),
|
||||||
|
OperatingSystems: make([]*wakatimeSummariesEntry, len(s.OperatingSystems)),
|
||||||
|
Projects: make([]*wakatimeSummariesEntry, len(s.Projects)),
|
||||||
|
GrandTotal: &wakatimeSummariesGrandTotal{
|
||||||
|
Digital: fmt.Sprintf("%d:%d", totalHrs, totalMins),
|
||||||
|
Hours: totalHrs,
|
||||||
|
Minutes: totalMins,
|
||||||
|
Text: utils.FmtWakatimeDuration(total),
|
||||||
|
TotalSeconds: total.Seconds(),
|
||||||
|
},
|
||||||
|
Range: &wakatimeSummariesRange{
|
||||||
|
Date: time.Now().Format(time.RFC3339),
|
||||||
|
End: s.ToTime,
|
||||||
|
Start: s.FromTime,
|
||||||
|
Text: "",
|
||||||
|
Timezone: zone,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, e := range s.Projects {
|
||||||
|
data.Projects[i] = convertEntry(e, s.TotalTimeBy(models.SummaryProject))
|
||||||
|
}
|
||||||
|
for i, e := range s.Editors {
|
||||||
|
data.Editors[i] = convertEntry(e, s.TotalTimeBy(models.SummaryEditor))
|
||||||
|
}
|
||||||
|
for i, e := range s.Languages {
|
||||||
|
data.Languages[i] = convertEntry(e, s.TotalTimeBy(models.SummaryLanguage))
|
||||||
|
}
|
||||||
|
for i, e := range s.OperatingSystems {
|
||||||
|
data.OperatingSystems[i] = convertEntry(e, s.TotalTimeBy(models.SummaryOS))
|
||||||
|
}
|
||||||
|
for i, e := range s.Machines {
|
||||||
|
data.Machines[i] = convertEntry(e, s.TotalTimeBy(models.SummaryMachine))
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertEntry(e *models.SummaryItem, entityTotal time.Duration) *wakatimeSummariesEntry {
|
||||||
|
// this is a workaround, since currently, the total time of a summary item is mistakenly represented in seconds
|
||||||
|
// TODO: fix some day, while migrating persisted summary items
|
||||||
|
total := e.Total * time.Second
|
||||||
|
hrs := int(total.Hours())
|
||||||
|
mins := int((total - time.Duration(hrs)*time.Hour).Minutes())
|
||||||
|
secs := int((total - time.Duration(hrs)*time.Hour - time.Duration(mins)*time.Minute).Seconds())
|
||||||
|
|
||||||
|
return &wakatimeSummariesEntry{
|
||||||
|
Digital: fmt.Sprintf("%d:%d:%d", hrs, mins, secs),
|
||||||
|
Hours: hrs,
|
||||||
|
Minutes: mins,
|
||||||
|
Name: e.Key,
|
||||||
|
Percent: math.Round((total.Seconds()/entityTotal.Seconds())*1e4) / 100,
|
||||||
|
Seconds: secs,
|
||||||
|
Text: utils.FmtWakatimeDuration(total),
|
||||||
|
TotalSeconds: total.Seconds(),
|
||||||
|
}
|
||||||
|
}
|
@ -125,7 +125,6 @@ func (s *Summary) TotalTime() time.Duration {
|
|||||||
var timeSum time.Duration
|
var timeSum time.Duration
|
||||||
|
|
||||||
mappedItems := s.MappedItems()
|
mappedItems := s.MappedItems()
|
||||||
|
|
||||||
// calculate total duration from any of the present sets of items
|
// calculate total duration from any of the present sets of items
|
||||||
for _, t := range s.Types() {
|
for _, t := range s.Types() {
|
||||||
if items := mappedItems[t]; len(*items) > 0 {
|
if items := mappedItems[t]; len(*items) > 0 {
|
||||||
@ -136,15 +135,26 @@ func (s *Summary) TotalTime() time.Duration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return timeSum
|
return timeSum * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Summary) TotalTimeBy(entityType uint8, key string) time.Duration {
|
func (s *Summary) TotalTimeBy(entityType uint8) time.Duration {
|
||||||
var timeSum time.Duration
|
var timeSum time.Duration
|
||||||
|
|
||||||
mappedItems := s.MappedItems()
|
mappedItems := s.MappedItems()
|
||||||
|
if items := mappedItems[entityType]; len(*items) > 0 {
|
||||||
|
for _, item := range *items {
|
||||||
|
timeSum += item.Total
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// calculate total duration from any of the present sets of items
|
return timeSum * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Summary) TotalTimeByKey(entityType uint8, key string) time.Duration {
|
||||||
|
var timeSum time.Duration
|
||||||
|
|
||||||
|
mappedItems := s.MappedItems()
|
||||||
if items := mappedItems[entityType]; len(*items) > 0 {
|
if items := mappedItems[entityType]; len(*items) > 0 {
|
||||||
for _, item := range *items {
|
for _, item := range *items {
|
||||||
if item.Key != key {
|
if item.Key != key {
|
||||||
@ -154,5 +164,5 @@ func (s *Summary) TotalTimeBy(entityType uint8, key string) time.Duration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return timeSum
|
return timeSum * time.Second
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,8 @@ func NewCompatV1AllHandler(summaryService *services.SummaryService) *CompatV1All
|
|||||||
|
|
||||||
func (h *CompatV1AllHandler) ApiGet(w http.ResponseWriter, r *http.Request) {
|
func (h *CompatV1AllHandler) ApiGet(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
|
values, _ := url.ParseQuery(r.URL.RawQuery)
|
||||||
|
|
||||||
requestedUser := vars["user"]
|
requestedUser := vars["user"]
|
||||||
authorizedUser := r.Context().Value(models.UserKey).(*models.User)
|
authorizedUser := r.Context().Value(models.UserKey).(*models.User)
|
||||||
|
|
||||||
@ -33,10 +35,6 @@ func (h *CompatV1AllHandler) ApiGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
values, _ := url.ParseQuery(r.URL.RawQuery)
|
|
||||||
values.Set("interval", models.IntervalAny)
|
|
||||||
r.URL.RawQuery = values.Encode()
|
|
||||||
|
|
||||||
summary, err, status := h.loadUserSummary(authorizedUser)
|
summary, err, status := h.loadUserSummary(authorizedUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
@ -44,21 +42,7 @@ func (h *CompatV1AllHandler) ApiGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var total time.Duration
|
vm := v1.NewAllTimeFrom(summary, &v1.Filters{Project: values.Get("project")})
|
||||||
if key := values.Get("project"); key != "" {
|
|
||||||
total = summary.TotalTimeBy(models.SummaryProject, key)
|
|
||||||
} else {
|
|
||||||
total = summary.TotalTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
vm := &v1.AllTimeViewModel{
|
|
||||||
Data: &v1.AllTimeViewModelData{
|
|
||||||
Seconds: float32(total),
|
|
||||||
Text: utils.FmtWakatimeDuration(total * time.Second),
|
|
||||||
IsUpToDate: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
utils.RespondJSON(w, http.StatusOK, vm)
|
utils.RespondJSON(w, http.StatusOK, vm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
96
routes/compat/v1/summaries.go
Normal file
96
routes/compat/v1/summaries.go
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
package v1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/muety/wakapi/models"
|
||||||
|
v1 "github.com/muety/wakapi/models/compat/v1"
|
||||||
|
"github.com/muety/wakapi/services"
|
||||||
|
"github.com/muety/wakapi/utils"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompatV1SummariesHandler struct {
|
||||||
|
summarySrvc *services.SummaryService
|
||||||
|
config *models.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCompatV1SummariesHandler(summaryService *services.SummaryService) *CompatV1SummariesHandler {
|
||||||
|
return &CompatV1SummariesHandler{
|
||||||
|
summarySrvc: summaryService,
|
||||||
|
config: models.GetConfig(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
TODO: support parameters: branches, timeout, writes_only, timezone
|
||||||
|
https://wakatime.com/developers#summaries
|
||||||
|
timezone can be specified via an offset suffix (e.g. +02:00) in date strings
|
||||||
|
*/
|
||||||
|
|
||||||
|
func (h *CompatV1SummariesHandler) ApiGet(w http.ResponseWriter, r *http.Request) {
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
requestedUser := vars["user"]
|
||||||
|
authorizedUser := r.Context().Value(models.UserKey).(*models.User)
|
||||||
|
|
||||||
|
if requestedUser != authorizedUser.ID && requestedUser != "current" {
|
||||||
|
w.WriteHeader(http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
summaries, err, status := h.loadUserSummaries(r)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(status)
|
||||||
|
w.Write([]byte(err.Error()))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
vm := v1.NewSummariesFrom(summaries, &v1.Filters{})
|
||||||
|
utils.RespondJSON(w, http.StatusOK, vm)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *CompatV1SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary, error, int) {
|
||||||
|
user := r.Context().Value(models.UserKey).(*models.User)
|
||||||
|
params := r.URL.Query()
|
||||||
|
|
||||||
|
var start, end time.Time
|
||||||
|
// TODO: find out what other special dates are supported by wakatime (e.g. tomorrow, yesterday, ...?)
|
||||||
|
if startKey, endKey := params.Get("start"), params.Get("end"); startKey == "today" && startKey == endKey {
|
||||||
|
start = utils.StartOfToday()
|
||||||
|
end = time.Now()
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
start, err = time.Parse(time.RFC3339, strings.Replace(startKey, " ", "+", 1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("missing required 'start' parameter"), http.StatusBadRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
end, err = time.Parse(time.RFC3339, strings.Replace(endKey, " ", "+", 1))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("missing required 'end' parameter"), http.StatusBadRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
overallParams := &models.SummaryParams{
|
||||||
|
From: start,
|
||||||
|
To: end,
|
||||||
|
User: user,
|
||||||
|
Recompute: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
intervals := utils.SplitRangeByDays(overallParams.From, overallParams.To)
|
||||||
|
summaries := make([]*models.Summary, len(intervals))
|
||||||
|
|
||||||
|
for i, interval := range intervals {
|
||||||
|
summary, err := h.summarySrvc.Construct(interval[0], interval[1], user, false) // 'to' is always constant
|
||||||
|
if err != nil {
|
||||||
|
return nil, err, http.StatusInternalServerError
|
||||||
|
}
|
||||||
|
summaries[i] = summary
|
||||||
|
}
|
||||||
|
|
||||||
|
return summaries, nil, http.StatusOK
|
||||||
|
}
|
@ -77,7 +77,7 @@ func (srv *HeartbeatService) DeleteBefore(t time.Time) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (srv *HeartbeatService) CleanUp() error {
|
func (srv *HeartbeatService) CleanUp() error {
|
||||||
refTime := utils.StartOfDay().Add(-cleanUpInterval)
|
refTime := utils.StartOfToday().Add(-cleanUpInterval)
|
||||||
if err := srv.DeleteBefore(refTime); err != nil {
|
if err := srv.DeleteBefore(refTime); err != nil {
|
||||||
log.Printf("Failed to clean up heartbeats older than %v – %v\n", refTime, err)
|
log.Printf("Failed to clean up heartbeats older than %v – %v\n", refTime, err)
|
||||||
return err
|
return err
|
||||||
|
@ -43,15 +43,14 @@ func MakeConnectionString(config *models.Config) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func mySqlConnectionString(config *models.Config) string {
|
func mySqlConnectionString(config *models.Config) string {
|
||||||
location, _ := time.LoadLocation("Local")
|
//location, _ := time.LoadLocation("Local")
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=true&loc=%s",
|
||||||
"%s:%s@tcp(%s:%d)/%s?charset=utf8&parseTime=true&loc=%s",
|
|
||||||
config.DbUser,
|
config.DbUser,
|
||||||
config.DbPassword,
|
config.DbPassword,
|
||||||
config.DbHost,
|
config.DbHost,
|
||||||
config.DbPort,
|
config.DbPort,
|
||||||
config.DbName,
|
config.DbName,
|
||||||
location.String(),
|
"Local",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,9 +5,12 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartOfDay() time.Time {
|
func StartOfToday() time.Time {
|
||||||
ref := time.Now()
|
return StartOfDay(time.Now())
|
||||||
return time.Date(ref.Year(), ref.Month(), ref.Day(), 0, 0, 0, 0, ref.Location())
|
}
|
||||||
|
|
||||||
|
func StartOfDay(date time.Time) time.Time {
|
||||||
|
return time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartOfWeek() time.Time {
|
func StartOfWeek() time.Time {
|
||||||
@ -26,6 +29,21 @@ func StartOfYear() time.Time {
|
|||||||
return time.Date(ref.Year(), time.January, 1, 0, 0, 0, 0, ref.Location())
|
return time.Date(ref.Year(), time.January, 1, 0, 0, 0, 0, ref.Location())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SplitRangeByDays(from time.Time, to time.Time) [][]time.Time {
|
||||||
|
intervals := make([][]time.Time, 0)
|
||||||
|
|
||||||
|
for t1 := from; t1.Before(to); {
|
||||||
|
t2 := StartOfDay(t1).Add(24 * time.Hour)
|
||||||
|
if t2.After(to) {
|
||||||
|
t2 = to
|
||||||
|
}
|
||||||
|
intervals = append(intervals, []time.Time{t1, t2})
|
||||||
|
t1 = t2
|
||||||
|
}
|
||||||
|
|
||||||
|
return intervals
|
||||||
|
}
|
||||||
|
|
||||||
func FmtWakatimeDuration(d time.Duration) string {
|
func FmtWakatimeDuration(d time.Duration) string {
|
||||||
d = d.Round(time.Minute)
|
d = d.Round(time.Minute)
|
||||||
h := d / time.Hour
|
h := d / time.Hour
|
||||||
|
@ -9,18 +9,16 @@ import (
|
|||||||
|
|
||||||
func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
|
func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
|
||||||
user := r.Context().Value(models.UserKey).(*models.User)
|
user := r.Context().Value(models.UserKey).(*models.User)
|
||||||
|
|
||||||
params := r.URL.Query()
|
params := r.URL.Query()
|
||||||
|
|
||||||
interval := params.Get("interval")
|
interval := params.Get("interval")
|
||||||
|
|
||||||
from, err := ParseDate(params.Get("from"))
|
from, err := ParseDate(params.Get("from"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch interval {
|
switch interval {
|
||||||
case models.IntervalToday:
|
case models.IntervalToday:
|
||||||
from = StartOfDay()
|
from = StartOfToday()
|
||||||
case models.IntervalLastDay:
|
case models.IntervalLastDay:
|
||||||
from = StartOfDay().Add(-24 * time.Hour)
|
from = StartOfToday().Add(-24 * time.Hour)
|
||||||
case models.IntervalLastWeek:
|
case models.IntervalLastWeek:
|
||||||
from = StartOfWeek()
|
from = StartOfWeek()
|
||||||
case models.IntervalLastMonth:
|
case models.IntervalLastMonth:
|
||||||
@ -38,7 +36,7 @@ func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
|
|||||||
|
|
||||||
recompute := params.Get("recompute") != "" && params.Get("recompute") != "false"
|
recompute := params.Get("recompute") != "" && params.Get("recompute") != "false"
|
||||||
|
|
||||||
to := StartOfDay()
|
to := StartOfToday()
|
||||||
if live {
|
if live {
|
||||||
to = time.Now()
|
to = time.Now()
|
||||||
}
|
}
|
||||||
|
@ -1 +1 @@
|
|||||||
1.9.2
|
1.10.1
|
Loading…
Reference in New Issue
Block a user