summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--GNUmakefile3
-rw-r--r--go.mod2
-rw-r--r--go.sum4
-rw-r--r--main.go5
-rw-r--r--src/dbx/db.go227
-rw-r--r--src/dbx/mintages.go65
-rw-r--r--src/dbx/sql/000-genesis.sql74
-rw-r--r--src/dbx/users.go64
-rw-r--r--src/http.go41
-rw-r--r--src/i18n.go6
-rw-r--r--src/mintage/mintage.go42
-rw-r--r--src/mintage/parser.go168
-rw-r--r--src/templates.go28
-rw-r--r--src/templates/coins-mintages.html.tmpl7
14 files changed, 495 insertions, 241 deletions
diff --git a/GNUmakefile b/GNUmakefile
index 3142cd5..d42cc0b 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -1,13 +1,14 @@
cssfiles := $(shell find static -name '*.css' -not -name '*.min.css')
cssfiles := $(cssfiles:.css=.min.css)
gofiles := $(shell find main.go src -name '*.go')
+sqlfiles := $(shell find src/dbx/sql -name '*.sql')
templates := $(shell find src/templates -name '*.tmpl')
exttmpl := $(wildcard cmd/exttmpl/*.go)
all: euro-cash.eu exttmpl
-euro-cash.eu: $(cssfiles) $(templates) $(gofiles)
+euro-cash.eu: $(cssfiles) $(templates) $(gofiles) $(sqlfiles)
go build
all-i18n: exttmpl
diff --git a/go.mod b/go.mod
index 89c5b16..ce85bca 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,8 @@ module git.thomasvoss.com/euro-cash.eu
go 1.24
require (
+ github.com/mattn/go-sqlite3 v1.14.28
+ golang.org/x/crypto v0.39.0
golang.org/x/text v0.26.0
golang.org/x/tools v0.34.0
)
diff --git a/go.sum b/go.sum
index c29acbb..513263d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,9 @@
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/mattn/go-sqlite3 v1.14.28 h1:ThEiQrnbtumT+QMknw63Befp/ce/nUPgBPMlRFEum7A=
+github.com/mattn/go-sqlite3 v1.14.28/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
+golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
diff --git a/main.go b/main.go
index 93d9017..299d77c 100644
--- a/main.go
+++ b/main.go
@@ -11,6 +11,7 @@ import (
"time"
"git.thomasvoss.com/euro-cash.eu/src"
+ "git.thomasvoss.com/euro-cash.eu/src/dbx"
"git.thomasvoss.com/euro-cash.eu/src/email"
)
@@ -29,11 +30,14 @@ func main() {
"address to send error messages from")
flag.StringVar(&email.Config.Password, "email-password", "",
"password to authenticate the email client")
+ flag.StringVar(&dbx.DBName, "db-name", "eurocash.db",
+ "database name or ‘:memory:’ for an in-memory database")
flag.Parse()
if *debugp {
go watch()
}
+ dbx.Init()
src.Run(*port)
}
@@ -55,6 +59,7 @@ func watch() {
}
if nstat.ModTime() != ostat.ModTime() {
+ dbx.DB.Close()
if err := syscall.Exec(path, os.Args, os.Environ()); err != nil {
log.Fatal(err)
}
diff --git a/src/dbx/db.go b/src/dbx/db.go
new file mode 100644
index 0000000..9086265
--- /dev/null
+++ b/src/dbx/db.go
@@ -0,0 +1,227 @@
+package dbx
+
+import (
+ "database/sql"
+ "embed"
+ "fmt"
+ "io/fs"
+ "log"
+ "path/filepath"
+ "reflect"
+ "sort"
+ "strings"
+
+ "github.com/mattn/go-sqlite3"
+)
+
+var (
+ DB *sql.DB
+ DBName string
+
+ //go:embed "sql/*.sql"
+ migrations embed.FS
+)
+
+func Init() {
+ var err error
+ if DB, err = sql.Open("sqlite3", DBName); err != nil {
+ log.Fatal(err)
+ }
+ if err = DB.Ping(); err != nil {
+ log.Fatal(err)
+ }
+
+ if err := applyMigrations("sql"); err != nil {
+ log.Fatal(err)
+ }
+
+ /* TODO: Remove debug code */
+ if err := CreateUser(User{
+ Email: "mail@thomasvoss.com",
+ Username: "Thomas",
+ Password: "69",
+ AdminP: true,
+ }); err != nil {
+ log.Fatal(err)
+ }
+ if err := CreateUser(User{
+ Email: "foo@BAR.baz",
+ Username: "Foobar",
+ Password: "420",
+ AdminP: false,
+ }); err != nil {
+ log.Fatal(err)
+ }
+ if _, err := GetMintages("ad"); err != nil {
+ log.Fatal(err)
+ }
+}
+
+func applyMigrations(dir string) error {
+ var latest int
+ migratedp := true
+
+ rows, err := DB.Query("SELECT latest FROM migration")
+ if err != nil {
+ e, ok := err.(sqlite3.Error)
+ /* IDK if there is a better way to do this… lol */
+ if ok && e.Error() == "no such table: migration" {
+ migratedp = false
+ } else {
+ return err
+ }
+ } else {
+ defer rows.Close()
+ }
+
+ if migratedp {
+ rows.Next()
+ if err := rows.Err(); err != nil {
+ return err
+ }
+ if err := rows.Scan(&latest); err != nil {
+ return err
+ }
+ } else {
+ latest = -1
+ }
+
+ files, err := fs.ReadDir(migrations, dir)
+ if err != nil {
+ return err
+ }
+
+ scripts := []string{}
+ for _, f := range files {
+ scripts = append(scripts, f.Name())
+ }
+
+ sort.Strings(scripts)
+ for _, f := range scripts[latest+1:] {
+ qry, err := migrations.ReadFile(filepath.Join(dir, f))
+ if err != nil {
+ return err
+ }
+
+ tx, err := DB.Begin()
+ if err != nil {
+ return err
+ }
+
+ if _, err := tx.Exec(string(qry)); err != nil {
+ tx.Rollback()
+ return fmt.Errorf("error in ‘%s’: %w", f, err)
+ }
+
+ var n int
+ if _, err := fmt.Sscanf(f, "%d", &n); err != nil {
+ return err
+ }
+ _, err = tx.Exec("UPDATE migration SET latest = ? WHERE id = 1", n)
+ if err != nil {
+ return err
+ }
+
+ if err := tx.Commit(); err != nil {
+ return err
+ }
+ log.Printf("Applied database migration ‘%s’", f)
+ }
+
+ return nil
+}
+
+func scanToStructs[T any](rs *sql.Rows) ([]T, error) {
+ xs := []T{}
+ for rs.Next() {
+ x, err := scanToStruct[T](rs)
+ if err != nil {
+ return nil, err
+ }
+ xs = append(xs, x)
+ }
+ return xs, rs.Err()
+}
+
+func scanToStruct[T any](rs *sql.Rows) (T, error) {
+ var t, zero T
+
+ cols, err := rs.Columns()
+ if err != nil {
+ return zero, err
+ }
+
+ v := reflect.ValueOf(&t).Elem()
+ tType := v.Type()
+
+ rawValues := make([]any, len(cols))
+ for i := range rawValues {
+ var zero any
+ rawValues[i] = &zero
+ }
+
+ if err := rs.Scan(rawValues...); err != nil {
+ return zero, err
+ }
+
+ /* col idx → [field idx, array idx] */
+ arrayTargets := make(map[int][2]int)
+ colToField := make(map[string]int)
+
+ for i := 0; i < tType.NumField(); i++ {
+ field := tType.Field(i)
+ tag := field.Tag.Get("db")
+ if tag == "" {
+ continue
+ }
+
+ if strings.Contains(tag, ";") {
+ parts := strings.Split(tag, ";")
+ tag, dbcols := parts[0], parts[1:]
+ if tag != "array" {
+ /* TODO: This is bad… it should log something */
+ return zero, fmt.Errorf("invalid `db:\"…\"` tag ‘%s’", tag)
+ }
+ fv := v.Field(i)
+ if fv.Kind() != reflect.Array {
+ return zero, fmt.Errorf("field ‘%s’ is not array",
+ field.Name)
+ }
+ if len(dbcols) != fv.Len() {
+ return zero, fmt.Errorf("field ‘%s’ array length mismatch",
+ field.Name)
+ }
+ for j, colName := range cols {
+ for k, dbColName := range dbcols {
+ if colName == dbColName {
+ arrayTargets[j] = [2]int{i, k}
+ }
+ }
+ }
+ } else {
+ colToField[tag] = i
+ }
+ }
+
+ for i, col := range cols {
+ vp := rawValues[i].(*any)
+ if fieldIdx, ok := colToField[col]; ok {
+ assignValue(v.Field(fieldIdx), *vp)
+ } else if target, ok := arrayTargets[i]; ok {
+ assignValue(v.Field(target[0]).Index(target[1]), *vp)
+ }
+ }
+
+ return t, nil
+}
+
+func assignValue(fv reflect.Value, val any) {
+ if val == nil {
+ fv.Set(reflect.Zero(fv.Type()))
+ return
+ }
+ v := reflect.ValueOf(val)
+ if v.Type().ConvertibleTo(fv.Type()) {
+ fv.Set(v.Convert(fv.Type()))
+ }
+}
diff --git a/src/dbx/mintages.go b/src/dbx/mintages.go
new file mode 100644
index 0000000..96cc871
--- /dev/null
+++ b/src/dbx/mintages.go
@@ -0,0 +1,65 @@
+package dbx
+
+type MintageData struct {
+ Standard []MSRow
+ Commemorative []MCRow
+}
+
+type MSRow struct {
+ Type int `db:"type"`
+ Year int `db:"year"`
+ Mintmark string `db:"mintmark"`
+ Mintages [ndenoms]int `db:"array;€0,01;€0,02;€0,05;€0,10;€0,20;€0,50;€1,00;€2,00"`
+ Reference string `db:"reference"`
+}
+
+type MCRow struct {
+ Type int `db:"type"`
+ Year int `db:"year"`
+ Name string `db:"name"`
+ Number int `db:"number"`
+ Mintmark string `db:"mintmark"`
+ Mintage int `db:"mintage"`
+ Reference string `db:"reference"`
+}
+
+/* DO NOT REORDER! */
+const (
+ TypeCirc = iota
+ TypeNifc
+ TypeProof
+)
+
+/* DO NOT REORDER! */
+const (
+ MintageUnknown = -iota - 1
+ MintageInvalid
+)
+
+const ndenoms = 8
+
+func GetMintages(country string) (MintageData, error) {
+ var zero MintageData
+
+ srows, err := DB.Query(`SELECT * FROM mintages_s WHERE country = ?`, country)
+ if err != nil {
+ return zero, err
+ }
+ defer srows.Close()
+ xs, err := scanToStructs[MSRow](srows)
+ if err != nil {
+ return zero, err
+ }
+
+ crows, err := DB.Query(`SELECT * FROM mintages_c WHERE country = ?`, country)
+ if err != nil {
+ return zero, err
+ }
+ defer crows.Close()
+ ys, err := scanToStructs[MCRow](crows)
+ if err != nil {
+ return zero, err
+ }
+
+ return MintageData{xs, ys}, nil
+}
diff --git a/src/dbx/sql/000-genesis.sql b/src/dbx/sql/000-genesis.sql
new file mode 100644
index 0000000..6daad31
--- /dev/null
+++ b/src/dbx/sql/000-genesis.sql
@@ -0,0 +1,74 @@
+PRAGMA encoding = "UTF-8";
+
+CREATE TABLE migration (
+ id INTEGER PRIMARY KEY CHECK (id = 1),
+ latest INTEGER
+);
+INSERT INTO migration (id, latest) VALUES (1, -1);
+
+CREATE TABLE mintages_s (
+ country CHAR(2) NOT NULL COLLATE BINARY
+ CHECK(length(country) = 2),
+ type INTEGER NOT NULL -- Codes correspond to contants in mintages.go
+ CHECK(type BETWEEN 0 AND 2),
+ year INTEGER NOT NULL,
+ mintmark TEXT,
+ [€0,01] INTEGER,
+ [€0,02] INTEGER,
+ [€0,05] INTEGER,
+ [€0,10] INTEGER,
+ [€0,20] INTEGER,
+ [€0,50] INTEGER,
+ [€1,00] INTEGER,
+ [€2,00] INTEGER,
+ reference TEXT
+);
+
+CREATE TABLE mintages_c (
+ country CHAR(2) NOT NULL COLLATE BINARY
+ CHECK(length(country) = 2),
+ type INTEGER NOT NULL -- Codes correspond to contants in mintages.go
+ CHECK(type BETWEEN 0 AND 2),
+ name TEXT NOT NULL,
+ year INTEGER NOT NULL,
+ number INTEGER NOT NULL,
+ mintmark TEXT,
+ mintage INTEGER,
+ reference TEXT
+);
+
+-- TODO: Remove dummy data
+INSERT INTO mintages_s (
+ country,
+ type,
+ year,
+ mintmark,
+ [€0,01],
+ [€0,02],
+ [€0,05],
+ [€0,10],
+ [€0,20],
+ [€0,50],
+ [€1,00],
+ [€2,00],
+ reference
+) VALUES
+ ("ad", 0, 2014, NULL, 60000, 60000, 860000, 860000, 860000, 340000, 511843, 360000, NULL),
+ ("ad", 0, 2015, NULL, 0, 0, 0, 0, 0, 0, 0, 1072400, NULL),
+ ("ad", 0, 2016, NULL, 0, 0, 0, 0, 0, 0, 2339200, 0, NULL),
+ ("ad", 0, 2017, NULL, 2582395, 1515000, 2191421, 1103000, 1213000, 968800, 17000, 794588, NULL),
+ ("ad", 0, 2018, NULL, 2430000, 2550000, 1800000, 980000, 1014000, 890000, 0, 868000, NULL),
+ ("ad", 0, 2019, NULL, 2447000, 1727000, 2100000, 1610000, 1570000, 930000, 0, 1058310, NULL),
+ ("ad", 0, 2020, NULL, 0, 0, 0, 860000, 175000, 740000, 0, 1500000, NULL),
+ ("ad", 0, 2021, NULL, 200000, 700000, 0, 1400000, 1420000, 600000, 50000, 1474500, NULL),
+ ("ad", 0, 2022, NULL, 700000, 450000, 400000, 700000, 700000, 380000, 0, 1708000, NULL),
+ ("ad", 0, 2023, NULL, 0, 0, 0, 0, 0, 0, 0, 2075250, NULL),
+ ("ad", 0, 2024, NULL, 0, 900300, 1950000, 1000000, 700000, 500000, 1050000, 1601200, NULL),
+ ("ad", 0, 2025, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
+
+CREATE TABLE users (
+ email TEXT COLLATE BINARY,
+ username TEXT COLLATE BINARY,
+ password TEXT COLLATE BINARY,
+ adminp INTEGER
+); \ No newline at end of file
diff --git a/src/dbx/users.go b/src/dbx/users.go
new file mode 100644
index 0000000..a0712ee
--- /dev/null
+++ b/src/dbx/users.go
@@ -0,0 +1,64 @@
+package dbx
+
+import (
+ "database/sql"
+ "errors"
+
+ "golang.org/x/crypto/bcrypt"
+ "golang.org/x/text/unicode/norm"
+)
+
+type User struct {
+ Email string
+ Username string
+ Password string
+ AdminP bool
+}
+
+var LoginFailed = errors.New("No user with the given username and password")
+
+func CreateUser(user User) error {
+ user.Username = norm.NFC.String(user.Username)
+ user.Password = norm.NFC.String(user.Password)
+
+ hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), 15)
+ if err != nil {
+ return err
+ }
+
+ _, err = DB.Exec(`
+ INSERT INTO users (
+ email,
+ username,
+ password,
+ adminp
+ ) VALUES (?, ?, ?, ?)
+ `, user.Email, user.Username, string(hash), user.AdminP)
+ return err
+}
+
+func Login(username, password string) (User, error) {
+ username = norm.NFC.String(username)
+ password = norm.NFC.String(password)
+
+ u := User{}
+ /* TODO: Pass a context here? */
+ err := DB.QueryRow(`SELECT * FROM users WHERE username = ?`, username).
+ Scan(&u.Email, &u.Username, &u.Password, &u.AdminP)
+
+ switch {
+ case errors.Is(err, sql.ErrNoRows):
+ return User{}, LoginFailed
+ case err != nil:
+ return User{}, err
+ }
+
+ err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
+ switch {
+ case errors.Is(err, bcrypt.ErrMismatchedHashAndPassword):
+ return User{}, LoginFailed
+ case err != nil:
+ return User{}, err
+ }
+ return u, nil
+}
diff --git a/src/http.go b/src/http.go
index eaaeb71..4c2f5c6 100644
--- a/src/http.go
+++ b/src/http.go
@@ -12,8 +12,8 @@ import (
"strconv"
"strings"
+ "git.thomasvoss.com/euro-cash.eu/src/dbx"
"git.thomasvoss.com/euro-cash.eu/src/email"
- "git.thomasvoss.com/euro-cash.eu/src/mintage"
)
type middleware = func(http.Handler) http.Handler
@@ -42,7 +42,9 @@ func Run(port int) {
portStr := ":" + strconv.Itoa(port)
log.Println("Listening on", portStr)
- log.Fatal(http.ListenAndServe(portStr, mux))
+ err := http.ListenAndServe(portStr, mux)
+ dbx.DB.Close()
+ log.Fatal(err)
}
func chain(xs ...middleware) middleware {
@@ -138,12 +140,13 @@ func mintageHandler(next http.Handler) http.Handler {
}
var err error
- td.Mintages, err = mintage.Parse(td.Code)
+ td.Mintages, err = dbx.GetMintages(td.Code)
if err != nil {
throwError(http.StatusInternalServerError, err, w, r)
return
}
+ processMintages(&td.Mintages, td.Type)
next.ServeHTTP(w, r)
})
}
@@ -188,3 +191,35 @@ func throwError(status int, err error, w http.ResponseWriter, r *http.Request) {
Msg: http.StatusText(status),
})
}
+
+func processMintages(md *dbx.MintageData, typeStr string) {
+ var typ int
+ switch typeStr {
+ case "nifc":
+ typ = dbx.TypeNifc
+ case "proof":
+ typ = dbx.TypeProof
+ default:
+ typ = dbx.TypeCirc
+ }
+
+ md.Standard = slices.DeleteFunc(md.Standard,
+ func(x dbx.MSRow) bool { return x.Type != typ })
+ md.Commemorative = slices.DeleteFunc(md.Commemorative,
+ func(x dbx.MCRow) bool { return x.Type != typ })
+ slices.SortFunc(md.Standard, func(x, y dbx.MSRow) int {
+ if x.Year != y.Year {
+ return x.Year - y.Year
+ }
+ return strings.Compare(x.Mintmark, y.Mintmark)
+ })
+ slices.SortFunc(md.Commemorative, func(x, y dbx.MCRow) int {
+ if x.Year != y.Year {
+ return x.Year - y.Year
+ }
+ if x.Number != y.Number {
+ return x.Number - y.Number
+ }
+ return strings.Compare(x.Mintmark, y.Mintmark)
+ })
+}
diff --git a/src/i18n.go b/src/i18n.go
index 1d53b52..b1c954d 100644
--- a/src/i18n.go
+++ b/src/i18n.go
@@ -27,8 +27,8 @@ type locale struct {
}
var (
- /* To determine the correct date format to use, use the ‘datefmt’ script in
- the repository root */
+ /* To determine the correct date format to use, use the ‘datefmt’ script in
+ the repository root */
Locales = [...]locale{
{
Bcp: "ca",
@@ -176,7 +176,7 @@ var (
Bcp: "bg",
Name: "български",
dateFmt: "2.01.2006 г.",
- Eurozone: false, /* TODO(2026): Set to true */
+ Eurozone: false, /* TODO(2026): Set to true */
Enabled: true,
},
{
diff --git a/src/mintage/mintage.go b/src/mintage/mintage.go
deleted file mode 100644
index 0e98bd1..0000000
--- a/src/mintage/mintage.go
+++ /dev/null
@@ -1,42 +0,0 @@
-package mintage
-
-type Data struct {
- Standard []SRow
- Commemorative []CRow
-}
-
-type SRow struct {
- Year int
- Mintmark string
- Mintages [denoms]int
- Reference string
-}
-
-type CRow struct {
- Year int
- Name string
- Mintmark string
- Mintage int
- Reference string
-}
-
-const (
- TypeCirc = iota
- TypeNifc
- TypeProof
-)
-
-const (
- Unknown = -iota - 1
- Invalid
-)
-
-const denoms = 8
-
-var cache map[string][3]Data = make(map[string][3]Data)
-
-func ClearCache(country string) {
- if _, ok := cache[country]; ok {
- delete(cache, country)
- }
-}
diff --git a/src/mintage/parser.go b/src/mintage/parser.go
deleted file mode 100644
index fe9ebd7..0000000
--- a/src/mintage/parser.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package mintage
-
-import (
- "encoding/csv"
- "io"
- "os"
- "path/filepath"
- "strconv"
- "time"
-)
-
-func Parse(country string) ([3]Data, error) {
- if data, ok := cache[country]; ok {
- return data, nil
- }
-
- var (
- data [3]Data
- err error
- path = filepath.Join("data", "mintages", country)
- )
-
- data[TypeCirc].Standard, err = parseS(path + "-s-circ.csv")
- if err != nil {
- return data, err
- }
- data[TypeNifc].Standard, err = parseS(path + "-s-nifc.csv")
- if err != nil {
- return data, err
- }
- data[TypeProof].Standard, err = parseS(path + "-s-proof.csv")
- if err != nil {
- return data, err
- }
- data[TypeCirc].Commemorative, err = parseC(path + "-c-circ.csv")
- if err != nil {
- return data, err
- }
- data[TypeNifc].Commemorative, err = parseC(path + "-c-nifc.csv")
- if err != nil {
- return data, err
- }
- data[TypeProof].Commemorative, err = parseC(path + "-c-proof.csv")
- if err == nil {
- cache[country] = data
- }
- return data, err
-}
-
-func parseS(path string) ([]SRow, error) {
- rows := make([]SRow, 0, guessRows(false))
-
- f, err := os.Open(path)
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- r := csv.NewReader(f)
- r.Comment = '#'
- r.FieldsPerRecord = 11
- r.ReuseRecord = true
-
- /* Skip header */
- if _, err := r.Read(); err != nil {
- return nil, err
- }
-
- for {
- record, err := r.Read()
- if err == io.EOF {
- break
- }
- if err != nil {
- return nil, err
- }
-
- data := SRow{
- Mintmark: record[1],
- Reference: record[10],
- }
-
- data.Year, err = strconv.Atoi(record[0])
- if err != nil {
- return nil, err
- }
-
- for i, s := range record[2:10] {
- if s == "" {
- data.Mintages[i] = Unknown
- } else {
- data.Mintages[i], err = strconv.Atoi(s)
- if err != nil {
- data.Mintages[i] = Invalid
- }
- }
- }
-
- rows = append(rows, data)
- }
-
- return rows, nil
-}
-
-func parseC(path string) ([]CRow, error) {
- rows := make([]CRow, 0, guessRows(true))
-
- f, err := os.Open(path)
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- r := csv.NewReader(f)
- r.Comment = '#'
- r.FieldsPerRecord = 5
- r.ReuseRecord = true
-
- /* Skip header */
- if _, err := r.Read(); err != nil {
- return nil, err
- }
-
- for {
- record, err := r.Read()
- if err == io.EOF {
- break
- }
- if err != nil {
- return nil, err
- }
-
- data := CRow{
- Name: record[1],
- Mintmark: record[2],
- Reference: record[4],
- }
-
- data.Year, err = strconv.Atoi(record[0])
- if err != nil {
- return nil, err
- }
-
- s := record[3]
- if s == "" {
- data.Mintage = Unknown
- } else {
- data.Mintage, err = strconv.Atoi(s)
- if err != nil {
- data.Mintage = Invalid
- }
- }
-
- rows = append(rows, data)
- }
-
- return rows, nil
-}
-
-func guessRows(commemorativep bool) int {
- /* Try to guess the number of rows for Germany, because nobody needs more
- rows than Germany. */
- n := (time.Now().Year() - 2002) * 5
- if commemorativep {
- return n * 2
- }
- return n
-}
diff --git a/src/templates.go b/src/templates.go
index f6413d9..28b7700 100644
--- a/src/templates.go
+++ b/src/templates.go
@@ -7,13 +7,13 @@ import (
"log"
"strings"
- "git.thomasvoss.com/euro-cash.eu/src/mintage"
+ "git.thomasvoss.com/euro-cash.eu/src/dbx"
)
type templateData struct {
Printer Printer
Code, Type string
- Mintages [3]mintage.Data
+ Mintages dbx.MintageData
Countries []country
}
@@ -24,13 +24,12 @@ var (
errorTmpl = buildTemplate("-error")
templates map[string]*template.Template
funcmap = map[string]any{
- "denoms": denoms,
- "locales": locales,
- "safe": asHTML,
- "sprintf": fmt.Sprintf,
- "strToCtype": strToCtype,
- "toUpper": strings.ToUpper,
- "tuple": templateMakeTuple,
+ "denoms": denoms,
+ "locales": locales,
+ "safe": asHTML,
+ "sprintf": fmt.Sprintf,
+ "toUpper": strings.ToUpper,
+ "tuple": templateMakeTuple,
}
)
@@ -83,17 +82,6 @@ func templateMakeTuple(args ...any) []any {
return args
}
-func strToCtype(s string) int {
- switch s {
- case "nifc":
- return mintage.TypeNifc
- case "proof":
- return mintage.TypeProof
- default:
- return mintage.TypeCirc
- }
-}
-
func (td templateData) T(fmt string, args ...any) string {
return td.Printer.T(fmt, args...)
}
diff --git a/src/templates/coins-mintages.html.tmpl b/src/templates/coins-mintages.html.tmpl
index 8554c3f..772db33 100644
--- a/src/templates/coins-mintages.html.tmpl
+++ b/src/templates/coins-mintages.html.tmpl
@@ -66,7 +66,6 @@
</div>
<button type="submit">{{ .T "Filter" }}</button>
</form>
- {{ $data := (index .Mintages (strToCtype .Type)) }}
<figure>
<figcaption>{{ .T "Standard Issue Coins" }}</figcaption>
<table class="mintage-table" role="grid">
@@ -81,7 +80,7 @@
<tbody>
{{ $p := .Printer }}
{{ $type := .Type }}
- {{ range $data.Standard }}
+ {{ range .Mintages.Standard }}
<tr>
<th scope="col">
{{- .Year -}}
@@ -106,7 +105,7 @@
</tbody>
</table>
</figure>
- {{ if ne (len $data.Commemorative) 0 }}
+ {{ if ne (len .Mintages.Commemorative) 0 }}
<figure>
<figcaption>{{ .T "Commemorative Coins" }}</figcaption>
<table class="mintage-table-cc" role="grid">
@@ -118,7 +117,7 @@
<tbody>
{{ $p := .Printer }}
{{ $type := .Type }}
- {{ range $data.Commemorative }}
+ {{ range .Mintages.Commemorative }}
<tr>
<th scope="col">
{{- .Year -}}