vibeStonk/server/repository/sqlitePurchase.go
2025-06-12 16:57:42 -04:00

227 lines
5.6 KiB
Go

package repository
import (
"database/sql"
"errors"
"fmt"
"time"
models "vibeStonk/server/models/v1"
"google.golang.org/protobuf/types/known/timestamppb"
)
var (
ErrPurchaseNotFound = errors.New("purchase not found")
)
func newSqlitePurchaseRepo(db *sql.DB) (PurchaseRepo, error) {
repo := &sqlitePurchaseRepo{db: db}
if err := repo.initialize(); err != nil {
return nil, err
}
return repo, nil
}
type sqlitePurchaseRepo struct {
db *sql.DB
}
// initialize creates the purchases table if it doesn't exist
func (s *sqlitePurchaseRepo) initialize() error {
query := `
CREATE TABLE IF NOT EXISTS purchases (
id INTEGER PRIMARY KEY AUTOINCREMENT,
purchase_date TIMESTAMP NOT NULL,
stock_id INTEGER NOT NULL,
qty REAL NOT NULL,
price REAL NOT NULL,
FOREIGN KEY (stock_id) REFERENCES stocks(id)
);
`
_, err := s.db.Exec(query)
if err != nil {
return fmt.Errorf("failed to create purchases table: %w", err)
}
query = `
CREATE INDEX IF NOT EXISTS purchases_stock_id
ON purchases(stock_id);
`
_, err = s.db.Exec(query)
if err != nil {
return fmt.Errorf("failed to create stock_id index for purchases table: %w", err)
}
return nil
}
func (s *sqlitePurchaseRepo) Create(purchase *models.Purchase) (*models.Purchase, error) {
query := `
INSERT INTO purchases (purchase_date, stock_id, qty, price)
VALUES (?, ?, ?, ?)
`
// Convert protobuf timestamp to time.Time for SQLite
var purchaseDate time.Time
if purchase.PurchaseDate != nil {
purchaseDate = purchase.PurchaseDate.AsTime()
} else {
purchaseDate = time.Now()
purchase.PurchaseDate = timestamppb.New(purchaseDate)
}
result, err := s.db.Exec(query, purchaseDate, purchase.StockID, purchase.Qty, purchase.Price)
if err != nil {
return nil, fmt.Errorf("failed to create purchase: %w", err)
}
id, err := result.LastInsertId()
if err != nil {
return nil, fmt.Errorf("failed to get last insert ID: %w", err)
}
purchase.Id = id
return purchase, nil
}
func (s *sqlitePurchaseRepo) Get(id int64) (*models.Purchase, error) {
query := `
SELECT id, purchase_date, stock_id, qty, price
FROM purchases
WHERE id = ?
`
row := s.db.QueryRow(query, id)
purchase := &models.Purchase{}
var purchaseDate time.Time
err := row.Scan(&purchase.Id, &purchaseDate, &purchase.StockID, &purchase.Qty, &purchase.Price)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, ErrPurchaseNotFound
}
return nil, fmt.Errorf("failed to get purchase: %w", err)
}
// Convert time.Time to protobuf timestamp
purchase.PurchaseDate = timestamppb.New(purchaseDate)
return purchase, nil
}
func (s *sqlitePurchaseRepo) GetByStockID(stockID int64) ([]*models.Purchase, error) {
query := `
SELECT id, purchase_date, stock_id, qty, price
FROM purchases
WHERE stock_id = ?
`
rows, err := s.db.Query(query, stockID)
if err != nil {
return nil, fmt.Errorf("failed to get purchases by stock ID: %w", err)
}
defer rows.Close()
var purchases []*models.Purchase
for rows.Next() {
purchase := &models.Purchase{}
var purchaseDate time.Time
err := rows.Scan(&purchase.Id, &purchaseDate, &purchase.StockID, &purchase.Qty, &purchase.Price)
if err != nil {
return nil, fmt.Errorf("failed to scan purchase row: %w", err)
}
// Convert time.Time to protobuf timestamp
purchase.PurchaseDate = timestamppb.New(purchaseDate)
purchases = append(purchases, purchase)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating purchase rows: %w", err)
}
return purchases, nil
}
func (s *sqlitePurchaseRepo) Update(purchase *models.Purchase) error {
query := `
UPDATE purchases
SET purchase_date = ?, stock_id = ?, qty = ?, price = ?
WHERE id = ?
`
// Convert protobuf timestamp to time.Time for SQLite
var purchaseDate time.Time
if purchase.PurchaseDate != nil {
purchaseDate = purchase.PurchaseDate.AsTime()
} else {
purchaseDate = time.Now()
purchase.PurchaseDate = timestamppb.New(purchaseDate)
}
result, err := s.db.Exec(query, purchaseDate, purchase.StockID, purchase.Qty, purchase.Price, purchase.Id)
if err != nil {
return fmt.Errorf("failed to update purchase: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return ErrPurchaseNotFound
}
return nil
}
func (s *sqlitePurchaseRepo) Delete(purchase *models.Purchase) error {
query := `
DELETE FROM purchases
WHERE id = ?
`
result, err := s.db.Exec(query, purchase.Id)
if err != nil {
return fmt.Errorf("failed to delete purchase: %w", err)
}
rowsAffected, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("failed to get rows affected: %w", err)
}
if rowsAffected == 0 {
return ErrPurchaseNotFound
}
return nil
}
func (s *sqlitePurchaseRepo) List() ([]*models.Purchase, error) {
query := `
SELECT id, purchase_date, stock_id, qty, price
FROM purchases
ORDER BY purchase_date DESC
`
rows, err := s.db.Query(query)
if err != nil {
return nil, fmt.Errorf("failed to list purchases: %w", err)
}
defer rows.Close()
var purchases []*models.Purchase
for rows.Next() {
purchase := &models.Purchase{}
var purchaseDate time.Time
err := rows.Scan(&purchase.Id, &purchaseDate, &purchase.StockID, &purchase.Qty, &purchase.Price)
if err != nil {
return nil, fmt.Errorf("failed to scan purchase row: %w", err)
}
// Convert time.Time to protobuf timestamp
purchase.PurchaseDate = timestamppb.New(purchaseDate)
purchases = append(purchases, purchase)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating purchase rows: %w", err)
}
return purchases, nil
}