mirror of
https://github.com/muety/wakapi.git
synced 2023-08-10 21:12:56 +03:00
refactor: split utility functions into utils and helpers
This commit is contained in:
parent
c5fda02900
commit
21f6809f05
@ -4,6 +4,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/muety/wakapi/utils"
|
||||||
"github.com/robfig/cron/v3"
|
"github.com/robfig/cron/v3"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -229,19 +230,19 @@ func (c *Config) GetMigrationFunc(dbDialect string) models.MigrationFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *appConfig) GetCustomLanguages() map[string]string {
|
func (c *appConfig) GetCustomLanguages() map[string]string {
|
||||||
return cloneStringMap(c.CustomLanguages, false)
|
return utils.CloneStringMap(c.CustomLanguages, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *appConfig) GetLanguageColors() map[string]string {
|
func (c *appConfig) GetLanguageColors() map[string]string {
|
||||||
return cloneStringMap(c.Colors["languages"], true)
|
return utils.CloneStringMap(c.Colors["languages"], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *appConfig) GetEditorColors() map[string]string {
|
func (c *appConfig) GetEditorColors() map[string]string {
|
||||||
return cloneStringMap(c.Colors["editors"], true)
|
return utils.CloneStringMap(c.Colors["editors"], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *appConfig) GetOSColors() map[string]string {
|
func (c *appConfig) GetOSColors() map[string]string {
|
||||||
return cloneStringMap(c.Colors["operating_systems"], true)
|
return utils.CloneStringMap(c.Colors["operating_systems"], true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *appConfig) GetAggregationTimeCron() string {
|
func (c *appConfig) GetAggregationTimeCron() string {
|
||||||
@ -261,14 +262,14 @@ func (c *appConfig) GetAggregationTimeCron() string {
|
|||||||
return fmt.Sprintf("0 %d %d * * *", m, h)
|
return fmt.Sprintf("0 %d %d * * *", m, h)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cronPadToSecondly(c.AggregationTime)
|
return utils.CronPadToSecondly(c.AggregationTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *appConfig) GetWeeklyReportCron() string {
|
func (c *appConfig) GetWeeklyReportCron() string {
|
||||||
if strings.Contains(c.ReportTimeWeekly, ",") {
|
if strings.Contains(c.ReportTimeWeekly, ",") {
|
||||||
// old gocron format, e.g. "fri,18:00"
|
// old gocron format, e.g. "fri,18:00"
|
||||||
split := strings.Split(c.ReportTimeWeekly, ",")
|
split := strings.Split(c.ReportTimeWeekly, ",")
|
||||||
weekday := parseWeekday(split[0])
|
weekday := utils.ParseWeekday(split[0])
|
||||||
timeParts := strings.Split(split[1], ":")
|
timeParts := strings.Split(split[1], ":")
|
||||||
|
|
||||||
h, err := strconv.Atoi(timeParts[0])
|
h, err := strconv.Atoi(timeParts[0])
|
||||||
@ -284,7 +285,7 @@ func (c *appConfig) GetWeeklyReportCron() string {
|
|||||||
return fmt.Sprintf("0 %d %d * * %d", m, h, weekday)
|
return fmt.Sprintf("0 %d %d * * %d", m, h, weekday)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cronPadToSecondly(c.ReportTimeWeekly)
|
return utils.CronPadToSecondly(c.ReportTimeWeekly)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *appConfig) GetLeaderboardGenerationTimeCron() []string {
|
func (c *appConfig) GetLeaderboardGenerationTimeCron() []string {
|
||||||
@ -310,11 +311,11 @@ func (c *appConfig) GetLeaderboardGenerationTimeCron() []string {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
parse = func(s string) string {
|
parse = func(s string) string {
|
||||||
return cronPadToSecondly(s)
|
return utils.CronPadToSecondly(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range strings.Split(c.LeaderboardGenerationTime, ";") {
|
for _, s := range utils.SplitMulti(c.LeaderboardGenerationTime, ",", ";") {
|
||||||
crons = append(crons, parse(strings.TrimSpace(s)))
|
crons = append(crons, parse(strings.TrimSpace(s)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,43 +394,6 @@ func resolveDbDialect(dbType string) string {
|
|||||||
return dbType
|
return dbType
|
||||||
}
|
}
|
||||||
|
|
||||||
func findString(needle string, haystack []string, defaultVal string) string {
|
|
||||||
for _, s := range haystack {
|
|
||||||
if s == needle {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaultVal
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseWeekday(s string) time.Weekday {
|
|
||||||
switch strings.ToLower(s) {
|
|
||||||
case "mon", strings.ToLower(time.Monday.String()):
|
|
||||||
return time.Monday
|
|
||||||
case "tue", strings.ToLower(time.Tuesday.String()):
|
|
||||||
return time.Tuesday
|
|
||||||
case "wed", strings.ToLower(time.Wednesday.String()):
|
|
||||||
return time.Wednesday
|
|
||||||
case "thu", strings.ToLower(time.Thursday.String()):
|
|
||||||
return time.Thursday
|
|
||||||
case "fri", strings.ToLower(time.Friday.String()):
|
|
||||||
return time.Friday
|
|
||||||
case "sat", strings.ToLower(time.Saturday.String()):
|
|
||||||
return time.Saturday
|
|
||||||
case "sun", strings.ToLower(time.Sunday.String()):
|
|
||||||
return time.Sunday
|
|
||||||
}
|
|
||||||
return time.Monday
|
|
||||||
}
|
|
||||||
|
|
||||||
func cronPadToSecondly(expr string) string {
|
|
||||||
parts := strings.Split(expr, " ")
|
|
||||||
if len(parts) == 6 {
|
|
||||||
return expr
|
|
||||||
}
|
|
||||||
return "0 " + expr
|
|
||||||
}
|
|
||||||
|
|
||||||
func Set(config *Config) {
|
func Set(config *Config) {
|
||||||
cfg = config
|
cfg = config
|
||||||
}
|
}
|
||||||
@ -489,7 +453,7 @@ func Load(version string) *Config {
|
|||||||
logbuch.Warn("with sqlite, only a single connection is supported") // otherwise 'PRAGMA foreign_keys=ON' would somehow have to be set for every connection in the pool
|
logbuch.Warn("with sqlite, only a single connection is supported") // otherwise 'PRAGMA foreign_keys=ON' would somehow have to be set for every connection in the pool
|
||||||
config.Db.MaxConn = 1
|
config.Db.MaxConn = 1
|
||||||
}
|
}
|
||||||
if config.Mail.Provider != "" && findString(config.Mail.Provider, emailProviders, "") == "" {
|
if config.Mail.Provider != "" && utils.FindString(config.Mail.Provider, emailProviders, "") == "" {
|
||||||
logbuch.Fatal("unknown mail provider '%s'", config.Mail.Provider)
|
logbuch.Fatal("unknown mail provider '%s'", config.Mail.Provider)
|
||||||
}
|
}
|
||||||
if _, err := time.ParseDuration(config.App.HeartbeatMaxAge); err != nil {
|
if _, err := time.ParseDuration(config.App.HeartbeatMaxAge); err != nil {
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
func cloneStringMap(m map[string]string, keysToLower bool) map[string]string {
|
|
||||||
m2 := make(map[string]string)
|
|
||||||
for k, v := range m {
|
|
||||||
if keysToLower {
|
|
||||||
k = strings.ToLower(k)
|
|
||||||
}
|
|
||||||
m2[k] = v
|
|
||||||
}
|
|
||||||
return m2
|
|
||||||
}
|
|
@ -1,9 +1,8 @@
|
|||||||
package utils
|
package helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"github.com/muety/wakapi/config"
|
"github.com/muety/wakapi/config"
|
||||||
"regexp"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,22 +40,10 @@ func FormatDateHuman(date time.Time) string {
|
|||||||
return date.Format("Mon, 02 Jan 2006")
|
return date.Format("Mon, 02 Jan 2006")
|
||||||
}
|
}
|
||||||
|
|
||||||
func Add(i, j int) int {
|
func FmtWakatimeDuration(d time.Duration) string {
|
||||||
return i + j
|
d = d.Round(time.Minute)
|
||||||
}
|
h := d / time.Hour
|
||||||
|
d -= h * time.Hour
|
||||||
func ParseUserAgent(ua string) (string, string, error) {
|
m := d / time.Minute
|
||||||
re := regexp.MustCompile(`(?iU)^wakatime\/(?:v?[\d+.]+|unset)\s\((\w+)-.*\)\s.+\s([^\/\s]+)-wakatime\/.+$`)
|
return fmt.Sprintf("%d hrs %d mins", h, m)
|
||||||
groups := re.FindAllStringSubmatch(ua, -1)
|
|
||||||
if len(groups) == 0 || len(groups[0]) != 3 {
|
|
||||||
return "", "", errors.New("failed to parse user agent string")
|
|
||||||
}
|
|
||||||
return groups[0][1], groups[0][2], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func SubSlice[T any](slice []T, from, to uint) []T {
|
|
||||||
if int(to) > len(slice) {
|
|
||||||
to = uint(len(slice))
|
|
||||||
}
|
|
||||||
return slice[from:int(to)]
|
|
||||||
}
|
}
|
4
helpers/helpers.go
Normal file
4
helpers/helpers.go
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
// helpers are different from utils in that they contain wakapi-specific utility functions
|
||||||
|
// also, helpers may depend on the config package, while utils must be entirely static
|
20
helpers/http.go
Normal file
20
helpers/http.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package helpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/utils"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExtractCookieAuth(r *http.Request) (username *string, err error) {
|
||||||
|
return utils.ExtractCookieAuth(r, config.Get().Security.SecureCookie)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RespondJSON(w http.ResponseWriter, r *http.Request, status int, object interface{}) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(status)
|
||||||
|
if err := json.NewEncoder(w).Encode(object); err != nil {
|
||||||
|
config.Log().Request(r).Error("error while writing json response: %v", err)
|
||||||
|
}
|
||||||
|
}
|
@ -1,78 +1,13 @@
|
|||||||
package utils
|
package helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
|
"github.com/muety/wakapi/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ParseInterval(interval string) (*models.IntervalKey, error) {
|
|
||||||
for _, i := range models.AllIntervals {
|
|
||||||
if i.HasAlias(interval) {
|
|
||||||
return i, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("not a valid interval")
|
|
||||||
}
|
|
||||||
|
|
||||||
func MustResolveIntervalRawTZ(interval string, tz *time.Location) (from, to time.Time) {
|
|
||||||
_, from, to = ResolveIntervalRawTZ(interval, tz)
|
|
||||||
return from, to
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResolveIntervalRawTZ(interval string, tz *time.Location) (err error, from, to time.Time) {
|
|
||||||
parsed, err := ParseInterval(interval)
|
|
||||||
if err != nil {
|
|
||||||
return err, time.Time{}, time.Time{}
|
|
||||||
}
|
|
||||||
return ResolveIntervalTZ(parsed, tz)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ResolveIntervalTZ(interval *models.IntervalKey, tz *time.Location) (err error, from, to time.Time) {
|
|
||||||
now := time.Now().In(tz)
|
|
||||||
to = now
|
|
||||||
|
|
||||||
switch interval {
|
|
||||||
case models.IntervalToday:
|
|
||||||
from = BeginOfToday(tz)
|
|
||||||
case models.IntervalYesterday:
|
|
||||||
from = BeginOfToday(tz).Add(-24 * time.Hour)
|
|
||||||
to = BeginOfToday(tz)
|
|
||||||
case models.IntervalThisWeek:
|
|
||||||
from = BeginOfThisWeek(tz)
|
|
||||||
case models.IntervalLastWeek:
|
|
||||||
from = BeginOfThisWeek(tz).AddDate(0, 0, -7)
|
|
||||||
to = BeginOfThisWeek(tz)
|
|
||||||
case models.IntervalThisMonth:
|
|
||||||
from = BeginOfThisMonth(tz)
|
|
||||||
case models.IntervalLastMonth:
|
|
||||||
from = BeginOfThisMonth(tz).AddDate(0, -1, 0)
|
|
||||||
to = BeginOfThisMonth(tz)
|
|
||||||
case models.IntervalThisYear:
|
|
||||||
from = BeginOfThisYear(tz)
|
|
||||||
case models.IntervalPast7Days:
|
|
||||||
from = now.AddDate(0, 0, -7)
|
|
||||||
case models.IntervalPast7DaysYesterday:
|
|
||||||
from = BeginOfToday(tz).AddDate(0, 0, -1).AddDate(0, 0, -7)
|
|
||||||
to = BeginOfToday(tz).AddDate(0, 0, -1)
|
|
||||||
case models.IntervalPast14Days:
|
|
||||||
from = now.AddDate(0, 0, -14)
|
|
||||||
case models.IntervalPast30Days:
|
|
||||||
from = now.AddDate(0, 0, -30)
|
|
||||||
case models.IntervalPast6Months:
|
|
||||||
from = now.AddDate(0, -6, 0)
|
|
||||||
case models.IntervalPast12Months:
|
|
||||||
from = now.AddDate(0, -12, 0)
|
|
||||||
case models.IntervalAny:
|
|
||||||
from = time.Time{}
|
|
||||||
default:
|
|
||||||
err = errors.New("invalid interval")
|
|
||||||
}
|
|
||||||
|
|
||||||
return err, from, to
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
|
func ParseSummaryParams(r *http.Request) (*models.SummaryParams, error) {
|
||||||
user := extractUser(r)
|
user := extractUser(r)
|
||||||
params := r.URL.Query()
|
params := r.URL.Query()
|
||||||
@ -144,3 +79,69 @@ func extractUser(r *http.Request) *models.User {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseInterval(interval string) (*models.IntervalKey, error) {
|
||||||
|
for _, i := range models.AllIntervals {
|
||||||
|
if i.HasAlias(interval) {
|
||||||
|
return i, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("not a valid interval")
|
||||||
|
}
|
||||||
|
|
||||||
|
func MustResolveIntervalRawTZ(interval string, tz *time.Location) (from, to time.Time) {
|
||||||
|
_, from, to = ResolveIntervalRawTZ(interval, tz)
|
||||||
|
return from, to
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveIntervalRawTZ(interval string, tz *time.Location) (err error, from, to time.Time) {
|
||||||
|
parsed, err := ParseInterval(interval)
|
||||||
|
if err != nil {
|
||||||
|
return err, time.Time{}, time.Time{}
|
||||||
|
}
|
||||||
|
return ResolveIntervalTZ(parsed, tz)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResolveIntervalTZ(interval *models.IntervalKey, tz *time.Location) (err error, from, to time.Time) {
|
||||||
|
now := time.Now().In(tz)
|
||||||
|
to = now
|
||||||
|
|
||||||
|
switch interval {
|
||||||
|
case models.IntervalToday:
|
||||||
|
from = utils.BeginOfToday(tz)
|
||||||
|
case models.IntervalYesterday:
|
||||||
|
from = utils.BeginOfToday(tz).Add(-24 * time.Hour)
|
||||||
|
to = utils.BeginOfToday(tz)
|
||||||
|
case models.IntervalThisWeek:
|
||||||
|
from = utils.BeginOfThisWeek(tz)
|
||||||
|
case models.IntervalLastWeek:
|
||||||
|
from = utils.BeginOfThisWeek(tz).AddDate(0, 0, -7)
|
||||||
|
to = utils.BeginOfThisWeek(tz)
|
||||||
|
case models.IntervalThisMonth:
|
||||||
|
from = utils.BeginOfThisMonth(tz)
|
||||||
|
case models.IntervalLastMonth:
|
||||||
|
from = utils.BeginOfThisMonth(tz).AddDate(0, -1, 0)
|
||||||
|
to = utils.BeginOfThisMonth(tz)
|
||||||
|
case models.IntervalThisYear:
|
||||||
|
from = utils.BeginOfThisYear(tz)
|
||||||
|
case models.IntervalPast7Days:
|
||||||
|
from = now.AddDate(0, 0, -7)
|
||||||
|
case models.IntervalPast7DaysYesterday:
|
||||||
|
from = utils.BeginOfToday(tz).AddDate(0, 0, -1).AddDate(0, 0, -7)
|
||||||
|
to = utils.BeginOfToday(tz).AddDate(0, 0, -1)
|
||||||
|
case models.IntervalPast14Days:
|
||||||
|
from = now.AddDate(0, 0, -14)
|
||||||
|
case models.IntervalPast30Days:
|
||||||
|
from = now.AddDate(0, 0, -30)
|
||||||
|
case models.IntervalPast6Months:
|
||||||
|
from = now.AddDate(0, -6, 0)
|
||||||
|
case models.IntervalPast12Months:
|
||||||
|
from = now.AddDate(0, -12, 0)
|
||||||
|
case models.IntervalAny:
|
||||||
|
from = time.Time{}
|
||||||
|
default:
|
||||||
|
err = errors.New("invalid interval")
|
||||||
|
}
|
||||||
|
|
||||||
|
return err, from, to
|
||||||
|
}
|
@ -2,6 +2,7 @@ package middlewares
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -121,7 +122,7 @@ func (m *AuthenticateMiddleware) tryGetUserByApiKeyQuery(r *http.Request) (*mode
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *AuthenticateMiddleware) tryGetUserByCookie(r *http.Request) (*models.User, error) {
|
func (m *AuthenticateMiddleware) tryGetUserByCookie(r *http.Request) (*models.User, error) {
|
||||||
username, err := utils.ExtractCookieAuth(r, m.config)
|
username, err := helpers.ExtractCookieAuth(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://shields.io/endpoint
|
// https://shields.io/endpoint
|
||||||
@ -23,7 +23,7 @@ func NewBadgeDataFrom(summary *models.Summary) *BadgeData {
|
|||||||
return &BadgeData{
|
return &BadgeData{
|
||||||
SchemaVersion: 1,
|
SchemaVersion: 1,
|
||||||
Label: defaultLabel,
|
Label: defaultLabel,
|
||||||
Message: utils.FmtWakatimeDuration(summary.TotalTime()),
|
Message: helpers.FmtWakatimeDuration(summary.TotalTime()),
|
||||||
Color: defaultColor,
|
Color: defaultColor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -33,13 +33,13 @@ func NewAllTimeFrom(summary *models.Summary) *AllTimeViewModel {
|
|||||||
return &AllTimeViewModel{
|
return &AllTimeViewModel{
|
||||||
Data: &AllTimeData{
|
Data: &AllTimeData{
|
||||||
TotalSeconds: float32(total.Seconds()),
|
TotalSeconds: float32(total.Seconds()),
|
||||||
Text: utils.FmtWakatimeDuration(total),
|
Text: helpers.FmtWakatimeDuration(total),
|
||||||
IsUpToDate: true,
|
IsUpToDate: true,
|
||||||
Range: &AllTimeRange{
|
Range: &AllTimeRange{
|
||||||
End: summary.ToTime.T().Format(time.RFC3339),
|
End: summary.ToTime.T().Format(time.RFC3339),
|
||||||
EndDate: utils.FormatDate(summary.ToTime.T()),
|
EndDate: helpers.FormatDate(summary.ToTime.T()),
|
||||||
Start: summary.FromTime.T().Format(time.RFC3339),
|
Start: summary.FromTime.T().Format(time.RFC3339),
|
||||||
StartDate: utils.FormatDate(summary.FromTime.T()),
|
StartDate: helpers.FormatDate(summary.FromTime.T()),
|
||||||
Timezone: tzName,
|
Timezone: tzName,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -2,8 +2,8 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
"math"
|
"math"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -96,7 +96,7 @@ func NewSummariesFrom(summaries []*models.Summary) *SummariesViewModel {
|
|||||||
Decimal: fmt.Sprintf("%.2f", totalHrs),
|
Decimal: fmt.Sprintf("%.2f", totalHrs),
|
||||||
Digital: fmt.Sprintf("%d:%d", int(totalHrs), int(totalMins)),
|
Digital: fmt.Sprintf("%d:%d", int(totalHrs), int(totalMins)),
|
||||||
Seconds: totalSecs,
|
Seconds: totalSecs,
|
||||||
Text: utils.FmtWakatimeDuration(totalTime),
|
Text: helpers.FmtWakatimeDuration(totalTime),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ func newDataFrom(s *models.Summary) *SummariesData {
|
|||||||
Digital: fmt.Sprintf("%d:%d", totalHrs, totalMins),
|
Digital: fmt.Sprintf("%d:%d", totalHrs, totalMins),
|
||||||
Hours: totalHrs,
|
Hours: totalHrs,
|
||||||
Minutes: totalMins,
|
Minutes: totalMins,
|
||||||
Text: utils.FmtWakatimeDuration(total),
|
Text: helpers.FmtWakatimeDuration(total),
|
||||||
TotalSeconds: total.Seconds(),
|
TotalSeconds: total.Seconds(),
|
||||||
},
|
},
|
||||||
Range: &SummariesRange{
|
Range: &SummariesRange{
|
||||||
@ -201,7 +201,7 @@ func convertEntry(e *models.SummaryItem, entityTotal time.Duration) *SummariesEn
|
|||||||
Name: e.Key,
|
Name: e.Key,
|
||||||
Percent: percentage,
|
Percent: percentage,
|
||||||
Seconds: secs,
|
Seconds: secs,
|
||||||
Text: utils.FmtWakatimeDuration(total),
|
Text: helpers.FmtWakatimeDuration(total),
|
||||||
TotalSeconds: total.Seconds(),
|
TotalSeconds: total.Seconds(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,13 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
conf "github.com/muety/wakapi/config"
|
conf "github.com/muety/wakapi/config"
|
||||||
"github.com/muety/wakapi/services"
|
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
|
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
|
"github.com/muety/wakapi/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DiagnosticsApiHandler struct {
|
type DiagnosticsApiHandler struct {
|
||||||
@ -55,5 +54,5 @@ func (h *DiagnosticsApiHandler) Post(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.RespondJSON(w, r, http.StatusCreated, struct{}{})
|
helpers.RespondJSON(w, r, http.StatusCreated, struct{}{})
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@ -120,7 +121,7 @@ func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
defer func() {}()
|
defer func() {}()
|
||||||
|
|
||||||
utils.RespondJSON(w, r, http.StatusCreated, constructSuccessResponse(len(heartbeats)))
|
helpers.RespondJSON(w, r, http.StatusCreated, constructSuccessResponse(len(heartbeats)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// construct weird response format (see https://github.com/wakatime/wakatime/blob/2e636d389bf5da4e998e05d5285a96ce2c181e3d/wakatime/api.py#L288)
|
// construct weird response format (see https://github.com/wakatime/wakatime/blob/2e636d389bf5da4e998e05d5285a96ce2c181e3d/wakatime/api.py#L288)
|
||||||
|
@ -5,13 +5,13 @@ import (
|
|||||||
"github.com/emvi/logbuch"
|
"github.com/emvi/logbuch"
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
conf "github.com/muety/wakapi/config"
|
conf "github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"github.com/muety/wakapi/middlewares"
|
"github.com/muety/wakapi/middlewares"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||||
mm "github.com/muety/wakapi/models/metrics"
|
mm "github.com/muety/wakapi/models/metrics"
|
||||||
"github.com/muety/wakapi/repositories"
|
"github.com/muety/wakapi/repositories"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
@ -129,7 +129,7 @@ func (h *MetricsHandler) getUserMetrics(user *models.User) (*mm.Metrics, error)
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
from, to := utils.MustResolveIntervalRawTZ("today", user.TZ())
|
from, to := helpers.MustResolveIntervalRawTZ("today", user.TZ())
|
||||||
|
|
||||||
summaryToday, err := h.summarySrvc.Aliased(from, to, user, h.summarySrvc.Retrieve, nil, false)
|
summaryToday, err := h.summarySrvc.Aliased(from, to, user, h.summarySrvc.Retrieve, nil, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
routeutils "github.com/muety/wakapi/routes/utils"
|
routeutils "github.com/muety/wakapi/routes/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
@ -8,7 +9,6 @@ import (
|
|||||||
conf "github.com/muety/wakapi/config"
|
conf "github.com/muety/wakapi/config"
|
||||||
"github.com/muety/wakapi/middlewares"
|
"github.com/muety/wakapi/middlewares"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type SummaryApiHandler struct {
|
type SummaryApiHandler struct {
|
||||||
@ -58,5 +58,5 @@ func (h *SummaryApiHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.RespondJSON(w, r, http.StatusOK, summary)
|
helpers.RespondJSON(w, r, http.StatusOK, summary)
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
routeutils "github.com/muety/wakapi/routes/utils"
|
routeutils "github.com/muety/wakapi/routes/utils"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
@ -11,7 +12,6 @@ import (
|
|||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
v1 "github.com/muety/wakapi/models/compat/shields/v1"
|
v1 "github.com/muety/wakapi/models/compat/shields/v1"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
"github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -63,7 +63,7 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
cacheKey := fmt.Sprintf("%s_%v_%s", user.ID, *interval.Key, filters.Hash())
|
cacheKey := fmt.Sprintf("%s_%v_%s", user.ID, *interval.Key, filters.Hash())
|
||||||
if cacheResult, ok := h.cache.Get(cacheKey); ok {
|
if cacheResult, ok := h.cache.Get(cacheKey); ok {
|
||||||
utils.RespondJSON(w, r, http.StatusOK, cacheResult.(*v1.BadgeData))
|
helpers.RespondJSON(w, r, http.StatusOK, cacheResult.(*v1.BadgeData))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,11 +83,11 @@ func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
vm := v1.NewBadgeDataFrom(summary)
|
vm := v1.NewBadgeDataFrom(summary)
|
||||||
h.cache.SetDefault(cacheKey, vm)
|
h.cache.SetDefault(cacheKey, vm)
|
||||||
utils.RespondJSON(w, r, http.StatusOK, vm)
|
helpers.RespondJSON(w, r, http.StatusOK, vm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BadgeHandler) loadUserSummary(user *models.User, interval *models.IntervalKey, filters *models.Filters) (*models.Summary, error, int) {
|
func (h *BadgeHandler) loadUserSummary(user *models.User, interval *models.IntervalKey, filters *models.Filters) (*models.Summary, error, int) {
|
||||||
err, from, to := utils.ResolveIntervalTZ(interval, user.TZ())
|
err, from, to := helpers.ResolveIntervalTZ(interval, user.TZ())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err, http.StatusBadRequest
|
return nil, err, http.StatusBadRequest
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,12 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
conf "github.com/muety/wakapi/config"
|
conf "github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"github.com/muety/wakapi/middlewares"
|
"github.com/muety/wakapi/middlewares"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||||
routeutils "github.com/muety/wakapi/routes/utils"
|
routeutils "github.com/muety/wakapi/routes/utils"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -50,7 +50,7 @@ func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
return // response was already sent by util function
|
return // response was already sent by util function
|
||||||
}
|
}
|
||||||
|
|
||||||
summary, err, status := h.loadUserSummary(user, utils.ParseSummaryFilters(r))
|
summary, err, status := h.loadUserSummary(user, helpers.ParseSummaryFilters(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
@ -58,7 +58,7 @@ func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vm := v1.NewAllTimeFrom(summary)
|
vm := v1.NewAllTimeFrom(summary)
|
||||||
utils.RespondJSON(w, r, http.StatusOK, vm)
|
helpers.RespondJSON(w, r, http.StatusOK, vm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AllTimeHandler) loadUserSummary(user *models.User, filters *models.Filters) (*models.Summary, error, int) {
|
func (h *AllTimeHandler) loadUserSummary(user *models.User, filters *models.Filters) (*models.Summary, error, int) {
|
||||||
|
@ -2,6 +2,7 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/duke-git/lancet/v2/datetime"
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -11,7 +12,6 @@ import (
|
|||||||
wakatime "github.com/muety/wakapi/models/compat/wakatime/v1"
|
wakatime "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||||
routeutils "github.com/muety/wakapi/routes/utils"
|
routeutils "github.com/muety/wakapi/routes/utils"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HeartbeatsResult struct {
|
type HeartbeatsResult struct {
|
||||||
@ -82,5 +82,5 @@ func (h *HeartbeatHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
End: rangeTo.UTC().Format(time.RFC3339),
|
End: rangeTo.UTC().Format(time.RFC3339),
|
||||||
Timezone: timezone.String(),
|
Timezone: timezone.String(),
|
||||||
}
|
}
|
||||||
utils.RespondJSON(w, r, http.StatusOK, res)
|
helpers.RespondJSON(w, r, http.StatusOK, res)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -11,7 +12,6 @@ import (
|
|||||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||||
routeutils "github.com/muety/wakapi/routes/utils"
|
routeutils "github.com/muety/wakapi/routes/utils"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProjectsHandler struct {
|
type ProjectsHandler struct {
|
||||||
@ -70,5 +70,5 @@ func (h *ProjectsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vm := &v1.ProjectsViewModel{Data: projects}
|
vm := &v1.ProjectsViewModel{Data: projects}
|
||||||
utils.RespondJSON(w, r, http.StatusOK, vm)
|
helpers.RespondJSON(w, r, http.StatusOK, vm)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -10,7 +11,6 @@ import (
|
|||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatsHandler struct {
|
type StatsHandler struct {
|
||||||
@ -79,7 +79,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
rangeParam = (*models.IntervalPast7Days)[0]
|
rangeParam = (*models.IntervalPast7Days)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
err, rangeFrom, rangeTo := utils.ResolveIntervalRawTZ(rangeParam, requestedUser.TZ())
|
err, rangeFrom, rangeTo := helpers.ResolveIntervalRawTZ(rangeParam, requestedUser.TZ())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
w.Write([]byte("invalid range"))
|
w.Write([]byte("invalid range"))
|
||||||
@ -94,7 +94,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
summary, err, status := h.loadUserSummary(requestedUser, rangeFrom, rangeTo, utils.ParseSummaryFilters(r))
|
summary, err, status := h.loadUserSummary(requestedUser, rangeFrom, rangeTo, helpers.ParseSummaryFilters(r))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
w.Write([]byte(err.Error()))
|
w.Write([]byte(err.Error()))
|
||||||
@ -120,7 +120,7 @@ func (h *StatsHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
stats.Data.Machines = nil
|
stats.Data.Machines = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.RespondJSON(w, r, http.StatusOK, stats)
|
helpers.RespondJSON(w, r, http.StatusOK, stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *StatsHandler) loadUserSummary(user *models.User, start, end time.Time, filters *models.Filters) (*models.Summary, error, int) {
|
func (h *StatsHandler) loadUserSummary(user *models.User, start, end time.Time, filters *models.Filters) (*models.Summary, error, int) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -11,7 +12,6 @@ import (
|
|||||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||||
routeutils "github.com/muety/wakapi/routes/utils"
|
routeutils "github.com/muety/wakapi/routes/utils"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type StatusBarViewModel struct {
|
type StatusBarViewModel struct {
|
||||||
@ -65,7 +65,7 @@ func (h *StatusBarHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
rangeParam = (*models.IntervalToday)[0]
|
rangeParam = (*models.IntervalToday)[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
err, rangeFrom, rangeTo := utils.ResolveIntervalRawTZ(rangeParam, user.TZ())
|
err, rangeFrom, rangeTo := helpers.ResolveIntervalRawTZ(rangeParam, user.TZ())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
w.Write([]byte("invalid range"))
|
w.Write([]byte("invalid range"))
|
||||||
@ -79,7 +79,7 @@ func (h *StatusBarHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
summariesView := v1.NewSummariesFrom([]*models.Summary{summary})
|
summariesView := v1.NewSummariesFrom([]*models.Summary{summary})
|
||||||
utils.RespondJSON(w, r, http.StatusOK, StatusBarViewModel{
|
helpers.RespondJSON(w, r, http.StatusOK, StatusBarViewModel{
|
||||||
CachedAt: time.Now(),
|
CachedAt: time.Now(),
|
||||||
Data: *summariesView.Data[0],
|
Data: *summariesView.Data[0],
|
||||||
})
|
})
|
||||||
|
@ -3,6 +3,7 @@ package v1
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/duke-git/lancet/v2/datetime"
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -76,7 +77,7 @@ func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
vm := v1.NewSummariesFrom(summaries)
|
vm := v1.NewSummariesFrom(summaries)
|
||||||
utils.RespondJSON(w, r, http.StatusOK, vm)
|
helpers.RespondJSON(w, r, http.StatusOK, vm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary, error, int) {
|
func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary, error, int) {
|
||||||
@ -94,24 +95,24 @@ func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary
|
|||||||
var start, end time.Time
|
var start, end time.Time
|
||||||
if rangeParam != "" {
|
if rangeParam != "" {
|
||||||
// range param takes precedence
|
// range param takes precedence
|
||||||
if err, parsedFrom, parsedTo := utils.ResolveIntervalRawTZ(rangeParam, timezone); err == nil {
|
if err, parsedFrom, parsedTo := helpers.ResolveIntervalRawTZ(rangeParam, timezone); err == nil {
|
||||||
start, end = parsedFrom, parsedTo
|
start, end = parsedFrom, parsedTo
|
||||||
} else {
|
} else {
|
||||||
return nil, errors.New("invalid 'range' parameter"), http.StatusBadRequest
|
return nil, errors.New("invalid 'range' parameter"), http.StatusBadRequest
|
||||||
}
|
}
|
||||||
} else if err, parsedFrom, parsedTo := utils.ResolveIntervalRawTZ(startParam, timezone); err == nil && startParam == endParam {
|
} else if err, parsedFrom, parsedTo := helpers.ResolveIntervalRawTZ(startParam, timezone); err == nil && startParam == endParam {
|
||||||
// also accept start param to be a range param
|
// also accept start param to be a range param
|
||||||
start, end = parsedFrom, parsedTo
|
start, end = parsedFrom, parsedTo
|
||||||
} else {
|
} else {
|
||||||
// eventually, consider start and end params a date
|
// eventually, consider start and end params a date
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
start, err = utils.ParseDateTimeTZ(strings.Replace(startParam, " ", "+", 1), timezone)
|
start, err = helpers.ParseDateTimeTZ(strings.Replace(startParam, " ", "+", 1), timezone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("missing required 'start' parameter"), http.StatusBadRequest
|
return nil, errors.New("missing required 'start' parameter"), http.StatusBadRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
end, err = utils.ParseDateTimeTZ(strings.Replace(endParam, " ", "+", 1), timezone)
|
end, err = helpers.ParseDateTimeTZ(strings.Replace(endParam, " ", "+", 1), timezone)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("missing required 'end' parameter"), http.StatusBadRequest
|
return nil, errors.New("missing required 'end' parameter"), http.StatusBadRequest
|
||||||
}
|
}
|
||||||
@ -133,7 +134,7 @@ func (h *SummariesHandler) loadUserSummaries(r *http.Request) ([]*models.Summary
|
|||||||
summaries := make([]*models.Summary, len(intervals))
|
summaries := make([]*models.Summary, len(intervals))
|
||||||
|
|
||||||
// filtering
|
// filtering
|
||||||
filters := utils.ParseSummaryFilters(r)
|
filters := helpers.ParseSummaryFilters(r)
|
||||||
|
|
||||||
for i, interval := range intervals {
|
for i, interval := range intervals {
|
||||||
summary, err := h.summarySrvc.Aliased(interval[0], interval[1], user, h.summarySrvc.Retrieve, filters, end.After(time.Now()))
|
summary, err := h.summarySrvc.Aliased(interval[0], interval[1], user, h.summarySrvc.Retrieve, filters, end.After(time.Now()))
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package v1
|
package v1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
@ -9,7 +10,6 @@ import (
|
|||||||
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
v1 "github.com/muety/wakapi/models/compat/wakatime/v1"
|
||||||
routeutils "github.com/muety/wakapi/routes/utils"
|
routeutils "github.com/muety/wakapi/routes/utils"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type UsersHandler struct {
|
type UsersHandler struct {
|
||||||
@ -56,5 +56,5 @@ func (h *UsersHandler) Get(w http.ResponseWriter, r *http.Request) {
|
|||||||
conf.Log().Request(r).Error("%v", err)
|
conf.Log().Request(r).Error("%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.RespondJSON(w, r, http.StatusOK, v1.UserViewModel{Data: user})
|
helpers.RespondJSON(w, r, http.StatusOK, v1.UserViewModel{Data: user})
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package routes
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
@ -24,16 +25,16 @@ func Init() {
|
|||||||
func DefaultTemplateFuncs() template.FuncMap {
|
func DefaultTemplateFuncs() template.FuncMap {
|
||||||
return template.FuncMap{
|
return template.FuncMap{
|
||||||
"json": utils.Json,
|
"json": utils.Json,
|
||||||
"date": utils.FormatDateHuman,
|
"date": helpers.FormatDateHuman,
|
||||||
"datetime": utils.FormatDateTimeHuman,
|
"datetime": helpers.FormatDateTimeHuman,
|
||||||
"simpledate": utils.FormatDate,
|
"simpledate": helpers.FormatDate,
|
||||||
"simpledatetime": utils.FormatDateTime,
|
"simpledatetime": helpers.FormatDateTime,
|
||||||
"duration": utils.FmtWakatimeDuration,
|
"duration": helpers.FmtWakatimeDuration,
|
||||||
"floordate": datetime.BeginOfDay,
|
"floordate": datetime.BeginOfDay,
|
||||||
"ceildate": utils.CeilDate,
|
"ceildate": utils.CeilDate,
|
||||||
"title": strings.Title,
|
"title": strings.Title,
|
||||||
"join": strings.Join,
|
"join": strings.Join,
|
||||||
"add": utils.Add,
|
"add": add,
|
||||||
"capitalize": utils.Capitalize,
|
"capitalize": utils.Capitalize,
|
||||||
"lower": strings.ToLower,
|
"lower": strings.ToLower,
|
||||||
"toRunes": utils.ToRunes,
|
"toRunes": utils.ToRunes,
|
||||||
@ -106,3 +107,7 @@ func loadTemplates() {
|
|||||||
func defaultErrorRedirectTarget() string {
|
func defaultErrorRedirectTarget() string {
|
||||||
return fmt.Sprintf("%s/?error=unauthorized", config.Get().Server.BasePath)
|
return fmt.Sprintf("%s/?error=unauthorized", config.Get().Server.BasePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func add(i, j int) int {
|
||||||
|
return i + j
|
||||||
|
}
|
||||||
|
@ -3,6 +3,7 @@ package routes
|
|||||||
import (
|
import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
conf "github.com/muety/wakapi/config"
|
conf "github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"github.com/muety/wakapi/middlewares"
|
"github.com/muety/wakapi/middlewares"
|
||||||
"github.com/muety/wakapi/models/view"
|
"github.com/muety/wakapi/models/view"
|
||||||
su "github.com/muety/wakapi/routes/utils"
|
su "github.com/muety/wakapi/routes/utils"
|
||||||
@ -47,7 +48,7 @@ func (h *SummaryHandler) GetIndex(w http.ResponseWriter, r *http.Request) {
|
|||||||
r.URL.RawQuery = q.Encode()
|
r.URL.RawQuery = q.Encode()
|
||||||
}
|
}
|
||||||
|
|
||||||
summaryParams, _ := utils.ParseSummaryParams(r)
|
summaryParams, _ := helpers.ParseSummaryParams(r)
|
||||||
summary, err, status := su.LoadUserSummary(h.summarySrvc, r)
|
summary, err, status := su.LoadUserSummary(h.summarySrvc, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
|
@ -2,8 +2,8 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
)
|
)
|
||||||
@ -31,12 +31,12 @@ func GetBadgeParams(r *http.Request, requestedUser *models.User) (*models.KeyedI
|
|||||||
|
|
||||||
var intervalKey = models.IntervalPast30Days
|
var intervalKey = models.IntervalPast30Days
|
||||||
if groups := intervalReg.FindStringSubmatch(r.URL.Path); len(groups) > 1 {
|
if groups := intervalReg.FindStringSubmatch(r.URL.Path); len(groups) > 1 {
|
||||||
if i, err := utils.ParseInterval(groups[1]); err == nil {
|
if i, err := helpers.ParseInterval(groups[1]); err == nil {
|
||||||
intervalKey = i
|
intervalKey = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, rangeFrom, rangeTo := utils.ResolveIntervalTZ(intervalKey, requestedUser.TZ())
|
_, rangeFrom, rangeTo := helpers.ResolveIntervalTZ(intervalKey, requestedUser.TZ())
|
||||||
interval := &models.KeyedInterval{
|
interval := &models.KeyedInterval{
|
||||||
Interval: models.Interval{Start: rangeFrom, End: rangeTo},
|
Interval: models.Interval{Start: rangeFrom, End: rangeTo},
|
||||||
Key: intervalKey,
|
Key: intervalKey,
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadUserSummary(ss services.ISummaryService, r *http.Request) (*models.Summary, error, int) {
|
func LoadUserSummary(ss services.ISummaryService, r *http.Request) (*models.Summary, error, int) {
|
||||||
summaryParams, err := utils.ParseSummaryParams(r)
|
summaryParams, err := helpers.ParseSummaryParams(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err, http.StatusBadRequest
|
return nil, err, http.StatusBadRequest
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,9 @@ import (
|
|||||||
"github.com/leandro-lugaresi/hub"
|
"github.com/leandro-lugaresi/hub"
|
||||||
"github.com/muety/artifex"
|
"github.com/muety/artifex"
|
||||||
"github.com/muety/wakapi/config"
|
"github.com/muety/wakapi/config"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"github.com/muety/wakapi/repositories"
|
"github.com/muety/wakapi/repositories"
|
||||||
"github.com/muety/wakapi/utils"
|
|
||||||
"github.com/patrickmn/go-cache"
|
"github.com/patrickmn/go-cache"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -212,7 +212,7 @@ func (srv *LeaderboardService) GetAggregatedByIntervalAndUser(interval *models.I
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (srv *LeaderboardService) GenerateByUser(user *models.User, interval *models.IntervalKey) (*models.LeaderboardItem, error) {
|
func (srv *LeaderboardService) GenerateByUser(user *models.User, interval *models.IntervalKey) (*models.LeaderboardItem, error) {
|
||||||
err, from, to := utils.ResolveIntervalTZ(interval, user.TZ())
|
err, from, to := helpers.ResolveIntervalTZ(interval, user.TZ())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -231,7 +231,7 @@ func (srv *LeaderboardService) GenerateByUser(user *models.User, interval *model
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (srv *LeaderboardService) GenerateAggregatedByUser(user *models.User, interval *models.IntervalKey, by uint8) ([]*models.LeaderboardItem, error) {
|
func (srv *LeaderboardService) GenerateAggregatedByUser(user *models.User, interval *models.IntervalKey, by uint8) ([]*models.LeaderboardItem, error) {
|
||||||
err, from, to := utils.ResolveIntervalTZ(interval, user.TZ())
|
err, from, to := helpers.ResolveIntervalTZ(interval, user.TZ())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package mail
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/muety/wakapi/helpers"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"github.com/muety/wakapi/routes"
|
"github.com/muety/wakapi/routes"
|
||||||
"github.com/muety/wakapi/services"
|
"github.com/muety/wakapi/services"
|
||||||
@ -115,7 +116,7 @@ func (m *MailService) SendReport(recipient *models.User, report *models.Report)
|
|||||||
mail := &models.Mail{
|
mail := &models.Mail{
|
||||||
From: models.MailAddress(m.config.Mail.Sender),
|
From: models.MailAddress(m.config.Mail.Sender),
|
||||||
To: models.MailAddresses([]models.MailAddress{models.MailAddress(recipient.Email)}),
|
To: models.MailAddresses([]models.MailAddress{models.MailAddress(recipient.Email)}),
|
||||||
Subject: fmt.Sprintf(subjectReport, utils.FormatDateHuman(time.Now().In(recipient.TZ()))),
|
Subject: fmt.Sprintf(subjectReport, helpers.FormatDateHuman(time.Now().In(recipient.TZ()))),
|
||||||
}
|
}
|
||||||
mail.WithHTML(tpl.String())
|
mail.WithHTML(tpl.String())
|
||||||
return m.sendingService.Send(mail)
|
return m.sendingService.Send(mail)
|
||||||
|
@ -3,7 +3,7 @@ package utils
|
|||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/muety/wakapi/config"
|
"github.com/gorilla/securecookie"
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -44,13 +44,13 @@ func ExtractBearerAuth(r *http.Request) (key string, err error) {
|
|||||||
return string(keyBytes), err
|
return string(keyBytes), err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExtractCookieAuth(r *http.Request, config *config.Config) (username *string, err error) {
|
func ExtractCookieAuth(r *http.Request, secureCookie *securecookie.SecureCookie) (username *string, err error) {
|
||||||
cookie, err := r.Cookie(models.AuthCookieKey)
|
cookie, err := r.Cookie(models.AuthCookieKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("missing authentication")
|
return nil, errors.New("missing authentication")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := config.Security.SecureCookie.Decode(models.AuthCookieKey, cookie.Value, &username); err != nil {
|
if err := secureCookie.Decode(models.AuthCookieKey, cookie.Value, &username); err != nil {
|
||||||
return nil, errors.New("cookie is invalid")
|
return nil, errors.New("cookie is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
utils/collection.go
Normal file
21
utils/collection.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func SubSlice[T any](slice []T, from, to uint) []T {
|
||||||
|
if int(to) > len(slice) {
|
||||||
|
to = uint(len(slice))
|
||||||
|
}
|
||||||
|
return slice[from:int(to)]
|
||||||
|
}
|
||||||
|
|
||||||
|
func CloneStringMap(m map[string]string, keysToLower bool) map[string]string {
|
||||||
|
m2 := make(map[string]string)
|
||||||
|
for k, v := range m {
|
||||||
|
if keysToLower {
|
||||||
|
k = strings.ToLower(k)
|
||||||
|
}
|
||||||
|
m2[k] = v
|
||||||
|
}
|
||||||
|
return m2
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"github.com/duke-git/lancet/v2/datetime"
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -47,16 +47,28 @@ func SplitRangeByDays(from time.Time, to time.Time) [][]time.Time {
|
|||||||
return intervals
|
return intervals
|
||||||
}
|
}
|
||||||
|
|
||||||
func FmtWakatimeDuration(d time.Duration) string {
|
|
||||||
d = d.Round(time.Minute)
|
|
||||||
h := d / time.Hour
|
|
||||||
d -= h * time.Hour
|
|
||||||
m := d / time.Minute
|
|
||||||
return fmt.Sprintf("%d hrs %d mins", h, m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalTZOffset returns the time difference between server local time and UTC
|
// LocalTZOffset returns the time difference between server local time and UTC
|
||||||
func LocalTZOffset() time.Duration {
|
func LocalTZOffset() time.Duration {
|
||||||
_, offset := time.Now().Zone()
|
_, offset := time.Now().Zone()
|
||||||
return time.Duration(offset * int(time.Second))
|
return time.Duration(offset * int(time.Second))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseWeekday(s string) time.Weekday {
|
||||||
|
switch strings.ToLower(s) {
|
||||||
|
case "mon", strings.ToLower(time.Monday.String()):
|
||||||
|
return time.Monday
|
||||||
|
case "tue", strings.ToLower(time.Tuesday.String()):
|
||||||
|
return time.Tuesday
|
||||||
|
case "wed", strings.ToLower(time.Wednesday.String()):
|
||||||
|
return time.Wednesday
|
||||||
|
case "thu", strings.ToLower(time.Thursday.String()):
|
||||||
|
return time.Thursday
|
||||||
|
case "fri", strings.ToLower(time.Friday.String()):
|
||||||
|
return time.Friday
|
||||||
|
case "sat", strings.ToLower(time.Saturday.String()):
|
||||||
|
return time.Saturday
|
||||||
|
case "sun", strings.ToLower(time.Sunday.String()):
|
||||||
|
return time.Sunday
|
||||||
|
}
|
||||||
|
return time.Monday
|
||||||
|
}
|
||||||
|
@ -2,7 +2,6 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/duke-git/lancet/v2/datetime"
|
"github.com/duke-git/lancet/v2/datetime"
|
||||||
"github.com/muety/wakapi/config"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
@ -23,8 +22,8 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestDate_SplitRangeByDays(t *testing.T) {
|
func TestDate_SplitRangeByDays(t *testing.T) {
|
||||||
df1, _ := time.Parse(config.SimpleDateTimeFormat, "2021-04-25 20:25:00")
|
df1, _ := time.Parse("2006-01-02 15:04:05", "2021-04-25 20:25:00")
|
||||||
dt1, _ := time.Parse(config.SimpleDateTimeFormat, "2021-04-28 06:45:00")
|
dt1, _ := time.Parse("2006-01-02 15:04:05", "2021-04-28 06:45:00")
|
||||||
df2 := df1
|
df2 := df1
|
||||||
dt2 := datetime.EndOfDay(df1)
|
dt2 := datetime.EndOfDay(df1)
|
||||||
df3 := df1
|
df3 := df1
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"errors"
|
||||||
"github.com/muety/wakapi/config"
|
|
||||||
"github.com/muety/wakapi/models"
|
"github.com/muety/wakapi/models"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
@ -23,14 +22,6 @@ func init() {
|
|||||||
cacheMaxAgeRe = regexp.MustCompile(cacheMaxAgePattern)
|
cacheMaxAgeRe = regexp.MustCompile(cacheMaxAgePattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RespondJSON(w http.ResponseWriter, r *http.Request, status int, object interface{}) {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(status)
|
|
||||||
if err := json.NewEncoder(w).Encode(object); err != nil {
|
|
||||||
config.Log().Request(r).Error("error while writing json response: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsNoCache(r *http.Request, cacheTtl time.Duration) bool {
|
func IsNoCache(r *http.Request, cacheTtl time.Duration) bool {
|
||||||
cacheControl := r.Header.Get("cache-control")
|
cacheControl := r.Header.Get("cache-control")
|
||||||
if strings.Contains(cacheControl, "no-cache") {
|
if strings.Contains(cacheControl, "no-cache") {
|
||||||
@ -67,3 +58,12 @@ func ParsePageParamsWithDefault(r *http.Request, page, size int) *models.PagePar
|
|||||||
}
|
}
|
||||||
return pageParams
|
return pageParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ParseUserAgent(ua string) (string, string, error) {
|
||||||
|
re := regexp.MustCompile(`(?iU)^wakatime\/(?:v?[\d+.]+|unset)\s\((\w+)-.*\)\s.+\s([^\/\s]+)-wakatime\/.+$`)
|
||||||
|
groups := re.FindAllStringSubmatch(ua, -1)
|
||||||
|
if len(groups) == 0 || len(groups[0]) != 3 {
|
||||||
|
return "", "", errors.New("failed to parse user agent string")
|
||||||
|
}
|
||||||
|
return groups[0][1], groups[0][2], nil
|
||||||
|
}
|
||||||
|
@ -8,3 +8,23 @@ import (
|
|||||||
func Capitalize(s string) string {
|
func Capitalize(s string) string {
|
||||||
return fmt.Sprintf("%s%s", strings.ToUpper(s[:1]), s[1:])
|
return fmt.Sprintf("%s%s", strings.ToUpper(s[:1]), s[1:])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SplitMulti(s string, delimiters ...string) []string {
|
||||||
|
return strings.FieldsFunc(s, func(r rune) bool {
|
||||||
|
for _, d := range delimiters {
|
||||||
|
if string(r) == d {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindString(needle string, haystack []string, defaultVal string) string {
|
||||||
|
for _, s := range haystack {
|
||||||
|
if s == needle {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultVal
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user