345 lines
12 KiB
Go
345 lines
12 KiB
Go
package repositories
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"nearle/models"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type UserRepository interface {
|
|
GetAllUsers(roleID, tenantID, pageno, pagesize int, keyword string) ([]models.UserInfo, error)
|
|
GetUserByID(uid int) (models.UserInfo, error)
|
|
Login(user models.User) (models.UserInfo, error)
|
|
FindUserID(authname, contactno string, configid int) (int, error)
|
|
UpdateStaff(user models.User) error
|
|
GetUserByAuthname(authname string, configid int) (int, string, string)
|
|
GetUserByContactNo(contactno string, configid int) (int, string, string)
|
|
UpdateFCMToken(userid int, token string) error
|
|
GetTenantUserById(userid int) models.TenantUserInfo
|
|
CreateUser(user models.User) (int, error)
|
|
GetUserById(uid int) (models.UserInfo, error)
|
|
GetUserLogin(field, value string, configid int) (int, string, string, int)
|
|
UpdateUserFcmToken(uid int, token string) error
|
|
GetLocationStatus(locationid int) string
|
|
DeleteUser(userid int) error
|
|
}
|
|
|
|
type userRepository struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewUserRepository(db *gorm.DB) UserRepository {
|
|
return &userRepository{db: db}
|
|
}
|
|
|
|
func (r *userRepository) GetAllUsers(roleID, tenantID, pageno, pagesize int, keyword string) ([]models.UserInfo, error) {
|
|
var users []models.UserInfo
|
|
var params []interface{}
|
|
var queryBuilder strings.Builder
|
|
|
|
offset := (pageno - 1) * pagesize
|
|
|
|
queryBuilder.WriteString(`SELECT
|
|
a.userid, a.authname, a.email, a.configid, a.roleid, a.authmode, a.contactno,
|
|
a.firstname, a.lastname, CONCAT(a.firstname, ' ', a.lastname) AS fullname,
|
|
a.address, a.suburb, a.city, a.state, a.postcode,
|
|
a.userfcmtoken, a.pin, a.deviceid, a.devicetype, a.tenantid, a.status, a.shiftid,
|
|
a.applocationid, b.locationname AS applocation, b.latitude AS applatitude, concat(c.starttime, ' - ', c.endtime) as shiftname,
|
|
b.longitude AS applongitude, b.radius AS appradius
|
|
FROM app_users a
|
|
LEFT JOIN app_location b ON a.applocationid = b.applocationid
|
|
LEFT JOIN ridershifts c ON a.shiftid = c.shiftid
|
|
WHERE 1=1`)
|
|
|
|
// Till accounts are not Nearle Daily users and must not be listed as though
|
|
// they were. The two products share this table and nothing else: a cashier
|
|
// has no app login, no rider shift and no back-office screen, so a row
|
|
// returned here is one every action on the page would fail against.
|
|
//
|
|
// Asking for 7 or 8 by name still works, so the POS console can read its own
|
|
// people through the same endpoint — this hides them from the general list,
|
|
// it does not make them unreachable.
|
|
if roleID != models.PosRoleSupervisor && roleID != models.PosRoleCashier {
|
|
queryBuilder.WriteString(" AND COALESCE(a.roleid, 0) NOT IN (7, 8)")
|
|
}
|
|
|
|
if roleID != 0 {
|
|
queryBuilder.WriteString(" AND a.roleid = ?")
|
|
params = append(params, roleID)
|
|
}
|
|
|
|
if tenantID != 0 {
|
|
queryBuilder.WriteString(" AND a.tenantid = ?")
|
|
params = append(params, tenantID)
|
|
}
|
|
|
|
if keyword != "" {
|
|
queryBuilder.WriteString(` AND (
|
|
LOWER(a.firstname) LIKE ? OR
|
|
LOWER(a.contactno) LIKE ? OR
|
|
LOWER(a.suburb) LIKE ?
|
|
)`)
|
|
search := "%" + strings.ToLower(keyword) + "%"
|
|
params = append(params, search, search, search)
|
|
}
|
|
|
|
queryBuilder.WriteString(" ORDER BY a.userid DESC LIMIT ? OFFSET ?")
|
|
params = append(params, pagesize, offset)
|
|
|
|
if err := r.db.Raw(queryBuilder.String(), params...).Scan(&users).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for i := range users {
|
|
users[i].Status = strings.ToLower(users[i].Status)
|
|
}
|
|
|
|
return users, nil
|
|
}
|
|
|
|
func (r *userRepository) GetUserByID(uid int) (models.UserInfo, error) {
|
|
var user models.UserInfo
|
|
|
|
q := `SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno,
|
|
a.firstname,a.lastname,concat(a.firstname,' ',a.lastname) as fullname,a.password,a.address,a.suburb,a.city,a.state,a.postcode,
|
|
a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.tenantid,a.shiftid,a.locationid,
|
|
a.applocationid,b.locationname as applocation,b.latitude as applatitude,b.longitude as applongitude, b.radius as appradius,
|
|
concat(c.starttime, ' - ', c.endtime) as shiftname, a.status
|
|
FROM app_users a
|
|
INNER JOIN app_location b ON a.applocationid = b.applocationid
|
|
LEFT JOIN ridershifts c ON a.shiftid = c.shiftid
|
|
WHERE a.userid = ?`
|
|
|
|
if err := r.db.Raw(q, uid).Scan(&user).Error; err != nil {
|
|
return models.UserInfo{}, err
|
|
}
|
|
|
|
user.Status = strings.ToLower(user.Status)
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (r *userRepository) Login(user models.User) (models.UserInfo, error) {
|
|
var uid int
|
|
var userInfo models.UserInfo
|
|
|
|
var q string
|
|
if user.Authname != "" {
|
|
q = `SELECT a.userid FROM app_users a
|
|
WHERE a.authname = ? AND a.configid = ?
|
|
AND COALESCE(a.roleid, 0) NOT IN (7, 8)`
|
|
if err := r.db.Raw(q, user.Authname, user.Configid).Scan(&uid).Error; err != nil {
|
|
return models.UserInfo{}, err
|
|
}
|
|
} else {
|
|
q = `SELECT a.userid FROM app_users a
|
|
WHERE a.contactno = ? AND a.configid = ?
|
|
AND COALESCE(a.roleid, 0) NOT IN (7, 8)`
|
|
if err := r.db.Raw(q, user.Contactno, user.Configid).Scan(&uid).Error; err != nil {
|
|
return models.UserInfo{}, err
|
|
}
|
|
}
|
|
|
|
if uid == 0 {
|
|
return models.UserInfo{}, gorm.ErrRecordNotFound
|
|
}
|
|
|
|
// ✅ Update FCM token in app_users table if provided
|
|
if user.Userfcmtoken != "" {
|
|
if err := r.db.Table("app_users").
|
|
Where("userid = ?", uid).
|
|
Update("userfcmtoken", user.Userfcmtoken).Error; err != nil {
|
|
return models.UserInfo{}, err
|
|
}
|
|
}
|
|
|
|
userInfo, err := r.GetUserByID(uid)
|
|
if err != nil {
|
|
return models.UserInfo{}, err
|
|
}
|
|
|
|
return userInfo, nil
|
|
}
|
|
|
|
|
|
func (r *userRepository) FindUserID(authname, contactno string, configid int) (int, error) {
|
|
var uid int
|
|
var query string
|
|
|
|
if authname != "" {
|
|
query = `SELECT a.userid FROM app_users a
|
|
WHERE a.authname = ? AND a.configid = ?
|
|
AND COALESCE(a.roleid, 0) NOT IN (7, 8)`
|
|
if err := r.db.Raw(query, authname, configid).Scan(&uid).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
} else {
|
|
query = `SELECT a.userid FROM app_users a
|
|
WHERE a.contactno = ? AND a.configid = ?
|
|
AND COALESCE(a.roleid, 0) NOT IN (7, 8)`
|
|
if err := r.db.Raw(query, contactno, configid).Scan(&uid).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
}
|
|
return uid, nil
|
|
}
|
|
|
|
|
|
|
|
func (r *userRepository) UpdateStaff(user models.User) error {
|
|
return r.db.Table("app_users").Where("userid = ?", user.Userid).Updates(&user).Error
|
|
}
|
|
|
|
// A till account is not a Nearle Daily user. The two products share this table
|
|
// and nothing else, so every way into the application excludes roles 7 and 8 in
|
|
// the lookup itself: a cashier is not "refused", they are simply not found.
|
|
//
|
|
// Doing it in the query rather than after it is deliberate. A check bolted on
|
|
// afterwards has to be repeated at each of these call sites and is one edit away
|
|
// from being forgotten at one of them, and that one would be the hole.
|
|
func (r *userRepository) GetUserByAuthname(authname string, configid int) (int, string, string) {
|
|
var uid int
|
|
var password, status string
|
|
query := `SELECT userid, password, status FROM app_users
|
|
WHERE authname = ? AND configid = ?
|
|
AND COALESCE(roleid, 0) NOT IN (7, 8)`
|
|
r.db.Raw(query, authname, configid).Row().Scan(&uid, &password, &status)
|
|
return uid, password, status
|
|
}
|
|
|
|
func (r *userRepository) GetUserByContactNo(contactno string, configid int) (int, string, string) {
|
|
var uid int
|
|
var password, status string
|
|
query := `SELECT userid, password, status FROM app_users
|
|
WHERE contactno = ? AND configid = ?
|
|
AND COALESCE(roleid, 0) NOT IN (7, 8)`
|
|
r.db.Raw(query, contactno, configid).Row().Scan(&uid, &password, &status)
|
|
return uid, password, status
|
|
}
|
|
|
|
func (r *userRepository) UpdateFCMToken(userid int, token string) error {
|
|
query := `UPDATE app_users SET userfcmtoken = ? WHERE userid = ?`
|
|
return r.db.Exec(query, token, userid).Error
|
|
}
|
|
|
|
// The one tenant-user read. There used to be two.
|
|
//
|
|
// A `GetTenantUserByID` sat beside this — one capital letter apart, twenty-eight
|
|
// columns short, selecting only userid, authname, contactno, tenantid and
|
|
// tenantname. `TenantLogin` called that one, so the mobile login it served
|
|
// answered with a record whose name, branch, region and coordinates were blank,
|
|
// and the route was quietly pointed at a different handler to work around it.
|
|
// Deleted rather than documented: two functions this similar, where picking the
|
|
// wrong one fails silently, is a trap and not an API.
|
|
func (r *userRepository) GetTenantUserById(userid int) models.TenantUserInfo {
|
|
var info models.TenantUserInfo
|
|
|
|
// app_location is LEFT JOINed (not INNER) so a user with no matching
|
|
// tenant/applocation row — e.g. a super admin, tenantid=0 — still comes
|
|
// back with their own fields (issuperadmin included) instead of the
|
|
// whole query silently returning zero rows.
|
|
query := `
|
|
SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno,
|
|
a.firstname,a.lastname,concat(a.firstname,' ',a.lastname) as fullname,
|
|
a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.tenantid,a.locationid,a.applocationid,
|
|
a.issuperadmin,
|
|
b.partnerid,b.moduleid,b.categoryid as categoryid,b.subcategoryid as subcategoryid,
|
|
b.applocationid,b.tenantname,b.address as tenantaddress,b.state as tenantstate,b.city as tenantcity,
|
|
b.postcode as tenantpostcode,b.latitude as tenantlat,b.longitude as tenantlong,c.locationname AS applocation,
|
|
c.latitude as applatitude,c.longitude as applongitude,c.radius as appradius, d.categoryname, e.locationname,
|
|
a.shiftid, concat(f.starttime, ' - ', f.endtime) as shiftname, a.status
|
|
from app_users a
|
|
LEFT JOIN tenants b ON a.tenantid=b.tenantid
|
|
LEFT JOIN app_location c on c.applocationid=b.applocationid
|
|
LEFT JOIN app_category d ON b.categoryid=d.categoryid
|
|
LEFT JOIN tenantlocations e ON a.locationid=e.locationid
|
|
LEFT JOIN ridershifts f ON a.shiftid = f.shiftid
|
|
WHERE a.userid = ?
|
|
`
|
|
|
|
r.db.Raw(query, userid).Scan(&info)
|
|
print(query)
|
|
return info
|
|
}
|
|
|
|
func (r *userRepository) CreateUser(user models.User) (int, error) {
|
|
tx := r.db.Begin()
|
|
|
|
if err := tx.Table("app_users").Create(&user).Error; err != nil {
|
|
tx.Rollback()
|
|
return 0, err
|
|
}
|
|
|
|
if err := tx.Commit().Error; err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return user.Userid, nil
|
|
}
|
|
|
|
// GetUserById reads one person back.
|
|
//
|
|
// The app_location join is LEFT, not INNER, and that is the whole fix. A user is
|
|
// not required to belong to an app location, and an INNER JOIN did not "filter"
|
|
// those users — it made them unreadable. CreateUser looks the new row up through
|
|
// here to return it, so creating a store user with no applocationid answered 201
|
|
// with userid 0 and every field blank. The user existed and every listing showed
|
|
// it; only the response meant to confirm the creation came back empty, which
|
|
// reads as a failure that silently succeeded.
|
|
func (r *userRepository) GetUserById(uid int) (models.UserInfo, error) {
|
|
var user models.UserInfo
|
|
|
|
q1 := `SELECT a.userid,a.authname,a.email,a.configid,a.roleid,a.authmode,a.contactno,
|
|
a.firstname,a.lastname,concat(a.firstname,' ',a.lastname) as fullname,a.password,a.address,a.suburb,a.city,a.state,a.postcode,
|
|
a.userfcmtoken,a.pin,a.deviceid,a.devicetype,a.tenantid,a.shiftid,
|
|
a.applocationid,b.locationname as applocation,b.latitude as applatitude,b.longitude as applongitude, b.radius as appradius , concat(c.starttime, ' - ', c.endtime) as shiftname, a.status
|
|
FROM app_users a
|
|
LEFT JOIN app_location b on a.applocationid=b.applocationid
|
|
LEFT JOIN ridershifts c ON a.shiftid = c.shiftid
|
|
WHERE a.userid= ?`
|
|
|
|
if err := r.db.Raw(q1, uid).Scan(&user).Error; err != nil {
|
|
return models.UserInfo{}, err
|
|
}
|
|
|
|
user.Status = strings.ToLower(user.Status)
|
|
|
|
return user, nil
|
|
}
|
|
|
|
func (r *userRepository) GetUserLogin(field, value string, configid int) (int, string, string, int) {
|
|
var uid, roleid int
|
|
var password, status string
|
|
|
|
query := fmt.Sprintf(`
|
|
SELECT userid, password, status, roleid
|
|
FROM app_users
|
|
WHERE %s = ? AND configid = ?
|
|
AND COALESCE(roleid, 0) NOT IN (7, 8)`, field)
|
|
|
|
r.db.Raw(query, value, configid).Row().Scan(&uid, &password, &status, &roleid)
|
|
|
|
return uid, password, status, roleid
|
|
}
|
|
|
|
func (r *userRepository) UpdateUserFcmToken(userid int, fcmToken string) error {
|
|
query := `UPDATE app_users SET userfcmtoken = ? WHERE userid = ?`
|
|
return r.db.Exec(query, fcmToken, userid).Error
|
|
}
|
|
|
|
func (r *userRepository) GetLocationStatus(locationid int) string {
|
|
var status string
|
|
query := `SELECT status FROM tenantlocations WHERE locationid = ?`
|
|
r.db.Raw(query, locationid).Row().Scan(&status)
|
|
return status
|
|
}
|
|
|
|
func (r *userRepository) DeleteUser(userid int) error {
|
|
return r.db.Table("app_users").Where("userid = ?", userid).Delete(&models.User{}).Error
|
|
}
|
|
|
|
|