Files
doormile_backend/controllers/clientController.go
2026-06-22 17:43:40 +05:30

387 lines
10 KiB
Go

package controllers
import (
"strconv"
"strings"
"doormile/db"
"doormile/dto"
"doormile/models"
"doormile/utils"
"github.com/gofiber/fiber/v2"
)
func RegisterClient(c *fiber.Ctx) error {
var input dto.CreateClientRequest
if err := c.BodyParser(&input); err != nil {
return utils.BadRequest(c, "invalid request body")
}
if input.FirstName == "" || input.Phone == "" {
return utils.BadRequest(c, "first_name and phone are required")
}
if input.RegistrationSource == "" {
input.RegistrationSource = DetermineSource(string(c.Request().Header.UserAgent()))
}
if input.DataConsent == "" {
input.DataConsent = "full"
}
if input.Status == "" {
input.Status = "newClient"
}
createAuth := input.Email != "" && input.Password != ""
var hashedPassword string
if createAuth {
var err error
hashedPassword, err = utils.HashPassword(input.Password)
if err != nil {
return utils.Internal(c, "failed to process registration")
}
}
tx := db.DB.Begin()
var existingClient models.DoormileClient
if tx.Where("phone = ?", input.Phone).First(&existingClient).Error == nil {
tx.Rollback()
return utils.Conflict(c, "a client with this phone number already exists")
}
if createAuth {
var existingAuth models.DoormileAuth
if tx.Where("email = ?", input.Email).First(&existingAuth).Error == nil {
tx.Rollback()
return utils.Conflict(c, "this email address is already registered")
}
}
client := models.DoormileClient{
FirstName: input.FirstName,
LastName: input.LastName,
Phone: input.Phone,
Address: input.Address,
City: input.City,
State: input.State,
Neighbourhood: input.Neighbourhood,
Pincode: input.Pincode,
SurveyLat: input.SurveyLat,
SurveyLong: input.SurveyLong,
SurveyAddress: input.SurveyAddress,
SurveyZone: input.SurveyZone,
SurveyPincode: input.SurveyPincode,
BusinessType: input.BusinessType,
Status: input.Status,
ShippingFrequency: input.ShippingFrequency,
LogisticsSegment: input.LogisticsSegment,
TransitFrom: input.TransitFrom,
TransitTo: input.TransitTo,
DataConsent: input.DataConsent,
RegistrationSource: input.RegistrationSource,
RegisteredByID: input.RegisteredByID,
}
if input.DataConsent == "full" {
client.ParcelVolume = input.ParcelVolume
client.ActiveContracts = input.ActiveContracts
client.LogisticsProvider = input.LogisticsProvider
client.ProviderEfficiency = input.ProviderEfficiency
client.Notes = input.Notes
} else {
client.ParcelVolume = 0
client.ActiveContracts = 0
client.LogisticsProvider = "Not disclosed"
client.ProviderEfficiency = ""
client.Notes = ""
}
if err := tx.Create(&client).Error; err != nil {
tx.Rollback()
return utils.Internal(c, "failed to create client")
}
email, role := "", ""
if createAuth {
auth := models.DoormileAuth{
ClientID: &client.ID,
Email: input.Email,
PasswordHash: hashedPassword,
Role: "user",
}
if err := tx.Create(&auth).Error; err != nil {
tx.Rollback()
return utils.Internal(c, "failed to create auth credentials")
}
email = auth.Email
role = auth.Role
}
tx.Commit()
return utils.Created(c, buildClientResponse(client, email, role))
}
func GetClients(c *fiber.Ctx) error {
var clients []models.DoormileClient
if err := db.DB.Find(&clients).Error; err != nil {
return utils.Internal(c, "failed to fetch clients")
}
// Bulk load auth records once and map by client_id for O(1) lookup
var auths []models.DoormileAuth
db.DB.Find(&auths)
authByClientID := make(map[uint64]models.DoormileAuth, len(auths))
for _, a := range auths {
if a.ClientID != nil {
authByClientID[*a.ClientID] = a
}
}
responses := make([]dto.ClientResponse, 0, len(clients))
for _, client := range clients {
auth := authByClientID[client.ID]
responses = append(responses, buildClientResponse(client, auth.Email, auth.Role))
}
return utils.List(c, responses, int64(len(responses)))
}
func GetClientDetails(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil || id == 0 {
return utils.BadRequest(c, "invalid client ID")
}
var client models.DoormileClient
if err := db.DB.First(&client, id).Error; err != nil {
return utils.NotFound(c, "client not found")
}
var auth models.DoormileAuth
email, role := "", ""
if db.DB.Where("client_id = ?", id).First(&auth).Error == nil {
email = auth.Email
role = auth.Role
}
return utils.OK(c, buildClientResponse(client, email, role))
}
func UpdateClient(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil || id == 0 {
return utils.BadRequest(c, "invalid client ID")
}
var client models.DoormileClient
if err := db.DB.First(&client, id).Error; err != nil {
return utils.NotFound(c, "client not found")
}
var input dto.CreateClientRequest
if err := c.BodyParser(&input); err != nil {
return utils.BadRequest(c, "invalid request body")
}
if input.FirstName != "" {
client.FirstName = input.FirstName
}
if input.LastName != "" {
client.LastName = input.LastName
}
if input.Phone != "" {
client.Phone = input.Phone
}
if input.Address != "" {
client.Address = input.Address
}
if input.City != "" {
client.City = input.City
}
if input.State != "" {
client.State = input.State
}
if input.Neighbourhood != "" {
client.Neighbourhood = input.Neighbourhood
}
if input.Pincode != "" {
client.Pincode = input.Pincode
}
if input.SurveyLat != 0 {
client.SurveyLat = input.SurveyLat
}
if input.SurveyLong != 0 {
client.SurveyLong = input.SurveyLong
}
if input.SurveyAddress != "" {
client.SurveyAddress = input.SurveyAddress
}
if input.SurveyZone != "" {
client.SurveyZone = input.SurveyZone
}
if input.SurveyPincode != "" {
client.SurveyPincode = input.SurveyPincode
}
if input.BusinessType != "" {
client.BusinessType = input.BusinessType
}
if input.Status != "" {
client.Status = input.Status
}
if input.ShippingFrequency != "" {
client.ShippingFrequency = input.ShippingFrequency
}
if input.LogisticsSegment != "" {
client.LogisticsSegment = input.LogisticsSegment
}
if input.TransitFrom != "" {
client.TransitFrom = input.TransitFrom
}
if input.TransitTo != "" {
client.TransitTo = input.TransitTo
}
if input.RegistrationSource != "" {
client.RegistrationSource = input.RegistrationSource
}
if input.RegisteredByID != 0 {
client.RegisteredByID = input.RegisteredByID
}
client.Notes = input.Notes
if input.DataConsent != "" {
client.DataConsent = input.DataConsent
}
if client.DataConsent == "full" {
client.ParcelVolume = input.ParcelVolume
client.ActiveContracts = input.ActiveContracts
client.LogisticsProvider = input.LogisticsProvider
client.ProviderEfficiency = input.ProviderEfficiency
} else {
client.ParcelVolume = 0
client.ActiveContracts = 0
client.LogisticsProvider = "Not disclosed"
client.ProviderEfficiency = ""
client.Notes = ""
}
tx := db.DB.Begin()
if err := tx.Save(&client).Error; err != nil {
tx.Rollback()
return utils.Internal(c, "failed to update client")
}
var auth models.DoormileAuth
email, role := "", ""
if db.DB.Where("client_id = ?", id).First(&auth).Error == nil {
email = auth.Email
role = auth.Role
}
authUpdated := false
if input.Email != "" && input.Email != auth.Email {
auth.Email = input.Email
email = input.Email
authUpdated = true
}
if input.Password != "" {
hashed, err := utils.HashPassword(input.Password)
if err != nil {
tx.Rollback()
return utils.Internal(c, "failed to process password update")
}
auth.PasswordHash = hashed
authUpdated = true
}
if authUpdated && auth.ID != 0 {
if err := tx.Save(&auth).Error; err != nil {
tx.Rollback()
return utils.Internal(c, "failed to update credentials")
}
}
tx.Commit()
return utils.OK(c, buildClientResponse(client, email, role))
}
func DeleteClient(c *fiber.Ctx) error {
id, err := strconv.ParseUint(c.Params("id"), 10, 64)
if err != nil || id == 0 {
return utils.BadRequest(c, "invalid client ID")
}
var client models.DoormileClient
if err := db.DB.First(&client, id).Error; err != nil {
return utils.NotFound(c, "client not found")
}
tx := db.DB.Begin()
if err := tx.Where("client_id = ?", id).Delete(&models.DoormileAuth{}).Error; err != nil {
tx.Rollback()
return utils.Internal(c, "failed to delete client credentials")
}
if err := tx.Delete(&client).Error; err != nil {
tx.Rollback()
return utils.Internal(c, "failed to delete client")
}
tx.Commit()
return utils.Message(c, "client deleted successfully")
}
func buildClientResponse(client models.DoormileClient, email, role string) dto.ClientResponse {
return dto.ClientResponse{
ID: client.ID,
CreatedAt: client.CreatedAt.Format("2006-01-02T15:04:05Z"),
LastUpdated: client.UpdatedAt.Format("2006-01-02"),
FirstName: client.FirstName,
LastName: client.LastName,
Email: email,
Phone: client.Phone,
Address: client.Address,
City: client.City,
State: client.State,
Neighbourhood: client.Neighbourhood,
Pincode: client.Pincode,
SurveyLat: client.SurveyLat,
SurveyLong: client.SurveyLong,
SurveyAddress: client.SurveyAddress,
SurveyZone: client.SurveyZone,
SurveyPincode: client.SurveyPincode,
BusinessType: client.BusinessType,
Status: client.Status,
ShippingFrequency: client.ShippingFrequency,
LogisticsSegment: client.LogisticsSegment,
TransitFrom: client.TransitFrom,
TransitTo: client.TransitTo,
ParcelVolume: client.ParcelVolume,
ActiveContracts: client.ActiveContracts,
LogisticsProvider: client.LogisticsProvider,
ProviderEfficiency: client.ProviderEfficiency,
Notes: client.Notes,
DataConsent: client.DataConsent,
RegistrationSource: client.RegistrationSource,
RegisteredByID: client.RegisteredByID,
Role: role,
}
}
func DetermineSource(userAgent string) string {
ua := strings.ToLower(userAgent)
if strings.Contains(ua, "dart") || strings.Contains(ua, "flutter") || strings.Contains(ua, "doormile") {
return "mobile"
}
if strings.Contains(ua, "mozilla") || strings.Contains(ua, "chrome") || strings.Contains(ua, "safari") {
return "web"
}
return "api_tool"
}