136 lines
5.0 KiB
Go
136 lines
5.0 KiB
Go
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)
|
|
}
|