Skip to content

Commit

Permalink
repository pattern and db conn finished
Browse files Browse the repository at this point in the history
  • Loading branch information
nelsonmarro committed Dec 30, 2024
1 parent e2ff2a7 commit 09e3407
Show file tree
Hide file tree
Showing 29 changed files with 660 additions and 40 deletions.
Binary file added assets/unreachable.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 52 additions & 8 deletions cmd/goldwatcher/main.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
package main

import (
"database/sql"
"log"
"os"
"time"

"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"github.com/nelsonmarro/gold-watcher/internal/contracts"

client "github.com/nelsonmarro/gold-watcher/internal/http"
"github.com/nelsonmarro/gold-watcher/internal/repository"
"github.com/nelsonmarro/gold-watcher/internal/services"
"github.com/nelsonmarro/gold-watcher/internal/ui"
)

var myApp ui.Config
_ "github.com/mattn/go-sqlite3"
)

func main() {
var myApp ui.Config

// create a fyne application
a := app.NewWithID("lilim.code.goldwatcher.preferences")
myApp.App = a
Expand All @@ -25,12 +29,14 @@ func main() {
myApp.ErrorLog = log.New(os.Stdout, "ERROR\t", log.Ldate|log.Ltime|log.Lshortfile)

// open a connection to the database

// create a database repository
db, err := createSqlConn(&myApp)
if err != nil {
log.Panic(err)
}
defer db.Close()

// dependency injection
client := client.NewHttpClient(5 * time.Second)
var goldService contracts.GoldService = services.NewGoldService(client)
setupDependencyInjection(&myApp, db)

// create and size a fyne window
myApp.MainWindow = a.NewWindow("Gold Watcher")
Expand All @@ -39,8 +45,46 @@ func main() {
myApp.MainWindow.CenterOnScreen()
myApp.MainWindow.SetMaster()

myApp.MakeUI(goldService)
myApp.MakeUI()

// show and run the application
myApp.MainWindow.ShowAndRun()
}

func createSqlConn(app *ui.Config) (*sql.DB, error) {
path := ""

if os.Getenv("DB_PATH") != "" {
path = os.Getenv("DB_PATH")
} else {
path = app.App.Storage().RootURI().Path() + "/sql.db"
app.InfoLog.Println("DB in: ", path)
}

db, err := sql.Open("sqlite3", path)
if err != nil {
return nil, err
}

return db, nil
}

func setupDependencyInjection(app *ui.Config, db *sql.DB) {
client := client.NewHttpClient("https://data-asg.goldprice.org/dbXRates/", 5*time.Second)
var goldService services.GoldService = services.NewGoldService(client)

app.GoldService = goldService

setupDB(app, db)
}

func setupDB(app *ui.Config, db *sql.DB) {
app.HoldingRepository = repository.NewHoldingRepository(db)

dbInitializer := repository.NewDbInitializer(db)
err := dbInitializer.Migrate()
if err != nil {
app.ErrorLog.Println(err)
log.Panic(err)
}
}
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ module github.com/nelsonmarro/gold-watcher

go 1.23.4

require fyne.io/fyne/v2 v2.5.3
require (
fyne.io/fyne/v2 v2.5.3
github.com/mattn/go-sqlite3 v1.14.24
)

require (
fyne.io/systray v1.11.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
Expand Down
Binary file added gold_chart.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 0 additions & 7 deletions internal/contracts/gold_service.go

This file was deleted.

6 changes: 4 additions & 2 deletions internal/http/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import (
"time"
)

var Base_url = "https://data-asg.goldprice.org/dbXRates/"
var base_url = ""

type HttpClient struct {
http http.Client
}

func NewHttpClient(timeout time.Duration) *HttpClient {
func NewHttpClient(baseUrl string, timeout time.Duration) *HttpClient {
base_url = baseUrl

return &HttpClient{
http: http.Client{
Timeout: timeout,
Expand Down
7 changes: 5 additions & 2 deletions internal/http/client_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import (
"net/http"
)

func (c *HttpClient) Get(url string) ([]byte, error) {
url = Base_url + url
func (c *HttpClient) Get(url string, appendBaseUrl bool) ([]byte, error) {
if appendBaseUrl {
url = base_url + url
}

req, err := http.NewRequest("GET", url, nil)
if err != nil {
Expand All @@ -28,6 +30,7 @@ func (c *HttpClient) Get(url string) ([]byte, error) {
if response.StatusCode > 299 {
return nil, fmt.Errorf(
"response failed with status code: %d and\nbody: %s",

response.StatusCode,
body,
)
Expand Down
10 changes: 10 additions & 0 deletions internal/models/holding.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package models

import "time"

type Holding struct {
ID int64 `json:"id"`
Amount float64 `json:"amount"`
PurchaseDate time.Time `json:"purchase_date"`
PurchasePrice int `json:"purchase_price"`
}
25 changes: 25 additions & 0 deletions internal/repository/db_initializer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package repository

import "database/sql"

type DBInitializer interface {
Migrate() error
}

type dbInitializer struct {
db *sql.DB
}

func NewDbInitializer(db *sql.DB) *dbInitializer {
return &dbInitializer{db: db}
}

func (dbi *dbInitializer) Migrate() error {
_, err := dbi.db.Exec(`CREATE TABLE IF NOT EXISTS holdings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
amount REAL NOT NULL,
purchase_date INTEGER NOT NULL,
purchase_price INTEGER NOT NULL
);`)
return err
}
10 changes: 10 additions & 0 deletions internal/repository/db_initializer_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package repository

import "testing"

func TestDBInitializer_Migrate(t *testing.T) {
err := testDBInitalizer.Migrate()
if err != nil {
t.Errorf("Error migrating database: %v", err)
}
}
134 changes: 134 additions & 0 deletions internal/repository/holdings_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package repository

import (
"database/sql"
"errors"
"time"

"github.com/nelsonmarro/gold-watcher/internal/models"
)

var (
errUpdateFailed = errors.New("failed to update the holding")
errDeleteFailed = errors.New("failed to delete the holding")
)

type HoldingRepository interface {
Create(holding models.Holding) (*models.Holding, error)
GetAll() ([]models.Holding, error)
GetByID(id int64) (*models.Holding, error)
Update(id int64, holding models.Holding) error
Delete(id int64) error
}

type holdingRepository struct {
db *sql.DB
}

func NewHoldingRepository(db *sql.DB) *holdingRepository {
return &holdingRepository{db: db}
}

func (r *holdingRepository) Create(holding models.Holding) (*models.Holding, error) {
cmd := `INSERT INTO holdings (amount, purchase_date, purchase_price) VALUES (?, ?, ?);`

result, err := r.db.Exec(cmd, holding.Amount, holding.PurchaseDate.Unix(), holding.PurchasePrice)
if err != nil {
return nil, err
}

id, err := result.LastInsertId()
if err != nil {
return nil, err
}

holding.ID = id
return &holding, nil
}

func (r *holdingRepository) GetAll() ([]models.Holding, error) {
query := `SELECT * FROM holdings;`

rows, err := r.db.Query(query)
if err != nil {
return nil, err
}
defer rows.Close()

holdings := make([]models.Holding, 0)
unixTime := int64(0)

for rows.Next() {
holding := models.Holding{}
err := rows.Scan(&holding.ID, &holding.Amount, &unixTime, &holding.PurchasePrice)
if err != nil {
return nil, err
}
holding.PurchaseDate = time.Unix(unixTime, 0)
holdings = append(holdings, holding)
}

return holdings, nil
}

func (r *holdingRepository) GetByID(id int64) (*models.Holding, error) {
row := r.db.QueryRow(`SELECT * FROM holdings WHERE id = ?;`, id)

var h models.Holding
var unixTime int64
err := row.Scan(&h.ID, &h.Amount, &unixTime, &h.PurchasePrice)
if err != nil {
return nil, err
}

h.PurchaseDate = time.Unix(unixTime, 0)
return &h, nil
}

func (r *holdingRepository) Update(id int64, holding models.Holding) error {
if id == 0 {
return errors.New("invalid updated id")
}

cmd := `UPDATE holdings SET amount = ?, purchase_date = ?, purchase_price = ? WHERE id = ?;`

res, err := r.db.Exec(cmd, holding.Amount, holding.PurchaseDate.Unix(), holding.PurchasePrice, id)
if err != nil {
return err
}

rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}

if rowsAffected == 0 {
return errUpdateFailed
}

return nil
}

func (r *holdingRepository) Delete(id int64) error {
if id == 0 {
return errors.New("invalid deleted id")
}

cmd := `DELETE FROM holdings WHERE id = ?;`

res, err := r.db.Exec(cmd, id)
if err != nil {
return err
}

rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}

if rowsAffected == 0 {
return errDeleteFailed
}

return nil
}
Loading

0 comments on commit 09e3407

Please sign in to comment.