diff --git a/go.mod b/go.mod index b5af37d..5f96bca 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,9 @@ go 1.17 require ( github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/gin-gonic/gin v1.7.4 + github.com/gofrs/uuid v4.0.0+incompatible + github.com/google/uuid v1.3.0 github.com/jackc/pgx/v4 v4.13.0 - github.com/oklog/ulid v1.3.1 github.com/pressly/goose/v3 v3.3.1 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa ) @@ -30,6 +31,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/ugorji/go/codec v1.1.7 // indirect golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect golang.org/x/text v0.3.6 // indirect diff --git a/go.sum b/go.sum index e0eb45e..a630b84 100644 --- a/go.sum +++ b/go.sum @@ -107,6 +107,8 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -212,7 +214,6 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLD github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= @@ -249,8 +250,9 @@ github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= diff --git a/http/http.go b/http/http.go index 6bfe35c..1424cad 100644 --- a/http/http.go +++ b/http/http.go @@ -14,6 +14,7 @@ import ( "git.javil.eu/jacob1123/budgeteer/web" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) // Handler handles incoming requests @@ -72,7 +73,13 @@ func (h *Handler) budget(c *gin.Context) { } budgetID := c.Param("budgetid") - budget, err := h.Service.DB.GetBudget(context.Background(), budgetID) + budgetUUID, err := uuid.Parse(budgetID) + if err != nil { + c.Redirect(http.StatusTemporaryRedirect, "/login") + return + } + + budget, err := h.Service.DB.GetBudget(context.Background(), budgetUUID) if err != nil { c.AbortWithError(http.StatusUnauthorized, err) return diff --git a/id.go b/id.go deleted file mode 100644 index 3743385..0000000 --- a/id.go +++ /dev/null @@ -1,9 +0,0 @@ -package budgeteer - -// ID is an implementation of a UUID -type ID [16]byte - -// IDGenerator generates new IDs -type IDGenerator interface { - New() string -} diff --git a/jwt/login.go b/jwt/login.go index 7a12ea2..353be48 100644 --- a/jwt/login.go +++ b/jwt/login.go @@ -7,6 +7,7 @@ import ( "git.javil.eu/jacob1123/budgeteer" "git.javil.eu/jacob1123/budgeteer/postgres" "github.com/dgrijalva/jwt-go" + "github.com/google/uuid" ) // TokenVerifier verifies Tokens @@ -18,7 +19,7 @@ type Token struct { username string name string expiry float64 - id string + id uuid.UUID } const ( @@ -65,7 +66,7 @@ func (tv *TokenVerifier) VerifyToken(tokenString string) (budgeteer.Token, error username: claims["usr"].(string), name: claims["name"].(string), expiry: claims["exp"].(float64), - id: claims["id"].(string), + id: uuid.MustParse(claims["id"].(string)), } return tkn, nil } @@ -99,6 +100,6 @@ func (t *Token) GetExpiry() float64 { return t.expiry } -func (t *Token) GetID() string { +func (t *Token) GetID() uuid.UUID { return t.id } diff --git a/postgres/budgets.sql.go b/postgres/budgets.sql.go index 97200ac..b9f3ab7 100644 --- a/postgres/budgets.sql.go +++ b/postgres/budgets.sql.go @@ -5,46 +5,43 @@ package postgres import ( "context" + + "github.com/google/uuid" ) const createBudget = `-- name: CreateBudget :one INSERT INTO budgets -(id, name, last_modification) -VALUES ($1, $2, NOW()) -RETURNING id, name, last_modification +(name, last_modification) +VALUES ($1, NOW()) +RETURNING name, last_modification, id ` -type CreateBudgetParams struct { - ID string - Name string -} - -func (q *Queries) CreateBudget(ctx context.Context, arg CreateBudgetParams) (Budget, error) { - row := q.db.QueryRowContext(ctx, createBudget, arg.ID, arg.Name) +func (q *Queries) CreateBudget(ctx context.Context, name string) (Budget, error) { + row := q.db.QueryRowContext(ctx, createBudget, name) var i Budget - err := row.Scan(&i.ID, &i.Name, &i.LastModification) + err := row.Scan(&i.Name, &i.LastModification, &i.ID) return i, err } const getBudget = `-- name: GetBudget :one -SELECT id, name, last_modification FROM budgets +SELECT name, last_modification, id FROM budgets WHERE id = $1 ` -func (q *Queries) GetBudget(ctx context.Context, id string) (Budget, error) { +func (q *Queries) GetBudget(ctx context.Context, id uuid.UUID) (Budget, error) { row := q.db.QueryRowContext(ctx, getBudget, id) var i Budget - err := row.Scan(&i.ID, &i.Name, &i.LastModification) + err := row.Scan(&i.Name, &i.LastModification, &i.ID) return i, err } const getBudgetsForUser = `-- name: GetBudgetsForUser :many -SELECT budgets.id, budgets.name, budgets.last_modification FROM budgets +SELECT budgets.name, budgets.last_modification, budgets.id FROM budgets LEFT JOIN user_budgets ON budgets.id = user_budgets.budget_id WHERE user_budgets.user_id = $1 ` -func (q *Queries) GetBudgetsForUser(ctx context.Context, userID string) ([]Budget, error) { +func (q *Queries) GetBudgetsForUser(ctx context.Context, userID uuid.UUID) ([]Budget, error) { rows, err := q.db.QueryContext(ctx, getBudgetsForUser, userID) if err != nil { return nil, err @@ -53,7 +50,7 @@ func (q *Queries) GetBudgetsForUser(ctx context.Context, userID string) ([]Budge var items []Budget for rows.Next() { var i Budget - if err := rows.Scan(&i.ID, &i.Name, &i.LastModification); err != nil { + if err := rows.Scan(&i.Name, &i.LastModification, &i.ID); err != nil { return nil, err } items = append(items, i) diff --git a/postgres/budgetservice.go b/postgres/budgetservice.go index c530891..e493594 100644 --- a/postgres/budgetservice.go +++ b/postgres/budgetservice.go @@ -2,10 +2,12 @@ package postgres import ( "context" + + "github.com/google/uuid" ) // Budget returns a budget for a given id. -func (s *Repository) Budget(id string) (*Budget, error) { +func (s *Repository) Budget(id uuid.UUID) (*Budget, error) { budget, err := s.DB.GetBudget(context.Background(), id) if err != nil { return nil, err @@ -13,7 +15,7 @@ func (s *Repository) Budget(id string) (*Budget, error) { return &budget, nil } -func (s *Repository) BudgetsForUser(id string) ([]Budget, error) { +func (s *Repository) BudgetsForUser(id uuid.UUID) ([]Budget, error) { budgets, err := s.DB.GetBudgetsForUser(context.Background(), id) if err != nil { return nil, err @@ -22,9 +24,8 @@ func (s *Repository) BudgetsForUser(id string) ([]Budget, error) { } -func (s *Repository) NewBudget(name string, userID string) (*Budget, error) { - b := CreateBudgetParams{ID: s.IDGenerator.New(), Name: name} - budget, err := s.DB.CreateBudget(context.Background(), b) +func (s *Repository) NewBudget(name string, userID uuid.UUID) (*Budget, error) { + budget, err := s.DB.CreateBudget(context.Background(), name) if err != nil { return nil, err } diff --git a/postgres/models.go b/postgres/models.go index afafba4..182c982 100644 --- a/postgres/models.go +++ b/postgres/models.go @@ -4,22 +4,33 @@ package postgres import ( "database/sql" + "time" + + "github.com/google/uuid" ) type Budget struct { - ID string Name string LastModification sql.NullTime + ID uuid.UUID +} + +type Transaction struct { + ID uuid.UUID + BudgetID uuid.UUID + Date time.Time + Memo sql.NullString + Amount string } type User struct { - ID string Email string Name string Password string + ID uuid.UUID } type UserBudget struct { - UserID string - BudgetID string + UserID uuid.UUID + BudgetID uuid.UUID } diff --git a/postgres/queries/budgets.sql b/postgres/queries/budgets.sql index 609f502..2ee4a08 100644 --- a/postgres/queries/budgets.sql +++ b/postgres/queries/budgets.sql @@ -1,7 +1,7 @@ -- name: CreateBudget :one INSERT INTO budgets -(id, name, last_modification) -VALUES ($1, $2, NOW()) +(name, last_modification) +VALUES ($1, NOW()) RETURNING *; -- name: GetBudgetsForUser :many diff --git a/postgres/queries/transactions.sql b/postgres/queries/transactions.sql new file mode 100644 index 0000000..fb6e43e --- /dev/null +++ b/postgres/queries/transactions.sql @@ -0,0 +1,9 @@ +-- name: CreateTransaction :one +INSERT INTO transactions +(budget_id, date, memo, amount) +VALUES ($1, $2, $3, $4) +RETURNING *; + +-- name: GetTransactionsForBudget :many +SELECT transactions.* FROM transactions +WHERE transactions.budget_id = $1; \ No newline at end of file diff --git a/postgres/repository.go b/postgres/repository.go index d308e92..2ea4048 100644 --- a/postgres/repository.go +++ b/postgres/repository.go @@ -2,19 +2,12 @@ package postgres // Repository represents a PostgreSQL implementation of all ModelServices type Repository struct { - DB *Queries - IDGenerator *UlidGenerator + DB *Queries } func NewRepository(queries *Queries) (*Repository, error) { - id, err := NewGenerator() - if err != nil { - return nil, err - } - repo := &Repository{ - DB: queries, - IDGenerator: id, + DB: queries, } return repo, nil } diff --git a/postgres/schema/0004_transactions.sql b/postgres/schema/0004_transactions.sql new file mode 100644 index 0000000..9905522 --- /dev/null +++ b/postgres/schema/0004_transactions.sql @@ -0,0 +1,11 @@ +-- +goose Up +CREATE TABLE transactions ( + id uuid DEFAULT uuid_generate_v4() NOT NULL, + budget_id uuid NOT NULL, + date date NOT NULL, + memo text NULL, + amount decimal(12,2) NOT NULL +); + +-- +goose Down +DROP TABLE transactions; \ No newline at end of file diff --git a/postgres/schema/0005_migrate-to-uuid.sql b/postgres/schema/0005_migrate-to-uuid.sql new file mode 100644 index 0000000..86894b0 --- /dev/null +++ b/postgres/schema/0005_migrate-to-uuid.sql @@ -0,0 +1,23 @@ +-- +goose Up +ALTER TABLE budgets DROP COLUMN id; +ALTER TABLE budgets ADD COLUMN id uuid DEFAULT uuid_generate_v4() NOT NULL; + +ALTER TABLE users DROP COLUMN id; +ALTER TABLE users ADD COLUMN id uuid DEFAULT uuid_generate_v4() NOT NULL; + +ALTER TABLE user_budgets DROP COLUMN user_id; +ALTER TABLE user_budgets DROP COLUMN budget_id; +ALTER TABLE user_budgets ADD COLUMN user_id uuid NOT NULL; +ALTER TABLE user_budgets ADD COLUMN budget_id uuid NOT NULL; + +-- +goose Down +ALTER TABLE budgets DROP COLUMN id; +ALTER TABLE budgets ADD COLUMN id char(26) NOT NULL; + +ALTER TABLE users DROP COLUMN id; +ALTER TABLE users ADD COLUMN id char(26) NOT NULL; + +ALTER TABLE user_budgets DROP COLUMN user_id; +ALTER TABLE user_budgets DROP COLUMN budget_id; +ALTER TABLE user_budgets ADD COLUMN user_id char(26) NOT NULL; +ALTER TABLE user_budgets ADD COLUMN budget_id char(26) NOT NULL; \ No newline at end of file diff --git a/postgres/transactions.sql.go b/postgres/transactions.sql.go new file mode 100644 index 0000000..6c27502 --- /dev/null +++ b/postgres/transactions.sql.go @@ -0,0 +1,78 @@ +// Code generated by sqlc. DO NOT EDIT. +// source: transactions.sql + +package postgres + +import ( + "context" + "database/sql" + "time" + + "github.com/google/uuid" +) + +const createTransaction = `-- name: CreateTransaction :one +INSERT INTO transactions +(budget_id, date, memo, amount) +VALUES ($1, $2, $3, $4) +RETURNING id, budget_id, date, memo, amount +` + +type CreateTransactionParams struct { + BudgetID uuid.UUID + Date time.Time + Memo sql.NullString + Amount string +} + +func (q *Queries) CreateTransaction(ctx context.Context, arg CreateTransactionParams) (Transaction, error) { + row := q.db.QueryRowContext(ctx, createTransaction, + arg.BudgetID, + arg.Date, + arg.Memo, + arg.Amount, + ) + var i Transaction + err := row.Scan( + &i.ID, + &i.BudgetID, + &i.Date, + &i.Memo, + &i.Amount, + ) + return i, err +} + +const getTransactionsForBudget = `-- name: GetTransactionsForBudget :many +SELECT transactions.id, transactions.budget_id, transactions.date, transactions.memo, transactions.amount FROM transactions +WHERE transactions.budget_id = $1 +` + +func (q *Queries) GetTransactionsForBudget(ctx context.Context, budgetID uuid.UUID) ([]Transaction, error) { + rows, err := q.db.QueryContext(ctx, getTransactionsForBudget, budgetID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Transaction + for rows.Next() { + var i Transaction + if err := rows.Scan( + &i.ID, + &i.BudgetID, + &i.Date, + &i.Memo, + &i.Amount, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} diff --git a/postgres/ulid.go b/postgres/ulid.go deleted file mode 100644 index b4b29fd..0000000 --- a/postgres/ulid.go +++ /dev/null @@ -1,27 +0,0 @@ -package postgres - -import ( - "math/rand" - "time" - - "github.com/oklog/ulid" -) - -type UlidGenerator struct { - time time.Time - entropy *rand.Rand -} - -func NewGenerator() (*UlidGenerator, error) { - t := time.Time{} - ug := &UlidGenerator{ - time: t, - entropy: rand.New(rand.NewSource(t.UnixNano())), - } - return ug, nil -} - -func (ug *UlidGenerator) New() string { - id := ulid.MustNew(ulid.Timestamp(time.Now()), ug.entropy) - return id.String() -} diff --git a/postgres/user_budgets.sql.go b/postgres/user_budgets.sql.go index 80e498c..4740c0e 100644 --- a/postgres/user_budgets.sql.go +++ b/postgres/user_budgets.sql.go @@ -5,6 +5,8 @@ package postgres import ( "context" + + "github.com/google/uuid" ) const linkBudgetToUser = `-- name: LinkBudgetToUser :one @@ -15,8 +17,8 @@ RETURNING user_id, budget_id ` type LinkBudgetToUserParams struct { - UserID string - BudgetID string + UserID uuid.UUID + BudgetID uuid.UUID } func (q *Queries) LinkBudgetToUser(ctx context.Context, arg LinkBudgetToUserParams) (UserBudget, error) { diff --git a/postgres/users.sql.go b/postgres/users.sql.go index e9f7023..ed28220 100644 --- a/postgres/users.sql.go +++ b/postgres/users.sql.go @@ -5,17 +5,19 @@ package postgres import ( "context" + + "github.com/google/uuid" ) const createUser = `-- name: CreateUser :one INSERT INTO users (id, email, name, password) VALUES ($1, $2, $3, $4) -RETURNING id, email, name, password +RETURNING email, name, password, id ` type CreateUserParams struct { - ID string + ID uuid.UUID Email string Name string Password string @@ -30,33 +32,33 @@ func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, e ) var i User err := row.Scan( - &i.ID, &i.Email, &i.Name, &i.Password, + &i.ID, ) return i, err } const getUser = `-- name: GetUser :one -SELECT id, email, name, password FROM users +SELECT email, name, password, id FROM users WHERE id = $1 ` -func (q *Queries) GetUser(ctx context.Context, id string) (User, error) { +func (q *Queries) GetUser(ctx context.Context, id uuid.UUID) (User, error) { row := q.db.QueryRowContext(ctx, getUser, id) var i User err := row.Scan( - &i.ID, &i.Email, &i.Name, &i.Password, + &i.ID, ) return i, err } const getUserByUsername = `-- name: GetUserByUsername :one -SELECT id, email, name, password FROM users +SELECT email, name, password, id FROM users WHERE email = $1 ` @@ -64,10 +66,10 @@ func (q *Queries) GetUserByUsername(ctx context.Context, email string) (User, er row := q.db.QueryRowContext(ctx, getUserByUsername, email) var i User err := row.Scan( - &i.ID, &i.Email, &i.Name, &i.Password, + &i.ID, ) return i, err } diff --git a/sqlc.yaml b/sqlc.yaml index 7a6fcae..5f34110 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -5,4 +5,4 @@ packages: engine: "postgresql" schema: "postgres/schema/" queries: "postgres/queries/" - # sql_package: "pgx/v4" + #sql_package: "pgx/v4" \ No newline at end of file diff --git a/token.go b/token.go index 4e8ee47..1a9cff3 100644 --- a/token.go +++ b/token.go @@ -1,13 +1,16 @@ package budgeteer -import "git.javil.eu/jacob1123/budgeteer/postgres" +import ( + "git.javil.eu/jacob1123/budgeteer/postgres" + "github.com/google/uuid" +) // Token contains data that authenticates a user type Token interface { GetUsername() string GetName() string GetExpiry() float64 - GetID() string + GetID() uuid.UUID } // TokenVerifier verifies a Token