package router import ( "tm/internal/assets" "tm/internal/cms" "tm/internal/company" "tm/internal/company_category" "tm/internal/contact" "tm/internal/customer" "tm/internal/dashboard" "tm/internal/document_scraper" "tm/internal/feedback" "tm/internal/inquiry" "tm/internal/kanban" "tm/internal/notification" "tm/internal/tender" "tm/internal/tender_approval" "tm/internal/user" "tm/pkg/filestore" "tm/pkg/security" "github.com/labstack/echo/v4" ) // SetupCSPSecurity registers global CSP middleware and the CSP report endpoint once. func SetupCSPSecurity(e *echo.Echo) { adminCSPConfig := security.DefaultCSPConfig() adminCSPConfig.ScriptSrc = []string{"'self'"} // No unsafe-inline for admin adminCSPConfig.StyleSrc = []string{"'self'"} // No unsafe-inline for admin publicCSPConfig := security.DefaultCSPConfig() e.Use(security.CSPMiddlewareForPaths("/admin", adminCSPConfig, "/api/v1", publicCSPConfig)) e.POST("/api/v1/csp-report", security.CSPReportHandler) } func RegisterAdminRoutes(e *echo.Echo, userHandler *user.Handler, companyHandler *company.Handler, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler, inquiryHandler *inquiry.Handler, categoryHandler *company_category.Handler, flagHandler *assets.Handler, notificationHandler *notification.NotificationHandler, contactHandler *contact.Handler, cmsHandler *cms.Handler, kanbanHandler *kanban.Handler, fileStoreHandler *filestore.Handler, dashboardHandler *dashboard.Handler, xssPolicy *security.XSSPolicy) { adminV1 := e.Group("/admin/v1") // Admin Users Routes adminUsersGP := adminV1.Group("/users") { adminUsersGP.Use(userHandler.AuthMiddleware()) adminUsersGP.POST("", userHandler.CreateUser) adminUsersGP.GET("", userHandler.ListUsers) adminUsersGP.GET("/:id", userHandler.GetUserByID) adminUsersGP.PUT("/:id", userHandler.UpdateUser) adminUsersGP.DELETE("/:id", userHandler.DeleteUser) adminUsersGP.PUT("/:id/status", userHandler.UpdateUserStatus) adminUsersGP.POST("/:id/reset-password", userHandler.ResetUserPassword, userHandler.AdminMiddleware()) } // Admin user profile profileGP := adminV1.Group("/profile") { userAuthorizationGP := profileGP.Group("") userAuthorizationGP.POST("/login", userHandler.Login) userAuthorizationGP.POST("/refresh-token", userHandler.RefreshToken) userAuthorizationGP.POST("/reset-password", userHandler.ResetPassword) userProfileGP := profileGP.Group("") userProfileGP.Use(userHandler.AuthMiddleware()) userProfileGP.GET("", userHandler.GetProfile) userProfileGP.PUT("", userHandler.UpdateProfile) userProfileGP.PUT("/change-password", userHandler.ChangePassword) userProfileGP.DELETE("/logout", userHandler.Logout) } // Admin Companies Routes companiesGP := adminV1.Group("/companies") { companiesGP.Use(userHandler.AuthMiddleware()) companiesGP.POST("", companyHandler.Create) companiesGP.GET("", companyHandler.Search) companiesGP.GET("/:id", companyHandler.Get) companiesGP.PUT("/:id", companyHandler.Update) companiesGP.DELETE("/:id", companyHandler.Delete) companiesGP.PATCH("/:id/status", companyHandler.UpdateStatus) companiesGP.PATCH("/:id/verification", companyHandler.UpdateVerification) companiesGP.POST("/:id/documents", companyHandler.UploadDocuments) } // Admin Categories Routes categoriesGP := adminV1.Group("/company-categories") { categoriesGP.Use(userHandler.AuthMiddleware()) categoriesGP.POST("", categoryHandler.Create) categoriesGP.GET("", categoryHandler.Search) categoriesGP.GET("/:id", categoryHandler.Get) categoriesGP.PUT("/:id", categoryHandler.Update) categoriesGP.DELETE("/:id", categoryHandler.Delete) categoriesGP.PATCH("/:id/publish", categoryHandler.TogglePublish) } // Admin Customers Routes customersGP := adminV1.Group("/customers") { customersGP.Use(userHandler.AuthMiddleware()) customersGP.POST("", customerHandler.CreateCustomer) customersGP.GET("", customerHandler.Search) customersGP.GET("/:id", customerHandler.GetCustomerByID) customersGP.PUT("/:id", customerHandler.UpdateCustomer) customersGP.DELETE("/:id", customerHandler.DeleteCustomer) customersGP.PUT("/:id/status", customerHandler.UpdateStatus) customersGP.POST("/:id/reset-password", customerHandler.ResetCustomerPassword) customersGP.PATCH("/:id/role", customerHandler.AssignRole) customersGP.PATCH("/:id/companies", customerHandler.AssignCompanies) } // Admin Tenders Routes tendersGP := adminV1.Group("/tenders") { tendersGP.Use(userHandler.AuthMiddleware()) tendersGP.GET("", tenderHandler.Search) tendersGP.GET("/ai-summaries", tenderHandler.GetAllAISummaries) tendersGP.GET("/scraped-documents", tenderHandler.ListTendersWithScrapedDocuments) tendersGP.GET("/:id", tenderHandler.Get) tendersGP.PUT("/:id", tenderHandler.Update) tendersGP.DELETE("/:id", tenderHandler.Delete) tendersGP.GET("/:id/documents", tenderHandler.GetDocuments) tendersGP.GET("/:id/documents/download", tenderHandler.DownloadDocuments) tendersGP.GET("/:id/ai-summary", tenderHandler.GetAISummary) tendersGP.POST("/:id/ai-summarize", tenderHandler.TriggerAISummarize) tendersGP.POST("/:id/ai-analyze", tenderHandler.TriggerAIAnalyze) tendersGP.POST("/:id/ai-translate", tenderHandler.TriggerAITranslate) } // Admin Feedback Routes feedbackGP := adminV1.Group("/feedback") { feedbackGP.Use(userHandler.AuthMiddleware()) feedbackGP.GET("", feedbackHandler.Search) feedbackGP.GET("/:id", feedbackHandler.Get) feedbackGP.DELETE("/:id", feedbackHandler.Delete) } // Admin Tender-Approvals Routes tenderApprovalGP := adminV1.Group("/tender-approvals") { tenderApprovalGP.Use(userHandler.AuthMiddleware()) tenderApprovalGP.GET("", tenderApprovalHandler.ListTenderApprovals) tenderApprovalGP.GET("/:id", tenderApprovalHandler.GetTenderApproval) tenderApprovalGP.GET("/stats", tenderApprovalHandler.GetTenderApprovalStats) tenderApprovalGP.GET("/submission-mode/:submission_mode", tenderApprovalHandler.GetTenderApprovalsBySubmissionMode) tenderApprovalGP.GET("/status/:status", tenderApprovalHandler.GetTenderApprovalsByStatus) } // Admin Inquiry Routes inquiryGP := adminV1.Group("/inquiries") { inquiryGP.Use(userHandler.AuthMiddleware()) inquiryGP.GET("", inquiryHandler.SearchInquiries) inquiryGP.GET("/:id", inquiryHandler.GetInquiryByID) inquiryGP.PUT("/:id/status", inquiryHandler.UpdateInquiryStatus) inquiryGP.DELETE("/:id", inquiryHandler.DeleteInquiry) } // Admin Flags Routes flagsGP := adminV1.Group("/flags") { flagsGP.GET("/:country_code", flagHandler.AdminGetFlagSVG) } // Admin Notifications Routes notificationsGP := adminV1.Group("/notifications") { notificationsGP.Use(userHandler.AuthMiddleware()) notificationsGP.POST("", notificationHandler.Send) notificationsGP.GET("", notificationHandler.GetNotifications) notificationsGP.GET("/view/:id", notificationHandler.ViewNotification) notificationsGP.GET("/mark-seen/:id", notificationHandler.MarkSeen) notificationsGP.GET("/my", notificationHandler.AdminNotifications) } // Admin Contact Routes contactsGP := adminV1.Group("/contacts") { contactsGP.Use(userHandler.AuthMiddleware()) contactsGP.GET("", contactHandler.Search) contactsGP.GET("/stats", contactHandler.GetStats) contactsGP.GET("/:id", contactHandler.GetByID) contactsGP.PUT("/:id/status", contactHandler.UpdateStatus) contactsGP.DELETE("/:id", contactHandler.Delete) } // Admin CMS Routes cmsGP := adminV1.Group("/cms") { cmsGP.Use(userHandler.AuthMiddleware()) cmsGP.POST("", cmsHandler.Create) cmsGP.GET("", cmsHandler.Search) cmsGP.GET("/:id", cmsHandler.GetByID) cmsGP.PUT("/:id", cmsHandler.Update) cmsGP.DELETE("/:id", cmsHandler.Delete) } // Kanban Routes — paths are /admin/v1/kanban/* (RegisterRoutes expects this /kanban group) kanbanGP := adminV1.Group("/kanban") { kanbanGP.Use(userHandler.AuthMiddleware()) kanbanHandler.RegisterRoutes(kanbanGP) } // File Store Routes filesGP := adminV1.Group("/files") { filesGP.Use(userHandler.AuthMiddleware()) filesGP.POST("/upload", fileStoreHandler.Upload) filesGP.POST("/upload/batch", fileStoreHandler.UploadBatch) filesGP.GET("", fileStoreHandler.ListFiles) filesGP.GET("/:file_id/info", fileStoreHandler.GetInfo) filesGP.GET("/:file_id/download", fileStoreHandler.Download) filesGP.DELETE("/:file_id", fileStoreHandler.Delete) } // Dashboard Routes dashboardGP := adminV1.Group("/dashboard") { dashboardGP.Use(userHandler.AuthMiddleware()) dashboardGP.GET("/summary", dashboardHandler.Summary) dashboardGP.GET("/trend", dashboardHandler.Trend) dashboardGP.GET("/countries", dashboardHandler.Countries) dashboardGP.GET("/notice-types", dashboardHandler.NoticeTypes) dashboardGP.GET("/closing-soon", dashboardHandler.ClosingSoon) dashboardGP.GET("/recent", dashboardHandler.Recent) dashboardGP.GET("/statistics", dashboardHandler.Statistics) } } func RegisterPublicRoutes(e *echo.Echo, customerHandler *customer.Handler, tenderHandler *tender.TenderHandler, feedbackHandler *feedback.Handler, tenderApprovalHandler *tender_approval.Handler, companyHandler *company.Handler, inquiryHandler *inquiry.Handler, categoryHandler *company_category.Handler, flagHandler *assets.Handler, notificationHandler *notification.NotificationHandler, contactHandler *contact.Handler, cmsHandler *cms.Handler, kanbanHandler *kanban.Handler, fileStoreHandler *filestore.Handler, xssPolicy *security.XSSPolicy) { v1 := e.Group("/api/v1") customerGP := v1.Group("/profile") { customerGP.POST("/login", customerHandler.Login) customerGP.POST("/refresh-token", customerHandler.RefreshToken) // Forgot password routes customerGP.POST("/forgot-password", customerHandler.RequestResetPassword) customerGP.POST("/verify-otp", customerHandler.VerifyOTP) customerGP.POST("/reset-password", customerHandler.ResetPassword) profileGP := customerGP.Group("") profileGP.Use(customerHandler.AuthMiddleware()) profileGP.GET("", customerHandler.GetProfile) profileGP.PUT("/keywords", customerHandler.UpdateProfileKeywords) profileGP.DELETE("/logout", customerHandler.Logout) } // Public Tenders Routes tendersGP := v1.Group("/tenders") tendersGP.Use(customerHandler.AuthMiddleware()) { tendersGP.GET("", tenderHandler.GetPublicTenders) tendersGP.GET("/recommend", tenderHandler.RecommendTenders) tendersGP.GET("/details/:id", tenderHandler.GetPublicTenderDetails) tendersGP.GET("/:id/documents", tenderHandler.GetDocuments) tendersGP.GET("/:id/documents/download", tenderHandler.DownloadDocuments) tendersGP.GET("/:id/document-summaries", tenderHandler.GetDocumentSummaries) tendersGP.GET("/:id/ai-summary", tenderHandler.GetPublicAISummary) tendersGP.POST("/:id/ai-translate", tenderHandler.TriggerPublicAITranslate) } // Public Feedback Routes feedbackGP := v1.Group("/feedback") { feedbackGP.Use(customerHandler.AuthMiddleware()) feedbackGP.POST("", feedbackHandler.Toggle) feedbackGP.GET("", feedbackHandler.PublicList) feedbackGP.GET("/tender/:tender_id", feedbackHandler.PublicGetByTenderID) feedbackGP.GET("/:id", feedbackHandler.PublicGet) feedbackGP.GET("/stats/customer", feedbackHandler.GetCustomerStats) feedbackGP.GET("/stats/company", feedbackHandler.GetCompanyStats) } // Public Tender Approvals Routes tenderApprovalGP := v1.Group("/tender-approvals") { tenderApprovalGP.Use(customerHandler.AuthMiddleware()) tenderApprovalGP.POST("", tenderApprovalHandler.ToggleTenderApproval) tenderApprovalGP.GET("", tenderApprovalHandler.PublicGetTenderApprovals) tenderApprovalGP.GET("/:id", tenderApprovalHandler.GetPublicTenderApproval) tenderApprovalGP.GET("/tender/:tender_id", tenderApprovalHandler.GetTenderApprovalByTenderAndCompany) tenderApprovalGP.GET("/stats", tenderApprovalHandler.GetCompanyTenderApprovalStats) } // Public Company Routes companiesGP := v1.Group("/companies") { companiesGP.Use(customerHandler.AuthMiddleware()) companiesGP.GET("", companyHandler.MyCompany) } // Public Inquiry Routes inquiryGP := v1.Group("/inquiries") { inquiryGP.POST("", inquiryHandler.CreateInquiry) } // Public Flags Routes flagsGP := v1.Group("/flags") { flagsGP.GET("/:country_code", flagHandler.GetFlagSVG) } // Public Notifications Routes notificationsGP := v1.Group("/notifications") { notificationsGP.Use(customerHandler.AuthMiddleware()) notificationsGP.GET("", notificationHandler.CustomerNotifications) notificationsGP.GET("/view/:id", notificationHandler.PublicViewNotification) notificationsGP.GET("/mark", notificationHandler.PublicAllMarkSeen) notificationsGP.GET("/mark/:id", notificationHandler.PublicMarkSeen) } // Public Contact Routes contactsPublicGP := v1.Group("/contacts") { contactsPublicGP.POST("", contactHandler.Create) } // Public CMS Routes cmsGP := v1.Group("/cms") { cmsGP.GET("/:key", cmsHandler.GetByKey) } // Kanban (mobile bid workflow board) — paths are /api/v1/kanban/* kanbanGP := v1.Group("/kanban") { kanbanGP.Use(customerHandler.AuthMiddleware()) kanbanHandler.RegisterRoutes(kanbanGP) } // File Store Routes publicFilesGP := v1.Group("/files") { publicFilesGP.Use(customerHandler.AuthMiddleware()) publicFilesGP.POST("/upload", fileStoreHandler.Upload) publicFilesGP.POST("/upload/batch", fileStoreHandler.UploadBatch) publicFilesGP.GET("", fileStoreHandler.ListFiles) publicFilesGP.GET("/:file_id/info", fileStoreHandler.GetInfo) publicFilesGP.GET("/:file_id/download", fileStoreHandler.Download) publicFilesGP.DELETE("/:file_id", fileStoreHandler.Delete) } } func RegisterDocumentScraperRoutes(e *echo.Echo, docScraperHandler *document_scraper.Handler, apiKey string) { v1 := e.Group("/api/v1/scraper") // Apply API key middleware to all document scraper routes v1.Use(document_scraper.APIKeyMiddleware(apiKey)) // Document Scraper Routes { v1.GET("/tenders", docScraperHandler.ListPendingTenders) v1.GET("/tenders/:notice_id", docScraperHandler.GetTenderByNoticeID) } }