package main import ( "context" "log" "photum-backend/docs" "photum-backend/internal/agenda" "photum-backend/internal/anos_formaturas" "photum-backend/internal/auth" "photum-backend/internal/availability" "photum-backend/internal/cadastro_fot" "photum-backend/internal/codigos" "photum-backend/internal/config" "photum-backend/internal/cursos" "photum-backend/internal/db" "photum-backend/internal/empresas" "photum-backend/internal/escalas" "photum-backend/internal/finance" "photum-backend/internal/funcoes" "photum-backend/internal/notification" "photum-backend/internal/logistica" "photum-backend/internal/profissionais" "photum-backend/internal/storage" "photum-backend/internal/tipos_eventos" "photum-backend/internal/tipos_servicos" "strings" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" // "photum-backend/docs" is already imported above ) // @title Photum Backend API // @version 1.0 // @description Backend authentication service for Photum. // @termsOfService http://swagger.io/terms/ // @contact.name API Support // @contact.url http://www.swagger.io/support // @contact.email support@swagger.io // @license.name Apache 2.0 // @license.url http://www.apache.org/licenses/LICENSE-2.0.html // @host localhost:8080 // @BasePath / // @tag.name auth // @tag.description Authentication related operations // @tag.name admin // @tag.description Administration operations // @securityDefinitions.apikey BearerAuth // @in header // @name Authorization func main() { cfg := config.LoadConfig() log.Printf("Loaded DSN: %s", cfg.DBDsn) queries, pool := db.Connect(cfg) defer pool.Close() // Run Migrations db.Migrate(pool) // Initialize services // Initialize services notificationService := notification.NewService() profissionaisService := profissionais.NewService(queries) authService := auth.NewService(queries, profissionaisService, cfg) funcoesService := funcoes.NewService(queries) cursosService := cursos.NewService(queries) empresasService := empresas.NewService(queries) anosFormaturasService := anos_formaturas.NewService(queries) tiposServicosService := tipos_servicos.NewService(queries) tiposEventosService := tipos_eventos.NewService(queries) cadastroFotService := cadastro_fot.NewService(queries) agendaService := agenda.NewService(queries, notificationService, cfg) availabilityService := availability.NewService(queries) s3Service := storage.NewS3Service(cfg) // Seed Demo Users if err := authService.EnsureDemoUsers(context.Background()); err != nil { log.Printf("Failed to seed demo users: %v", err) } // Initialize handlers authHandler := auth.NewHandler(authService, s3Service) profissionaisHandler := profissionais.NewHandler(profissionaisService) funcoesHandler := funcoes.NewHandler(funcoesService) cursosHandler := cursos.NewHandler(cursosService) empresasHandler := empresas.NewHandler(empresasService) anosFormaturasHandler := anos_formaturas.NewHandler(anosFormaturasService) tiposServicosHandler := tipos_servicos.NewHandler(tiposServicosService) tiposEventosHandler := tipos_eventos.NewHandler(tiposEventosService) cadastroFotHandler := cadastro_fot.NewHandler(cadastroFotService) agendaHandler := agenda.NewHandler(agendaService) availabilityHandler := availability.NewHandler(availabilityService) escalasHandler := escalas.NewHandler(escalas.NewService(queries)) logisticaHandler := logistica.NewHandler(logistica.NewService(queries, notificationService, cfg)) codigosHandler := codigos.NewHandler(codigos.NewService(queries)) financeHandler := finance.NewHandler(finance.NewService(queries, profissionaisService)) r := gin.Default() // CORS Middleware configCors := cors.DefaultConfig() if cfg.CorsAllowedOrigins == "*" { configCors.AllowAllOrigins = true } else { configCors.AllowOrigins = strings.Split(cfg.CorsAllowedOrigins, ",") } configCors.AllowHeaders = []string{"Origin", "Content-Length", "Content-Type", "Authorization", "x-regiao"} r.Use(cors.New(configCors)) // Swagger // Dynamically update Swagger Info docs.SwaggerInfo.Host = cfg.SwaggerHost if cfg.AppEnv == "production" { docs.SwaggerInfo.Schemes = []string{"https", "http"} } else { docs.SwaggerInfo.Schemes = []string{"http", "https"} } // Swagger UI - usando URL relativa para funcionar em qualquer ambiente r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.PersistAuthorization(true), ginSwagger.DeepLinking(true), ginSwagger.URL("doc.json"), )) // Public Routes authGroup := r.Group("/auth") { authGroup.POST("/register", authHandler.Register) authGroup.POST("/login", authHandler.Login) authGroup.POST("/refresh", authHandler.Refresh) authGroup.POST("/logout", authHandler.Logout) authGroup.POST("/upload-url", authHandler.GetUploadURL) } // Public API Routes (Data Lists) - Need Region Context // Simple middleware to extract x-regiao header for public routes publicWithRegion := r.Group("/api") publicWithRegion.Use(func(c *gin.Context) { regiao := c.GetHeader("x-regiao") if regiao == "" { regiao = "SP" // Default to SP for public access if not specified } c.Set("regiao", regiao) c.Next() }) { publicWithRegion.GET("/funcoes", funcoesHandler.List) publicWithRegion.GET("/cursos", cursosHandler.List) publicWithRegion.GET("/empresas", empresasHandler.List) publicWithRegion.GET("/anos-formaturas", anosFormaturasHandler.List) publicWithRegion.GET("/profissionais/check", profissionaisHandler.CheckCPF) // Public Check publicWithRegion.GET("/tipos-servicos", tiposServicosHandler.List) publicWithRegion.GET("/tipos-eventos", tiposEventosHandler.List) publicWithRegion.GET("/tipos-eventos/:id/precos", tiposEventosHandler.ListPrices) publicWithRegion.GET("/public/codigos-acesso/verificar", codigosHandler.Verify) // already has explicit handler but consistent context helps } // Protected Routes api := r.Group("/api") api.Use(auth.AuthMiddleware(cfg)) { api.GET("/me", authHandler.Me) profGroup := api.Group("/profissionais") { profGroup.POST("", profissionaisHandler.Create) profGroup.POST("/import", profissionaisHandler.Import) profGroup.GET("", profissionaisHandler.List) profGroup.GET("/me", profissionaisHandler.Me) profGroup.GET("/:id", profissionaisHandler.Get) profGroup.PUT("/:id", profissionaisHandler.Update) profGroup.DELETE("/:id", profissionaisHandler.Delete) // Rota de extrato financeiro (usando agendaHandler por conveniência de serviço) profGroup.GET("/me/financial-statement", agendaHandler.GetProfessionalFinancialStatement) } funcoesGroup := api.Group("/funcoes") { funcoesGroup.POST("", funcoesHandler.Create) funcoesGroup.PUT("/:id", funcoesHandler.Update) funcoesGroup.DELETE("/:id", funcoesHandler.Delete) } // protected CRUD (create/update/delete) api.POST("/cursos", cursosHandler.Create) api.PUT("/cursos/:id", cursosHandler.Update) api.DELETE("/cursos/:id", cursosHandler.Delete) api.POST("/empresas", empresasHandler.Create) api.PUT("/empresas/:id", empresasHandler.Update) api.DELETE("/empresas/:id", empresasHandler.Delete) api.POST("/anos-formaturas", anosFormaturasHandler.Create) api.PUT("/anos-formaturas/:id", anosFormaturasHandler.Update) api.DELETE("/anos-formaturas/:id", anosFormaturasHandler.Delete) api.POST("/tipos-servicos", tiposServicosHandler.Create) api.PUT("/tipos-servicos/:id", tiposServicosHandler.Update) api.DELETE("/tipos-servicos/:id", tiposServicosHandler.Delete) api.POST("/tipos-eventos", tiposEventosHandler.Create) api.PUT("/tipos-eventos/:id", tiposEventosHandler.Update) api.DELETE("/tipos-eventos/:id", tiposEventosHandler.Delete) api.POST("/tipos-eventos/precos", tiposEventosHandler.SetPrice) api.GET("/cadastro-fot", cadastroFotHandler.List) api.POST("/cadastro-fot", cadastroFotHandler.Create) api.GET("/cadastro-fot/:id", cadastroFotHandler.Get) api.PUT("/cadastro-fot/:id", cadastroFotHandler.Update) api.DELETE("/cadastro-fot/:id", cadastroFotHandler.Delete) api.POST("/import/fot", cadastroFotHandler.Import) // Agenda routes - read access for AGENDA_VIEWER api.GET("/agenda", agendaHandler.List) api.GET("/agenda/:id", agendaHandler.Get) api.GET("/agenda/:id/professionals", agendaHandler.GetProfessionals) api.GET("/agenda/:id/available", agendaHandler.ListAvailableProfessionals) // Agenda routes - write access (blocked for AGENDA_VIEWER) api.POST("/agenda", auth.RequireWriteAccess(), agendaHandler.Create) api.PUT("/agenda/:id", auth.RequireWriteAccess(), agendaHandler.Update) api.DELETE("/agenda/:id", auth.RequireWriteAccess(), agendaHandler.Delete) api.POST("/agenda/:id/professionals", auth.RequireWriteAccess(), agendaHandler.AssignProfessional) api.DELETE("/agenda/:id/professionals/:profId", auth.RequireWriteAccess(), agendaHandler.RemoveProfessional) api.PATCH("/agenda/:id/professionals/:profId/status", auth.RequireWriteAccess(), agendaHandler.UpdateAssignmentStatus) api.PATCH("/agenda/:id/professionals/:profId/position", auth.RequireWriteAccess(), agendaHandler.UpdateAssignmentPosition) api.PATCH("/agenda/:id/status", auth.RequireWriteAccess(), agendaHandler.UpdateStatus) api.POST("/agenda/:id/notify-logistics", auth.RequireWriteAccess(), agendaHandler.NotifyLogistics) api.POST("/import/agenda", auth.RequireWriteAccess(), agendaHandler.Import) api.POST("/availability", availabilityHandler.SetAvailability) api.GET("/availability", availabilityHandler.ListAvailability) // Escalas Routes api.POST("/escalas", escalasHandler.Create) api.GET("/escalas", escalasHandler.ListByAgenda) api.DELETE("/escalas/:id", escalasHandler.Delete) api.PUT("/escalas/:id", escalasHandler.Update) // Logistics Routes - blocked for AGENDA_VIEWER logisticaGroup := api.Group("/logistica", auth.RequireLogisticsAccess()) { logisticaGroup.POST("/carros", logisticaHandler.CreateCarro) logisticaGroup.GET("/carros", logisticaHandler.ListCarros) logisticaGroup.DELETE("/carros/:id", logisticaHandler.DeleteCarro) logisticaGroup.PUT("/carros/:id", logisticaHandler.UpdateCarro) logisticaGroup.POST("/carros/:id/passageiros", logisticaHandler.AddPassenger) logisticaGroup.DELETE("/carros/:id/passageiros/:profID", logisticaHandler.RemovePassenger) logisticaGroup.GET("/carros/:id/passageiros", logisticaHandler.ListPassengers) } codigosGroup := api.Group("/codigos-acesso") { codigosGroup.POST("", codigosHandler.Create) codigosGroup.GET("", codigosHandler.List) codigosGroup.DELETE("/:id", codigosHandler.Delete) } financeGroup := api.Group("/finance") { financeGroup.POST("", financeHandler.Create) financeGroup.POST("/import", financeHandler.Import) financeGroup.GET("", financeHandler.List) financeGroup.GET("/autofill", financeHandler.AutoFill) financeGroup.GET("/fot-events", financeHandler.GetFotEvents) financeGroup.GET("/fot-search", financeHandler.SearchFot) financeGroup.GET("/professionals", financeHandler.SearchProfessionals) financeGroup.GET("/price", financeHandler.GetPrice) financeGroup.PUT("/:id", financeHandler.Update) financeGroup.DELETE("/:id", financeHandler.Delete) } admin := api.Group("/admin") { admin.GET("/users", authHandler.ListUsers) admin.GET("/users/pending", authHandler.ListPending) admin.GET("/users/:id", authHandler.GetUser) admin.PATCH("/users/:id/approve", authHandler.Approve) admin.POST("/users", authHandler.AdminCreateUser) admin.PATCH("/users/:id/role", authHandler.UpdateRole) admin.DELETE("/users/:id", authHandler.DeleteUser) } } log.Printf("Swagger Host Configured: %s", cfg.SwaggerHost) log.Printf("Server running on port %s", cfg.AppPort) r.Run(":" + cfg.AppPort) }