taldir

Directory service to resolve wallet mailboxes by messenger addresses
Log | Files | Refs | Submodules | README | LICENSE

commit ebb0b8205b01574ca66db7198c8a9a11f22c7b97
parent b98cfd344d0500d720cc551bfb861436a9c6aa04
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date:   Sat, 14 Feb 2026 17:39:43 +0100

Database refactor: This removes the GORM dependency and implements Taler-like database versioning. This also removes the sqlite in-memory testing database, which means you need to have a database running (PQ) to run the tests

Diffstat:
Mcmd/taldir-server/main.go | 10++++++++--
Mcmd/taldir-server/main_test.go | 98++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Mcmd/taldir-server/testdata/taldir-test.conf | 2+-
Mdebian/rules | 3+++
Mgo.mod | 21+++++++--------------
Mgo.sum | 42++++++++++++------------------------------
Mpkg/taldir/config.go | 5+++--
Mpkg/taldir/db.go | 466++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mpkg/taldir/taldir.go | 186+++++++++++++++++++++++++++++++++++++------------------------------------------
9 files changed, 678 insertions(+), 155 deletions(-)

diff --git a/cmd/taldir-server/main.go b/cmd/taldir-server/main.go @@ -34,9 +34,10 @@ import ( "os" "path" + "database/sql" + _ "github.com/lib/pq" "github.com/schanzen/taler-go/pkg/merchant" "gopkg.in/ini.v1" - "gorm.io/driver/postgres" "rsc.io/getopt" taldir "taler.net/taldir/pkg/taldir" ) @@ -100,7 +101,12 @@ func main() { cfg.Section("taldir-pq").Key("user").MustString("taldir"), cfg.Section("taldir-pq").Key("password").MustString("secret"), cfg.Section("taldir-pq").Key("db_name").MustString("taldir")) - db := postgres.Open(psqlconn) + + db, err := sql.Open("postgres", psqlconn) + if err != nil { + log.Panic(err) + } + defer db.Close() merchURL := cfg.Section("taldir").Key("base_url_merchant").MustString("https://backend.demo.taler.net") merchToken := cfg.Section("taldir").Key("merchant_token").MustString("sandbox") cfg.WriteTo(os.Stdout) diff --git a/cmd/taldir-server/main_test.go b/cmd/taldir-server/main_test.go @@ -20,18 +20,20 @@ package main_test import ( "bytes" + "database/sql" "fmt" "io" "log" "net/http" "net/http/httptest" "os" + "os/exec" "strings" "testing" + _ "github.com/mattn/go-sqlite3" "github.com/schanzen/taler-go/pkg/merchant" "gopkg.in/ini.v1" - "gorm.io/driver/sqlite" _ "taler.net/taldir/cmd/taldir-server" "taler.net/taldir/internal/util" taldir "taler.net/taldir/pkg/taldir" @@ -96,13 +98,103 @@ const merchantConfigResponse = `{ "version": "18:0:15" }` +func CheckVersioning(db *sql.DB) (bool, error) { + rows, err := db.Query(`SELECT schema_name FROM information_schema.schemata WHERE schema_name='_v';`) + if err != nil { + return false, err + } + defer rows.Close() + if rows.Next() { + fmt.Println("Versioning applied") + return true, nil + } + fmt.Printf("Versioning not applied: %v", rows.Err()) + return false, nil +} + +func CheckPatch(db *sql.DB, patchName string) (bool, error) { + rows, err := db.Query(`SELECT applied_by FROM _v.patches WHERE patch_name=$1 LIMIT 1;`, patchName) + if err != nil { + return false, err + } + defer rows.Close() + if rows.Next() { + return true, nil + } + return false, nil +} + +func RunSQL(db *sql.DB, patchName string) error { + path, err := exec.LookPath("psql") + if err != nil { + return err + } + out, err := exec.Command(path, "taler-directory", "-f", patchName, "-q", "--set", "ON_ERROR_STOP=1").Output() + // FIXME logger + fmt.Printf("Executing `%s %s %s %s %s %s %s`\n", path, "taler-directory", "-f", patchName, "-q", "--set", "ON_ERROR_STOP=1") + if err != nil { + fmt.Printf("%s, %v\n", out, err) + return err + } + return nil + +} + +func DBInit(db *sql.DB) error { + applied, err := CheckVersioning(db) + loadSuffix := "../../sql/" + if err != nil { + fmt.Printf("%v\n", err) + } + if !applied { + err := RunSQL(db, fmt.Sprintf("%s%s", loadSuffix, "versioning.sql")) + if err != nil { + return err + } + } + for i := range 10000 { + patchName := fmt.Sprintf("taler-directory-%04d", i+1) + applied, err := CheckPatch(db, patchName) + if err != nil { + return err + } + if applied { + fmt.Printf("Patch %s already applied\n", patchName) + continue + } + patchFile := fmt.Sprintf("%s%s.sql", loadSuffix, patchName) + if _, err := os.Stat(patchFile); err != nil { + fmt.Printf("Patch %s not found, up-to-date.\n", patchFile) + break + } + fmt.Printf("Applying patch %s\n", patchName) + err = RunSQL(db, patchFile) + if err != nil { + return err + } + } + return nil +} + func TestMain(m *testing.M) { cfg, err := ini.LooseLoad("testdata/taldir-test.conf") if err != nil { log.Fatalf("Failed to read config: %v", err) - os.Exit(1) } - db := sqlite.Open("file::memory:?cache=shared") + psqlconn := fmt.Sprintf("host=%s port=%d dbname=%s sslmode=disable", + cfg.Section("taldir-pq").Key("host").MustString("localhost"), + cfg.Section("taldir-pq").Key("port").MustInt64(5432), + cfg.Section("taldir-pq").Key("db_name").MustString("taler-directory")) + + db, err := sql.Open("postgres", psqlconn) + if err != nil { + log.Panic(err) + } + defer db.Close() + err = DBInit(db) + if err != nil { + log.Fatalf("Failed to apply versioning or patches: %v", err) + } merchServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path == "/config" { w.WriteHeader(http.StatusOK) diff --git a/cmd/taldir-server/testdata/taldir-test.conf b/cmd/taldir-server/testdata/taldir-test.conf @@ -34,4 +34,4 @@ host = "localhost" port = 5432 user = "taldir" password = "secret" -db_name = "taldir" +db_name = "taler-directory" diff --git a/debian/rules b/debian/rules @@ -12,6 +12,9 @@ override_dh_auto_clean: override_dh_auto_test: override_dh_installsystemd: dh_installsystemd -ptaler-directory --no-start --no-enable taler-directory.service + # final invocation to generate daemon reload + dh_installsystemd + #override_dh_auto_build: #override_dh_auto_install: # wget -N --progress=dot:mega $(URL) diff --git a/go.mod b/go.mod @@ -1,34 +1,27 @@ module taler.net/taldir -go 1.24.0 +go 1.25.0 require ( github.com/gertd/go-pluralize v0.2.1 github.com/gorilla/mux v1.8.1 github.com/kataras/i18n v0.0.8 + github.com/lib/pq v1.11.2 + github.com/mattn/go-sqlite3 v1.14.34 github.com/schanzen/taler-go v1.1.1 github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e gopkg.in/ini.v1 v1.67.0 - gorm.io/driver/postgres v1.6.0 - gorm.io/driver/sqlite v1.6.0 - gorm.io/gorm v1.31.1 rsc.io/getopt v0.0.0-20170811000552-20be20937449 ) require ( github.com/BurntSushi/toml v1.5.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/pgx/v5 v5.7.6 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - github.com/kr/text v0.1.0 // indirect - github.com/mattn/go-sqlite3 v1.14.22 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect - golang.org/x/crypto v0.46.0 // indirect golang.org/x/net v0.48.0 // indirect - golang.org/x/sync v0.19.0 // indirect golang.org/x/text v0.32.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum @@ -1,67 +1,49 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gertd/go-pluralize v0.2.1 h1:M3uASbVjMnTsPb0PNqg+E/24Vwigyo/tvyMTtAlLgiA= github.com/gertd/go-pluralize v0.2.1/go.mod h1:rbYaKDbsXxmRfr8uygAEKhOWsjyrrqrkHVpZvoOp8zk= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk= -github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/kataras/i18n v0.0.8 h1:thiDRqq4fN2sQOK5CwR5Yh1yT7swP6B3wnadILhqjhk= github.com/kataras/i18n v0.0.8/go.mod h1:M/yRAqQ3Y7z2oSpotxG/+nPrgsJLas6t0kEJfIJk98E= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= +github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/mattn/go-sqlite3 v1.14.34 h1:3NtcvcUnFBPsuRcno8pUtupspG/GM+9nZ88zgJcp6Zk= +github.com/mattn/go-sqlite3 v1.14.34/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/schanzen/taler-go v1.1.1 h1:No/N8Wa9CZjwLDqS47sdSLtWno08I7B43OKOUNpzjgg= github.com/schanzen/taler-go v1.1.1/go.mod h1:+l2TVAPZkF2d15X/XPLYZI5R6PdW6gc6Wft12jrl7tA= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= -gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= -gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= -gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= -gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= -gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= rsc.io/getopt v0.0.0-20170811000552-20be20937449 h1:UukjJOsjQH0DIuyyrcod6CXHS6cdaMMuJmrt+SN1j4A= rsc.io/getopt v0.0.0-20170811000552-20be20937449/go.mod h1:dhCdeqAxkyt5u3/sKRkUXuHaMXUu1Pt13GTQAM2xnig= diff --git a/pkg/taldir/config.go b/pkg/taldir/config.go @@ -19,9 +19,10 @@ package taldir import ( + "database/sql" + "github.com/schanzen/taler-go/pkg/merchant" "gopkg.in/ini.v1" - "gorm.io/gorm" ) type TaldirConfig struct { @@ -35,7 +36,7 @@ type TaldirConfig struct { Datahome string // The database connection to use - Db gorm.Dialector + Db *sql.DB // The merchant connection to use Merchant merchant.Merchant diff --git a/pkg/taldir/db.go b/pkg/taldir/db.go @@ -19,15 +19,56 @@ package taldir import ( - "gorm.io/gorm" + "context" + "database/sql" + "errors" + "fmt" "time" ) +// Validation is the object created when a registration for an entry is initiated. +// The Validation stores the identity key (sha256(identity)) the secret +// Validation reference. The Validation reference is sent to the identity +// depending on the out-of-band channel defined through the identity key type. +type Validation struct { + // When was this entry created in microseconds + CreatedAt int64 `json:"-"` + + // The hash (SHA512) of the alias + HAlias string `json:"h_alias"` + + // For how long should the registration last + Duration int64 `json:"duration"` + + // Target URI to associate with this alias + TargetURI string `json:"target_uri"` + + // The activation code sent to the client + Challenge string `json:"-"` + + // The challenge has been sent already + ChallengeSent bool `json:"-"` + + // true if this validation also requires payment + RequiresPayment bool `json:"-"` + + // How often was a solution for this validation tried + SolutionAttemptCount int + + // The beginning of the last solution timeframe + LastSolutionTimeframeStart int64 + + // The order ID associated with this validation + OrderID string `json:"-"` + + // Name of the validator + ValidatorName string +} + // Entry is a mapping from the alias hash to a target URI type Entry struct { - - // ORM - gorm.Model `json:"-"` + // When was this entry created in microseconds + CreatedAt int64 `json:"-"` // The salted hash (SHA512) of the hashed alias HsAlias string `json:"-"` @@ -36,5 +77,420 @@ type Entry struct { TargetURI string `json:"target_uri"` // How long the registration lasts in microseconds - Duration time.Duration `json:"-"` + Duration int64 `json:"-"` +} + +// Update validation in database +func UpdateValidationInDatabase(db *sql.DB, v *Validation) error { + query := `UPDATE taler_directory.validations + SET + "created_at" = $2, + "duration" = $3, + "target_uri" = $4, + "challenge" = $5, + "challenge_sent" = $6, + "requires_payment" = $7, + "solution_attempt_count" = $8, + "last_solution_timeframe_start" = $9, + "order_id" = $10, + "validator_name" = $11 + WHERE "h_alias" = $1;` + rows, err := db.Query(query, v.HAlias, v.CreatedAt, v.Duration, v.TargetURI, v.Challenge, v.ChallengeSent, v.RequiresPayment, v.SolutionAttemptCount, v.LastSolutionTimeframeStart, v.OrderID, v.ValidatorName) + if err != nil { + return err + } + defer rows.Close() + return nil +} + +// Insert new validation into database +func InsertValidationIntoDatabase(db *sql.DB, v *Validation) error { + query := `INSERT INTO taler_directory.validations + VALUES (DEFAULT, $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);` + rows, err := db.Query(query, v.CreatedAt, v.HAlias, v.Duration, v.TargetURI, v.Challenge, v.ChallengeSent, v.RequiresPayment, v.SolutionAttemptCount, v.LastSolutionTimeframeStart, v.OrderID, v.ValidatorName) + if err != nil { + return err + } + defer rows.Close() + return nil +} + +// Get Validation from database +func GetValidationFromDatabase(db *sql.DB, v *Validation, hAlias string, targetURI string, duration time.Duration) error { + query := `SELECT + "created_at", + "h_alias", + "duration", + "target_uri", + "challenge", + "challenge_sent", + "requires_payment", + "solution_attempt_count", + "last_solution_timeframe_start", + "order_id", + "validator_name" + FROM taler_directory.validations + WHERE + "h_alias"=$1 AND + "target_uri"=$2 AND + "duration"=$3 + ;` + // Execute Query + rows, err := db.Query(query, hAlias, targetURI, duration.Microseconds()) + if err != nil { + return err + } + defer rows.Close() + // Iterate over first + if !rows.Next() { + fmt.Printf("error val %v\n", rows.Err()) + return errors.New("Validation does not exist") + } + return rows.Scan( + &v.CreatedAt, + &v.HAlias, + &v.Duration, + &v.TargetURI, + &v.Challenge, + &v.ChallengeSent, + &v.RequiresPayment, + &v.SolutionAttemptCount, + &v.LastSolutionTimeframeStart, + &v.OrderID, + &v.ValidatorName, + ) +} + +// Get all Validations by hash-salted alias from database +func GetAllValidationsByHAliasFromDatabase(db *sql.DB, hAlias string) ([]Validation, error) { + query := `SELECT + "created_at", + "h_alias", + "duration", + "target_uri", + "challenge", + "challenge_sent", + "requires_payment", + "solution_attempt_count", + "last_solution_timeframe_start", + "order_id", + "validator_name" + FROM taler_directory.validations + WHERE + "h_alias" = $1 + ;` + // Execute Query + rows, err := db.Query(query, hAlias) + if err != nil { + return []Validation{}, err + } + defer rows.Close() + var validations = make([]Validation, 0) + // Iterate over first + for rows.Next() { + var v Validation + err = rows.Scan( + &v.CreatedAt, + &v.HAlias, + &v.Duration, + &v.TargetURI, + &v.Challenge, + &v.ChallengeSent, + &v.RequiresPayment, + &v.SolutionAttemptCount, + &v.LastSolutionTimeframeStart, + &v.OrderID, + &v.ValidatorName, + ) + if err != nil { + return validations, err + } + validations = append(validations, v) + } + return validations, nil +} + +// Get Hash-salted alias from database +func GetFirstValidationByHAliasFromDatabase(db *sql.DB, v *Validation, hAlias string) error { + query := `SELECT + "created_at", + "h_alias", + "duration", + "target_uri", + "challenge", + "challenge_sent", + "requires_payment", + "solution_attempt_count", + "last_solution_timeframe_start", + "order_id", + "validator_name" + FROM taler_directory.validations + WHERE + "h_alias"=$1 + ;` + // Execute Query + rows, err := db.Query(query, hAlias) + if err != nil { + return err + } + defer rows.Close() + // Iterate over first + if !rows.Next() { + return errors.New("Validation not found") + } + return rows.Scan( + &v.CreatedAt, + &v.HAlias, + &v.Duration, + &v.TargetURI, + &v.Challenge, + &v.ChallengeSent, + &v.RequiresPayment, + &v.SolutionAttemptCount, + &v.LastSolutionTimeframeStart, + &v.OrderID, + &v.ValidatorName, + ) +} + +// Get Hash-salted alias from database +func GetAllEntriesFromDatabase(db *sql.DB) ([]Entry, error) { + query := `SELECT + "hs_alias", + "created_at", + "target_uri", + "duration" + FROM taler_directory.entries + WHERE + "1" = "1" + ;` + // Execute Query + rows, err := db.Query(query) + if err != nil { + return []Entry{}, err + } + defer rows.Close() + var entries = make([]Entry, 0) + for rows.Next() { + var e Entry + err = rows.Scan( + &e.HsAlias, + &e.CreatedAt, + &e.TargetURI, + &e.Duration, + ) + if err != nil { + return entries, err + } + entries = append(entries, e) + } + return entries, nil +} + +// Get Hash-salted alias from database +func GetEntryByHsAliasFromDatabase(db *sql.DB, e *Entry, hsAlias string) error { + query := `SELECT + "hs_alias", + "created_at", + "target_uri", + "duration" + FROM taler_directory.entries + WHERE + "hs_alias"=$1 + ;` + // Execute Query + rows, err := db.Query(query, hsAlias) + if err != nil { + return err + } + defer rows.Close() + // Iterate over first + if !rows.Next() { + return errors.New("Entry not found") + } + return rows.Scan( + &e.HsAlias, + &e.CreatedAt, + &e.TargetURI, + &e.Duration, + ) +} + +// DeleteStaleValidationsFromDatabase purges stale validations +func DeleteStaleValidationsFromDatabase(db *sql.DB, validationExpiration time.Duration) (int64, error) { + var ctx context.Context + ctx, stop := context.WithCancel(context.Background()) + defer stop() + conn, err := db.Conn(ctx) + if err != nil { + return 0, err + } + defer conn.Close() + query := `DELETE + FROM taler_directory.validations + WHERE + "created_at" < $1 + ;` + // Execute Query + cutoffTime := time.Now().Add(-validationExpiration) + result, err := conn.ExecContext(ctx, query, cutoffTime.UnixMicro()) + if err != nil { + return 0, err + } + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + return rows, nil +} + +func ClearDatabase(db *sql.DB) error { + _, err := DeleteAllEntriesFromDatabase(db) + if err != nil { + return err + } + _, err = DeleteAllValidationsFromDatabase(db) + return err +} + +// DeleteValidationsByHAliasFromDatabase purges Validations +func DeleteValidationsByHAliasFromDatabase(db *sql.DB, hAlias string) (int64, error) { + var ctx context.Context + ctx, stop := context.WithCancel(context.Background()) + defer stop() + conn, err := db.Conn(ctx) + if err != nil { + return 0, err + } + defer conn.Close() + query := `DELETE + FROM taler_directory.validations + WHERE + "h_alias" = $1 + ;` + // Execute Query + result, err := conn.ExecContext(ctx, query, hAlias) + if err != nil { + return 0, err + } + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + return rows, nil +} + +// Purge Validations +func DeleteAllValidationsFromDatabase(db *sql.DB) (int64, error) { + var ctx context.Context + ctx, stop := context.WithCancel(context.Background()) + defer stop() + conn, err := db.Conn(ctx) + if err != nil { + return 0, err + } + defer conn.Close() + query := `DELETE + FROM taler_directory.validations + WHERE + 1 = 1 + ;` + // Execute Query + result, err := conn.ExecContext(ctx, query) + if err != nil { + return 0, err + } + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + return rows, nil +} + +// Purge Validations +func DeleteValidationFromDatabase(db *sql.DB, v *Validation) (int64, error) { + return DeleteValidationsByHAliasFromDatabase(db, v.HAlias) +} + +// Purge Entries +func DeleteAllEntriesFromDatabase(db *sql.DB) (int64, error) { + var ctx context.Context + ctx, stop := context.WithCancel(context.Background()) + defer stop() + conn, err := db.Conn(ctx) + if err != nil { + return 0, err + } + defer conn.Close() + query := `DELETE + FROM taler_directory.entries + WHERE + 1=1 + ;` + // Execute Query + result, err := conn.ExecContext(ctx, query) + if err != nil { + return 0, err + } + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + return rows, nil +} + +// Delete Entry +func DeleteEntryFromDatabase(db *sql.DB, e *Entry) (int64, error) { + var ctx context.Context + ctx, stop := context.WithCancel(context.Background()) + defer stop() + conn, err := db.Conn(ctx) + if err != nil { + return 0, err + } + defer conn.Close() + query := `DELETE + FROM taler_directory.entries + WHERE + "hs_alias" = $1 + ;` + // Execute Query + result, err := conn.ExecContext(ctx, query, e.HsAlias) + if err != nil { + return 0, err + } + rows, err := result.RowsAffected() + if err != nil { + return 0, err + } + return rows, nil +} + +// Update Entry in database +func UpdateEntryInDatabase(db *sql.DB, e *Entry) error { + query := `UPDATE taler_directory.entries + SET + "created_at" = $2, + "target_uri" = $3, + "duration" = $4 + WHERE "hs_alias" = $1;` + rows, err := db.Query(query, e.HsAlias, e.CreatedAt, e.TargetURI, e.Duration) + if err != nil { + return err + } + defer rows.Close() + return nil +} + +// Insert new Entry into database +func InsertEntryIntoDatabase(db *sql.DB, e *Entry) error { + e.CreatedAt = time.Now().UnixMicro() + query := `INSERT INTO taler_directory.entries + VALUES (DEFAULT, $1, $2, $3, $4);` + rows, err := db.Query(query, e.HsAlias, e.CreatedAt, e.TargetURI, e.Duration) + if err != nil { + return err + } + defer rows.Close() + return nil } diff --git a/pkg/taldir/taldir.go b/pkg/taldir/taldir.go @@ -40,15 +40,15 @@ import ( "strings" "time" + "database/sql" "github.com/gertd/go-pluralize" "github.com/gorilla/mux" "github.com/kataras/i18n" + _ "github.com/lib/pq" "github.com/schanzen/taler-go/pkg/merchant" tos "github.com/schanzen/taler-go/pkg/rest" talerutil "github.com/schanzen/taler-go/pkg/util" "github.com/skip2/go-qrcode" - "gorm.io/gorm" - "gorm.io/gorm/logger" "taler.net/taldir/internal/gana" "taler.net/taldir/internal/util" ) @@ -60,7 +60,7 @@ type Taldir struct { Router *mux.Router // The main DB handle - DB *gorm.DB + DB *sql.DB // Our configuration from the config.json Cfg TaldirConfig @@ -182,46 +182,6 @@ type RegisterMessage struct { Duration int64 `json:"duration"` } -// Validation is the object created when a registration for an entry is initiated. -// The Validation stores the identity key (sha256(identity)) the secret -// Validation reference. The Validation reference is sent to the identity -// depending on the out-of-band channel defined through the identity key type. -type Validation struct { - - // ORM - gorm.Model `json:"-"` - - // The hash (SHA512) of the alias - HAlias string `json:"h_alias"` - - // For how long should the registration last - Duration int64 `json:"duration"` - - // Target URI to associate with this alias - TargetURI string `json:"target_uri"` - - // The activation code sent to the client - Challenge string `json:"-"` - - // The challenge has been sent already - ChallengeSent bool `json:"-"` - - // true if this validation also requires payment - RequiresPayment bool `json:"-"` - - // How often was a solution for this validation tried - SolutionAttemptCount int - - // The beginning of the last solution timeframe - LastSolutionTimeframeStart time.Time - - // The order ID associated with this validation - OrderID string `json:"-"` - - // Name of the validator - ValidatorName string -} - // ErrorDetail is the detailed error payload returned from Taldir endpoints type ErrorDetail struct { @@ -295,7 +255,7 @@ func (t *Taldir) getSingleEntry(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) var entry Entry hsAlias := saltHAlias(vars["h_alias"], t.Salt) - var err = t.DB.First(&entry, "hs_alias = ?", hsAlias).Error + var err = GetEntryByHsAliasFromDatabase(t.DB, &entry, hsAlias) if err == nil { w.Header().Set("Content-Type", "application/json") resp, _ := json.Marshal(entry) @@ -328,8 +288,10 @@ func (t *Taldir) disseminateStart(e Entry) { // Disseminate all entries func (t *Taldir) disseminateEntries() error { - var entries []Entry - t.DB.Where("1 = 1").Find(&entries) + entries, err := GetAllEntriesFromDatabase(t.DB) + if nil != err { + return err + } for _, e := range entries { t.disseminateStart(e) } @@ -378,75 +340,89 @@ func (t *Taldir) validationRequest(w http.ResponseWriter, r *http.Request) { w.Write(resp) return } - err = t.DB.First(&validation, "h_alias = ?", vars["h_alias"]).Error + err = GetFirstValidationByHAliasFromDatabase(t.DB, &validation, vars["h_alias"]) + t.Logger.Logf(LogDebug, "Got validation %v", validation) if err != nil { w.WriteHeader(http.StatusNotFound) return } validation.SolutionAttemptCount++ - if validation.LastSolutionTimeframeStart.Add(t.SolutionTimeframe).After(time.Now()) { + if time.UnixMicro(validation.LastSolutionTimeframeStart + t.SolutionTimeframe.Microseconds()).After(time.Now()) { if validation.SolutionAttemptCount > t.SolutionAttemptsMax { w.WriteHeader(http.StatusTooManyRequests) return } } else { t.Logger.Logf(LogDebug, "New solution timeframe set.") - validation.LastSolutionTimeframeStart = time.Now() + validation.LastSolutionTimeframeStart = time.Now().UnixMicro() validation.SolutionAttemptCount = 1 } - t.DB.Save(&validation) + UpdateValidationInDatabase(t.DB, &validation) + t.Logger.Logf(LogDebug, "Generating solution from %s and %s", validation.TargetURI, validation.Challenge) expectedSolution := util.GenerateSolution(validation.TargetURI, validation.Challenge) + t.Logger.Logf(LogDebug, "Expected solution: `%s', given: `%s'\n", expectedSolution, confirm.Solution) if confirm.Solution != expectedSolution { w.WriteHeader(http.StatusForbidden) return } - err = t.DB.Delete(&validation).Error + _, err = DeleteValidationFromDatabase(t.DB, &validation) if err != nil { - t.Logger.Logf(LogError, "Error deleting validation") + t.Logger.Logf(LogError, "Error deleting validation: %v", err) w.WriteHeader(http.StatusInternalServerError) return } entry.HsAlias = saltHAlias(validation.HAlias, t.Salt) - entry.TargetURI = validation.TargetURI - tmpDuration := (entry.Duration.Microseconds() + validation.Duration) * 1000 - entry.Duration = time.Duration(tmpDuration) - err = t.DB.First(&entry, "hs_alias = ?", entry.HsAlias).Error + tmpDuration := (entry.Duration + validation.Duration) * 1000 + err = GetEntryByHsAliasFromDatabase(t.DB, &entry, entry.HsAlias) if err == nil { if validation.TargetURI == "" { - t.Logger.Logf(LogDebug, "Deleted entry for '%s´\n", entry.HsAlias) - err = t.DB.Delete(&entry).Error + _, err = DeleteEntryFromDatabase(t.DB, &entry) if err != nil { + t.Logger.Logf(LogError, "Error deleting entry: %v", err) w.WriteHeader(http.StatusInternalServerError) return } + t.Logger.Logf(LogDebug, "Deleted entry for '%s´\n", entry.HsAlias) t.disseminateStop(entry) } else { - t.DB.Save(&entry) + entry.TargetURI = validation.TargetURI + entry.Duration = tmpDuration + err = UpdateEntryInDatabase(t.DB, &entry) + t.Logger.Logf(LogDebug, "Updated entry in database to: %v", entry) + if err != nil { + t.Logger.Logf(LogError, "Error updating entry: %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } t.disseminateStart(entry) } } else { + t.Logger.Logf(LogError, "Entry does not yet exist: %v", err) if validation.TargetURI == "" { t.Logger.Logf(LogWarning, "Validated a deletion request but no entry found for `%s'\n", entry.HsAlias) } else { - err = t.DB.Create(&entry).Error + entry.TargetURI = validation.TargetURI + err = InsertEntryIntoDatabase(t.DB, &entry) if err != nil { + t.Logger.Logf(LogError, "Error inserting entry: %v", err) w.WriteHeader(http.StatusInternalServerError) return } + t.Logger.Logf(LogError, "Inserted entry: %v", entry) } } w.WriteHeader(http.StatusNoContent) } func (t *Taldir) isRateLimited(hAlias string) (bool, error) { - var validations []Validation - res := t.DB.Where("h_alias = ?", hAlias).Find(&validations) + validations, err := GetAllValidationsByHAliasFromDatabase(t.DB, hAlias) // NOTE: Check rate limit - if res.Error == nil { + if err == nil { // Limit re-initiation attempts to ValidationInitiationMax times // within the expiration timeframe of a validation. - return res.RowsAffected >= t.ValidationInitiationMax, nil + t.Logger.Logf(LogDebug, "Pending validations are %d", len(validations)) + return len(validations) >= int(t.ValidationInitiationMax), nil } return false, nil } @@ -457,6 +433,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { var errDetail ErrorDetail var validation Validation var entry Entry + // Check if this validation method is supported or not. validator, ok := t.Validators[vars["alias_type"]] if !ok { @@ -482,6 +459,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { w.Write(resp) return } + t.Logger.Logf(LogDebug, "Received registerRequest %v", req) if req.TargetURI != "" { err = t.isPMSValid(req.TargetURI) @@ -503,17 +481,20 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { validation.HAlias = hAlias validation.ValidatorName = validator.Name() hsAlias := saltHAlias(validation.HAlias, t.Salt) - err = t.DB.First(&entry, "hs_alias = ?", hsAlias).Error + err = GetEntryByHsAliasFromDatabase(t.DB, &entry, hsAlias) // Round to the nearest multiple of a month reqDuration := time.Duration(req.Duration * 1000) reqDuration = reqDuration.Round(monthDuration) if err == nil { + t.Logger.Logf(LogDebug, "Found entry in database %v matching %v", entry, hsAlias) // Check if this entry is to be modified or extended entryModified := (req.TargetURI != entry.TargetURI) - entryValidity := entry.CreatedAt.Add(entry.Duration) + entryValidity := time.UnixMicro(entry.CreatedAt + entry.Duration) // NOTE: The extension must be at least one month + t.Logger.Logf(LogDebug, "Entry to be modified: %t, requested (rounded) duration: %d", entryModified, reqDuration.Microseconds()) if (reqDuration.Microseconds() == 0) && !entryModified { // Nothing changed. Return validity + t.Logger.Logf(LogDebug, "Returning validity of entry") w.WriteHeader(http.StatusOK) w.Header().Set("Content-Type", "application/json") w.Write(fmt.Appendf(make([]byte, 0), "{\"valid_for\": %d}", time.Until(entryValidity).Microseconds())) @@ -534,19 +515,26 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { } jsonResp, _ := json.Marshal(rlResponse) w.Write(jsonResp) - t.DB.Delete(&validation) + DeleteValidationFromDatabase(t.DB, &validation) return } - err = t.DB.First(&validation, "h_alias = ? AND target_uri = ? AND duration = ?", - hAlias, req.TargetURI, reqDuration).Error + t.Logger.Logf(LogDebug, "Looking for validation with %v %v %v\n", hAlias, req.TargetURI, reqDuration.Microseconds()) + err = GetValidationFromDatabase(t.DB, &validation, hAlias, req.TargetURI, reqDuration) validationExists := (nil == err) + t.Logger.Logf(LogDebug, "Validation exists %v\n", validationExists) // FIXME: Always set new challenge? validation.Challenge = util.GenerateChallenge(t.ChallengeBytes) - if !validationExists { - validation.TargetURI = req.TargetURI - validation.SolutionAttemptCount = 0 - validation.LastSolutionTimeframeStart = time.Now() - validation.Duration = reqDuration.Microseconds() + validation.TargetURI = req.TargetURI + validation.SolutionAttemptCount = 0 + validation.LastSolutionTimeframeStart = time.Now().UnixMicro() + validation.Duration = reqDuration.Microseconds() + validation.CreatedAt = validation.LastSolutionTimeframeStart + t.Logger.Logf(LogDebug, "Storing new validation %v\n", validation) + err = InsertValidationIntoDatabase(t.DB, &validation) + if nil != err { + t.Logger.Logf(LogError, "Error inserting validation! %v", err) + w.WriteHeader(http.StatusInternalServerError) + return } sliceDuration := time.Duration(validation.Duration * 1000) @@ -583,14 +571,19 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { return } if len(payto) != 0 { - t.DB.Save(&validation) + err = UpdateValidationInDatabase(t.DB, &validation) + if nil != err { + t.Logger.Logf(LogError, "Error inserting validation! %v", err) + w.WriteHeader(http.StatusInternalServerError) + return + } w.WriteHeader(http.StatusPaymentRequired) w.Header().Set("Taler", payto) // FIXME no idea what to do with this. return } // In this case, this order was paid } - err = t.DB.Save(&validation).Error + err = UpdateValidationInDatabase(t.DB, &validation) if err != nil { t.Logger.Logf(LogError, "%s\n", err.Error()) w.WriteHeader(http.StatusInternalServerError) @@ -602,7 +595,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) { redirectionLink, err := validator.RegistrationStart(topic, link, message, req.Alias, validation.Challenge) if err != nil { t.Logger.Logf(LogError, "%s\n", err.Error()) - t.DB.Delete(&validation) + DeleteValidationFromDatabase(t.DB, &validation) w.WriteHeader(http.StatusInternalServerError) return } @@ -667,7 +660,7 @@ func (t *Taldir) validationPage(w http.ResponseWriter, r *http.Request) { var png []byte var validation Validation - err := t.DB.First(&validation, "h_alias = ?", vars["h_alias"]).Error + err := GetFirstValidationByHAliasFromDatabase(t.DB, &validation, vars["h_alias"]) w.Header().Set("Content-Type", "text/html; charset=utf-8") if err != nil { // This validation does not exist. @@ -740,8 +733,10 @@ func (t *Taldir) validationPage(w http.ResponseWriter, r *http.Request) { // ClearDatabase nukes the database (for tests) func (t *Taldir) ClearDatabase() { - t.DB.Where("1 = 1").Delete(&Entry{}) - t.DB.Where("1 = 1").Delete(&Validation{}) + err := ClearDatabase(t.DB) + if err != nil { + t.Logger.Logf(LogWarning, "Error clearing database: %v", err) + } } func (t *Taldir) termsResponse(w http.ResponseWriter, r *http.Request) { @@ -842,7 +837,7 @@ func (t *Taldir) typeLookupResultPage(w http.ResponseWriter, r *http.Request) { hAliasBin := HashAlias(val.Name(), r.URL.Query().Get("alias")) hAlias := util.Base32CrockfordEncode(hAliasBin[:]) hsAlias := saltHAlias(hAlias, t.Salt) - err = t.DB.First(&entry, "hs_alias = ?", hsAlias).Error + err := GetEntryByHsAliasFromDatabase(t.DB, &entry, hsAlias) if err != nil { t.Logger.Logf(LogError, "`%s` not found.\n", hAlias) } else { @@ -968,6 +963,7 @@ func (t *Taldir) Initialize(cfg TaldirConfig) { t.Cfg = cfg t.Logger = TaldirLogger{ InternalLogger: log.New(os.Stdout, "taler-directory:", log.LstdFlags), + logLevel: cfg.Loglevel, } // FIXME localedir i18n, err := i18n.New(i18n.Glob("./locales/*/*", i18n.LoaderConfig{ @@ -1046,23 +1042,14 @@ func (t *Taldir) Initialize(cfg TaldirConfig) { } t.MonthlyFee = cfg.Ini.Section("taldir").Key("monthly_fee").MustString("KUDOS:0") - _db, err := gorm.Open(cfg.Db, &gorm.Config{ - Logger: logger.Default.LogMode(logger.Silent), - }) - if err != nil { - panic(err) - } - t.DB = _db - if err := t.DB.AutoMigrate(&Entry{}); err != nil { - panic(err) - } - if err := t.DB.AutoMigrate(&Validation{}); err != nil { - panic(err) - } + t.DB = cfg.Db if cfg.Ini.Section("taldir").Key("purge_mappings_on_startup_dangerous").MustBool(false) { t.Logger.Logf(LogWarning, "DANGER Purging mappings!") - tx := t.DB.Where("1 = 1").Delete(&Entry{}) - t.Logger.Logf(LogDebug, "Deleted %d entries.\n", tx.RowsAffected) + num, err := DeleteAllEntriesFromDatabase(t.DB) + if err != nil { + t.Logger.Logf(LogDebug, "Error purging entries: `%v'.\n", err) + } + t.Logger.Logf(LogDebug, "Deleted %d entries.\n", num) } // Clean up validations validationExpStr := cfg.Ini.Section("taldir").Key("validation_expiration").MustString("24h") @@ -1073,8 +1060,11 @@ func (t *Taldir) Initialize(cfg TaldirConfig) { } go func() { for { - tx := t.DB.Where("created_at < ?", time.Now().Add(-validationExp)).Delete(&Validation{}) - t.Logger.Logf(LogInfo, "Cleaned up %d stale validations.\n", tx.RowsAffected) + num, err := DeleteStaleValidationsFromDatabase(t.DB, validationExp) + if err != nil { + t.Logger.Logf(LogDebug, "Error purging stale validations: `%v'.\n", err) + } + t.Logger.Logf(LogInfo, "Cleaned up %d stale validations.\n", num) time.Sleep(validationExp) } }()