Handle circular required keys
Use a dummy-value at first and update it later. Deferrable doesn't seem to work for NOT NULL - only for FOREIGN KEYs.
This commit is contained in:
		| @@ -1,6 +1,7 @@ | |||||||
| package http | package http | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"net/http" | 	"net/http" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @@ -10,45 +11,50 @@ import ( | |||||||
| ) | ) | ||||||
|  |  | ||||||
| func (h *Handler) newTransaction(c *gin.Context) { | func (h *Handler) newTransaction(c *gin.Context) { | ||||||
| 	transactionMemo, succ := c.GetPostForm("memo") | 	transactionMemo, _ := c.GetPostForm("memo") | ||||||
| 	if !succ { |  | ||||||
| 		c.AbortWithStatus(http.StatusNotAcceptable) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	transactionAccount, succ := c.GetPostForm("account_id") | 	transactionAccount, succ := c.GetPostForm("account_id") | ||||||
| 	if !succ { | 	if !succ { | ||||||
| 		c.AbortWithStatus(http.StatusNotAcceptable) | 		c.AbortWithError(http.StatusNotAcceptable, fmt.Errorf("account_id missing")) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	transactionAccountID, err := uuid.Parse(transactionAccount) | 	transactionAccountID, err := uuid.Parse(transactionAccount) | ||||||
| 	if !succ { | 	if !succ { | ||||||
| 		c.AbortWithStatus(http.StatusNotAcceptable) | 		c.AbortWithError(http.StatusNotAcceptable, fmt.Errorf("account_id is not a valid uuid")) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	transactionDate, succ := c.GetPostForm("date") | 	transactionDate, succ := c.GetPostForm("date") | ||||||
| 	if !succ { | 	if !succ { | ||||||
| 		c.AbortWithStatus(http.StatusNotAcceptable) | 		c.AbortWithError(http.StatusNotAcceptable, fmt.Errorf("date missing")) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	transactionDateValue, err := time.Parse("2006-01-02", transactionDate) | 	transactionDateValue, err := time.Parse("2006-01-02", transactionDate) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		c.AbortWithStatus(http.StatusNotAcceptable) | 		c.AbortWithError(http.StatusNotAcceptable, fmt.Errorf("date is not a valid date")) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	transactionAmount, succ := c.GetPostForm("amount") | ||||||
|  | 	if !succ { | ||||||
|  | 		c.AbortWithError(http.StatusNotAcceptable, fmt.Errorf("amount missing")) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	amount := postgres.Numeric{} | ||||||
|  | 	amount.Set(transactionAmount) | ||||||
| 	new := postgres.CreateTransactionParams{ | 	new := postgres.CreateTransactionParams{ | ||||||
| 		Memo:       transactionMemo, | 		Memo:       transactionMemo, | ||||||
| 		Date:       transactionDateValue, | 		Date:       transactionDateValue, | ||||||
| 		Amount:    postgres.Numeric{}, | 		Amount:     amount, | ||||||
| 		AccountID:  transactionAccountID, | 		AccountID:  transactionAccountID, | ||||||
|  | 		PayeeID:    uuid.NullUUID{}, | ||||||
|  | 		CategoryID: uuid.NullUUID{}, | ||||||
| 	} | 	} | ||||||
| 	_, err = h.Service.CreateTransaction(c.Request.Context(), new) | 	_, err = h.Service.CreateTransaction(c.Request.Context(), new) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		c.AbortWithError(http.StatusInternalServerError, err) | 		c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("create transaction: %w", err)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,13 +12,18 @@ import ( | |||||||
|  |  | ||||||
| const createBudget = `-- name: CreateBudget :one | const createBudget = `-- name: CreateBudget :one | ||||||
| INSERT INTO budgets | INSERT INTO budgets | ||||||
| (name, last_modification) | (name, income_category_id, last_modification) | ||||||
| VALUES ($1, NOW()) | VALUES ($1, $2, NOW()) | ||||||
| RETURNING id, name, last_modification, income_category_id | RETURNING id, name, last_modification, income_category_id | ||||||
| ` | ` | ||||||
|  |  | ||||||
| func (q *Queries) CreateBudget(ctx context.Context, name string) (Budget, error) { | type CreateBudgetParams struct { | ||||||
| 	row := q.db.QueryRowContext(ctx, createBudget, name) | 	Name             string | ||||||
|  | 	IncomeCategoryID uuid.UUID | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func (q *Queries) CreateBudget(ctx context.Context, arg CreateBudgetParams) (Budget, error) { | ||||||
|  | 	row := q.db.QueryRowContext(ctx, createBudget, arg.Name, arg.IncomeCategoryID) | ||||||
| 	var i Budget | 	var i Budget | ||||||
| 	err := row.Scan( | 	err := row.Scan( | ||||||
| 		&i.ID, | 		&i.ID, | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ package postgres | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"database/sql" | 	"database/sql" | ||||||
|  | 	"fmt" | ||||||
|  |  | ||||||
| 	"github.com/google/uuid" | 	"github.com/google/uuid" | ||||||
| ) | ) | ||||||
| @@ -11,15 +12,18 @@ import ( | |||||||
| func (s *Database) NewBudget(context context.Context, name string, userID uuid.UUID) (*Budget, error) { | func (s *Database) NewBudget(context context.Context, name string, userID uuid.UUID) (*Budget, error) { | ||||||
| 	tx, err := s.BeginTx(context, &sql.TxOptions{}) | 	tx, err := s.BeginTx(context, &sql.TxOptions{}) | ||||||
| 	q := s.WithTx(tx) | 	q := s.WithTx(tx) | ||||||
| 	budget, err := q.CreateBudget(context, name) | 	budget, err := q.CreateBudget(context, CreateBudgetParams{ | ||||||
|  | 		Name:             name, | ||||||
|  | 		IncomeCategoryID: uuid.New(), | ||||||
|  | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, fmt.Errorf("create budget: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	ub := LinkBudgetToUserParams{UserID: userID, BudgetID: budget.ID} | 	ub := LinkBudgetToUserParams{UserID: userID, BudgetID: budget.ID} | ||||||
| 	_, err = q.LinkBudgetToUser(context, ub) | 	_, err = q.LinkBudgetToUser(context, ub) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, fmt.Errorf("link budget to user: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	group, err := q.CreateCategoryGroup(context, CreateCategoryGroupParams{ | 	group, err := q.CreateCategoryGroup(context, CreateCategoryGroupParams{ | ||||||
| @@ -27,18 +31,25 @@ func (s *Database) NewBudget(context context.Context, name string, userID uuid.U | |||||||
| 		BudgetID: budget.ID, | 		BudgetID: budget.ID, | ||||||
| 	}) | 	}) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, fmt.Errorf("create inflow category_group: %w", err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	cat, err := q.CreateCategory(context, CreateCategoryParams{ | 	cat, err := q.CreateCategory(context, CreateCategoryParams{ | ||||||
| 		Name:            "Ready to assign", | 		Name:            "Ready to Assign", | ||||||
| 		CategoryGroupID: group.ID, | 		CategoryGroupID: group.ID, | ||||||
| 	}) | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("create ready to assign category: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	q.SetInflowCategory(context, SetInflowCategoryParams{ | 	err = q.SetInflowCategory(context, SetInflowCategoryParams{ | ||||||
| 		IncomeCategoryID: cat.ID, | 		IncomeCategoryID: cat.ID, | ||||||
| 		ID:               budget.ID, | 		ID:               budget.ID, | ||||||
| 	}) | 	}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		return nil, fmt.Errorf("set inflow category: %w", err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	tx.Commit() | 	tx.Commit() | ||||||
|  |  | ||||||
| 	return &budget, nil | 	return &budget, nil | ||||||
|   | |||||||
| @@ -1,7 +1,7 @@ | |||||||
| -- name: CreateBudget :one | -- name: CreateBudget :one | ||||||
| INSERT INTO budgets | INSERT INTO budgets | ||||||
| (name, last_modification) | (name, income_category_id, last_modification) | ||||||
| VALUES ($1, NOW()) | VALUES ($1, $2, NOW()) | ||||||
| RETURNING *; | RETURNING *; | ||||||
|  |  | ||||||
| -- name: SetInflowCategory :exec | -- name: SetInflowCategory :exec | ||||||
|   | |||||||
| @@ -4,7 +4,8 @@ CREATE TABLE categories ( | |||||||
|     category_group_id uuid NOT NULL REFERENCES category_groups (id) ON DELETE CASCADE, |     category_group_id uuid NOT NULL REFERENCES category_groups (id) ON DELETE CASCADE, | ||||||
|     name varchar(50) NOT NULL |     name varchar(50) NOT NULL | ||||||
| ); | ); | ||||||
| ALTER TABLE budgets ADD COLUMN income_category_id uuid NOT NULL REFERENCES categories (id) DEFERRABLE; | ALTER TABLE budgets ADD COLUMN | ||||||
|  |     income_category_id uuid NOT NULL REFERENCES categories (id) DEFERRABLE INITIALLY DEFERRED; | ||||||
|  |  | ||||||
| -- +goose Down | -- +goose Down | ||||||
| ALTER TABLE budgets DROP COLUMN income_category_id; | ALTER TABLE budgets DROP COLUMN income_category_id; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user