main.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. package main
  2. import (
  3. "context"
  4. "log/slog"
  5. "net/http"
  6. "os"
  7. "os/signal"
  8. "syscall"
  9. "time"
  10. "github.com/go-chi/chi/v5"
  11. chimiddleware "github.com/go-chi/chi/v5/middleware"
  12. "github.com/go-chi/cors"
  13. "github.com/redis/go-redis/v9"
  14. "github.com/photoplaces/backend/internal/config"
  15. "github.com/photoplaces/backend/internal/handlers"
  16. "github.com/photoplaces/backend/internal/log"
  17. "github.com/photoplaces/backend/internal/middleware"
  18. "github.com/photoplaces/backend/internal/repository"
  19. "github.com/photoplaces/backend/internal/services"
  20. )
  21. func main() {
  22. cfg := config.Load()
  23. ctx := context.Background()
  24. // Инициализация структурированного логгера
  25. logger := log.Init(cfg.AppEnv)
  26. ctx = log.WithContext(ctx, logger)
  27. logger.InfoContext(ctx, "starting application",
  28. slog.String("env", cfg.AppEnv),
  29. slog.String("port", cfg.ServerPort),
  30. )
  31. // Подключение к базе данных
  32. pool, err := repository.NewPool(ctx, cfg.DatabaseURL)
  33. if err != nil {
  34. logger.ErrorContext(ctx, "database connection failed", log.WithError(err))
  35. os.Exit(1)
  36. }
  37. defer pool.Close()
  38. logger.InfoContext(ctx, "database connected")
  39. // Репозитории
  40. userRepo := repository.NewUserRepo(pool)
  41. refreshTokenRepo := repository.NewRefreshTokenRepo(pool)
  42. placeRepo := repository.NewPlaceRepo(pool)
  43. serviceRepo := repository.NewServiceRepo(pool)
  44. reviewRepo := repository.NewReviewRepo(pool)
  45. bookingRepo := repository.NewBookingRepo(pool)
  46. tagRepo := repository.NewTagRepo(pool)
  47. featureRepo := repository.NewFeatureRepo(pool)
  48. // Сервисы
  49. authSvc := services.NewAuthService(userRepo, refreshTokenRepo, cfg.JWTSecret, cfg.JWTRefreshSecret)
  50. placeSvc := services.NewPlaceService(placeRepo)
  51. // Хендлеры
  52. authHandler := handlers.NewAuthHandler(authSvc, cfg.AppEnv)
  53. userHandler := handlers.NewUserHandler(userRepo)
  54. placeHandler := handlers.NewPlaceHandler(placeSvc)
  55. serviceHandler := handlers.NewServiceHandler(serviceRepo, tagRepo)
  56. reviewHandler := handlers.NewReviewHandler(reviewRepo)
  57. bookingHandler := handlers.NewBookingHandler(bookingRepo, placeRepo)
  58. tagHandler := handlers.NewTagHandler(tagRepo, featureRepo)
  59. wsHub := handlers.NewWSHub()
  60. uploadHandler, err := handlers.NewUploadHandler(cfg.S3Endpoint, cfg.S3PublicEndpoint, cfg.S3AccessKey, cfg.S3SecretKey, cfg.S3Bucket, false)
  61. if err != nil {
  62. logger.ErrorContext(ctx, "upload handler init failed", log.WithError(err))
  63. os.Exit(1)
  64. }
  65. // Redis для rate limiting
  66. redisOpts, err := redis.ParseURL(cfg.RedisURL)
  67. if err != nil {
  68. logger.ErrorContext(ctx, "parse redis url failed", log.WithError(err))
  69. os.Exit(1)
  70. }
  71. redisClient := redis.NewClient(redisOpts)
  72. defer redisClient.Close()
  73. if err := redisClient.Ping(ctx).Err(); err != nil {
  74. logger.WarnContext(ctx, "redis connection failed, rate limiting disabled", log.WithError(err))
  75. } else {
  76. logger.InfoContext(ctx, "redis connected")
  77. }
  78. authLimiter, _ := middleware.RateLimitAuthEndpoints(redisClient)
  79. apiReadLimiter, _ := middleware.RateLimitAPIRead(redisClient)
  80. apiWriteLimiter, _ := middleware.RateLimitAPIWrite(redisClient)
  81. adminLimiter, _ := middleware.RateLimitAdmin(redisClient)
  82. // Роутер
  83. r := chi.NewRouter()
  84. // Middleware для логирования запросов
  85. r.Use(requestLoggerMiddleware(logger))
  86. r.Use(chimiddleware.Recoverer)
  87. r.Use(chimiddleware.RequestID)
  88. r.Use(cors.Handler(cors.Options{
  89. AllowedOrigins: cfg.AllowedOrigins,
  90. AllowedMethods: []string{"GET", "POST", "PATCH", "DELETE", "OPTIONS"},
  91. AllowedHeaders: []string{"Accept", "Authorization", "Content-Type"},
  92. AllowCredentials: true,
  93. MaxAge: 300,
  94. }))
  95. r.Route("/api/v1", func(r chi.Router) {
  96. r.Get("/health", func(w http.ResponseWriter, r *http.Request) {
  97. w.Write([]byte(`{"status":"ok"}`))
  98. })
  99. // Public auth - strict rate limiting
  100. r.Group(func(r chi.Router) {
  101. if authLimiter != nil {
  102. r.Use(authLimiter.Middleware())
  103. }
  104. r.Post("/auth/register", authHandler.Register)
  105. r.Post("/auth/login", authHandler.Login)
  106. r.Post("/auth/refresh", authHandler.Refresh)
  107. })
  108. // Public read endpoints - moderate rate limiting
  109. r.Group(func(r chi.Router) {
  110. if apiReadLimiter != nil {
  111. r.Use(apiReadLimiter.Middleware())
  112. }
  113. r.Get("/reviews", reviewHandler.List)
  114. r.Get("/tags", tagHandler.ListTags)
  115. r.Get("/features", tagHandler.ListFeatures)
  116. r.Get("/services", serviceHandler.List)
  117. r.Get("/services/{id}", serviceHandler.GetByID)
  118. r.Get("/places", placeHandler.List)
  119. r.Get("/places/{id}", placeHandler.GetByID)
  120. r.Get("/users/{id}", userHandler.GetProfile)
  121. })
  122. // WebSocket visitor dots
  123. r.Get("/ws/visitors", wsHub.HandleWS)
  124. // Authenticated write endpoints - stricter rate limiting
  125. r.Group(func(r chi.Router) {
  126. r.Use(middleware.AuthMiddleware(authSvc))
  127. if apiWriteLimiter != nil {
  128. r.Use(apiWriteLimiter.Middleware())
  129. }
  130. r.Get("/auth/me", authHandler.Me)
  131. r.Post("/auth/logout", authHandler.Logout)
  132. // Users
  133. r.Patch("/users/me", userHandler.UpdateMe)
  134. // Upload
  135. r.Post("/upload/presigned-url", uploadHandler.PresignedURL)
  136. // Bookings
  137. r.Post("/bookings", bookingHandler.Create)
  138. r.Get("/bookings/me", bookingHandler.ListMy)
  139. r.Patch("/bookings/{id}/cancel", bookingHandler.Cancel)
  140. // Reviews
  141. r.Post("/reviews", reviewHandler.Create)
  142. // Services
  143. r.Post("/services", serviceHandler.Create)
  144. r.Patch("/services/{id}", serviceHandler.Update)
  145. r.Delete("/services/{id}", serviceHandler.Delete)
  146. // Place creation
  147. r.Post("/places", placeHandler.Create)
  148. r.Patch("/places/{id}", placeHandler.Update)
  149. r.Delete("/places/{id}", placeHandler.Delete)
  150. })
  151. // Moderator / Admin - higher limits
  152. r.Group(func(r chi.Router) {
  153. r.Use(middleware.AuthMiddleware(authSvc))
  154. r.Use(middleware.RoleMiddleware("moderator", "superadmin"))
  155. if adminLimiter != nil {
  156. r.Use(adminLimiter.Middleware())
  157. }
  158. r.Post("/places/{id}/moderate", placeHandler.Moderate)
  159. r.Get("/admin/users", userHandler.AdminListUsers)
  160. r.Patch("/admin/users/{id}", userHandler.AdminUpdateUser)
  161. r.Patch("/admin/bookings/{id}/confirm", bookingHandler.Confirm)
  162. r.Post("/admin/tags", tagHandler.CreateTag)
  163. r.Delete("/admin/tags", tagHandler.DeleteTag)
  164. r.Post("/admin/features", tagHandler.CreateFeature)
  165. r.Delete("/admin/features", tagHandler.DeleteFeature)
  166. })
  167. })
  168. // HTTP сервер с graceful shutdown
  169. server := &http.Server{
  170. Addr: ":" + cfg.ServerPort,
  171. Handler: r,
  172. ReadTimeout: 15 * time.Second,
  173. WriteTimeout: 15 * time.Second,
  174. IdleTimeout: 60 * time.Second,
  175. }
  176. // Запуск сервера в горутине
  177. go func() {
  178. logger.InfoContext(ctx, "starting http server", slog.String("addr", server.Addr))
  179. if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
  180. logger.ErrorContext(ctx, "http server error", log.WithError(err))
  181. os.Exit(1)
  182. }
  183. }()
  184. // Ожидание сигнала завершения
  185. quit := make(chan os.Signal, 1)
  186. signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
  187. <-quit
  188. logger.InfoContext(ctx, "shutdown signal received, gracefully stopping...")
  189. // Graceful shutdown с таймаутом
  190. shutdownCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
  191. defer cancel()
  192. if err := server.Shutdown(shutdownCtx); err != nil {
  193. logger.ErrorContext(ctx, "server shutdown failed", log.WithError(err))
  194. os.Exit(1)
  195. }
  196. logger.InfoContext(ctx, "server stopped gracefully")
  197. }
  198. // requestLoggerMiddleware логирует входящие HTTP запросы
  199. func requestLoggerMiddleware(logger *slog.Logger) func(http.Handler) http.Handler {
  200. return func(next http.Handler) http.Handler {
  201. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  202. start := time.Now()
  203. ww := chimiddleware.NewWrapResponseWriter(w, r.ProtoMajor)
  204. requestID := chimiddleware.GetReqID(r.Context())
  205. ctx := log.WithContext(r.Context(), logger.With("request_id", requestID))
  206. logger.InfoContext(ctx, "request started",
  207. slog.String("method", r.Method),
  208. slog.String("path", r.URL.Path),
  209. slog.String("remote_addr", r.RemoteAddr),
  210. slog.String("user_agent", r.UserAgent()),
  211. )
  212. next.ServeHTTP(ww, r.WithContext(ctx))
  213. logger.InfoContext(ctx, "request completed",
  214. slog.Int("status", ww.Status()),
  215. slog.Int("bytes_written", ww.BytesWritten()),
  216. slog.Duration("duration", time.Since(start)),
  217. )
  218. })
  219. }
  220. }