package models import ( "context" "database/sql" "errors" "strings" "time" "golang.org/x/crypto/bcrypt" ) // DBModel is the type for database connection values type DBModel struct { DB *sql.DB } // Models is the wrapper for all models type Models struct { DB DBModel } // NewModels returns a model type with database connection pool func NewModels(db *sql.DB) Models { return Models{ DB: DBModel{DB: db}, } } // Widget is the type for all widgets type Widget struct { ID int `json:"id"` Name string `json:"name"` Description string `json:"description"` InventoryLevel int `json:"inventory_level"` Price int `json:"price"` CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` Image string `json:"image"` IsRecurring bool `json:"is_recurring"` PlanID string `json:"plan_id"` } // Order is the type for all orders type Order struct { ID int `json:"id"` WidgetID int `json:"widget_id"` TransactionID int `json:"transaction_id"` CustomerID int `json:"customer_id"` StatusID int `json:"status_id"` Quantity int `json:"quantity"` Amount int `json:"amount"` CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` } // Status is the type for orders statuses type Status struct { ID int `json:"id"` Name string `json:"name"` CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` } // TransactionStatus is the type for transaction statuses type TransactionStatus struct { ID int `json:"id"` Name string `json:"name"` CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` } // Transaction is the type for transactions type Transaction struct { ID int `json:"id"` Amount int `json:"amount"` Currency string `json:"currency"` LastFour string `json:"last_four"` ExpiryMonth int `json:"expiry_month"` ExpiryYear int `json:"expiry_year"` PaymentIntent string `json:"payment_intent"` PaymentMethod string `json:"payment_method"` BankReturnCode string `json:"bank_return_code"` TransactionStatusID int `json:"transaction_status_id"` CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` } // User is the type for users type User struct { ID int `json:"id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email"` Password string `json:"password"` CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` } // Customer is the type for customers type Customer struct { ID int `json:"id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Email string `json:"email"` CreatedAt time.Time `json:"-"` UpdatedAt time.Time `json:"-"` } func (m *DBModel) GetWidget(id int) (Widget, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var widget Widget query := `SELECT id, name, description, inventory_level, price, coalesce(image, ''), is_recurring, plan_id, created_at, updated_at FROM widgets WHERE id = ?;` row := m.DB.QueryRowContext(ctx, query, id) err := row.Scan( &widget.ID, &widget.Name, &widget.Description, &widget.InventoryLevel, &widget.Price, &widget.Image, &widget.IsRecurring, &widget.PlanID, &widget.CreatedAt, &widget.UpdatedAt, ) if err != nil { return widget, err } return widget, nil } // InsertTransaction inserts a new txn, and returns its id func (m *DBModel) InsertTransaction(txn Transaction) (int, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() stmt := `INSERT INTO transactions (amount, currency, last_four, expiry_month, expiry_year, payment_intent, payment_method, bank_return_code, transaction_status_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` result, err := m.DB.ExecContext(ctx, stmt, txn.Amount, txn.Currency, txn.LastFour, txn.ExpiryMonth, txn.ExpiryYear, txn.PaymentIntent, txn.PaymentMethod, txn.BankReturnCode, txn.TransactionStatusID, time.Now(), time.Now(), ) if err != nil { return 0, err } id, err := result.LastInsertId() if err != nil { return 0, err } return int(id), nil } // InsertOrder inserts a new order, and returns its id func (m *DBModel) InsertOrder(order Order) (int, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() stmt := `INSERT INTO orders (widget_id, transaction_id, customer_id, status_id, quantity, amount, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)` result, err := m.DB.ExecContext(ctx, stmt, order.WidgetID, order.TransactionID, order.CustomerID, order.StatusID, order.Quantity, order.Amount, time.Now(), time.Now(), ) if err != nil { return 0, err } id, err := result.LastInsertId() if err != nil { return 0, err } return int(id), nil } // InsertCustomer inserts a new customer, and returns its id func (m *DBModel) InsertCustomer(customer Customer) (int, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() stmt := `INSERT INTO customers (first_name, last_name, email, created_at, updated_at) VALUES (?, ?, ?, ?, ?)` result, err := m.DB.ExecContext(ctx, stmt, customer.FirstName, customer.LastName, customer.Email, time.Now(), time.Now(), ) if err != nil { return 0, err } id, err := result.LastInsertId() if err != nil { return 0, err } return int(id), nil } // GetUserByEmail gets a user by email address func (m *DBModel) GetUserByEmail(email string) (User, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() email = strings.ToLower(email) var u User query := `SELECT id, first_name, last_name, email, password, created_at, updated_at FROM users WHERE email = ?` row := m.DB.QueryRowContext(ctx, query, email) err := row.Scan( &u.ID, &u.FirstName, &u.LastName, &u.Email, &u.Password, &u.CreatedAt, &u.UpdatedAt, ) if err != nil { return u, err } return u, nil } func (m *DBModel) Authenticate(email, password string) (int, error) { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() var id int var hashedPassword string row := m.DB.QueryRowContext(ctx, "SELECT id, password from users WHERE email = ?", email) err := row.Scan(&id, &hashedPassword) if err != nil { return 0, err } err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) if err == bcrypt.ErrMismatchedHashAndPassword { return 0, errors.New("incorrect password") } else if err != nil { return 0, err } return id, nil } func (m *DBModel) UpdatePasswordForUser(u User, hash string) error { ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() stmt := `UPDATE users SET password = ? where id = ?` _, err := m.DB.ExecContext(ctx, stmt, hash, u.ID) if err != nil { return err } return nil }