chore: refactor
All checks were successful
Deploy / build-and-deploy (push) Successful in 3m45s

This commit is contained in:
juancwu 2026-03-14 16:27:45 +00:00
commit 45fcecdc04
29 changed files with 2865 additions and 3867 deletions

View file

@ -32,29 +32,24 @@ func NewBudgetRepository(db *sqlx.DB) BudgetRepository {
}
func (r *budgetRepository) Create(budget *model.Budget, tagIDs []string) error {
tx, err := r.db.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
return WithTx(r.db, func(tx *sqlx.Tx) error {
query := `INSERT INTO budgets (id, space_id, amount_cents, period, start_date, end_date, is_active, created_by, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);`
if _, err := tx.Exec(query, budget.ID, budget.SpaceID, budget.AmountCents, budget.Period, budget.StartDate, budget.EndDate, budget.IsActive, budget.CreatedBy, budget.CreatedAt, budget.UpdatedAt); err != nil {
return err
}
query := `INSERT INTO budgets (id, space_id, amount_cents, period, start_date, end_date, is_active, created_by, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10);`
_, err = tx.Exec(query, budget.ID, budget.SpaceID, budget.AmountCents, budget.Period, budget.StartDate, budget.EndDate, budget.IsActive, budget.CreatedBy, budget.CreatedAt, budget.UpdatedAt)
if err != nil {
return err
}
if len(tagIDs) > 0 {
tagQuery := `INSERT INTO budget_tags (budget_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(tagQuery, budget.ID, tagID); err != nil {
return err
if len(tagIDs) > 0 {
tagQuery := `INSERT INTO budget_tags (budget_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(tagQuery, budget.ID, tagID); err != nil {
return err
}
}
}
}
return tx.Commit()
return nil
})
}
func (r *budgetRepository) GetByID(id string) (*model.Budget, error) {
@ -136,36 +131,37 @@ func (r *budgetRepository) GetTagsByBudgetIDs(budgetIDs []string) (map[string][]
}
func (r *budgetRepository) Update(budget *model.Budget, tagIDs []string) error {
tx, err := r.db.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
return WithTx(r.db, func(tx *sqlx.Tx) error {
query := `UPDATE budgets SET amount_cents = $1, period = $2, start_date = $3, end_date = $4, is_active = $5, updated_at = $6 WHERE id = $7;`
if _, err := tx.Exec(query, budget.AmountCents, budget.Period, budget.StartDate, budget.EndDate, budget.IsActive, budget.UpdatedAt, budget.ID); err != nil {
return err
}
query := `UPDATE budgets SET amount_cents = $1, period = $2, start_date = $3, end_date = $4, is_active = $5, updated_at = $6 WHERE id = $7;`
_, err = tx.Exec(query, budget.AmountCents, budget.Period, budget.StartDate, budget.EndDate, budget.IsActive, budget.UpdatedAt, budget.ID)
if err != nil {
return err
}
if _, err := tx.Exec(`DELETE FROM budget_tags WHERE budget_id = $1;`, budget.ID); err != nil {
return err
}
// Replace tags: delete old, insert new
if _, err := tx.Exec(`DELETE FROM budget_tags WHERE budget_id = $1;`, budget.ID); err != nil {
return err
}
if len(tagIDs) > 0 {
tagQuery := `INSERT INTO budget_tags (budget_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(tagQuery, budget.ID, tagID); err != nil {
return err
if len(tagIDs) > 0 {
tagQuery := `INSERT INTO budget_tags (budget_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(tagQuery, budget.ID, tagID); err != nil {
return err
}
}
}
}
return tx.Commit()
return nil
})
}
func (r *budgetRepository) Delete(id string) error {
_, err := r.db.Exec(`DELETE FROM budgets WHERE id = $1;`, id)
result, err := r.db.Exec(`DELETE FROM budgets WHERE id = $1;`, id)
if err != nil {
return err
}
rows, err := result.RowsAffected()
if err == nil && rows == 0 {
return ErrBudgetNotFound
}
return err
}

View file

