diff --git a/internal/dashboard/template/default.go b/internal/dashboard/template/default.go
deleted file mode 100644
index 38cdfe4..0000000
--- a/internal/dashboard/template/default.go
+++ /dev/null
@@ -1 +0,0 @@
-package template
diff --git a/internal/default.go b/internal/default.go
index 1d7de57..c3049d2 100644
--- a/internal/default.go
+++ b/internal/default.go
@@ -13,11 +13,9 @@ import (
"spend-sparrow/internal/dashboard"
"spend-sparrow/internal/handler"
"spend-sparrow/internal/handler/middleware"
- "spend-sparrow/internal/log"
- "spend-sparrow/internal/service"
+ "spend-sparrow/internal/transaction"
"spend-sparrow/internal/transaction_recurring"
"spend-sparrow/internal/treasure_chest"
- "spend-sparrow/internal/types"
"sync"
"syscall"
"time"
@@ -31,7 +29,7 @@ func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env fu
ctx, cancel := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM)
defer cancel()
- otelEnabled := types.IsOtelEnabled(env)
+ otelEnabled := core.IsOtelEnabled(env)
if otelEnabled {
// use context.Background(), otherwise the shutdown can't be called, as the context is already cancelled
otelShutdown, err := setupOTelSDK(context.Background())
@@ -48,13 +46,13 @@ func Run(ctx context.Context, database *sqlx.DB, migrationsPrefix string, env fu
cancel()
}()
- slog.SetDefault(log.NewLogPropagator())
+ slog.SetDefault(core.NewLogPropagator())
}
slog.InfoContext(ctx, "Starting server...")
// init server settings
- serverSettings, err := types.NewSettingsFromEnv(ctx, env)
+ serverSettings, err := core.NewSettingsFromEnv(ctx, env)
if err != nil {
return err
}
@@ -107,7 +105,7 @@ func shutdownServer(ctx context.Context, s *http.Server, wg *sync.WaitGroup) {
}
}
-func createHandlerWithServices(ctx context.Context, d *sqlx.DB, serverSettings *types.Settings) http.Handler {
+func createHandlerWithServices(ctx context.Context, d *sqlx.DB, serverSettings *core.Settings) http.Handler {
var router = http.NewServeMux()
authDb := authentication.NewDbSqlite(d)
@@ -119,8 +117,8 @@ func createHandlerWithServices(ctx context.Context, d *sqlx.DB, serverSettings *
authService := authentication.NewService(authDb, randomService, clockService, mailService, serverSettings)
accountService := account.NewServiceImpl(d, randomService, clockService)
treasureChestService := treasure_chest.NewService(d, randomService, clockService)
- transactionService := service.NewTransaction(d, randomService, clockService)
- transactionRecurringService := transaction_recurring.NewService(d, randomService, clockService, transactionService)
+ transactionService := transaction.NewService(d, randomService, clockService)
+ transactionRecurringService := transaction_recurring.NewService(d, randomService, clockService)
dashboardService := dashboard.NewService(d)
render := core.NewRender()
@@ -129,10 +127,10 @@ func createHandlerWithServices(ctx context.Context, d *sqlx.DB, serverSettings *
authHandler := authentication.NewHandler(authService, render)
accountHandler := account.NewHandler(accountService, render)
treasureChestHandler := treasure_chest.NewHandler(treasureChestService, transactionRecurringService, render)
- transactionHandler := handler.NewTransaction(transactionService, accountService, treasureChestService, render)
+ transactionHandler := transaction.NewHandler(transactionService, accountService, treasureChestService, render)
transactionRecurringHandler := transaction_recurring.NewHandler(transactionRecurringService, render)
- go dailyTaskTimer(ctx, transactionRecurringService, authService)
+ go dailyTaskTimer(ctx, transactionService, authService)
indexHandler.Handle(router)
dashboardHandler.Handle(router)
@@ -160,8 +158,8 @@ func createHandlerWithServices(ctx context.Context, d *sqlx.DB, serverSettings *
return wrapper
}
-func dailyTaskTimer(ctx context.Context, transactionRecurring transaction_recurring.Service, auth authentication.Service) {
- runDailyTasks(ctx, transactionRecurring, auth)
+func dailyTaskTimer(ctx context.Context, transaction transaction.Service, auth authentication.Service) {
+ runDailyTasks(ctx, transaction, auth)
ticker := time.NewTicker(24 * time.Hour)
defer ticker.Stop()
@@ -170,13 +168,13 @@ func dailyTaskTimer(ctx context.Context, transactionRecurring transaction_recurr
case <-ctx.Done():
return
case <-ticker.C:
- runDailyTasks(ctx, transactionRecurring, auth)
+ runDailyTasks(ctx, transaction, auth)
}
}
}
-func runDailyTasks(ctx context.Context, transactionRecurring transaction_recurring.Service, auth authentication.Service) {
+func runDailyTasks(ctx context.Context, transaction transaction.Service, auth authentication.Service) {
slog.InfoContext(ctx, "Running daily tasks")
- _ = transactionRecurring.GenerateTransactions(ctx)
+ _ = transaction.GenerateRecurringTransactions(ctx)
_ = auth.CleanupSessionsAndTokens(ctx)
}
diff --git a/internal/handler/middleware/cross_site_request_forgery.go b/internal/handler/middleware/cross_site_request_forgery.go
index 407ab2c..91e4cb0 100644
--- a/internal/handler/middleware/cross_site_request_forgery.go
+++ b/internal/handler/middleware/cross_site_request_forgery.go
@@ -5,7 +5,6 @@ import (
"net/http"
"spend-sparrow/internal/authentication"
"spend-sparrow/internal/core"
- "spend-sparrow/internal/utils"
"strings"
)
@@ -52,7 +51,7 @@ func CrossSiteRequestForgery(auth authentication.Service) func(http.Handler) htt
if session == nil || csrfToken == "" || !auth.IsCsrfTokenValid(ctx, csrfToken, session.Id) {
slog.InfoContext(ctx, "CSRF-Token not correct", "token", csrfToken)
if r.Header.Get("Hx-Request") == "true" {
- utils.TriggerToastWithStatus(ctx, w, r, "error", "CSRF-Token not correct", http.StatusBadRequest)
+ core.TriggerToastWithStatus(ctx, w, r, "error", "CSRF-Token not correct", http.StatusBadRequest)
} else {
http.Error(w, "CSRF-Token not correct", http.StatusBadRequest)
}
@@ -63,7 +62,7 @@ func CrossSiteRequestForgery(auth authentication.Service) func(http.Handler) htt
token, err := auth.GetCsrfToken(ctx, session)
if err != nil {
if r.Header.Get("Hx-Request") == "true" {
- utils.TriggerToastWithStatus(ctx, w, r, "error", "Could not generate CSRF Token", http.StatusBadRequest)
+ core.TriggerToastWithStatus(ctx, w, r, "error", "Could not generate CSRF Token", http.StatusBadRequest)
} else {
http.Error(w, "Could not generate CSRF Token", http.StatusBadRequest)
}
diff --git a/internal/handler/middleware/security_headers.go b/internal/handler/middleware/security_headers.go
index 4ea4c59..699f710 100644
--- a/internal/handler/middleware/security_headers.go
+++ b/internal/handler/middleware/security_headers.go
@@ -2,10 +2,10 @@ package middleware
import (
"net/http"
- "spend-sparrow/internal/types"
+ "spend-sparrow/internal/core"
)
-func SecurityHeaders(serverSettings *types.Settings) func(http.Handler) http.Handler {
+func SecurityHeaders(serverSettings *core.Settings) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
diff --git a/internal/handler/root_and_404.go b/internal/handler/root_and_404.go
index e09b6e4..1a8958c 100644
--- a/internal/handler/root_and_404.go
+++ b/internal/handler/root_and_404.go
@@ -4,7 +4,6 @@ import (
"net/http"
"spend-sparrow/internal/core"
"spend-sparrow/internal/template"
- "spend-sparrow/internal/utils"
"github.com/a-h/templ"
)
@@ -36,7 +35,7 @@ func (handler IndexImpl) handleRootAnd404() http.HandlerFunc {
user := core.GetUser(r)
- htmx := utils.IsHtmx(r)
+ htmx := core.IsHtmx(r)
var comp templ.Component
@@ -46,7 +45,7 @@ func (handler IndexImpl) handleRootAnd404() http.HandlerFunc {
status = http.StatusNotFound
} else {
if user != nil {
- utils.DoRedirect(w, r, "/dashboard")
+ core.DoRedirect(w, r, "/dashboard")
return
} else {
comp = template.Index()
diff --git a/internal/template/transaction/default.go b/internal/template/transaction/default.go
deleted file mode 100644
index 0619207..0000000
--- a/internal/template/transaction/default.go
+++ /dev/null
@@ -1 +0,0 @@
-package transaction
diff --git a/internal/handler/transaction.go b/internal/transaction/handler.go
similarity index 76%
rename from internal/handler/transaction.go
rename to internal/transaction/handler.go
index 3c351b6..678bce9 100644
--- a/internal/handler/transaction.go
+++ b/internal/transaction/handler.go
@@ -1,4 +1,4 @@
-package handler
+package transaction
import (
"fmt"
@@ -6,12 +6,8 @@ import (
"net/http"
"spend-sparrow/internal/account"
"spend-sparrow/internal/core"
- "spend-sparrow/internal/service"
- t "spend-sparrow/internal/template/transaction"
"spend-sparrow/internal/treasure_chest"
"spend-sparrow/internal/treasure_chest_types"
- "spend-sparrow/internal/types"
- "spend-sparrow/internal/utils"
"strconv"
"time"
@@ -23,19 +19,19 @@ const (
DECIMALS_MULTIPLIER = 100
)
-type Transaction interface {
+type Handler interface {
Handle(router *http.ServeMux)
}
-type TransactionImpl struct {
- s service.Transaction
+type HandlerImpl struct {
+ s Service
account account.Service
treasureChest treasure_chest.Service
r *core.Render
}
-func NewTransaction(s service.Transaction, account account.Service, treasureChest treasure_chest.Service, r *core.Render) Transaction {
- return TransactionImpl{
+func NewHandler(s Service, account account.Service, treasureChest treasure_chest.Service, r *core.Render) Handler {
+ return HandlerImpl{
s: s,
account: account,
treasureChest: treasureChest,
@@ -43,7 +39,7 @@ func NewTransaction(s service.Transaction, account account.Service, treasureChes
}
}
-func (h TransactionImpl) Handle(r *http.ServeMux) {
+func (h HandlerImpl) Handle(r *http.ServeMux) {
r.Handle("GET /transaction", h.handleTransactionPage())
r.Handle("GET /transaction/{id}", h.handleTransactionItemComp())
r.Handle("POST /transaction/{id}", h.handleUpdateTransaction())
@@ -51,17 +47,17 @@ func (h TransactionImpl) Handle(r *http.ServeMux) {
r.Handle("DELETE /transaction/{id}", h.handleDeleteTransaction())
}
-func (h TransactionImpl) handleTransactionPage() http.HandlerFunc {
+func (h HandlerImpl) handleTransactionPage() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
core.UpdateSpan(r)
user := core.GetUser(r)
if user == nil {
- utils.DoRedirect(w, r, "/auth/signin")
+ core.DoRedirect(w, r, "/auth/signin")
return
}
- filter := types.TransactionItemsFilter{
+ filter := TransactionItemsFilter{
AccountId: r.URL.Query().Get("account-id"),
TreasureChestId: r.URL.Query().Get("treasure-chest-id"),
Error: r.URL.Query().Get("error"),
@@ -88,23 +84,23 @@ func (h TransactionImpl) handleTransactionPage() http.HandlerFunc {
accountMap, treasureChestMap := h.getTransactionData(accounts, treasureChests)
- items := t.TransactionItems(transactions, accountMap, treasureChestMap)
- if utils.IsHtmx(r) {
+ items := TransactionItems(transactions, accountMap, treasureChestMap)
+ if core.IsHtmx(r) {
h.r.Render(r, w, items)
} else {
- comp := t.Transaction(items, filter, accounts, treasureChests)
+ comp := TransactionComp(items, filter, accounts, treasureChests)
h.r.RenderLayout(r, w, comp, user)
}
}
}
-func (h TransactionImpl) handleTransactionItemComp() http.HandlerFunc {
+func (h HandlerImpl) handleTransactionItemComp() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
core.UpdateSpan(r)
user := core.GetUser(r)
if user == nil {
- utils.DoRedirect(w, r, "/auth/signin")
+ core.DoRedirect(w, r, "/auth/signin")
return
}
@@ -122,7 +118,7 @@ func (h TransactionImpl) handleTransactionItemComp() http.HandlerFunc {
id := r.PathValue("id")
if id == "new" {
- comp := t.EditTransaction(nil, accounts, treasureChests)
+ comp := EditTransaction(nil, accounts, treasureChests)
h.r.Render(r, w, comp)
return
}
@@ -135,22 +131,22 @@ func (h TransactionImpl) handleTransactionItemComp() http.HandlerFunc {
var comp templ.Component
if r.URL.Query().Get("edit") == "true" {
- comp = t.EditTransaction(transaction, accounts, treasureChests)
+ comp = EditTransaction(transaction, accounts, treasureChests)
} else {
accountMap, treasureChestMap := h.getTransactionData(accounts, treasureChests)
- comp = t.TransactionItem(transaction, accountMap, treasureChestMap)
+ comp = TransactionItem(transaction, accountMap, treasureChestMap)
}
h.r.Render(r, w, comp)
}
}
-func (h TransactionImpl) handleUpdateTransaction() http.HandlerFunc {
+func (h HandlerImpl) handleUpdateTransaction() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
core.UpdateSpan(r)
user := core.GetUser(r)
if user == nil {
- utils.DoRedirect(w, r, "/auth/signin")
+ core.DoRedirect(w, r, "/auth/signin")
return
}
@@ -203,7 +199,7 @@ func (h TransactionImpl) handleUpdateTransaction() http.HandlerFunc {
return
}
- input := types.Transaction{
+ input := Transaction{
Id: id,
AccountId: accountId,
TreasureChestId: treasureChestId,
@@ -213,7 +209,7 @@ func (h TransactionImpl) handleUpdateTransaction() http.HandlerFunc {
Description: r.FormValue("description"),
}
- var transaction *types.Transaction
+ var transaction *Transaction
if idStr == "new" {
transaction, err = h.s.Add(r.Context(), nil, user, input)
if err != nil {
@@ -241,18 +237,18 @@ func (h TransactionImpl) handleUpdateTransaction() http.HandlerFunc {
}
accountMap, treasureChestMap := h.getTransactionData(accounts, treasureChests)
- comp := t.TransactionItem(transaction, accountMap, treasureChestMap)
+ comp := TransactionItem(transaction, accountMap, treasureChestMap)
h.r.Render(r, w, comp)
}
}
-func (h TransactionImpl) handleRecalculate() http.HandlerFunc {
+func (h HandlerImpl) handleRecalculate() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
core.UpdateSpan(r)
user := core.GetUser(r)
if user == nil {
- utils.DoRedirect(w, r, "/auth/signin")
+ core.DoRedirect(w, r, "/auth/signin")
return
}
@@ -262,17 +258,17 @@ func (h TransactionImpl) handleRecalculate() http.HandlerFunc {
return
}
- utils.TriggerToastWithStatus(r.Context(), w, r, "success", "Balances recalculated, please refresh", http.StatusOK)
+ core.TriggerToastWithStatus(r.Context(), w, r, "success", "Balances recalculated, please refresh", http.StatusOK)
}
}
-func (h TransactionImpl) handleDeleteTransaction() http.HandlerFunc {
+func (h HandlerImpl) handleDeleteTransaction() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
core.UpdateSpan(r)
user := core.GetUser(r)
if user == nil {
- utils.DoRedirect(w, r, "/auth/signin")
+ core.DoRedirect(w, r, "/auth/signin")
return
}
@@ -286,7 +282,7 @@ func (h TransactionImpl) handleDeleteTransaction() http.HandlerFunc {
}
}
-func (h TransactionImpl) getTransactionData(accounts []*account.Account, treasureChests []*treasure_chest_types.TreasureChest) (map[uuid.UUID]string, map[uuid.UUID]string) {
+func (h HandlerImpl) getTransactionData(accounts []*account.Account, treasureChests []*treasure_chest_types.TreasureChest) (map[uuid.UUID]string, map[uuid.UUID]string) {
accountMap := make(map[uuid.UUID]string, 0)
for _, account := range accounts {
accountMap[account.Id] = account.Name
diff --git a/internal/service/transaction.go b/internal/transaction/service.go
similarity index 80%
rename from internal/service/transaction.go
rename to internal/transaction/service.go
index ecf7784..123b5b7 100644
--- a/internal/service/transaction.go
+++ b/internal/transaction/service.go
@@ -1,4 +1,4 @@
-package service
+package transaction
import (
"context"
@@ -7,8 +7,8 @@ import (
"log/slog"
"spend-sparrow/internal/auth_types"
"spend-sparrow/internal/core"
+ "spend-sparrow/internal/transaction_recurring"
"spend-sparrow/internal/treasure_chest_types"
- "spend-sparrow/internal/types"
"strconv"
"time"
@@ -18,31 +18,32 @@ import (
const page_size = 25
-type Transaction interface {
- Add(ctx context.Context, tx *sqlx.Tx, user *auth_types.User, transaction types.Transaction) (*types.Transaction, error)
- Update(ctx context.Context, user *auth_types.User, transaction types.Transaction) (*types.Transaction, error)
- Get(ctx context.Context, user *auth_types.User, id string) (*types.Transaction, error)
- GetAll(ctx context.Context, user *auth_types.User, filter types.TransactionItemsFilter) ([]*types.Transaction, error)
+type Service interface {
+ Add(ctx context.Context, tx *sqlx.Tx, user *auth_types.User, transaction Transaction) (*Transaction, error)
+ Update(ctx context.Context, user *auth_types.User, transaction Transaction) (*Transaction, error)
+ Get(ctx context.Context, user *auth_types.User, id string) (*Transaction, error)
+ GetAll(ctx context.Context, user *auth_types.User, filter TransactionItemsFilter) ([]*Transaction, error)
Delete(ctx context.Context, user *auth_types.User, id string) error
RecalculateBalances(ctx context.Context, user *auth_types.User) error
+ GenerateRecurringTransactions(ctx context.Context) error
}
-type TransactionImpl struct {
+type ServiceImpl struct {
db *sqlx.DB
clock core.Clock
random core.Random
}
-func NewTransaction(db *sqlx.DB, random core.Random, clock core.Clock) Transaction {
- return TransactionImpl{
+func NewService(db *sqlx.DB, random core.Random, clock core.Clock) Service {
+ return ServiceImpl{
db: db,
clock: clock,
random: random,
}
}
-func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *auth_types.User, transactionInput types.Transaction) (*types.Transaction, error) {
+func (s ServiceImpl) Add(ctx context.Context, tx *sqlx.Tx, user *auth_types.User, transactionInput Transaction) (*Transaction, error) {
if user == nil {
return nil, core.ErrUnauthorized
}
@@ -109,7 +110,7 @@ func (s TransactionImpl) Add(ctx context.Context, tx *sqlx.Tx, user *auth_types.
return transaction, nil
}
-func (s TransactionImpl) Update(ctx context.Context, user *auth_types.User, input types.Transaction) (*types.Transaction, error) {
+func (s ServiceImpl) Update(ctx context.Context, user *auth_types.User, input Transaction) (*Transaction, error) {
if user == nil {
return nil, core.ErrUnauthorized
}
@@ -123,7 +124,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *auth_types.User, inpu
_ = tx.Rollback()
}()
- transaction := &types.Transaction{}
+ transaction := &Transaction{}
err = tx.GetContext(ctx, transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, input.Id)
err = core.TransformAndLogDbError(ctx, "transaction Update", nil, err)
if err != nil {
@@ -208,7 +209,7 @@ func (s TransactionImpl) Update(ctx context.Context, user *auth_types.User, inpu
return transaction, nil
}
-func (s TransactionImpl) Get(ctx context.Context, user *auth_types.User, id string) (*types.Transaction, error) {
+func (s ServiceImpl) Get(ctx context.Context, user *auth_types.User, id string) (*Transaction, error) {
if user == nil {
return nil, core.ErrUnauthorized
}
@@ -218,7 +219,7 @@ func (s TransactionImpl) Get(ctx context.Context, user *auth_types.User, id stri
return nil, fmt.Errorf("could not parse Id: %w", core.ErrBadRequest)
}
- var transaction types.Transaction
+ var transaction Transaction
err = s.db.GetContext(ctx, &transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = core.TransformAndLogDbError(ctx, "transaction Get", nil, err)
if err != nil {
@@ -231,7 +232,7 @@ func (s TransactionImpl) Get(ctx context.Context, user *auth_types.User, id stri
return &transaction, nil
}
-func (s TransactionImpl) GetAll(ctx context.Context, user *auth_types.User, filter types.TransactionItemsFilter) ([]*types.Transaction, error) {
+func (s ServiceImpl) GetAll(ctx context.Context, user *auth_types.User, filter TransactionItemsFilter) ([]*Transaction, error) {
if user == nil {
return nil, core.ErrUnauthorized
}
@@ -251,7 +252,7 @@ func (s TransactionImpl) GetAll(ctx context.Context, user *auth_types.User, filt
}
}
- transactions := make([]*types.Transaction, 0)
+ transactions := make([]*Transaction, 0)
err = s.db.SelectContext(ctx, &transactions, `
SELECT *
FROM "transaction"
@@ -279,7 +280,7 @@ func (s TransactionImpl) GetAll(ctx context.Context, user *auth_types.User, filt
return transactions, nil
}
-func (s TransactionImpl) Delete(ctx context.Context, user *auth_types.User, id string) error {
+func (s ServiceImpl) Delete(ctx context.Context, user *auth_types.User, id string) error {
if user == nil {
return core.ErrUnauthorized
}
@@ -298,7 +299,7 @@ func (s TransactionImpl) Delete(ctx context.Context, user *auth_types.User, id s
_ = tx.Rollback()
}()
- var transaction types.Transaction
+ var transaction Transaction
err = tx.GetContext(ctx, &transaction, `SELECT * FROM "transaction" WHERE user_id = ? AND id = ?`, user.Id, uuid)
err = core.TransformAndLogDbError(ctx, "transaction Delete", nil, err)
if err != nil {
@@ -344,7 +345,7 @@ func (s TransactionImpl) Delete(ctx context.Context, user *auth_types.User, id s
return nil
}
-func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *auth_types.User) error {
+func (s ServiceImpl) RecalculateBalances(ctx context.Context, user *auth_types.User) error {
if user == nil {
return core.ErrUnauthorized
}
@@ -391,7 +392,7 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *auth_typ
}
}()
- var transaction types.Transaction
+ var transaction Transaction
for rows.Next() {
err = rows.StructScan(&transaction)
err = core.TransformAndLogDbError(ctx, "transaction RecalculateBalances", nil, err)
@@ -445,7 +446,63 @@ func (s TransactionImpl) RecalculateBalances(ctx context.Context, user *auth_typ
return nil
}
-func (s TransactionImpl) validateAndEnrichTransaction(ctx context.Context, tx *sqlx.Tx, oldTransaction *types.Transaction, userId uuid.UUID, input types.Transaction) (*types.Transaction, error) {
+func (s ServiceImpl) GenerateRecurringTransactions(ctx context.Context) error {
+ now := s.clock.Now()
+
+ tx, err := s.db.BeginTxx(ctx, nil)
+ err = core.TransformAndLogDbError(ctx, "transaction GenerateRecurringTransactions", nil, err)
+ if err != nil {
+ return err
+ }
+ defer func() {
+ _ = tx.Rollback()
+ }()
+
+ recurringTransactions := make([]*transaction_recurring.TransactionRecurring, 0)
+ err = tx.SelectContext(ctx, &recurringTransactions, `
+ SELECT * FROM transaction_recurring WHERE next_execution <= ?`,
+ now)
+ err = core.TransformAndLogDbError(ctx, "transaction GenerateRecurringTransactions", nil, err)
+ if err != nil {
+ return err
+ }
+
+ for _, transactionRecurring := range recurringTransactions {
+ user := &auth_types.User{
+ Id: transactionRecurring.UserId,
+ }
+ transaction := Transaction{
+ Timestamp: *transactionRecurring.NextExecution,
+ Party: transactionRecurring.Party,
+ Description: transactionRecurring.Description,
+
+ TreasureChestId: transactionRecurring.TreasureChestId,
+ Value: transactionRecurring.Value,
+ }
+
+ _, err = s.Add(ctx, tx, user, transaction)
+ if err != nil {
+ return err
+ }
+
+ nextExecution := transactionRecurring.NextExecution.AddDate(0, int(transactionRecurring.IntervalMonths), 0)
+ r, err := tx.ExecContext(ctx, `UPDATE transaction_recurring SET next_execution = ? WHERE id = ? AND user_id = ?`,
+ nextExecution, transactionRecurring.Id, user.Id)
+ err = core.TransformAndLogDbError(ctx, "transaction GenerateRecurringTransactions", r, err)
+ if err != nil {
+ return err
+ }
+ }
+
+ err = tx.Commit()
+ err = core.TransformAndLogDbError(ctx, "transaction GenerateRecurringTransactions", nil, err)
+ if err != nil {
+ return err
+ }
+ return nil
+}
+
+func (s ServiceImpl) validateAndEnrichTransaction(ctx context.Context, tx *sqlx.Tx, oldTransaction *Transaction, userId uuid.UUID, input Transaction) (*Transaction, error) {
var (
id uuid.UUID
createdAt time.Time
@@ -513,7 +570,7 @@ func (s TransactionImpl) validateAndEnrichTransaction(ctx context.Context, tx *s
}
}
- transaction := types.Transaction{
+ transaction := Transaction{
Id: id,
UserId: userId,
@@ -536,7 +593,7 @@ func (s TransactionImpl) validateAndEnrichTransaction(ctx context.Context, tx *s
return &transaction, nil
}
-func (s TransactionImpl) updateErrors(t *types.Transaction) {
+func (s ServiceImpl) updateErrors(t *Transaction) {
errorStr := ""
switch {
diff --git a/internal/template/transaction/transaction.templ b/internal/transaction/template.templ
similarity index 92%
rename from internal/template/transaction/transaction.templ
rename to internal/transaction/template.templ
index a5a1a34..2daeae7 100644
--- a/internal/template/transaction/transaction.templ
+++ b/internal/transaction/template.templ
@@ -4,13 +4,13 @@ import (
"fmt"
"github.com/google/uuid"
"spend-sparrow/internal/account"
+ "spend-sparrow/internal/core"
"spend-sparrow/internal/template/svg"
"spend-sparrow/internal/treasure_chest_types"
- "spend-sparrow/internal/types"
"time"
)
-templ Transaction(items templ.Component, filter types.TransactionItemsFilter, accounts []*account.Account, treasureChests []*treasure_chest_types.TreasureChest) {
+templ TransactionComp(items templ.Component, filter TransactionItemsFilter, accounts []*account.Account, treasureChests []*treasure_chest_types.TreasureChest) {
}
-templ TransactionItem(transaction *types.Transaction, accounts, treasureChests map[uuid.UUID]string) {
+templ TransactionItem(transaction *Transaction, accounts, treasureChests map[uuid.UUID]string) {
{{
background := "bg-gray-50"
if transaction.Error != nil {
@@ -276,9 +276,9 @@ templ TransactionItem(transaction *types.Transaction, accounts, treasureChests m
if transaction.Value < 0 {
- { types.FormatEuros(transaction.Value) }
+ { core.FormatEuros(transaction.Value) }
} else {
- { types.FormatEuros(transaction.Value) }
+ { core.FormatEuros(transaction.Value) }
}