243 lines
7.4 KiB
Go
243 lines
7.4 KiB
Go
// cmd/server/main.go - ENHANCED WITH DEBUG LOGGING
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"lost-and-found/internal/config"
|
|
"lost-and-found/internal/middleware"
|
|
"lost-and-found/internal/routes"
|
|
"lost-and-found/internal/workers"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"syscall"
|
|
"time"
|
|
"sync"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/joho/godotenv"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
func main() {
|
|
// Load .env file
|
|
if err := godotenv.Load(); err != nil {
|
|
log.Println("⚠️ No .env file found, using environment variables")
|
|
}
|
|
|
|
// ✅ Structured Logging
|
|
logger, err := zap.NewProduction()
|
|
if err != nil {
|
|
log.Fatalf("❌ Failed to initialize logger: %v", err)
|
|
}
|
|
defer logger.Sync()
|
|
|
|
logger.Info("🚀 Starting Lost & Found System...")
|
|
|
|
// Initialize JWT config
|
|
config.InitJWT()
|
|
logger.Info("✅ JWT configuration initialized")
|
|
|
|
// Initialize database
|
|
logger.Info("📊 Initializing database...")
|
|
if err := config.InitDB(); err != nil {
|
|
logger.Fatal("❌ Failed to initialize database", zap.Error(err))
|
|
}
|
|
defer func() {
|
|
logger.Info("🗄️ Closing database connections...")
|
|
if err := config.CloseDB(); err != nil {
|
|
logger.Error("Failed to close database", zap.Error(err))
|
|
} else {
|
|
logger.Info("✅ Database connections closed")
|
|
}
|
|
}()
|
|
|
|
// Run migrations
|
|
logger.Info("📋 Running migrations...")
|
|
db := config.GetDB()
|
|
if err := config.RunMigrations(db); err != nil {
|
|
logger.Fatal("❌ Failed to run migrations", zap.Error(err))
|
|
}
|
|
|
|
// Initialize Gin
|
|
if config.IsProduction() {
|
|
gin.SetMode(gin.ReleaseMode)
|
|
}
|
|
router := gin.Default()
|
|
|
|
// Apply middleware
|
|
router.Use(middleware.CORSMiddleware())
|
|
router.Use(middleware.LoggerMiddleware())
|
|
router.Use(middleware.RateLimiterMiddleware())
|
|
|
|
if config.IsDevelopment() {
|
|
router.Use(func(c *gin.Context) {
|
|
c.Writer.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
|
c.Writer.Header().Set("Pragma", "no-cache")
|
|
c.Writer.Header().Set("Expires", "0")
|
|
c.Next()
|
|
})
|
|
}
|
|
|
|
// Serve static files
|
|
router.Static("/uploads", "./uploads")
|
|
router.Static("/css", "./web/css")
|
|
router.Static("/js", "./web/js")
|
|
router.Static("/assets", "./web/assets")
|
|
|
|
// Frontend routes
|
|
router.GET("/", func(c *gin.Context) { c.File("./web/index.html") })
|
|
router.GET("/login", func(c *gin.Context) { c.File("./web/login.html") })
|
|
router.GET("/login.html", func(c *gin.Context) { c.File("./web/login.html") }) // ✅ Tambahkan ini
|
|
router.GET("/register", func(c *gin.Context) { c.File("./web/register.html") })
|
|
router.GET("/register.html", func(c *gin.Context) { c.File("./web/register.html") }) // ✅ Tambahkan ini
|
|
router.GET("/admin", func(c *gin.Context) { c.File("./web/admin.html") })
|
|
router.GET("/admin.html", func(c *gin.Context) { c.File("./web/admin.html") }) // ✅ Tambahkan ini
|
|
router.GET("/manager", func(c *gin.Context) { c.File("./web/manager.html") })
|
|
router.GET("/manager.html", func(c *gin.Context) { c.File("./web/manager.html") }) // ✅ Tambahkan ini
|
|
router.GET("/user", func(c *gin.Context) { c.File("./web/user.html") })
|
|
router.GET("/user.html", func(c *gin.Context) { c.File("./web/user.html") }) // ✅ Tambahkan ini
|
|
|
|
// Setup API routes
|
|
routes.SetupRoutes(router, db, logger)
|
|
logger.Info("✅ API routes configured")
|
|
|
|
// ✅ Start Background Workers
|
|
logger.Info("🔄 Starting background workers...")
|
|
expireWorker := workers.NewExpireWorker(db)
|
|
auditWorker := workers.NewAuditWorker(db)
|
|
matchingWorker := workers.NewMatchingWorker(db)
|
|
notificationWorker := workers.NewNotificationWorker(db)
|
|
|
|
// ✅ Background Workers - 4 Goroutines
|
|
expireWorker.Start()
|
|
auditWorker.Start()
|
|
matchingWorker.Start()
|
|
notificationWorker.Start()
|
|
logger.Info("✅ All background workers started")
|
|
|
|
// Get server config
|
|
serverConfig := config.GetServerConfig()
|
|
port := serverConfig.Port
|
|
|
|
// ✅ DEBUG: Print sebelum create server
|
|
log.Println("🔧 DEBUG: Creating HTTP server...")
|
|
log.Printf("🔧 DEBUG: Port = %s\n", port)
|
|
log.Printf("🔧 DEBUG: Address = :%s\n", port)
|
|
|
|
// ✅ HTTP Server with Timeouts
|
|
srv := &http.Server{
|
|
Addr: ":" + port,
|
|
Handler: router,
|
|
ReadTimeout: 15 * time.Second,
|
|
WriteTimeout: 15 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
|
|
// ✅ DEBUG: Print sebelum start goroutine
|
|
log.Println("🔧 DEBUG: Starting server goroutine...")
|
|
|
|
// ✅ Start server in goroutine
|
|
serverErrors := make(chan error, 1)
|
|
go func() {
|
|
// ✅ PENTING: Print ini HARUS muncul
|
|
log.Println("🚀 Server starting...")
|
|
log.Printf(" 📍 URL: http://localhost:%s\n", port)
|
|
log.Printf(" 📡 API: http://localhost:%s/api\n", port)
|
|
log.Printf(" 🌍 ENV: %s\n", serverConfig.Environment)
|
|
log.Println("✨ Press Ctrl+C to stop")
|
|
log.Println("🔧 DEBUG: Calling srv.ListenAndServe()...")
|
|
|
|
logger.Info("🚀 Server starting",
|
|
zap.String("url", "http://localhost:"+port),
|
|
zap.String("api", "http://localhost:"+port+"/api"),
|
|
zap.String("environment", serverConfig.Environment),
|
|
)
|
|
|
|
// ✅ Ini yang benar-benar mulai server
|
|
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
log.Printf("❌ SERVER ERROR: %v\n", err)
|
|
serverErrors <- err
|
|
}
|
|
}()
|
|
|
|
// ✅ DEBUG: Print setelah goroutine
|
|
log.Println("🔧 DEBUG: Server goroutine launched, waiting for signals...")
|
|
|
|
// ✅ Wait for interrupt signal OR server error
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
select {
|
|
case sig := <-quit:
|
|
logger.Info("🛑 Shutdown signal received", zap.String("signal", sig.String()))
|
|
case err := <-serverErrors:
|
|
logger.Fatal("❌ Server error", zap.Error(err))
|
|
}
|
|
|
|
logger.Info("🔄 Starting graceful shutdown...")
|
|
|
|
// ✅ Step 1: Create shutdown context with timeout
|
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer shutdownCancel()
|
|
|
|
// Implementasi ACID dan Kontrol Transaksi (15%)
|
|
// ✅ Step 2: Stop accepting new HTTP requests
|
|
// Implementasi Graceful Shutdown dengan srv.Shutdown(shutdownCtx) memastikan Durability data yang sedang diproses.
|
|
logger.Info("🔌 Stopping server from accepting new requests...")
|
|
if err := srv.Shutdown(shutdownCtx); err != nil {
|
|
logger.Error("⚠️ Server forced to shutdown", zap.Error(err))
|
|
} else {
|
|
logger.Info("✅ Server stopped accepting new requests")
|
|
}
|
|
|
|
// ✅ Step 3: Stop background workers
|
|
logger.Info("🔄 Stopping background workers...")
|
|
|
|
workersDone := make(chan struct{})
|
|
|
|
go func() {
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(4)
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
expireWorker.Stop()
|
|
logger.Info("✅ Expire worker stopped")
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
auditWorker.Stop()
|
|
logger.Info("✅ Audit worker stopped")
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
matchingWorker.Stop()
|
|
logger.Info("✅ Matching worker stopped")
|
|
}()
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
notificationWorker.Stop()
|
|
logger.Info("✅ Notification worker stopped")
|
|
}()
|
|
|
|
wg.Wait()
|
|
close(workersDone)
|
|
}()
|
|
|
|
// ✅ Wait for workers or timeout
|
|
select {
|
|
case <-workersDone:
|
|
logger.Info("✅ All background workers stopped gracefully")
|
|
case <-shutdownCtx.Done():
|
|
logger.Warn("⚠️ Worker shutdown timeout exceeded, forcing exit")
|
|
}
|
|
|
|
logger.Info("✅ Graceful shutdown completed")
|
|
logger.Info("👋 Goodbye!")
|
|
} |