From 4ec4444a5141f860437a58b729ef56b07a390630 Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Mon, 26 May 2025 11:39:32 +0200 Subject: [PATCH] ff --- account.go => admin/account.go | 830 +++++++++--------- admin.go => admin/admin.go | 1466 ++++++++++++++++---------------- go.sum | 4 - main.go | 17 +- 4 files changed, 1157 insertions(+), 1160 deletions(-) rename account.go => admin/account.go (93%) rename admin.go => admin/admin.go (95%) diff --git a/account.go b/admin/account.go similarity index 93% rename from account.go rename to admin/account.go index e0ab768..c90beb9 100644 --- a/account.go +++ b/admin/account.go @@ -1,415 +1,415 @@ -package main - -import ( - "crypto/rand" - "encoding/base64" - "encoding/json" - "net/http" - "time" -) - -type User struct { - Username string `json:"username"` - Password string `json:"password"` - Role string `json:"role"` -} - -type Session struct { - Token string - Username string - Role string - ExpiresAt time.Time -} - -type LoginRequest struct { - Username string `json:"username"` - Password string `json:"password"` -} - -type LoginResponse struct { - Success bool `json:"success"` - Message string `json:"message"` - Token string `json:"token,omitempty"` - Role string `json:"role,omitempty"` -} - -// In-memory storage (replace with database in production) -var ( - users = map[string]User{ - "admin": { - Username: "admin", - Password: "admin123", // In production, use hashed passwords - Role: "admin", - }, - } - sessions = make(map[string]Session) -) - -func generateToken() (string, error) { - bytes := make([]byte, 32) - if _, err := rand.Read(bytes); err != nil { - return "", err - } - return base64.URLEncoding.EncodeToString(bytes), nil -} - -func handleLogin(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - if r.Method == "GET" { - // Serve login page - tmpl := ` - - - - - Přihlášení - Správa - - - -
- - -
- -
-
- - -
- -
- - -
- - -
- -
- Přihlašování... -
-
- - - -` - w.Header().Set("Content-Type", "text/html") - w.Write([]byte(tmpl)) - return - } - - if r.Method != "POST" { - w.WriteHeader(http.StatusMethodNotAllowed) - json.NewEncoder(w).Encode(LoginResponse{ - Success: false, - Message: "Method not allowed", - }) - return - } - - var loginReq LoginRequest - if err := json.NewDecoder(r.Body).Decode(&loginReq); err != nil { - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(LoginResponse{ - Success: false, - Message: "Invalid request format", - }) - return - } - - // Check credentials - user, exists := users[loginReq.Username] - if !exists || user.Password != loginReq.Password { - w.WriteHeader(http.StatusUnauthorized) - json.NewEncoder(w).Encode(LoginResponse{ - Success: false, - Message: "Neplatné přihlašovací údaje", - }) - return - } - - // Generate session token - token, err := generateToken() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - json.NewEncoder(w).Encode(LoginResponse{ - Success: false, - Message: "Chyba při vytváření relace", - }) - return - } - - // Store session - sessions[token] = Session{ - Token: token, - Username: user.Username, - Role: user.Role, - ExpiresAt: time.Now().Add(24 * time.Hour), - } - - json.NewEncoder(w).Encode(LoginResponse{ - Success: true, - Message: "Přihlášení úspěšné", - Token: token, - Role: user.Role, - }) -} - -func handleLogout(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - token := r.Header.Get("Authorization") - if token == "" { - // Try to get from cookie - if cookie, err := r.Cookie("authToken"); err == nil { - token = cookie.Value - } - } - - if token != "" { - delete(sessions, token) - } - - json.NewEncoder(w).Encode(map[string]interface{}{ - "success": true, - "message": "Odhlášení úspěšné", - }) -} - -func requireAuth(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - token := r.Header.Get("Authorization") - if token == "" { - // Try to get from cookie - if cookie, err := r.Cookie("authToken"); err == nil { - token = cookie.Value - } - } - - if token == "" { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - session, exists := sessions[token] - if !exists || time.Now().After(session.ExpiresAt) { - if exists { - delete(sessions, token) - } - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - // Extend session - session.ExpiresAt = time.Now().Add(24 * time.Hour) - sessions[token] = session - - next(w, r) - } -} - -func requireAdminAuth(next http.HandlerFunc) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - token := r.Header.Get("Authorization") - if token == "" { - // Try to get from cookie - if cookie, err := r.Cookie("authToken"); err == nil { - token = cookie.Value - } - } - - if token == "" { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - session, exists := sessions[token] - if !exists || time.Now().After(session.ExpiresAt) || session.Role != "admin" { - if exists { - delete(sessions, token) - } - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - // Extend session - session.ExpiresAt = time.Now().Add(24 * time.Hour) - sessions[token] = session - - next(w, r) - } -} - -func getCurrentUser(r *http.Request) *Session { - token := r.Header.Get("Authorization") - if token == "" { - if cookie, err := r.Cookie("authToken"); err == nil { - token = cookie.Value - } - } - - if token == "" { - return nil - } - - session, exists := sessions[token] - if !exists || time.Now().After(session.ExpiresAt) { - return nil - } - - return &session -} +package admin + +import ( + "crypto/rand" + "encoding/base64" + "encoding/json" + "net/http" + "time" +) + +type User struct { + Username string `json:"username"` + Password string `json:"password"` + Role string `json:"role"` +} + +type Session struct { + Token string + Username string + Role string + ExpiresAt time.Time +} + +type LoginRequest struct { + Username string `json:"username"` + Password string `json:"password"` +} + +type LoginResponse struct { + Success bool `json:"success"` + Message string `json:"message"` + Token string `json:"token,omitempty"` + Role string `json:"role,omitempty"` +} + +// In-memory storage (replace with database in production) +var ( + users = map[string]User{ + "admin": { + Username: "admin", + Password: "admin123", // In production, use hashed passwords + Role: "admin", + }, + } + sessions = make(map[string]Session) +) + +func generateToken() (string, error) { + bytes := make([]byte, 32) + if _, err := rand.Read(bytes); err != nil { + return "", err + } + return base64.URLEncoding.EncodeToString(bytes), nil +} + +func HandleLogin(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Method == "GET" { + // Serve login page + tmpl := ` + + + + + Přihlášení - Správa + + + +
+ + +
+ +
+
+ + +
+ +
+ + +
+ + +
+ +
+ Přihlašování... +
+
+ + + +` + w.Header().Set("Content-Type", "text/html") + w.Write([]byte(tmpl)) + return + } + + if r.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + json.NewEncoder(w).Encode(LoginResponse{ + Success: false, + Message: "Method not allowed", + }) + return + } + + var loginReq LoginRequest + if err := json.NewDecoder(r.Body).Decode(&loginReq); err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(LoginResponse{ + Success: false, + Message: "Invalid request format", + }) + return + } + + // Check credentials + user, exists := users[loginReq.Username] + if !exists || user.Password != loginReq.Password { + w.WriteHeader(http.StatusUnauthorized) + json.NewEncoder(w).Encode(LoginResponse{ + Success: false, + Message: "Neplatné přihlašovací údaje", + }) + return + } + + // Generate session token + token, err := generateToken() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + json.NewEncoder(w).Encode(LoginResponse{ + Success: false, + Message: "Chyba při vytváření relace", + }) + return + } + + // Store session + sessions[token] = Session{ + Token: token, + Username: user.Username, + Role: user.Role, + ExpiresAt: time.Now().Add(24 * time.Hour), + } + + json.NewEncoder(w).Encode(LoginResponse{ + Success: true, + Message: "Přihlášení úspěšné", + Token: token, + Role: user.Role, + }) +} + +func HandleLogout(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + token := r.Header.Get("Authorization") + if token == "" { + // Try to get from cookie + if cookie, err := r.Cookie("authToken"); err == nil { + token = cookie.Value + } + } + + if token != "" { + delete(sessions, token) + } + + json.NewEncoder(w).Encode(map[string]interface{}{ + "success": true, + "message": "Odhlášení úspěšné", + }) +} + +func RequireAuth(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + if token == "" { + // Try to get from cookie + if cookie, err := r.Cookie("authToken"); err == nil { + token = cookie.Value + } + } + + if token == "" { + http.Redirect(w, r, "/login", http.StatusFound) + return + } + + session, exists := sessions[token] + if !exists || time.Now().After(session.ExpiresAt) { + if exists { + delete(sessions, token) + } + http.Redirect(w, r, "/login", http.StatusFound) + return + } + + // Extend session + session.ExpiresAt = time.Now().Add(24 * time.Hour) + sessions[token] = session + + next(w, r) + } +} + +func RequireAdminAuth(next http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + token := r.Header.Get("Authorization") + if token == "" { + // Try to get from cookie + if cookie, err := r.Cookie("authToken"); err == nil { + token = cookie.Value + } + } + + if token == "" { + http.Redirect(w, r, "/login", http.StatusFound) + return + } + + session, exists := sessions[token] + if !exists || time.Now().After(session.ExpiresAt) || session.Role != "admin" { + if exists { + delete(sessions, token) + } + http.Redirect(w, r, "/login", http.StatusFound) + return + } + + // Extend session + session.ExpiresAt = time.Now().Add(24 * time.Hour) + sessions[token] = session + + next(w, r) + } +} + +func GetCurrentUser(r *http.Request) *Session { + token := r.Header.Get("Authorization") + if token == "" { + if cookie, err := r.Cookie("authToken"); err == nil { + token = cookie.Value + } + } + + if token == "" { + return nil + } + + session, exists := sessions[token] + if !exists || time.Now().After(session.ExpiresAt) { + return nil + } + + return &session +} diff --git a/admin.go b/admin/admin.go similarity index 95% rename from admin.go rename to admin/admin.go index c5ea7cc..18366eb 100644 --- a/admin.go +++ b/admin/admin.go @@ -1,733 +1,733 @@ -package main - -import ( - "encoding/json" - "html/template" - "net/http" - "strings" -) - -type GridCard struct { - ID string `json:"id"` - Title string `json:"title"` - Description string `json:"description"` - Icon string `json:"icon"` - Link string `json:"link"` - Color string `json:"color"` - Order int `json:"order"` - Enabled bool `json:"enabled"` -} - -// In-memory storage for grid cards (replace with database in production) -var gridCards = []GridCard{ - { - ID: "evidence-aut", - Title: "Evidence aut", - Description: "Záznam o jízdách služebním autem", - Icon: "🚗", - Link: "/evidence-aut", - Color: "#004990", - Order: 1, - Enabled: true, - }, - { - ID: "kontakt", - Title: "Kontakt", - Description: "Kontaktní formulář", - Icon: "📧", - Link: "/kontakt", - Color: "#0072b0", - Order: 2, - Enabled: true, - }, -} - -func handleAdmin(w http.ResponseWriter, r *http.Request) { - user := getCurrentUser(r) - if user == nil { - http.Redirect(w, r, "/login", http.StatusFound) - return - } - - tmpl := ` - - - - - Administrace - - - -
-

