diff --git a/budget.go b/budget.go deleted file mode 100644 index a2d8808..0000000 --- a/budget.go +++ /dev/null @@ -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) -} diff --git a/cmd/budgeteer/main.go b/cmd/budgeteer/main.go index 60cbd91..1fc5780 100644 --- a/cmd/budgeteer/main.go +++ b/cmd/budgeteer/main.go @@ -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{ diff --git a/data.go b/data.go deleted file mode 100644 index 19681c9..0000000 --- a/data.go +++ /dev/null @@ -1,5 +0,0 @@ -package budgeteer - -type Data interface { - GetBudgets() []*Budget -} diff --git a/http/data.go b/http/data.go index b772739..9b0a09b 100644 --- a/http/data.go +++ b/http/data.go @@ -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 { diff --git a/http/http.go b/http/http.go index 4c8c770..6bfe35c 100644 --- a/http/http.go +++ b/http/http.go @@ -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) } diff --git a/jwt/login.go b/jwt/login.go index cb1e557..7a12ea2 100644 --- a/jwt/login.go +++ b/jwt/login.go @@ -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, diff --git a/postgres/budgets.sql.go b/postgres/budgets.sql.go index c8c2ba2..97200ac 100644 --- a/postgres/budgets.sql.go +++ b/postgres/budgets.sql.go @@ -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 } diff --git a/postgres/budgetservice.go b/postgres/budgetservice.go index 02fa13d..c530891 100644 --- a/postgres/budgetservice.go +++ b/postgres/budgetservice.go @@ -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 } diff --git a/postgres/conn.go b/postgres/conn.go index 94a2c83..1c24663 100644 --- a/postgres/conn.go +++ b/postgres/conn.go @@ -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 } diff --git a/postgres/db.go b/postgres/db.go index f2900b8..8d02508 100644 --- a/postgres/db.go +++ b/postgres/db.go @@ -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, } diff --git a/postgres/models.go b/postgres/models.go index 0f4042b..afafba4 100644 --- a/postgres/models.go +++ b/postgres/models.go @@ -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 { diff --git a/postgres/budgets.sql b/postgres/queries/budgets.sql similarity index 60% rename from postgres/budgets.sql rename to postgres/queries/budgets.sql index c4b9a0e..609f502 100644 --- a/postgres/budgets.sql +++ b/postgres/queries/budgets.sql @@ -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) @@ -13,4 +7,8 @@ RETURNING *; -- name: GetBudgetsForUser :many SELECT budgets.* FROM budgets LEFT JOIN user_budgets ON budgets.id = user_budgets.budget_id -WHERE user_budgets.user_id = $1; \ No newline at end of file +WHERE user_budgets.user_id = $1; + +-- name: GetBudget :one +SELECT * FROM budgets +WHERE id = $1; \ No newline at end of file diff --git a/postgres/user_budgets.sql b/postgres/queries/user_budgets.sql similarity index 52% rename from postgres/user_budgets.sql rename to postgres/queries/user_budgets.sql index 7bd9138..b136295 100644 --- a/postgres/user_budgets.sql +++ b/postgres/queries/user_budgets.sql @@ -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) diff --git a/postgres/users.sql b/postgres/queries/users.sql similarity index 68% rename from postgres/users.sql rename to postgres/queries/users.sql index d6fdcc6..542cc20 100644 --- a/postgres/users.sql +++ b/postgres/queries/users.sql @@ -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; diff --git a/postgres/repository.go b/postgres/repository.go index 6f48ff6..d308e92 100644 --- a/postgres/repository.go +++ b/postgres/repository.go @@ -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 } diff --git a/postgres/schema/0001_budgets.sql b/postgres/schema/0001_budgets.sql new file mode 100644 index 0000000..8117997 --- /dev/null +++ b/postgres/schema/0001_budgets.sql @@ -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; \ No newline at end of file diff --git a/postgres/schema/0002_user_budgets.sql b/postgres/schema/0002_user_budgets.sql new file mode 100644 index 0000000..9d66c8d --- /dev/null +++ b/postgres/schema/0002_user_budgets.sql @@ -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; \ No newline at end of file diff --git a/postgres/schema/0003_users.sql b/postgres/schema/0003_users.sql new file mode 100644 index 0000000..31d1050 --- /dev/null +++ b/postgres/schema/0003_users.sql @@ -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; \ No newline at end of file diff --git a/ulid/ulid.go b/postgres/ulid.go similarity index 90% rename from ulid/ulid.go rename to postgres/ulid.go index 9888149..b4b29fd 100644 --- a/ulid/ulid.go +++ b/postgres/ulid.go @@ -1,4 +1,4 @@ -package ulid +package postgres import ( "math/rand" diff --git a/postgres/user_budgets.sql.go b/postgres/user_budgets.sql.go index cc8fd80..80e498c 100644 --- a/postgres/user_budgets.sql.go +++ b/postgres/user_budgets.sql.go @@ -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 diff --git a/postgres/users.sql.go b/postgres/users.sql.go index f6662ed..e9f7023 100644 --- a/postgres/users.sql.go +++ b/postgres/users.sql.go @@ -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, diff --git a/services.go b/services.go deleted file mode 100644 index 5f22dda..0000000 --- a/services.go +++ /dev/null @@ -1,6 +0,0 @@ -package budgeteer - -type ModelService interface { - BudgetService - UserService -} diff --git a/sqlc.yaml b/sqlc.yaml index 89cf28c..7a6fcae 100644 --- a/sqlc.yaml +++ b/sqlc.yaml @@ -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" diff --git a/token.go b/token.go index 8f40ba9..4e8ee47 100644 --- a/token.go +++ b/token.go @@ -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) } diff --git a/user.go b/user.go deleted file mode 100644 index b52e9d9..0000000 --- a/user.go +++ /dev/null @@ -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) -}