the last implementation just accepted any input for the shipping validation and then only validated the address on payment. this is now changed to validation upon creation upon finishing the shipping address form. when a user completes the shipping form, a client-side check is first made to just simply check if there are any empty fields, if this check passes it gets check server-side by shippo. If the address is not found it will return an address not found error, and the user will not be able to continue to the payment form. The invalid address will also not be saved to the database. if the check pass, we simply save the address to the database with an ID. this ID is then passe in when doing the payment, instead of validating at the payment phase. this implementation currently only uses Shippo, which i have come to realize does NOT support shipping in countries outside of the US (i should have read the friendly manual before using Shippo). This means that a huge rewrite is due. i will focus on adding address validation for Norway first since thats where i live. i plan on using Bring's api for this validation. i will still keep Shippo for US based address verification even though this project is focused fon nordic shipping - just for a rainy day if some poor soul decides to order from AMERICA
92 lines
2.2 KiB
Go
92 lines
2.2 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
|
|
"terminalShop/pkg/auth"
|
|
"terminalShop/api/routes"
|
|
"terminalShop/pkg/config"
|
|
"terminalShop/pkg/database"
|
|
)
|
|
|
|
const version = "v0.1.0"
|
|
|
|
func main() {
|
|
// Load configuration
|
|
cfg, err := config.Load()
|
|
if err != nil {
|
|
log.Fatalf("Failed to load config: %v", err)
|
|
}
|
|
|
|
// Initialize database connection
|
|
db, err := database.Connect(cfg.DatabaseURL)
|
|
if err != nil {
|
|
log.Fatalf("Failed to connect to database: %v", err)
|
|
}
|
|
defer database.Close()
|
|
|
|
// Run migrations
|
|
if err := database.Migrate(db); err != nil {
|
|
log.Fatalf("Failed to run migrations: %v", err)
|
|
}
|
|
|
|
// Seed database with initial data
|
|
if err := database.Seed(db); err != nil {
|
|
log.Fatalf("Failed to seed database: %v", err)
|
|
}
|
|
|
|
// Init JWT manager (same secret + duration as SSH server)
|
|
jwtManager := auth.NewJWTManager(cfg.JWTSecret, 30*time.Minute)
|
|
|
|
// Setup routes
|
|
router := routes.SetupRoutes(version, cfg.StripeSecretKey, jwtManager, cfg.AuthFingerprintKey, cfg.ShippoAPIKey)
|
|
|
|
// Create HTTP server
|
|
server := &http.Server{
|
|
Addr: fmt.Sprintf(":%s", cfg.APIPort),
|
|
Handler: router,
|
|
ReadTimeout: 15 * time.Second,
|
|
WriteTimeout: 15 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
|
|
// Channel to listen for interrupt signals
|
|
done := make(chan os.Signal, 1)
|
|
signal.Notify(done, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
// Start server in a goroutine
|
|
go func() {
|
|
log.Printf("Starting API server on port %s", cfg.APIPort)
|
|
log.Printf("Environment: %s", cfg.Environment)
|
|
log.Printf("Database: %s", cfg.DatabaseURL)
|
|
log.Printf("Health check: http://localhost:%s/api/v1/health", cfg.APIPort)
|
|
|
|
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Fatalf("Server failed to start: %v", err)
|
|
}
|
|
}()
|
|
|
|
log.Println("API server started. Press Ctrl+C to shutdown.")
|
|
|
|
// Block until signal received
|
|
<-done
|
|
log.Println("Server is shutting down...")
|
|
|
|
// Graceful shutdown with 30 second timeout
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
if err := server.Shutdown(ctx); err != nil {
|
|
log.Fatalf("Server forced to shutdown: %v", err)
|
|
}
|
|
|
|
log.Println("Server exited gracefully")
|
|
}
|