Administrace

- -
- -
-
-

Správa karet hlavní stránky

-
-
- - - -
- -
-
-
- - - - - - -` - - t, err := template.New("admin").Parse(tmpl) - if err != nil { - http.Error(w, "Template error", http.StatusInternalServerError) - return - } - - data := struct { - Username string - Role string - }{ - Username: user.Username, - Role: user.Role, - } - - w.Header().Set("Content-Type", "text/html") - t.Execute(w, data) -} - -func handleAdminCards(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - switch r.Method { - case "GET": - json.NewEncoder(w).Encode(gridCards) - - case "POST": - var card GridCard - if err := json.NewDecoder(r.Body).Decode(&card); err != nil { - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(map[string]string{"error": "Invalid JSON"}) - return - } - - // Check if updating existing card - found := false - for i, existingCard := range gridCards { - if existingCard.ID == card.ID { - gridCards[i] = card - found = true - break - } - } - - if !found { - gridCards = append(gridCards, card) - } - - json.NewEncoder(w).Encode(map[string]string{"message": "Card saved successfully"}) - - default: - w.WriteHeader(http.StatusMethodNotAllowed) - json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"}) - } -} - -func handleAdminCardToggle(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - if r.Method != "POST" { - w.WriteHeader(http.StatusMethodNotAllowed) - json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"}) - return - } - - // Extract card ID from URL path - path := r.URL.Path - parts := strings.Split(path, "/") - if len(parts) < 4 { - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(map[string]string{"error": "Invalid card ID"}) - return - } - - cardID := parts[3] // /admin/cards/{id}/toggle - - for i, card := range gridCards { - if card.ID == cardID { - gridCards[i].Enabled = !gridCards[i].Enabled - json.NewEncoder(w).Encode(map[string]string{"message": "Card toggled successfully"}) - return - } - } - - w.WriteHeader(http.StatusNotFound) - json.NewEncoder(w).Encode(map[string]string{"error": "Card not found"}) -} - -func handleAdminCardDelete(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - if r.Method != "DELETE" { - w.WriteHeader(http.StatusMethodNotAllowed) - json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"}) - return - } - - // Extract card ID from URL path - path := r.URL.Path - parts := strings.Split(path, "/") - if len(parts) < 4 { - w.WriteHeader(http.StatusBadRequest) - json.NewEncoder(w).Encode(map[string]string{"error": "Invalid card ID"}) - return - } - - cardID := parts[3] // /admin/cards/{id} - - for i, card := range gridCards { - if card.ID == cardID { - // Remove card from slice - gridCards = append(gridCards[:i], gridCards[i+1:]...) - json.NewEncoder(w).Encode(map[string]string{"message": "Card deleted successfully"}) - return - } - } - - w.WriteHeader(http.StatusNotFound) - json.NewEncoder(w).Encode(map[string]string{"error": "Card not found"}) -} - -func handleGetCards(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - - // Filter only enabled cards and sort by order - var enabledCards []GridCard - for _, card := range gridCards { - if card.Enabled { - enabledCards = append(enabledCards, card) - } - } - - // Sort by order - for i := 0; i < len(enabledCards)-1; i++ { - for j := i + 1; j < len(enabledCards); j++ { - if enabledCards[i].Order > enabledCards[j].Order { - enabledCards[i], enabledCards[j] = enabledCards[j], enabledCards[i] - } - } - } - - json.NewEncoder(w).Encode(enabledCards) -} +package admin + +import ( + "encoding/json" + "html/template" + "net/http" + "strings" +) + +type GridCard struct { + ID string `json:"id"` + Title string `json:"title"` + Description string `json:"description"` + Icon string `json:"icon"` + Link string `json:"link"` + Color string `json:"color"` + Order int `json:"order"` + Enabled bool `json:"enabled"` +} + +// In-memory storage for grid cards (replace with database in production) +var gridCards = []GridCard{ + { + ID: "evidence-aut", + Title: "Evidence aut", + Description: "Záznam o jízdách služebním autem", + Icon: "🚗", + Link: "/evidence-aut", + Color: "#004990", + Order: 1, + Enabled: true, + }, + { + ID: "kontakt", + Title: "Kontakt", + Description: "Kontaktní formulář", + Icon: "📧", + Link: "/kontakt", + Color: "#0072b0", + Order: 2, + Enabled: true, + }, +} + +func HandleAdmin(w http.ResponseWriter, r *http.Request) { + user := GetCurrentUser(r) + if user == nil { + http.Redirect(w, r, "/login", http.StatusFound) + return + } + + tmpl := ` + + + + + Administrace + + + +
+

