Use uuid.UUID everywhere and have postgres generate ids

This commit is contained in:
Jan Bader 2021-11-29 21:49:37 +00:00
parent 5e8a98872f
commit 85ef7557c1
19 changed files with 199 additions and 93 deletions

4
go.mod
View File

@ -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

6
go.sum
View File

@ -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=

View File

@ -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

9
id.go
View File

@ -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
}

View File

@ -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
}

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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;

View File

@ -3,18 +3,11 @@ package postgres
// Repository represents a PostgreSQL implementation of all ModelServices
type Repository struct {
DB *Queries
IDGenerator *UlidGenerator
}
func NewRepository(queries *Queries) (*Repository, error) {
id, err := NewGenerator()
if err != nil {
return nil, err
}
repo := &Repository{
DB: queries,
IDGenerator: id,
}
return repo, nil
}

View File

@ -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;

View File

@ -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;

View File

@ -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
}

View File

@ -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()
}

View File

@ -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) {

View File

@ -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
}

View File

@ -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