295 lines
8.1 KiB
Go
295 lines
8.1 KiB
Go
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
|
|
}
|