Administrace

+ +
+ +
+
+

Správa karet hlavní stránky

+
+
+ + + +
+ +
+
+
+ + + + + + +` + + t, err := template.New("admin").Parse(tmpl) + if err != nil { + http.Error(w, "Template error", http.StatusInternalServerError) + return + } + + data := struct { + Username string + Role string + }{ + Username: user.Username, + Role: user.Role, + } + + w.Header().Set("Content-Type", "text/html") + t.Execute(w, data) +} + +func HandleAdminCards(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + switch r.Method { + case "GET": + json.NewEncoder(w).Encode(gridCards) + + case "POST": + var card GridCard + if err := json.NewDecoder(r.Body).Decode(&card); err != nil { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(map[string]string{"error": "Invalid JSON"}) + return + } + + // Check if updating existing card + found := false + for i, existingCard := range gridCards { + if existingCard.ID == card.ID { + gridCards[i] = card + found = true + break + } + } + + if !found { + gridCards = append(gridCards, card) + } + + json.NewEncoder(w).Encode(map[string]string{"message": "Card saved successfully"}) + + default: + w.WriteHeader(http.StatusMethodNotAllowed) + json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"}) + } +} + +func HandleAdminCardToggle(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"}) + return + } + + // Extract card ID from URL path + path := r.URL.Path + parts := strings.Split(path, "/") + if len(parts) < 4 { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(map[string]string{"error": "Invalid card ID"}) + return + } + + cardID := parts[3] // /admin/cards/{id}/toggle + + for i, card := range gridCards { + if card.ID == cardID { + gridCards[i].Enabled = !gridCards[i].Enabled + json.NewEncoder(w).Encode(map[string]string{"message": "Card toggled successfully"}) + return + } + } + + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]string{"error": "Card not found"}) +} + +func HandleAdminCardDelete(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + if r.Method != "DELETE" { + w.WriteHeader(http.StatusMethodNotAllowed) + json.NewEncoder(w).Encode(map[string]string{"error": "Method not allowed"}) + return + } + + // Extract card ID from URL path + path := r.URL.Path + parts := strings.Split(path, "/") + if len(parts) < 4 { + w.WriteHeader(http.StatusBadRequest) + json.NewEncoder(w).Encode(map[string]string{"error": "Invalid card ID"}) + return + } + + cardID := parts[3] // /admin/cards/{id} + + for i, card := range gridCards { + if card.ID == cardID { + // Remove card from slice + gridCards = append(gridCards[:i], gridCards[i+1:]...) + json.NewEncoder(w).Encode(map[string]string{"message": "Card deleted successfully"}) + return + } + } + + w.WriteHeader(http.StatusNotFound) + json.NewEncoder(w).Encode(map[string]string{"error": "Card not found"}) +} + +func HandleGetCards(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + + // Filter only enabled cards and sort by order + var enabledCards []GridCard + for _, card := range gridCards { + if card.Enabled { + enabledCards = append(enabledCards, card) + } + } + + // Sort by order + for i := 0; i < len(enabledCards)-1; i++ { + for j := i + 1; j < len(enabledCards); j++ { + if enabledCards[i].Order > enabledCards[j].Order { + enabledCards[i], enabledCards[j] = enabledCards[j], enabledCards[i] + } + } + } + + json.NewEncoder(w).Encode(enabledCards) +} diff --git a/go.sum b/go.sum index da222ec..45058a1 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= @@ -25,8 +23,6 @@ golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= diff --git a/main.go b/main.go index c246e2b..cb6924e 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ import ( "time" "gopkg.in/gomail.v2" + "ppve/admin" // Import the local admin package ) type TripEntry struct { @@ -81,26 +82,26 @@ func main() { http.Redirect(w, r, "http://webportal:8080/", http.StatusFound) })) // Authentication routes - http.HandleFunc("/login", enableCORS(handleLogin)) - http.HandleFunc("/logout", enableCORS(handleLogout)) + http.HandleFunc("/login", enableCORS(admin.HandleLogin)) + http.HandleFunc("/logout", enableCORS(admin.HandleLogout)) // Admin routes (protected) - http.HandleFunc("/admin", enableCORS(requireAdminAuth(handleAdmin))) - http.HandleFunc("/admin/cards", enableCORS(requireAdminAuth(handleAdminCards))) + http.HandleFunc("/admin", enableCORS(admin.RequireAdminAuth(admin.HandleAdmin))) + http.HandleFunc("/admin/cards", enableCORS(admin.RequireAdminAuth(admin.HandleAdminCards))) - http.HandleFunc("/admin/cards/", enableCORS(requireAdminAuth(func(w http.ResponseWriter, r *http.Request) { + http.HandleFunc("/admin/cards/", enableCORS(admin.RequireAdminAuth(func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path if strings.HasSuffix(path, "/toggle") { - handleAdminCardToggle(w, r) + admin.HandleAdminCardToggle(w, r) } else if r.Method == "DELETE" { - handleAdminCardDelete(w, r) + admin.HandleAdminCardDelete(w, r) } else { w.WriteHeader(http.StatusNotFound) } }))) // Public API to get cards for homepage - http.HandleFunc("/api/cards", enableCORS(handleGetCards)) + http.HandleFunc("/api/cards", enableCORS(admin.HandleGetCards)) port := os.Getenv("PORT") if port == "" {