first commit
This commit is contained in:
@@ -0,0 +1,213 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
// User represents a user account
|
||||
type User struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
Username string `json:"username" gorm:"uniqueIndex;not null;size:50"`
|
||||
Email string `json:"email" gorm:"uniqueIndex;not null;size:255"`
|
||||
Password string `json:"-" gorm:"-"` // Only used for input, never stored
|
||||
PasswordHash string `json:"-" gorm:"column:password_hash;not null"`
|
||||
BaseCurrency string `json:"base_currency" gorm:"not null;default:EUR;size:3"`
|
||||
FuelStops []FuelStop `json:"fuel_stops,omitempty" gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
|
||||
Vehicles []Vehicle `json:"vehicles,omitempty" gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
// CheckPassword verifies a password against the stored hash
|
||||
func (u *User) CheckPassword(password string) bool {
|
||||
// Defensive programming - check for nil user
|
||||
if u == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for empty password or password hash
|
||||
if password == "" || u.PasswordHash == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// Vehicle represents a user's vehicle
|
||||
type Vehicle struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
UserID uint `json:"user_id" gorm:"not null;index"`
|
||||
User User `json:"-" gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
|
||||
Name string `json:"name" gorm:"not null;size:100"`
|
||||
Make string `json:"make" gorm:"size:50"`
|
||||
Model string `json:"model" gorm:"size:50"`
|
||||
Year int `json:"year" gorm:"default:0"`
|
||||
LicensePlate string `json:"license_plate" gorm:"size:20"`
|
||||
FuelType string `json:"fuel_type" gorm:"size:50"`
|
||||
Notes string `json:"notes" gorm:"type:text"`
|
||||
IsActive bool `json:"is_active" gorm:"default:true"`
|
||||
FuelStops []FuelStop `json:"fuel_stops,omitempty" gorm:"foreignKey:VehicleID;constraint:OnDelete:CASCADE"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
// FuelStop represents a fuel station stop record
|
||||
type FuelStop struct {
|
||||
ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
|
||||
UserID uint `json:"user_id" gorm:"not null;index"`
|
||||
User User `json:"-" gorm:"foreignKey:UserID;constraint:OnDelete:CASCADE"`
|
||||
VehicleID uint `json:"vehicle_id" gorm:"not null;index"`
|
||||
Vehicle Vehicle `json:"vehicle,omitempty" gorm:"foreignKey:VehicleID;constraint:OnDelete:CASCADE"`
|
||||
Date time.Time `json:"date" gorm:"not null;type:date"`
|
||||
StationName string `json:"station_name" gorm:"not null;size:100"`
|
||||
Location string `json:"location" gorm:"not null;size:255"`
|
||||
FuelType string `json:"fuel_type" gorm:"not null;size:50"`
|
||||
Liters float64 `json:"liters" gorm:"not null;type:decimal(10,3)"`
|
||||
PricePerL float64 `json:"price_per_l" gorm:"not null;type:decimal(10,4)"`
|
||||
TotalPrice float64 `json:"total_price" gorm:"not null;type:decimal(10,2)"`
|
||||
Currency string `json:"currency" gorm:"not null;default:EUR;size:3"`
|
||||
Odometer int `json:"odometer" gorm:"default:0"`
|
||||
TripLength float64 `json:"trip_length" gorm:"default:0;type:decimal(8,2);comment:Distance traveled since last fillup in km"`
|
||||
Notes string `json:"notes" gorm:"type:text"`
|
||||
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
// FuelStopStats represents statistics for fuel consumption
|
||||
type FuelStopStats struct {
|
||||
TotalStops int `json:"total_stops"`
|
||||
TotalLiters float64 `json:"total_liters"`
|
||||
TotalSpent float64 `json:"total_spent"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
AverageConsumption float64 `json:"average_consumption"`
|
||||
LastFillup *FuelStop `json:"last_fillup"`
|
||||
}
|
||||
|
||||
// FuelStopForm represents form data for creating/updating fuel stops
|
||||
type FuelStopForm struct {
|
||||
Date string `json:"date" form:"date"`
|
||||
VehicleID uint `json:"vehicle_id" form:"vehicle_id"`
|
||||
StationName string `json:"station_name" form:"station_name"`
|
||||
Location string `json:"location" form:"location"`
|
||||
FuelType string `json:"fuel_type" form:"fuel_type"`
|
||||
Liters float64 `json:"liters" form:"liters"`
|
||||
PricePerL float64 `json:"price_per_l" form:"price_per_l"`
|
||||
TotalPrice float64 `json:"total_price" form:"total_price"`
|
||||
Currency string `json:"currency" form:"currency"`
|
||||
Odometer int `json:"odometer" form:"odometer"`
|
||||
TripLength float64 `json:"trip_length" form:"trip_length"`
|
||||
Notes string `json:"notes" form:"notes"`
|
||||
}
|
||||
|
||||
// UserRegistrationForm represents form data for user registration
|
||||
type UserRegistrationForm struct {
|
||||
Username string `json:"username" form:"username"`
|
||||
Email string `json:"email" form:"email"`
|
||||
Password string `json:"password" form:"password"`
|
||||
ConfirmPassword string `json:"confirm_password" form:"confirm_password"`
|
||||
BaseCurrency string `json:"base_currency" form:"base_currency"`
|
||||
}
|
||||
|
||||
// UserLoginForm represents form data for user login
|
||||
type UserLoginForm struct {
|
||||
Username string `json:"username" form:"username"`
|
||||
Password string `json:"password" form:"password"`
|
||||
}
|
||||
|
||||
// UserSettingsForm represents form data for user settings
|
||||
type UserSettingsForm struct {
|
||||
Email string `json:"email" form:"email"`
|
||||
BaseCurrency string `json:"base_currency" form:"base_currency"`
|
||||
}
|
||||
|
||||
// MonthlyStats represents monthly statistics for fuel consumption
|
||||
type MonthlyStats struct {
|
||||
Month string `json:"month"`
|
||||
Year string `json:"year"`
|
||||
TotalStops int `json:"total_stops"`
|
||||
TotalLiters float64 `json:"total_liters"`
|
||||
TotalSpent float64 `json:"total_spent"`
|
||||
AveragePrice float64 `json:"average_price"`
|
||||
}
|
||||
|
||||
// VehicleForm represents form data for creating/updating vehicles
|
||||
type VehicleForm struct {
|
||||
Name string `json:"name" form:"name"`
|
||||
Make string `json:"make" form:"make"`
|
||||
Model string `json:"model" form:"model"`
|
||||
Year int `json:"year" form:"year"`
|
||||
LicensePlate string `json:"license_plate" form:"license_plate"`
|
||||
FuelType string `json:"fuel_type" form:"fuel_type"`
|
||||
Notes string `json:"notes" form:"notes"`
|
||||
IsActive bool `json:"is_active" form:"is_active"`
|
||||
}
|
||||
|
||||
// ToFuelStop converts a FuelStopForm to a FuelStop
|
||||
func (f *FuelStopForm) ToFuelStop(userID uint) (*FuelStop, error) {
|
||||
date, err := time.Parse("2006-01-02", f.Date)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Use EUR as default currency if not provided
|
||||
currency := f.Currency
|
||||
if currency == "" {
|
||||
currency = "EUR"
|
||||
}
|
||||
|
||||
return &FuelStop{
|
||||
UserID: userID,
|
||||
VehicleID: f.VehicleID,
|
||||
Date: date,
|
||||
StationName: f.StationName,
|
||||
Location: f.Location,
|
||||
FuelType: f.FuelType,
|
||||
Liters: f.Liters,
|
||||
PricePerL: f.PricePerL,
|
||||
TotalPrice: f.TotalPrice,
|
||||
Currency: currency,
|
||||
Odometer: f.Odometer,
|
||||
TripLength: f.TripLength,
|
||||
Notes: f.Notes,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ToVehicle converts a VehicleForm to a Vehicle
|
||||
func (f *VehicleForm) ToVehicle(userID uint) *Vehicle {
|
||||
return &Vehicle{
|
||||
UserID: userID,
|
||||
Name: f.Name,
|
||||
Make: f.Make,
|
||||
Model: f.Model,
|
||||
Year: f.Year,
|
||||
LicensePlate: f.LicensePlate,
|
||||
FuelType: f.FuelType,
|
||||
Notes: f.Notes,
|
||||
IsActive: f.IsActive,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// ToUser converts a UserRegistrationForm to a User
|
||||
func (f *UserRegistrationForm) ToUser() (*User, error) {
|
||||
// Use EUR as default currency if not provided
|
||||
currency := f.BaseCurrency
|
||||
if currency == "" {
|
||||
currency = "EUR"
|
||||
}
|
||||
|
||||
return &User{
|
||||
Username: f.Username,
|
||||
Email: f.Email,
|
||||
Password: f.Password,
|
||||
BaseCurrency: currency,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user