diff --git a/.gitignore b/.gitignore index 0a0f3d9..1668e40 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ data/* images/* .DS_Store build/* +boxes-api diff --git a/config/boxes.db b/config/boxes.db new file mode 100644 index 0000000..e69de29 diff --git a/config/config.yaml b/config/config.yaml index 5de9a2d..fe48c49 100644 --- a/config/config.yaml +++ b/config/config.yaml @@ -1,7 +1,7 @@ -database_path: "data/boxes.db" +database_path: "/app/data/boxes.db" test_database_path: "data/test_database.db" jwt_secret: "super_secret_key" -image_storage_dir: "images" +image_storage_dir: "/app/images/" listening_port: 8080 log_file: "boxes.log" static_files_dir: "/app/build/" diff --git a/config/config_local.yaml b/config/config_local.yaml index 6c2776c..2ddd791 100644 --- a/config/config_local.yaml +++ b/config/config_local.yaml @@ -1,8 +1,8 @@ -database_path: "data/boxes.db" -test_database_path: "data/test_database.db" +database_path: "./data/boxes.db" +test_database_path: "./data/test_database.db" jwt_secret: "super_secret_key" -image_storage_dir: "images/" +image_storage_dir: "./images/" listening_port: 8080 log_file: "boxes.log" -static_files_dir: "build/" +static_files_dir: "./build/" allowed_origins: "*" diff --git a/main.go b/main.go index a17ba91..b1888b6 100644 --- a/main.go +++ b/main.go @@ -5,8 +5,8 @@ import ( "log" "net/http" "os" - "strings" "path/filepath" + "strings" "github.com/gorilla/mux" "github.com/jinzhu/gorm" @@ -19,51 +19,105 @@ var ( ) func main() { - - configFile := os.Getenv("BOXES_API_CONFIG") - var err error - 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 { + // Load configuration + config, err := loadAndValidateConfig() + if err != nil { 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 - db, err = ConnectDB(config.DatabasePath) - if err != nil || db == nil { + db, err = connectToDatabase(config) + if err != nil { log.Fatalf("Failed to connect to database: %v", err) } defer db.Close() - // Modify your custom handler to include more detailed logging - customHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Create routers + 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) fullPath := filepath.Join(staticPath, r.URL.Path) if _, err := os.Stat(fullPath); os.IsNotExist(err) { @@ -72,65 +126,42 @@ func main() { return } 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 - 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{ + return cors.New(cors.Options{ AllowedOrigins: origins, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, 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"}, AllowCredentials: true, - }) - - // Start the server with CORS middleware - fmt.Printf("Server listening on port %d\n", config.ListeningPort) - http.ListenAndServe(fmt.Sprintf(":%d", config.ListeningPort), c.Handler(baseRouter)) + }).Handler +} + +func startServer(port int, handler http.Handler) { + 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) } diff --git a/scripts/createAdminUser.bash b/scripts/createAdminUser.bash index 59509d6..bc2e570 100644 --- a/scripts/createAdminUser.bash +++ b/scripts/createAdminUser.bash @@ -1,10 +1,11 @@ #!/binb/ash # 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 HASHED_PASSWORD=$(htpasswd -nbBC 10 "" "$PASSWORD" | tr -d ':\n') # 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');" \ No newline at end of file +sqlite3 $DATABASE "INSERT INTO users (username, password, email) VALUES ('boxuser', '$HASHED_PASSWORD','boxuser@r8z.us')" diff --git a/scripts/get_token.bash b/scripts/get_token.bash new file mode 100644 index 0000000..82e3ff0 --- /dev/null +++ b/scripts/get_token.bash @@ -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 diff --git a/scripts/getboxes.bash b/scripts/getboxes.bash index c466333..476a878 100644 --- a/scripts/getboxes.bash +++ b/scripts/getboxes.bash @@ -1,7 +1,7 @@ #!/bin/bash # API base URL -API_BASE_URL="http://10.0.0.66:8080" +API_BASE_URL="http://localhost:8080/api/v1" # Login credentials USERNAME="boxuser" diff --git a/scripts/getusers.bash b/scripts/getusers.bash index 3440108..7f8ab76 100644 --- a/scripts/getusers.bash +++ b/scripts/getusers.bash @@ -1,7 +1,7 @@ #!/bin/bash # API base URL -API_BASE_URL="http://localhost:8080" +API_BASE_URL="http://localhost:8080/api/v1/" # Login credentials USERNAME="boxuser" @@ -11,8 +11,9 @@ PASSWORD="boxuser" TOKEN=$(curl -s -X POST -H "Content-Type: application/json" \ -d "{\"username\":\"$USERNAME\", \"password\":\"$PASSWORD\"}" \ "$API_BASE_URL/login" | jq -r '.token') +echo $TOKEN curl -X GET \ $API_BASE_URL/admin/user \ -H "Authorization: Bearer $TOKEN" \ - -H "Content-Type: application/json" \ No newline at end of file + -H "Content-Type: application/json"