Merge branch 'master' into feat/drivvo-import

This commit is contained in:
Alf Sebastian Houge
2023-01-28 22:22:24 +01:00
committed by GitHub
58 changed files with 42546 additions and 13205 deletions

4
server/.gitignore vendored
View File

@@ -12,6 +12,10 @@
*.out
*.db
# MS VSCode
.vscode
__debug_bin
# Dependency directories (remove the comment below to include it)
# vendor/
assets/*

View File

@@ -16,7 +16,7 @@ RUN go build -o ./app ./main.go
FROM alpine:latest
LABEL org.opencontainers.image.source="https://github.com/akhilrex/hammond"
LABEL org.opencontainers.image.source="https://github.com/alfhou/hammond"
ENV CONFIG=/config
ENV DATA=/assets
@@ -38,4 +38,4 @@ COPY dist ./dist
EXPOSE 3000
ENTRYPOINT ["./app"]
ENTRYPOINT ["./app"]

View File

@@ -25,6 +25,33 @@ func RandString(n int) string {
return string(b)
}
// A helper to convert from litres to gallon
func LitreToGallon(litres float32) float32 {
gallonConversionFactor := 0.21997
return litres * float32(gallonConversionFactor);
}
// A helper to convert from gallon to litres
func GallonToLitre(gallons float32) float32 {
litreConversionFactor := 3.785412
return gallons * float32(litreConversionFactor);
}
// A helper to convert from km to miles
func KmToMiles(km float32) float32 {
kmConversionFactor := 0.62137119
return km * float32(kmConversionFactor);
}
// A helper to convert from miles to km
func MilesToKm(miles float32) float32 {
milesConversionFactor := 1.609344
return miles * float32(milesConversionFactor);
}
// A Util function to generate jwt_token which can be used in the request header
func GenToken(id string, role db.Role) (string, string) {
jwt_token := jwt.New(jwt.GetSigningMethod("HS256"))

View File

@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"os"
"strings"
"github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/db"
@@ -91,20 +92,20 @@ func userLogin(c *gin.Context) {
c.JSON(http.StatusUnprocessableEntity, common.NewValidatorError(err))
return
}
user, err := db.FindOneUser(&db.User{Email: loginRequest.Email})
user, err := db.FindOneUser(&db.User{Email: strings.ToLower(loginRequest.Email)})
if err != nil {
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("Not Registered email or invalid password")))
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("not Registered email or invalid password")))
return
}
if user.CheckPassword(loginRequest.Password) != nil {
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("Not Registered email or invalid password")))
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("not Registered email or invalid password")))
return
}
if user.IsDisabled {
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("Your user has been disabled by the admin. Please contact them to get it re-enabled.")))
c.JSON(http.StatusForbidden, common.NewError("login", errors.New("your user has been disabled by the admin. Please contact them to get it re-enabled")))
return
}
UpdateContextUserModel(c, user.ID)
@@ -114,7 +115,7 @@ func userLogin(c *gin.Context) {
Email: user.Email,
Token: token,
RefreshToken: refreshToken,
Role: user.RoleDetail().Long,
Role: user.RoleDetail().Key,
}
c.JSON(http.StatusOK, response)
}
@@ -148,7 +149,7 @@ func refresh(c *gin.Context) {
Email: user.Email,
Token: token,
RefreshToken: refreshToken,
Role: user.RoleDetail().Long,
Role: user.RoleDetail().Key,
}
c.JSON(http.StatusOK, response)
} else {
@@ -170,16 +171,16 @@ func changePassword(c *gin.Context) {
user, err := service.GetUserById(c.GetString("userId"))
if err != nil {
c.JSON(http.StatusForbidden, common.NewError("changePassword", errors.New("Not Registered email or invalid password")))
c.JSON(http.StatusForbidden, common.NewError("changePassword", errors.New("not Registered email or invalid password")))
return
}
if user.CheckPassword(request.OldPassword) != nil {
c.JSON(http.StatusForbidden, common.NewError("changePassword", errors.New("Incorrect old password")))
c.JSON(http.StatusForbidden, common.NewError("changePassword", errors.New("incorrect old password")))
return
}
user.SetPassword(request.NewPassword)
success, err := service.UpdatePassword(user.ID, request.NewPassword)
success, _ := service.UpdatePassword(user.ID, request.NewPassword)
c.JSON(http.StatusOK, success)
}

View File

@@ -23,8 +23,8 @@ func stripBearerPrefixFromTokenString(tok string) (string, error) {
// Extract token from Authorization header
// Uses PostExtractionFilter to strip "TOKEN " prefix from header
var AuthorizationHeaderExtractor = &request.PostExtractionFilter{
request.HeaderExtractor{"Authorization"},
stripBearerPrefixFromTokenString,
Extractor: request.HeaderExtractor{"Authorization"},
Filter: stripBearerPrefixFromTokenString,
}
// Extractor for OAuth2 access tokens. Looks in 'Authorization'

View File

@@ -25,7 +25,7 @@ func getMileageForVehicle(c *gin.Context) {
return
}
fillups, err := service.GetMileageByVehicleId(searchByIdQuery.Id, model.Since)
fillups, err := service.GetMileageByVehicleId(searchByIdQuery.Id, model.Since, model.MileageOption)
if err != nil {
c.JSON(http.StatusUnprocessableEntity, common.NewError("getMileageForVehicle", err))
return

View File

@@ -51,7 +51,7 @@ func migrate(c *gin.Context) {
canMigrate, _, _ := db.CanMigrate(request.Url)
if !canMigrate {
c.JSON(http.StatusBadRequest, fmt.Errorf("cannot migrate database. please check connection string."))
c.JSON(http.StatusBadRequest, fmt.Errorf("cannot migrate database. please check connection string"))
return
}

View File

@@ -397,7 +397,7 @@ func deleteVehicle(c *gin.Context) {
return
}
if !canDelete {
c.JSON(http.StatusUnprocessableEntity, common.NewError("shareVehicle", errors.New("You are not allowed to delete this vehicle.")))
c.JSON(http.StatusUnprocessableEntity, common.NewError("shareVehicle", errors.New("you are not allowed to delete this vehicle")))
return
}
err = service.DeleteVehicle(searchByIdQuery.Id)

View File

@@ -60,6 +60,7 @@ type Vehicle struct {
Base
Nickname string `json:"nickname"`
Registration string `json:"registration"`
VIN string `json:"vin"`
Make string `json:"make"`
Model string `json:"model"`
YearOfManufacture int `json:"yearOfManufacture"`
@@ -195,3 +196,50 @@ type VehicleAttachment struct {
VehicleID string `gorm:"primaryKey" json:"vehicleId"`
Title string `json:"title"`
}
type VehicleAlert struct {
Base
VehicleID string `json:"vehicleId"`
Vehicle Vehicle `json:"-"`
UserID string `json:"userId"`
User User `json:"user"`
Title string `json:"title"`
Comments string `json:"comments"`
StartDate time.Time `json:"date"`
StartOdoReading int `json:"startOdoReading"`
DistanceUnit DistanceUnit `json:"distanceUnit"`
AlertFrequency AlertFrequency `json:"alertFrequency"`
OdoFrequency int `json:"odoFrequency"`
DayFrequency int `json:"dayFrequency"`
AlertAllUsers bool `json:"alertAllUsers"`
IsActive bool `json:"isActive"`
EndDate *time.Time `json:"endDate"`
AlertType AlertType `json:"alertType"`
}
type AlertOccurance struct {
Base
VehicleID string `json:"vehicleId"`
Vehicle Vehicle `json:"-"`
VehicleAlertID string `json:"vehicleAlertId"`
VehicleAlert VehicleAlert `json:"-"`
UserID string `json:"userId"`
User User `json:"-"`
OdoReading int `json:"odoReading"`
Date *time.Time `json:"date"`
ProcessDate *time.Time `json:"processDate"`
AlertProcessType AlertType `json:"alertProcessType"`
CompleteDate *time.Time `json:"completeDate"`
}
type Notification struct {
Base
Title string `json:"title"`
Content string `json:"content"`
UserID string `json:"userId"`
VehicleID string `json:"vehicleId"`
User User `json:"-"`
Date time.Time `json:"date"`
ReadDate *time.Time `json:"readDate"`
ParentID string `json:"parentId"`
ParentType string `json:"parentType"`
}

View File

@@ -117,7 +117,7 @@ func UnshareVehicle(vehicleId, userId string) error {
return nil
}
if mapping.IsOwner {
return fmt.Errorf("Cannot unshare owner")
return fmt.Errorf("cannot unshare owner")
}
result := DB.Where("id=?", mapping.ID).Delete(&UserVehicle{})
return result.Error
@@ -160,6 +160,11 @@ func GetFillupsByVehicleId(id string) (*[]Fillup, error) {
result := DB.Preload(clause.Associations).Order("date desc").Find(&obj, &Fillup{VehicleID: id})
return &obj, result.Error
}
func GetLatestFillupsByVehicleId(id string) (*Fillup, error) {
var obj Fillup
result := DB.Preload(clause.Associations).Order("date desc").First(&obj, &Fillup{VehicleID: id})
return &obj, result.Error
}
func GetFillupsByVehicleIdSince(id string, since time.Time) (*[]Fillup, error) {
var obj []Fillup
result := DB.Where("date >= ? AND vehicle_id = ?", since, id).Preload(clause.Associations).Order("date desc").Find(&obj)
@@ -190,6 +195,11 @@ func GetExpensesByVehicleId(id string) (*[]Expense, error) {
result := DB.Preload(clause.Associations).Order("date desc").Find(&obj, &Expense{VehicleID: id})
return &obj, result.Error
}
func GetLatestExpenseByVehicleId(id string) (*Expense, error) {
var obj Expense
result := DB.Preload(clause.Associations).Order("date desc").First(&obj, &Expense{VehicleID: id})
return &obj, result.Error
}
func GetExpenseById(id string) (*Expense, error) {
var obj Expense
result := DB.Preload(clause.Associations).First(&obj, "id=?", id)
@@ -271,6 +281,29 @@ func GetVehicleAttachments(vehicleId string) (*[]Attachment, error) {
}
return &attachments, nil
}
func GeAlertById(id string) (*VehicleAlert, error) {
var alert VehicleAlert
result := DB.Preload(clause.Associations).First(&alert, "id=?", id)
return &alert, result.Error
}
func GetAlertOccurenceByAlertId(id string) (*[]AlertOccurance, error) {
var alertOccurance []AlertOccurance
result := DB.Preload(clause.Associations).Order("created_at desc").Find(&alertOccurance, "vehicle_alert_id=?", id)
return &alertOccurance, result.Error
}
func GetUnprocessedAlertOccurances() (*[]AlertOccurance, error) {
var alertOccurance []AlertOccurance
result := DB.Preload(clause.Associations).Order("created_at desc").Find(&alertOccurance, "process_date is NULL")
return &alertOccurance, result.Error
}
func MarkAlertOccuranceAsProcessed(id string, alertProcessType AlertType, date time.Time) error {
tx := DB.Debug().Model(&AlertOccurance{}).Where("id= ?", id).
Update("alert_process_type", alertProcessType).
Update("process_date", date)
return tx.Error
}
func UpdateSettings(setting *Setting) error {
tx := DB.Save(&setting)
@@ -332,8 +365,7 @@ func UnlockMissedJobs() {
if (job.Date == time.Time{}) {
continue
}
var duration time.Duration
duration = time.Duration(job.Duration)
var duration = time.Duration(job.Duration)
d := job.Date.Add(time.Minute * duration)
if d.Before(time.Now()) {
fmt.Println(job.Name + " is unlocked")

View File

@@ -36,76 +36,74 @@ const (
USER
)
type AlertFrequency int
const (
ONETIME AlertFrequency = iota
RECURRING
)
type AlertType int
const (
DISTANCE AlertType = iota
TIME
BOTH
)
type EnumDetail struct {
Short string `json:"short"`
Long string `json:"long"`
Key string `json:"key"`
}
var FuelUnitDetails map[FuelUnit]EnumDetail = map[FuelUnit]EnumDetail{
LITRE: {
Short: "Lt",
Long: "Litre",
Key: "litre",
},
GALLON: {
Short: "Gal",
Long: "Gallon",
Key: "gallon",
}, KILOGRAM: {
Short: "Kg",
Long: "Kilogram",
Key: "kilogram",
}, KILOWATT_HOUR: {
Short: "KwH",
Long: "Kilowatt Hour",
Key: "kilowatthour",
}, US_GALLON: {
Short: "US Gal",
Long: "US Gallon",
Key: "usgallon",
},
MINUTE: {
Short: "Mins",
Long: "Minutes",
Key: "minutes",
},
}
var FuelTypeDetails map[FuelType]EnumDetail = map[FuelType]EnumDetail{
PETROL: {
Short: "Petrol",
Long: "Petrol",
Key: "petrol",
},
DIESEL: {
Short: "Diesel",
Long: "Diesel",
Key: "diesel",
}, CNG: {
Short: "CNG",
Long: "CNG",
Key: "cng",
}, LPG: {
Short: "LPG",
Long: "LPG",
Key: "lpg",
}, ELECTRIC: {
Short: "Electric",
Long: "Electric",
Key: "electric",
}, ETHANOL: {
Short: "Ethanol",
Long: "Ethanol",
Key: "ethanol",
},
}
var DistanceUnitDetails map[DistanceUnit]EnumDetail = map[DistanceUnit]EnumDetail{
KILOMETERS: {
Short: "Km",
Long: "Kilometers",
Key: "kilometers",
},
MILES: {
Short: "Mi",
Long: "Miles",
Key: "miles",
},
}
var RoleDetails map[Role]EnumDetail = map[Role]EnumDetail{
ADMIN: {
Short: "Admin",
Long: "ADMIN",
Key: "ADMIN",
},
USER: {
Short: "User",
Long: "USER",
Key: "USER",
},
}

View File

@@ -18,6 +18,15 @@ var migrations = []localMigration{
Name: "2021_06_24_04_42_SetUserDisabledFalse",
Query: "update users set is_disabled=0",
},
{
Name: "2021_02_07_00_09_LowerCaseEmails",
Query: "update users set email=lower(email)",
},
{
Name: "2022_03_08_13_16_AddVIN",
Query: "ALTER TABLE vehicles ADD COLUMN vin text",
},
}
func RunMigrations() {

21
server/models/alert.go Normal file
View File

@@ -0,0 +1,21 @@
package models
import (
"time"
"github.com/akhilrex/hammond/db"
)
type CreateAlertModel struct {
Comments string `json:"comments"`
Title string `json:"title"`
StartDate time.Time `json:"date"`
StartOdoReading int `json:"startOdoReading"`
DistanceUnit *db.DistanceUnit `json:"distanceUnit"`
AlertFrequency *db.AlertFrequency `json:"alertFrequency"`
OdoFrequency int `json:"odoFrequency"`
DayFrequency int `json:"dayFrequency"`
AlertAllUsers bool `json:"alertAllUsers"`
IsActive bool `json:"isActive"`
AlertType *db.AlertType `json:"alertType"`
}

View File

@@ -14,7 +14,7 @@ type MileageModel struct {
FuelQuantity float32 `form:"fuelQuantity" json:"fuelQuantity" binding:"required"`
PerUnitPrice float32 `form:"perUnitPrice" json:"perUnitPrice" binding:"required"`
Currency string `json:"currency"`
DistanceUnit db.DistanceUnit `form:"distanceUnit" json:"distanceUnit"`
Mileage float32 `form:"mileage" json:"mileage" binding:"mileage"`
CostPerMile float32 `form:"costPerMile" json:"costPerMile" binding:"costPerMile"`
OdoReading int `form:"odoReading" json:"odoReading" binding:"odoReading"`
@@ -35,4 +35,5 @@ func (b *MileageModel) MarshalJSON() ([]byte, error) {
type MileageQueryModel struct {
Since time.Time `json:"since" query:"since" form:"since"`
MileageOption string `json:"mileageOption" query:"mileageOption" form:"mileageOption"`
}

View File

@@ -17,6 +17,7 @@ type SubItemQuery struct {
type CreateVehicleRequest struct {
Nickname string `form:"nickname" json:"nickname" binding:"required"`
Registration string `form:"registration" json:"registration" binding:"required"`
VIN string `form:"vin" json:"vin"`
Make string `form:"make" json:"make" binding:"required"`
Model string `form:"model" json:"model" binding:"required"`
YearOfManufacture int `form:"yearOfManufacture" json:"yearOfManufacture"`

View File

@@ -0,0 +1,172 @@
package service
import (
"errors"
"time"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
)
func CreateAlert(model models.CreateAlertModel, vehicleId, userId string) (*db.VehicleAlert, error) {
alert := db.VehicleAlert{
VehicleID: vehicleId,
UserID: userId,
Title: model.Title,
Comments: model.Comments,
StartDate: model.StartDate,
StartOdoReading: model.StartOdoReading,
DistanceUnit: *model.DistanceUnit,
AlertFrequency: *model.AlertFrequency,
OdoFrequency: model.OdoFrequency,
DayFrequency: model.DayFrequency,
AlertAllUsers: model.AlertAllUsers,
IsActive: model.IsActive,
AlertType: *model.AlertType,
}
tx := db.DB.Create(&alert)
if tx.Error != nil {
return nil, tx.Error
}
go CreateAlertInstance(alert.ID)
return &alert, nil
}
func CreateAlertInstance(alertId string) error {
alert, err := db.GeAlertById(alertId)
if err != nil {
return err
}
existingOccurence, err := db.GetAlertOccurenceByAlertId(alertId)
if err != nil {
return err
}
var lastOccurance db.AlertOccurance
useOccurance := false
if len(*existingOccurence) > 0 {
lastOccurance = (*existingOccurence)[0]
useOccurance = true
if alert.AlertFrequency == db.ONETIME {
return errors.New("Only single occurance is possible for this kind of alert")
}
}
users := []string{alert.UserID}
if alert.AlertAllUsers {
allUsers, err := db.GetVehicleUsers(alert.VehicleID)
if err != nil {
return err
}
users = make([]string, len(*allUsers))
for i, user := range *allUsers {
users[i] = user.UserID
}
}
for _, userId := range users {
model := db.AlertOccurance{
VehicleID: alert.VehicleID,
UserID: userId,
VehicleAlertID: alertId,
}
if alert.AlertType == db.DISTANCE || alert.AlertType == db.BOTH {
model.OdoReading = alert.StartOdoReading + alert.OdoFrequency
if useOccurance {
model.OdoReading = lastOccurance.OdoReading + alert.OdoFrequency
}
}
if alert.AlertType == db.TIME || alert.AlertType == db.BOTH {
date := alert.StartDate.Add(time.Duration(alert.DayFrequency) * 24 * time.Hour)
if useOccurance {
date = lastOccurance.Date.Add(time.Duration(alert.DayFrequency) * 24 * time.Hour)
}
model.Date = &date
}
tx := db.DB.Create(&model)
if tx.Error != nil {
return tx.Error
}
}
return nil
}
func ProcessAlertOccurance(occurance db.AlertOccurance, today time.Time) error {
if occurance.ProcessDate != nil {
return errors.New("Alert occurence already processed")
}
alert := occurance.VehicleAlert
if !alert.IsActive {
return errors.New("Alert is not active")
}
notification := db.Notification{
Title: alert.Title,
Content: alert.Comments,
UserID: occurance.UserID,
VehicleID: occurance.VehicleID,
Date: today,
ParentID: occurance.ID,
ParentType: "AlertOccurance",
}
var alertProcessType db.AlertType
if alert.AlertType == db.DISTANCE || alert.AlertType == db.BOTH {
odoReading, err := GetLatestOdoReadingForVehicle(occurance.VehicleID)
if err != nil {
return err
}
if odoReading >= occurance.OdoReading {
alertProcessType = db.DISTANCE
}
}
if alert.AlertType == db.TIME || alert.AlertType == db.BOTH {
if occurance.Date.Before(today) {
alertProcessType = db.TIME
}
}
db.DB.Create(&notification)
return db.MarkAlertOccuranceAsProcessed(occurance.ID, alertProcessType, today)
}
func FindAlertOccurancesToProcess(today time.Time) ([]db.AlertOccurance, error) {
occurances, err := db.GetUnprocessedAlertOccurances()
if err != nil {
return nil, err
}
if len(*occurances) == 0 {
return make([]db.AlertOccurance, 0), nil
}
var toReturn []db.AlertOccurance
for _, occurance := range *occurances {
alert := occurance.VehicleAlert
if !alert.IsActive {
continue
}
if alert.AlertType == db.DISTANCE || alert.AlertType == db.BOTH {
odoReading, err := GetLatestOdoReadingForVehicle(occurance.VehicleID)
if err != nil {
return nil, err
}
if odoReading >= occurance.OdoReading {
toReturn = append(toReturn, occurance)
continue
}
}
if alert.AlertType == db.TIME || alert.AlertType == db.BOTH {
if occurance.Date.Before(today) {
toReturn = append(toReturn, occurance)
continue
}
}
}
return toReturn, nil
}
func MarkAlertOccuranceAsCompleted() {
}

View File

@@ -3,7 +3,6 @@ package service
import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"net/http"
@@ -126,14 +125,14 @@ func CreateBackup() (string, error) {
tarballFilePath := path.Join(folder, backupFileName)
file, err := os.Create(tarballFilePath)
if err != nil {
return "", errors.New(fmt.Sprintf("Could not create tarball file '%s', got error '%s'", tarballFilePath, err.Error()))
return "", fmt.Errorf("could not create tarball file '%s', got error '%s'", tarballFilePath, err.Error())
}
defer file.Close()
dbPath := path.Join(configPath, "hammond.db")
_, err = os.Stat(dbPath)
if err != nil {
return "", errors.New(fmt.Sprintf("Could not find db file '%s', got error '%s'", dbPath, err.Error()))
return "", fmt.Errorf("could not find db file '%s', got error '%s'", dbPath, err.Error())
}
gzipWriter := gzip.NewWriter(file)
defer gzipWriter.Close()
@@ -151,13 +150,13 @@ func CreateBackup() (string, error) {
func addFileToTarWriter(filePath string, tarWriter *tar.Writer) error {
file, err := os.Open(filePath)
if err != nil {
return errors.New(fmt.Sprintf("Could not open file '%s', got error '%s'", filePath, err.Error()))
return fmt.Errorf("could not open file '%s', got error '%s'", filePath, err.Error())
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return errors.New(fmt.Sprintf("Could not get stat for file '%s', got error '%s'", filePath, err.Error()))
return fmt.Errorf("could not get stat for file '%s', got error '%s'", filePath, err.Error())
}
header := &tar.Header{
@@ -169,12 +168,12 @@ func addFileToTarWriter(filePath string, tarWriter *tar.Writer) error {
err = tarWriter.WriteHeader(header)
if err != nil {
return errors.New(fmt.Sprintf("Could not write header for file '%s', got error '%s'", filePath, err.Error()))
return fmt.Errorf("could not write header for file '%s', got error '%s'", filePath, err.Error())
}
_, err = io.Copy(tarWriter, file)
if err != nil {
return errors.New(fmt.Sprintf("Could not copy the file '%s' data to the tarball, got error '%s'", filePath, err.Error()))
return fmt.Errorf("could not copy the file '%s' data to the tarball, got error '%s'", filePath, err.Error())
}
return nil

View File

@@ -1,13 +1,15 @@
package service
import (
"sort"
"time"
"github.com/akhilrex/hammond/common"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
)
func GetMileageByVehicleId(vehicleId string, since time.Time) (mileage []models.MileageModel, err error) {
func GetMileageByVehicleId(vehicleId string, since time.Time, mileageOption string) (mileage []models.MileageModel, err error) {
data, err := db.GetFillupsByVehicleIdSince(vehicleId, since)
if err != nil {
return nil, err
@@ -15,6 +17,9 @@ func GetMileageByVehicleId(vehicleId string, since time.Time) (mileage []models.
fillups := make([]db.Fillup, len(*data))
copy(fillups, *data)
sort.Slice(fillups, func(i, j int) bool {
return fillups[i].OdoReading > fillups[j].OdoReading
})
var mileages []models.MileageModel
@@ -32,14 +37,48 @@ func GetMileageByVehicleId(vehicleId string, since time.Time) (mileage []models.
PerUnitPrice: currentFillup.PerUnitPrice,
OdoReading: currentFillup.OdoReading,
Currency: currentFillup.Currency,
DistanceUnit: currentFillup.DistanceUnit,
Mileage: 0,
CostPerMile: 0,
}
if currentFillup.IsTankFull != nil && *currentFillup.IsTankFull && (currentFillup.HasMissedFillup == nil || !(*currentFillup.HasMissedFillup)) {
distance := float32(currentFillup.OdoReading - lastFillup.OdoReading)
mileage.Mileage = distance / currentFillup.FuelQuantity
mileage.CostPerMile = distance / currentFillup.TotalAmount
currentOdoReading := float32(currentFillup.OdoReading);
lastFillupOdoReading := float32(lastFillup.OdoReading);
currentFuelQuantity := float32(currentFillup.FuelQuantity);
// If miles per gallon option and distanceUnit is km, convert from km to miles
// then check if fuel unit is litres. If it is, convert to gallons
if (mileageOption == "mpg" && mileage.DistanceUnit == db.KILOMETERS) {
currentOdoReading = common.KmToMiles(currentOdoReading);
lastFillupOdoReading = common.KmToMiles(lastFillupOdoReading);
if (mileage.FuelUnit == db.LITRE) {
currentFuelQuantity = common.LitreToGallon(currentFuelQuantity);
}
}
// If km_litre option or litre per 100km and distanceUnit is miles, convert from miles to km
// then check if fuel unit is not litres. If it isn't, convert to litres
if ((mileageOption == "km_litre" || mileageOption == "litre_100km") && mileage.DistanceUnit == db.MILES) {
currentOdoReading = common.MilesToKm(currentOdoReading);
lastFillupOdoReading = common.MilesToKm(lastFillupOdoReading);
if (mileage.FuelUnit == db.US_GALLON) {
currentFuelQuantity = common.GallonToLitre(currentFuelQuantity);
}
}
distance := float32(currentOdoReading - lastFillupOdoReading);
if (mileageOption == "litre_100km") {
mileage.Mileage = currentFuelQuantity / distance * 100;
} else {
mileage.Mileage = distance / currentFuelQuantity;
}
mileage.CostPerMile = distance / currentFillup.TotalAmount;
}

View File

@@ -1,6 +1,8 @@
package service
import (
"strings"
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
)
@@ -8,7 +10,7 @@ import (
func CreateUser(userModel *models.RegisterRequest, role db.Role) error {
setting := db.GetOrCreateSetting()
toCreate := db.User{
Email: userModel.Email,
Email: strings.ToLower(userModel.Email),
Name: userModel.Name,
Role: role,
Currency: setting.Currency,

View File

@@ -5,6 +5,7 @@ import (
"github.com/akhilrex/hammond/db"
"github.com/akhilrex/hammond/models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
@@ -13,6 +14,7 @@ func CreateVehicle(model models.CreateVehicleRequest, userId string) (*db.Vehicl
Nickname: model.Nickname,
Registration: model.Registration,
Model: model.Model,
VIN: model.VIN,
Make: model.Make,
YearOfManufacture: model.YearOfManufacture,
EngineSize: model.EngineSize,
@@ -99,6 +101,7 @@ func UpdateVehicle(vehicleID string, model models.UpdateVehicleRequest) error {
//return db.DB.Model(&toUpdate).Updates(db.Vehicle{
toUpdate.Nickname = model.Nickname
toUpdate.Registration = model.Registration
toUpdate.VIN = model.VIN
toUpdate.Model = model.Model
toUpdate.Make = model.Make
toUpdate.YearOfManufacture = model.YearOfManufacture
@@ -243,6 +246,24 @@ func GetDistinctFuelSubtypesForVehicle(vehicleId string) ([]string, error) {
return names, tx.Error
}
func GetLatestOdoReadingForVehicle(vehicleId string) (int, error) {
odoReading := 0
latestFillup, err := db.GetLatestExpenseByVehicleId(vehicleId)
if err != nil && err != gorm.ErrRecordNotFound {
return 0, err
}
odoReading = latestFillup.OdoReading
latestExpense, err := db.GetLatestExpenseByVehicleId(vehicleId)
if err != nil && err != gorm.ErrRecordNotFound {
return 0, err
}
if latestExpense.OdoReading > odoReading {
odoReading = latestExpense.OdoReading
}
return odoReading, nil
}
func GetUserStats(userId string, model models.UserStatsQueryModel) ([]models.VehicleStatsModel, error) {
vehicles, err := GetUserVehicles(userId)