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/*
.DS_Store
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"
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/"

View File

@ -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: "*"

215
main.go
View File

@ -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)
}

View File

@ -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');"
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
# API base URL
API_BASE_URL="http://10.0.0.66:8080"
API_BASE_URL="http://localhost:8080/api/v1"
# Login credentials
USERNAME="boxuser"

View File

@ -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"
-H "Content-Type: application/json"