package controllers import ( "strconv" "strings" "time" "doormile/constants" "doormile/db" "doormile/models" "doormile/utils" "github.com/gofiber/fiber/v2" "gorm.io/gorm" ) // Rider (miler) accounts: the rules for creating, editing, blocking and // signing in that the console and the rider app both depend on. // // Before this file, CreateMiler checked nothing: two riders could share a phone // number (login then picked the oldest row, often a blocked or old account, and // answered "miler account is not active"), a duplicate email surfaced as a // bare 500, a Coimbatore rider could be attached to a Hyderabad hub, and a // blocked rider who was still signed in could start duty and put themselves // back to Available. // milerVehicleTypes are the vehicle types the console offers; matched without // regard to case and stored in this spelling. var milerVehicleTypes = []string{"Bike", "Scooter", "Bicycle", "Car", "Van"} func canonicalVehicleType(v string) (string, bool) { v = strings.TrimSpace(v) if v == "" { return "Bike", true } for _, t := range milerVehicleTypes { if strings.EqualFold(t, v) { return t, true } } return "", false } // milerLoginLookup finds the rider account for a phone number. Several rows // can share a number (riders created before duplicates were refused). The // order is: an Active miler that already has a PIN (the account the rider // actually uses), then an Active miler without one, then any miler row, then // anything else; oldest first within each, which is what the plain lookup // used to return. Ranking a PIN-less duplicate first would answer "incorrect // PIN" to a rider who signed in yesterday, and let set-pin claim the duplicate. func milerLoginLookup(phone string, configID int) *gorm.DB { return db.DB.Where("contactno = ? AND configid = ?", normalisePhone(phone), configID). Order(`CASE WHEN roleid = 5 AND status = 'Active' AND COALESCE(password, '') <> '' THEN 0 WHEN roleid = 5 AND status = 'Active' THEN 1 WHEN roleid = 5 THEN 2 ELSE 3 END, userid ASC`) } // milerNotActiveMessage is what a rider is told when their account cannot sign // in; a blocked rider is told so, rather than a generic "not active". func milerNotActiveMessage(status string) string { if strings.EqualFold(status, constants.MilerBlocked) { return "your account is blocked — contact your manager" } return "miler account is not active" } // milerIsBlocked reports whether ops have blocked this rider. Checked on the // rider-app actions that would otherwise undo a block for a rider who was // already signed in when it happened (starting duty, setting availability). func milerIsBlocked(milerUserID int) bool { var p models.MilerProfile if db.DB.Select("availabilitystatus").Where("userid = ?", milerUserID).First(&p).Error == nil && strings.EqualFold(p.Availabilitystatus, constants.MilerBlocked) { return true } var u models.AppUser return db.DB.Select("status").Where("userid = ?", milerUserID).First(&u).Error == nil && strings.EqualFold(u.Status, constants.MilerBlocked) } // milerPhoneTaken reports whether another rider already signs in with this // number (exceptUserID is the rider being edited, 0 when creating). func milerPhoneTaken(phone string, configID, exceptUserID int) bool { var n int64 db.DB.Model(&models.AppUser{}). Where("contactno = ? AND configid = ? AND roleid = 5 AND userid <> ?", phone, configID, exceptUserID). Count(&n) return n > 0 } // checkMilerHub makes sure a hub exists and sits in the rider's city. A nil hub // (no base) is always fine. func checkMilerHub(hubID *int, cityID int) string { if hubID == nil { return "" } var hub models.Hub if err := db.DB.Select("hubid", "applocationid").Where("hubid = ? AND deletedat IS NULL", *hubID).First(&hub).Error; err != nil { return "that hub does not exist" } if hub.Applocationid != cityID { return "that hub is in a different city from the rider — choose a hub in the rider's city" } return "" } // UnblockMiler — PUT /admin/milers/:id/unblock // The counterpart of BlockMiler: the rider can sign in again and is Offline // until they start duty. Same scoping as block (a client login, its own riders). func UnblockMiler(c *fiber.Ctx) error { id, _ := strconv.Atoi(c.Params("id")) profile, ok := findMilerForConsole(c, id) if !ok { return utils.NotFound(c, "miler not found") } if !milerIsBlocked(profile.Userid) { return utils.BadRequest(c, "this miler is not blocked") } now := time.Now() err := db.DB.Transaction(func(tx *gorm.DB) error { if err := tx.Model(&models.MilerProfile{}).Where("milerprofileid = ?", profile.Milerprofileid). Updates(map[string]interface{}{"availabilitystatus": constants.MilerOffline, "updatedat": now}).Error; err != nil { return err } return tx.Model(&models.AppUser{}).Where("userid = ? AND status = ?", profile.Userid, constants.MilerBlocked). Update("status", "Active").Error }) if err != nil { return utils.Internal(c, "failed to unblock miler") } profile.Availabilitystatus = constants.MilerOffline profile.Updatedat = now return utils.OK(c, profile) }