Use goose for migrations

This commit is contained in:
Jan Bader 2021-11-08 22:24:21 +00:00
parent cf1bc70103
commit 5de7d32c30
25 changed files with 134 additions and 145 deletions

View File

@ -1,25 +0,0 @@
package budgeteer
import (
"time"
)
// Budget represents a budget
type Budget struct {
ID string
Name string
LastModification time.Time
}
// UserBudget represents the relation between users and budgets
type UserBudget struct {
UserID string `sql:",pk"`
BudgetID string `sql:",pk"`
}
// BudgetService provides Methods for CRUD of Budgets
type BudgetService interface {
Budget(id string) (*Budget, error)
BudgetsForUser(id string) ([]*Budget, error)
NewBudget(name string, userID string) (*Budget, error)
}

View File

@ -6,7 +6,6 @@ import (
"git.javil.eu/jacob1123/budgeteer/http"
"git.javil.eu/jacob1123/budgeteer/jwt"
"git.javil.eu/jacob1123/budgeteer/postgres"
"git.javil.eu/jacob1123/budgeteer/ulid"
)
func main() {
@ -22,12 +21,11 @@ func main() {
panic("Failed connecting to DB")
}
id, err := ulid.NewGenerator()
us, err := postgres.NewRepository(db)
if err != nil {
panic("Failed creating ID-Generator")
panic("Failed building Repository")
}
us := &postgres.Repository{DB: db, IDGenerator: id}
tv := &jwt.TokenVerifier{}
h := &http.Handler{

View File

@ -1,5 +0,0 @@
package budgeteer
type Data interface {
GetBudgets() []*Budget
}

View File

@ -1,14 +1,17 @@
package http
import "git.javil.eu/jacob1123/budgeteer"
import (
"git.javil.eu/jacob1123/budgeteer"
"git.javil.eu/jacob1123/budgeteer/postgres"
)
type TemplateData struct {
Token budgeteer.Token
Budget *budgeteer.Budget
budgetService budgeteer.ModelService
Budget *postgres.Budget
budgetService *postgres.Repository
}
func (d TemplateData) GetBudgets() []*budgeteer.Budget {
func (d TemplateData) GetBudgets() []postgres.Budget {
userID := d.Token.GetID()
budgets, err := d.budgetService.BudgetsForUser(userID)
if err != nil {

View File

@ -1,6 +1,7 @@
package http
import (
"context"
"io/fs"
"net/http"
"time"
@ -8,6 +9,8 @@ import (
"html/template"
"git.javil.eu/jacob1123/budgeteer"
"git.javil.eu/jacob1123/budgeteer/bcrypt"
"git.javil.eu/jacob1123/budgeteer/postgres"
"git.javil.eu/jacob1123/budgeteer/web"
"github.com/gin-gonic/gin"
@ -15,9 +18,9 @@ import (
// Handler handles incoming requests
type Handler struct {
Service budgeteer.BudgetService
Service *postgres.Repository
TokenVerifier budgeteer.TokenVerifier
CredentialsVerifier budgeteer.CredentialVerifier
CredentialsVerifier *bcrypt.Verifier
}
const (
@ -69,7 +72,7 @@ func (h *Handler) budget(c *gin.Context) {
}
budgetID := c.Param("budgetid")
budget, err := h.Service.Budget(budgetID)
budget, err := h.Service.DB.GetBudget(context.Background(), budgetID)
if err != nil {
c.AbortWithError(http.StatusUnauthorized, err)
return
@ -77,7 +80,7 @@ func (h *Handler) budget(c *gin.Context) {
d := TemplateData{
Token: token,
Budget: budget,
Budget: &budget,
budgetService: h.Service,
}
@ -163,7 +166,7 @@ func (h *Handler) loginPost(c *gin.Context) {
username, _ := c.GetPostForm("username")
password, _ := c.GetPostForm("password")
user, err := h.Service.UserByUsername(username)
user, err := h.Service.DB.GetUserByUsername(context.Background(), username)
if err != nil {
c.AbortWithError(http.StatusUnauthorized, err)
return
@ -174,7 +177,7 @@ func (h *Handler) loginPost(c *gin.Context) {
return
}
t, err := h.TokenVerifier.CreateToken(user)
t, err := h.TokenVerifier.CreateToken(&user)
if err != nil {
c.AbortWithError(http.StatusUnauthorized, err)
}
@ -192,7 +195,7 @@ func (h *Handler) registerPost(c *gin.Context) {
password, _ := c.GetPostForm("password")
name, _ := c.GetPostForm("name")
user, err := h.Service.UserByUsername(email)
_, err := h.Service.DB.GetUserByUsername(context.Background(), email)
if err == nil {
c.AbortWithStatus(http.StatusUnauthorized)
return
@ -204,12 +207,12 @@ func (h *Handler) registerPost(c *gin.Context) {
return
}
user = &budgeteer.User{
createUser := postgres.CreateUserParams{
Name: name,
Password: hash,
Email: email,
}
err = h.Service.CreateUser(user)
_, err = h.Service.DB.CreateUser(context.Background(), createUser)
if err != nil {
c.AbortWithError(http.StatusInternalServerError, err)
}

View File

@ -5,6 +5,7 @@ import (
"time"
"git.javil.eu/jacob1123/budgeteer"
"git.javil.eu/jacob1123/budgeteer/postgres"
"github.com/dgrijalva/jwt-go"
)
@ -26,7 +27,7 @@ const (
)
// CreateToken creates a new token from username and name
func (tv *TokenVerifier) CreateToken(user *budgeteer.User) (string, error) {
func (tv *TokenVerifier) CreateToken(user *postgres.User) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"usr": user.Email,
"name": user.Name,

View File

@ -20,7 +20,19 @@ type CreateBudgetParams struct {
}
func (q *Queries) CreateBudget(ctx context.Context, arg CreateBudgetParams) (Budget, error) {
row := q.db.QueryRow(ctx, createBudget, arg.ID, arg.Name)
row := q.db.QueryRowContext(ctx, createBudget, arg.ID, arg.Name)
var i Budget
err := row.Scan(&i.ID, &i.Name, &i.LastModification)
return i, err
}
const getBudget = `-- name: GetBudget :one
SELECT id, name, last_modification FROM budgets
WHERE id = $1
`
func (q *Queries) GetBudget(ctx context.Context, id string) (Budget, error) {
row := q.db.QueryRowContext(ctx, getBudget, id)
var i Budget
err := row.Scan(&i.ID, &i.Name, &i.LastModification)
return i, err
@ -33,7 +45,7 @@ WHERE user_budgets.user_id = $1
`
func (q *Queries) GetBudgetsForUser(ctx context.Context, userID string) ([]Budget, error) {
rows, err := q.db.Query(ctx, getBudgetsForUser, userID)
rows, err := q.db.QueryContext(ctx, getBudgetsForUser, userID)
if err != nil {
return nil, err
}
@ -46,6 +58,9 @@ func (q *Queries) GetBudgetsForUser(ctx context.Context, userID string) ([]Budge
}
items = append(items, i)
}
if err := rows.Close(); err != nil {
return nil, err
}
if err := rows.Err(); err != nil {
return nil, err
}

View File

@ -2,23 +2,19 @@ package postgres
import (
"context"
"database/sql"
"git.javil.eu/jacob1123/budgeteer"
)
// Budget returns a budget for a given id.
func (s *Repository) Budget(id string) (*budgeteer.Budget, error) {
b := &budgeteer.Budget{ID: id}
err := s.DB.Select(&b)
func (s *Repository) Budget(id string) (*Budget, error) {
budget, err := s.DB.GetBudget(context.Background(), id)
if err != nil {
return nil, err
}
return b, nil
return &budget, nil
}
func (s *Repository) BudgetsForUser(id string) ([]Budget, error) {
budgets, err := s.DB.GetBudgetsForUser(context.Background(), sql.NullString{id, true})
budgets, err := s.DB.GetBudgetsForUser(context.Background(), id)
if err != nil {
return nil, err
}
@ -26,7 +22,7 @@ func (s *Repository) BudgetsForUser(id string) ([]Budget, error) {
}
func (s *Repository) NewBudget(name string, userID 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)
if err != nil {
@ -39,5 +35,5 @@ func (s *Repository) NewBudget(name string, userID string) (Budget, error) {
return nil, err
}
return budget, nil
return &budget, nil
}

View File

@ -1,17 +1,29 @@
package postgres
import (
"context"
"database/sql"
"embed"
"fmt"
"github.com/jackc/pgx/v4"
_ "github.com/jackc/pgx/v4/stdlib"
"github.com/pressly/goose/v3"
)
// go:embed schema/*.sql
var migrations embed.FS
// Connect to a database
func Connect(server string, user string, password string, database string) (*Queries, error) {
conn, err := pgx.Connect(context.Background(), fmt.Sprintf("postgresql://%s:%s@%s/%s", user, password, server, database))
connString := fmt.Sprintf("pgx://%s:%s@%s/%s", user, password, server, database)
conn, err := sql.Open("pgx", connString)
if err != nil {
return nil, err
}
goose.SetBaseFS(migrations)
if err = goose.Up(conn, "schema"); err != nil {
return nil, err
}
return New(conn), nil
}

View File

@ -4,15 +4,14 @@ package postgres
import (
"context"
"github.com/jackc/pgconn"
"github.com/jackc/pgx/v4"
"database/sql"
)
type DBTX interface {
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
QueryRow(context.Context, string, ...interface{}) pgx.Row
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
PrepareContext(context.Context, string) (*sql.Stmt, error)
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
}
func New(db DBTX) *Queries {
@ -23,7 +22,7 @@ type Queries struct {
db DBTX
}
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
return &Queries{
db: tx,
}

View File

@ -13,10 +13,10 @@ type Budget struct {
}
type User struct {
ID sql.NullString
Email sql.NullString
Name sql.NullString
Password sql.NullString
ID string
Email string
Name string
Password string
}
type UserBudget struct {

View File

@ -1,9 +1,3 @@
CREATE TABLE budgets (
id char(26) NOT NULL,
name text NOT NULL,
last_modification timestamp with time zone
);
-- name: CreateBudget :one
INSERT INTO budgets
(id, name, last_modification)
@ -14,3 +8,7 @@ RETURNING *;
SELECT budgets.* FROM budgets
LEFT JOIN user_budgets ON budgets.id = user_budgets.budget_id
WHERE user_budgets.user_id = $1;
-- name: GetBudget :one
SELECT * FROM budgets
WHERE id = $1;

View File

@ -1,8 +1,3 @@
CREATE TABLE user_budgets (
user_id char(26) NOT NULL,
budget_id char(26) NOT NULL
);
-- name: LinkBudgetToUser :one
INSERT INTO user_budgets
(user_id, budget_id)

View File

@ -1,10 +1,3 @@
CREATE TABLE users (
id char(26),
email text,
name text,
password text
);
-- name: GetUserByUsername :one
SELECT * FROM users
WHERE email = $1;

View File

@ -1,11 +1,20 @@
package postgres
import (
"git.javil.eu/jacob1123/budgeteer"
)
// Repository represents a PostgreSQL implementation of all ModelServices
type Repository struct {
DB *Queries
IDGenerator budgeteer.IDGenerator
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,9 @@
-- +goose Up
CREATE TABLE budgets (
id char(26) NOT NULL,
name text NOT NULL,
last_modification timestamp with time zone
);
-- +goose Down
DROP TABLE budgets;

View File

@ -0,0 +1,8 @@
-- +goose Up
CREATE TABLE user_budgets (
user_id char(26) NOT NULL,
budget_id char(26) NOT NULL
);
-- +goose Down
DROP TABLE user_budgets;

View File

@ -0,0 +1,10 @@
-- +goose Up
CREATE TABLE users (
id char(26) NOT NULL,
email text NOT NULL,
name text NOT NULL,
password text NOT NULL
);
-- +goose Down
DROP TABLE users;

View File

@ -1,4 +1,4 @@
package ulid
package postgres
import (
"math/rand"

View File

@ -20,7 +20,7 @@ type LinkBudgetToUserParams struct {
}
func (q *Queries) LinkBudgetToUser(ctx context.Context, arg LinkBudgetToUserParams) (UserBudget, error) {
row := q.db.QueryRow(ctx, linkBudgetToUser, arg.UserID, arg.BudgetID)
row := q.db.QueryRowContext(ctx, linkBudgetToUser, arg.UserID, arg.BudgetID)
var i UserBudget
err := row.Scan(&i.UserID, &i.BudgetID)
return i, err

View File

@ -5,7 +5,6 @@ package postgres
import (
"context"
"database/sql"
)
const createUser = `-- name: CreateUser :one
@ -16,14 +15,14 @@ RETURNING id, email, name, password
`
type CreateUserParams struct {
ID sql.NullString
Email sql.NullString
Name sql.NullString
Password sql.NullString
ID string
Email string
Name string
Password string
}
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
row := q.db.QueryRow(ctx, createUser,
row := q.db.QueryRowContext(ctx, createUser,
arg.ID,
arg.Email,
arg.Name,
@ -44,8 +43,8 @@ SELECT id, email, name, password FROM users
WHERE id = $1
`
func (q *Queries) GetUser(ctx context.Context, id sql.NullString) (User, error) {
row := q.db.QueryRow(ctx, getUser, id)
func (q *Queries) GetUser(ctx context.Context, id string) (User, error) {
row := q.db.QueryRowContext(ctx, getUser, id)
var i User
err := row.Scan(
&i.ID,
@ -61,8 +60,8 @@ SELECT id, email, name, password FROM users
WHERE email = $1
`
func (q *Queries) GetUserByUsername(ctx context.Context, email sql.NullString) (User, error) {
row := q.db.QueryRow(ctx, getUserByUsername, email)
func (q *Queries) GetUserByUsername(ctx context.Context, email string) (User, error) {
row := q.db.QueryRowContext(ctx, getUserByUsername, email)
var i User
err := row.Scan(
&i.ID,

View File

@ -1,6 +0,0 @@
package budgeteer
type ModelService interface {
BudgetService
UserService
}

View File

@ -3,6 +3,6 @@ packages:
- path: "postgres"
name: "postgres"
engine: "postgresql"
schema: "postgres/"
queries: "postgres/"
sql_package: "pgx/v4"
schema: "postgres/schema/"
queries: "postgres/queries/"
# sql_package: "pgx/v4"

View File

@ -1,5 +1,7 @@
package budgeteer
import "git.javil.eu/jacob1123/budgeteer/postgres"
// Token contains data that authenticates a user
type Token interface {
GetUsername() string
@ -11,5 +13,5 @@ type Token interface {
// TokenVerifier verifies a Token
type TokenVerifier interface {
VerifyToken(string) (Token, error)
CreateToken(*User) (string, error)
CreateToken(*postgres.User) (string, error)
}

25
user.go
View File

@ -1,25 +0,0 @@
package budgeteer
// User struct contains Login information
type User struct {
ID string
Email string
Password string
Name string
Budgets []*Budget `pg:",many2many:user_budgets"`
}
// UserService provides Methods for CRUD of Users
type UserService interface {
User(id string) (*User, error)
UserByUsername(username string) (*User, error)
//Users() ([]*User, error)
CreateUser(u *User) error
//DeleteUser(id int) error
}
// CredentialVerifier verifies the provided credentials
type CredentialVerifier interface {
Verify(password string, hashOnDb string) error
Hash(password string) (string, error)
}