From 19bdd27de153dc0c623dcb02b21a069feed60737 Mon Sep 17 00:00:00 2001 From: trafficlunar Date: Sat, 22 Mar 2025 12:05:19 +0000 Subject: [PATCH] feat: data store --- .gitignore | 2 +- internal/handler/hit.go | 6 ++- internal/service/hit.go | 56 +++------------------------ internal/storage/datastore.go | 72 +++++++++++++++++++++++++++++++++++ internal/worker/datastore.go | 17 +++++++++ internal/worker/lastfm.go | 1 - internal/worker/worker.go | 1 + main.go | 21 ++++++++++ 8 files changed, 123 insertions(+), 53 deletions(-) create mode 100644 internal/storage/datastore.go create mode 100644 internal/worker/datastore.go diff --git a/.gitignore b/.gitignore index ad60b67..360b70b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ .env -data/ \ No newline at end of file +data.json \ No newline at end of file diff --git a/internal/handler/hit.go b/internal/handler/hit.go index f6d95c7..8471755 100644 --- a/internal/handler/hit.go +++ b/internal/handler/hit.go @@ -2,12 +2,16 @@ package handler import ( "api/internal/service" + "api/internal/storage" "encoding/json" "net/http" ) func HandleGetHitCounter(w http.ResponseWriter, r *http.Request) { - data := service.GetHitCounter() + data := storage.GlobalDataStore.Get("hits") + if data == nil { + data = 0 + } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(data) diff --git a/internal/service/hit.go b/internal/service/hit.go index a45c62d..f3162f1 100644 --- a/internal/service/hit.go +++ b/internal/service/hit.go @@ -1,63 +1,19 @@ package service import ( - "encoding/json" - "io" - "log/slog" - "os" - "path/filepath" - "api/internal/model" + "api/internal/storage" ) -const path = "./data/hit.json" - -func GetHitCounter() model.HitCounter { - var data model.HitCounter - - jsonFile, err := os.Open(path) - if err != nil { - slog.Warn("File not found or unable to open", slog.Any("error", err), slog.Any("path", path)) - return data - } - defer jsonFile.Close() - - bytes, err := io.ReadAll(jsonFile) - if err != nil { - slog.Error("Error reading file", slog.Any("error", err), slog.Any("path", path)) - return data - } - - err = json.Unmarshal(bytes, &data) - if err != nil { - slog.Error("Error unmarshalling JSON", slog.Any("error", err), slog.Any("path", path)) - return data - } - - return data -} - func IncrementHitCounter() model.Success { - data := GetHitCounter() - data.Counter++ + hitsData := storage.GlobalDataStore.Get("hits") - err := os.MkdirAll(filepath.Dir(path), 0755) - if err != nil { - slog.Error("Unable to create directory", slog.Any("error", err), slog.Any("path", filepath.Dir(path))) - return model.Success{} + var hits uint32 + if hitsData != nil { + hits = hitsData.(uint32) } - jsonString, err := json.Marshal(data) - if err != nil { - slog.Error("Error marshalling JSON", slog.Any("error", err), slog.Any("path", path)) - return model.Success{} - } - - err = os.WriteFile(path, jsonString, 0644) - if err != nil { - slog.Error("Error writing to file", slog.Any("error", err), slog.Any("path", path)) - return model.Success{} - } + storage.GlobalDataStore.Set("hits", hits+1) return model.Success{ Success: true, diff --git a/internal/storage/datastore.go b/internal/storage/datastore.go new file mode 100644 index 0000000..5d9fc97 --- /dev/null +++ b/internal/storage/datastore.go @@ -0,0 +1,72 @@ +package storage + +import ( + "encoding/json" + "log/slog" + "os" + "sync" +) + +type DataStore struct { + Data map[string]any + Mutex sync.Mutex +} + +var GlobalDataStore *DataStore + +func InitDataStore() *DataStore { + GlobalDataStore = &DataStore{ + Data: make(map[string]any), + } + + file, err := os.Open("./data.json") + if err != nil { + if os.IsNotExist(err) { + slog.Warn("Data store file not found; creating new store") + return GlobalDataStore + } + slog.Error("Could not load data store file!", slog.Any("error", err)) + return nil + } + defer file.Close() + + decoder := json.NewDecoder(file) + if err := decoder.Decode(&GlobalDataStore.Data); err != nil { + slog.Error("Failed to decode data store file", slog.Any("error", err)) + return nil + } + + return GlobalDataStore +} + +func (store *DataStore) Get(key string) any { + store.Mutex.Lock() + defer store.Mutex.Unlock() + return store.Data[key] +} + +func (store *DataStore) Set(key string, value any) { + store.Mutex.Lock() + defer store.Mutex.Unlock() + store.Data[key] = value +} + +func (store *DataStore) Save() error { + store.Mutex.Lock() + defer store.Mutex.Unlock() + + file, err := os.Create("./data.json") + if err != nil { + slog.Error("Could not create data store file", slog.Any("error", err)) + return err + } + defer file.Close() + + encoder := json.NewEncoder(file) + if err := encoder.Encode(store.Data); err != nil { + slog.Error("Failed to encode data store", slog.Any("error", err)) + return err + } + + return nil +} diff --git a/internal/worker/datastore.go b/internal/worker/datastore.go new file mode 100644 index 0000000..aa8d819 --- /dev/null +++ b/internal/worker/datastore.go @@ -0,0 +1,17 @@ +package worker + +import ( + "api/internal/storage" + "log/slog" + "time" +) + +func StartDataStoreWorker() { + slog.Info("Starting data store worker...") + + for range time.Tick(1 * time.Minute) { + if err := storage.GlobalDataStore.Save(); err != nil { + slog.Error("Error saving data store", slog.Any("error", err)) + } + } +} diff --git a/internal/worker/lastfm.go b/internal/worker/lastfm.go index 00c914f..6f0166f 100644 --- a/internal/worker/lastfm.go +++ b/internal/worker/lastfm.go @@ -14,7 +14,6 @@ func StartLastFMWorker() { LastFMData = service.GetLastFMData() for range time.Tick(30 * time.Second) { - slog.Info("Requesting last.fm...") LastFMData = service.GetLastFMData() } } diff --git a/internal/worker/worker.go b/internal/worker/worker.go index fd1310b..ad959f7 100644 --- a/internal/worker/worker.go +++ b/internal/worker/worker.go @@ -1,6 +1,7 @@ package worker func StartWorkers() { + go StartDataStoreWorker() go StartLastFMWorker() go StartComputerWorker() } diff --git a/main.go b/main.go index bcec210..fd60ff5 100644 --- a/main.go +++ b/main.go @@ -3,11 +3,14 @@ package main import ( "log/slog" "os" + "os/signal" + "syscall" "github.com/joho/godotenv" "github.com/lmittmann/tint" "api/internal/server" + "api/internal/storage" "api/internal/worker" ) @@ -20,6 +23,24 @@ func main() { slog.Warn("No .env file was found; using environment variables.", slog.Any("error", err)) } + storage.InitDataStore() go worker.StartWorkers() + + // Shutdown-chan~~ + shutdownChan := make(chan os.Signal, 1) + signal.Notify(shutdownChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM) + + go func() { + <-shutdownChan + + if err := storage.GlobalDataStore.Save(); err != nil { + slog.Error("Error saving data store on shutdown", slog.Any("error", err)) + } else { + slog.Info("Data store saved successfully on shutdown") + } + + os.Exit(0) + }() + server.NewRouter() }