Initial commit - Lost and Found Revisi
This commit is contained in:
@@ -0,0 +1,243 @@
|
||||
// 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!")
|
||||
}
|
||||
Reference in New Issue
Block a user