feat: data store
This commit is contained in:
parent
4f8ee6cf9c
commit
19bdd27de1
8 changed files with 123 additions and 53 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,2 +1,2 @@
|
||||||
.env
|
.env
|
||||||
data/
|
data.json
|
||||||
|
|
@ -2,12 +2,16 @@ package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"api/internal/service"
|
"api/internal/service"
|
||||||
|
"api/internal/storage"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func HandleGetHitCounter(w http.ResponseWriter, r *http.Request) {
|
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")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
json.NewEncoder(w).Encode(data)
|
json.NewEncoder(w).Encode(data)
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,19 @@
|
||||||
package service
|
package service
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"io"
|
|
||||||
"log/slog"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"api/internal/model"
|
"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 {
|
func IncrementHitCounter() model.Success {
|
||||||
data := GetHitCounter()
|
hitsData := storage.GlobalDataStore.Get("hits")
|
||||||
data.Counter++
|
|
||||||
|
|
||||||
err := os.MkdirAll(filepath.Dir(path), 0755)
|
var hits uint32
|
||||||
if err != nil {
|
if hitsData != nil {
|
||||||
slog.Error("Unable to create directory", slog.Any("error", err), slog.Any("path", filepath.Dir(path)))
|
hits = hitsData.(uint32)
|
||||||
return model.Success{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonString, err := json.Marshal(data)
|
storage.GlobalDataStore.Set("hits", hits+1)
|
||||||
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{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return model.Success{
|
return model.Success{
|
||||||
Success: true,
|
Success: true,
|
||||||
|
|
|
||||||
72
internal/storage/datastore.go
Normal file
72
internal/storage/datastore.go
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
17
internal/worker/datastore.go
Normal file
17
internal/worker/datastore.go
Normal file
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,6 @@ func StartLastFMWorker() {
|
||||||
LastFMData = service.GetLastFMData()
|
LastFMData = service.GetLastFMData()
|
||||||
|
|
||||||
for range time.Tick(30 * time.Second) {
|
for range time.Tick(30 * time.Second) {
|
||||||
slog.Info("Requesting last.fm...")
|
|
||||||
LastFMData = service.GetLastFMData()
|
LastFMData = service.GetLastFMData()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package worker
|
package worker
|
||||||
|
|
||||||
func StartWorkers() {
|
func StartWorkers() {
|
||||||
|
go StartDataStoreWorker()
|
||||||
go StartLastFMWorker()
|
go StartLastFMWorker()
|
||||||
go StartComputerWorker()
|
go StartComputerWorker()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
main.go
21
main.go
|
|
@ -3,11 +3,14 @@ package main
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/lmittmann/tint"
|
"github.com/lmittmann/tint"
|
||||||
|
|
||||||
"api/internal/server"
|
"api/internal/server"
|
||||||
|
"api/internal/storage"
|
||||||
"api/internal/worker"
|
"api/internal/worker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -20,6 +23,24 @@ func main() {
|
||||||
slog.Warn("No .env file was found; using environment variables.", slog.Any("error", err))
|
slog.Warn("No .env file was found; using environment variables.", slog.Any("error", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
storage.InitDataStore()
|
||||||
go worker.StartWorkers()
|
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()
|
server.NewRouter()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue