aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/dbx
diff options
context:
space:
mode:
Diffstat (limited to 'src/dbx')
-rw-r--r--src/dbx/.gitignore1
-rw-r--r--src/dbx/db.go185
-rw-r--r--src/dbx/mintages.go217
-rw-r--r--src/dbx/sql/000-genesis.sql26
-rw-r--r--src/dbx/sql/last.sql157
-rw-r--r--src/dbx/users.go11
6 files changed, 401 insertions, 196 deletions
diff --git a/src/dbx/.gitignore b/src/dbx/.gitignore
deleted file mode 100644
index d14a707..0000000
--- a/src/dbx/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-sql/last.sql \ No newline at end of file
diff --git a/src/dbx/db.go b/src/dbx/db.go
index fcb345e..5ee3782 100644
--- a/src/dbx/db.go
+++ b/src/dbx/db.go
@@ -1,44 +1,52 @@
package dbx
import (
- "database/sql"
+ "context"
"fmt"
"io/fs"
"log"
- "reflect"
"sort"
- "strings"
"git.thomasvoss.com/euro-cash.eu/pkg/atexit"
. "git.thomasvoss.com/euro-cash.eu/pkg/try"
+ "github.com/jmoiron/sqlx"
"github.com/mattn/go-sqlite3"
)
var (
- db *sql.DB
+ db *sqlx.DB
DBName string
)
func Init(sqlDir fs.FS) {
- db = Try2(sql.Open("sqlite3", DBName))
- Try(db.Ping())
+ db = sqlx.MustConnect("sqlite3", DBName)
atexit.Register(Close)
+
+ conn := Try2(db.Conn(context.Background()))
+ Try(conn.Raw(func(driverConn any) error {
+ return driverConn.(*sqlite3.SQLiteConn).RegisterFunc("C_",
+ func(s, _ string) string {
+ return s
+ }, true)
+ }))
+ conn.Close()
+
Try(applyMigrations(sqlDir))
/* TODO: Remove debug code */
- Try(CreateUser(User{
- Email: "mail@thomasvoss.com",
- Username: "Thomas",
- Password: "69",
- AdminP: true,
- }))
- Try(CreateUser(User{
- Email: "foo@BAR.baz",
- Username: "Foobar",
- Password: "420",
- AdminP: false,
- }))
- Try2(GetMintages("ad"))
+ /* Try(CreateUser(User{
+ Email: "mail@thomasvoss.com",
+ Username: "Thomas",
+ Password: "69",
+ AdminP: true,
+ }))
+ Try(CreateUser(User{
+ Email: "foo@BAR.baz",
+ Username: "Foobar",
+ Password: "420",
+ AdminP: false,
+ }))
+ Try2(GetMintages("ad", TypeCirc)) */
}
func Close() {
@@ -49,7 +57,7 @@ func applyMigrations(dir fs.FS) error {
var latest int
migratedp := true
- rows, err := db.Query("SELECT latest FROM migration")
+ err := db.QueryRow("SELECT latest FROM migration").Scan(&latest)
if err != nil {
e, ok := err.(sqlite3.Error)
/* IDK if there is a better way to do this… lol */
@@ -58,19 +66,9 @@ func applyMigrations(dir fs.FS) error {
} 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 {
+ if !migratedp {
latest = -1
}
@@ -104,24 +102,31 @@ func applyMigrations(dir fs.FS) error {
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 {
+ goto error
}
- var n int
- if _, err := fmt.Sscanf(f, "%d", &n); err != nil {
- return err
+ if _, err = tx.Exec(string(qry)); err != nil {
+ err = fmt.Errorf("error in ‘%s’: %w", f, err)
+ goto error
}
+
_, err = tx.Exec("UPDATE migration SET latest = ? WHERE id = 1", n)
if err != nil {
- return err
+ goto error
}
- if err := tx.Commit(); err != nil {
- return err
+ if err = tx.Commit(); err != nil {
+ goto error
}
+
log.Printf("Applied database migration ‘%s’\n", f)
+ continue
+
+ error:
+ tx.Rollback()
+ return err
}
if last != "" {
@@ -137,103 +142,3 @@ func applyMigrations(dir fs.FS) error {
return nil
}
-
-func scanToStruct[T any](rs *sql.Rows) (T, error) {
- return scanToStruct2[T](rs, true)
-}
-
-func scanToStructs[T any](rs *sql.Rows) ([]T, error) {
- xs := []T{}
- for rs.Next() {
- x, err := scanToStruct2[T](rs, false)
- if err != nil {
- return nil, err
- }
- xs = append(xs, x)
- }
- return xs, rs.Err()
-}
-
-func scanToStruct2[T any](rs *sql.Rows, callNextP bool) (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 callNextP {
- rs.Next()
- if err := rs.Err(); err != nil {
- return zero, err
- }
- }
- 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, ";") {
- dbcols := strings.Split(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
index 4a6d5d3..2223eff 100644
--- a/src/dbx/mintages.go
+++ b/src/dbx/mintages.go
@@ -1,65 +1,212 @@
package dbx
-type MintageData struct {
- Standard []MSRow
- Commemorative []MCRow
+import (
+ "context"
+ "database/sql"
+ "slices"
+)
+
+type CountryMintageData struct {
+ Standard []MSCountryRow
+ Commemorative []MCommemorative
+}
+
+type YearMintageData struct {
+ Standard []MSYearRow
+ Commemorative []MCommemorative
+}
+
+type msRow struct {
+ Country string
+ Type MintageType
+ Year int
+ Denomination float64
+ Mintmark sql.Null[string]
+ Mintage sql.Null[int]
+ Reference sql.Null[string]
}
-type MSRow struct {
- Type int `db:"type"`
- Year int `db:"year"`
- Mintmark string `db:"mintmark"`
- Mintages [ndenoms]int `db:"€0,01;€0,02;€0,05;€0,10;€0,20;€0,50;€1,00;€2,00"`
- Reference string `db:"reference"`
+type MSCountryRow struct {
+ Year int
+ Mintmark sql.Null[string]
+ Mintages [ndenoms]sql.Null[int]
+ References [ndenoms]sql.Null[string]
}
-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"`
+type MSYearRow struct {
+ Country string
+ Mintmark sql.Null[string]
+ Mintages [ndenoms]sql.Null[int]
+ References [ndenoms]sql.Null[string]
}
+type MCommemorative struct {
+ Country string
+ Type MintageType
+ Year int
+ Name string
+ Number int
+ Mintmark sql.Null[string]
+ Mintage sql.Null[int]
+ Reference sql.Null[string]
+}
+
+type MintageType int
+
/* DO NOT REORDER! */
const (
- TypeCirc = iota
+ TypeCirc MintageType = iota
TypeNifc
TypeProof
)
-/* DO NOT REORDER! */
-const (
- MintageUnknown = -iota - 1
- MintageInvalid
-)
-
const ndenoms = 8
-func GetMintages(country string) (MintageData, error) {
- var zero MintageData
+func NewMintageType(s string) MintageType {
+ switch s {
+ case "circ":
+ return TypeCirc
+ case "nifc":
+ return TypeNifc
+ case "proof":
+ return TypeProof
+ }
+ /* We can get here if the user sends a request manually, so just
+ fallback to this */
+ return TypeCirc
+}
+
+func GetMintagesByYear(year int, typ MintageType) (YearMintageData, error) {
+ var (
+ zero YearMintageData
+ xs []MSYearRow
+ ys []MCommemorative
+ )
- srows, err := db.Query(`SELECT * FROM mintages_s WHERE country = ?`, country)
+ rs, err := db.QueryxContext(context.TODO(), `
+ SELECT * FROM mintages_s
+ WHERE year = ? AND type = ?
+ ORDER BY country, mintmark, denomination
+ `, year, typ)
if err != nil {
return zero, err
}
- defer srows.Close()
- xs, err := scanToStructs[MSRow](srows)
- if err != nil {
+
+ for rs.Next() {
+ var x msRow
+ if err = rs.StructScan(&x); err != nil {
+ return zero, err
+ }
+
+ loop:
+ msr := MSYearRow{
+ Country: x.Country,
+ Mintmark: x.Mintmark,
+ }
+ i := denomToIdx(x.Denomination)
+ msr.Mintages[i] = x.Mintage
+ msr.References[i] = x.Reference
+
+ for rs.Next() {
+ var y msRow
+ if err = rs.StructScan(&y); err != nil {
+ return zero, err
+ }
+
+ if x.Country != y.Country || x.Mintmark != y.Mintmark {
+ x = y
+ xs = append(xs, msr)
+ goto loop
+ }
+
+ i = denomToIdx(y.Denomination)
+ msr.Mintages[i] = y.Mintage
+ msr.References[i] = y.Reference
+ }
+
+ xs = append(xs, msr)
+ }
+
+ if err = rs.Err(); err != nil {
return zero, err
}
- crows, err := db.Query(`SELECT * FROM mintages_c WHERE country = ?`, country)
+ db.SelectContext(context.TODO(), &ys, `
+ SELECT * FROM mintages_c
+ WHERE year = ? and type = ?
+ ORDER BY country, mintmark, number
+ `, year, typ)
+
+ return YearMintageData{xs, ys}, nil
+}
+
+func GetMintagesByCountry(country string, typ MintageType) (CountryMintageData, error) {
+ var (
+ zero CountryMintageData
+ xs []MSCountryRow
+ ys []MCommemorative
+ )
+
+ rs, err := db.QueryxContext(context.TODO(), `
+ SELECT * FROM mintages_s
+ WHERE country = ? AND type = ?
+ ORDER BY year, mintmark, denomination
+ `, country, typ)
if err != nil {
return zero, err
}
- defer crows.Close()
- ys, err := scanToStructs[MCRow](crows)
- if err != nil {
+
+ for rs.Next() {
+ var x msRow
+ if err = rs.StructScan(&x); err != nil {
+ return zero, err
+ }
+
+ loop:
+ msr := MSCountryRow{
+ Year: x.Year,
+ Mintmark: x.Mintmark,
+ }
+ i := denomToIdx(x.Denomination)
+ msr.Mintages[i] = x.Mintage
+ msr.References[i] = x.Reference
+
+ for rs.Next() {
+ var y msRow
+ if err = rs.StructScan(&y); err != nil {
+ return zero, err
+ }
+
+ if x.Year != y.Year || x.Mintmark != y.Mintmark {
+ x = y
+ xs = append(xs, msr)
+ goto loop
+ }
+
+ i = denomToIdx(y.Denomination)
+ msr.Mintages[i] = y.Mintage
+ msr.References[i] = y.Reference
+ }
+
+ xs = append(xs, msr)
+ }
+
+ if err = rs.Err(); err != nil {
return zero, err
}
- return MintageData{xs, ys}, nil
+ db.SelectContext(context.TODO(), &ys, `
+ SELECT * FROM mintages_c
+ WHERE country = ? and type = ?
+ ORDER BY year, mintmark, number
+ `, country, typ)
+
+ return CountryMintageData{xs, ys}, rs.Err()
+}
+
+func denomToIdx(d float64) int {
+ return slices.Index([]float64{
+ 0.01, 0.02, 0.05, 0.10,
+ 0.20, 0.50, 1.00, 2.00,
+ }, d)
}
diff --git a/src/dbx/sql/000-genesis.sql b/src/dbx/sql/000-genesis.sql
index 56ae7c3..c16c6ae 100644
--- a/src/dbx/sql/000-genesis.sql
+++ b/src/dbx/sql/000-genesis.sql
@@ -7,30 +7,26 @@ CREATE TABLE migration (
INSERT INTO migration (id, latest) VALUES (1, -1);
CREATE TABLE mintages_s (
- country CHAR(2) NOT NULL COLLATE BINARY
+ country CHAR(2) NOT NULL COLLATE BINARY
CHECK(length(country) = 2),
- type INTEGER NOT NULL -- Codes correspond to contants in mintages.go
+ -- Codes correspond to contants in mintages.go
+ type INTEGER NOT NULL
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
+ year INTEGER NOT NULL,
+ denomination REAL NOT NULL,
+ mintmark TEXT,
+ mintage 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
+ -- Codes correspond to contants in mintages.go
+ type INTEGER NOT NULL
CHECK(type BETWEEN 0 AND 2),
- name TEXT NOT NULL,
year INTEGER NOT NULL,
+ name TEXT NOT NULL,
number INTEGER NOT NULL,
mintmark TEXT,
mintage INTEGER,
diff --git a/src/dbx/sql/last.sql b/src/dbx/sql/last.sql
new file mode 100644
index 0000000..22ebab8
--- /dev/null
+++ b/src/dbx/sql/last.sql
@@ -0,0 +1,157 @@
+DELETE FROM mintages_s;
+DELETE FROM mintages_c;
+
+INSERT INTO mintages_s (
+ country,
+ type,
+ year,
+ denomination,
+ mintmark,
+ mintage,
+ reference
+) VALUES
+ ('at', 0, 2017, 0.01, NULL, 37700000, NULL),
+ ('at', 0, 2017, 0.02, NULL, 57200000, NULL),
+ ('at', 0, 2017, 0.05, NULL, 35200000, NULL),
+ ('at', 0, 2017, 0.10, NULL, 39500000, NULL),
+ ('at', 0, 2017, 0.20, NULL, 30000000, NULL),
+ ('at', 0, 2017, 0.50, NULL, 15000000, NULL),
+ ('at', 0, 2017, 1.00, NULL, 8000000, NULL),
+ ('at', 0, 2017, 2.00, NULL, 17700000, NULL),
+ ('de', 0, 2017, 0.01, 'A', 81600000, NULL),
+ ('de', 0, 2017, 0.02, 'A', 72200000, NULL),
+ ('de', 0, 2017, 0.05, 'A', 30000000, NULL),
+ ('de', 0, 2017, 0.10, 'A', 25200000, NULL),
+ ('de', 0, 2017, 0.20, 'A', 21600000, NULL),
+ ('de', 0, 2017, 0.50, 'A', 0, NULL),
+ ('de', 0, 2017, 1.00, 'A', 0, NULL),
+ ('de', 0, 2017, 2.00, 'A', 18120000, NULL),
+ ('de', 0, 2017, 0.01, 'D', 85680000, NULL),
+ ('de', 0, 2017, 0.02, 'D', 75810000, NULL),
+ ('de', 0, 2017, 0.05, 'D', 31500000, NULL),
+ ('de', 0, 2017, 0.10, 'D', 26460000, NULL),
+ ('de', 0, 2017, 0.20, 'D', 22680000, NULL),
+ ('de', 0, 2017, 0.50, 'D', 0, NULL),
+ ('de', 0, 2017, 1.00, 'D', 0, NULL),
+ ('de', 0, 2017, 2.00, 'D', 19110000, NULL),
+ ('ad', 0, 2014, 0.01, NULL, 60000, NULL),
+ ('ad', 0, 2014, 0.02, NULL, 60000, NULL),
+ ('ad', 0, 2014, 0.05, NULL, 860000, NULL),
+ ('ad', 0, 2014, 0.10, NULL, 860000, NULL),
+ ('ad', 0, 2014, 0.20, NULL, 860000, NULL),
+ ('ad', 0, 2014, 0.50, NULL, 340000, NULL),
+ ('ad', 0, 2014, 1.00, NULL, 511843, NULL),
+ ('ad', 0, 2014, 2.00, NULL, 360000, NULL),
+ ('ad', 0, 2015, 0.01, NULL, 0, NULL),
+ ('ad', 0, 2015, 0.02, NULL, 0, NULL),
+ ('ad', 0, 2015, 0.05, NULL, 0, NULL),
+ ('ad', 0, 2015, 0.10, NULL, 0, NULL),
+ ('ad', 0, 2015, 0.20, NULL, 0, NULL),
+ ('ad', 0, 2015, 0.50, NULL, 0, NULL),
+ ('ad', 0, 2015, 1.00, NULL, 0, NULL),
+ ('ad', 0, 2015, 2.00, NULL, 1072400, NULL),
+ ('ad', 0, 2016, 0.01, NULL, 0, NULL),
+ ('ad', 0, 2016, 0.02, NULL, 0, NULL),
+ ('ad', 0, 2016, 0.05, NULL, 0, NULL),
+ ('ad', 0, 2016, 0.10, NULL, 0, NULL),
+ ('ad', 0, 2016, 0.20, NULL, 0, NULL),
+ ('ad', 0, 2016, 0.50, NULL, 0, NULL),
+ ('ad', 0, 2016, 1.00, NULL, 2339200, NULL),
+ ('ad', 0, 2016, 2.00, NULL, 0, NULL),
+ ('ad', 0, 2017, 0.01, NULL, 2582395, NULL),
+ ('ad', 0, 2017, 0.02, NULL, 1515000, NULL),
+ ('ad', 0, 2017, 0.05, NULL, 2191421, NULL),
+ ('ad', 0, 2017, 0.10, NULL, 1103000, NULL),
+ ('ad', 0, 2017, 0.20, NULL, 1213000, NULL),
+ ('ad', 0, 2017, 0.50, NULL, 968800, NULL),
+ ('ad', 0, 2017, 1.00, NULL, 17000, NULL),
+ ('ad', 0, 2017, 2.00, NULL, 794588, NULL),
+ ('ad', 0, 2018, 0.01, NULL, 2430000, NULL),
+ ('ad', 0, 2018, 0.02, NULL, 2550000, NULL),
+ ('ad', 0, 2018, 0.05, NULL, 1800000, NULL),
+ ('ad', 0, 2018, 0.10, NULL, 980000, NULL),
+ ('ad', 0, 2018, 0.20, NULL, 1014000, NULL),
+ ('ad', 0, 2018, 0.50, NULL, 890000, NULL),
+ ('ad', 0, 2018, 1.00, NULL, 0, NULL),
+ ('ad', 0, 2018, 2.00, NULL, 868000, NULL),
+ ('ad', 0, 2019, 0.01, NULL, 2447000, NULL),
+ ('ad', 0, 2019, 0.02, NULL, 1727000, NULL),
+ ('ad', 0, 2019, 0.05, NULL, 2100000, NULL),
+ ('ad', 0, 2019, 0.10, NULL, 1610000, NULL),
+ ('ad', 0, 2019, 0.20, NULL, 1570000, NULL),
+ ('ad', 0, 2019, 0.50, NULL, 930000, NULL),
+ ('ad', 0, 2019, 1.00, NULL, 0, NULL),
+ ('ad', 0, 2019, 2.00, NULL, 1058310, NULL),
+ ('ad', 0, 2020, 0.01, NULL, 0, NULL),
+ ('ad', 0, 2020, 0.02, NULL, 0, NULL),
+ ('ad', 0, 2020, 0.05, NULL, 0, NULL),
+ ('ad', 0, 2020, 0.10, NULL, 860000, NULL),
+ ('ad', 0, 2020, 0.20, NULL, 175000, NULL),
+ ('ad', 0, 2020, 0.50, NULL, 740000, NULL),
+ ('ad', 0, 2020, 1.00, NULL, 0, NULL),
+ ('ad', 0, 2020, 2.00, NULL, 1500000, NULL),
+ ('ad', 0, 2021, 0.01, NULL, 200000, NULL),
+ ('ad', 0, 2021, 0.02, NULL, 700000, NULL),
+ ('ad', 0, 2021, 0.05, NULL, 0, NULL),
+ ('ad', 0, 2021, 0.10, NULL, 1400000, NULL),
+ ('ad', 0, 2021, 0.20, NULL, 1420000, NULL),
+ ('ad', 0, 2021, 0.50, NULL, 600000, NULL),
+ ('ad', 0, 2021, 1.00, NULL, 50000, NULL),
+ ('ad', 0, 2021, 2.00, NULL, 1474500, NULL),
+ ('ad', 0, 2022, 0.01, NULL, 700000, NULL),
+ ('ad', 0, 2022, 0.02, NULL, 450000, NULL),
+ ('ad', 0, 2022, 0.05, NULL, 400000, NULL),
+ ('ad', 0, 2022, 0.10, NULL, 700000, NULL),
+ ('ad', 0, 2022, 0.20, NULL, 700000, NULL),
+ ('ad', 0, 2022, 0.50, NULL, 380000, NULL),
+ ('ad', 0, 2022, 1.00, NULL, 0, NULL),
+ ('ad', 0, 2022, 2.00, NULL, 1708000, NULL),
+ ('ad', 0, 2023, 0.01, NULL, 0, NULL),
+ ('ad', 0, 2023, 0.02, NULL, 0, NULL),
+ ('ad', 0, 2023, 0.05, NULL, 0, NULL),
+ ('ad', 0, 2023, 0.10, NULL, 0, NULL),
+ ('ad', 0, 2023, 0.20, NULL, 0, NULL),
+ ('ad', 0, 2023, 0.50, NULL, 0, NULL),
+ ('ad', 0, 2023, 1.00, NULL, 0, NULL),
+ ('ad', 0, 2023, 2.00, NULL, 2075250, NULL),
+ ('ad', 0, 2024, 0.01, NULL, 0, NULL),
+ ('ad', 0, 2024, 0.02, NULL, 900300, NULL),
+ ('ad', 0, 2024, 0.05, NULL, 1950000, NULL),
+ ('ad', 0, 2024, 0.10, NULL, 1000000, NULL),
+ ('ad', 0, 2024, 0.20, NULL, 700000, NULL),
+ ('ad', 0, 2024, 0.50, NULL, 500000, NULL),
+ ('ad', 0, 2024, 1.00, NULL, 1050000, NULL),
+ ('ad', 0, 2024, 2.00, NULL, 1601200, NULL);
+
+INSERT INTO mintages_c (
+ country,
+ type,
+ year,
+ name,
+ number,
+ mintmark,
+ mintage,
+ reference
+) VALUES
+ ('de', 0, 2015, C_('Hessen', 'CC Name'), 1, 'A', 6000000, NULL),
+ ('de', 0, 2015, C_('German Reunification', 'CC Name'), 2, 'A', 6000000, NULL),
+ ('de', 0, 2015, C_('EU Flag', 'CC Name'), 3, 'A', 6000000, NULL),
+ ('de', 0, 2015, C_('Hessen', 'CC Name'), 1, 'D', 6300000, NULL),
+ ('de', 0, 2015, C_('German Reunification', 'CC Name'), 2, 'D', 6300000, NULL),
+ ('de', 0, 2015, C_('EU Flag', 'CC Name'), 3, 'D', 6300000, NULL),
+ ('de', 0, 2015, C_('Hessen', 'CC Name'), 1, 'F', 7200000, NULL),
+ ('de', 0, 2015, C_('German Reunification', 'CC Name'), 2, 'F', 7200000, NULL),
+ ('de', 0, 2015, C_('EU Flag', 'CC Name'), 3, 'F', 7200000, NULL),
+ ('de', 0, 2015, C_('Hessen', 'CC Name'), 1, 'G', 4200000, NULL),
+ ('de', 0, 2015, C_('German Reunification', 'CC Name'), 2, 'G', 4200000, NULL),
+ ('de', 0, 2015, C_('EU Flag', 'CC Name'), 3, 'G', 4200000, NULL),
+ ('de', 0, 2015, C_('Hessen', 'CC Name'), 1, 'J', 6300000, NULL),
+ ('de', 0, 2015, C_('German Reunification', 'CC Name'), 2, 'J', 6300000, NULL),
+ ('de', 0, 2015, C_('EU Flag', 'CC Name'), 3, 'J', 6300000, NULL),
+ ('sk', 0, 2014, C_('Slovak Republic to the EU', 'CC Name'), 1, NULL, 1000000, NULL),
+ ('sk', 0, 2015, C_('Ľudovít Štúr', 'CC Name'), 1, NULL, 1000000, NULL),
+ ('sk', 0, 2015, C_('EU Flag', 'CC Name'), 2, NULL, 1000000, NULL),
+ ('nl', 0, 2015, C_('EU Flag', 'CC Name'), 2, NULL, NULL, NULL),
+ ('fr', 0, 2015, C_('Peace and security', 'CC Name'), 1, NULL, 4000000, NULL),
+ ('fr', 0, 2015, C_('Fête de la Fédération', 'CC Name'), 2, NULL, 4000000, NULL),
+ ('fr', 0, 2015, C_('EU Flag', 'CC Name'), 3, NULL, 4000000, NULL); \ No newline at end of file
diff --git a/src/dbx/users.go b/src/dbx/users.go
index e2270db..bf78dcd 100644
--- a/src/dbx/users.go
+++ b/src/dbx/users.go
@@ -1,6 +1,7 @@
package dbx
import (
+ "context"
"database/sql"
"errors"
@@ -27,7 +28,7 @@ func CreateUser(user User) error {
return err
}
- _, err = db.Exec(`
+ _, err = db.ExecContext(context.TODO(), `
INSERT INTO users (
email,
username,
@@ -43,14 +44,14 @@ func Login(username, password string) (User, error) {
username = norm.NFC.String(username)
password = norm.NFC.String(password)
- /* TODO: Pass a context here? */
- rs, err := db.Query(`SELECT * FROM users WHERE username = ?`, username)
+ rs, err := db.QueryxContext(context.TODO(),
+ `SELECT * FROM users WHERE username = ?`, username)
if err != nil {
return User{}, err
}
- u, err := scanToStruct[User](rs)
- switch {
+ var u User
+ switch err = rs.Scan(&u); {
case errors.Is(err, sql.ErrNoRows):
return User{}, LoginFailed
case err != nil: