A cmd/shi-import/.gitignore => cmd/shi-import/.gitignore +1 -0
@@ 0,0 1,1 @@
+shi-import
M cmd/shi-import/main.go => cmd/shi-import/main.go +62 -4
@@ 1,14 1,59 @@
package main
import (
+ "database/sql"
"encoding/csv"
- "fmt"
"log"
"os"
+ "strconv"
"strings"
+ "time"
+
+ "git.sr.ht/~inferiormartin/shishutsu/config"
+ "git.sr.ht/~inferiormartin/shishutsu/database"
)
func main() {
+ //TODO: this program is too specific with what it imports, needs to improve
+ cfg, err := config.Load()
+ if err != nil {
+ log.Fatal(err)
+ }
+ conn, ok := cfg.Get("shishutsu", "connection-string")
+ if !ok {
+ log.Fatal("shi-import: unable to get connection-string. shishutsu connection-string not defined?")
+ }
+ db, err := sql.Open("postgres", conn)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer db.Close()
+ transactions := read()
+ if err = database.InsertTransactions(transactions, db); err != nil {
+ log.Fatal(err)
+ }
+ log.Printf("%d transactions imported.", len(transactions))
+}
+
+func parseDate(date string) time.Time {
+ r, err := time.Parse("02/01/2006", date)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return r
+}
+
+func parseFloat(value string) float64 {
+ value = strings.Replace(value, ".", "", -1)
+ value = strings.Replace(value, ",", ".", -1)
+ result, err := strconv.ParseFloat(value, 64)
+ if err != nil {
+ log.Fatal(err)
+ }
+ return result
+}
+
+func read() []*database.Transaction {
args := os.Args[1:]
data, err := os.ReadFile(args[0])
if err != nil {
@@ 16,10 61,23 @@ func main() {
}
r := csv.NewReader(strings.NewReader(string(data)))
r.Comma = ';'
- rows, err := r.ReadAll()
+ results, err := r.ReadAll()
if err != nil {
log.Fatal(err)
}
- //TODO: connect and insert rows into database
- fmt.Print(rows[len(rows)-1])
+ results = results[1:]
+ var transactions []*database.Transaction
+ for _, result := range results {
+ item := &database.Transaction{
+ -1,
+ "dc5dadda-063c-45a4-b6a8-61357b962a37", //TODO: allow import for any user
+ "TODO", //TODO: my bank's specific csv does not contain an IBAN because the statement exports are specific for an account. allow manual override using flag?
+ int64(parseFloat(result[6]) * 100),
+ parseDate(result[4]),
+ parseDate(result[5]),
+ result[8],
+ }
+ transactions = append(transactions, item)
+ }
+ return transactions
}
M cmd/shi-web/main.go => cmd/shi-web/main.go +31 -4
@@ 1,11 1,14 @@
package main
import (
+ "database/sql"
"html/template"
"log"
"net/http"
"git.sr.ht/~inferiormartin/shishutsu/config"
+ "git.sr.ht/~inferiormartin/shishutsu/database"
+ "git.sr.ht/~inferiormartin/shishutsu/web"
)
func main() {
@@ 20,10 23,34 @@ func main() {
log.Fatal(err)
}
+ conn, ok := cfg.Get("shishutsu", "connection-string")
+ if !ok {
+ log.Fatal("shi-web: unable to get connection-string. shishutsu connection-string not defined?")
+ }
+ //TODO: better connection management
+ db, err := sql.Open("postgres", conn)
+ defer db.Close()
+
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- err = tmpl.ExecuteTemplate(w, "index.html", web.getDashboard())
+ rows, err := db.Query("SELECT id, account_id, iban, amount, transaction_date, interest_date, description FROM transaction")
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer rows.Close()
+ var transactions []database.Transaction
+ for rows.Next() {
+ var t database.Transaction
+ if err = rows.Scan(&t.ID, &t.AccountID, &t.IBAN, &t.Amount, &t.TransactionDate, &t.InterestDate, &t.Description); err != nil {
+ log.Fatal(err)
+ }
+ transactions = append(transactions, t)
+ }
+ if err = rows.Err(); err != nil {
+ log.Fatal(err)
+ }
+ err = tmpl.ExecuteTemplate(w, "index.html", &web.DashboardPage{transactions})
})
http.HandleFunc("/sign-in", func(w http.ResponseWriter, r *http.Request) {
@@ 31,10 58,10 @@ func main() {
case http.MethodGet:
err = tmpl.ExecuteTemplate(w, "sign-in.html", nil)
case http.MethodPost:
- _ := r.FormValue("email")
- _ := r.FormValue("password")
+ //_ := r.FormValue("email")
+ //_ := r.FormValue("password")
- err = tmpl.ExecuteTemplate(w, "index.html", web.getDashboard())
+ //err = tmpl.ExecuteTemplate(w, "index.html", web.getDashboard())
}
})
A database/transaction.go => database/transaction.go +53 -0
@@ 0,0 1,53 @@
+package database
+
+import (
+ "database/sql"
+ "fmt"
+ "time"
+
+ "github.com/lib/pq"
+)
+
+type Transaction struct {
+ ID int32
+ AccountID string
+ IBAN string
+ Amount int64
+ TransactionDate time.Time
+ InterestDate time.Time
+ Description string
+}
+
+func (transaction *Transaction) ToMoney() string {
+ money := float64(transaction.Amount) / 100
+ return fmt.Sprintf("%.2f", money)
+}
+
+//TODO: there probably is a better way to do this
+//TODO: do i have to close trans?
+func InsertTransactions(transactions []*Transaction, db *sql.DB) error {
+ txn, err := db.Begin()
+ if err != nil {
+ return err
+ }
+ stmt, err := txn.Prepare(pq.CopyIn("transaction", "account_id", "iban", "amount", "transaction_date", "interest_date", "description"))
+ if err != nil {
+ return err
+ }
+ for _, transaction := range transactions {
+ _, err := stmt.Exec(transaction.AccountID, transaction.IBAN, transaction.Amount, transaction.TransactionDate, transaction.InterestDate, transaction.Description)
+ if err != nil {
+ return err
+ }
+ }
+ if _, err = stmt.Exec(); err != nil {
+ return err
+ }
+ if err = stmt.Close(); err != nil {
+ return err
+ }
+ if err = txn.Commit(); err != nil {
+ return err
+ }
+ return nil
+}
M templates/transactions.html => templates/transactions.html +6 -20
@@ 1,33 1,19 @@
<table class="styled-table">
<thead>
<tr>
- <th>Account</th>
- <!--<th>Owner</th>-->
- <!--<th>Opponent</th>-->
- <!--<th>Sales Id</th>-->
- <th>Date</th>
- <!--<th>Currency Date</th>-->
+ <th>IBAN</th>
<th>Amount</th>
- <!--<th>Currency</th>-->
+ <th>Date</th>
<th>Description</th>
- <!--<th>Sales Detail</th>-->
- <!--<th>Message</th>-->
</tr>
</thead>
<tbody>
- {{ range .Items }}
+ {{ range .Transactions }}
<tr>
- <!--<td>{{ .AccountId }}</td>-->
- <td>{{ .AccountName }}</td>
- <!--<td>{{ .AccountOpponent }}</td>-->
- <!--<td>{{ .SalesId }}</td>-->
- <td>{{ .Date.Format "02/01/2006" }}</td>
- <!--<td>{{ .CurrencyDate }}</td>-->
- <td>€ {{ .ToEuro }}</td>
- <!--<td>{{ .Currency }}</td>-->
+ <td>{{ .IBAN }}</td>
+ <td>€ {{ .ToMoney }}</td>
+ <td>{{ .TransactionDate.Format "02/01/2006" }}</td>
<td>{{ .Description }}</td>
- <!--<td>{{ .SalesDetail }}</td>-->
- <!--<td>{{ .Message }}</td>-->
</tr>
{{ end }}
</tbody>
M web/dashboard.go => web/dashboard.go +2 -89
@@ 1,96 1,9 @@
package web
import (
- "encoding/csv"
- "fmt"
- "log"
- "os"
- "strconv"
- "strings"
- "time"
+ "git.sr.ht/~inferiormartin/shishutsu/database"
)
type DashboardPage struct {
- Items []DashboardItem
-}
-
-type DashboardItem struct {
- AccountId string
- AccountName string
- AccountOpponent string
- SalesId string
- Date time.Time
- CurrencyDate time.Time
- Amount int64
- Currency string
- Description string
- SalesDetail string
- Message string
-}
-
-//TODO: rename: also works for dollars
-func toEuro(amount int64) string {
- euro := float64(amount) / 100
- return fmt.Sprintf("%.2f", euro)
-}
-
-func (e *DashboardItem) ToEuro() string {
- return toEuro(e.Amount)
-}
-
-func parseDate(date string) time.Time {
- r, err := time.Parse("02/01/2006", date)
- if err != nil {
- log.Fatal(err)
- }
- return r
-}
-
-func parseFloat(value string) float64 {
- value = strings.Replace(value, ".", "", -1)
- value = strings.Replace(value, ",", ".", -1)
- result, err := strconv.ParseFloat(value, 64)
-
- if err != nil {
- log.Fatal(err)
- }
-
- return result
-}
-
-//TODO: use cmd/shi-import to import csv
-func GetDashboard() *DashboardPage {
- args := os.Args[1:]
- data, err := os.ReadFile(args[0])
- if err != nil {
- log.Fatal(err)
- }
- r := csv.NewReader(strings.NewReader(string(data)))
- r.Comma = ';'
- results, err := r.ReadAll()
- if err != nil {
- log.Fatal(err)
- }
- results = results[1:]
-
- page := &DashboardPage{make([]DashboardItem, 0)}
-
- for _, result := range results {
- item := DashboardItem{
- result[0],
- result[1],
- result[2],
- result[3],
- parseDate(result[4]),
- parseDate(result[5]),
- int64(parseFloat(result[6]) * 100),
- result[7],
- result[8],
- result[9],
- result[10],
- }
- page.Items = append(page.Items, item)
- }
-
- return page
+ Transactions []database.Transaction
}