Basdat/cmd/server/main.go
2025-12-20 00:01:08 +07:00

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!")
}