package main import ( "context" "log" "net/http" "github.com/go-chi/chi/v5" chimiddleware "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" "golang.org/x/time/rate" "github.com/photoplaces/backend/internal/config" "github.com/photoplaces/backend/internal/handlers" "github.com/photoplaces/backend/internal/middleware" "github.com/photoplaces/backend/internal/repository" "github.com/photoplaces/backend/internal/services" ) func main() { cfg := config.Load() ctx := context.Background() pool, err := repository.NewPool(ctx, cfg.DatabaseURL) if err != nil { log.Fatalf("database connection: %v", err) } defer pool.Close() userRepo := repository.NewUserRepo(pool) placeRepo := repository.NewPlaceRepo(pool) serviceRepo := repository.NewServiceRepo(pool) reviewRepo := repository.NewReviewRepo(pool) bookingRepo := repository.NewBookingRepo(pool) tagRepo := repository.NewTagRepo(pool) featureRepo := repository.NewFeatureRepo(pool) authSvc := services.NewAuthService(userRepo, cfg.JWTSecret, cfg.JWTRefreshSecret) placeSvc := services.NewPlaceService(placeRepo) authHandler := handlers.NewAuthHandler(authSvc) userHandler := handlers.NewUserHandler(userRepo) placeHandler := handlers.NewPlaceHandler(placeSvc) serviceHandler := handlers.NewServiceHandler(serviceRepo, tagRepo) reviewHandler := handlers.NewReviewHandler(reviewRepo) bookingHandler := handlers.NewBookingHandler(bookingRepo, placeRepo) tagHandler := handlers.NewTagHandler(tagRepo, featureRepo) wsHub := handlers.NewWSHub() uploadHandler, err := handlers.NewUploadHandler(cfg.S3Endpoint, cfg.S3AccessKey, cfg.S3SecretKey, cfg.S3Bucket, false) if err != nil { log.Fatalf("upload handler: %v", err) } rl := middleware.NewRateLimiter(rate.Limit(60), 60) r := chi.NewRouter() r.Use(chimiddleware.Logger) r.Use(chimiddleware.Recoverer) r.Use(chimiddleware.RequestID) r.Use(cors.Handler(cors.Options{ AllowedOrigins: cfg.AllowedOrigins, AllowedMethods: []string{"GET", "POST", "PATCH", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-Refresh-Token"}, AllowCredentials: true, MaxAge: 300, })) r.Use(rl.Middleware()) r.Route("/api/v1", func(r chi.Router) { r.Get("/health", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"status":"ok"}`)) }) // Public auth r.Post("/auth/register", authHandler.Register) r.Post("/auth/login", authHandler.Login) r.Post("/auth/refresh", authHandler.Refresh) // Public reviews (read) r.Get("/reviews", reviewHandler.List) // Public tags & features r.Get("/tags", tagHandler.ListTags) r.Get("/features", tagHandler.ListFeatures) // Public services (read) r.Get("/services", serviceHandler.List) r.Get("/services/{id}", serviceHandler.GetByID) // Public places (read) r.Get("/places", placeHandler.List) r.Get("/places/{id}", placeHandler.GetByID) // WebSocket visitor dots r.Get("/ws/visitors", wsHub.HandleWS) // Authenticated r.Group(func(r chi.Router) { r.Use(middleware.AuthMiddleware(authSvc)) r.Get("/auth/me", authHandler.Me) // Users r.Patch("/users/me", userHandler.UpdateMe) // Upload r.Post("/upload/presigned-url", uploadHandler.PresignedURL) // Bookings r.Post("/bookings", bookingHandler.Create) r.Get("/bookings/me", bookingHandler.ListMy) r.Patch("/bookings/{id}/cancel", bookingHandler.Cancel) // Reviews r.Post("/reviews", reviewHandler.Create) // Services r.Post("/services", serviceHandler.Create) r.Patch("/services/{id}", serviceHandler.Update) r.Delete("/services/{id}", serviceHandler.Delete) // Place creation r.Post("/places", placeHandler.Create) r.Patch("/places/{id}", placeHandler.Update) r.Delete("/places/{id}", placeHandler.Delete) }) // Public user profile r.Get("/users/{id}", userHandler.GetProfile) // Moderator / Admin r.Group(func(r chi.Router) { r.Use(middleware.AuthMiddleware(authSvc)) r.Use(middleware.RoleMiddleware("moderator", "superadmin")) r.Post("/places/{id}/moderate", placeHandler.Moderate) r.Get("/admin/users", userHandler.AdminListUsers) r.Patch("/admin/users/{id}", userHandler.AdminUpdateUser) r.Patch("/admin/bookings/{id}/confirm", bookingHandler.Confirm) r.Post("/admin/tags", tagHandler.CreateTag) r.Delete("/admin/tags", tagHandler.DeleteTag) r.Post("/admin/features", tagHandler.CreateFeature) r.Delete("/admin/features", tagHandler.DeleteFeature) }) }) port := cfg.ServerPort log.Printf("Starting server on :%s", port) if err := http.ListenAndServe(":"+port, r); err != nil { log.Fatal(err) } }