diff options
Diffstat (limited to 'src/dbx')
| -rw-r--r-- | src/dbx/.gitignore | 1 | ||||
| -rw-r--r-- | src/dbx/db.go | 185 | ||||
| -rw-r--r-- | src/dbx/mintages.go | 217 | ||||
| -rw-r--r-- | src/dbx/sql/000-genesis.sql | 26 | ||||
| -rw-r--r-- | src/dbx/sql/last.sql | 157 | ||||
| -rw-r--r-- | src/dbx/users.go | 11 |
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: |