From 7df265120873a05e0470b9eeab8ec1bb4024283e Mon Sep 17 00:00:00 2001 From: jafreli Date: Fri, 30 Jan 2026 23:45:13 +0100 Subject: [PATCH] Backend --- .gitignore | 17 +++++ backend/api.go | 182 ++++++++++++++++++++++++++++++++++++++++++++++++ backend/go.mod | 7 ++ backend/go.sum | 4 ++ backend/main.go | 34 +++++++++ 5 files changed, 244 insertions(+) create mode 100644 .gitignore create mode 100644 backend/api.go create mode 100644 backend/go.mod create mode 100644 backend/go.sum create mode 100644 backend/main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1a6139a --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Output of the go generate command +*.out + +# Dependency directories (go modules) +/vendor/ + +# Go build cache +.cache/go-build/ + +# Database files +*.db diff --git a/backend/api.go b/backend/api.go new file mode 100644 index 0000000..fb98371 --- /dev/null +++ b/backend/api.go @@ -0,0 +1,182 @@ +package main + +import ( + "database/sql" + "encoding/json" + "log" + "net/http" + "strconv" + + "github.com/go-chi/chi/v5" + _ "github.com/mattn/go-sqlite3" +) + +// Item repräsentiert einen Eintrag im Dashboard. +type Item struct { + ID int64 `json:"id"` + Name string `json:"name"` // Ein eindeutiger interner Name/Slug + DisplayName string `json:"displayName"` // Der Anzeigename in der UI + Target string `json:"target"` // Die Domain oder IP + IconURL string `json:"iconUrl"` // URL zu einem Icon +} + +// --- Datenbank-Funktionen --- + +// InitDB initialisiert die Datenbank und erstellt die Tabelle, falls sie nicht existiert. +func InitDB(filepath string) *sql.DB { + db, err := sql.Open("sqlite3", filepath) + if err != nil { + log.Fatal(err) + } + + createTableSQL := `CREATE TABLE IF NOT EXISTS items ( + "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + "name" TEXT, + "displayName" TEXT, + "target" TEXT, + "iconUrl" TEXT + );` + + _, err = db.Exec(createTableSQL) + if err != nil { + log.Fatal(err) + } + + return db +} + +// --- HTTP Handler --- + +// GetAllItemsHandler gibt alle Dashboard-Einträge zurück. +func GetAllItemsHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + rows, err := db.Query("SELECT id, name, displayName, target, iconUrl FROM items") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer rows.Close() + + items := []Item{} + for rows.Next() { + var item Item + if err := rows.Scan(&item.ID, &item.Name, &item.DisplayName, &item.Target, &item.IconURL); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + items = append(items, item) + } + + respondWithJSON(w, http.StatusOK, items) + } +} + +// CreateItemHandler erstellt einen neuen Eintrag. +func CreateItemHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + var item Item + if err := json.NewDecoder(r.Body).Decode(&item); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + stmt, err := db.Prepare("INSERT INTO items(name, displayName, target, iconUrl) VALUES(?, ?, ?, ?)") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + res, err := stmt.Exec(item.Name, item.DisplayName, item.Target, item.IconURL) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + id, _ := res.LastInsertId() + item.ID = id + + respondWithJSON(w, http.StatusCreated, item) + } +} + +// UpdateItemHandler aktualisiert einen bestehenden Eintrag. +func UpdateItemHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + http.Error(w, "Invalid item ID", http.StatusBadRequest) + return + } + + var item Item + if err := json.NewDecoder(r.Body).Decode(&item); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + stmt, err := db.Prepare("UPDATE items SET name = ?, displayName = ?, target = ?, iconUrl = ? WHERE id = ?") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + _, err = stmt.Exec(item.Name, item.DisplayName, item.Target, item.IconURL, id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + item.ID = id // Stelle sicher, dass die ID im Antwortobjekt korrekt ist + respondWithJSON(w, http.StatusOK, item) + } +} + +// DeleteItemHandler löscht einen Eintrag. +func DeleteItemHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + http.Error(w, "Invalid item ID", http.StatusBadRequest) + return + } + + _, err = db.Exec("DELETE FROM items WHERE id = ?", id) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusNoContent) + } +} + +// GetItemHandler ruft einen einzelnen Eintrag ab. +func GetItemHandler(db *sql.DB) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) + if err != nil { + http.Error(w, "Invalid item ID", http.StatusBadRequest) + return + } + + var item Item + err = db.QueryRow("SELECT id, name, displayName, target, iconUrl FROM items WHERE id = ?", id).Scan(&item.ID, &item.Name, &item.DisplayName, &item.Target, &item.IconURL) + if err == sql.ErrNoRows { + http.Error(w, "Item not found", http.StatusNotFound) + return + } else if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + respondWithJSON(w, http.StatusOK, item) + } +} + +// respondWithJSON ist eine Hilfsfunktion zum Senden von JSON-Antworten. +func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) { + response, _ := json.Marshal(payload) + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + w.Write(response) +} diff --git a/backend/go.mod b/backend/go.mod new file mode 100644 index 0000000..4f69635 --- /dev/null +++ b/backend/go.mod @@ -0,0 +1,7 @@ +module git.out.jafre.li/jafreli/dashboard/backend + +go 1.25.6 + +require github.com/go-chi/chi/v5 v5.2.3 + +require github.com/mattn/go-sqlite3 v1.14.33 diff --git a/backend/go.sum b/backend/go.sum new file mode 100644 index 0000000..50e3f0f --- /dev/null +++ b/backend/go.sum @@ -0,0 +1,4 @@ +github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= +github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= +github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0= +github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= diff --git a/backend/main.go b/backend/main.go new file mode 100644 index 0000000..6988762 --- /dev/null +++ b/backend/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "log" + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +func main() { + db := InitDB("./dashboard.db") + r := chi.NewRouter() + + r.Use(middleware.Logger) + r.Use(middleware.Recoverer) + + r.Route("/api/items", func(r chi.Router) { + r.Get("/", GetAllItemsHandler(db)) // GET /api/items + r.Post("/", CreateItemHandler(db)) // POST /api/items + + r.Route("/{id}", func(r chi.Router) { + r.Get("/", GetItemHandler(db)) // GET /api/items/123 + r.Put("/", UpdateItemHandler(db)) // PUT /api/items/123 + r.Delete("/", DeleteItemHandler(db)) // DELETE /api/items/123 + }) + }) + + log.Println("Server startet auf http://localhost:8080") + err := http.ListenAndServe(":8080", r) + if err != nil { + log.Fatal(err) + } +}