diff --git a/cmd/budgeteer/main.go b/cmd/budgeteer/main.go index a6d6f58..cb57d20 100644 --- a/cmd/budgeteer/main.go +++ b/cmd/budgeteer/main.go @@ -16,19 +16,16 @@ func main() { log.Fatalf("Could not load config: %v", err) } - bv := &bcrypt.Verifier{} - - q, err := postgres.Connect(cfg.DatabaseHost, cfg.DatabaseUser, cfg.DatabasePassword, cfg.DatabaseName) + q, err := postgres.Connect("pgx", cfg.DatabaseConnection) if err != nil { log.Fatalf("Failed connecting to DB: %v", err) } - tv := &jwt.TokenVerifier{} - h := &http.Handler{ Service: q, - TokenVerifier: tv, - CredentialsVerifier: bv, + TokenVerifier: &jwt.TokenVerifier{}, + CredentialsVerifier: &bcrypt.Verifier{}, } + h.Serve() } diff --git a/config/config.go b/config/config.go index 200be74..0f17ee5 100644 --- a/config/config.go +++ b/config/config.go @@ -6,23 +6,13 @@ import ( // Config contains all needed configurations type Config struct { - DatabaseUser string - DatabaseHost string - DatabasePassword string - DatabaseName string + DatabaseConnection string } // LoadConfig from path func LoadConfig() (*Config, error) { configuration := Config{ - DatabaseUser: os.Getenv("BUDGETEER_DB_USER"), - DatabaseHost: os.Getenv("BUDGETEER_DB_HOST"), - DatabasePassword: os.Getenv("BUDGETEER_DB_PASS"), - DatabaseName: os.Getenv("BUDGETEER_DB_NAME"), - } - - if configuration.DatabaseName == "" { - configuration.DatabaseName = "budgeteer" + DatabaseConnection: os.Getenv("BUDGETEER_DB"), } return &configuration, nil diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index bb051d0..26843b0 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -18,15 +18,14 @@ services: - ~/.go:/go - ~/.cache:/.cache environment: - BUDGETEER_DB_NAME: budgeteer - BUDGETEER_DB_USER: budgeteer - BUDGETEER_DB_PASS: budgeteer - BUDGETEER_DB_HOST: db:5432 + BUDGETEER_DB: postgres://budgeteer:budgeteer@db:5432/budgeteer depends_on: - db db: image: postgres:14 + ports: + - 5432:5432 volumes: - db:/var/lib/postgresql/data environment: diff --git a/go.mod b/go.mod index 4484579..240f1d9 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,8 @@ require ( golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa ) +require github.com/DATA-DOG/go-txdb v0.1.5 // indirect + require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.13.0 // indirect diff --git a/go.sum b/go.sum index a630b84..03f0d45 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX 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/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI= +github.com/DATA-DOG/go-txdb v0.1.5 h1:kKzz+LYk9qw1+fMyo8/9yDQiNXrJ2HbfX/TY61HkkB4= +github.com/DATA-DOG/go-txdb v0.1.5/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= diff --git a/http/account.go b/http/account.go index 93867a7..03296c2 100644 --- a/http/account.go +++ b/http/account.go @@ -28,8 +28,10 @@ func (h *Handler) transactionsForAccount(c *gin.Context) { return } - c.JSON(http.StatusOK, struct { - Account postgres.Account - Transactions []postgres.GetTransactionsForAccountRow - }{account, transactions}) + c.JSON(http.StatusOK, TransactionsResponse{account, transactions}) +} + +type TransactionsResponse struct { + Account postgres.Account + Transactions []postgres.GetTransactionsForAccountRow } diff --git a/http/account_test.go b/http/account_test.go new file mode 100644 index 0000000..df8a430 --- /dev/null +++ b/http/account_test.go @@ -0,0 +1,79 @@ +package http + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "git.javil.eu/jacob1123/budgeteer/bcrypt" + "git.javil.eu/jacob1123/budgeteer/jwt" + "git.javil.eu/jacob1123/budgeteer/postgres" + "github.com/gin-gonic/gin" + + txdb "github.com/DATA-DOG/go-txdb" +) + +func init() { + txdb.Register("pgtx", "pgx", "postgres://budgeteer_test:budgeteer_test@localhost:5432/budgeteer_test") +} + +func TestListTimezonesHandler(t *testing.T) { + db, err := postgres.Connect("pgtx", "example") + if err != nil { + t.Errorf("could not connect to db: %s", err) + return + } + + h := Handler{ + Service: db, + TokenVerifier: &jwt.TokenVerifier{}, + CredentialsVerifier: &bcrypt.Verifier{}, + } + + rr := httptest.NewRecorder() + c, engine := gin.CreateTestContext(rr) + h.LoadRoutes(engine) + + t.Run("RegisterUser", func(t *testing.T) { + c.Request, err = http.NewRequest(http.MethodPost, "/api/v1/user/register", strings.NewReader(`{"password":"pass","email":"info@example.com","name":"Test"}`)) + if err != nil { + t.Errorf("error creating request: %s", err) + return + } + + h.registerPost(c) + + if rr.Code != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK) + } + + var response LoginResponse + err = json.NewDecoder(rr.Body).Decode(&response) + if err != nil { + t.Error(err.Error()) + t.Error("Error registering") + } + if len(response.Token) == 0 { + t.Error("Did not get a token") + } + }) + + t.Run("GetTransactions", func(t *testing.T) { + c.Request, err = http.NewRequest(http.MethodGet, "/account/accountid/transactions", nil) + if rr.Code != http.StatusOK { + t.Errorf("handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK) + } + + var response TransactionsResponse + err = json.NewDecoder(rr.Body).Decode(&response) + if err != nil { + t.Error(err.Error()) + t.Error("Error retreiving list of transactions.") + } + if len(response.Transactions) == 0 { + t.Error("Did not get any transactions.") + } + }) +} diff --git a/http/http.go b/http/http.go index 745e60b..53870b4 100644 --- a/http/http.go +++ b/http/http.go @@ -4,7 +4,6 @@ import ( "io/fs" "net/http" "strings" - "time" "git.javil.eu/jacob1123/budgeteer" "git.javil.eu/jacob1123/budgeteer/bcrypt" @@ -25,11 +24,15 @@ const ( expiration = 72 ) -// Serve starts the HTTP Server +// Serve starts the http server func (h *Handler) Serve() { router := gin.Default() - router.FuncMap["now"] = time.Now + h.LoadRoutes(router) + router.Run(":1323") +} +// LoadRoutes initializes all the routes +func (h *Handler) LoadRoutes(router *gin.Engine) { static, err := fs.Sub(web.Static, "dist") if err != nil { panic("couldn't open static files") @@ -77,8 +80,6 @@ func (h *Handler) Serve() { transaction := authenticated.Group("/transaction") transaction.POST("/new", h.newTransaction) transaction.POST("/:transactionid", h.newTransaction) - - router.Run(":1323") } func enableCachingForStaticFiles() gin.HandlerFunc { diff --git a/http/session.go b/http/session.go index e784d03..a76298a 100644 --- a/http/session.go +++ b/http/session.go @@ -84,11 +84,13 @@ func (h *Handler) loginPost(c *gin.Context) { return } - c.JSON(http.StatusOK, struct { - Token string - User postgres.User - Budgets []postgres.Budget - }{t, user, budgets}) + c.JSON(http.StatusOK, LoginResponse{t, user, budgets}) +} + +type LoginResponse struct { + Token string + User postgres.User + Budgets []postgres.Budget } type registerInformation struct { @@ -108,13 +110,13 @@ func (h *Handler) registerPost(c *gin.Context) { _, err := h.Service.GetUserByUsername(c.Request.Context(), register.Email) if err == nil { - c.AbortWithError(http.StatusUnauthorized, err) + c.AbortWithError(http.StatusBadRequest, fmt.Errorf("email is already taken")) return } hash, err := h.CredentialsVerifier.Hash(register.Password) if err != nil { - c.AbortWithError(http.StatusUnauthorized, err) + c.AbortWithError(http.StatusBadRequest, err) return } @@ -140,9 +142,5 @@ func (h *Handler) registerPost(c *gin.Context) { return } - c.JSON(http.StatusOK, struct { - Token string - User postgres.User - Budgets []postgres.Budget - }{t, user, budgets}) + c.JSON(http.StatusOK, LoginResponse{t, user, budgets}) } diff --git a/postgres/conn.go b/postgres/conn.go index e6d815e..046e127 100644 --- a/postgres/conn.go +++ b/postgres/conn.go @@ -18,9 +18,8 @@ type Database struct { } // Connect to a database -func Connect(server string, user string, password string, database string) (*Database, error) { - connString := fmt.Sprintf("postgres://%s:%s@%s/%s", user, password, server, database) - conn, err := sql.Open("pgx", connString) +func Connect(typ string, connString string) (*Database, error) { + conn, err := sql.Open(typ, connString) if err != nil { return nil, fmt.Errorf("open connection: %w", err) } diff --git a/web/src/components/Autocomplete.vue b/web/src/components/Autocomplete.vue index e524dfb..e4e14ba 100644 --- a/web/src/components/Autocomplete.vue +++ b/web/src/components/Autocomplete.vue @@ -56,6 +56,12 @@ export default defineComponent({ if(e.key == "Enter") { const selected = this.$data.Suggestions[0]; this.selectElement(selected); + const el = (e.target); + const inputElements = Array.from(el.ownerDocument.querySelectorAll('input:not([disabled]):not([readonly])')); + const currentIndex = inputElements.indexOf(el); + const nextElement = inputElements[currentIndex < inputElements.length - 1 ? currentIndex + 1 : 0]; + (nextElement).focus(); + } }, selectElement(element : Suggestion) { @@ -82,7 +88,7 @@ export default defineComponent({