From 99f88d6adf1d5d6169989e98b17ec32d88857c9f Mon Sep 17 00:00:00 2001 From: jimmy Date: Sun, 18 Dec 2022 13:42:21 +1300 Subject: [PATCH] Add go backend --- api/controllers/session.go | 37 ++++ api/controllers/user.go | 188 ++++++++++++++++++ api/go.mod | 38 ++++ api/go.sum | 272 ++++++++++++++++++++++++++ api/main.go | 27 +++ api/middleware/basicauth.go | 33 ++++ api/middleware/jwt.go | 58 ++++++ api/models/db.go | 66 +++++++ api/models/migrations.go | 9 + api/models/user.go | 37 ++++ api/test/controllers/user_test.go | 199 +++++++++++++++++++ api/test/middleware/basicauth_test.go | 18 ++ api/test/models/db_test.go | 72 +++++++ api/test/models/user_test.go | 76 +++++++ api/test/test.go | 49 +++++ 15 files changed, 1179 insertions(+) create mode 100644 api/controllers/session.go create mode 100644 api/controllers/user.go create mode 100644 api/go.mod create mode 100644 api/go.sum create mode 100644 api/main.go create mode 100644 api/middleware/basicauth.go create mode 100644 api/middleware/jwt.go create mode 100644 api/models/db.go create mode 100644 api/models/migrations.go create mode 100644 api/models/user.go create mode 100644 api/test/controllers/user_test.go create mode 100644 api/test/middleware/basicauth_test.go create mode 100644 api/test/models/db_test.go create mode 100644 api/test/models/user_test.go create mode 100644 api/test/test.go diff --git a/api/controllers/session.go b/api/controllers/session.go new file mode 100644 index 0000000..28ae9ec --- /dev/null +++ b/api/controllers/session.go @@ -0,0 +1,37 @@ +package controllers + +import ( + "magmise/models" + "net/http" + "os" + + "github.com/wader/gormstore/v2" +) + +var ( + // key must be 16, 24 or 32 bytes long (AES-128, AES-192 or AES-256) + key = []byte("super-secret-key") + store = gormstore.NewOptions(models.DB(), gormstore.Options{}, []byte(os.Getenv("HASHKEY")), []byte("BLOCKKEY")) +) + +func Login(w http.ResponseWriter, r *http.Request) { + session, err := store.Get(r, "session") + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + // Authentication goes here + // ... + // Set user as authenticated + session.Values["user"] = "test" + session.Values["authenticated"] = true + session.Save(r, w) +} + +func Logout(w http.ResponseWriter, r *http.Request) { + session, _ := store.Get(r, "session") + + // Revoke users authentication + session.Values["authenticated"] = false + session.Save(r, w) +} diff --git a/api/controllers/user.go b/api/controllers/user.go new file mode 100644 index 0000000..2f1872b --- /dev/null +++ b/api/controllers/user.go @@ -0,0 +1,188 @@ +package controllers + +import ( + "encoding/json" + "magmise/models" + "net/http" + + "github.com/go-chi/chi/v5" +) + +type User struct { +} + +// "/user/:username" +func (u User) Get(w http.ResponseWriter, r *http.Request) { + username := chi.URLParam(r, "username") + if username == "" { + http.Error(w, "Missing user id", http.StatusNotFound) + return + } + user := models.User{Username: username} + if user.Read() != nil { + http.Error(w, "Not found", http.StatusNotFound) + return + } + if err := json.NewEncoder(w).Encode(user); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Add("Content-Type", "application/json") +} + +// "/user" +func (User) Create(w http.ResponseWriter, r *http.Request) { + var user models.User + if err := json.NewDecoder(r.Body).Decode(&user); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + } + + if user.Create() != nil { + http.Error(w, "failed to create user", http.StatusInternalServerError) + return + } +} + +// // /users/application/:status/ +// func (t User) Getusers(w http.ResponseWriter, r *http.Request) { +// status := vestigo.Param(r, "status") +// offset, err := strconv.ParseInt(vestigo.Param(r, "offset"), 0, 32) +// if err != nil { +// offset = 0 +// } + +// limit, err := strconv.ParseInt(vestigo.Param(r, "limit"), 0, 32) +// if err != nil { +// limit = 100 +// } + +// var user models.User +// switch status { +// case "open", "closed": +// user = models.User{Status: status} +// case "all": +// user = models.User{} +// default: +// http.Error(w, "Missing status", http.StatusNotFound) +// return +// } + +// users, err := user.ReadAll(int(offset), int(limit)) +// if err != nil { +// http.Error(w, "Not found", http.StatusNotFound) +// return +// } +// if err := json.NewEncoder(w).Encode(users); err != nil { +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } +// w.Header().Add("Content-Type", "application/json") +// } + +// // "/user/application/:userid" +// func (t User) Updateuser(w http.ResponseWriter, r *http.Request) { +// userid := vestigo.Param(r, "userid") +// if userid == "" { +// http.Error(w, "Missing user or discord id", http.StatusNotFound) +// return +// } +// log.Println(r.Body) +// var user models.User +// if err := json.NewDecoder(r.Body).Decode(&user); err != nil { +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } +// log.Println(user) +// if err := user.Update(); err != nil { +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } + +// } + +// // "/user/application/:userid" +// func (t User) Deleteuser(w http.ResponseWriter, r *http.Request) { +// userid := vestigo.Param(r, "userid") +// if userid == "" { +// http.Error(w, "Missing user or discord id", http.StatusNotFound) +// } +// user := models.User{userId: userid} + +// if user.Delete() != nil { +// http.Error(w, "failed to delete user", http.StatusInternalServerError) +// return +// } + +// } + +// // /user/:discordid/users/application/:status/ +// func (t User) GetUserusers(w http.ResponseWriter, r *http.Request) { +// discordid, _ := strconv.ParseInt(vestigo.Param(r, "discordid"), 0, 64) +// status := vestigo.Param(r, "status") +// offset, err := strconv.ParseInt(vestigo.Param(r, "offset"), 0, 32) +// if err != nil { +// offset = 0 +// } + +// limit, err := strconv.ParseInt(vestigo.Param(r, "limit"), 0, 32) +// if err != nil { +// limit = 100 +// } + +// log.Println(discordid, status) +// var user models.User +// switch status { +// case "open", "closed": +// user = models.User{DiscordId: discordid, Status: status} +// case "all": +// user = models.User{DiscordId: discordid} +// default: +// http.Error(w, "Missing status", http.StatusNotFound) +// return +// } + +// users, err := user.ReadAll(int(offset), int(limit)) +// if err != nil { +// http.Error(w, "Not found", http.StatusNotFound) +// return +// } +// if err := json.NewEncoder(w).Encode(users); err != nil { +// http.Error(w, err.Error(), http.StatusInternalServerError) +// return +// } +// w.Header().Add("Content-Type", "application/json") +// } + +// // "/answer/application/:userid/:modalid" + +// func (t User) AddAnswer(w http.ResponseWriter, r *http.Request) { +// userid := vestigo.Param(r, "userid") +// modalid := vestigo.Param(r, "modalid") +// if userid == "" { +// http.Error(w, "Missing user or discord id", http.StatusNotFound) +// return +// } +// b, err := ioutil.ReadAll(r.Body) +// if err != nil { +// http.Error(w, "Missing body", http.StatusNotFound) +// } +// user := models.User{userId: userid} +// if user.Read() != nil { +// http.Error(w, "Failed to read user", http.StatusInternalServerError) +// } +// for _, answer := range user.Answers { +// if answer.ModalId == modalid { +// answer.Response = string(b) +// if answer.Update() != nil { +// http.Error(w, "Failed to update answer", http.StatusInternalServerError) +// return +// } +// return +// } +// } +// answer := models.Answer{ModalId: modalid, Response: string(b)} +// user.Answers = append(user.Answers, answer) +// if user.Update() != nil { +// http.Error(w, "Failed to update user", http.StatusInternalServerError) +// } +// } diff --git a/api/go.mod b/api/go.mod new file mode 100644 index 0000000..d8d4194 --- /dev/null +++ b/api/go.mod @@ -0,0 +1,38 @@ +module magmise + +go 1.19 + +require ( + github.com/docker/docker v20.10.18+incompatible + github.com/go-chi/chi/v5 v5.0.7 + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/gorilla/csrf v1.7.1 + github.com/gorilla/websocket v1.5.0 + github.com/wader/gormstore/v2 v2.0.1 + golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 + gorm.io/driver/sqlite v1.3.6 + gorm.io/gorm v1.23.8 +) + +require ( + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/gorilla/securecookie v1.1.1 // indirect + github.com/gorilla/sessions v1.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-sqlite3 v1.14.12 // indirect + github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/sirupsen/logrus v1.7.0 // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect + golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect + gotest.tools/v3 v3.3.0 // indirect +) diff --git a/api/go.sum b/api/go.sum new file mode 100644 index 0000000..ac752d2 --- /dev/null +++ b/api/go.sum @@ -0,0 +1,272 @@ +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v20.10.18+incompatible h1:SN84VYXTBNGn92T/QwIRPlum9zfemfitN7pbsp26WSc= +github.com/docker/docker v20.10.18+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8= +github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/gorilla/csrf v1.7.1 h1:Ir3o2c1/Uzj6FBxMlAUB6SivgVMy1ONXwYgXn+/aHPE= +github.com/gorilla/csrf v1.7.1/go.mod h1:+a/4tCmqhG6/w4oafeAZ9pEa3/NZOWYVbD9fV0FwIQA= +github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= +github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +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/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= +github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.9.1 h1:MJc2s0MFS8C3ok1wQTdQxWuXQcB6+HwAm5x1CzW7mf0= +github.com/jackc/pgtype v1.9.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.14.1 h1:71oo1KAGI6mXhLiTMn6iDFcp3e7+zon/capWjl2OEFU= +github.com/jackc/pgx/v4 v4.14.1/go.mod h1:RgDuE4Z34o7XE92RpLsvFiOEfrAUT0Xt2KxvX73W06M= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +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.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= +github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0= +github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae h1:O4SWKdcHVCvYqyDV+9CJA1fcDN2L11Bule0iFy3YlAI= +github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/wader/gormstore/v2 v2.0.1 h1:X0yd9pRoottggrz1pO+TcMQo6GDPEUXKVHx6ayspNLk= +github.com/wader/gormstore/v2 v2.0.1/go.mod h1:ZHLcDNchj+BxKJR9iCnVd8MV5nukl3yBSZxjXqqAT3w= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90 h1:Y/gsMcFOcR+6S6f3YeMKl5g+dZMEWqcz5Czj/GWYbkM= +golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ= +golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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/mysql v1.3.2 h1:QJryWiqQ91EvZ0jZL48NOpdlPdMjdip1hQ8bTgo4H7I= +gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= +gorm.io/driver/postgres v1.3.1 h1:Pyv+gg1Gq1IgsLYytj/S2k7ebII3CzEdpqQkPOdH24g= +gorm.io/driver/postgres v1.3.1/go.mod h1:WwvWOuR9unCLpGWCL6Y3JOeBWvbKi6JLhayiVclSZZU= +gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw= +gorm.io/driver/sqlite v1.3.6 h1:Fi8xNYCUplOqWiPa3/GuCeowRNBRGTf62DEmhMDHeQQ= +gorm.io/driver/sqlite v1.3.6/go.mod h1:Sg1/pvnKtbQ7jLXxfZa+jSHvoX8hoZA8cn4xllOMTgE= +gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE= +gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.3.0 h1:MfDY1b1/0xN1CyMlQDac0ziEy9zJQd9CXBRRDHw2jJo= +gotest.tools/v3 v3.3.0/go.mod h1:Mcr9QNxkg0uMvy/YElmo4SpXgJKWgQvYrT7Kw5RzJ1A= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/api/main.go b/api/main.go new file mode 100644 index 0000000..85c45d5 --- /dev/null +++ b/api/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "magmise/controllers" + "magmise/models" + "net/http" + "os" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/gorilla/csrf" +) + +func main() { + models.Store() + models.Init() + var user controllers.User + r := chi.NewRouter() + r.Use(middleware.Logger) + r.Handle("/*", http.FileServer(http.Dir("/api/www"))) + r.Get("/api/user/{username}", user.Get) + r.Post("/api/user/{username}", user.Create) + r.Post("/api/login", controllers.Login) + r.Post("/api/logout", controllers.Logout) + CSRF := csrf.Protect([]byte(os.Getenv("CSRFKEY"))) + http.ListenAndServe(":8080", CSRF(r)) +} diff --git a/api/middleware/basicauth.go b/api/middleware/basicauth.go new file mode 100644 index 0000000..a198623 --- /dev/null +++ b/api/middleware/basicauth.go @@ -0,0 +1,33 @@ +package middleware + +import ( + "net/http" + "os" + + "golang.org/x/crypto/bcrypt" +) + +func BasicAuth(f http.HandlerFunc) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + username, password, ok := r.BasicAuth() + hash, err := HashPassword(os.Getenv("PASSWORD")) + if err == nil && ok { + if username == os.Getenv("USERNAME") && CheckPasswordHash(password, hash) { + f(w, r) + return + } + } + w.Header().Set("WWW-Authenticate", `Basic realm="restricted", charset="UTF-8"`) + http.Error(w, "Unauthorized", http.StatusUnauthorized) + }) +} + +func HashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14) + return string(bytes), err +} + +func CheckPasswordHash(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} diff --git a/api/middleware/jwt.go b/api/middleware/jwt.go new file mode 100644 index 0000000..7c9d24b --- /dev/null +++ b/api/middleware/jwt.go @@ -0,0 +1,58 @@ +package middleware + +import ( + "fmt" + "log" + "net/http" + "os" + "strings" + + "github.com/golang-jwt/jwt" +) + +var secret = []byte(os.Getenv("SECRET")) + +func Auth(f http.HandlerFunc) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + // get token + authheader, ok := r.Header["Authorization"] + if !ok { + http.Error(w, "Missing token", http.StatusBadRequest) + return + } + tokenString := strings.Split(authheader[0], " ")[1] + + //parse token + token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return secret, nil + }) + if err != nil { + http.Error(w, "Bad token", http.StatusBadRequest) + return + } + + // check if path is allowed + if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid && r.URL.Path == claims["path"] { + log.Println(r.URL.Path) + f(w, r) + } else { + log.Println(err) + http.Error(w, "Forbidden", http.StatusUnauthorized) + } + // it's all good + } +} + +func GenerateToken(path string) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ + "path": path, + }) + tokenString, err := token.SignedString(secret) + if err != nil { + panic(err) + } + fmt.Println(tokenString) +} diff --git a/api/models/db.go b/api/models/db.go new file mode 100644 index 0000000..2ec6446 --- /dev/null +++ b/api/models/db.go @@ -0,0 +1,66 @@ +package models + +import ( + "errors" + "log" + "os" + + "github.com/wader/gormstore/v2" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +var db *gorm.DB +var store *gormstore.Store + +func Init() { + if _, err := os.Stat("/data/database.db"); errors.Is(err, os.ErrNotExist) { + log.Printf("Creating sqlite database") + _, err := os.Create("/data/database.db") + if err != nil { + log.Fatal(err) + } + } + var err error + + db, err = gorm.Open(sqlite.Open("/data/database.db"), &gorm.Config{}) + if err != nil { + log.Println("Failed to connect to db") + } + migrate() +} + +func DB() *gorm.DB { + if db == nil { + Init() + } + return db +} + +func Store() *gormstore.Store { + if store == nil { + store = gormstore.NewOptions(DB(), gormstore.Options{}, []byte(os.Getenv("HASHKEY")), []byte("BLOCKKEY")) + } + return store +} + +func Create(d interface{}) error { + return DB().Create(d).Error +} + +func Read(d interface{}) error { + return DB().First(d, d).Error +} + +func ReadAll(d interface{}, offset, limit int) error { + return DB().Offset(offset).Limit(limit).Find(d).Error +} + +func Update(d interface{}, where string, f interface{}) error { + return DB().Model(d).Where(where+" = ?", f).Updates(d).Error +} + +func Delete(d interface{}) error { + + return DB().Delete(d).Error +} diff --git a/api/models/migrations.go b/api/models/migrations.go new file mode 100644 index 0000000..effb178 --- /dev/null +++ b/api/models/migrations.go @@ -0,0 +1,9 @@ +package models + +import "fmt" + +func migrate() { + DB().AutoMigrate(&User{}) + + fmt.Println("Migrated") +} diff --git a/api/models/user.go b/api/models/user.go new file mode 100644 index 0000000..c8d2e71 --- /dev/null +++ b/api/models/user.go @@ -0,0 +1,37 @@ +package models + +import ( + "gorm.io/gorm" +) + +type User struct { + gorm.Model + Username string `json:"username" gorm:"uniqueIndex, column: username"` + Password string +} + +func (u User) Create() error { + return DB().Create(&u).Error +} + +func (u *User) Read() error { + return Read(u) +} + +func (u User) ReadAll(offset, limit int) ([]User, error) { + var users []User + err := ReadAll(&users, offset, limit) + return users, err +} + +func (u *User) Update() error { + return Update(u, "username", u.Username) +} + +func (u User) Delete() error { + err := u.Read() + if err == nil { + err = Delete(&u) + } + return err +} diff --git a/api/test/controllers/user_test.go b/api/test/controllers/user_test.go new file mode 100644 index 0000000..073cd97 --- /dev/null +++ b/api/test/controllers/user_test.go @@ -0,0 +1,199 @@ +package controllers_test + +import ( + "encoding/json" + "magmise/controllers" + "magmise/models" + "magmise/test" + "net/http" + "testing" +) + +// "/user/{username}" +func TestGetUser(t *testing.T) { + setup := test.Setup(t, "get user") + defer setup() + + var user controllers.User + w := test.Request(t, user.Get, "GET", "/user/{username}", `{}`, map[string]string{"username": "fred"}) + + var usermodel models.User + if json.Unmarshal(w.Body.Bytes(), &usermodel) != nil { + t.Fail() + } + if usermodel.Username != "fred" { + t.Fatal(usermodel) + } +} + +// "/user" +func TestCreateUser(t *testing.T) { + setup := test.Setup(t, "create user") + defer setup() + var user controllers.User + w := test.Request(t, user.Create, "POST", "/user/{username}", `{"username": "fred", "password": "a"}`, nil) + if w.Code != http.StatusOK { + t.Fatal(w.Code, w.Result().Status) + } +} + +// // "/user/application/:userid/:discordid" +// func TestCreateuser(t *testing.T) { +// setup := test.Setup(t) +// defer setup() + +// var user controllers.User +// handler := http.HandlerFunc(user.Createuser) +// r, err := http.NewRequest("POST", "/user/application/:userid/:discordid", nil) +// if err != nil { +// t.Fatal(err) +// } +// q := r.URL.Query() +// q.Add(":userid", "6") +// q.Add(":discordid", "6") +// r.URL.RawQuery = q.Encode() +// w := httptest.NewRecorder() +// handler(w, r) +// if w.Code != 200 { +// t.Fail() +// } +// } + +// // PATCH "/user/application/:userid" +// func TestUpdateuser(t *testing.T) { +// setup := test.Setup(t) +// defer setup() +// t.Log("Test Updateuser") +// usermodel := models.User{userId: "1", Status: "closed"} +// userjson, err := json.Marshal(usermodel) +// if err != nil { +// t.Fatal(err) +// } +// var user controllers.Userco +// handler := http.HandlerFunc(user.Updateuser) +// r, err := http.NewRequest("PATCH", "/user/application/:userid", bytes.NewBuffer(userjson)) +// r.Header.Set("Content-Type", "application/json; charset=UTF-8") +// if err != nil { +// t.Fatal(err) +// } +// q := r.URL.Query() +// q.Add(":userid", "1") +// r.URL.RawQuery = q.Encode() +// w := httptest.NewRecorder() +// handler(w, r) +// if w.Code != 200 { +// t.Log(w.Body) +// t.Fatal(w.Code) +// } +// usermodel.Read() +// if usermodel.Status != "closed" { + +// t.Fail() +// } +// } + +// // "/user/application/:userid" +// func TestDeleteuser(t *testing.T) { +// setup := test.Setup(t) +// defer setup() + +// var user controllers.User +// handler := http.HandlerFunc(user.Deleteuser) +// r, err := http.NewRequest("POST", "/user/application/:userid", nil) +// if err != nil { +// t.Fatal(err) +// } +// q := r.URL.Query() +// q.Add(":userid", "1") + +// r.URL.RawQuery = q.Encode() +// w := httptest.NewRecorder() +// handler(w, r) +// if w.Code != 200 { +// t.Fail() +// } +// } + +// // /user/:discordid/users/application/:status/ +// func TestGetUserusers(t *testing.T) { +// setup := test.Setup(t) +// defer setup() + +// handler := http.HandlerFunc(controllers.User{}.GetUserusers) +// r, err := http.NewRequest("GET", "/user/:discordid/users/application/:status/", nil) +// if err != nil { +// t.Fatal(err) +// } +// q := r.URL.Query() +// q.Add(":discordid", "1") +// q.Add(":status", "all") +// q.Add(":limit", "100") +// r.URL.RawQuery = q.Encode() +// w := httptest.NewRecorder() +// handler(w, r) +// if w.Code != 200 { +// t.Fail() +// } +// var users []models.User +// if json.Unmarshal(w.Body.Bytes(), &users) != nil { +// t.Fatal("Failed unmarshal") +// } + +// for _, user := range users { +// t.Log(user) +// t.Log("\n") +// } + +// } + +// // /users/application/:status/ +// func TestGetusers(t *testing.T) { +// setup := test.Setup(t) +// defer setup() + +// handler := http.HandlerFunc(controllers.User{}.GetUserusers) +// r, err := http.NewRequest("GET", "/users/application/:status/", nil) +// if err != nil { +// t.Fatal(err) +// } +// q := r.URL.Query() +// q.Add(":status", "all") +// q.Add(":limit", "100") +// r.URL.RawQuery = q.Encode() +// w := httptest.NewRecorder() +// handler(w, r) +// if w.Code != 200 { +// t.Fail() +// } +// var users []models.User +// if json.Unmarshal(w.Body.Bytes(), &users) != nil { +// t.Fatal("Failed unmarshal") +// } + +// for _, user := range users { +// t.Log(user) +// t.Log("\n") +// } + +// } + +// // "/answer/application/:userid/" +// func TestAddAnswer(t *testing.T) { +// setup := test.Setup(t) +// defer setup() + +// var user controllers.User +// handler := http.HandlerFunc(user.AddAnswer) +// r, err := http.NewRequest("POST", "/answer/application/:userid/", bytes.NewBufferString("Hello world")) +// if err != nil { +// t.Fatal(err) +// } +// q := r.URL.Query() +// q.Add(":userid", "1") +// r.URL.RawQuery = q.Encode() +// w := httptest.NewRecorder() +// handler(w, r) +// if w.Code != 200 { +// t.Fatal("Failed to add answer", w.Code) +// } +// } diff --git a/api/test/middleware/basicauth_test.go b/api/test/middleware/basicauth_test.go new file mode 100644 index 0000000..269810d --- /dev/null +++ b/api/test/middleware/basicauth_test.go @@ -0,0 +1,18 @@ +package middleware_test + +import ( + "magmise/middleware" + "testing" +) + +func TestCheckPasswordHash(t *testing.T) { + password := "password" + hash, _ := middleware.HashPassword(password) + if middleware.CheckPasswordHash(password, hash) == false { + t.Fail() + } +} + +func TestBasicAuth(t *testing.T) { + +} diff --git a/api/test/models/db_test.go b/api/test/models/db_test.go new file mode 100644 index 0000000..6a8aa3b --- /dev/null +++ b/api/test/models/db_test.go @@ -0,0 +1,72 @@ +package models_test + +import ( + "magmise/models" + "testing" +) + +type Test struct { + Name string +} + +func TestDB(t *testing.T) { + // Create table + models.DB().AutoMigrate(&models.User{}) + + user := models.User{Username: "test"} + + //Create + if err := models.Create(&user); err != nil { + t.Fatal(err) + } + + t.Log("\033[33mShould error with unique contraint\033[0m") + if err := models.Create(&user); err == nil { + t.Fatal(err) + } + + // Read + if err := models.Read(&user); err != nil { + t.Fatal(err) + } + + // Create more rows + user = models.User{Username: "test2"} + models.Create(&user) + user = models.User{Username: "test3"} + models.Create(&user) + user = models.User{Username: "test4"} + models.Create(&user) + + // Get second and third row + var users []models.User + if err := models.ReadAll(&users, 1, 2); err != nil { + t.Fatal(err) + } + if users[0].Username != "test2" { + t.Fatal("Offset failed\n", users) + } + + if len(users) != 2 { + t.Fatal("Limit failed\n", users) + } + + // Update + user.Password = "b" + if err := models.Update(&user, "username", user.Username); err != nil { + t.Fatal(err) + } + // Check Update + if err := models.Read(&user); err != nil { + t.Fatal(err) + } + if user.Password != "b" { + t.Fatal("failed update") + } + + // Delete + if err := models.Delete(&user); err != nil { + t.Fatal(err) + } + models.DB().Migrator().DropTable(&models.User{}) +} diff --git a/api/test/models/user_test.go b/api/test/models/user_test.go new file mode 100644 index 0000000..42c3a91 --- /dev/null +++ b/api/test/models/user_test.go @@ -0,0 +1,76 @@ +package models_test + +import ( + "magmise/models" + "magmise/test" + "testing" +) + +func TestUserCreate(t *testing.T) { + cleanup := test.Setup(t, "user create") + defer cleanup() + user := models.User{Username: "Bob", Password: "a"} + if user.Create() != nil { + t.Fail() + } +} + +func TestUserRead(t *testing.T) { + cleanup := test.Setup(t, "user read") + defer cleanup() + user := models.User{Username: "fred"} + if user.Read() != nil { + t.Fatal("Faied to read application user") + } + + if user.Password != "a" { + t.Log(user) + } +} + +func TestUserReadAll(t *testing.T) { + cleanup := test.Setup(t, "user readall") + defer cleanup() + user := models.User{} + users, err := user.ReadAll(0, 10) + if err != nil { + t.Fatal(err) + } + + if users[0].Username != "fred" { + t.Log(users) + t.Fail() + } + +} + +func TestUserUpdate(t *testing.T) { + cleanup := test.Setup(t, "user update") + defer cleanup() + user := models.User{Username: "fred"} + user.Read() + user.Password = "b" + if user.Update() != nil { + t.Fail() + } + user.Read() + if user.Password != "b" { + t.Fail() + } +} + +func TestUserDelete(t *testing.T) { + cleanup := test.Setup(t, "user delete") + defer cleanup() + user := models.User{Username: "fred"} + + if err := user.Delete(); err != nil { + t.Fatal(err) + } + t.Log() + + t.Log("\033[33m\033[1mShould error with \033[95mrecord not found\033[0m") + if user.Read() == nil { + t.Fatal(user) + } +} diff --git a/api/test/test.go b/api/test/test.go new file mode 100644 index 0000000..07c54cb --- /dev/null +++ b/api/test/test.go @@ -0,0 +1,49 @@ +package test + +import ( + "bytes" + "context" + "magmise/models" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-chi/chi/v5" +) + +func Setup(t *testing.T, name string) func() { + t.Log("Setup " + name) + models.Init() + //models.DB().Exec("DROP TABLE ticketapplication_answers") + models.DB().Migrator().DropTable(models.User{}) + models.DB().AutoMigrate(models.User{}) + + models.User{Username: "fred", Password: "a"}.Create() + return func() { + t.Log("Cleanup " + name) + models.DB().Migrator().DropTable(models.User{}) + + } +} + +func Request(t *testing.T, model http.HandlerFunc, method, path, _json string, urlparams map[string]string) *httptest.ResponseRecorder { + handler := http.HandlerFunc(model) + r, err := http.NewRequest("GET", "/user/{username}", bytes.NewBufferString(_json)) + if err != nil { + t.Fatal(err) + } + rctx := chi.NewRouteContext() + for key, value := range urlparams { + rctx.URLParams.Add(key, value) + } + + r = r.WithContext(context.WithValue(r.Context(), chi.RouteCtxKey, rctx)) + + w := httptest.NewRecorder() + handler(w, r) + if w.Code != 200 { + t.Fail() + } + + return w +}