@ -40,43 +40,34 @@ func NewExpenseRepository(db *sqlx.DB) ExpenseRepository {
}
func (r *expenseRepository) Create(expense *model.Expense, tagIDs []string, itemIDs []string) error {
tx, err := r.db.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
return WithTx(r.db, func(tx *sqlx.Tx) error {
queryExpense := `INSERT INTO expenses (id, space_id, created_by, description, amount_cents, type, date, payment_method_id, recurring_expense_id, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);`
_, err := tx.Exec(queryExpense, expense.ID, expense.SpaceID, expense.CreatedBy, expense.Description, expense.AmountCents, expense.Type, expense.Date, expense.PaymentMethodID, expense.RecurringExpenseID, expense.CreatedAt, expense.UpdatedAt)
if err != nil {
return err
}
// Insert Expense
queryExpense := `INSERT INTO expenses (id, space_id, created_by, description, amount_cents, type, date, payment_method_id, recurring_expense_id, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);`
_, err = tx.Exec(queryExpense, expense.ID, expense.SpaceID, expense.CreatedBy, expense.Description, expense.AmountCents, expense.Type, expense.Date, expense.PaymentMethodID, expense.RecurringExpenseID, expense.CreatedAt, expense.UpdatedAt)
if err != nil {
return err
}
// Insert Tags
if len(tagIDs) > 0 {
queryTags := `INSERT INTO expense_tags (expense_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
_, err := tx.Exec(queryTags, expense.ID, tagID)
if err != nil {
return err
if len(tagIDs) > 0 {
queryTags := `INSERT INTO expense_tags (expense_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(queryTags, expense.ID, tagID); err != nil {
return err
}
}
}
}
// Insert Items
if len(itemIDs) > 0 {
queryItems := `INSERT INTO expense_items (expense_id, item_id) VALUES ($1, $2);`
for _, itemID := range itemIDs {
_, err := tx.Exec(queryItems, expense.ID, itemID)
if err != nil {
return err
if len(itemIDs) > 0 {
queryItems := `INSERT INTO expense_items (expense_id, item_id) VALUES ($1, $2);`
for _, itemID := range itemIDs {
if _, err := tx.Exec(queryItems, expense.ID, itemID); err != nil {
return err
}
}
}
}
return tx.Commit()
return nil
})
}
func (r *expenseRepository) GetByID(id string) (*model.Expense, error) {
@ -223,38 +214,38 @@ func (r *expenseRepository) GetPaymentMethodsByExpenseIDs(expenseIDs []string) (
}
func (r *expenseRepository) Update(expense *model.Expense, tagIDs []string) error {
tx, err := r.db.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
return WithTx(r.db, func(tx *sqlx.Tx) error {
query := `UPDATE expenses SET description = $1, amount_cents = $2, type = $3, date = $4, payment_method_id = $5, updated_at = $6 WHERE id = $7;`
if _, err := tx.Exec(query, expense.Description, expense.AmountCents, expense.Type, expense.Date, expense.PaymentMethodID, expense.UpdatedAt, expense.ID); err != nil {
return err
}
query := `UPDATE expenses SET description = $1, amount_cents = $2, type = $3, date = $4, payment_method_id = $5, updated_at = $6 WHERE id = $7;`
_, err = tx.Exec(query, expense.Description, expense.AmountCents, expense.Type, expense.Date, expense.PaymentMethodID, expense.UpdatedAt, expense.ID)
if err != nil {
return err
}
if _, err := tx.Exec(`DELETE FROM expense_tags WHERE expense_id = $1;`, expense.ID); err != nil {
return err
}
// Replace tags: delete all existing, re-insert
_, err = tx.Exec(`DELETE FROM expense_tags WHERE expense_id = $1;`, expense.ID)
if err != nil {
return err
}
if len(tagIDs) > 0 {
insertTag := `INSERT INTO expense_tags (expense_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(insertTag, expense.ID, tagID); err != nil {
return err
if len(tagIDs) > 0 {
insertTag := `INSERT INTO expense_tags (expense_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(insertTag, expense.ID, tagID); err != nil {
return err
}
}
}
}
return tx.Commit()
return nil
})
}
func (r *expenseRepository) Delete(id string) error {
_, err := r.db.Exec(`DELETE FROM expenses WHERE id = $1;`, id)
result, err := r.db.Exec(`DELETE FROM expenses WHERE id = $1;`, id)
if err != nil {
return err
}
rows, err := result.RowsAffected()
if err == nil && rows == 0 {
return ErrExpenseNotFound
}
return err
}

View file

@ -0,0 +1,18 @@
package repository
import "github.com/jmoiron/sqlx"
// WithTx runs fn inside a transaction. If fn returns an error, the transaction
// is rolled back; otherwise it is committed.
func WithTx(db *sqlx.DB, fn func(*sqlx.Tx) error) error {
tx, err := db.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
if err := fn(tx); err != nil {
return err
}
return tx.Commit()
}

View file

@ -1,105 +0,0 @@
package repository
import (
"database/sql"
"errors"
"time"
"git.juancwu.dev/juancwu/budgit/internal/model"
"github.com/jmoiron/sqlx"
)
var (
ErrRecurringDepositNotFound = errors.New("recurring deposit not found")
)
type RecurringDepositRepository interface {
Create(rd *model.RecurringDeposit) error
GetByID(id string) (*model.RecurringDeposit, error)
GetBySpaceID(spaceID string) ([]*model.RecurringDeposit, error)
Update(rd *model.RecurringDeposit) error
Delete(id string) error
SetActive(id string, active bool) error
GetDueRecurrences(now time.Time) ([]*model.RecurringDeposit, error)
GetDueRecurrencesForSpace(spaceID string, now time.Time) ([]*model.RecurringDeposit, error)
UpdateNextOccurrence(id string, next time.Time) error
Deactivate(id string) error
}
type recurringDepositRepository struct {
db *sqlx.DB
}
func NewRecurringDepositRepository(db *sqlx.DB) RecurringDepositRepository {
return &recurringDepositRepository{db: db}
}
func (r *recurringDepositRepository) Create(rd *model.RecurringDeposit) error {
query := `INSERT INTO recurring_deposits (id, space_id, account_id, amount_cents, frequency, start_date, end_date, next_occurrence, is_active, title, created_by, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);`
_, err := r.db.Exec(query, rd.ID, rd.SpaceID, rd.AccountID, rd.AmountCents, rd.Frequency, rd.StartDate, rd.EndDate, rd.NextOccurrence, rd.IsActive, rd.Title, rd.CreatedBy, rd.CreatedAt, rd.UpdatedAt)
return err
}
func (r *recurringDepositRepository) GetByID(id string) (*model.RecurringDeposit, error) {
rd := &model.RecurringDeposit{}
query := `SELECT * FROM recurring_deposits WHERE id = $1;`
err := r.db.Get(rd, query, id)
if err == sql.ErrNoRows {
return nil, ErrRecurringDepositNotFound
}
return rd, err
}
func (r *recurringDepositRepository) GetBySpaceID(spaceID string) ([]*model.RecurringDeposit, error) {
var results []*model.RecurringDeposit
query := `SELECT * FROM recurring_deposits WHERE space_id = $1 ORDER BY is_active DESC, next_occurrence ASC;`
err := r.db.Select(&results, query, spaceID)
return results, err
}
func (r *recurringDepositRepository) Update(rd *model.RecurringDeposit) error {
query := `UPDATE recurring_deposits SET account_id = $1, amount_cents = $2, frequency = $3, start_date = $4, end_date = $5, next_occurrence = $6, title = $7, updated_at = $8 WHERE id = $9;`
result, err := r.db.Exec(query, rd.AccountID, rd.AmountCents, rd.Frequency, rd.StartDate, rd.EndDate, rd.NextOccurrence, rd.Title, rd.UpdatedAt, rd.ID)
if err != nil {
return err
}
rows, err := result.RowsAffected()
if err == nil && rows == 0 {
return ErrRecurringDepositNotFound
}
return err
}
func (r *recurringDepositRepository) Delete(id string) error {
_, err := r.db.Exec(`DELETE FROM recurring_deposits WHERE id = $1;`, id)
return err
}
func (r *recurringDepositRepository) SetActive(id string, active bool) error {
_, err := r.db.Exec(`UPDATE recurring_deposits SET is_active = $1, updated_at = $2 WHERE id = $3;`, active, time.Now(), id)
return err
}
func (r *recurringDepositRepository) GetDueRecurrences(now time.Time) ([]*model.RecurringDeposit, error) {
var results []*model.RecurringDeposit
query := `SELECT * FROM recurring_deposits WHERE is_active = true AND next_occurrence <= $1;`
err := r.db.Select(&results, query, now)
return results, err
}
func (r *recurringDepositRepository) GetDueRecurrencesForSpace(spaceID string, now time.Time) ([]*model.RecurringDeposit, error) {
var results []*model.RecurringDeposit
query := `SELECT * FROM recurring_deposits WHERE is_active = true AND space_id = $1 AND next_occurrence <= $2;`
err := r.db.Select(&results, query, spaceID, now)
return results, err
}
func (r *recurringDepositRepository) UpdateNextOccurrence(id string, next time.Time) error {
_, err := r.db.Exec(`UPDATE recurring_deposits SET next_occurrence = $1, updated_at = $2 WHERE id = $3;`, next, time.Now(), id)
return err
}
func (r *recurringDepositRepository) Deactivate(id string) error {
return r.SetActive(id, false)
}

View file

@ -37,29 +37,24 @@ func NewRecurringExpenseRepository(db *sqlx.DB) RecurringExpenseRepository {
}
func (r *recurringExpenseRepository) Create(re *model.RecurringExpense, tagIDs []string) error {
tx, err := r.db.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
return WithTx(r.db, func(tx *sqlx.Tx) error {
query := `INSERT INTO recurring_expenses (id, space_id, created_by, description, amount_cents, type, payment_method_id, frequency, start_date, end_date, next_occurrence, is_active, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);`
if _, err := tx.Exec(query, re.ID, re.SpaceID, re.CreatedBy, re.Description, re.AmountCents, re.Type, re.PaymentMethodID, re.Frequency, re.StartDate, re.EndDate, re.NextOccurrence, re.IsActive, re.CreatedAt, re.UpdatedAt); err != nil {
return err
}
query := `INSERT INTO recurring_expenses (id, space_id, created_by, description, amount_cents, type, payment_method_id, frequency, start_date, end_date, next_occurrence, is_active, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);`
_, err = tx.Exec(query, re.ID, re.SpaceID, re.CreatedBy, re.Description, re.AmountCents, re.Type, re.PaymentMethodID, re.Frequency, re.StartDate, re.EndDate, re.NextOccurrence, re.IsActive, re.CreatedAt, re.UpdatedAt)
if err != nil {
return err
}
if len(tagIDs) > 0 {
tagQuery := `INSERT INTO recurring_expense_tags (recurring_expense_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(tagQuery, re.ID, tagID); err != nil {
return err
if len(tagIDs) > 0 {
tagQuery := `INSERT INTO recurring_expense_tags (recurring_expense_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(tagQuery, re.ID, tagID); err != nil {
return err
}
}
}
}
return tx.Commit()
return nil
})
}
func (r *recurringExpenseRepository) GetByID(id string) (*model.RecurringExpense, error) {
@ -165,37 +160,38 @@ func (r *recurringExpenseRepository) GetPaymentMethodsByRecurringExpenseIDs(ids
}
func (r *recurringExpenseRepository) Update(re *model.RecurringExpense, tagIDs []string) error {
tx, err := r.db.Beginx()
if err != nil {
return err
}
defer tx.Rollback()
return WithTx(r.db, func(tx *sqlx.Tx) error {
query := `UPDATE recurring_expenses SET description = $1, amount_cents = $2, type = $3, payment_method_id = $4, frequency = $5, start_date = $6, end_date = $7, next_occurrence = $8, updated_at = $9 WHERE id = $10;`
if _, err := tx.Exec(query, re.Description, re.AmountCents, re.Type, re.PaymentMethodID, re.Frequency, re.StartDate, re.EndDate, re.NextOccurrence, re.UpdatedAt, re.ID); err != nil {
return err
}
query := `UPDATE recurring_expenses SET description = $1, amount_cents = $2, type = $3, payment_method_id = $4, frequency = $5, start_date = $6, end_date = $7, next_occurrence = $8, updated_at = $9 WHERE id = $10;`
_, err = tx.Exec(query, re.Description, re.AmountCents, re.Type, re.PaymentMethodID, re.Frequency, re.StartDate, re.EndDate, re.NextOccurrence, re.UpdatedAt, re.ID)
if err != nil {
return err
}
if _, err := tx.Exec(`DELETE FROM recurring_expense_tags WHERE recurring_expense_id = $1;`, re.ID); err != nil {
return err
}
_, err = tx.Exec(`DELETE FROM recurring_expense_tags WHERE recurring_expense_id = $1;`, re.ID)
if err != nil {
return err
}
if len(tagIDs) > 0 {
tagQuery := `INSERT INTO recurring_expense_tags (recurring_expense_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(tagQuery, re.ID, tagID); err != nil {
return err
if len(tagIDs) > 0 {
tagQuery := `INSERT INTO recurring_expense_tags (recurring_expense_id, tag_id) VALUES ($1, $2);`
for _, tagID := range tagIDs {
if _, err := tx.Exec(tagQuery, re.ID, tagID); err != nil {
return err
}
}
}
}
return tx.Commit()
return nil
})
}
func (r *recurringExpenseRepository) Delete(id string) error {
_, err := r.db.Exec(`DELETE FROM recurring_expenses WHERE id = $1;`, id)
result, err := r.db.Exec(`DELETE FROM recurring_expenses WHERE id = $1;`, id)
if err != nil {
return err
}
rows, err := result.RowsAffected()
if err == nil && rows == 0 {
return ErrRecurringExpenseNotFound
}
return err
}