89faa08b1c
continuous-integration/drone/push Build is passing
- Introduced the ability to append external links to company profiles through a new API endpoint. - Enhanced the `Company` entity to include a `Links` field for storing external resource links. - Created `AddLinksForm` for validating incoming link data and implemented corresponding logic in the service layer. - Added error handling for link validation, ensuring only valid URLs are accepted and limiting the number of links to 20. - Implemented unit tests for link management functions, including sanitization and merging of links. - Updated relevant API documentation to reflect the new functionality. This update significantly enhances the company management capabilities by allowing the addition of external links, improving the overall user experience and data richness in company profiles.
401 lines
16 KiB
Go
401 lines
16 KiB
Go
package router
|
|
|
|
import (
|
|
"tm/internal/assets"
|
|
"tm/internal/ai_pipeline"
|
|
"tm/internal/auditlog"
|
|
"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, aiPipelineHandler *ai_pipeline.Handler, auditLogHandler *auditlog.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 Audit Logs Routes
|
|
auditLogsGP := adminV1.Group("/audit-logs")
|
|
{
|
|
auditLogsGP.Use(userHandler.AuthMiddleware(), userHandler.AdminMiddleware())
|
|
auditLogsGP.GET("", auditLogHandler.Search)
|
|
}
|
|
|
|
// 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/recommended-tenders", tenderHandler.AdminRecommendTenders)
|
|
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)
|
|
companiesGP.POST("/:id/links", companyHandler.AddLinks)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// AI Pipeline Routes (proxy to Opplens AI service)
|
|
aiPipelineGP := adminV1.Group("/ai-pipeline")
|
|
{
|
|
aiPipelineGP.Use(userHandler.AuthMiddleware())
|
|
aiPipelineGP.POST("/scrape/documents", aiPipelineHandler.ScrapeDocuments)
|
|
aiPipelineGP.POST("/scrape/documents/batch", aiPipelineHandler.ScrapeDocumentsBatch)
|
|
aiPipelineGP.GET("/scrape/portals", aiPipelineHandler.GetScrapePortals)
|
|
aiPipelineGP.POST("/summarize/batch", aiPipelineHandler.SummarizeBatch)
|
|
aiPipelineGP.POST("/translate/batch", aiPipelineHandler.TranslateBatch)
|
|
aiPipelineGP.POST("/sync", aiPipelineHandler.Sync)
|
|
aiPipelineGP.POST("/run", aiPipelineHandler.Run)
|
|
aiPipelineGP.POST("/run/batch", aiPipelineHandler.RunBatch)
|
|
aiPipelineGP.POST("/analyze", aiPipelineHandler.Analyze)
|
|
aiPipelineGP.POST("/daily-run", aiPipelineHandler.DailyRun)
|
|
aiPipelineGP.POST("/auto", aiPipelineHandler.Auto)
|
|
aiPipelineGP.GET("/last-run", aiPipelineHandler.GetLastRun)
|
|
aiPipelineGP.GET("/last-auto-run", aiPipelineHandler.GetLastAutoRun)
|
|
aiPipelineGP.GET("/report", aiPipelineHandler.GetReport)
|
|
aiPipelineGP.GET("/stats", aiPipelineHandler.GetStats)
|
|
aiPipelineGP.GET("/minio-stats", aiPipelineHandler.GetMinioStats)
|
|
}
|
|
}
|
|
|
|
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(), customerHandler.CompanyContextMiddleware())
|
|
{
|
|
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(), customerHandler.CompanyContextMiddleware())
|
|
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(), customerHandler.CompanyContextMiddleware())
|
|
companiesGP.GET("", companyHandler.MyCompany)
|
|
}
|
|
|
|
// AI tender recommendation routes
|
|
recommendationGP := v1.Group("")
|
|
recommendationGP.Use(customerHandler.AuthMiddleware(), customerHandler.CompanyContextMiddleware())
|
|
{
|
|
recommendationGP.POST("/onboarding", companyHandler.StartOnboarding)
|
|
recommendationGP.POST("/recommend", companyHandler.RecommendTenders)
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
}
|