From f99c93ac236d3c9052ee2a19bcb4a62ff3f25692 Mon Sep 17 00:00:00 2001 From: LordMathis Date: Fri, 27 Sep 2024 14:30:49 +0200 Subject: [PATCH] Add settings persistance on backend --- .gitignore | 29 ++++++++++++ backend/cmd/server/main.go | 40 ++++++++++------ backend/go.mod | 5 +- backend/go.sum | 2 + backend/internal/api/handlers.go | 40 ++++++++++++++++ backend/internal/api/routes.go | 23 +++++++++ backend/internal/db/db.go | 33 +++++++++++++ backend/internal/db/migrations.go | 73 +++++++++++++++++++++++++++++ backend/internal/db/settings.go | 38 +++++++++++++++ backend/internal/models/settings.go | 6 +++ 10 files changed, 273 insertions(+), 16 deletions(-) create mode 100644 backend/internal/api/routes.go create mode 100644 backend/internal/db/db.go create mode 100644 backend/internal/db/migrations.go create mode 100644 backend/internal/db/settings.go create mode 100644 backend/internal/models/settings.go diff --git a/.gitignore b/.gitignore index c6bba59..d29ce5f 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,32 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + + +##### Go ##### + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +# env file +.env + +main +*.db \ No newline at end of file diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 20b4bda..0941c44 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -9,33 +9,43 @@ import ( "github.com/go-chi/chi/v5/middleware" "novamd/internal/api" + "novamd/internal/db" "novamd/internal/filesystem" ) func main() { - r := chi.NewRouter() - // Middleware + // Initialize database + dbPath := os.Getenv("NOVAMD_DB_PATH") + if dbPath == "" { + dbPath = "./sqlite.db" + } + database, err := db.Init(dbPath) + if err != nil { + log.Fatal(err) + } + defer database.Close() + + // Workdir + workdir := os.Getenv("NOVAMD_WORKDIR") + if workdir == "" { + workdir = "./data" + } + fs := filesystem.New(workdir) + + // Set up router + r := chi.NewRouter() r.Use(middleware.Logger) r.Use(middleware.Recoverer) - // Initialize filesystem - folderPath := os.Getenv("FOLDER_PATH") - fs := filesystem.New(folderPath) + // Set up routes + api.SetupRoutes(r, database, fs) - // API routes - r.Route("/api/v1", func(r chi.Router) { - r.Get("/files", api.ListFiles(fs)) - r.Get("/files/*", api.GetFileContent(fs)) - r.Post("/files/*", api.SaveFile(fs)) - r.Delete("/files/*", api.DeleteFile(fs)) - }) - - port := os.Getenv("PORT") + // Start server + port := os.Getenv("NOVAMD_PORT") if port == "" { port = "8080" } - log.Printf("Server starting on port %s", port) log.Fatal(http.ListenAndServe(":"+port, r)) } diff --git a/backend/go.mod b/backend/go.mod index 5932a29..c980f49 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -2,4 +2,7 @@ module novamd go 1.23.1 -require github.com/go-chi/chi/v5 v5.1.0 +require ( + github.com/go-chi/chi/v5 v5.1.0 + github.com/mattn/go-sqlite3 v1.14.23 +) diff --git a/backend/go.sum b/backend/go.sum index 823cdbb..3491db0 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -1,2 +1,4 @@ github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= +github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/backend/internal/api/handlers.go b/backend/internal/api/handlers.go index 968a02a..e494dc8 100644 --- a/backend/internal/api/handlers.go +++ b/backend/internal/api/handlers.go @@ -4,9 +4,12 @@ import ( "encoding/json" "io" "net/http" + "strconv" "strings" + "novamd/internal/db" "novamd/internal/filesystem" + "novamd/internal/models" ) func ListFiles(fs *filesystem.FileSystem) http.HandlerFunc { @@ -69,3 +72,40 @@ func DeleteFile(fs *filesystem.FileSystem) http.HandlerFunc { w.Write([]byte("File deleted successfully")) } } + +func GetSettings(db *db.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + userIDStr := r.URL.Query().Get("userId") + userID, err := strconv.Atoi(userIDStr) + if err != nil { + http.Error(w, "Invalid userId", http.StatusBadRequest) + return + } + + settings, err := db.GetSettings(userID) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + json.NewEncoder(w).Encode(settings) + } +} + +func UpdateSettings(db *db.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var settings models.Settings + if err := json.NewDecoder(r.Body).Decode(&settings); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + err := db.SaveSettings(settings) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + } +} diff --git a/backend/internal/api/routes.go b/backend/internal/api/routes.go new file mode 100644 index 0000000..95c7855 --- /dev/null +++ b/backend/internal/api/routes.go @@ -0,0 +1,23 @@ +package api + +import ( + "novamd/internal/db" + "novamd/internal/filesystem" + + "github.com/go-chi/chi/v5" +) + +func SetupRoutes(r chi.Router, db *db.DB, fs *filesystem.FileSystem) { + r.Route("/api/v1", func(r chi.Router) { + r.Route("/settings", func(r chi.Router) { + r.Get("/", GetSettings(db)) + r.Post("/", UpdateSettings(db)) + }) + r.Route("/files", func(r chi.Router) { + r.Get("/", ListFiles(fs)) + r.Get("/*", GetFileContent(fs)) + r.Post("/*", SaveFile(fs)) + r.Delete("/*", DeleteFile(fs)) + }) + }) +} diff --git a/backend/internal/db/db.go b/backend/internal/db/db.go new file mode 100644 index 0000000..6389ff5 --- /dev/null +++ b/backend/internal/db/db.go @@ -0,0 +1,33 @@ +package db + +import ( + "database/sql" + + _ "github.com/mattn/go-sqlite3" +) + +type DB struct { + *sql.DB +} + +func Init(dbPath string) (*DB, error) { + db, err := sql.Open("sqlite3", dbPath) + if err != nil { + return nil, err + } + + if err := db.Ping(); err != nil { + return nil, err + } + + database := &DB{db} + if err := database.Migrate(); err != nil { + return nil, err + } + + return database, nil +} + +func (db *DB) Close() error { + return db.DB.Close() +} diff --git a/backend/internal/db/migrations.go b/backend/internal/db/migrations.go new file mode 100644 index 0000000..9c2e6ce --- /dev/null +++ b/backend/internal/db/migrations.go @@ -0,0 +1,73 @@ +package db + +import ( + "fmt" + "log" +) + +type Migration struct { + Version int + SQL string +} + +var migrations = []Migration{ + { + Version: 1, + SQL: `CREATE TABLE IF NOT EXISTS settings ( + user_id INTEGER PRIMARY KEY, + settings TEXT + )`, + }, + // Add new migrations here as your schema evolves +} + +func (db *DB) Migrate() error { + // Create migrations table if it doesn't exist + _, err := db.Exec(`CREATE TABLE IF NOT EXISTS migrations ( + version INTEGER PRIMARY KEY + )`) + if err != nil { + return err + } + + // Get current version + var currentVersion int + err = db.QueryRow("SELECT COALESCE(MAX(version), 0) FROM migrations").Scan(¤tVersion) + if err != nil { + return err + } + + // Apply new migrations + for _, migration := range migrations { + if migration.Version > currentVersion { + log.Printf("Applying migration %d", migration.Version) + + tx, err := db.Begin() + if err != nil { + return err + } + + _, err = tx.Exec(migration.SQL) + if err != nil { + tx.Rollback() + return fmt.Errorf("migration %d failed: %v", migration.Version, err) + } + + _, err = tx.Exec("INSERT INTO migrations (version) VALUES (?)", migration.Version) + if err != nil { + tx.Rollback() + return fmt.Errorf("failed to update migration version: %v", err) + } + + err = tx.Commit() + if err != nil { + return fmt.Errorf("failed to commit migration %d: %v", migration.Version, err) + } + + currentVersion = migration.Version + } + } + + log.Printf("Database is at version %d", currentVersion) + return nil +} diff --git a/backend/internal/db/settings.go b/backend/internal/db/settings.go new file mode 100644 index 0000000..e6b844f --- /dev/null +++ b/backend/internal/db/settings.go @@ -0,0 +1,38 @@ +package db + +import ( + "database/sql" + "encoding/json" + + "novamd/internal/models" +) + +func (db *DB) GetSettings(userID int) (models.Settings, error) { + var settings models.Settings + var settingsJSON string + + err := db.QueryRow("SELECT user_id, settings FROM settings WHERE user_id = ?", userID).Scan(&settings.UserID, &settingsJSON) + if err != nil { + if err == sql.ErrNoRows { + return settings, nil + } + return settings, err + } + + err = json.Unmarshal([]byte(settingsJSON), &settings.Settings) + if err != nil { + return settings, err + } + + return settings, nil +} + +func (db *DB) SaveSettings(settings models.Settings) error { + settingsJSON, err := json.Marshal(settings.Settings) + if err != nil { + return err + } + + _, err = db.Exec("INSERT OR REPLACE INTO settings (user_id, settings) VALUES (?, ?)", settings.UserID, string(settingsJSON)) + return err +} diff --git a/backend/internal/models/settings.go b/backend/internal/models/settings.go new file mode 100644 index 0000000..6fcc15b --- /dev/null +++ b/backend/internal/models/settings.go @@ -0,0 +1,6 @@ +package models + +type Settings struct { + UserID int `json:"userId"` + Settings map[string]interface{} `json:"settings"` +}