refactored by AI to be clearer and easier to support. Seems to work.

This commit is contained in:
Steve White 2024-10-25 13:23:41 -05:00
parent 76a03cda57
commit e6708ff832
9 changed files with 168 additions and 103 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@ data/*
images/* images/*
.DS_Store .DS_Store
build/* build/*
boxes-api

0
config/boxes.db Normal file
View File

View File

@ -1,7 +1,7 @@
database_path: "data/boxes.db" database_path: "/app/data/boxes.db"
test_database_path: "data/test_database.db" test_database_path: "data/test_database.db"
jwt_secret: "super_secret_key" jwt_secret: "super_secret_key"
image_storage_dir: "images" image_storage_dir: "/app/images/"
listening_port: 8080 listening_port: 8080
log_file: "boxes.log" log_file: "boxes.log"
static_files_dir: "/app/build/" static_files_dir: "/app/build/"

View File

@ -1,8 +1,8 @@
database_path: "data/boxes.db" database_path: "./data/boxes.db"
test_database_path: "data/test_database.db" test_database_path: "./data/test_database.db"
jwt_secret: "super_secret_key" jwt_secret: "super_secret_key"
image_storage_dir: "images/" image_storage_dir: "./images/"
listening_port: 8080 listening_port: 8080
log_file: "boxes.log" log_file: "boxes.log"
static_files_dir: "build/" static_files_dir: "./build/"
allowed_origins: "*" allowed_origins: "*"

215
main.go
View File

@ -5,8 +5,8 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"strings"
"path/filepath" "path/filepath"
"strings"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
@ -19,51 +19,105 @@ var (
) )
func main() { func main() {
// Load configuration
configFile := os.Getenv("BOXES_API_CONFIG") config, err := loadAndValidateConfig()
var err error if err != nil {
config, err = LoadConfig(configFile)
// get the static files directory
staticPath := config.StaticFilesDir
// Add this before setting up the handler
if _, err := os.Stat(staticPath); os.IsNotExist(err) {
log.Fatalf("Static directory does not exist: %s", staticPath)
}
// Get the allowed origins from the ALLOWED_ORIGINS environment variable
// If empty, defaults to http://localhost:3000
allowedOrigins := config.AllowedOrigins
fmt.Println("Allowed origins: ", allowedOrigins)
origins := []string{"http://localhost:3000"} // Default value
if allowedOrigins != "" {
// Split the comma-separated string into a slice of strings
origins = strings.Split(allowedOrigins, ",")
fmt.Println("Listening for connections from: ", origins)
}
// check for errors
if err != nil || config == nil {
log.Fatalf("Failed to load config: %v", err) log.Fatalf("Failed to load config: %v", err)
} }
fmt.Println(config.DatabasePath)
fmt.Println(config.ImageStorageDir)
fmt.Println(config.JWTSecret)
fmt.Println(config.LogFile)
fmt.Println(config.ListeningPort)
// Connect to the database // Connect to the database
db, err = ConnectDB(config.DatabasePath) db, err = connectToDatabase(config)
if err != nil || db == nil { if err != nil {
log.Fatalf("Failed to connect to database: %v", err) log.Fatalf("Failed to connect to database: %v", err)
} }
defer db.Close() defer db.Close()
// Modify your custom handler to include more detailed logging // Create routers
customHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { baseRouter := mux.NewRouter()
apiRouter := createAPIRouter(baseRouter)
staticRouter := createStaticRouter(baseRouter, config.StaticFilesDir)
// Set up routes
setupAPIRoutes(apiRouter)
setupStaticRoutes(staticRouter, config.StaticFilesDir)
// Create CORS handler
corsHandler := createCORSHandler(config.AllowedOrigins)
// Start the server
startServer(config.ListeningPort, corsHandler(baseRouter))
}
func loadAndValidateConfig() (*Config, error) {
configFile := os.Getenv("BOXES_API_CONFIG")
config, err := LoadConfig(configFile)
if err != nil || config == nil {
return nil, fmt.Errorf("failed to load config: %v", err)
}
logConfigDetails(config)
return config, nil
}
func connectToDatabase(config *Config) (*gorm.DB, error) {
db, err := ConnectDB(config.DatabasePath)
if err != nil || db == nil {
return nil, fmt.Errorf("failed to connect to database: %v", err)
}
return db, nil
}
func createAPIRouter(baseRouter *mux.Router) *mux.Router {
return baseRouter.PathPrefix("/api/v1").Subrouter()
}
func createStaticRouter(baseRouter *mux.Router, staticPath string) *mux.Router {
if err := validateStaticDirectory(staticPath); err != nil {
log.Fatalf("Static directory error: %v", err)
}
return baseRouter.PathPrefix("/").Subrouter()
}
func setupAPIRoutes(router *mux.Router) {
router.Handle("/login", http.HandlerFunc(LoginHandler)).Methods("POST", "OPTIONS")
// Protected routes
protected := router.NewRoute().Subrouter()
protected.Use(AuthMiddleware)
protected.Handle("/boxes", http.HandlerFunc(GetBoxesHandler)).Methods("GET", "OPTIONS")
protected.Handle("/boxes", http.HandlerFunc(CreateBoxHandler)).Methods("POST", "OPTIONS")
protected.Handle("/boxes/{id}", http.HandlerFunc(DeleteBoxHandler)).Methods("DELETE", "OPTIONS")
protected.Handle("/boxes/{id}", http.HandlerFunc(GetBoxHandler)).Methods("GET", "OPTIONS")
protected.Handle("/items", http.HandlerFunc(GetItemsHandler)).Methods("GET", "OPTIONS")
protected.Handle("/items", http.HandlerFunc(CreateItemHandler)).Methods("POST", "OPTIONS")
protected.Handle("/items/{id}", http.HandlerFunc(GetItemHandler)).Methods("GET", "OPTIONS")
protected.Handle("/boxes/{id}/items", http.HandlerFunc(GetItemsInBoxHandler)).Methods("GET", "OPTIONS")
protected.Handle("/items/{id}", http.HandlerFunc(UpdateItemHandler)).Methods("PUT", "OPTIONS")
protected.Handle("/items/{id}", http.HandlerFunc(DeleteItemHandler)).Methods("DELETE", "OPTIONS")
protected.Handle("/items/{id}/image", http.HandlerFunc(GetItemImageHandler)).Methods("GET", "OPTIONS")
protected.Handle("/search/items", http.HandlerFunc(SearchItemsHandler)).Methods("GET", "OPTIONS")
protected.Handle("/items/{id}/upload", http.HandlerFunc(UploadItemImageHandler)).Methods("POST", "OPTIONS")
setupManagementRoutes(protected.PathPrefix("/admin").Subrouter())
}
func setupManagementRoutes(router *mux.Router) {
router.Handle("/user", http.HandlerFunc(GetUsersHandler)).Methods("GET", "OPTIONS")
router.Handle("/user", http.HandlerFunc(CreateUserHandler)).Methods("POST", "OPTIONS")
router.Handle("/user/{id}", http.HandlerFunc(GetUserHandler)).Methods("GET", "OPTIONS")
router.Handle("/user/{id}", http.HandlerFunc(DeleteUserHandler)).Methods("DELETE", "OPTIONS")
router.Handle("/db", http.HandlerFunc(BackupDatabaseHandler)).Methods("GET", "OPTIONS")
router.Handle("/db", http.HandlerFunc(RestoreDatabaseHandler)).Methods("POST", "OPTIONS")
}
func setupStaticRoutes(router *mux.Router, staticPath string) {
customHandler := createCustomStaticHandler(staticPath)
router.PathPrefix("/").Handler(http.StripPrefix("/", customHandler))
}
func createCustomStaticHandler(staticPath string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
log.Printf("Attempting to serve: %s from directory: %s", r.URL.Path, staticPath) log.Printf("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) {
@ -72,65 +126,42 @@ func main() {
return return
} }
http.FileServer(http.Dir(staticPath)).ServeHTTP(w, r) http.FileServer(http.Dir(staticPath)).ServeHTTP(w, r)
}) }
}
fmt.Println("Default user 'boxuser' created successfully!") func createCORSHandler(allowedOrigins string) func(http.Handler) http.Handler {
origins := strings.Split(allowedOrigins, ",")
if len(origins) == 0 {
origins = []string{"http://localhost:3000"}
}
// Create the router return cors.New(cors.Options{
baseRouter := mux.NewRouter()
router := baseRouter.PathPrefix("/api/v1").Subrouter()
staticRouter := baseRouter.PathPrefix("/").Subrouter()
// Define your routes
router.Handle("/login", http.HandlerFunc(LoginHandler)).Methods("POST", "OPTIONS")
router.Handle("/boxes", AuthMiddleware(http.HandlerFunc(GetBoxesHandler))).Methods("GET", "OPTIONS")
router.Handle("/boxes", AuthMiddleware(http.HandlerFunc(CreateBoxHandler))).Methods("POST", "OPTIONS")
router.Handle("/boxes/{id}", AuthMiddleware(http.HandlerFunc(DeleteBoxHandler))).Methods("DELETE", "OPTIONS")
router.Handle("/boxes/{id}", AuthMiddleware(http.HandlerFunc(GetBoxHandler))).Methods("GET", "OPTIONS")
router.Handle("/items", AuthMiddleware(http.HandlerFunc(GetItemsHandler))).Methods("GET", "OPTIONS")
router.Handle("/items", AuthMiddleware(http.HandlerFunc(CreateItemHandler))).Methods("POST", "OPTIONS")
router.Handle("/items/{id}", AuthMiddleware(http.HandlerFunc(GetItemHandler))).Methods("GET", "OPTIONS")
router.Handle("/boxes/{id}/items", AuthMiddleware(http.HandlerFunc(GetItemsInBoxHandler))).Methods("GET", "OPTIONS")
router.Handle("/items/{id}", AuthMiddleware(http.HandlerFunc(UpdateItemHandler))).Methods("PUT", "OPTIONS")
router.Handle("/items/{id}", AuthMiddleware(http.HandlerFunc(DeleteItemHandler))).Methods("DELETE", "OPTIONS")
router.Handle("/items/{id}/image", AuthMiddleware(http.HandlerFunc(GetItemImageHandler))).Methods("GET", "OPTIONS")
fmt.Println("Registering route for search items...")
router.Handle("/search/items", AuthMiddleware(http.HandlerFunc(SearchItemsHandler))).Methods("GET", "OPTIONS")
// Add a new route for uploading an image with AuthMiddleware
router.HandleFunc("/items/{id}/upload", UploadItemImageHandler).
Methods("POST").
Handler(AuthMiddleware(http.HandlerFunc(UploadItemImageHandler)))
managementRouter := router.PathPrefix("/admin").Subrouter()
managementRouter.Use(AuthMiddleware)
managementRouter.Handle("/user", http.HandlerFunc(GetUsersHandler)).Methods("GET", "OPTIONS")
managementRouter.Handle("/user", http.HandlerFunc(CreateUserHandler)).Methods("POST", "OPTIONS")
managementRouter.Handle("/user/{id}", http.HandlerFunc(GetUserHandler)).Methods("GET", "OPTIONS")
managementRouter.Handle("/user/{id}", http.HandlerFunc(DeleteUserHandler)).Methods("DELETE", "OPTIONS")
managementRouter.Handle("/db", http.HandlerFunc(BackupDatabaseHandler)).Methods("GET", "OPTIONS")
managementRouter.Handle("/db", http.HandlerFunc(RestoreDatabaseHandler)).Methods("POST", "OPTIONS")
// Define a route for serving static files
fmt.Println("Serving static files from:", staticPath)
//staticHandler := http.FileServer(http.Dir(staticPath))
fmt.Println("Registering route for serving static files, no StripPrefix")
//staticRouter.HandleFunc("/", customHandler).Methods("GET", "OPTIONS")
// perplexity recommends:
staticRouter.PathPrefix("/").Handler(http.StripPrefix("/", customHandler))
// Apply CORS middleware
c := cors.New(cors.Options{
AllowedOrigins: origins, AllowedOrigins: origins,
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowedHeaders: []string{"Authorization", "Content-Type"}, AllowedHeaders: []string{"Authorization", "Content-Type"},
ExposedHeaders: []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Cache-Control", "Content-Language", "Content-Type", "Expires", "Last-Modified", "Pragma", "ETag"}, ExposedHeaders: []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Cache-Control", "Content-Language", "Content-Type", "Expires", "Last-Modified", "Pragma", "ETag"},
AllowCredentials: true, AllowCredentials: true,
}) }).Handler
}
// Start the server with CORS middleware
fmt.Printf("Server listening on port %d\n", config.ListeningPort) func startServer(port int, handler http.Handler) {
http.ListenAndServe(fmt.Sprintf(":%d", config.ListeningPort), c.Handler(baseRouter)) fmt.Printf("Server listening on port %d\n", port)
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", port), handler))
}
func validateStaticDirectory(path string) error {
if _, err := os.Stat(path); os.IsNotExist(err) {
return fmt.Errorf("static directory does not exist: %s", path)
}
return nil
}
func logConfigDetails(config *Config) {
fmt.Println("Config details:")
fmt.Printf("Database Path: %s\n", config.DatabasePath)
fmt.Printf("Image Storage Dir: %s\n", config.ImageStorageDir)
fmt.Printf("JWT Secret: %s\n", config.JWTSecret)
fmt.Printf("Log File: %s\n", config.LogFile)
fmt.Printf("Listening Port: %d\n", config.ListeningPort)
fmt.Printf("Allowed Origins: %s\n", config.AllowedOrigins)
} }

View File

@ -1,10 +1,11 @@
#!/binb/ash #!/binb/ash
# Set the password you want to use # Set the password you want to use
PASSWORD="12m0nk3ys" PASSWORD="boxuser"
DATABASE="/Users/stwhite/CODE/Boxes-App/boxes-api/data/boxes.db"
# Use htpasswd to create a bcrypt hash of the password # Use htpasswd to create a bcrypt hash of the password
HASHED_PASSWORD=$(htpasswd -nbBC 10 "" "$PASSWORD" | tr -d ':\n') HASHED_PASSWORD=$(htpasswd -nbBC 10 "" "$PASSWORD" | tr -d ':\n')
# Create the user in the SQLite database # Create the user in the SQLite database
sqlite3 your_database.db "INSERT INTO users (username, password, email, email, email, email, email, email, email, email, email) VALUES ('boxuser', '$HASHED_PASSWORD','boxuser@r8z.us');" sqlite3 $DATABASE "INSERT INTO users (username, password, email) VALUES ('boxuser', '$HASHED_PASSWORD','boxuser@r8z.us')"

31
scripts/get_token.bash Normal file
View File

@ -0,0 +1,31 @@
#!/bin/bash
API_BASE_URL="http://localhost:8080/api/v1" # Adjusted to include /api/v1
USERNAME="boxuser"
PASSWORD="boxuser"
echo "Sending request to: $API_BASE_URL/login"
echo "Username: $USERNAME"
echo "Password: $PASSWORD"
response=$(curl -vi -w "\n%{http_code}" -X POST -H "Content-Type: application/json" \
-d "{\"username\":\"$USERNAME\", \"password\":\"$PASSWORD\"}" \
"$API_BASE_URL/login")
http_status=$(echo "$response" | tail -n1)
body=$(echo "$response" | sed '$d')
echo "HTTP Status: $http_status"
echo "Response body: $body"
if [ "$http_status" -eq 200 ]; then
TOKEN=$(echo "$body" | jq -r '.token // empty')
if [ -n "$TOKEN" ]; then
echo "Token obtained successfully:"
echo "$TOKEN"
else
echo "Failed to extract token from response."
fi
else
echo "Failed to obtain token. Server returned status $http_status"
fi

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# API base URL # API base URL
API_BASE_URL="http://10.0.0.66:8080" API_BASE_URL="http://localhost:8080/api/v1"
# Login credentials # Login credentials
USERNAME="boxuser" USERNAME="boxuser"

View File

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
# API base URL # API base URL
API_BASE_URL="http://localhost:8080" API_BASE_URL="http://localhost:8080/api/v1/"
# Login credentials # Login credentials
USERNAME="boxuser" USERNAME="boxuser"
@ -11,8 +11,9 @@ PASSWORD="boxuser"
TOKEN=$(curl -s -X POST -H "Content-Type: application/json" \ TOKEN=$(curl -s -X POST -H "Content-Type: application/json" \
-d "{\"username\":\"$USERNAME\", \"password\":\"$PASSWORD\"}" \ -d "{\"username\":\"$USERNAME\", \"password\":\"$PASSWORD\"}" \
"$API_BASE_URL/login" | jq -r '.token') "$API_BASE_URL/login" | jq -r '.token')
echo $TOKEN
curl -X GET \ curl -X GET \
$API_BASE_URL/admin/user \ $API_BASE_URL/admin/user \
-H "Authorization: Bearer $TOKEN" \ -H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" -H "Content-Type: application/json"