summaryrefslogtreecommitdiff
path: root/internal/worktime
diff options
context:
space:
mode:
Diffstat (limited to 'internal/worktime')
-rw-r--r--internal/worktime/db.go59
-rw-r--r--internal/worktime/db_test.go52
2 files changed, 110 insertions, 1 deletions
diff --git a/internal/worktime/db.go b/internal/worktime/db.go
index ad87c4d..e6b06f4 100644
--- a/internal/worktime/db.go
+++ b/internal/worktime/db.go
@@ -4,9 +4,11 @@ import (
"encoding/json"
"errors"
"fmt"
+ "math"
"os"
"path/filepath"
"sort"
+ "strconv"
"strings"
)
@@ -23,6 +25,51 @@ type Entry struct {
Descr string `json:"descr,omitempty"`
}
+// UnmarshalJSON supports legacy value encodings where "value" can be int or float.
+func (e *Entry) UnmarshalJSON(data []byte) error {
+ type entryAlias Entry
+ aux := struct {
+ entryAlias
+ Value json.RawMessage `json:"value"`
+ }{}
+
+ if err := json.Unmarshal(data, &aux); err != nil {
+ return err
+ }
+
+ *e = Entry(aux.entryAlias)
+ e.Value = 0
+
+ raw := strings.TrimSpace(string(aux.Value))
+ if raw == "" || raw == "null" {
+ return nil
+ }
+
+ var intValue int64
+ if err := json.Unmarshal(aux.Value, &intValue); err == nil {
+ e.Value = intValue
+ return nil
+ }
+
+ var floatValue float64
+ if err := json.Unmarshal(aux.Value, &floatValue); err == nil {
+ e.Value = int64(math.Round(floatValue))
+ return nil
+ }
+
+ var stringValue string
+ if err := json.Unmarshal(aux.Value, &stringValue); err == nil {
+ parsed, parseErr := strconv.ParseFloat(strings.TrimSpace(stringValue), 64)
+ if parseErr != nil {
+ return fmt.Errorf("parse string value %q: %w", stringValue, parseErr)
+ }
+ e.Value = int64(math.Round(parsed))
+ return nil
+ }
+
+ return fmt.Errorf("unsupported value encoding %s", raw)
+}
+
// Database is the on-disk JSON structure used by worktime.
type Database struct {
Entries map[string][]Entry `json:"entries"`
@@ -45,7 +92,12 @@ func LoadAll(dbDir string) ([]Entry, error) {
if err != nil {
return nil, err
}
- for _, hostEntries := range db.Entries {
+ for host, hostEntries := range db.Entries {
+ for idx := range hostEntries {
+ if strings.TrimSpace(hostEntries[idx].Source) == "" {
+ hostEntries[idx].Source = host
+ }
+ }
entries = append(entries, hostEntries...)
}
}
@@ -76,6 +128,11 @@ func LoadHost(dbDir, hostname string) (Database, error) {
if _, ok := db.Entries[host]; !ok {
db.Entries[host] = []Entry{}
}
+ for idx := range db.Entries[host] {
+ if strings.TrimSpace(db.Entries[host][idx].Source) == "" {
+ db.Entries[host][idx].Source = host
+ }
+ }
sortEntries(db.Entries[host])
return db, nil
}
diff --git a/internal/worktime/db_test.go b/internal/worktime/db_test.go
index d011a9e..f599fa1 100644
--- a/internal/worktime/db_test.go
+++ b/internal/worktime/db_test.go
@@ -129,6 +129,32 @@ func TestLoadAllMergesAndSortsEntries(t *testing.T) {
}
}
+func TestLoadAllBackfillsMissingSourceFromHost(t *testing.T) {
+ dbDir := t.TempDir()
+ dbFile := filepath.Join(dbDir, "db.host-a.json")
+ content := `{
+ "entries": {
+ "host-a": [
+ {"action":"login","what":"work","epoch":10,"human":"h1"}
+ ]
+ }
+}`
+ if err := os.WriteFile(dbFile, []byte(content), 0o644); err != nil {
+ t.Fatalf("WriteFile() error = %v", err)
+ }
+
+ entries, err := LoadAll(dbDir)
+ if err != nil {
+ t.Fatalf("LoadAll() error = %v", err)
+ }
+ if len(entries) != 1 {
+ t.Fatalf("entries len = %d, want 1", len(entries))
+ }
+ if entries[0].Source != "host-a" {
+ t.Fatalf("entries[0].Source = %q, want host-a", entries[0].Source)
+ }
+}
+
func TestLoadAllOnMissingDirectoryReturnsEmptySlice(t *testing.T) {
dbDir := filepath.Join(t.TempDir(), "does-not-exist")
@@ -159,6 +185,32 @@ func TestLoadHostInvalidJSON(t *testing.T) {
}
}
+func TestLoadAllAcceptsFloatValueEncoding(t *testing.T) {
+ dbDir := t.TempDir()
+ dbFile := filepath.Join(dbDir, "db.host-a.json")
+ content := `{
+ "entries": {
+ "host-a": [
+ {"action":"add","what":"work","epoch":10,"source":"host-a","human":"h1","value":31680.000000000004}
+ ]
+ }
+}`
+ if err := os.WriteFile(dbFile, []byte(content), 0o644); err != nil {
+ t.Fatalf("WriteFile() error = %v", err)
+ }
+
+ entries, err := LoadAll(dbDir)
+ if err != nil {
+ t.Fatalf("LoadAll() error = %v", err)
+ }
+ if len(entries) != 1 {
+ t.Fatalf("entries len = %d, want 1", len(entries))
+ }
+ if entries[0].Value != 31680 {
+ t.Fatalf("entries[0].Value = %d, want 31680", entries[0].Value)
+ }
+}
+
func TestLoadAllInvalidJSON(t *testing.T) {
dbDir := t.TempDir()
badFile := filepath.Join(dbDir, "db.host-a.json")