Files
chat_room/cmd/api/main.go
T

142 lines
5.8 KiB
Go
Raw Normal View History

package main
import (
"context"
"errors"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/gin-gonic/gin"
"dating-app/internal/config"
"dating-app/internal/database"
"dating-app/internal/handler"
"dating-app/internal/middleware"
"dating-app/internal/monitoring"
"dating-app/internal/repository"
"dating-app/internal/router"
"dating-app/internal/service"
"dating-app/internal/utils"
)
func main() {
cfg := config.Load()
db, err := database.Connect(cfg)
if err != nil {
log.Fatalf("اتصال به دیتابیس ناموفق بود: %v", err)
}
if err := database.AutoMigrate(db); err != nil {
log.Fatalf("migrate ناموفق بود: %v", err)
}
if err := database.SeedCategories(db); err != nil {
log.Fatalf("seed کردن دسته‌ها ناموفق بود: %v", err)
}
if err := database.BootstrapAdmin(db, cfg.AdminBootstrapPhone); err != nil {
log.Fatalf("ساخت ادمین اولیه ناموفق بود: %v", err)
}
// -------- Repositories --------
userRepo := repository.NewUserRepository(db)
optionRepo := repository.NewOptionRepository(db)
questionRepo := repository.NewQuestionRepository(db)
photoRepo := repository.NewPhotoRepository(db)
swipeRepo := repository.NewSwipeRepository(db)
matchRepo := repository.NewMatchRepository(db)
messageRepo := repository.NewMessageRepository(db)
reportRepo := repository.NewReportRepository(db)
// -------- Utils / Infra --------
jwtManager := utils.NewJWTManager(
cfg.JWTAccessSecret,
cfg.JWTRefreshSecret,
cfg.JWTAccessTTL,
cfg.JWTRefreshTTL,
)
// برای اتصال به یک گیت‌وی پیامک واقعی، این خط رو با پیاده‌سازی خودت
// (که service.OTPSender رو implement می‌کنه) عوض کن.
otpSender := service.NewMockOTPSender(cfg.AppEnv != "production")
// برای اتصال به یک object storage واقعی، این خط رو با پیاده‌سازی خودت
// (که service.PhotoStorage رو implement می‌کنه) عوض کن.
photoStorage := service.NewLocalPhotoStorage("./uploads/photos", "/uploads/photos")
// -------- Services --------
authService := service.NewAuthService(
userRepo, otpSender, jwtManager,
cfg.OTPExpirySeconds, cfg.OTPLength, cfg.OTPHashSecret,
)
2026-09-16 09:21:50 +03:30
onboardingService := service.NewOnboardingService(userRepo, optionRepo, questionRepo, photoRepo)
profileService := service.NewProfileService(photoRepo, userRepo, photoStorage)
adminService := service.NewAdminService(optionRepo, questionRepo, userRepo, reportRepo, matchRepo, photoRepo, profileService)
discoveryService := service.NewDiscoveryService(userRepo, photoRepo, swipeRepo, matchRepo, optionRepo, questionRepo)
chatService := service.NewChatService(matchRepo, messageRepo, userRepo, photoRepo)
backupService := service.NewBackupService(cfg, "./backups")
// -------- مانیتورینگ --------
metricsStore := monitoring.NewStore()
// -------- Handlers --------
handlers := router.Handlers{
Auth: handler.NewAuthHandler(authService),
Onboarding: handler.NewOnboardingHandler(onboardingService),
Profile: handler.NewProfileHandler(optionRepo, questionRepo, photoRepo, onboardingService, profileService),
Admin: handler.NewAdminHandler(adminService, optionRepo, questionRepo, photoRepo, onboardingService),
Discovery: handler.NewDiscoveryHandler(discoveryService),
Chat: handler.NewChatHandler(chatService),
Monitoring: handler.NewMonitoringHandler(metricsStore),
Backup: handler.NewBackupHandler(backupService),
}
if cfg.AppEnv == "production" {
gin.SetMode(gin.ReleaseMode)
}
// به‌جای gin.Default() (که فقط Logger+Recovery پیش‌فرض رو می‌ده)، دستی
// می‌سازیم تا هم Metrics رو اضافه کنیم و هم Recovery خودمون که envelope
// استاندارد JSON برمی‌گردونه. ترتیب مهمه: Metrics باید بیرونی‌ترین لایه
// باشه تا حتی درخواست‌هایی که پنیک می‌کنن (و توسط Recovery مهار می‌شن) هم
// توی آمار لحاظ بشن.
engine := gin.New()
engine.Use(middleware.Metrics(metricsStore))
engine.Use(middleware.Recovery())
engine.Use(gin.Logger())
router.Setup(engine, handlers, jwtManager, userRepo)
// یک http.Server دستی به‌جای engine.Run() ساده، به دو دلیل:
// ۱. تایم‌اوت‌های صریح روی خوندن/نوشتن، تا یک کلاینت کند یا مخرب (slowloris)
// نتونه goroutine/فایل‌دسکریپتورهای سرور رو زیر بار بالا قفل نگه داره.
// ۲. Shutdown مهلت‌دار، تا موقع دیپلوی/ری‌استارت، درخواست‌های در حال پردازش
// ناتمام قطع نشن (به‌جای کشتن فوری پروسه).
srv := &http.Server{
Addr: ":" + cfg.AppPort,
Handler: engine,
ReadTimeout: 30 * time.Second,
ReadHeaderTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 90 * time.Second,
}
go func() {
log.Printf("سرور روی پورت %s در حال اجراست (env=%s)", cfg.AppPort, cfg.AppEnv)
if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
log.Fatalf("اجرای سرور ناموفق بود: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("سیگنال خاموشی دریافت شد؛ منتظر تمام‌شدن درخواست‌های در حال پردازش...")
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("خاموش کردن سرور با مشکل مواجه شد: %v", err)
}
log.Println("سرور با موفقیت خاموش شد")
}