Pull Proclamations & Categories from API and store them in the database
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"rhemawavetech.com/rhemawave/internal/mixer"
|
||||
"rhemawavetech.com/rhemawave/internal/page"
|
||||
"rhemawavetech.com/rhemawave/internal/shaderManager"
|
||||
"rhemawavetech.com/rhemawave/internal/sync"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -27,9 +28,7 @@ const (
|
||||
volumeStepSize = 16
|
||||
openGLMajorVersion = 3
|
||||
openGLMinorVersion = 3
|
||||
apiURL = "http://localhost:8000"
|
||||
apiPath = "/api/v1/licenseKey/validate"
|
||||
audioFileURL = "https://files.neilclack.com/audio.wav"
|
||||
apiURL = "http://localhost:5173/api/v1"
|
||||
)
|
||||
|
||||
type Application struct {
|
||||
@@ -56,7 +55,6 @@ func Run() {
|
||||
var result *gorm.DB
|
||||
|
||||
logger.Info("Starting Application")
|
||||
logger.Info("Finding $HOME")
|
||||
|
||||
homeDir, err = os.UserHomeDir()
|
||||
if err != nil {
|
||||
@@ -64,7 +62,6 @@ func Run() {
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Constructing DataDir Path")
|
||||
dataDir = filepath.Join(homeDir, ".local", "share", "rhemawave")
|
||||
if err := os.MkdirAll(dataDir, 0755); err != nil {
|
||||
logger.Error("unable to create data directory %s %v", dataDir, err)
|
||||
@@ -82,8 +79,6 @@ func Run() {
|
||||
|
||||
logger.AddFile(filepath.Join(dataDir, "log.txt"))
|
||||
|
||||
logger.Info("Connecting to database")
|
||||
|
||||
DB, err = db.Connect(filepath.Join(dataDir, "database.sqlite"))
|
||||
if err != nil {
|
||||
logger.Error("application failed to connect to the database: %v", err)
|
||||
@@ -141,6 +136,11 @@ func Run() {
|
||||
H: h,
|
||||
RefreshConfig: app.refreshCache,
|
||||
})
|
||||
|
||||
// Sync the proclamations from the API to the Database
|
||||
sync.SyncProclamations(apiURL, DB)
|
||||
sync.SyncCategories(apiURL, DB)
|
||||
|
||||
app.runEventLoop()
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ func Connect(dbpath string) (*gorm.DB, error) {
|
||||
// Migrate the database schema
|
||||
func Migrate(db *gorm.DB) error {
|
||||
// Pass all models to be migrated to this function call.
|
||||
if err := db.AutoMigrate(&AppConfig{}, &Log{}, &Proclamation{}); err != nil {
|
||||
if err := db.AutoMigrate(&AppConfig{}, &Log{}, &Proclamation{}, &ProclamationCategory{}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -27,7 +27,6 @@ type Log struct {
|
||||
}
|
||||
|
||||
type Proclamation struct {
|
||||
gorm.Model
|
||||
ID string `gorm:"primaryKey"`
|
||||
Name string
|
||||
Content string
|
||||
@@ -37,3 +36,9 @@ type Proclamation struct {
|
||||
Available bool
|
||||
Price float64
|
||||
}
|
||||
|
||||
type ProclamationCategory struct {
|
||||
ID int `gorm:"primaryKey"`
|
||||
Name string
|
||||
ParentId int
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
package netreq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Makes a GET request to the specified URL and returns the JSON response
|
||||
// as a byte slice. If the request fails, it returns an error.
|
||||
func GetJSON(url string) ([]byte, error) {
|
||||
fmt.Println(url)
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
if res.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("request failed with status code %d: %s", res.StatusCode, string(body))
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package netreq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
url := "http://localhost:5173/api/v1/proclamations"
|
||||
body, err := GetJSON(url)
|
||||
if err != nil {
|
||||
t.Fatalf("GetJSON failed: %v", err)
|
||||
}
|
||||
if len(body) == 0 {
|
||||
t.Fatal("GetJSON returned empty body")
|
||||
}
|
||||
cnt := string(body)
|
||||
fmt.Printf("%s\n", cnt)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package netreq
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Makes a GET request to the specified URL and returns the JSON response
|
||||
// as a byte slice. If the request fails, it returns an error.
|
||||
func GetJSON[T any](url string) ([]T, error) {
|
||||
fmt.Println("Fetching:", url)
|
||||
res, err := http.Get(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if res.StatusCode > 299 {
|
||||
return nil, fmt.Errorf("status %d: %s", res.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// 1. Trim whitespace to find the first actual JSON character
|
||||
trimmed := bytes.TrimSpace(body)
|
||||
if len(trimmed) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var result []T
|
||||
|
||||
// 2. Determine if we are looking at an Array [ ] or an Object { }
|
||||
if trimmed[0] == '[' {
|
||||
// It's already an array, unmarshal directly
|
||||
err = json.Unmarshal(body, &result)
|
||||
} else {
|
||||
// It's a single object, unmarshal into a single instance and wrap it
|
||||
var single T
|
||||
err = json.Unmarshal(body, &single)
|
||||
result = []T{single}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse JSON: %w", err)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package netreq
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"rhemawavetech.com/rhemawave/internal/db"
|
||||
)
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
url := "http://localhost:5173/api/v1/proclamations"
|
||||
|
||||
// Explicitly tell GetJSON to return a slice of db.Proclamation
|
||||
proclamations, err := GetJSON[db.Proclamation](url)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("GetJSON failed: %v", err)
|
||||
}
|
||||
|
||||
if len(proclamations) == 0 {
|
||||
t.Fatal("GetJSON returned no records")
|
||||
}
|
||||
|
||||
// You can now inspect the actual struct fields instead of raw strings
|
||||
fmt.Printf("Fetched %d records. First ID: %s\n", len(proclamations), proclamations[0].ID)
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"rhemawavetech.com/rhemawave/internal/db"
|
||||
"rhemawavetech.com/rhemawave/internal/logger"
|
||||
"rhemawavetech.com/rhemawave/internal/netreq"
|
||||
)
|
||||
|
||||
var htmlRegex = regexp.MustCompile(`<[^>]*>`)
|
||||
|
||||
// SyncProclamations fetches data from the API, cleans it, and updates the local DB.
|
||||
// It returns the slice of proclamations so the UI can update immediately.
|
||||
func SyncProclamations(apiURL string, database *gorm.DB) ([]db.Proclamation, error) {
|
||||
fullURL := fmt.Sprintf("%s/proclamations", apiURL)
|
||||
|
||||
// 1. Fetch from API
|
||||
proclamations, err := netreq.GetJSON[db.Proclamation](fullURL)
|
||||
if err != nil {
|
||||
// If we're offline or API is down, we log it but don't crash.
|
||||
// The UI can still use whatever is currently in the DB.
|
||||
logger.Error("Proclamation sync failed, skipping update: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 2. Data Transformation (Cleaning)
|
||||
for i := range proclamations {
|
||||
proclamations[i].Content = htmlRegex.ReplaceAllString(proclamations[i].Content, "")
|
||||
}
|
||||
|
||||
// 3. Persist to Database
|
||||
// Using Transaction ensures we don't end up with partial data if something fails
|
||||
err = database.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Save(&proclamations).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to save synced proclamations: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("Successfully synced proclamations: %d", len(proclamations))
|
||||
return proclamations, nil
|
||||
}
|
||||
|
||||
func SyncCategories(apiURL string, database *gorm.DB) ([]db.ProclamationCategory, error) {
|
||||
fullURL := fmt.Sprintf("%s/categories", apiURL)
|
||||
categories, err := netreq.GetJSON[db.ProclamationCategory](fullURL)
|
||||
if err != nil {
|
||||
logger.Error("Category sync failed, skipping update: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
err = database.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Save(&categories).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
logger.Info("Successfully synced proclamation categories: %d", len(categories))
|
||||
return categories, nil
|
||||
}
|
||||
Reference in New Issue
Block a user