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:
@@ -3,7 +3,7 @@ package utils
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/gorilla/securecookie"
|
||||
"github.com/muety/wakapi/models"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net/http"
|
||||
@@ -44,13 +44,13 @@ func ExtractBearerAuth(r *http.Request) (key string, err error) {
|
||||
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)
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
|
||||
|
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,62 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/muety/wakapi/config"
|
||||
"regexp"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ParseDateTimeTZ attempts to parse the given date string from multiple formats.
|
||||
// First, a time-zoned date-time string (e.g. 2006-01-02T15:04:05+02:00) is tried
|
||||
// Second, a non-time-zoned date-time string (e.g. 2006-01-02 15:04:05) is tried at the given zone
|
||||
// Third, a non-time-zoned date string (e.g. 2006-01-02) is tried at the given zone
|
||||
// Example:
|
||||
// - Server runs in CEST (UTC+2), requesting user lives in PDT (UTC-7).
|
||||
// - 2021-04-25T10:30:00Z, 2021-04-25T3:30:00-0100 and 2021-04-25T12:30:00+0200 are equivalent, they represent the same point in time
|
||||
// - When user requests non-time-zoned range (e.g. 2021-04-25T00:00:00), but has their time zone properly configured, this will resolve to 2021-04-25T09:00:00
|
||||
func ParseDateTimeTZ(date string, tz *time.Location) (time.Time, error) {
|
||||
if t, err := time.Parse(time.RFC3339, date); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
if t, err := time.ParseInLocation(config.SimpleDateTimeFormat, date, tz); err == nil {
|
||||
return t, nil
|
||||
}
|
||||
return time.ParseInLocation(config.SimpleDateFormat, date, tz)
|
||||
}
|
||||
|
||||
func FormatDate(date time.Time) string {
|
||||
return date.Format(config.SimpleDateFormat)
|
||||
}
|
||||
|
||||
func FormatDateTime(date time.Time) string {
|
||||
return date.Format(config.SimpleDateTimeFormat)
|
||||
}
|
||||
|
||||
func FormatDateTimeHuman(date time.Time) string {
|
||||
return date.Format("Mon, 02 Jan 2006 15:04")
|
||||
}
|
||||
|
||||
func FormatDateHuman(date time.Time) string {
|
||||
return date.Format("Mon, 02 Jan 2006")
|
||||
}
|
||||
|
||||
func Add(i, j int) int {
|
||||
return i + j
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func SubSlice[T any](slice []T, from, to uint) []T {
|
||||
if int(to) > len(slice) {
|
||||
to = uint(len(slice))
|
||||
}
|
||||
return slice[from:int(to)]
|
||||
}
|
@@ -1,8 +1,8 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/duke-git/lancet/v2/datetime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -47,16 +47,28 @@ func SplitRangeByDays(from time.Time, to time.Time) [][]time.Time {
|
||||
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
|
||||
func LocalTZOffset() time.Duration {
|
||||
_, offset := time.Now().Zone()
|
||||
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 (
|
||||
"github.com/duke-git/lancet/v2/datetime"
|
||||
"github.com/muety/wakapi/config"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -23,8 +22,8 @@ func init() {
|
||||
}
|
||||
|
||||
func TestDate_SplitRangeByDays(t *testing.T) {
|
||||
df1, _ := time.Parse(config.SimpleDateTimeFormat, "2021-04-25 20:25:00")
|
||||
dt1, _ := time.Parse(config.SimpleDateTimeFormat, "2021-04-28 06:45:00")
|
||||
df1, _ := time.Parse("2006-01-02 15:04:05", "2021-04-25 20:25:00")
|
||||
dt1, _ := time.Parse("2006-01-02 15:04:05", "2021-04-28 06:45:00")
|
||||
df2 := df1
|
||||
dt2 := datetime.EndOfDay(df1)
|
||||
df3 := df1
|
||||
|
@@ -1,8 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/muety/wakapi/config"
|
||||
"errors"
|
||||
"github.com/muety/wakapi/models"
|
||||
"net/http"
|
||||
"regexp"
|
||||
@@ -23,14 +22,6 @@ func init() {
|
||||
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 {
|
||||
cacheControl := r.Header.Get("cache-control")
|
||||
if strings.Contains(cacheControl, "no-cache") {
|
||||
@@ -67,3 +58,12 @@ func ParsePageParamsWithDefault(r *http.Request, page, size int) *models.PagePar
|
||||
}
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
146
utils/summary.go
146
utils/summary.go
@@ -1,146 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/muety/wakapi/models"
|
||||
"net/http"
|
||||
"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) {
|
||||
user := extractUser(r)
|
||||
params := r.URL.Query()
|
||||
|
||||
var err error
|
||||
var from, to time.Time
|
||||
|
||||
if interval := params.Get("interval"); interval != "" {
|
||||
err, from, to = ResolveIntervalRawTZ(interval, user.TZ())
|
||||
} else if start := params.Get("start"); start != "" {
|
||||
err, from, to = ResolveIntervalRawTZ(start, user.TZ())
|
||||
} else {
|
||||
from, err = ParseDateTimeTZ(params.Get("from"), user.TZ())
|
||||
if err != nil {
|
||||
return nil, errors.New("missing or invalid 'from' parameter")
|
||||
}
|
||||
|
||||
to, err = ParseDateTimeTZ(params.Get("to"), user.TZ())
|
||||
if err != nil {
|
||||
return nil, errors.New("missing or invalid 'to' parameter")
|
||||
}
|
||||
}
|
||||
|
||||
recompute := params.Get("recompute") != "" && params.Get("recompute") != "false"
|
||||
|
||||
filters := ParseSummaryFilters(r)
|
||||
|
||||
return &models.SummaryParams{
|
||||
From: from,
|
||||
To: to,
|
||||
User: user,
|
||||
Recompute: recompute,
|
||||
Filters: filters,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseSummaryFilters(r *http.Request) *models.Filters {
|
||||
filters := &models.Filters{}
|
||||
if q := r.URL.Query().Get("project"); q != "" {
|
||||
filters.With(models.SummaryProject, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("language"); q != "" {
|
||||
filters.With(models.SummaryLanguage, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("editor"); q != "" {
|
||||
filters.With(models.SummaryEditor, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("machine"); q != "" {
|
||||
filters.With(models.SummaryMachine, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("operating_system"); q != "" {
|
||||
filters.With(models.SummaryOS, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("label"); q != "" {
|
||||
filters.With(models.SummaryLabel, q)
|
||||
}
|
||||
if q := r.URL.Query().Get("branch"); q != "" {
|
||||
filters.With(models.SummaryBranch, q)
|
||||
}
|
||||
return filters
|
||||
}
|
||||
|
||||
func extractUser(r *http.Request) *models.User {
|
||||
type principalGetter interface {
|
||||
GetPrincipal() *models.User
|
||||
}
|
||||
if p := r.Context().Value("principal"); p != nil {
|
||||
return p.(principalGetter).GetPrincipal()
|
||||
}
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user