package main import ( "encoding/json" "fmt" "io" "net/http" "os" "github.com/gorilla/mux" "golang.org/x/crypto/bcrypt" ) // GetUsersHandler handles GET requests to /admin/user func GetUsersHandler(w http.ResponseWriter, r *http.Request) { log := GetLogger() username := r.Context().Value(userKey).(string) log.Info("Admin request to get all users by user: %s", username) var users []User if err := db.Find(&users).Error; err != nil { log.Error("Failed to fetch users: %v", err) http.Error(w, "Failed to fetch users", http.StatusInternalServerError) return } log.Info("Successfully retrieved %d users", len(users)) json.NewEncoder(w).Encode(users) } // CreateUserHandler handles POST requests to /admin/user func CreateUserHandler(w http.ResponseWriter, r *http.Request) { log := GetLogger() adminUser := r.Context().Value(userKey).(string) var user User err := json.NewDecoder(r.Body).Decode(&user) if err != nil { log.Error("Failed to decode user creation request: %v", err) http.Error(w, "Invalid request body", http.StatusBadRequest) return } log.Info("Admin user %s attempting to create new user: %s", adminUser, user.Username) // Hash the password before storing hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12) if err != nil { log.Error("Failed to hash password for new user %s: %v", user.Username, err) http.Error(w, "Failed to hash password", http.StatusInternalServerError) return } user.Password = string(hashedPassword) if err := db.Create(&user).Error; err != nil { log.Error("Failed to create user %s: %v", user.Username, err) http.Error(w, "Failed to create user", http.StatusInternalServerError) return } log.DatabaseAction("create", fmt.Sprintf("User %s created by admin %s", user.Username, adminUser)) json.NewEncoder(w).Encode(user) } // GetUserHandler handles GET requests to /admin/user/{id} func GetUserHandler(w http.ResponseWriter, r *http.Request) { log := GetLogger() adminUser := r.Context().Value(userKey).(string) vars := mux.Vars(r) id := vars["id"] log.Info("Admin user %s requesting details for user ID: %s", adminUser, id) var user User if err := db.First(&user, id).Error; err != nil { log.Warn("User not found with ID %s, requested by admin %s", id, adminUser) http.Error(w, "User not found", http.StatusNotFound) return } log.Info("Successfully retrieved user %s details", user.Username) json.NewEncoder(w).Encode(user) } // DeleteUserHandler handles DELETE requests to /admin/user/{id} func DeleteUserHandler(w http.ResponseWriter, r *http.Request) { log := GetLogger() adminUser := r.Context().Value(userKey).(string) vars := mux.Vars(r) id := vars["id"] log.Info("Admin user %s attempting to delete user ID: %s", adminUser, id) var user User if err := db.First(&user, id).Error; err != nil { log.Warn("Attempt to delete non-existent user ID %s by admin %s", id, adminUser) http.Error(w, "User not found", http.StatusNotFound) return } if err := db.Delete(&user).Error; err != nil { log.Error("Failed to delete user %s: %v", user.Username, err) http.Error(w, "Failed to delete user", http.StatusInternalServerError) return } log.DatabaseAction("delete", fmt.Sprintf("User %s deleted by admin %s", user.Username, adminUser)) w.WriteHeader(http.StatusNoContent) } // BackupDatabaseHandler handles GET requests to /admin/db func BackupDatabaseHandler(w http.ResponseWriter, r *http.Request) { log := GetLogger() adminUser := r.Context().Value(userKey).(string) log.Info("Database backup requested by admin user: %s", adminUser) // Open the database file using the path from the config file, err := os.Open(*DatabasePath) if err != nil { log.Error("Failed to open database file for backup: %v", err) http.Error(w, "Failed to open database file", http.StatusInternalServerError) return } defer file.Close() // Copy the file to the response writer _, err = io.Copy(w, file) if err != nil { log.Error("Failed to send database backup: %v", err) http.Error(w, "Failed to send database file", http.StatusInternalServerError) return } log.DatabaseAction("backup", fmt.Sprintf("Database backup created by admin %s", adminUser)) log.Info("Database backup successfully completed") } // RestoreDatabaseHandler handles POST requests to /admin/db func RestoreDatabaseHandler(w http.ResponseWriter, r *http.Request) { log := GetLogger() adminUser := r.Context().Value(userKey).(string) log.Info("Database restore requested by admin user: %s", adminUser) // Create a backup of the existing database err := createDatabaseBackup() if err != nil { log.Error("Failed to create backup before restore: %v", err) http.Error(w, "Failed to create database backup", http.StatusInternalServerError) return } log.Info("Created backup of existing database") // Save the new database err = saveNewDatabase(r) if err != nil { log.Error("Failed to save new database during restore: %v", err) http.Error(w, "Failed to save new database", http.StatusInternalServerError) return } log.Info("Saved new database file") // Validate the new database is properly initialized err = validateNewDatabase() if err != nil { log.Error("New database validation failed: %v", err) http.Error(w, "New database is not properly initialized", http.StatusInternalServerError) return } log.Info("New database validation successful") // Switch to the new database app-wide err = switchToNewDatabase() if err != nil { log.Error("Failed to switch to new database: %v", err) http.Error(w, "Failed to switch to new database", http.StatusInternalServerError) return } log.DatabaseAction("restore", fmt.Sprintf("Database restored by admin %s", adminUser)) log.Info("Database restore completed successfully") w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(map[string]string{"message": "Database restored successfully"}) } func createDatabaseBackup() error { log := GetLogger() backupPath := *DatabasePath + ".bak" log.Info("Creating database backup at: %s", backupPath) src, err := os.Open(*DatabasePath) if err != nil { log.Error("Failed to open source database for backup: %v", err) return err } defer src.Close() dst, err := os.Create(backupPath) if err != nil { log.Error("Failed to create backup file: %v", err) return err } defer dst.Close() _, err = io.Copy(dst, src) if err != nil { log.Error("Failed to copy database to backup: %v", err) return err } log.Info("Database backup created successfully") return nil } func saveNewDatabase(r *http.Request) error { log := GetLogger() file, _, err := r.FormFile("database") if err != nil { log.Error("Failed to get database file from request: %v", err) return err } defer file.Close() dst, err := os.Create(*DatabasePath) if err != nil { log.Error("Failed to create new database file: %v", err) return err } defer dst.Close() _, err = io.Copy(dst, file) if err != nil { log.Error("Failed to save new database file: %v", err) return err } log.Info("New database file saved successfully") return nil } func validateNewDatabase() error { log := GetLogger() log.Info("Validating new database") db, err := ConnectDB(*DatabasePath) if err != nil { log.Error("Failed to connect to new database: %v", err) return err } defer db.Close() // Check if required tables exist tables := []string{"users", "boxes", "items"} for _, table := range tables { var count int err := db.Debug().Raw("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?;", table).Row().Scan(&count) if err != nil { log.Error("Error checking for table %s: %v", table, err) return err } if count == 0 { log.Error("Required table %s does not exist in new database", table) return fmt.Errorf("table %s does not exist", table) } log.Info("Validated table exists: %s", table) } log.Info("Database validation completed successfully") return nil } func switchToNewDatabase() error { log := GetLogger() log.Info("Switching to new database") newDB, err := ConnectDB(*DatabasePath) if err != nil { log.Error("Failed to connect to new database during switch: %v", err) return err } // Update the global db variable db = newDB log.Info("Successfully switched to new database") return nil }