Doormile should not be wired to jupiter's APIs at all. The ingest routes, the legacy identity middleware and the worker-side fan-out existed to copy jupiter's rider GPS into Doormile, which was never the goal: Doormile needs its own JetStream in front of its own endpoints, not a pipe from someone else's. Removed here: /internal/miler/* ingest, LegacyMilerIdentity, and the legacyuserid field on the miler update payload. The worker-side shadow forward and its k8s secret are removed separately in the Kubernetes repo; jupiter forwarding is untouched and verified still healthy. MilerProfile.Legacyuserid is deliberately kept. Nothing reads it now, but it records which jupiter rider each of the six migrated riders came from, which is worth having during the cutover. Dropping a populated column buys nothing and AutoMigrate would not drop it anyway. Kept from that work because they are unrelated to jupiter and fix real bugs: db.EnsureStreams (four subjects were publishing to no stream and being dropped silently) and the tenantlocationid column. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
432 lines
19 KiB
Go
432 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)
|
|
// Per-site performance — jupiter's getlocationsummary. Needs ?tenantid= for
|
|
// Doormile staff; a client login is already pinned to its own sites.
|
|
adminAuth.Get("/locations/summary", controllers.GetLocationSummary)
|
|
|
|
// 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)
|
|
// Registered before /milers/:id — Fiber matches in registration order, so
|
|
// the literal path has to come first or ":id" swallows "summary".
|
|
adminAuth.Get("/milers/summary", controllers.GetMilerSummary)
|
|
adminAuth.Post("/milers", controllers.CreateMiler)
|
|
adminAuth.Get("/milers/:id", controllers.GetMilerDetails)
|
|
adminAuth.Get("/milers/:id/logs", controllers.GetMilerLogs)
|
|
adminAuth.Get("/milers/:id/activity", controllers.GetMilerActivity)
|
|
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.Get("/bookings/:id/track", controllers.GetAdminBookingTrack)
|
|
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/:id/logs", controllers.GetAdminConsignmentLogs)
|
|
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))
|
|
}
|