New endpoints: - Admin: partner CRUD (GET/POST /admin/partners, GET/PUT/DELETE /admin/partners/:id), bulk express booking create (POST /admin/expressbooking/bulk), bulk cancel (POST /admin/bookings/bulk-cancel), reports (GET /admin/reports), password change (PUT /admin/profile/password), miler notify (POST /admin/milers/:id/notify) - Miler: PIN reset (POST /miler/reset-pin), cancel assignment (POST /miler/bookings/:bookingid/cancel), skip delivery (POST /miler/consignments/:id/skip) - Hub: batch assign (POST /hub/bookings/batch-assign) - greedy nearest-rider queue clearing, capped per rider Bug fixes: - BookingPickupComplete now sets Consignment.Tenantid from the booking's tenant instead of the completing miler's own tenant (fixes cross-tenant shipment mis-attribution) - GetHubUnassignedBookings/GetHubBookingsRange now scoped via scopeBookingsToOwnTenant (fixes partner hub staff seeing other tenants' bookings) Also: CRM booking routes renamed to expressbooking to end the naming collision with the separate CRM clients feature; PickupBooking gains nullable Tenantid; adds CLAUDE.md project memory. Verified: go build ./... and go vet ./... both clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
407 lines
18 KiB
Go
407 lines
18 KiB
Go
package routes
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"doormile/config"
|
|
"doormile/controllers"
|
|
"doormile/db"
|
|
"doormile/internal/ws"
|
|
"doormile/middlewares"
|
|
|
|
"github.com/gofiber/fiber/v2"
|
|
"github.com/gofiber/fiber/v2/middleware/limiter"
|
|
"github.com/gofiber/websocket/v2"
|
|
)
|
|
|
|
// authLimiter throttles credential-checking endpoints per IP. Miler and
|
|
// customer PINs are 4 digits — only 10,000 combinations — so without this an
|
|
// attacker can walk the whole keyspace in seconds. 10/minute keeps a genuine
|
|
// user's retries and typos working while making enumeration impractical.
|
|
func authLimiter() fiber.Handler {
|
|
return limiter.New(limiter.Config{
|
|
Max: 10,
|
|
Expiration: 1 * time.Minute,
|
|
LimitReached: func(c *fiber.Ctx) error {
|
|
return c.Status(fiber.StatusTooManyRequests).
|
|
JSON(fiber.Map{"success": false, "message": "too many attempts, please try again in a minute"})
|
|
},
|
|
})
|
|
}
|
|
|
|
func RegisterRoutes(app *fiber.App, cfg *config.Config) {
|
|
api := app.Group("/api/v1")
|
|
|
|
// One shared instance, so the 10/minute budget is spent across all
|
|
// credential endpoints combined — an attacker can't reset it by rotating
|
|
// between /login, /verify-pin and /reset-pin.
|
|
authThrottle := authLimiter()
|
|
|
|
// --------------------
|
|
// HEALTH & READINESS PROBES
|
|
// --------------------
|
|
api.Get("/health", func(c *fiber.Ctx) error {
|
|
return c.JSON(fiber.Map{"status": "OK", "time": time.Now()})
|
|
})
|
|
|
|
api.Get("/ready", func(c *fiber.Ctx) error {
|
|
dbStatus := "connected"
|
|
sqlDB, err := db.DB.DB()
|
|
if err != nil || sqlDB.Ping() != nil {
|
|
dbStatus = "unavailable"
|
|
}
|
|
|
|
redisStatus := "connected"
|
|
if db.Rdb == nil {
|
|
redisStatus = "unavailable"
|
|
} else {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
if _, pingErr := db.Rdb.Ping(ctx).Result(); pingErr != nil {
|
|
redisStatus = "unavailable"
|
|
}
|
|
}
|
|
|
|
status := fiber.StatusOK
|
|
if dbStatus == "unavailable" || redisStatus == "unavailable" {
|
|
status = fiber.StatusServiceUnavailable
|
|
}
|
|
|
|
return c.Status(status).JSON(fiber.Map{
|
|
"status": "ready",
|
|
"checks": fiber.Map{
|
|
"postgres": dbStatus,
|
|
"redis": redisStatus,
|
|
},
|
|
})
|
|
})
|
|
|
|
// --------------------
|
|
// CUSTOMER APIS
|
|
// --------------------
|
|
customer := api.Group("/customer")
|
|
customer.Post("/register", controllers.RegisterCustomer(cfg))
|
|
customer.Post("/login", authThrottle, controllers.LoginCustomer)
|
|
customer.Post("/verify-pin", authThrottle, controllers.VerifyCustomerPin(cfg))
|
|
customer.Post("/reset-pin", authThrottle, controllers.ResetCustomerPin)
|
|
customer.Post("/send-email-otp", authThrottle, controllers.SendCustomerEmailOtp(cfg))
|
|
customer.Post("/verify-email-otp", authThrottle, controllers.VerifyCustomerEmailOtp())
|
|
|
|
// Authenticated Customer App routes
|
|
customerAuth := customer.Use(middlewares.AuthMiddleware(cfg), middlewares.RoleCheckMiddleware(9))
|
|
customerAuth.Get("/profile", controllers.GetCustomerProfile)
|
|
customerAuth.Put("/profile", controllers.UpdateCustomerProfile)
|
|
|
|
customerAuth.Get("/locations", controllers.GetCustomerLocations)
|
|
customerAuth.Post("/locations", controllers.CreateCustomerLocation)
|
|
customerAuth.Put("/locations/:id", controllers.UpdateCustomerLocation)
|
|
customerAuth.Delete("/locations/:id", controllers.DeleteCustomerLocation)
|
|
|
|
customerAuth.Put("/device-token", controllers.SaveCustomerDeviceToken)
|
|
|
|
customerAuth.Post("/bookings", middlewares.CityGateMiddleware, controllers.CreateCustomerBooking)
|
|
customerAuth.Get("/bookings", controllers.GetCustomerBookings)
|
|
customerAuth.Get("/bookings/:bookingid", controllers.GetCustomerBookingDetails)
|
|
customerAuth.Post("/bookings/:bookingid/cancel", controllers.CancelCustomerBooking)
|
|
customerAuth.Get("/bookings/:bookingid/price", controllers.GetCustomerBookingQuote)
|
|
customerAuth.Get("/track/:trackingno", controllers.TrackConsignment)
|
|
|
|
// --------------------
|
|
// MILER APIS
|
|
// --------------------
|
|
miler := api.Group("/miler")
|
|
miler.Post("/login", authThrottle, controllers.LoginMiler(cfg))
|
|
miler.Post("/verify-pin", authThrottle, controllers.VerifyMilerPin(cfg))
|
|
miler.Post("/reset-pin", authThrottle, controllers.ResetMilerPin)
|
|
|
|
// Authenticated Miler App routes
|
|
milerAuth := miler.Use(middlewares.AuthMiddleware(cfg), middlewares.RoleCheckMiddleware(5))
|
|
milerAuth.Get("/profile", controllers.GetMilerProfile)
|
|
milerAuth.Put("/profile", controllers.UpdateMilerProfile)
|
|
|
|
milerAuth.Put("/device-token", controllers.SaveMilerDeviceToken)
|
|
|
|
milerAuth.Put("/location", controllers.UpdateMilerLocation)
|
|
milerAuth.Put("/availability", controllers.UpdateMilerAvailability)
|
|
|
|
milerAuth.Get("/assignments", controllers.GetMilerAssignments)
|
|
milerAuth.Get("/assignments/:id", controllers.GetMilerAssignmentDetails)
|
|
milerAuth.Post("/assignments/:id/accept", controllers.AcceptMilerAssignment)
|
|
milerAuth.Post("/assignments/:id/reject", controllers.RejectMilerAssignment)
|
|
|
|
milerAuth.Post("/bookings/:bookingid/reached", controllers.BookingReachedCustomer)
|
|
milerAuth.Post("/bookings/:bookingid/parcel", controllers.BookingParcelConfirm)
|
|
milerAuth.Post("/bookings/:bookingid/payment", controllers.BookingPaymentCollect)
|
|
milerAuth.Post("/bookings/:bookingid/pickup-complete", controllers.BookingPickupComplete)
|
|
milerAuth.Post("/bookings/:bookingid/vehicle-required", controllers.BookingVehicleRequiredEscalate)
|
|
milerAuth.Post("/bookings/:bookingid/cancel", controllers.MilerCancelAssignment)
|
|
|
|
// Redis periodic telemetry and status logs
|
|
milerAuth.Post("/logs", controllers.CreateMilerPeriodicLog)
|
|
milerAuth.Get("/logs", controllers.GetMilerPeriodicLogs)
|
|
milerAuth.Post("/status", controllers.CreateMilerStatus)
|
|
milerAuth.Get("/status", controllers.GetMilerStatus)
|
|
|
|
// Redis consignment tracking and telemetry logs
|
|
milerAuth.Post("/consignments/logs", controllers.PublishConsignmentLogs)
|
|
milerAuth.Get("/consignments/logs/:consignmentid", controllers.GetConsignmentLogs)
|
|
milerAuth.Get("/consignments/userlogs/:userid", controllers.GetUserConsignmentLogs)
|
|
|
|
// Duty management
|
|
milerAuth.Post("/duty/start", controllers.MilerStartDuty)
|
|
milerAuth.Put("/duty/end", controllers.MilerEndDuty)
|
|
milerAuth.Get("/duty/current", controllers.MilerGetDutyStatus)
|
|
|
|
// Break management
|
|
milerAuth.Post("/breaks/start", controllers.MilerStartBreak)
|
|
milerAuth.Put("/breaks/end", controllers.MilerEndBreak)
|
|
|
|
// Miler's own bookings
|
|
milerAuth.Get("/bookings", controllers.MilerGetMyBookings)
|
|
|
|
// Delivery confirmation
|
|
milerAuth.Post("/consignments/:id/deliver", controllers.MilerDeliverConsignment)
|
|
milerAuth.Post("/consignments/:id/skip", controllers.MilerSkipDelivery)
|
|
|
|
// Earnings
|
|
milerAuth.Get("/earnings", controllers.MilerGetEarnings)
|
|
|
|
// Notifications
|
|
milerAuth.Get("/notifications", controllers.MilerGetNotifications)
|
|
milerAuth.Patch("/notifications/:id/read", controllers.MilerMarkNotificationRead)
|
|
|
|
// Support
|
|
milerAuth.Post("/support", controllers.MilerCreateTicket)
|
|
milerAuth.Get("/support", controllers.MilerGetTickets)
|
|
|
|
// --------------------
|
|
// ADMIN CONSOLE APIS — all open, no token required
|
|
// --------------------
|
|
admin := api.Group("/admin")
|
|
|
|
admin.Post("/login", authThrottle, controllers.LoginAdmin(cfg))
|
|
|
|
// Authenticated Admin Console routes
|
|
adminAuth := admin.Use(middlewares.AuthMiddleware(cfg), middlewares.RoleCheckMiddleware(1, 3, 4))
|
|
|
|
adminAuth.Get("/dashboard", controllers.GetAdminDashboard)
|
|
adminAuth.Get("/reports", controllers.GetAdminReports)
|
|
adminAuth.Get("/profile", controllers.GetAdminProfile)
|
|
adminAuth.Get("/me", controllers.GetAdminProfile)
|
|
adminAuth.Put("/profile/password", controllers.AdminChangePassword)
|
|
|
|
// App Users Management
|
|
adminAuth.Get("/users", controllers.GetAppUsers)
|
|
adminAuth.Post("/users", controllers.CreateAppUser)
|
|
adminAuth.Put("/users/:id", controllers.UpdateAppUser)
|
|
adminAuth.Delete("/users/:id", controllers.DeleteAppUser)
|
|
|
|
// Partner management (fleet/rider suppliers — distinct from tenants,
|
|
// which are the client companies Doormile delivers for)
|
|
adminAuth.Get("/partners", controllers.GetPartners)
|
|
adminAuth.Post("/partners", controllers.CreatePartner)
|
|
adminAuth.Get("/partners/:id", controllers.GetPartnerDetails)
|
|
adminAuth.Put("/partners/:id", controllers.UpdatePartner)
|
|
adminAuth.Delete("/partners/:id", controllers.DeletePartner)
|
|
|
|
// Tenant management
|
|
adminAuth.Get("/tenants", controllers.GetTenants)
|
|
adminAuth.Post("/tenants", controllers.CreateTenant)
|
|
adminAuth.Get("/tenants/:id", controllers.GetTenantDetails)
|
|
adminAuth.Put("/tenants/:id", controllers.UpdateTenant)
|
|
adminAuth.Delete("/tenants/:id", controllers.DeleteTenant)
|
|
adminAuth.Get("/tenants/:id/locations", controllers.GetTenantLocations)
|
|
adminAuth.Post("/tenants/:id/locations", controllers.CreateTenantLocation)
|
|
adminAuth.Put("/tenantlocations/:id", controllers.UpdateTenantLocation)
|
|
|
|
// Tenant customers
|
|
adminAuth.Get("/tenantcustomers", controllers.GetTenantCustomers)
|
|
adminAuth.Post("/tenantcustomers", controllers.CreateTenantCustomer)
|
|
adminAuth.Get("/tenantcustomers/:id", controllers.GetTenantCustomerDetails)
|
|
adminAuth.Put("/tenantcustomers/:id", controllers.UpdateTenantCustomer)
|
|
adminAuth.Delete("/tenantcustomers/:id", controllers.DeleteTenantCustomer)
|
|
|
|
// B2C App customers
|
|
adminAuth.Get("/customers", controllers.GetAdminCustomers)
|
|
adminAuth.Patch("/customers/:id", controllers.UpdateAdminCustomer)
|
|
|
|
// Hubs
|
|
adminAuth.Get("/hubs", controllers.GetHubs)
|
|
adminAuth.Post("/hubs", controllers.CreateHub)
|
|
adminAuth.Get("/hubs/:id", controllers.GetHubDetails)
|
|
adminAuth.Put("/hubs/:id", controllers.UpdateHub)
|
|
adminAuth.Delete("/hubs/:id", controllers.DeleteHub)
|
|
|
|
// Vehicles
|
|
adminAuth.Get("/vehicles", controllers.GetVehicles)
|
|
adminAuth.Post("/vehicles", controllers.CreateVehicle)
|
|
adminAuth.Get("/vehicles/:id", controllers.GetVehicleDetails)
|
|
adminAuth.Put("/vehicles/:id", controllers.UpdateVehicle)
|
|
adminAuth.Delete("/vehicles/:id", controllers.DeleteVehicle)
|
|
|
|
// Milers
|
|
adminAuth.Get("/milers", controllers.GetMilers)
|
|
adminAuth.Post("/milers", controllers.CreateMiler)
|
|
adminAuth.Get("/milers/:id", controllers.GetMilerDetails)
|
|
adminAuth.Put("/milers/:id", controllers.UpdateMiler)
|
|
adminAuth.Put("/milers/:id/block", controllers.BlockMiler)
|
|
adminAuth.Put("/milers/:id/assign-vehicle", controllers.AssignMilerVehicle)
|
|
adminAuth.Post("/milers/:id/notify", controllers.AdminNotifyMiler)
|
|
|
|
// Bookings
|
|
adminAuth.Get("/bookings", controllers.GetAdminBookings)
|
|
adminAuth.Post("/expressbooking", middlewares.CityGateMiddleware, controllers.CreateExpressBooking)
|
|
adminAuth.Post("/expressbooking/bulk", middlewares.CityGateMiddleware, controllers.AdminBulkCreateBookings)
|
|
adminAuth.Get("/bookings/:id", controllers.GetAdminBookingDetails)
|
|
adminAuth.Post("/bookings/:id/assign-miler", controllers.AdminAssignMiler)
|
|
adminAuth.Post("/bookings/:id/assign-vehicle", controllers.AdminAssignVehicle)
|
|
adminAuth.Put("/bookings/:id/status", controllers.AdminUpdateBookingStatus)
|
|
adminAuth.Post("/bookings/:id/cancel", controllers.AdminCancelBooking)
|
|
adminAuth.Post("/bookings/bulk-cancel", controllers.AdminBulkCancelBookings)
|
|
|
|
// Consignments
|
|
adminAuth.Get("/consignments", controllers.GetAdminConsignments)
|
|
adminAuth.Get("/consignments/:id", controllers.GetAdminConsignmentDetails)
|
|
adminAuth.Get("/consignments/track/:trackingno", controllers.GetAdminConsignmentTracking)
|
|
adminAuth.Put("/consignments/:id/status", controllers.AdminUpdateConsignmentStatus)
|
|
|
|
// Tripsheets
|
|
adminAuth.Get("/tripsheets", controllers.GetTripsheets)
|
|
adminAuth.Post("/tripsheets", controllers.CreateTripsheet)
|
|
adminAuth.Get("/tripsheets/:id", controllers.GetTripsheetDetails)
|
|
adminAuth.Post("/tripsheets/:id/items", controllers.AddTripsheetItem)
|
|
adminAuth.Delete("/tripsheets/:id/items/:itemid", controllers.DeleteTripsheetItem)
|
|
adminAuth.Put("/tripsheets/:id/dispatch", controllers.DispatchTripsheet)
|
|
adminAuth.Put("/tripsheets/:id/arrive", controllers.ArriveTripsheet)
|
|
|
|
// Competitor branch survey data (from Enquiry.xlsx Sheet 1)
|
|
adminAuth.Get("/competitor-branches", controllers.GetCompetitorBranches)
|
|
adminAuth.Post("/competitor-branches", controllers.CreateCompetitorBranch)
|
|
adminAuth.Put("/competitor-branches/:id", controllers.UpdateCompetitorBranch)
|
|
adminAuth.Delete("/competitor-branches/:id", controllers.DeleteCompetitorBranch)
|
|
|
|
// Carrier pricing benchmarks (from Enquiry.xlsx Sheet 2)
|
|
adminAuth.Get("/carrier-pricing", controllers.GetCarrierPricing)
|
|
adminAuth.Post("/carrier-pricing", controllers.CreateCarrierPricing)
|
|
adminAuth.Put("/carrier-pricing/:id", controllers.UpdateCarrierPricing)
|
|
adminAuth.Delete("/carrier-pricing/:id", controllers.DeleteCarrierPricing)
|
|
|
|
// Pricing
|
|
adminAuth.Get("/pricing", controllers.GetPricing)
|
|
adminAuth.Post("/pricing", controllers.CreatePricing)
|
|
adminAuth.Put("/pricing/:id", controllers.UpdatePricing)
|
|
adminAuth.Delete("/pricing/:id", controllers.DeletePricing)
|
|
adminAuth.Post("/pricing/simulate", controllers.GetPricingQuoteSimulate)
|
|
adminAuth.Post("/pricing/quote", controllers.GetPricingQuoteSimulate)
|
|
|
|
// Doormile pricing bands
|
|
adminAuth.Get("/doormile-pricing", controllers.GetDoormilePricing)
|
|
adminAuth.Post("/doormile-pricing", controllers.CreateDoormilePricing)
|
|
adminAuth.Put("/doormile-pricing/:id", controllers.UpdateDoormilePricing)
|
|
adminAuth.Delete("/doormile-pricing/:id", controllers.DeleteDoormilePricing)
|
|
|
|
// Exceptions
|
|
adminAuth.Get("/exceptions", controllers.GetExceptions)
|
|
adminAuth.Post("/exceptions", controllers.CreateException)
|
|
adminAuth.Get("/exceptions/:id", controllers.GetExceptionDetails)
|
|
adminAuth.Put("/exceptions/:id/status", controllers.ResolveException)
|
|
|
|
// --------------------
|
|
// HUB CONSOLE APIS
|
|
// --------------------
|
|
hub := api.Group("/hub")
|
|
hub.Post("/login", authThrottle, controllers.HubStaffLogin(cfg))
|
|
|
|
// Authenticated Hub Console routes (role 6)
|
|
hubAuth := hub.Use(middlewares.HubStaffAuth(cfg))
|
|
hubAuth.Get("/dashboard", controllers.GetHubDashboardStats)
|
|
hubAuth.Get("/bookings/unassigned", controllers.GetHubUnassignedBookings)
|
|
hubAuth.Get("/bookings", controllers.GetHubBookingsRange)
|
|
hubAuth.Get("/inbound/today", controllers.GetHubInboundToday)
|
|
hubAuth.Get("/inbound", controllers.GetHubInboundRange)
|
|
hubAuth.Post("/bookings/:id/inbound", controllers.CreateInboundScan)
|
|
hubAuth.Post("/bookings/:id/assign-miler", controllers.HubAssignMiler)
|
|
hubAuth.Post("/bookings/:id/auto-assign", controllers.HubAutoAssign)
|
|
hubAuth.Post("/bookings/batch-assign", controllers.HubBatchAssign)
|
|
hubAuth.Get("/batches", controllers.GetHubBatches)
|
|
hubAuth.Post("/batches", controllers.CreateHubBatch)
|
|
hubAuth.Patch("/batches/:id/status", controllers.UpdateBatchStatus)
|
|
hubAuth.Get("/milers", controllers.GetHubMilers)
|
|
|
|
// Hub management — enforced Doormile-staff-only inside each handler
|
|
hubAuth.Post("/staff", controllers.CreateHubStaffAccount)
|
|
hubAuth.Get("/hubs", controllers.GetHubsInCity)
|
|
hubAuth.Post("/hubs", controllers.CreateCityHub)
|
|
|
|
hubAuth.Get("/tripsheets/in-transit", controllers.GetInTransitTripsheets)
|
|
hubAuth.Get("/inbound/vehicles", controllers.GetInboundVehicles)
|
|
hubAuth.Get("/activity", controllers.GetHubActivity)
|
|
hubAuth.Get("/zones", controllers.GetHubZones)
|
|
hubAuth.Get("/notifications", controllers.GetHubNotifications)
|
|
hubAuth.Patch("/notifications/:id/read", controllers.MarkNotificationRead)
|
|
hubAuth.Get("/routing/:trackingno", controllers.GetRoutingInfo)
|
|
hubAuth.Get("/rider-routes", controllers.GetAllRiderRoutes)
|
|
hubAuth.Get("/milers/:id/route", controllers.GetMilerRoute)
|
|
|
|
hubAuth.Get("/messages", controllers.GetHubMessages)
|
|
hubAuth.Get("/messages/:id", controllers.GetHubMessageThread)
|
|
hubAuth.Post("/messages/:id", controllers.SendHubMessage)
|
|
hubAuth.Patch("/messages/:id/read", controllers.MarkHubMessagesRead)
|
|
|
|
hubAuth.Get("/report", controllers.GetHubReport)
|
|
|
|
// Redis active user caching utilities
|
|
redisUsers := api.Group("/utils/users/redis")
|
|
redisUsers.Post("/", controllers.CreateUserRedis)
|
|
redisUsers.Get("/", controllers.GetUserRedis)
|
|
redisUsers.Put("/:userid", controllers.UpdateUserRedis)
|
|
redisUsers.Delete("/:userid", controllers.DeleteUserRedis)
|
|
|
|
// --------------------
|
|
// DOORMILE PRICING — public check & meta (no auth)
|
|
// --------------------
|
|
api.Get("/pricing/meta", controllers.GetPricingMeta)
|
|
api.Post("/pricing/check", controllers.CheckPrice)
|
|
|
|
// --------------------
|
|
// BOOKING CACHE APIS — no auth required (testing)
|
|
// --------------------
|
|
bookingCache := api.Group("/bookings/cache")
|
|
bookingCache.Get("/", controllers.ListAllBookingsFromCache)
|
|
bookingCache.Get("/customer/:customer_id", controllers.GetCustomerBookingsFromCache)
|
|
bookingCache.Get("/:booking_id", controllers.GetBookingFromCache)
|
|
|
|
// --------------------
|
|
// CRM APIS — all open (field reps register from Flutter; web console reads without a separate CRM login)
|
|
// --------------------
|
|
crm := api.Group("/crm")
|
|
crm.Post("/clients", controllers.RegisterClient)
|
|
crm.Get("/clients", controllers.GetClients)
|
|
crm.Get("/clients/:id", controllers.GetClientDetails)
|
|
crm.Put("/clients/:id", controllers.UpdateClient)
|
|
crm.Delete("/clients/:id", controllers.DeleteClient)
|
|
|
|
// --------------------
|
|
// INTERNAL — machine-to-machine endpoints (API key auth, no JWT)
|
|
// --------------------
|
|
internal := api.Group("/internal", middlewares.InternalKeyAuth)
|
|
internal.Post("/notify", controllers.InternalNotify)
|
|
internal.Post("/bookings/:id/reassign", controllers.InternalReassign)
|
|
internal.Post("/agent-decisions", controllers.CreateAgentDecision)
|
|
internal.Get("/agent-decisions/similar", controllers.FindSimilarDecisions)
|
|
internal.Patch("/agent-decisions/:id/outcome", controllers.UpdateDecisionOutcome)
|
|
|
|
// --------------------
|
|
// WEBSOCKET — live miler tracking (no auth, public tracking link)
|
|
// --------------------
|
|
app.Use("/ws", func(c *fiber.Ctx) error {
|
|
if websocket.IsWebSocketUpgrade(c) {
|
|
return c.Next()
|
|
}
|
|
return fiber.ErrUpgradeRequired
|
|
})
|
|
app.Get("/ws/bookings/:bookingid/track", websocket.New(ws.TrackingHandler))
|
|
app.Get("/ws/bookings/:bookingid/chat", middlewares.WsChatAuth(cfg), websocket.New(ws.ChatHandler))
|
|
}
|