/api/v1/utils/users/redis exposed open CRUD — create, read, update and delete on the cached-user store — to anonymous callers. The store is currently empty and nothing in the console or apps calls it, so closing it breaks no client. Third of the three route groups flagged in the 2026-07-27 audit. /crm/* stays open by explicit decision: the field-sales Flutter app sends no credentials. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
422 lines
19 KiB
Go
422 lines
19 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))
|
|
// PIN reset is console-operated, NOT self-service: ResetMilerPin overwrites
|
|
// the PIN given only a phone number, and phone numbers are the miler login
|
|
// identifier rather than a secret. Left unauthenticated, two calls
|
|
// (reset-pin then verify-pin) take over any miler account. Ops resets a
|
|
// rider's PIN on request instead, so this carries admin/manager/executive
|
|
// auth even though it sits under the /miler prefix.
|
|
miler.Post("/reset-pin", authThrottle,
|
|
middlewares.AuthMiddleware(cfg), middlewares.RoleCheckMiddleware(1, 3, 4),
|
|
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 — console/ops only. Open CRUD on a
|
|
// user cache with no authentication has no legitimate anonymous caller;
|
|
// the store is currently empty, so closing it breaks nothing.
|
|
redisUsers := api.Group("/utils/users/redis",
|
|
middlewares.AuthMiddleware(cfg), middlewares.RoleCheckMiddleware(1, 3, 4))
|
|
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 — console/ops only
|
|
// --------------------
|
|
// Previously open "for testing", but live: listing the cache returns real
|
|
// bookings including customer delivery addresses, and the per-customer route
|
|
// takes a customer id straight from the URL. Behind console auth now.
|
|
bookingCache := api.Group("/bookings/cache",
|
|
middlewares.AuthMiddleware(cfg), middlewares.RoleCheckMiddleware(1, 3, 4))
|
|
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))
|
|
}
|