The admin customer page collected an address (door no, street, suburb, city, state, postcode, landmark, lat/lng) that the backend had nowhere to store, so it was silently dropped on save. Add those fields flat onto appcustomers, matching the reference console's shape, plus an applocationid for zone scoping. - GET /admin/customers: add ?applocationid= filter; emit firstname/lastname split and the address fields alongside the existing joined name. - GET /admin/customers/summary (new): stat-tile counts (total/active/blocked) scoped like the list, so the client stops deriving them from the full page. - PATCH /admin/customers/🆔 accept firstname/lastname directly (single name still splits as a fallback) and persist every address field; pointer fields so an omitted field is not confused with one cleared to empty. Pagination and keyword search were already present. Additive, nullable columns — AutoMigrate handles it, no data rewrite. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
433 lines
19 KiB
Go
433 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.Get("/customers/summary", controllers.GetAdminCustomersSummary)
|
|
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))
|
|
}
|