boxes-api/main.go

271 lines
8.4 KiB
Go
Raw Normal View History

2024-10-05 01:10:35 +00:00
package main
import (
"fmt"
"log"
"net/http"
"os"
"path/filepath"
"strings"
2024-10-05 01:10:35 +00:00
"github.com/gorilla/mux"
"github.com/jinzhu/gorm"
2024-10-05 03:05:30 +00:00
"github.com/rs/cors"
2024-10-05 01:10:35 +00:00
)
var (
db *gorm.DB // Declare db globally
config *Config
JWTSecret *[]byte
ImageStorage *string
DatabasePath *string
2024-10-05 01:10:35 +00:00
)
func main() {
2024-10-29 16:32:47 +00:00
log := GetLogger()
// Load configuration
var err error
config, err = loadAndValidateConfig()
if err != nil {
log.Fatalf("Failed to load config: %v", err)
}
2024-10-29 16:32:47 +00:00
log.Printf("Config loaded successfully in main(), DB path %s\n", config.DatabasePath)
// Set up logging BEFORE logging config details
2024-10-26 17:22:57 +00:00
if err := setupLogging(config.LogFile); err != nil {
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
db, err = connectToDatabase(config)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
defer db.Close()
// Create routers
baseRouter := mux.NewRouter()
baseRouter.Use(loggingMiddleware)
apiRouter := createAPIRouter(baseRouter)
staticRouter := createStaticRouter(baseRouter, config.StaticFilesDir)
// Set up routes
setupAPIRoutes(apiRouter)
setupStaticRoutes(staticRouter, config.StaticFilesDir)
// Create CORS handler
corsHandler := createCORSHandler(config.AllowedOrigins)
2024-10-18 15:00:04 +00:00
// Start the server
startServer(config.ListeningPort, corsHandler(baseRouter))
}
2024-10-18 15:00:04 +00:00
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)
}
// Validate the database path
if config.DatabasePath == "" {
return nil, fmt.Errorf("database path is not set in config")
}
DatabasePath = &config.DatabasePath
2024-10-05 01:10:35 +00:00
// Set JWTSecret
jwtSecretBytes := []byte(config.JWTSecret)
JWTSecret = &jwtSecretBytes
ImageStorage = &config.ImageStorageDir
return config, nil
}
2024-10-26 17:22:57 +00:00
func setupLogging(logFile string) error {
// Initialize returns a *Logger, but we don't need to store it
2024-10-29 16:32:47 +00:00
_, err := Initialize(logFile, config.LogOutput)
2024-10-26 17:22:57 +00:00
if err != nil {
return fmt.Errorf("failed to initialize logger: %v", err)
}
// Get the logger instance
log := GetLogger()
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)
}
2024-10-26 17:22:57 +00:00
}
2024-10-26 17:22:57 +00:00
return nil
}
func logConfigDetails(config *Config) {
log := GetLogger()
if log == nil {
2024-10-29 16:32:47 +00:00
log.Warn("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)
2024-10-29 16:32:47 +00:00
log.Info("Log Output: %s", config.LogOutput)
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]")
}
2024-10-26 17:22:57 +00:00
func connectToDatabase(config *Config) (*gorm.DB, error) {
2024-10-29 16:32:47 +00:00
log := GetLogger()
if config == nil {
2024-10-29 16:32:47 +00:00
log.Error("config is nil in connectToDatabase")
return nil, fmt.Errorf("config is nil")
}
db, err := ConnectDB(config.DatabasePath)
2024-10-05 01:10:35 +00:00
if err != nil || db == nil {
2024-10-29 16:32:47 +00:00
log.Error("Failed to connect to database in connectToDatabase")
return nil, fmt.Errorf("failed to connect to database: %v", err)
2024-10-05 01:10:35 +00:00
}
2024-10-29 16:32:47 +00:00
log.Info("Connected to database in connectToDatabase")
return db, nil
}
2024-10-05 01:10:35 +00:00
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 := GetLogger()
if log != nil {
log.Info("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) {
if log != nil {
log.Warn("File not found: %s", fullPath)
}
http.NotFound(w, r)
return
}
http.FileServer(http.Dir(staticPath)).ServeHTTP(w, r)
}
}
func createCORSHandler(allowedOrigins string) func(http.Handler) http.Handler {
origins := strings.Split(allowedOrigins, ",")
if len(origins) == 0 {
origins = []string{"http://localhost:3000"}
}
2024-10-05 01:10:35 +00:00
return cors.New(cors.Options{
2024-10-18 15:00:04 +00:00
AllowedOrigins: origins,
2024-10-05 03:05:30 +00:00
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"},
2024-10-05 03:05:30 +00:00
AllowCredentials: true,
}).Handler
}
func startServer(port int, handler http.Handler) {
log := GetLogger()
if log != nil {
log.Info("Server starting on port %d", port)
}
2024-10-29 16:32:47 +00:00
log.Info("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
}
2024-10-05 03:05:30 +00:00
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username := "anonymous"
if user, ok := r.Context().Value(userKey).(string); ok {
username = user
}
// 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)
2024-10-05 01:10:35 +00:00
}