refactored for ordered consistent logging
This commit is contained in:
parent
d0eb8f733c
commit
cb202de53f
147
admin.go
147
admin.go
|
@ -13,66 +13,113 @@ import (
|
||||||
|
|
||||||
// GetUsersHandler handles GET requests to /admin/user
|
// GetUsersHandler handles GET requests to /admin/user
|
||||||
func GetUsersHandler(w http.ResponseWriter, r *http.Request) {
|
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
|
var users []User
|
||||||
db.Find(&users)
|
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)
|
json.NewEncoder(w).Encode(users)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateUserHandler handles POST requests to /admin/user
|
// CreateUserHandler handles POST requests to /admin/user
|
||||||
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
|
func CreateUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
|
adminUser := r.Context().Value(userKey).(string)
|
||||||
|
|
||||||
var user User
|
var user User
|
||||||
err := json.NewDecoder(r.Body).Decode(&user)
|
err := json.NewDecoder(r.Body).Decode(&user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to decode user creation request: %v", err)
|
||||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("Admin user %s attempting to create new user: %s", adminUser, user.Username)
|
||||||
|
|
||||||
// Hash the password before storing
|
// Hash the password before storing
|
||||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
|
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 12)
|
||||||
if err != nil {
|
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)
|
http.Error(w, "Failed to hash password", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
user.Password = string(hashedPassword)
|
user.Password = string(hashedPassword)
|
||||||
|
|
||||||
db.Create(&user)
|
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)
|
json.NewEncoder(w).Encode(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetUserHandler handles GET requests to /admin/user/{id}
|
// GetUserHandler handles GET requests to /admin/user/{id}
|
||||||
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
|
func GetUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
|
adminUser := r.Context().Value(userKey).(string)
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
|
log.Info("Admin user %s requesting details for user ID: %s", adminUser, id)
|
||||||
|
|
||||||
var user User
|
var user User
|
||||||
db.First(&user, id)
|
if err := db.First(&user, id).Error; err != nil {
|
||||||
if user.ID == 0 {
|
log.Warn("User not found with ID %s, requested by admin %s", id, adminUser)
|
||||||
http.Error(w, "User not found", http.StatusNotFound)
|
http.Error(w, "User not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("Successfully retrieved user %s details", user.Username)
|
||||||
json.NewEncoder(w).Encode(user)
|
json.NewEncoder(w).Encode(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUserHandler handles DELETE requests to /admin/user/{id}
|
// DeleteUserHandler handles DELETE requests to /admin/user/{id}
|
||||||
func DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
|
func DeleteUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
|
adminUser := r.Context().Value(userKey).(string)
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
|
log.Info("Admin user %s attempting to delete user ID: %s", adminUser, id)
|
||||||
|
|
||||||
var user User
|
var user User
|
||||||
db.First(&user, id)
|
if err := db.First(&user, id).Error; err != nil {
|
||||||
if user.ID == 0 {
|
log.Warn("Attempt to delete non-existent user ID %s by admin %s", id, adminUser)
|
||||||
http.Error(w, "User not found", http.StatusNotFound)
|
http.Error(w, "User not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
db.Delete(&user)
|
|
||||||
|
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)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BackupDatabaseHandler handles GET requests to /admin/db
|
// BackupDatabaseHandler handles GET requests to /admin/db
|
||||||
func BackupDatabaseHandler(w http.ResponseWriter, r *http.Request) {
|
func BackupDatabaseHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// ...
|
log := GetLogger()
|
||||||
fmt.Println("BackupDatabaseHandler called")
|
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
|
// Open the database file using the path from the config
|
||||||
file, err := os.Open(*DatabasePath)
|
file, err := os.Open(*DatabasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to open database file for backup: %v", err)
|
||||||
http.Error(w, "Failed to open database file", http.StatusInternalServerError)
|
http.Error(w, "Failed to open database file", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -81,85 +128,129 @@ func BackupDatabaseHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// Copy the file to the response writer
|
// Copy the file to the response writer
|
||||||
_, err = io.Copy(w, file)
|
_, err = io.Copy(w, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to send database backup: %v", err)
|
||||||
http.Error(w, "Failed to send database file", http.StatusInternalServerError)
|
http.Error(w, "Failed to send database file", http.StatusInternalServerError)
|
||||||
return
|
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
|
// RestoreDatabaseHandler handles POST requests to /admin/db
|
||||||
func RestoreDatabaseHandler(w http.ResponseWriter, r *http.Request) {
|
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
|
// Create a backup of the existing database
|
||||||
err := createDatabaseBackup()
|
err := createDatabaseBackup()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to create backup before restore: %v", err)
|
||||||
http.Error(w, "Failed to create database backup", http.StatusInternalServerError)
|
http.Error(w, "Failed to create database backup", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Info("Created backup of existing database")
|
||||||
|
|
||||||
// Save the new database
|
// Save the new database
|
||||||
err = saveNewDatabase(r)
|
err = saveNewDatabase(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to save new database during restore: %v", err)
|
||||||
http.Error(w, "Failed to save new database", http.StatusInternalServerError)
|
http.Error(w, "Failed to save new database", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Info("Saved new database file")
|
||||||
|
|
||||||
// Validate the new database is properly initialized
|
// Validate the new database is properly initialized
|
||||||
err = validateNewDatabase()
|
err = validateNewDatabase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("New database validation failed: %v", err)
|
||||||
http.Error(w, "New database is not properly initialized", http.StatusInternalServerError)
|
http.Error(w, "New database is not properly initialized", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
log.Info("New database validation successful")
|
||||||
|
|
||||||
// Switch to the new database app-wide
|
// Switch to the new database app-wide
|
||||||
err = switchToNewDatabase()
|
err = switchToNewDatabase()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to switch to new database: %v", err)
|
||||||
http.Error(w, "Failed to switch to new database", http.StatusInternalServerError)
|
http.Error(w, "Failed to switch to new database", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("Database restored successfully")
|
|
||||||
|
log.DatabaseAction("restore", fmt.Sprintf("Database restored by admin %s", adminUser))
|
||||||
|
log.Info("Database restore completed successfully")
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "Database restored successfully"})
|
json.NewEncoder(w).Encode(map[string]string{"message": "Database restored successfully"})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDatabaseBackup() error {
|
func createDatabaseBackup() error {
|
||||||
// Create a backup of the existing database
|
log := GetLogger()
|
||||||
|
backupPath := *DatabasePath + ".bak"
|
||||||
|
|
||||||
|
log.Info("Creating database backup at: %s", backupPath)
|
||||||
|
|
||||||
src, err := os.Open(*DatabasePath)
|
src, err := os.Open(*DatabasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to open source database for backup: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer src.Close()
|
defer src.Close()
|
||||||
|
|
||||||
dst, err := os.Create(*DatabasePath + ".bak")
|
dst, err := os.Create(backupPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to create backup file: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer dst.Close()
|
defer dst.Close()
|
||||||
|
|
||||||
_, err = io.Copy(dst, src)
|
_, err = io.Copy(dst, src)
|
||||||
return err
|
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 {
|
func saveNewDatabase(r *http.Request) error {
|
||||||
// Save the new database
|
log := GetLogger()
|
||||||
|
|
||||||
file, _, err := r.FormFile("database")
|
file, _, err := r.FormFile("database")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to get database file from request: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
dst, err := os.Create(*DatabasePath)
|
dst, err := os.Create(*DatabasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to create new database file: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer dst.Close()
|
defer dst.Close()
|
||||||
|
|
||||||
_, err = io.Copy(dst, file)
|
_, err = io.Copy(dst, file)
|
||||||
return err
|
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 {
|
func validateNewDatabase() error {
|
||||||
// Validate the new database is properly initialized
|
log := GetLogger()
|
||||||
|
|
||||||
|
log.Info("Validating new database")
|
||||||
|
|
||||||
db, err := ConnectDB(*DatabasePath)
|
db, err := ConnectDB(*DatabasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to connect to new database: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
@ -168,22 +259,36 @@ func validateNewDatabase() error {
|
||||||
tables := []string{"users", "boxes", "items"}
|
tables := []string{"users", "boxes", "items"}
|
||||||
for _, table := range tables {
|
for _, table := range tables {
|
||||||
var count int
|
var count int
|
||||||
db.Debug().Raw("SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?;", table).Row().Scan(&count)
|
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 {
|
if count == 0 {
|
||||||
|
log.Error("Required table %s does not exist in new database", table)
|
||||||
return fmt.Errorf("table %s does not exist", 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func switchToNewDatabase() error {
|
func switchToNewDatabase() error {
|
||||||
// Switch to the new database app-wide
|
log := GetLogger()
|
||||||
db, err := ConnectDB(*DatabasePath)
|
|
||||||
|
log.Info("Switching to new database")
|
||||||
|
|
||||||
|
newDB, err := ConnectDB(*DatabasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to connect to new database during switch: %v", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// Update the db variable with the new database connection
|
|
||||||
db = db
|
// Update the global db variable
|
||||||
|
db = newDB
|
||||||
|
|
||||||
|
log.Info("Successfully switched to new database")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
75
auth.go
75
auth.go
|
@ -30,42 +30,46 @@ type LoginResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
fmt.Printf("handlers.go init: config = %+v", config)
|
log := GetLogger()
|
||||||
|
if log != nil {
|
||||||
|
log.Info("Initializing authentication module")
|
||||||
|
log.Debug("Current config: %+v", config)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// loginHandler handles the /login endpoint.
|
|
||||||
// LoginHandler handles the /login endpoint.
|
// LoginHandler handles the /login endpoint.
|
||||||
func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
var req LoginRequest
|
var req LoginRequest
|
||||||
//fmt.Println("db is ", db)
|
|
||||||
//fmt.Println("config is ", config)
|
|
||||||
if db == nil {
|
if db == nil {
|
||||||
fmt.Println("DB is nil")
|
if log != nil {
|
||||||
|
log.Error("Database connection not initialized in LoginHandler")
|
||||||
|
}
|
||||||
http.Error(w, "Database not initialized", http.StatusInternalServerError)
|
http.Error(w, "Database not initialized", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("DB is not nil")
|
|
||||||
|
|
||||||
//if config == nil {
|
if log != nil {
|
||||||
//fmt.Println("Config is nil in LoginHandler")
|
log.Info("Processing login request")
|
||||||
//h//ttp.Error(w, "Configuration not loaded", http.StatusInternalServerError)
|
}
|
||||||
//return
|
|
||||||
//}
|
|
||||||
//fmt.Println("Config is not nil")
|
|
||||||
|
|
||||||
fmt.Printf("DB: %+v\n", db)
|
|
||||||
//fmt.Printf("Config: %+v\n", config)
|
|
||||||
fmt.Println("LoginHandler called")
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&req)
|
err := json.NewDecoder(r.Body).Decode(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if log != nil {
|
||||||
|
log.Error("Failed to decode login request: %v", err)
|
||||||
|
}
|
||||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user exists and the password matches
|
// Check if the user exists and the password matches
|
||||||
var user User
|
var user User
|
||||||
db.Where("username = ?", req.Username).First(&user)
|
result := db.Where("username = ?", req.Username).First(&user)
|
||||||
if user.ID == 0 {
|
if result.Error != nil || user.ID == 0 {
|
||||||
|
if log != nil {
|
||||||
|
log.Warn("Login attempt failed for username: %s - user not found", req.Username)
|
||||||
|
}
|
||||||
http.Error(w, "Invalid username or password", http.StatusUnauthorized)
|
http.Error(w, "Invalid username or password", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -73,6 +77,9 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// Compare the provided password with the stored hashed password
|
// Compare the provided password with the stored hashed password
|
||||||
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password))
|
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(req.Password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if log != nil {
|
||||||
|
log.Warn("Login attempt failed for username: %s - invalid password", req.Username)
|
||||||
|
}
|
||||||
http.Error(w, "Invalid username or password", http.StatusUnauthorized)
|
http.Error(w, "Invalid username or password", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -85,19 +92,29 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
tokenString, err := token.SignedString(*JWTSecret)
|
tokenString, err := token.SignedString(*JWTSecret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if log != nil {
|
||||||
|
log.Error("Failed to generate JWT token for user %s: %v", user.Username, err)
|
||||||
|
}
|
||||||
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
|
http.Error(w, "Failed to generate token", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if log != nil {
|
||||||
|
log.Info("Successful login for user: %s", user.Username)
|
||||||
|
log.UserAction(user.Username, "login")
|
||||||
|
}
|
||||||
|
|
||||||
// Return the token in the response
|
// Return the token in the response
|
||||||
json.NewEncoder(w).Encode(LoginResponse{Token: tokenString})
|
json.NewEncoder(w).Encode(LoginResponse{Token: tokenString})
|
||||||
}
|
}
|
||||||
|
|
||||||
// authMiddleware is a middleware function that checks for a valid JWT token in the request header and enables CORS.
|
// AuthMiddleware is a middleware function that checks for a valid JWT token in the request header and enables CORS.
|
||||||
func AuthMiddleware(next http.Handler) http.Handler {
|
func AuthMiddleware(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
|
|
||||||
// Set CORS headers
|
// Set CORS headers
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*") // Replace "*" with your allowed frontend origin if needed
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||||
|
|
||||||
|
@ -110,6 +127,9 @@ func AuthMiddleware(next http.Handler) http.Handler {
|
||||||
// Get the token from the request header
|
// Get the token from the request header
|
||||||
tokenString := r.Header.Get("Authorization")
|
tokenString := r.Header.Get("Authorization")
|
||||||
if tokenString == "" {
|
if tokenString == "" {
|
||||||
|
if log != nil {
|
||||||
|
log.Warn("Request rejected: missing Authorization header")
|
||||||
|
}
|
||||||
http.Error(w, "Authorization header missing", http.StatusUnauthorized)
|
http.Error(w, "Authorization header missing", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -121,21 +141,36 @@ func AuthMiddleware(next http.Handler) http.Handler {
|
||||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||||
// Make sure that the signing method is HMAC
|
// Make sure that the signing method is HMAC
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
|
if log != nil {
|
||||||
|
log.Warn("Invalid signing method in token: %v", token.Header["alg"])
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||||
}
|
}
|
||||||
return *JWTSecret, nil
|
return *JWTSecret, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil || !token.Valid {
|
if err != nil || !token.Valid {
|
||||||
|
if log != nil {
|
||||||
|
log.Warn("Invalid token: %v", err)
|
||||||
|
}
|
||||||
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
http.Error(w, "Invalid token", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract the user claims from the token
|
// Extract the user claims from the token
|
||||||
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
if claims, ok := token.Claims.(jwt.MapClaims); ok {
|
||||||
|
username := claims["username"].(string)
|
||||||
// Add the "user" claim to the request context
|
// Add the "user" claim to the request context
|
||||||
newCtx := context.WithValue(r.Context(), userKey, claims["username"])
|
newCtx := context.WithValue(r.Context(), userKey, username)
|
||||||
r = r.WithContext(newCtx)
|
r = r.WithContext(newCtx)
|
||||||
|
|
||||||
|
if log != nil {
|
||||||
|
log.Debug("Authenticated request for user: %s", username)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if log != nil {
|
||||||
|
log.Warn("Invalid token claims structure")
|
||||||
|
}
|
||||||
http.Error(w, "Invalid token claims", http.StatusUnauthorized)
|
http.Error(w, "Invalid token claims", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,18 +10,22 @@ import (
|
||||||
|
|
||||||
// getBoxesHandler handles the GET /boxes endpoint.
|
// getBoxesHandler handles the GET /boxes endpoint.
|
||||||
func GetBoxesHandler(w http.ResponseWriter, r *http.Request) {
|
func GetBoxesHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Printf("Received %s request to %s\n", r.Method, r.URL)
|
log := GetLogger()
|
||||||
|
log.Info("Received %s request to %s", r.Method, r.URL)
|
||||||
|
|
||||||
var boxes []Box
|
var boxes []Box
|
||||||
db.Find(&boxes)
|
db.Find(&boxes)
|
||||||
json.NewEncoder(w).Encode(boxes)
|
json.NewEncoder(w).Encode(boxes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBoxHandler(w http.ResponseWriter, r *http.Request) {
|
func GetBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
var box Box
|
var box Box
|
||||||
if err := db.First(&box, id).Error; err != nil {
|
if err := db.First(&box, id).Error; err != nil {
|
||||||
|
log.Warn("Box not found with ID: %s", id)
|
||||||
http.Error(w, "Box not found", http.StatusNotFound)
|
http.Error(w, "Box not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -31,9 +35,11 @@ func GetBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// createBoxHandler handles the POST /boxes endpoint.
|
// createBoxHandler handles the POST /boxes endpoint.
|
||||||
func CreateBoxHandler(w http.ResponseWriter, r *http.Request) {
|
func CreateBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
var box Box
|
var box Box
|
||||||
err := json.NewDecoder(r.Body).Decode(&box)
|
err := json.NewDecoder(r.Body).Decode(&box)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to decode box creation request: %v", err)
|
||||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -51,26 +57,26 @@ func CreateBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
Name: box.Name,
|
Name: box.Name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.DatabaseAction("create", fmt.Sprintf("Created box with ID %d", box.ID))
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteBoxHandler handles the DELETE /boxes/{id} endpoint.
|
// deleteBoxHandler handles the DELETE /boxes/{id} endpoint.
|
||||||
func DeleteBoxHandler(w http.ResponseWriter, r *http.Request) {
|
func DeleteBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
// Retrieve the box from the database
|
// Retrieve the box from the database
|
||||||
var box Box
|
var box Box
|
||||||
if err := db.First(&box, id).Error; err != nil {
|
if err := db.First(&box, id).Error; err != nil {
|
||||||
|
log.Warn("Attempt to delete non-existent box with ID: %s", id)
|
||||||
http.Error(w, "Box not found", http.StatusNotFound)
|
http.Error(w, "Box not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optionally, delete associated items (if you want cascading delete)
|
|
||||||
// db.Where("box_id = ?", id).Delete(&Item{})
|
|
||||||
|
|
||||||
// Delete the box
|
// Delete the box
|
||||||
db.Delete(&box)
|
db.Delete(&box)
|
||||||
|
log.DatabaseAction("delete", fmt.Sprintf("Deleted box with ID %d", box.ID))
|
||||||
w.WriteHeader(http.StatusNoContent) // 204 No Content
|
w.WriteHeader(http.StatusNoContent) // 204 No Content
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ type Config struct {
|
||||||
ImageStorageDir string `yaml:"image_storage_dir"`
|
ImageStorageDir string `yaml:"image_storage_dir"`
|
||||||
ListeningPort int `yaml:"listening_port"`
|
ListeningPort int `yaml:"listening_port"`
|
||||||
LogFile string `yaml:"log_file"`
|
LogFile string `yaml:"log_file"`
|
||||||
|
LogLevel string `yaml:"log_level"`
|
||||||
StaticFilesDir string `yaml:"static_files_dir"`
|
StaticFilesDir string `yaml:"static_files_dir"`
|
||||||
AllowedOrigins string `yaml:"allowed_origins"`
|
AllowedOrigins string `yaml:"allowed_origins"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,5 +4,6 @@ jwt_secret: "super_secret_key"
|
||||||
image_storage_dir: "/app/images"
|
image_storage_dir: "/app/images"
|
||||||
listening_port: 8080
|
listening_port: 8080
|
||||||
log_file: "/app/data/boxes.log"
|
log_file: "/app/data/boxes.log"
|
||||||
|
log_level: "INFO"
|
||||||
static_files_dir: "/app/build/"
|
static_files_dir: "/app/build/"
|
||||||
allowed_origins: "*"
|
allowed_origins: "*"
|
||||||
|
|
73
db.go
73
db.go
|
@ -30,17 +30,82 @@ type User struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ConnectDB establishes a connection to the database and sets up the schema
|
||||||
func ConnectDB(dbPath string) (*gorm.DB, error) {
|
func ConnectDB(dbPath string) (*gorm.DB, error) {
|
||||||
|
log := GetLogger()
|
||||||
|
if log != nil {
|
||||||
|
log.Info("Attempting to connect to database at: %s", dbPath)
|
||||||
|
}
|
||||||
|
|
||||||
db, err := gorm.Open("sqlite3", dbPath)
|
db, err := gorm.Open("sqlite3", dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if log != nil {
|
||||||
|
log.Error("Failed to connect to database: %v", err)
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("failed to connect to database: %v", err)
|
return nil, fmt.Errorf("failed to connect to database: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set auto_vacuum mode to ON
|
// Enable detailed SQL logging if we're in DEBUG mode
|
||||||
// this automagically removes old rows from the database when idle
|
if log != nil && log.GetLogLevel() == "DEBUG" {
|
||||||
db.Exec("PRAGMA auto_vacuum = ON;")
|
db.LogMode(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if log != nil {
|
||||||
|
log.Info("Successfully connected to database")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set auto_vacuum mode to ON
|
||||||
|
if err := db.Exec("PRAGMA auto_vacuum = ON;").Error; err != nil {
|
||||||
|
if log != nil {
|
||||||
|
log.Error("Failed to set auto_vacuum pragma: %v", err)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("failed to set auto_vacuum pragma: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if log != nil {
|
||||||
|
log.Info("Auto-vacuum mode enabled")
|
||||||
|
}
|
||||||
|
|
||||||
// AutoMigrate will create the tables if they don't exist
|
// AutoMigrate will create the tables if they don't exist
|
||||||
db.AutoMigrate(&Box{}, &Item{}, &User{})
|
if err := autoMigrateSchema(db); err != nil {
|
||||||
|
if log != nil {
|
||||||
|
log.Error("Schema migration failed: %v", err)
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if log != nil {
|
||||||
|
log.Info("Database schema migration completed successfully")
|
||||||
|
}
|
||||||
|
|
||||||
return db, nil
|
return db, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// autoMigrateSchema handles the database schema migration
|
||||||
|
func autoMigrateSchema(db *gorm.DB) error {
|
||||||
|
log := GetLogger()
|
||||||
|
if log != nil {
|
||||||
|
log.Info("Starting schema migration")
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of models to migrate
|
||||||
|
models := []interface{}{
|
||||||
|
&Box{},
|
||||||
|
&Item{},
|
||||||
|
&User{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, model := range models {
|
||||||
|
if err := db.AutoMigrate(model).Error; err != nil {
|
||||||
|
if log != nil {
|
||||||
|
log.Error("Failed to migrate model %T: %v", model, err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("failed to migrate model %T: %v", model, err)
|
||||||
|
}
|
||||||
|
if log != nil {
|
||||||
|
log.Debug("Successfully migrated model: %T", model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -11,38 +11,29 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Move all item-related handlers here:
|
|
||||||
// - GetItemsHandler
|
|
||||||
// - CreateItemHandler
|
|
||||||
// - GetItemHandler
|
|
||||||
// - UpdateItemHandler
|
|
||||||
// - DeleteItemHandler
|
|
||||||
// - UploadItemImageHandler
|
|
||||||
// - GetItemImageHandler
|
|
||||||
// - SearchItemsHandler
|
|
||||||
// - GetItemsInBoxHandler
|
|
||||||
|
|
||||||
// getItemsHandler handles the GET /items endpoint.
|
|
||||||
func GetItemsHandler(w http.ResponseWriter, r *http.Request) {
|
func GetItemsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
|
log.Info("Received %s request to %s", r.Method, r.URL)
|
||||||
|
|
||||||
var items []Item
|
var items []Item
|
||||||
db.Find(&items)
|
db.Find(&items)
|
||||||
json.NewEncoder(w).Encode(items)
|
json.NewEncoder(w).Encode(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createItemHandler handles the POST /items endpoint.
|
|
||||||
func CreateItemHandler(w http.ResponseWriter, r *http.Request) {
|
func CreateItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
var item Item
|
var item Item
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&item)
|
err := json.NewDecoder(r.Body).Decode(&item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to decode item creation request: %v", err)
|
||||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println(item)
|
log.Info("Creating new item: %s", item.Name)
|
||||||
|
|
||||||
db.Create(&item)
|
db.Create(&item)
|
||||||
|
|
||||||
// Create a response struct to include the ID
|
|
||||||
type createItemResponse struct {
|
type createItemResponse struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
@ -53,47 +44,46 @@ func CreateItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
Name: item.Name,
|
Name: item.Name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.DatabaseAction("create", fmt.Sprintf("Created item with ID %d", item.ID))
|
||||||
json.NewEncoder(w).Encode(response)
|
json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UploadItemImageHandler handles the image upload for an item
|
|
||||||
func UploadItemImageHandler(w http.ResponseWriter, r *http.Request) {
|
func UploadItemImageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// Extract the authenticated user from context (assuming this is how AuthMiddleware works)
|
log := GetLogger()
|
||||||
user, ok := r.Context().Value(userKey).(string)
|
user, ok := r.Context().Value(userKey).(string)
|
||||||
if !ok || user == "" {
|
if !ok || user == "" {
|
||||||
|
log.Warn("Unauthorized image upload attempt")
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the form data, 10MB limit for file uploads
|
|
||||||
err := r.ParseMultipartForm(10 << 20)
|
err := r.ParseMultipartForm(10 << 20)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to parse multipart form: %v", err)
|
||||||
http.Error(w, "Unable to parse form", http.StatusBadRequest)
|
http.Error(w, "Unable to parse form", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the file from the form data
|
|
||||||
file, handler, err := r.FormFile("image")
|
file, handler, err := r.FormFile("image")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Error retrieving file from form: %v", err)
|
||||||
http.Error(w, "Error retrieving the file", http.StatusBadRequest)
|
http.Error(w, "Error retrieving the file", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// Get item ID from the URL
|
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
itemID := vars["id"]
|
itemID := vars["id"]
|
||||||
|
|
||||||
// Validate that the item exists (fetch from DB using itemID)
|
|
||||||
var item Item
|
var item Item
|
||||||
if err := db.First(&item, itemID).Error; err != nil {
|
if err := db.First(&item, itemID).Error; err != nil {
|
||||||
|
log.Warn("Attempt to upload image for non-existent item with ID: %s", itemID)
|
||||||
http.Error(w, "Item not found", http.StatusNotFound)
|
http.Error(w, "Item not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the uploaded file locally or to a storage service
|
|
||||||
// Ensure the directory exists
|
|
||||||
if err := os.MkdirAll(*ImageStorage, 0755); err != nil {
|
if err := os.MkdirAll(*ImageStorage, 0755); err != nil {
|
||||||
|
log.Error("Failed to create image storage directory: %v", err)
|
||||||
http.Error(w, "Unable to create image storage directory", http.StatusInternalServerError)
|
http.Error(w, "Unable to create image storage directory", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -101,88 +91,87 @@ func UploadItemImageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
filePath := fmt.Sprintf("%s/%s", *ImageStorage, handler.Filename)
|
filePath := fmt.Sprintf("%s/%s", *ImageStorage, handler.Filename)
|
||||||
outFile, err := os.Create(filePath)
|
outFile, err := os.Create(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to create file: %v", err)
|
||||||
http.Error(w, "Unable to save the file", http.StatusInternalServerError)
|
http.Error(w, "Unable to save the file", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer outFile.Close()
|
defer outFile.Close()
|
||||||
|
|
||||||
// Copy the uploaded file to the destination
|
|
||||||
_, err = io.Copy(outFile, file)
|
_, err = io.Copy(outFile, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to save file: %v", err)
|
||||||
http.Error(w, "Unable to save the file", http.StatusInternalServerError)
|
http.Error(w, "Unable to save the file", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the item record in the database with the image path
|
|
||||||
item.ImagePath = filePath
|
item.ImagePath = filePath
|
||||||
if err := db.Save(&item).Error; err != nil {
|
if err := db.Save(&item).Error; err != nil {
|
||||||
|
log.Error("Failed to update item with image path: %v", err)
|
||||||
http.Error(w, "Unable to save image path in database", http.StatusInternalServerError)
|
http.Error(w, "Unable to save image path in database", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println("Image upload called")
|
|
||||||
// Return the image path in the response
|
log.Info("Image uploaded successfully for item %s by user %s", itemID, user)
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"imagePath": filePath})
|
json.NewEncoder(w).Encode(map[string]string{"imagePath": filePath})
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetItemImageHandler retrieves an item's image by item ID.
|
|
||||||
func GetItemImageHandler(w http.ResponseWriter, r *http.Request) {
|
func GetItemImageHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
itemID := vars["id"]
|
itemID := vars["id"]
|
||||||
// fmt.Println("Getting image")
|
|
||||||
// Retrieve the item from the database
|
|
||||||
var item Item
|
var item Item
|
||||||
if err := db.First(&item, itemID).Error; err != nil {
|
if err := db.First(&item, itemID).Error; err != nil {
|
||||||
|
log.Info("Item not found, using default image for ID: %s", itemID)
|
||||||
item.ImagePath = "images/default.jpg"
|
item.ImagePath = "images/default.jpg"
|
||||||
} else if item.ImagePath == "" {
|
} else if item.ImagePath == "" {
|
||||||
item.ImagePath = "images/default.jpg"
|
item.ImagePath = "images/default.jpg"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the image file
|
|
||||||
imageFile, err := os.Open(item.ImagePath)
|
imageFile, err := os.Open(item.ImagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log the error for debugging, but don't return an HTTP error
|
log.Error("Error opening image file: %v", err)
|
||||||
fmt.Println("Error opening image.", err)
|
|
||||||
item.ImagePath = "images/default.jpg"
|
item.ImagePath = "images/default.jpg"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer imageFile.Close()
|
defer imageFile.Close()
|
||||||
|
|
||||||
// Determine the content type of the image
|
|
||||||
imageData, err := io.ReadAll(imageFile)
|
imageData, err := io.ReadAll(imageFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Error reading image")
|
log.Error("Error reading image file: %v", err)
|
||||||
item.ImagePath = "images/default.jpg"
|
item.ImagePath = "images/default.jpg"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
contentType := http.DetectContentType(imageData)
|
contentType := http.DetectContentType(imageData)
|
||||||
|
|
||||||
// Set the content type header and write the image data to the response
|
|
||||||
w.Header().Set("Content-Type", contentType)
|
w.Header().Set("Content-Type", contentType)
|
||||||
w.Write(imageData)
|
w.Write(imageData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchItemsHandler handles the GET /items/search endpoint.
|
|
||||||
func SearchItemsHandler(w http.ResponseWriter, r *http.Request) {
|
func SearchItemsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
query := r.URL.Query().Get("q")
|
query := r.URL.Query().Get("q")
|
||||||
if query == "" {
|
if query == "" {
|
||||||
|
log.Warn("Search attempt with empty query")
|
||||||
http.Error(w, "Search query is required", http.StatusBadRequest)
|
http.Error(w, "Search query is required", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println(query)
|
|
||||||
|
log.Info("Searching for items with query: %s", query)
|
||||||
var items []Item
|
var items []Item
|
||||||
db.Where("name GLOB ? OR description GLOB ?", "*"+query+"*", "*"+query+"*").Find(&items)
|
db.Where("name GLOB ? OR description GLOB ?", "*"+query+"*", "*"+query+"*").Find(&items)
|
||||||
json.NewEncoder(w).Encode(items)
|
json.NewEncoder(w).Encode(items)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getItemHandler handles the GET /items/{id} endpoint.
|
|
||||||
func GetItemHandler(w http.ResponseWriter, r *http.Request) {
|
func GetItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
var item Item
|
var item Item
|
||||||
if err := db.First(&item, id).Error; err != nil {
|
if err := db.First(&item, id).Error; err != nil {
|
||||||
|
log.Warn("Item not found with ID: %s", id)
|
||||||
http.Error(w, "Item not found", http.StatusNotFound)
|
http.Error(w, "Item not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -190,52 +179,59 @@ func GetItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
json.NewEncoder(w).Encode(item)
|
json.NewEncoder(w).Encode(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getItemsInBoxHandler handles the GET /boxes/{id}/items endpoint.
|
|
||||||
func GetItemsInBoxHandler(w http.ResponseWriter, r *http.Request) {
|
func GetItemsInBoxHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
var items []Item
|
var items []Item
|
||||||
if err := db.Where("box_id = ?", id).Find(&items).Error; err != nil {
|
if err := db.Where("box_id = ?", id).Find(&items).Error; err != nil {
|
||||||
|
log.Warn("Failed to fetch items for box ID: %s", id)
|
||||||
http.Error(w, "Items not found", http.StatusNotFound)
|
http.Error(w, "Items not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("Retrieved %d items from box %s", len(items), id)
|
||||||
json.NewEncoder(w).Encode(items)
|
json.NewEncoder(w).Encode(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateItemHandler handles the PUT /items/{id} endpoint.
|
|
||||||
func UpdateItemHandler(w http.ResponseWriter, r *http.Request) {
|
func UpdateItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
var item Item
|
var item Item
|
||||||
if err := db.First(&item, id).Error; err != nil {
|
if err := db.First(&item, id).Error; err != nil {
|
||||||
|
log.Warn("Attempt to update non-existent item with ID: %s", id)
|
||||||
http.Error(w, "Item not found", http.StatusNotFound)
|
http.Error(w, "Item not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&item)
|
err := json.NewDecoder(r.Body).Decode(&item)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Error("Failed to decode item update request: %v", err)
|
||||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
fmt.Println(item)
|
|
||||||
|
log.DatabaseAction("update", fmt.Sprintf("Updated item with ID %s", id))
|
||||||
db.Save(&item)
|
db.Save(&item)
|
||||||
json.NewEncoder(w).Encode(item)
|
json.NewEncoder(w).Encode(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
// deleteItemHandler handles the DELETE /items/{id} endpoint.
|
|
||||||
func DeleteItemHandler(w http.ResponseWriter, r *http.Request) {
|
func DeleteItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
log := GetLogger()
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id := vars["id"]
|
id := vars["id"]
|
||||||
|
|
||||||
var item Item
|
var item Item
|
||||||
if err := db.First(&item, id).Error; err != nil {
|
if err := db.First(&item, id).Error; err != nil {
|
||||||
|
log.Warn("Attempt to delete non-existent item with ID: %s", id)
|
||||||
http.Error(w, "Item not found", http.StatusNotFound)
|
http.Error(w, "Item not found", http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.DatabaseAction("delete", fmt.Sprintf("Deleted item with ID %s", id))
|
||||||
db.Delete(&item)
|
db.Delete(&item)
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,178 @@
|
||||||
|
// logger.go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
*log.Logger
|
||||||
|
file *os.File
|
||||||
|
level LogLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
type LogLevel int
|
||||||
|
|
||||||
|
const (
|
||||||
|
DEBUG LogLevel = iota
|
||||||
|
INFO
|
||||||
|
WARN
|
||||||
|
ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
instance *Logger
|
||||||
|
// Map for converting string to LogLevel
|
||||||
|
logLevelMap = map[string]LogLevel{
|
||||||
|
"DEBUG": DEBUG,
|
||||||
|
"INFO": INFO,
|
||||||
|
"WARN": WARN,
|
||||||
|
"ERROR": ERROR,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initialize creates a new logger instance with specified level
|
||||||
|
func Initialize(logPath string) (*Logger, error) {
|
||||||
|
if instance != nil {
|
||||||
|
return instance, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to open log file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to INFO if no level specified
|
||||||
|
logger := &Logger{
|
||||||
|
Logger: log.New(file, "", 0),
|
||||||
|
file: file,
|
||||||
|
level: INFO,
|
||||||
|
}
|
||||||
|
instance = logger
|
||||||
|
return logger, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogLevel sets the logging level from a string
|
||||||
|
func (l *Logger) SetLogLevel(levelStr string) error {
|
||||||
|
level, ok := logLevelMap[levelStr]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("invalid log level: %s. Must be one of: DEBUG, INFO, WARN, ERROR", levelStr)
|
||||||
|
}
|
||||||
|
l.level = level
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogLevel returns the current logging level as a string
|
||||||
|
func (l *Logger) GetLogLevel() string {
|
||||||
|
for str, level := range logLevelMap {
|
||||||
|
if level == l.level {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "UNKNOWN"
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLogger returns the singleton logger instance
|
||||||
|
func GetLogger() *Logger {
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the log file
|
||||||
|
func (l *Logger) Close() error {
|
||||||
|
if l.file != nil {
|
||||||
|
return l.file.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) log(level LogLevel, format string, v ...interface{}) {
|
||||||
|
if l == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this message should be logged based on current level
|
||||||
|
if level < l.level {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get caller information
|
||||||
|
_, file, line, _ := runtime.Caller(2)
|
||||||
|
|
||||||
|
// Create timestamp
|
||||||
|
timestamp := time.Now().Format("2006-01-02 15:04:05")
|
||||||
|
|
||||||
|
// Create level string
|
||||||
|
levelStr := "INFO"
|
||||||
|
switch level {
|
||||||
|
case DEBUG:
|
||||||
|
levelStr = "DEBUG"
|
||||||
|
case WARN:
|
||||||
|
levelStr = "WARN"
|
||||||
|
case ERROR:
|
||||||
|
levelStr = "ERROR"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format message
|
||||||
|
message := fmt.Sprintf(format, v...)
|
||||||
|
|
||||||
|
// Final log format
|
||||||
|
logLine := fmt.Sprintf("[%s] [%s] [%s:%d] %s\n",
|
||||||
|
timestamp,
|
||||||
|
levelStr,
|
||||||
|
file,
|
||||||
|
line,
|
||||||
|
message,
|
||||||
|
)
|
||||||
|
|
||||||
|
l.Logger.Print(logLine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug logs a debug message
|
||||||
|
func (l *Logger) Debug(format string, v ...interface{}) {
|
||||||
|
l.log(DEBUG, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info logs an info message
|
||||||
|
func (l *Logger) Info(format string, v ...interface{}) {
|
||||||
|
l.log(INFO, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warn logs a warning message
|
||||||
|
func (l *Logger) Warn(format string, v ...interface{}) {
|
||||||
|
l.log(WARN, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error logs an error message
|
||||||
|
func (l *Logger) Error(format string, v ...interface{}) {
|
||||||
|
l.log(ERROR, format, v...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPRequest logs an HTTP request
|
||||||
|
func (l *Logger) HTTPRequest(method, path, username string, statusCode int) {
|
||||||
|
l.log(INFO, "HTTP %s %s - User: %s - Status: %d",
|
||||||
|
method,
|
||||||
|
path,
|
||||||
|
username,
|
||||||
|
statusCode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAction logs user actions like login, logout, etc.
|
||||||
|
func (l *Logger) UserAction(username, action string) {
|
||||||
|
l.log(INFO, "User Action - Username: %s - Action: %s",
|
||||||
|
username,
|
||||||
|
action,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DatabaseAction logs database operations
|
||||||
|
func (l *Logger) DatabaseAction(operation, details string) {
|
||||||
|
l.log(INFO, "Database Action - Operation: %s - Details: %s",
|
||||||
|
operation,
|
||||||
|
details,
|
||||||
|
)
|
||||||
|
}
|
104
main.go
104
main.go
|
@ -23,17 +23,21 @@ var (
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Load configuration
|
// Load configuration
|
||||||
config, err := loadAndValidateConfig()
|
var err error
|
||||||
|
config, err = loadAndValidateConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to load config: %v", err)
|
log.Fatalf("Failed to load config: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Printf("Config loaded successfully in main(), DB path %s", config.DatabasePath)
|
fmt.Printf("Config loaded successfully in main(), DB path %s\n", config.DatabasePath)
|
||||||
|
|
||||||
// Set up logging
|
// Set up logging BEFORE logging config details
|
||||||
if err := setupLogging(config.LogFile); err != nil {
|
if err := setupLogging(config.LogFile); err != nil {
|
||||||
log.Fatalf("Failed to set up logging: %v", err)
|
log.Fatalf("Failed to set up logging: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now that logging is set up, log the config details
|
||||||
|
logConfigDetails(config)
|
||||||
|
|
||||||
// Connect to the database
|
// Connect to the database
|
||||||
db, err = connectToDatabase(config)
|
db, err = connectToDatabase(config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -43,6 +47,7 @@ func main() {
|
||||||
|
|
||||||
// Create routers
|
// Create routers
|
||||||
baseRouter := mux.NewRouter()
|
baseRouter := mux.NewRouter()
|
||||||
|
baseRouter.Use(loggingMiddleware)
|
||||||
apiRouter := createAPIRouter(baseRouter)
|
apiRouter := createAPIRouter(baseRouter)
|
||||||
staticRouter := createStaticRouter(baseRouter, config.StaticFilesDir)
|
staticRouter := createStaticRouter(baseRouter, config.StaticFilesDir)
|
||||||
|
|
||||||
|
@ -76,24 +81,49 @@ func loadAndValidateConfig() (*Config, error) {
|
||||||
|
|
||||||
ImageStorage = &config.ImageStorageDir
|
ImageStorage = &config.ImageStorageDir
|
||||||
|
|
||||||
// Log config details
|
|
||||||
logConfigDetails(config)
|
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupLogging(logFile string) error {
|
func setupLogging(logFile string) error {
|
||||||
fmt.Printf("Attempting to set up logging to file: %s\n", logFile)
|
// Initialize returns a *Logger, but we don't need to store it
|
||||||
file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
|
_, err := Initialize(logFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("Failed to open log file: %v\n", err)
|
return fmt.Errorf("failed to initialize logger: %v", err)
|
||||||
return fmt.Errorf("failed to open log file: %v", err)
|
|
||||||
}
|
}
|
||||||
log.SetOutput(file)
|
|
||||||
log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile)
|
// Get the logger instance
|
||||||
fmt.Println("Logging initialized")
|
log := GetLogger()
|
||||||
log.Println("Logging initialized")
|
if log == nil {
|
||||||
|
return fmt.Errorf("logger not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set log level from config if specified
|
||||||
|
if config.LogLevel != "" {
|
||||||
|
if err := log.SetLogLevel(config.LogLevel); err != nil {
|
||||||
|
return fmt.Errorf("failed to set log level: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
func logConfigDetails(config *Config) {
|
||||||
|
log := GetLogger()
|
||||||
|
if log == nil {
|
||||||
|
fmt.Println("Warning: Logger not initialized when attempting to log config details")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Configuration loaded:")
|
||||||
|
log.Info("Database Path: %s", config.DatabasePath)
|
||||||
|
log.Info("Image Storage Dir: %s", config.ImageStorageDir)
|
||||||
|
log.Info("Log File: %s", config.LogFile)
|
||||||
|
log.Info("Log Level: %s", config.LogLevel)
|
||||||
|
log.Info("Listening Port: %d", config.ListeningPort)
|
||||||
|
log.Info("Allowed Origins: %s", config.AllowedOrigins)
|
||||||
|
|
||||||
|
// Don't log the JWT secret for security reasons
|
||||||
|
log.Info("JWT Secret: [REDACTED]")
|
||||||
|
}
|
||||||
|
|
||||||
func connectToDatabase(config *Config) (*gorm.DB, error) {
|
func connectToDatabase(config *Config) (*gorm.DB, error) {
|
||||||
if config == nil {
|
if config == nil {
|
||||||
|
@ -158,10 +188,16 @@ func setupStaticRoutes(router *mux.Router, staticPath string) {
|
||||||
|
|
||||||
func createCustomStaticHandler(staticPath string) http.HandlerFunc {
|
func createCustomStaticHandler(staticPath string) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("Attempting to serve: %s from directory: %s", r.URL.Path, staticPath)
|
log := GetLogger()
|
||||||
|
if log != nil {
|
||||||
|
log.Info("Attempting to serve: %s from directory: %s", r.URL.Path, staticPath)
|
||||||
|
}
|
||||||
|
|
||||||
fullPath := filepath.Join(staticPath, r.URL.Path)
|
fullPath := filepath.Join(staticPath, r.URL.Path)
|
||||||
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
|
||||||
log.Printf("File not found: %s", fullPath)
|
if log != nil {
|
||||||
|
log.Warn("File not found: %s", fullPath)
|
||||||
|
}
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -185,6 +221,10 @@ func createCORSHandler(allowedOrigins string) func(http.Handler) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
func startServer(port int, handler http.Handler) {
|
func startServer(port int, handler http.Handler) {
|
||||||
|
log := GetLogger()
|
||||||
|
if log != nil {
|
||||||
|
log.Info("Server starting on port %d", port)
|
||||||
|
}
|
||||||
fmt.Printf("Server listening on port %d\n", port)
|
fmt.Printf("Server listening on port %d\n", port)
|
||||||
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), handler))
|
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), handler))
|
||||||
}
|
}
|
||||||
|
@ -196,12 +236,30 @@ func validateStaticDirectory(path string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func logConfigDetails(config *Config) {
|
func loggingMiddleware(next http.Handler) http.Handler {
|
||||||
fmt.Println("Config details:")
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Printf("Database Path: %s\n", config.DatabasePath)
|
username := "anonymous"
|
||||||
fmt.Printf("Image Storage Dir: %s\n", config.ImageStorageDir)
|
if user, ok := r.Context().Value(userKey).(string); ok {
|
||||||
fmt.Printf("JWT Secret: %s\n", config.JWTSecret)
|
username = user
|
||||||
fmt.Printf("Log File: %s\n", config.LogFile)
|
}
|
||||||
fmt.Printf("Listening Port: %d\n", config.ListeningPort)
|
|
||||||
fmt.Printf("Allowed Origins: %s\n", config.AllowedOrigins)
|
// Create a response wrapper to capture the status code
|
||||||
|
rw := &responseWriter{w, http.StatusOK}
|
||||||
|
|
||||||
|
next.ServeHTTP(rw, r)
|
||||||
|
|
||||||
|
if log := GetLogger(); log != nil {
|
||||||
|
log.HTTPRequest(r.Method, r.URL.Path, username, rw.statusCode)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type responseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
statusCode int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rw *responseWriter) WriteHeader(code int) {
|
||||||
|
rw.statusCode = code
|
||||||
|
rw.ResponseWriter.WriteHeader(code)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue